mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-14 10:13:33 +02:00
572 lines
21 KiB
Lua
572 lines
21 KiB
Lua
-- The Gen 2 evolution screen: engine/movie/evolution_animation.asm plus the
|
|
-- text beats around it in engine/pokemon/evolve.asm's EvolveAfterBattle.
|
|
--
|
|
-- All of the arithmetic (which species, whether the condition is met, what the
|
|
-- party record becomes, and every frame count below) is
|
|
-- src/core/gen2/Evolution.lua's; this file is the only half that draws, so an
|
|
-- evolution can be asserted end to end with no window.
|
|
--
|
|
-- The animation is NOT a cross fade. The cart loads BOTH frontpics into
|
|
-- vTiles2 -- the new species' 7x7 = 49 tiles at tile $31, the old one's at
|
|
-- tile $00 -- and then adds or subtracts 49 to every tilemap entry of the
|
|
-- 7x7 box at hlcoord 7, 2. That is a hard swap between two pics, once per
|
|
-- WaitBGMap, run in accelerating bursts:
|
|
--
|
|
-- `lb bc, 1, 16` then, per round, `inc b / dec c / dec c`
|
|
-- round 1: hold the old pic 16 frames, then flash 1 time
|
|
-- round 2: hold 14, flash 2 times
|
|
-- ... eight rounds, ending 2 / 8, and each "flash" is new-then-old.
|
|
--
|
|
-- The whole flashing half runs under PREDEFPAL_BLACKOUT (_CGB_Evolution's
|
|
-- `ld c` = TRUE branch), which is what makes both pics read as one silhouette;
|
|
-- the new species' real colours only arrive on the last swap.
|
|
--
|
|
-- Layout: hlcoord 7, 2 with `lb bc, 7, 7` (PrepMonFrontpic), i.e. a 7x7 tile
|
|
-- box at (56, 16). The text box below it is never cleared -- EvolveAfterBattle
|
|
-- clears rows 0..11 only -- so "What? <NICK> is evolving!" stays under the pic
|
|
-- for the whole animation.
|
|
|
|
local Assets = require("src.render.Assets")
|
|
local Chrome = require("src.ui.gen2.Chrome")
|
|
local Evolution = require("src.core.gen2.Evolution")
|
|
local GbcPalette = require("src.render.GbcPalette")
|
|
local Mon = require("src.battle.gen2.Mon")
|
|
local Music = require("src.core.Music")
|
|
local Palettes = require("src.world.gen2.Palettes")
|
|
local Sound = require("src.core.Sound")
|
|
local SpriteAnims = require("src.ui.gen2.SpriteAnims")
|
|
|
|
local EvolutionAnim = {}
|
|
EvolutionAnim.__index = EvolutionAnim
|
|
EvolutionAnim.isOpaque = true
|
|
|
|
local SCREEN_W, SCREEN_H = 160, 144
|
|
|
|
-- PrepMonFrontpic's box: hlcoord 7, 2, `lb bc, 7, 7`.
|
|
local PIC_TILE_X, PIC_TILE_Y, PIC_TILES = 7, 2, 7
|
|
|
|
-- TEXTBOX_Y is SCREEN_HEIGHT - TEXTBOX_HEIGHT and TEXTBOX_INNERY is
|
|
-- TEXTBOX_Y + 2; `line` ($4f) targets the box's absolute second line, which is
|
|
-- two rows below the first.
|
|
local BOX_X, BOX_Y, BOX_W, BOX_H = 0, 12, 20, 6
|
|
local TEXT_X, TEXT_Y, TEXT_LINE = 1, 14, 2
|
|
|
|
-- PromptButton pages: the port holds a prompt for this long and takes A or B
|
|
-- as the press, the same deal src/ui/gen2/BattleState.lua's MESSAGE_FRAMES
|
|
-- gives every other Gold text box.
|
|
local PROMPT_FRAMES = 48
|
|
|
|
-- Paragraph's own `ld c, 20 / call DelayFrames` between clearing the box and
|
|
-- printing the next page.
|
|
local PARAGRAPH_FRAMES = 20
|
|
|
|
-- gfx/sgb/predef.pal PREDEFPAL_BLACKOUT, through the extractor's own 5-bit to
|
|
-- 8-bit scale (floor(v * 255 / 31 + 0.5)).
|
|
local BLACKOUT = {
|
|
{ 255, 255, 255 }, { 58, 58, 58 }, { 16, 25, 25 }, { 0, 0, 0 },
|
|
}
|
|
|
|
-- NUM_SPRITE_ANIM_STRUCTS. InitSpriteAnimStruct refuses once the pool is
|
|
-- full, so the balls of light cap out at ten on screen no matter how many
|
|
-- .GenerateBallOfLight would like to make.
|
|
local BALL_LIMIT = 10
|
|
|
|
--------------------------------------------------------------------------
|
|
-- Construction
|
|
--------------------------------------------------------------------------
|
|
|
|
function EvolutionAnim:wantsFillScale() return true end
|
|
function EvolutionAnim:drawsWidescreen() return true end
|
|
|
|
-- opts:
|
|
-- mon the party record as it stands BEFORE evolving
|
|
-- entry the EvosAttacks row Evolution.check picked
|
|
-- index its party slot, so the new record lands in the right one
|
|
-- party the party table to write back into (defaults to save.party)
|
|
-- save for SetSeenAndCaughtMon
|
|
-- force wForceEvolution: a stone evolution cannot be cancelled with B
|
|
-- onDone(result) result = { canceled, evolved, learned, full }
|
|
function EvolutionAnim.new(game, opts)
|
|
opts = opts or {}
|
|
local self = setmetatable({}, EvolutionAnim)
|
|
self.game = game
|
|
self.data = (game and game.data) or {}
|
|
self.palettes = self.data.gen2Palettes
|
|
self.save = opts.save or (game and game.save)
|
|
self.party = opts.party or (self.save and self.save.party) or {}
|
|
self.index = opts.index or 1
|
|
self.mon = opts.mon or self.party[self.index]
|
|
self.entry = opts.entry
|
|
self.force = opts.force or false
|
|
self.onDone = opts.onDone
|
|
|
|
self.oldSpecies = self.mon and self.mon.species
|
|
self.newSpecies = self.entry and self.entry.into
|
|
-- wStringBuffer2 is filled with GetNickname BEFORE the animation, so every
|
|
-- line below (including "stopped evolving!") names the mon by the name it
|
|
-- had going in.
|
|
self.nick = (self.mon and (self.mon.nickname or self.mon.name))
|
|
or self.oldSpecies or "?"
|
|
self.newName = Evolution.speciesName(self.data, self.newSpecies)
|
|
|
|
self.picCache = {}
|
|
self.rounds = Evolution.flashRounds()
|
|
self.canceled = false
|
|
self.learned = {}
|
|
self.full = {}
|
|
self.balls = {}
|
|
self.ballFrame = 0
|
|
-- Which of the two pics the 7x7 box is showing. The box starts on the OLD
|
|
-- one and every round of flashing ends back on it, which is why a B press
|
|
-- during a hold always leaves the old mon on screen.
|
|
self.showNew = false
|
|
self.blackout = false
|
|
|
|
self:setPhase("evolving")
|
|
return self
|
|
end
|
|
|
|
--------------------------------------------------------------------------
|
|
-- Phases
|
|
--------------------------------------------------------------------------
|
|
|
|
function EvolutionAnim:playCry(species)
|
|
if not species then return end
|
|
local cries = self.data.audio and self.data.audio.cries
|
|
if cries and cries[species] then Sound.playCry(self.data, species) end
|
|
end
|
|
|
|
function EvolutionAnim:playSfx(name)
|
|
local sfx = self.data.audio and self.data.audio.sfx
|
|
if sfx and sfx[Sound.resolve(self.data, name)] then
|
|
Sound.play(self.data, name)
|
|
end
|
|
end
|
|
|
|
-- NOT `enter`. `enter` is the stack's own lifecycle hook -- StateStack:push
|
|
-- calls `state:enter(...)` (src/core/StateStack.lua:18), and Gold runs that
|
|
-- same stack (src/core/Game2.lua:makeStack) -- so a screen that also used
|
|
-- `enter` for its internal state machine had that machine reset the instant
|
|
-- it was pushed:
|
|
-- Screens.push passes no extra arguments, so the hook arrived as `enter(nil)`,
|
|
-- phase became nil and every branch of update() then missed. The screen sat
|
|
-- there decrementing its timer forever with the world still marked busy, which
|
|
-- made a post-battle evolution an unrecoverable hang -- the Gold route bot hit
|
|
-- it the first time its starter reached level 14.
|
|
function EvolutionAnim:setPhase(phase)
|
|
self.phase = phase
|
|
self.timer = 0
|
|
|
|
if phase == "evolving" then
|
|
-- PrintText EvolvingText, then `ld c, 50 / call DelayFrames`. The pics are
|
|
-- not placed yet: on the cart the battle screen is still up behind this
|
|
-- line and ClearBox only wipes rows 0..11 once the delay is over.
|
|
self.lines = { "What? " .. self.nick, "is evolving!" }
|
|
self.timer = Evolution.EVOLVING_FRAMES
|
|
return
|
|
end
|
|
|
|
if phase == "cry" then
|
|
-- PlayMonCry of the OLD species, then MUSIC_EVOLUTION, then 80 frames.
|
|
-- PlayMusic MUSIC_NONE ran first, so nothing else is sounding.
|
|
Music.stop()
|
|
self:playCry(self.oldSpecies)
|
|
local songs = self.data.audio and self.data.audio.songs
|
|
if songs and songs.Music_Evolution then
|
|
Music.play(self.data, "Music_Evolution", true, { reason = "evolution" })
|
|
end
|
|
self.timer = Evolution.MUSIC_FRAMES
|
|
return
|
|
end
|
|
|
|
if phase == "flash" then
|
|
-- GetSGBLayout with c = TRUE: PREDEFPAL_BLACKOUT for the whole burst.
|
|
self.blackout = true
|
|
self.round = 1
|
|
self.step = "wait"
|
|
self.timer = self.rounds[1].wait
|
|
self.swapsLeft = 0
|
|
return
|
|
end
|
|
|
|
if phase == "reveal" then
|
|
-- The final `ld a, 7 * 7 / call .ReplaceFrontpic` commits the new pic, then
|
|
-- wPlayerHPPal takes the new species and GetSGBLayout comes back with
|
|
-- c = FALSE, so the colours arrive on the same beat.
|
|
self.blackout = false
|
|
self.showNew = not self.canceled
|
|
if self.canceled then
|
|
-- .PlayEvolvedSFX returns immediately once wEvolutionCanceled is set:
|
|
-- no SFX_EVOLVED and no balls of light, straight to the cry.
|
|
self:playCry(self.oldSpecies)
|
|
return self:setPhase("stopped")
|
|
end
|
|
self:playSfx("Sfx_Evolved")
|
|
self.timer = Evolution.BALL_SPAWN_FRAMES + Evolution.BALL_TAIL_FRAMES
|
|
return
|
|
end
|
|
|
|
if phase == "stopped" then
|
|
-- CancelEvolution: StoppedEvolvingText over the pic, then ClearTilemap.
|
|
self.lines = { "Huh? " .. self.nick, "stopped evolving!" }
|
|
self.timer = PROMPT_FRAMES
|
|
return
|
|
end
|
|
|
|
if phase == "congrats" then
|
|
self.lines = { "Congratulations!", "Your " .. self.nick }
|
|
self.timer = PROMPT_FRAMES
|
|
return
|
|
end
|
|
|
|
if phase == "paragraph" then
|
|
-- Paragraph clears the box and waits 20 frames before the next page.
|
|
self.lines = nil
|
|
self.timer = PARAGRAPH_FRAMES
|
|
return
|
|
end
|
|
|
|
if phase == "evolved" then
|
|
-- EvolvedIntoText, then MUSIC_NONE / SFX_CAUGHT_MON / WaitSFX and
|
|
-- `ld c, 40 / call DelayFrames`.
|
|
self.lines = { "evolved into", self.newName .. "!" }
|
|
Music.stop()
|
|
self:playSfx("Sfx_CaughtMon")
|
|
self.timer = Evolution.CONGRATS_FRAMES
|
|
return
|
|
end
|
|
|
|
if phase == "learn" then
|
|
-- ClearTilemap, then the party slot is rewritten and LearnLevelMoves runs.
|
|
self:commit()
|
|
self.learnIndex = 0
|
|
return self:nextLearn()
|
|
end
|
|
|
|
if phase == "done" then
|
|
if self.onDone then
|
|
self.onDone({
|
|
canceled = self.canceled,
|
|
evolved = self.evolved,
|
|
learned = self.learned,
|
|
full = self.full,
|
|
})
|
|
end
|
|
return
|
|
end
|
|
end
|
|
|
|
-- The writeback: `pop de / pop hl / ld a, [wTempMonSpecies] / ld [hl], a`
|
|
-- after CalcMonStats has already rebuilt the struct. Everything the record
|
|
-- becomes is Evolution.apply's, which builds it through
|
|
-- src/battle/gen2/Mon.lua so there is still exactly one builder.
|
|
function EvolutionAnim:commit()
|
|
local evolved = Evolution.apply(self.data, self.mon, self.entry)
|
|
if not evolved then return end
|
|
self.evolved = evolved
|
|
self.party[self.index] = evolved
|
|
Evolution.markPokedex(self.save, evolved.species)
|
|
-- LearnLevelMoves at wCurPartyLevel, which the .proceed block set to the
|
|
-- level the mon already had.
|
|
self.pending = Evolution.learnedOnEvolve(self.data, evolved.species,
|
|
evolved.level, evolved)
|
|
end
|
|
|
|
function EvolutionAnim:nextLearn()
|
|
self.learnIndex = (self.learnIndex or 0) + 1
|
|
local moveId = self.pending and self.pending[self.learnIndex]
|
|
if not (moveId and self.evolved) then
|
|
self.lines = nil
|
|
return self:setPhase("done")
|
|
end
|
|
local moveDef = self.data.moves and self.data.moves[moveId]
|
|
local moveName = (moveDef and moveDef.name) or moveId
|
|
local ok, reason = Mon.learnMove(self.evolved, moveId, self.data)
|
|
if ok then
|
|
self.learned[#self.learned + 1] = moveId
|
|
self.lines = { self.nick .. " learned", moveName .. "!" }
|
|
elseif reason == "full" then
|
|
if self.game and self.game.learnMoveOn then
|
|
self.phase = "waitingLearn"
|
|
return self.game:learnMoveOn(self.evolved, moveId, function(learned)
|
|
if learned then
|
|
self.learned[#self.learned + 1] = moveId
|
|
else
|
|
self.full[#self.full + 1] = moveId
|
|
end
|
|
self:nextLearn()
|
|
end)
|
|
end
|
|
self.full[#self.full + 1] = moveId
|
|
self.lines = { self.nick .. " wants to", "learn " .. moveName .. "!" }
|
|
else
|
|
return self:nextLearn()
|
|
end
|
|
self.phase = "learn"
|
|
self.timer = PROMPT_FRAMES
|
|
end
|
|
|
|
--------------------------------------------------------------------------
|
|
-- Update
|
|
--------------------------------------------------------------------------
|
|
|
|
-- .WaitFrames_CheckPressedB reads the joypad every frame of a hold, and
|
|
-- .pressed_b honours it only while wForceEvolution is clear: a stone
|
|
-- evolution cannot be stopped.
|
|
function EvolutionAnim:cancelPressed(input)
|
|
if self.force then return false end
|
|
return input and input:wasPressed("b") or false
|
|
end
|
|
|
|
function EvolutionAnim:update(_dt)
|
|
local input = self.game and self.game.input
|
|
local phase = self.phase
|
|
-- onDone has already fired; the caller pops this state on its own beat.
|
|
if phase == "done" then return end
|
|
|
|
if phase == "flash" then
|
|
return self:updateFlash(input)
|
|
end
|
|
|
|
if phase == "reveal" then
|
|
self:updateBalls()
|
|
self.timer = self.timer - 1
|
|
if self.timer <= 0 then
|
|
-- ClearSpriteAnims, then PlayMonCry of the species the screen is now
|
|
-- showing.
|
|
self:playCry(self.newSpecies)
|
|
self:setPhase("congrats")
|
|
end
|
|
return
|
|
end
|
|
|
|
self.timer = (self.timer or 0) - 1
|
|
local prompt = phase == "stopped" or phase == "congrats" or phase == "learn"
|
|
if prompt and input
|
|
and (input:wasPressed("a") or input:wasPressed("b")) then
|
|
self.timer = 0
|
|
end
|
|
if self.timer > 0 then return end
|
|
|
|
if phase == "evolving" then return self:setPhase("cry") end
|
|
if phase == "cry" then return self:setPhase("flash") end
|
|
if phase == "stopped" then return self:setPhase("done") end
|
|
if phase == "congrats" then return self:setPhase("paragraph") end
|
|
if phase == "paragraph" then return self:setPhase("evolved") end
|
|
if phase == "evolved" then return self:setPhase("learn") end
|
|
if phase == "learn" then return self:nextLearn() end
|
|
end
|
|
|
|
-- One frame of the flash loop. A round is `.WaitFrames_CheckPressedB` for its
|
|
-- own `c` frames and then `.Flash` b times, each flash being two
|
|
-- .ReplaceFrontpic calls (new, then back to old) a WaitBGMap apart.
|
|
function EvolutionAnim:updateFlash(input)
|
|
local round = self.rounds[self.round]
|
|
if not round then
|
|
return self:setPhase("reveal")
|
|
end
|
|
|
|
if self.step == "wait" then
|
|
if self:cancelPressed(input) then
|
|
-- .cancel_evo: wEvolutionCanceled, and the pic stays on the old stage.
|
|
self.canceled = true
|
|
self.showNew = false
|
|
return self:setPhase("reveal")
|
|
end
|
|
self.timer = self.timer - 1
|
|
if self.timer <= 0 then
|
|
self.step = "flash"
|
|
-- Two swaps per flash: to the new pic and back.
|
|
self.swapsLeft = round.flashes * 2
|
|
self.timer = Evolution.SWAP_FRAMES
|
|
self.showNew = true
|
|
end
|
|
return
|
|
end
|
|
|
|
-- Each swap holds for one WaitBGMap.
|
|
self.timer = self.timer - 1
|
|
if self.timer > 0 then return end
|
|
self.swapsLeft = self.swapsLeft - 1
|
|
if self.swapsLeft <= 0 then
|
|
self.showNew = false
|
|
self.round = self.round + 1
|
|
local nextRound = self.rounds[self.round]
|
|
if not nextRound then return self:setPhase("reveal") end
|
|
self.step = "wait"
|
|
self.timer = nextRound.wait
|
|
return
|
|
end
|
|
self.showNew = not self.showNew
|
|
self.timer = Evolution.SWAP_FRAMES
|
|
end
|
|
|
|
--------------------------------------------------------------------------
|
|
-- Balls of light
|
|
--------------------------------------------------------------------------
|
|
|
|
-- .balls_of_light spawns two balls on every EVEN wJumptableIndex over the 32
|
|
-- spawn frames. The angle it writes is read AFTER `inc [hl]`, so it is the
|
|
-- POST-increment index masked to %1110 and shifted left; the second ball of a
|
|
-- pair is a further $10 round the table.
|
|
--
|
|
-- AnimSeq_RevealNewMon then walks each ball out: VAR1 is the radius, starting
|
|
-- at $10 and stepping $08 a frame until it passes $80, and the angle is xor'd
|
|
-- with $20 every frame so a ball alternates between the two ends of its own
|
|
-- diameter. y comes from Sine and x from Cosine.
|
|
function EvolutionAnim:updateBalls()
|
|
local frame = self.ballFrame
|
|
if frame < Evolution.BALL_SPAWN_FRAMES and frame % 2 == 0 then
|
|
-- `ld a, [hl] / inc [hl]` leaves the PRE-increment index in a, which is
|
|
-- what the `and $1` even/odd test sees; .GenerateBallOfLight then re-reads
|
|
-- wJumptableIndex and gets the POST-increment one. So the angle is
|
|
-- (post & %1110) << 1, with post always odd here.
|
|
local post = frame + 1
|
|
local base = (((post % 16) - (post % 2)) * 2) % 256
|
|
for _, offset in ipairs({ 0x00, 0x10 }) do
|
|
if #self.balls < BALL_LIMIT then
|
|
self.balls[#self.balls + 1] = {
|
|
angle = (base + offset) % 256,
|
|
radius = Evolution.BALL_RADIUS_START,
|
|
age = 0,
|
|
}
|
|
end
|
|
end
|
|
end
|
|
self.ballFrame = frame + 1
|
|
|
|
local alive = {}
|
|
for _, ball in ipairs(self.balls) do
|
|
if ball.radius < Evolution.BALL_RADIUS_END then
|
|
local radius = ball.radius
|
|
ball.radius = radius + Evolution.BALL_RADIUS_STEP
|
|
-- `xor $20` on an angle the sine table masks to six bits is the same as
|
|
-- half a period, and Lua 5.1 has no bitwise xor to spell it with.
|
|
ball.angle = (ball.angle + 0x20) % 0x40
|
|
ball.y = EvolutionAnim.signed(SpriteAnims.sine(ball.angle, radius))
|
|
ball.x = EvolutionAnim.signed(SpriteAnims.cosine(ball.angle, radius))
|
|
ball.age = ball.age + 1
|
|
alive[#alive + 1] = ball
|
|
end
|
|
end
|
|
self.balls = alive
|
|
end
|
|
|
|
-- The sine helpers hand back the byte the ASM leaves in a, so a negative
|
|
-- offset arrives in two's complement.
|
|
function EvolutionAnim.signed(value)
|
|
value = value % 256
|
|
if value >= 128 then return value - 256 end
|
|
return value
|
|
end
|
|
|
|
--------------------------------------------------------------------------
|
|
-- Draw
|
|
--------------------------------------------------------------------------
|
|
|
|
function EvolutionAnim:pic(species)
|
|
local def = species and self.data.pokemon and self.data.pokemon[species]
|
|
local path = def and def.spriteFront
|
|
if not path then return nil end
|
|
local cached = self.picCache[path]
|
|
if cached == nil then
|
|
-- "and" would truncate the pcall's second return, so the call stands on
|
|
-- its own line.
|
|
local ok, image = pcall(Assets.image, path)
|
|
cached = ok and image or false
|
|
self.picCache[path] = cached
|
|
end
|
|
return cached or nil
|
|
end
|
|
|
|
-- The four colours the box draws through right now: the mon's own while the
|
|
-- SGB layout is c = FALSE, PREDEFPAL_BLACKOUT while it is TRUE.
|
|
-- Shininess is a DV pattern, and the DVs survive the evolution, so both stages
|
|
-- read the same flag off the record that walked in.
|
|
function EvolutionAnim:picColors(species)
|
|
if self.blackout then return BLACKOUT end
|
|
return Palettes.monColors(self.palettes, species,
|
|
self.mon and self.mon.shiny)
|
|
end
|
|
|
|
function EvolutionAnim:drawPic()
|
|
local species = self.showNew and self.newSpecies or self.oldSpecies
|
|
local image = self:pic(species)
|
|
if not image then return end
|
|
local G = love.graphics
|
|
local w, h = image:getDimensions()
|
|
-- PlaceGraphic pads the pic into the 7x7 box bottom-first, so a 40x40
|
|
-- Cyndaquil stands on the same ground line a 56x56 Onix does.
|
|
local box = PIC_TILES * 8
|
|
local px = PIC_TILE_X * 8 + math.floor((box - w) / 2)
|
|
local py = PIC_TILE_Y * 8 + (box - h)
|
|
G.setColor(1, 1, 1, 1)
|
|
local colors = self:picColors(species)
|
|
local function body() G.draw(image, px, py) end
|
|
if colors and GbcPalette.available() then
|
|
GbcPalette.with(colors, body)
|
|
else
|
|
body()
|
|
end
|
|
end
|
|
|
|
-- gfx/evo/bubble_large.2bpp and gfx/evo/bubble.2bpp are not in the Gold cache
|
|
-- (the extractor does not pull gfx/evo/), so the ball is drawn as the disc
|
|
-- those two 8x8 tiles are, at the two sizes .Frameset_EvolutionBallOfLight
|
|
-- alternates between every two frames. Its colour goes through
|
|
-- GbcPalette.color so the COLOR option still reaches it.
|
|
function EvolutionAnim:drawBalls()
|
|
if #self.balls == 0 then return end
|
|
local G = love.graphics
|
|
local colors = Palettes.monColors(self.palettes, self.newSpecies,
|
|
self.evolved and self.evolved.shiny)
|
|
local rgb = GbcPalette.color(colors, 3) or { 0, 0, 0 }
|
|
G.setColor(rgb[1] / 255, rgb[2] / 255, rgb[3] / 255, 1)
|
|
for _, ball in ipairs(self.balls) do
|
|
local radius = (math.floor(ball.age / 2) % 2 == 0) and 4 or 3
|
|
G.circle("fill", Evolution.BALL_ORIGIN_X + (ball.x or 0) + 4,
|
|
Evolution.BALL_ORIGIN_Y + (ball.y or 0) + 4, radius)
|
|
end
|
|
G.setColor(1, 1, 1, 1)
|
|
end
|
|
|
|
-- The pic is on screen from the moment .PlaceFrontpic runs until ClearTilemap,
|
|
-- which is only reached once the "evolved into" page has had its 40 frames --
|
|
-- so the congratulation text prints OVER the new mon, and CancelEvolution's
|
|
-- "stopped evolving!" prints over the old one. The two phases without a pic
|
|
-- are the 50 frames before the animation starts and the LearnLevelMoves run
|
|
-- after the tilemap is cleared.
|
|
function EvolutionAnim:drawPanel()
|
|
Chrome.clear()
|
|
if self.phase ~= "evolving" and self.phase ~= "learn" then
|
|
self:drawPic()
|
|
self:drawBalls()
|
|
end
|
|
if self.lines then
|
|
Chrome.box(BOX_X, BOX_Y, BOX_W, BOX_H)
|
|
for index, line in ipairs(self.lines) do
|
|
Chrome.print(line, TEXT_X, TEXT_Y + (index - 1) * TEXT_LINE)
|
|
end
|
|
end
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
end
|
|
|
|
function EvolutionAnim:draw()
|
|
self:drawPanel()
|
|
end
|
|
|
|
function EvolutionAnim:drawWidescreen(winW, winH)
|
|
local G = love.graphics
|
|
G.setColor(1, 1, 1, 1)
|
|
G.rectangle("fill", 0, 0, winW, winH)
|
|
local scale = Chrome.fitScale(winW, winH)
|
|
G.push()
|
|
G.translate(math.floor((winW - SCREEN_W * scale) / 2),
|
|
math.floor((winH - SCREEN_H * scale) / 2))
|
|
G.scale(scale, scale)
|
|
self:drawPanel()
|
|
G.pop()
|
|
end
|
|
|
|
return EvolutionAnim
|