mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-18 19:54:21 +02:00
G2 support
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
-- The battle HUD, drawn from the cart's own tiles.
|
||||
--
|
||||
-- The HUD is not lines and boxes an engine invents: it is tiles the cart loads
|
||||
-- into fixed VRAM slots (engine/gfx/load_font.asm LoadBattleFontsHPBar /
|
||||
-- LoadHPBar), placed at fixed tile coordinates. Drawing it from those tiles is
|
||||
-- what makes it align on the 8px grid by construction rather than by eye, and
|
||||
-- it is why "HP:" looks like the cart's bold glyph instead of three font
|
||||
-- letters.
|
||||
--
|
||||
-- FontBattleExtra -> $60 "HP:" is $60/$61; the bar's cells are $62
|
||||
-- (empty) through $6a (8 pixels of fill), and
|
||||
-- $6b is the bar's right end cap
|
||||
-- EnemyHPBarBorderGFX -> $6c 4 tiles: $6d left side, $6f bottom left
|
||||
-- HPExpBarBorderGFX -> $73 6 tiles: $73 right side, $74 bottom left,
|
||||
-- $76 bottom side, $77 / $78 bottom right
|
||||
-- ExpBarGFX -> $55 9 exp-bar fill cells
|
||||
--
|
||||
-- The HP-bar cells are 2bpp: the bar's rule is shade 3 and its fill shades 1-2,
|
||||
-- so they are coloured through palettes.hpBar (gfx/battle/hp_bar.pal) the same
|
||||
-- way the cart colours PAL_BATTLE_BG_PLAYER_HP. The two border sheets are
|
||||
-- 1bpp and draw black.
|
||||
|
||||
local Assets = require("src.render.Assets")
|
||||
local GbcPalette = require("src.render.GbcPalette")
|
||||
local HpBar = require("src.battle.gen2.HpBar")
|
||||
|
||||
local BattleHud = {}
|
||||
BattleHud.__index = BattleHud
|
||||
|
||||
-- Tile ids, so the arithmetic below reads as the ASM does.
|
||||
local TILE_HP_LABEL = 0x60 -- and $61
|
||||
local TILE_BAR_EMPTY = 0x62 -- $62..$6a is 0..8 pixels of fill
|
||||
local TILE_BAR_END = 0x6b
|
||||
local FIRST_BATTLE_EXTRA = 0x60
|
||||
|
||||
-- Enemy border sheet ($6c..$6f) and player border sheet ($73..$78).
|
||||
local ENEMY_BORDER_FIRST = 0x6c
|
||||
local PLAYER_BORDER_FIRST = 0x73
|
||||
local TILE_ENEMY_LEFT = 0x6d
|
||||
local TILE_ENEMY_BOTTOM_LEFT = 0x74 -- from the player sheet, per the ASM
|
||||
local TILE_ENEMY_BOTTOM_RIGHT = 0x78
|
||||
local TILE_BOTTOM_SIDE = 0x76
|
||||
local TILE_PLAYER_RIGHT = 0x73
|
||||
local TILE_PLAYER_BOTTOM_RIGHT = 0x77
|
||||
local TILE_PLAYER_BOTTOM_LEFT = 0x6f
|
||||
|
||||
-- DrawEnemyHUDBorder's tail: ExpBarGFX's 9th tile, the caught mark
|
||||
-- (engine/battle/trainer_huds.asm:143-152).
|
||||
local TILE_CAUGHT = 0x5d
|
||||
|
||||
function BattleHud.new(menuGfx, palettes)
|
||||
local self = setmetatable({}, BattleHud)
|
||||
self.gfx = menuGfx and menuGfx.battleHud or nil
|
||||
self.palettes = palettes
|
||||
self.images = {}
|
||||
self.quads = {}
|
||||
return self
|
||||
end
|
||||
|
||||
function BattleHud:image(key)
|
||||
local path = self.gfx and self.gfx[key]
|
||||
if not path then return nil end
|
||||
local cached = self.images[path]
|
||||
if cached == nil then
|
||||
local ok, image = pcall(Assets.image, path)
|
||||
cached = ok and image or false
|
||||
self.images[path] = cached
|
||||
end
|
||||
return cached or nil
|
||||
end
|
||||
|
||||
-- One 8x8 tile out of a horizontal strip, cached per (sheet, index).
|
||||
function BattleHud:quad(image, index)
|
||||
local key = tostring(image) .. ":" .. index
|
||||
local quad = self.quads[key]
|
||||
if not quad then
|
||||
local w, h = image:getDimensions()
|
||||
quad = love.graphics.newQuad(index * 8, 0, 8, 8, w, h)
|
||||
self.quads[key] = quad
|
||||
end
|
||||
return quad
|
||||
end
|
||||
|
||||
function BattleHud:available()
|
||||
return self:image("hpBar") ~= nil
|
||||
end
|
||||
|
||||
-- The 4-colour palette the HP-bar cells draw with: white, the bar's own light
|
||||
-- colour, the state's fill colour, black -- which is HPBarPals' two colours
|
||||
-- bracketed the way every Gen 2 palette is.
|
||||
function BattleHud:barColors(key)
|
||||
local pal = self.palettes and self.palettes.hpBar and self.palettes.hpBar[key]
|
||||
if not pal then return nil end
|
||||
return {
|
||||
{ 255, 255, 255 },
|
||||
{ pal[1][1], pal[1][2], pal[1][3] },
|
||||
{ pal[2][1], pal[2][2], pal[2][3] },
|
||||
{ 0, 0, 0 },
|
||||
}
|
||||
end
|
||||
|
||||
-- Draw a run of tiles from a sheet whose first tile is `firstTile`, colouring
|
||||
-- with `colors` when one is given.
|
||||
function BattleHud:drawTile(key, firstTile, tile, tx, ty, colors, mirror)
|
||||
local image = self:image(key)
|
||||
if not image then return false end
|
||||
local index = tile - firstTile
|
||||
if index < 0 then return false end
|
||||
local G = love.graphics
|
||||
G.setColor(1, 1, 1, 1)
|
||||
local function body()
|
||||
if mirror then
|
||||
-- Flip in place: the origin moves a tile right and x scales by -1.
|
||||
G.draw(image, self:quad(image, index), tx * 8 + 8, ty * 8, 0, -1, 1)
|
||||
else
|
||||
G.draw(image, self:quad(image, index), tx * 8, ty * 8)
|
||||
end
|
||||
end
|
||||
if colors and GbcPalette.available() then
|
||||
GbcPalette.with(colors, body)
|
||||
else
|
||||
G.setColor(0, 0, 0, 1)
|
||||
body()
|
||||
G.setColor(1, 1, 1, 1)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Six bar cells at (tx, ty), no label and no end cap.
|
||||
--
|
||||
-- This is DrawBattleHPBar itself: the party menu calls it with `ld d, $6` and
|
||||
-- `ld b, $0` (PlacePartyHPBar), so a party row's bar is literally the battle
|
||||
-- HUD's bar minus the "HP:" prefix -- same tiles, same HPBarPals colour, same
|
||||
-- one-pixel-at-a-time fill. Sharing this method is what keeps the two screens
|
||||
-- from ever disagreeing about how full a bar looks.
|
||||
function BattleHud:drawBar(hp, maxHp, tx, ty)
|
||||
local pixels = HpBar.pixels(hp, maxHp)
|
||||
local colors = self:barColors(HpBar.palette(pixels))
|
||||
for cell = 0, HpBar.LENGTH_TILES - 1 do
|
||||
local remaining = pixels - cell * 8
|
||||
local filled = math.max(0, math.min(8, remaining))
|
||||
self:drawTile("hpBar", FIRST_BATTLE_EXTRA, TILE_BAR_EMPTY + filled,
|
||||
tx + cell, ty, colors)
|
||||
end
|
||||
return tx + HpBar.LENGTH_TILES
|
||||
end
|
||||
|
||||
-- "HP:" plus the six bar cells plus the end cap, starting at tile (tx, ty).
|
||||
-- Returns the column just past the assembly (tx + 9), so the caller can put the
|
||||
-- frame's vertical stub there.
|
||||
function BattleHud:drawHpBar(hp, maxHp, tx, ty)
|
||||
local pixels = HpBar.pixels(hp, maxHp)
|
||||
local colors = self:barColors(HpBar.palette(pixels))
|
||||
-- The "HP:" badge sits inside the bar's own attrmap region, so it wears the
|
||||
-- HP palette too: its background is HPBarPals' light colour (the cream the
|
||||
-- cart shows) and its letters are black. Drawing it white-on-black was the
|
||||
-- one place this HUD invented a colour instead of reading one.
|
||||
self:drawTile("hpBar", FIRST_BATTLE_EXTRA, TILE_HP_LABEL, tx, ty, colors)
|
||||
self:drawTile("hpBar", FIRST_BATTLE_EXTRA, TILE_HP_LABEL + 1, tx + 1, ty,
|
||||
colors)
|
||||
self:drawBar(hp, maxHp, tx + 2, ty)
|
||||
self:drawTile("hpBar", FIRST_BATTLE_EXTRA, TILE_BAR_END,
|
||||
tx + 2 + HpBar.LENGTH_TILES, ty, colors)
|
||||
return tx + 3 + HpBar.LENGTH_TILES
|
||||
end
|
||||
|
||||
-- The exp bar, transcribed from FillInExpBar / PlaceExpBar rather than guessed
|
||||
-- from the tile art:
|
||||
--
|
||||
-- FillInExpBar starts at (10,11), adds 7 to reach the RIGHTMOST tile, then
|
||||
-- PlaceExpBar writes 8 tiles walking LEFT (ld [hld]):
|
||||
-- * while at least 8 pixels remain: tile $6a, the HP bar's full cell
|
||||
-- * the leftover 1..7 pixels: tile $54 + remainder, i.e. $55..$5b from
|
||||
-- ExpBarGFX -- which is why those cells anchor their fill to the right,
|
||||
-- against the full cells beside them
|
||||
-- * every remaining cell: tile $62, the HP bar's empty cell
|
||||
--
|
||||
-- So the bar is eight tiles wide (64 pixels), it grows from the RIGHT, and two
|
||||
-- of its three tiles come from FontBattleExtra rather than ExpBarGFX. Getting
|
||||
-- any of those three facts wrong is what makes it look like dashes.
|
||||
BattleHud.EXP_CELLS = 8
|
||||
BattleHud.EXP_LENGTH_PX = BattleHud.EXP_CELLS * 8
|
||||
local TILE_EXP_FULL = 0x6a -- FontBattleExtra
|
||||
local TILE_EXP_EMPTY = 0x62 -- FontBattleExtra
|
||||
local EXP_PARTIAL_BASE = 0x54 -- $54 + remainder lands in ExpBarGFX
|
||||
|
||||
function BattleHud:drawExpBar(fraction, tx, ty)
|
||||
if not self:image("hpBar") then return false end
|
||||
fraction = math.max(0, math.min(1, fraction or 0))
|
||||
local pixels = math.floor(fraction * BattleHud.EXP_LENGTH_PX)
|
||||
-- The whole row wears the exp bar's palette, full and empty cells included.
|
||||
local pal = self.palettes and self.palettes.expBar
|
||||
local colors = pal and {
|
||||
{ 255, 255, 255 },
|
||||
{ pal[1][1], pal[1][2], pal[1][3] },
|
||||
{ pal[2][1], pal[2][2], pal[2][3] },
|
||||
{ 0, 0, 0 },
|
||||
} or nil
|
||||
|
||||
local remaining = pixels
|
||||
for cell = BattleHud.EXP_CELLS - 1, 0, -1 do
|
||||
local column = tx + cell
|
||||
if remaining >= 8 then
|
||||
remaining = remaining - 8
|
||||
self:drawTile("hpBar", FIRST_BATTLE_EXTRA, TILE_EXP_FULL, column, ty,
|
||||
colors)
|
||||
elseif remaining > 0 then
|
||||
self:drawTile("expBar", self.gfx.expBarFirstTile,
|
||||
EXP_PARTIAL_BASE + remaining, column, ty, colors)
|
||||
remaining = 0
|
||||
else
|
||||
self:drawTile("hpBar", FIRST_BATTLE_EXTRA, TILE_EXP_EMPTY, column, ty,
|
||||
colors)
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- The mark sits inside the enemy HP block, which the battle attrmap fills with
|
||||
-- PAL_BATTLE_BG_ENEMY_HP (engine/gfx/cgb_layouts.asm:123-128).
|
||||
function BattleHud:drawCaughtIcon(tx, ty, hp, maxHp)
|
||||
local first = self.gfx and self.gfx.expBarFirstTile
|
||||
if not first or (self.gfx.expBarCells or 0) < 9 then return false end
|
||||
local colors = self:barColors(HpBar.palette(HpBar.pixels(hp, maxHp)))
|
||||
if not colors then return false end
|
||||
return self:drawTile("expBar", first, TILE_CAUGHT, tx, ty, colors)
|
||||
end
|
||||
|
||||
-- Both frames come out of PlaceHUDBorderTiles, which lays four tiles in a
|
||||
-- fixed pattern from one starting coordinate:
|
||||
--
|
||||
-- tiles[0] (side) at the start
|
||||
-- tiles[1] (near corner) one row BELOW it, same column
|
||||
-- tiles[3] (bottom side) x8, stepping by de (+1 right, -1 left)
|
||||
-- tiles[2] (far corner) one step past that run
|
||||
--
|
||||
-- Getting the corner's row wrong is what leaves the vertical stub floating
|
||||
-- clear of the bottom rule instead of joined to it.
|
||||
function BattleHud:placeBorder(tiles, tx, ty, step)
|
||||
-- side
|
||||
self:drawTile(tiles.sideSheet, tiles.sideFirst, tiles.side, tx, ty)
|
||||
-- near corner, one row down
|
||||
self:drawTile(tiles.cornerSheet, tiles.cornerFirst, tiles.nearCorner,
|
||||
tx, ty + 1)
|
||||
-- eight bottom-side tiles, then the far corner
|
||||
local x = tx
|
||||
for _ = 1, 8 do
|
||||
x = x + step
|
||||
self:drawTile(tiles.cornerSheet, tiles.cornerFirst, tiles.bottom,
|
||||
x, ty + 1)
|
||||
end
|
||||
x = x + step
|
||||
self:drawTile(tiles.farSheet or tiles.cornerSheet,
|
||||
tiles.farFirst or tiles.cornerFirst, tiles.farCorner, x, ty + 1)
|
||||
end
|
||||
|
||||
-- DrawEnemyHUDBorder: hlcoord 1, 2 stepping right, tiles $6d / $74 / $78 / $76.
|
||||
function BattleHud:drawEnemyFrame()
|
||||
self:placeBorder({
|
||||
sideSheet = "enemyBorder", sideFirst = ENEMY_BORDER_FIRST,
|
||||
cornerSheet = "playerBorder", cornerFirst = PLAYER_BORDER_FIRST,
|
||||
side = TILE_ENEMY_LEFT,
|
||||
nearCorner = TILE_ENEMY_BOTTOM_LEFT,
|
||||
farCorner = TILE_ENEMY_BOTTOM_RIGHT,
|
||||
bottom = TILE_BOTTOM_SIDE,
|
||||
}, 1, 2, 1)
|
||||
end
|
||||
|
||||
-- DrawPlayerHUDBorder: hlcoord 18, 10 stepping LEFT, tiles $73 / $77 / $6f /
|
||||
-- $76, plus the extra vertical bar DrawPlayerHUD writes at (18,9) so the stub
|
||||
-- is two rows tall.
|
||||
function BattleHud:drawPlayerFrame()
|
||||
self:drawTile("playerBorder", PLAYER_BORDER_FIRST, TILE_PLAYER_RIGHT, 18, 9)
|
||||
self:placeBorder({
|
||||
sideSheet = "playerBorder", sideFirst = PLAYER_BORDER_FIRST,
|
||||
cornerSheet = "playerBorder", cornerFirst = PLAYER_BORDER_FIRST,
|
||||
-- $6f is the LAST tile of EnemyHPBarBorderGFX, not the player sheet
|
||||
-- (engine/gfx/load_font.asm:57-65).
|
||||
farSheet = "enemyBorder", farFirst = ENEMY_BORDER_FIRST,
|
||||
side = TILE_PLAYER_RIGHT,
|
||||
nearCorner = TILE_PLAYER_BOTTOM_RIGHT,
|
||||
farCorner = TILE_PLAYER_BOTTOM_LEFT,
|
||||
bottom = TILE_BOTTOM_SIDE,
|
||||
}, 18, 10, -1)
|
||||
end
|
||||
|
||||
BattleHud.TILE_HP_LABEL = TILE_HP_LABEL
|
||||
BattleHud.TILE_BAR_EMPTY = TILE_BAR_EMPTY
|
||||
BattleHud.TILE_BAR_END = TILE_BAR_END
|
||||
|
||||
return BattleHud
|
||||
Reference in New Issue
Block a user