mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-17 19:24:01 +02:00
@@ -72,11 +72,11 @@ function Pool:reset()
|
||||
for row = 0, SCREEN_ROWS do self.lyBackup[row] = 0 end
|
||||
-- wBGP / wOBP0 / wOBP1, as DMG palette bytes.
|
||||
self.bgp, self.obp0, self.obp1 = NORMAL_PAL, NORMAL_PAL, NORMAL_PAL
|
||||
-- Per-battler state the CGB paths write instead of touching wBGP: a DMG
|
||||
-- shade byte the view remaps that battler's pic through, whether the pic is
|
||||
-- hidden outright, and which of the six BG squares it is drawn at.
|
||||
-- Per-battler state the CGB paths write instead of touching wBGP: shade
|
||||
-- byte, hidden flag, lifted tile rows, and which BG square it is drawn at.
|
||||
self.monShade = { player = NORMAL_PAL, enemy = NORMAL_PAL }
|
||||
self.hidden = { player = false, enemy = false }
|
||||
self.liftedRows = { player = nil, enemy = nil }
|
||||
self.picSize = { player = nil, enemy = nil }
|
||||
self.slide = { player = 0, enemy = 0 }
|
||||
-- wSurfWaveBGEffect: the $40-byte rolling wave Surf keeps beside the
|
||||
@@ -488,6 +488,7 @@ local function runPicResize(self, st, script)
|
||||
self.picSize[side] = step
|
||||
self.hidden[side] = false
|
||||
end
|
||||
self.liftedRows[side] = nil
|
||||
incJt(st)
|
||||
elseif jt >= 1 and jt <= 2 then
|
||||
incJt(st)
|
||||
@@ -545,7 +546,7 @@ end
|
||||
|
||||
-- The two battler-pic objects: the animation borrows the mon's own tiles as
|
||||
-- an OBJ so it can be moved without touching the tilemap.
|
||||
local function battlerObj(self, st, objectPlayer, objectEnemy, clearRows)
|
||||
local function battlerObj(self, st, objectPlayer, objectEnemy, rows)
|
||||
local jt = st.jt
|
||||
if jt == 0 then
|
||||
if self:flyDig(st) then
|
||||
@@ -562,25 +563,26 @@ local function battlerObj(self, st, objectPlayer, objectEnemy, clearRows)
|
||||
}
|
||||
elseif jt == 1 then
|
||||
incJt(st)
|
||||
-- The rows the OBJ now covers are cleared out of the tilemap so the mon
|
||||
-- is not drawn twice.
|
||||
self.hidden[self:sideKey(st)] = clearRows
|
||||
-- engine/battle_anims/bg_effects.asm:448-465: the rows the OBJ now covers
|
||||
-- come out of the tilemap, and .five never puts them back.
|
||||
self.liftedRows[self:sideKey(st)] = rows[self:sideKey(st)]
|
||||
elseif jt >= 2 and jt <= 4 then
|
||||
incJt(st)
|
||||
elseif jt == 5 then
|
||||
self.hidden[self:sideKey(st)] = false
|
||||
endEffect(st)
|
||||
end
|
||||
end
|
||||
|
||||
E.BATTLE_BG_EFFECT_BATTLEROBJ_1ROW = function(self, st)
|
||||
battlerObj(self, st, "BATTLE_ANIM_OBJ_PLAYERHEAD_1ROW",
|
||||
"BATTLE_ANIM_OBJ_ENEMYFEET_1ROW", true)
|
||||
"BATTLE_ANIM_OBJ_ENEMYFEET_1ROW",
|
||||
{ player = { 0, 1 }, enemy = { 6, 1 } })
|
||||
end
|
||||
|
||||
E.BATTLE_BG_EFFECT_BATTLEROBJ_2ROW = function(self, st)
|
||||
battlerObj(self, st, "BATTLE_ANIM_OBJ_PLAYERHEAD_2ROW",
|
||||
"BATTLE_ANIM_OBJ_ENEMYFEET_2ROW", true)
|
||||
"BATTLE_ANIM_OBJ_ENEMYFEET_2ROW",
|
||||
{ player = { 0, 2 }, enemy = { 5, 2 } })
|
||||
end
|
||||
|
||||
-- BGEffect_RapidCyclePals. On a CGB the palette is applied to ONE battler
|
||||
|
||||
@@ -150,6 +150,10 @@ end
|
||||
-- animation (most of them) skips the canvas entirely.
|
||||
local function needsCanvas(runner)
|
||||
local bg = runner.bg
|
||||
-- engine/battle_anims/bg_effects.asm:448-465: a lifted battler row stays
|
||||
-- out of the BG until the next pic redraw, so those frames stay baked too.
|
||||
local lifted = bg.liftedRows
|
||||
if lifted and (lifted.player or lifted.enemy) then return true end
|
||||
if bg.scx ~= 0 or bg.scy ~= 0 then return true end
|
||||
if not bg.lcdc then return false end
|
||||
if bg.lyEnd <= bg.lyStart then return false end
|
||||
@@ -229,13 +233,12 @@ end
|
||||
-- times, and the grouping is by VALUE so a table that happens to repeat costs
|
||||
-- nothing extra.
|
||||
local function bgpBands(bg)
|
||||
local base = bg.bgp or GbcPalette.BGP_IDENTITY
|
||||
local order, bands = {}, {}
|
||||
for row = 0, SCREEN_H - 1 do
|
||||
local inWindow = row >= bg.lyStart and row < bg.lyEnd
|
||||
-- Outside the window the register still reads whatever wBGP holds, which
|
||||
-- for every effect that aims hLCDCPointer at rBGP is the identity.
|
||||
local byte = inWindow and (bg.lyBackup[row] or GbcPalette.BGP_IDENTITY)
|
||||
or GbcPalette.BGP_IDENTITY
|
||||
-- Outside the window the register still reads whatever wBGP holds.
|
||||
local byte = inWindow and (bg.lyBackup[row] or base) or base
|
||||
local band = bands[byte]
|
||||
if not band then
|
||||
band = { byte = byte, rows = {} }
|
||||
@@ -244,24 +247,56 @@ local function bgpBands(bg)
|
||||
end
|
||||
band.rows[#band.rows + 1] = row
|
||||
end
|
||||
-- Identity first so the fillBackground below it happens before any blit and
|
||||
-- the common band is the one drawn from the first bake.
|
||||
-- The base band first so the fillBackground below it happens before any blit
|
||||
-- and the common band is the one drawn from the first bake.
|
||||
table.sort(order, function(a, b)
|
||||
if a.byte == b.byte then return false end
|
||||
if a.byte == GbcPalette.BGP_IDENTITY then return true end
|
||||
if b.byte == GbcPalette.BGP_IDENTITY then return false end
|
||||
if a.byte == base then return true end
|
||||
if b.byte == base then return false end
|
||||
return a.rows[1] < b.rows[1]
|
||||
end)
|
||||
return order
|
||||
end
|
||||
|
||||
-- Runs `drawBg` (the battle panel) and then puts it on screen through the
|
||||
-- animation's BG registers. Returns without a canvas when nothing is
|
||||
-- displacing anything, which is the common case and costs nothing.
|
||||
function BattleAnimView:present(runner, drawBg)
|
||||
-- engine/battle_anims/anim_commands.asm:1293 BattleAnim_SetBGPals
|
||||
function BattleAnimView:panelPalettes(battle)
|
||||
local list = {}
|
||||
local shades = {}
|
||||
for index = 1, 4 do shades[index] = GbcPalette.color(nil, index) end
|
||||
list[#list + 1] = shades
|
||||
local function bracket(pair)
|
||||
if not (pair and pair[1] and pair[2]) then return end
|
||||
list[#list + 1] = {
|
||||
{ 255, 255, 255 },
|
||||
{ pair[1][1], pair[1][2], pair[1][3] },
|
||||
{ pair[2][1], pair[2][2], pair[2][3] },
|
||||
{ 0, 0, 0 },
|
||||
}
|
||||
end
|
||||
for _, side in ipairs({ "player", "enemy" }) do
|
||||
local mon = battle and battle[side]
|
||||
local colors = mon
|
||||
and Palettes.monColors(self.palettes, mon.species, mon.shiny)
|
||||
if colors then list[#list + 1] = colors end
|
||||
end
|
||||
local hpBar = self.palettes and self.palettes.hpBar
|
||||
if hpBar then
|
||||
bracket(hpBar.green)
|
||||
bracket(hpBar.yellow)
|
||||
bracket(hpBar.red)
|
||||
end
|
||||
bracket(self.palettes and self.palettes.expBar)
|
||||
return list
|
||||
end
|
||||
|
||||
-- Runs `drawBg` (the battle panel) and puts it on screen through the
|
||||
-- animation's BG registers; skips the canvas when nothing needs one.
|
||||
function BattleAnimView:present(runner, drawBg, battle)
|
||||
if not (love and love.graphics) then return end
|
||||
local bg = runner.bg
|
||||
if not needsCanvas(runner) then
|
||||
local invert = bg.bgp and bg.bgp ~= GbcPalette.BGP_IDENTITY
|
||||
and bg.lcdc ~= "BGP" and GbcPalette.remapShader() ~= nil
|
||||
if not invert and not needsCanvas(runner) then
|
||||
drawBg()
|
||||
return
|
||||
end
|
||||
@@ -292,9 +327,10 @@ function BattleAnimView:present(runner, drawBg)
|
||||
|
||||
self:bake(drawBg, nil)
|
||||
|
||||
-- A shifted scanline exposes whatever the BG map holds beside the pic, which
|
||||
-- outside the two pic boxes is the blank tile. Without this the exposed
|
||||
-- strip is the canvas's own transparency and every shake shows a seam.
|
||||
local remapped = invert
|
||||
and GbcPalette.useRemap(self:panelPalettes(battle), bg.bgp)
|
||||
-- A shifted scanline exposes the blank tile beside the pic boxes; without
|
||||
-- this the exposed strip is the canvas's own transparency.
|
||||
self:fillBackground()
|
||||
G.setColor(1, 1, 1, 1)
|
||||
-- hSCX / hSCY move the whole background; the per-scanline overrides only
|
||||
@@ -314,6 +350,7 @@ function BattleAnimView:present(runner, drawBg)
|
||||
self:blitRow(row, dx, dy)
|
||||
end
|
||||
end
|
||||
if remapped then GbcPalette.clear() end
|
||||
-- Shaderless boot: the panel is raw grayscale, so there are no palettes to
|
||||
-- permute and the entry's BRIGHTNESS is the only thing left to reproduce.
|
||||
if bg.lcdc == "BGP" then
|
||||
@@ -417,5 +454,6 @@ end
|
||||
|
||||
BattleAnimView.SCREEN_W = SCREEN_W
|
||||
BattleAnimView.SCREEN_H = SCREEN_H
|
||||
BattleAnimView.needsCanvas = needsCanvas
|
||||
|
||||
return BattleAnimView
|
||||
|
||||
@@ -661,7 +661,7 @@ function BattleState:drawPic(mon, back)
|
||||
-- the mon drawn at this frame.
|
||||
local scale = self:picScale(path, mon, back)
|
||||
if anim then
|
||||
px = px + (anim.slide or 0)
|
||||
if not self.liftedPass then px = px + (anim.slide or 0) end
|
||||
local resized = anim.size and PIC_RESIZE_TILES[anim.size]
|
||||
if resized then scale = scale * (resized / boxTiles) end
|
||||
end
|
||||
@@ -714,11 +714,39 @@ function BattleState:drawPic(mon, back)
|
||||
-- A mod-supplied pic that says it is already coloured is drawn as it is:
|
||||
-- pokemon.sprite's ctx.trueColor, the same flag Gen 1's Sprites.path hands
|
||||
-- back to its own draw site.
|
||||
if colors and not trueColor and GbcPalette.available() then
|
||||
GbcPalette.with(colors, body)
|
||||
else
|
||||
body()
|
||||
local function paint()
|
||||
if colors and not trueColor and GbcPalette.available() then
|
||||
GbcPalette.with(colors, body)
|
||||
else
|
||||
body()
|
||||
end
|
||||
end
|
||||
local lifted = anim and anim.lifted
|
||||
if not lifted then
|
||||
paint()
|
||||
return
|
||||
end
|
||||
-- engine/battle_anims/bg_effects.asm:448-465: the ClearBoxed band is off the BG.
|
||||
local bandY = (back and BattleState.PLAYER_PIC_TILE_Y
|
||||
or BattleState.ENEMY_PIC_TILE_Y) * 8 + lifted[1] * 8
|
||||
local bandH = lifted[2] * 8
|
||||
local psx, psy, psw, psh
|
||||
if G.getScissor then psx, psy, psw, psh = G.getScissor() end
|
||||
if self.liftedPass then
|
||||
G.setScissor(0, bandY, 160, bandH)
|
||||
paint()
|
||||
else
|
||||
if bandY > 0 then
|
||||
G.setScissor(0, 0, 160, bandY)
|
||||
paint()
|
||||
end
|
||||
local below = 144 - bandY - bandH
|
||||
if below > 0 then
|
||||
G.setScissor(0, bandY + bandH, 160, below)
|
||||
paint()
|
||||
end
|
||||
end
|
||||
if psx then G.setScissor(psx, psy, psw, psh) else G.setScissor() end
|
||||
end
|
||||
|
||||
-- MonsterSpriteGFX (gfx/sprites.asm:82): the facing-DOWN 16x16 frame for the
|
||||
@@ -1137,6 +1165,7 @@ function BattleState:animPicState(side)
|
||||
local bg = self.anim.bg
|
||||
return {
|
||||
hidden = bg.hidden[side],
|
||||
lifted = bg.liftedRows and bg.liftedRows[side] or nil,
|
||||
size = bg.picSize[side],
|
||||
slide = bg.slide[side] or 0,
|
||||
shade = bg.monShade[side],
|
||||
@@ -3436,6 +3465,36 @@ function BattleState:drawScene()
|
||||
end
|
||||
end
|
||||
|
||||
-- data/battle_anims/objects.asm:390-397: the lifted band rides at ABSOLUTE_X,
|
||||
-- outside the scanline blit, so the attacker's SCX never moves it.
|
||||
function BattleState:drawLiftedRows()
|
||||
local battle = self.battle
|
||||
if not battle then return end
|
||||
local enemy = self:animPicState("enemy")
|
||||
local player = self:animPicState("player")
|
||||
local enemyLift = enemy and enemy.lifted
|
||||
local playerLift = player and player.lifted
|
||||
if not (enemyLift or playerLift) then return end
|
||||
local G = love.graphics
|
||||
if not self.liftCanvas then
|
||||
self.liftCanvas = G.newCanvas(160, 144)
|
||||
self.liftCanvas:setFilter("nearest", "nearest")
|
||||
end
|
||||
local previous = G.getCanvas()
|
||||
G.setCanvas(self.liftCanvas)
|
||||
G.clear(0, 0, 0, 0)
|
||||
G.push()
|
||||
G.origin()
|
||||
self.liftedPass = true
|
||||
if enemyLift then self:drawPic(battle.enemy, false) end
|
||||
if playerLift then self:drawPic(battle.player, true) end
|
||||
self.liftedPass = nil
|
||||
G.pop()
|
||||
G.setCanvas(previous)
|
||||
G.setColor(1, 1, 1, 1)
|
||||
G.draw(self.liftCanvas, 0, 0)
|
||||
end
|
||||
|
||||
function BattleState:drawSceneBody()
|
||||
local panel = function() self:drawPanel() end
|
||||
if self.animView and self.slideFrame < BattleAnimView.SLIDE_FRAMES then
|
||||
@@ -3456,7 +3515,8 @@ function BattleState:drawSceneBody()
|
||||
return
|
||||
end
|
||||
if self.anim and self.animView then
|
||||
self.animView:present(self.anim, panel)
|
||||
self.animView:present(self.anim, panel, self.battle)
|
||||
self:drawLiftedRows()
|
||||
self.animView:drawObjects(self.anim, self.battle)
|
||||
return
|
||||
end
|
||||
|
||||
@@ -6,6 +6,7 @@ local Assets = require("src.render.Assets")
|
||||
local BorderFill = require("src.world.gen2.BorderFill")
|
||||
local GbcPalette = require("src.render.GbcPalette")
|
||||
local Palettes = require("src.world.gen2.Palettes")
|
||||
local PixelCanvas = require("src.render.PixelCanvas")
|
||||
|
||||
local MapPreview = {}
|
||||
|
||||
@@ -96,9 +97,8 @@ function MapPreview.bake(baker, map, daytime)
|
||||
local blocks = tileset.blocks
|
||||
local tilesPerRow = tileset.tilesPerRow or 16
|
||||
local pw, ph = map.width * 32, map.height * 32
|
||||
local okCanvas, canvas = pcall(love.graphics.newCanvas, pw, ph)
|
||||
local okCanvas, canvas = pcall(PixelCanvas.new, pw, ph, "nearest")
|
||||
if not okCanvas or not canvas then return nil end
|
||||
if canvas.setFilter then canvas:setFilter("nearest", "nearest") end
|
||||
local quads = {}
|
||||
local function quadFor(tile)
|
||||
local q = quads[tile]
|
||||
|
||||
@@ -47,6 +47,7 @@ local NPC = require("src.world.gen2.Npc")
|
||||
local Party = require("src.pokemon.Party")
|
||||
local Permissions = require("src.world.gen2.Permissions")
|
||||
local Pipelines = require("src.render.Pipelines")
|
||||
local PixelCanvas = require("src.render.PixelCanvas")
|
||||
local Player = require("src.world.gen2.Player")
|
||||
local Pokerus = require("src.core.gen2.Pokerus")
|
||||
local Roamers = require("src.core.gen2.Roamers")
|
||||
@@ -7612,8 +7613,9 @@ function World:bakeMapImage(map, daytime, flicker)
|
||||
local blocks = tileset.blocks
|
||||
local tilesPerRow = tileset.tilesPerRow or 16
|
||||
local pw, ph = map.width * 32, map.height * 32
|
||||
local canvas = love.graphics.newCanvas(pw, ph)
|
||||
canvas:setFilter("nearest", "nearest")
|
||||
-- Map pixels, not the screen's: a DPI-scaled canvas bakes them non-square
|
||||
-- (#208, see src/render/PixelCanvas.lua).
|
||||
local canvas = PixelCanvas.new(pw, ph, "nearest")
|
||||
local quads = {}
|
||||
local function quadFor(tile)
|
||||
local q = quads[tile]
|
||||
@@ -8140,12 +8142,11 @@ function World:scrollStrip(mapDef, tileset, tile, scroll)
|
||||
local cached = self.scrollStrips[key]
|
||||
if cached ~= nil then return cached or nil end
|
||||
local atlas = self:atlasFor(mapDef)
|
||||
local ok, canvas = pcall(love.graphics.newCanvas, 8, 8 * 8)
|
||||
local ok, canvas = pcall(PixelCanvas.new, 8, 8 * 8, "nearest")
|
||||
if not (atlas and ok and canvas) then
|
||||
self.scrollStrips[key] = false
|
||||
return nil
|
||||
end
|
||||
canvas:setFilter("nearest", "nearest")
|
||||
local perRow = tileset.tilesPerRow or 16
|
||||
local sx, sy = (tile % perRow) * 8, math.floor(tile / perRow) * 8
|
||||
local aw, ah = atlas:getDimensions()
|
||||
@@ -9945,8 +9946,7 @@ function World:drawTilted(w, h, s, gw, gh)
|
||||
if self.tiltCanvas and self.tiltCanvas.release then
|
||||
self.tiltCanvas:release()
|
||||
end
|
||||
self.tiltCanvas = G.newCanvas(gw, gh)
|
||||
self.tiltCanvas:setFilter("linear", "linear")
|
||||
self.tiltCanvas = PixelCanvas.new(gw, gh, "linear")
|
||||
end
|
||||
|
||||
local previous = G.getCanvas()
|
||||
|
||||
Reference in New Issue
Block a user