mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-18 03:35:56 +02:00
CLOSES #223, CLOSES #233, CLOSES #236, CLOSES #240, CLOSES #241, CLOSES #249, CLOSES #252, CLOSES #255, CLOSES #257, CLOSES #258, CLOSES #263, CLOSES #265, CLOSES #274, CLOSES #275, CLOSES #276, CLOSES #279, CLOSES #280, CLOSES #282, CLOSES #283, CLOSES #287, CLOSES #291, CLOSES #292, CLOSES #293, CLOSES #301, CLOSES #304, CLOSES #315, CLOSES #316, CLOSES #317, CLOSES #321, CLOSES #322, CLOSES #330
This commit is contained in:
+30
-6
@@ -41,10 +41,17 @@ local function showMessages(game, msgs, onDone)
|
||||
game.stack:push(TextBox.new(game, table.concat(msgs, "\f"), onDone))
|
||||
end
|
||||
|
||||
-- run the use-flow for an item on a chosen target
|
||||
local function useOn(game, battle, id, target, list, moveIndex)
|
||||
-- run the use-flow for an item on a chosen target. `picker` is the party
|
||||
-- menu when it was opened with keepOpen (HP medicine only): it is still on
|
||||
-- the stack, so every exit that prints has to close it afterwards. For
|
||||
-- every other item the picker popped itself first and closePicker's identity
|
||||
-- check makes it a no-op (#252).
|
||||
local function useOn(game, battle, id, target, list, moveIndex, picker)
|
||||
local result, payload, extra = ItemEffects.use(game.data, game.save, id, target,
|
||||
battle, moveIndex, game.overworld)
|
||||
local function closePicker()
|
||||
if picker then picker:close() end
|
||||
end
|
||||
|
||||
-- field POKé FLUTE: play the tune, then the no-effect text
|
||||
if result == "flute_field" then
|
||||
@@ -288,16 +295,29 @@ local function useOn(game, battle, id, target, list, moveIndex)
|
||||
end
|
||||
end
|
||||
list.index = math.min(list.index, math.max(1, #list.items))
|
||||
-- HP medicine: fill the bar in the still-open picker first, then print
|
||||
-- and close, the order item_effects.asm .doneHealing runs in
|
||||
-- (SFX_HEAL_HP -> UpdateHPBar2 -> RedrawPartyMenu prints the message).
|
||||
-- picker is nil for every other item and for in-battle use, which keeps
|
||||
-- the pop-then-print path below. #252
|
||||
if picker and extra and extra.healedFrom and target then
|
||||
picker:animateTo(target, extra.healedFrom, function()
|
||||
showMessages(game, payload, closePicker)
|
||||
end)
|
||||
return
|
||||
end
|
||||
if battle then
|
||||
list:close()
|
||||
showMessages(game, payload, function() battle:itemUsed({}) end)
|
||||
else
|
||||
showMessages(game, payload)
|
||||
showMessages(game, payload, closePicker)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
showMessages(game, payload) -- failed
|
||||
-- .healingItemNoEffect prints over the still-drawn party menu too, so the
|
||||
-- refusal closes the picker the same way (#252)
|
||||
showMessages(game, payload, closePicker) -- failed
|
||||
end
|
||||
|
||||
local function pickTargetAndUse(game, battle, id, list)
|
||||
@@ -308,9 +328,13 @@ local function pickTargetAndUse(game, battle, id, list)
|
||||
local def = game.data.items[id]
|
||||
local opts = {
|
||||
pickOnly = true,
|
||||
onSwitch = function(mon)
|
||||
-- HP medicine animates its bar with the picker still up (#252). Only
|
||||
-- out of battle: the in-battle tail closes the bag list underneath
|
||||
-- first, which needs the picker already gone.
|
||||
keepOpen = (not battle) and ItemEffects.healsHP(id),
|
||||
onSwitch = function(mon, picker)
|
||||
if not wantsMove then
|
||||
useOn(game, battle, id, mon, list)
|
||||
useOn(game, battle, id, mon, list, nil, picker)
|
||||
return
|
||||
end
|
||||
local rows = {}
|
||||
|
||||
@@ -7,6 +7,7 @@ local Font = require("src.render.Font")
|
||||
local ListMenu = require("src.ui.ListMenu")
|
||||
local Menu = require("src.ui.Menu")
|
||||
local Party = require("src.pokemon.Party")
|
||||
local Stats = require("src.pokemon.Stats")
|
||||
local TextBox = require("src.render.TextBox")
|
||||
local Strings = require("src.core.Strings")
|
||||
|
||||
@@ -73,6 +74,14 @@ local function withdraw(game)
|
||||
list.footer = "The party is full!"
|
||||
return
|
||||
end
|
||||
-- add_mon.asm _MoveMon's tail ("returning mon to party, compute
|
||||
-- level and stats"): a box mon carries no stat block, because
|
||||
-- box_struct stops before MON_LEVEL/MON_STATS, so the party copy
|
||||
-- runs CalcStats. Without it a mon decoded out of an imported .sav
|
||||
-- reaches the party menu with mon.stats nil and the HP bar draw
|
||||
-- nil-indexes it (#304, same family as #233). Already-shaped mons
|
||||
-- (everything the engine itself put in a box) pass through.
|
||||
Stats.ensure(game.data.pokemon[mon.species], mon)
|
||||
table.remove(box, item.value)
|
||||
table.insert(game.save.party, mon)
|
||||
local name = monName(game, mon)
|
||||
|
||||
@@ -20,6 +20,22 @@ EvolutionState.isOpaque = true
|
||||
-- SGB: SetPal_PokemonWholeScreen for the mon on display
|
||||
function EvolutionState:sgbPalettes(game)
|
||||
local P = require("src.render.PaletteFX")
|
||||
-- engine/movie/evolution.asm EvolveMon runs the back-and-forth flash with
|
||||
-- the whole screen on PAL_BLACK -- `ld c, 1 ; set PAL_BLACK instead of mon
|
||||
-- palette` right before .animLoop, then `ld c, 0` again at .done once the
|
||||
-- loop is over -- so both forms read as silhouettes while they trade places
|
||||
-- and only the settled form wears a mon palette (#279). PAL_BLACK is not
|
||||
-- four blacks: data/sgb/sgb_palettes.asm gives it `RGB 31,29,31, 07,07,07,
|
||||
-- 02,03,03, 03,02,02`, the usual paper white with the three darker shades
|
||||
-- crushed, which is why a hardware capture shows a dark mon on an unchanged
|
||||
-- background rather than an all-black screen. Going through P.pal keeps
|
||||
-- every COLORS mode honest for free: OG RED short-circuits every name to the
|
||||
-- one global boot-ROM palette (a Game Boy Color ignores the SGB packets, so
|
||||
-- it never blacks out) and the mono modes replace it in effectiveColors.
|
||||
if not self.done then
|
||||
local black = P.pal(game.data, "BLACK")
|
||||
if black then return { P.whole(black) } end
|
||||
end
|
||||
-- a cancelled evolution keeps the old species (never applied), so only
|
||||
-- colorize with the new form once it has actually evolved
|
||||
local species = (self.done and not self.canceled) and self.newSpecies
|
||||
|
||||
+1
-1
@@ -75,7 +75,7 @@ function ListMenu.new(game, title, items, opts)
|
||||
self.dialogue = opts.dialogue
|
||||
-- PC item lists (players_pc.asm): PrintListMenuEntries shows 4 names
|
||||
-- and PrintText footers ("How many?", stored/withdrew) use the standard
|
||||
-- bottom text box — same row budget as the mart, without the money box.
|
||||
-- bottom text box -- same row budget as the mart, without the money box.
|
||||
self.messageBox = opts.messageBox
|
||||
self.money = opts.money -- () -> current money for the box
|
||||
self.rows = opts.rows or ((opts.dialogue or opts.messageBox) and 4 or ROWS)
|
||||
|
||||
+198
-15
@@ -23,9 +23,51 @@ local PartyMenu = {}
|
||||
PartyMenu.__index = PartyMenu
|
||||
PartyMenu.isOpaque = true
|
||||
|
||||
-- SGB: generic whole-screen palette (SET_PAL_GENERIC)
|
||||
-- SGB (SetPal_PartyMenu, engine/gfx/palettes.asm:90): the party screen is
|
||||
-- NOT a one-palette screen. data/sgb/sgb_packets.asm BlkPacket_PartyMenu
|
||||
-- splits it into MEWMON over the mon-icon column with GREENBAR everywhere
|
||||
-- else, plus one block per HP bar row whose palette
|
||||
-- UpdatePartyMenuBlkPacket (engine/gfx/palettes.asm:299-325) sets from that
|
||||
-- mon's GetHealthBarColor -- pal 1 GREENBAR / 2 YELLOWBAR / 3 REDBAR
|
||||
-- (PalPacket_PartyMenu, sgb_packets.asm:219). Handing the whole screen
|
||||
-- MEWMON instead painted every bar with MEWMON's shades, which is why a
|
||||
-- full bar came out black and a low one purple (#274, absorbing #272).
|
||||
--
|
||||
-- Two rects differ from the packet's, both because this port draws pixels
|
||||
-- where the hardware drew OAM over BG:
|
||||
-- * the icon block is rows 0-11, not the packet's 0-12 -- row 12 is the
|
||||
-- message box's top edge, which on hardware was BG under an OBJ-free
|
||||
-- part of the block; here it would take MEWMON instead of the base.
|
||||
-- * the bar blocks sit one tile right of the packet's 05-11 because this
|
||||
-- port's bar starts at tile 5 where party_menu.asm:71-76 starts it at
|
||||
-- 4; the span is the same "left cap + six fill tiles".
|
||||
function PartyMenu:sgbPalettes(game)
|
||||
return require("src.render.PaletteFX").wholeNamed(game.data, "MEWMON")
|
||||
local P = require("src.render.PaletteFX")
|
||||
local base = P.pal(game.data, "GREENBAR")
|
||||
if not base then return nil end
|
||||
local zones = { P.whole(base) }
|
||||
local mew = P.pal(game.data, "MEWMON")
|
||||
if mew then zones[#zones + 1] = P.zone(mew, 1, 0, 2, 11) end
|
||||
-- the TM/HM list prints ABLE / NOT ABLE where the bar would be, so those
|
||||
-- rows have no bar to color (party_menu.asm .teachMoveMenu; #210)
|
||||
if not self.tmhm then
|
||||
local party = self.party or (game.save and game.save.party) or {}
|
||||
for i, mon in ipairs(party) do
|
||||
-- While a medicine's bar fill runs the block palette is STALE, not
|
||||
-- recomputed: SetPartyMenuHPBarColor (party_menu.asm:80/295) is only
|
||||
-- reached from the party-menu redraw loop, never from hp_bar.asm, so
|
||||
-- UpdateHPBar2 lengthens the bar under the PRE-heal color and
|
||||
-- RedrawPartyMenu snaps it green when the message prints. Hold the
|
||||
-- starting HP here for exactly that window (#252).
|
||||
local hp = mon.hp
|
||||
if self.heal and self.heal.mon == mon then hp = self.heal.from end
|
||||
local bar = P.pal(game.data, P.barPalName(hp, mon.stats.hp))
|
||||
if bar then
|
||||
zones[#zones + 1] = P.zone(bar, 6, i * 2 - 1, 12, i * 2 - 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
return zones
|
||||
end
|
||||
|
||||
local function sameItems(_, items) return items end
|
||||
@@ -50,7 +92,9 @@ local DIG_TILESETS = { FOREST = true, CEMETERY = true, CAVERN = true,
|
||||
-- Frame1; SNAKE/QUADRUPED are the reverse. Sprite-reused icons draw
|
||||
-- from 16x16x6 overworld sheets where index 3 is walk-down (tile 12):
|
||||
-- MON/FAIRY/BIRD rest on the walk frame and animate to standing
|
||||
-- (tile 0); WATER (Seel) is the reverse.
|
||||
-- (tile 0); WATER (Seel) is the reverse. Only the frame's LEFT half
|
||||
-- ever reaches the screen -- see PartyMenu.mirrorsIcon (#276) -- which
|
||||
-- is why a walk frame does not look like a walk frame here.
|
||||
PartyMenu.iconFrames = {
|
||||
BUG = { rest = 1, alt = 0 }, -- BugIconFrame2 <-> BugIconFrame1
|
||||
GRASS = { rest = 1, alt = 0 }, -- PlantIconFrame2 <-> PlantIconFrame1
|
||||
@@ -71,7 +115,45 @@ function PartyMenu.frameFor(name, alt, ih)
|
||||
return alt and ((ih or 0) >= 64 and 3 or 1) or 0
|
||||
end
|
||||
|
||||
-- HELIX is the one icon WriteMonPartySpriteOAM sends down the asymmetric
|
||||
-- path (engine/gfx/mon_icons.asm:246 `cp ICON_HELIX << 2 / jr z, .helix`);
|
||||
-- every other built-in icon is drawn as a mirrored left half (see
|
||||
-- drawIcon). A mod that supplies its own image instead of a built-in icon
|
||||
-- name has no vanilla counterpart, so its art draws whole. #276
|
||||
function PartyMenu.mirrorsIcon(name)
|
||||
return name ~= nil and name ~= "HELIX"
|
||||
end
|
||||
|
||||
local iconImages = {}
|
||||
|
||||
-- Party icons are OBJs (engine/gfx/mon_icons.asm WriteMonPartySpriteOAM
|
||||
-- writes OAM blocks), so they render through OBP0, and GBPalNormal
|
||||
-- (home/palettes.asm:20-26 `ld a, %11010000 ; 3100 / ldh [rOBP0], a`)
|
||||
-- holds OBP0 at "3100": OBJ color 1 shows as shade 0, color 2 as shade 1,
|
||||
-- color 3 as shade 3. An object never displays shade 2. This canvas has
|
||||
-- no OBJ layer, so bake that map into the icon art once per path (the same
|
||||
-- CPU-remap trick as SpriteRenderer.getObpImage, and the same "#obp" cache
|
||||
-- key convention) and let the screen's SGB zone color the result. Without
|
||||
-- it every color-2 pixel took the zone palette's shade-2 color -- the
|
||||
-- ADVANCED pack's MEWMON purple {115,33,165}, i.e. the "weirdly colored"
|
||||
-- party sprites of #274.
|
||||
local function obpIcon(path)
|
||||
if not (love.image and love.image.newImageData) then
|
||||
return love.graphics.newImage(Assets.resolve(path)) -- headless stub
|
||||
end
|
||||
local id = Assets.imageData(path)
|
||||
id:mapPixel(function(_, _, r, _, _, a)
|
||||
-- the extracted art is the four DMG grays, keyed off the red channel
|
||||
-- exactly the way PaletteFX's shade-remap shader keys them
|
||||
local v = 0
|
||||
if r > 0.5 then v = 1 -- OBJ colors 0 and 1 -> shade 0
|
||||
elseif r > 0.17 then v = 170 / 255 -- OBJ color 2 -> shade 1
|
||||
end -- OBJ color 3 -> shade 3
|
||||
return v, v, v, a
|
||||
end)
|
||||
return love.graphics.newImage(id)
|
||||
end
|
||||
|
||||
local function drawIcon(game, mon, x, y, selected, counter)
|
||||
local icons = game.data.icons
|
||||
if not icons then return end
|
||||
@@ -98,14 +180,26 @@ local function drawIcon(game, mon, x, y, selected, counter)
|
||||
end
|
||||
path = require("src.pokemon.Sprites").iconPath(game.data, mon, path, { name = name })
|
||||
if not path then return end
|
||||
if iconImages[path] == nil then
|
||||
-- Built-in icon classes are DMG 2bpp OBJ art and get the OBP0 bake; a
|
||||
-- mod's own image (an entry table rather than an icon name) is authored
|
||||
-- art with no hardware counterpart, so it loads untouched -- the same
|
||||
-- split PartyMenu.mirrorsIcon makes for the OAM mirror. Both live in one
|
||||
-- cache under different keys, so a mod pointing a table entry at a
|
||||
-- built-in path still gets its unbaked copy. #274
|
||||
local key = name and (path .. "#obp") or path
|
||||
if iconImages[key] == nil then
|
||||
-- resolve through Assets so an overrides/ or transform-derived icon
|
||||
-- (e.g. a per-species image at assets/generated/icons/<name>.png) is
|
||||
-- picked up the same way battle sprites are
|
||||
local ok, img = pcall(love.graphics.newImage, Assets.resolve(path))
|
||||
iconImages[path] = ok and img or false
|
||||
local ok, img
|
||||
if name then
|
||||
ok, img = pcall(obpIcon, path)
|
||||
else
|
||||
ok, img = pcall(love.graphics.newImage, Assets.resolve(path))
|
||||
end
|
||||
iconImages[key] = ok and img or false
|
||||
end
|
||||
local img = iconImages[path]
|
||||
local img = iconImages[key]
|
||||
if not img then return end
|
||||
local alt = false
|
||||
if selected then
|
||||
@@ -118,10 +212,28 @@ local function drawIcon(game, mon, x, y, selected, counter)
|
||||
alt = false
|
||||
end
|
||||
local iw, ih = img:getDimensions()
|
||||
if ih > 16 then
|
||||
local frame = PartyMenu.frameFor(name, alt, ih)
|
||||
-- a 16x16 sheet (BALL, HELIX) is its own only frame
|
||||
local frame = ih > 16 and PartyMenu.frameFor(name, alt, ih) or 0
|
||||
if PartyMenu.mirrorsIcon(name) then
|
||||
-- WriteSymmetricMonPartySpriteOAM (engine/items/town_map.asm:494-534)
|
||||
-- lays each icon out as 2x2 OAM blocks that use only the frame's LEFT
|
||||
-- column of tiles (base+0, base+2): the inner loop writes the same
|
||||
-- wOAMBaseTile twice with the attributes alternating 0 / OAM_XFLIP and
|
||||
-- only then bumps the tile by 2, because "all the sprites other than
|
||||
-- the helix one have a vertical line of symmetry". MON / FAIRY / BIRD
|
||||
-- reuse overworld sheets whose walk-down frame is NOT symmetric, so
|
||||
-- drawing the raw 16x16 showed a tucked-back foot the hardware never
|
||||
-- displays (#276, absorbing #238).
|
||||
local half = love.graphics.newQuad(0, frame * 16, 8, 16, iw, ih)
|
||||
love.graphics.draw(img, half, x, y)
|
||||
-- sx = -1 about the block's right edge, so the flipped copy lands on
|
||||
-- x+8..x+16: the OAM_XFLIP half
|
||||
love.graphics.draw(img, half, x + 16, y, 0, -1, 1)
|
||||
elseif ih > 16 then
|
||||
love.graphics.draw(img, love.graphics.newQuad(0, frame * 16, 16, 16, iw, ih), x, y)
|
||||
else
|
||||
-- HELIX and any mod art that is a single frame: drawn whole, at
|
||||
-- whatever size the file is (unchanged path)
|
||||
love.graphics.draw(img, x, y)
|
||||
end
|
||||
end
|
||||
@@ -134,6 +246,11 @@ function PartyMenu.new(game, opts)
|
||||
self.onSwitch = opts.onSwitch
|
||||
self.onCancel = opts.onCancel
|
||||
self.pickOnly = opts.pickOnly
|
||||
-- Medicine keeps the picker on screen: item_effects.asm .doneHealing
|
||||
-- animates the party HP bar and then prints the message through
|
||||
-- RedrawPartyMenu with the menu STILL up, so BagMenu asks for keepOpen and
|
||||
-- calls :close() itself once the message is done (#252).
|
||||
self.keepOpen = opts.keepOpen
|
||||
-- TM/HM teaching: opts.tmhm = { move, kind } switches the list to Gen 1's
|
||||
-- TM/HM display (ABLE / NOT ABLE per mon instead of the HP bar, and the
|
||||
-- "Use TM on which POKeMON?" prompt). Set by BagMenu.pickTargetAndUse. #210
|
||||
@@ -148,9 +265,47 @@ function PartyMenu.new(game, opts)
|
||||
return self
|
||||
end
|
||||
|
||||
-- UpdateHPBar2 (engine/gfx/hp_bar.asm, predef'd from item_effects.asm's
|
||||
-- .doneHealing): UpdateHPBar_AnimateHPBar is documented "for (a) ticks (two
|
||||
-- waiting frames each)" over a 48-pixel bar, so the shown HP walks
|
||||
-- maxHP/96 per frame -- the same rate the battle HUD drains at
|
||||
-- (BattleState:stepHPDrain). onDone fires on the frame it lands, which is
|
||||
-- when the caller prints its message. #252
|
||||
function PartyMenu:animateTo(mon, fromHP, onDone)
|
||||
if not (mon and mon.stats) then
|
||||
if onDone then onDone() end
|
||||
return
|
||||
end
|
||||
local from = math.max(0, fromHP or mon.hp)
|
||||
-- `from` outlives `shown`: sgbPalettes above needs the pre-heal HP for the
|
||||
-- whole fill, because the SGB bar color does not move until the redraw.
|
||||
self.heal = { mon = mon, from = from, shown = from, onDone = onDone }
|
||||
end
|
||||
|
||||
-- Close a picker the caller kept open (see self.keepOpen). A TextBox pops
|
||||
-- itself BEFORE it fires onDone (src/render/TextBox.lua), so this menu is
|
||||
-- the top state by then; the identity check makes it a no-op for the pickers
|
||||
-- that already popped themselves, and stops a double close eating the bag
|
||||
-- underneath. #252
|
||||
function PartyMenu:close()
|
||||
if self.game.stack:top() == self then self.game.stack:pop() end
|
||||
end
|
||||
|
||||
function PartyMenu:update(dt)
|
||||
-- icon animation counter; 320 = a whole cycle at every HP speed
|
||||
self.blink = ((self.blink or 0) + 1) % 320
|
||||
-- The bar fill owns the menu while it runs: UpdateHPBar2 is a blocking
|
||||
-- predef in item_effects.asm, so no button is read until it lands (#252).
|
||||
local heal = self.heal
|
||||
if heal then
|
||||
heal.shown = math.min(heal.mon.hp,
|
||||
heal.shown + math.max(1, heal.mon.stats.hp) / 96)
|
||||
if heal.shown >= heal.mon.hp then
|
||||
self.heal = nil
|
||||
if heal.onDone then heal.onDone() end
|
||||
end
|
||||
return
|
||||
end
|
||||
local input = self.game.input
|
||||
local party = self.party or self.game.save.party
|
||||
|
||||
@@ -369,8 +524,12 @@ function PartyMenu:update(dt)
|
||||
end
|
||||
self.swapFrom = nil
|
||||
elseif self.onSwitch and (self.forceSwitch or self.pickOnly or not self.battle) then
|
||||
self.game.stack:pop()
|
||||
self.onSwitch(mon)
|
||||
-- keepOpen callers (HP medicine) need the menu still drawn while the
|
||||
-- bar fills and the message prints, and close it themselves; everyone
|
||||
-- else keeps the old pop-then-call order. Popping first is what made
|
||||
-- a POTION snap the picker shut before the item had even run (#252).
|
||||
if not self.keepOpen then self.game.stack:pop() end
|
||||
self.onSwitch(mon, self)
|
||||
else
|
||||
self.submenu = true
|
||||
self.subIndex = 1
|
||||
@@ -391,7 +550,7 @@ function PartyMenu:update(dt)
|
||||
-- Battle still excludes this list via `not self.battle`. Softboiled
|
||||
-- can appear for a fainted user; its heal transfer then no-ops.
|
||||
if not self.battle and ow then
|
||||
-- FLY/TELEPORT: CheckIfInOutsideMap (OVERWORLD + PLATEAU —
|
||||
-- FLY/TELEPORT: CheckIfInOutsideMap (OVERWORLD + PLATEAU --
|
||||
-- Route 23 / Indigo Plateau outdoor), not OVERWORLD alone (#83)
|
||||
local outside = Map.isOutside(ow.map.def,
|
||||
FieldDefaults.field(self.game.data, "outsideTilesets"))
|
||||
@@ -486,6 +645,16 @@ function PartyMenu:draw()
|
||||
Font.draw(Strings("No POKéMON!"), 16, 64)
|
||||
end
|
||||
local HudTiles = require("src.render.HudTiles")
|
||||
local PaletteFX = require("src.render.PaletteFX")
|
||||
-- Each bar row carries its own GREENBAR / YELLOWBAR / REDBAR zone (see
|
||||
-- sgbPalettes), so the fill must stay the raw DMG shade-2 gray and let
|
||||
-- the zone color it -- but only when a zone pass will actually run.
|
||||
-- Renderer's blit takes the shader path exactly when the zone list is
|
||||
-- non-empty AND PaletteFX.shader() resolves, which is the same pair of
|
||||
-- conditions tested here; with no shader the canvas blits unshaded and
|
||||
-- drawHPBar's per-pixel tint is the only color the bar can get. #274
|
||||
local barZoned = PaletteFX.shader() ~= nil
|
||||
and PaletteFX.pal(self.game.data, "GREENBAR") ~= nil
|
||||
for i, mon in ipairs(party) do
|
||||
local def = self.game.data.pokemon[mon.species]
|
||||
local y = PartyMenu.entryY(i)
|
||||
@@ -525,11 +694,25 @@ function PartyMenu:draw()
|
||||
elseif mon.status then
|
||||
Font.draw(mon.status, 136, y)
|
||||
end
|
||||
-- the colored tile HP bar (DrawHP2 + SetPartyMenuHPBarColor)
|
||||
-- the tile HP bar (DrawHP2 + SetPartyMenuHPBarColor). grayFill:
|
||||
-- tinting the fill AND running it through the row's zone
|
||||
-- double-applies -- a green fill has red channel 0, so the tint
|
||||
-- zeroes the bar's red and the zone's red-keyed shade shader then
|
||||
-- maps every pixel to color 3, i.e. black. That is the #229 hazard
|
||||
-- HudTiles documents; #274 (with #272) is this screen's instance.
|
||||
--
|
||||
-- While a medicine's UpdateHPBar2 fill runs, this row draws the HP the
|
||||
-- animation has reached rather than the final value; drawHPBar reads
|
||||
-- only .hp and .stats, so a shim table is enough and the real mon is
|
||||
-- never mutated for display (#252).
|
||||
local shown = mon
|
||||
if self.heal and self.heal.mon == mon then
|
||||
shown = { hp = math.floor(self.heal.shown), stats = mon.stats }
|
||||
end
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
HudTiles.drawHPBar(self.game.data, 5, (y + 8) / 8, mon)
|
||||
HudTiles.drawHPBar(self.game.data, 5, (y + 8) / 8, shown, nil, barZoned)
|
||||
love.graphics.setColor(0, 0, 0, 1)
|
||||
Font.draw(("%3d/%3d"):format(mon.hp, mon.stats.hp), 104, y + 8)
|
||||
Font.draw(("%3d/%3d"):format(shown.hp, mon.stats.hp), 104, y + 8)
|
||||
end
|
||||
-- home/pokemon.asm PartyMenuInit seeds wTopMenuItemY/X with 1/0, so the
|
||||
-- cursor sits on the entry's *second* tile row (the level/HP line),
|
||||
|
||||
+71
-18
@@ -13,6 +13,7 @@ local Font = require("src.render.Font")
|
||||
-- battle move-type box already do (#214).
|
||||
local TypeChart = require("src.battle.TypeChart")
|
||||
local Strings = require("src.core.Strings")
|
||||
local Stats = require("src.pokemon.Stats")
|
||||
|
||||
local SummaryMenu = {}
|
||||
SummaryMenu.__index = SummaryMenu
|
||||
@@ -30,6 +31,16 @@ function SummaryMenu:sgbPalettes(game)
|
||||
end
|
||||
|
||||
function SummaryMenu.new(game, mon)
|
||||
-- status_screen.asm:66-76: StatusScreen recalculates the stat block before
|
||||
-- it draws anything when the mon came from a box or the daycare ("mon is
|
||||
-- in a box or daycare" -> CalcStats), because box_struct carries none.
|
||||
-- Bill's PC hands us that mon table directly (src/ui/BoxMenu.lua's STATS
|
||||
-- submenu entry), and for a .sav imported through
|
||||
-- src/save_convert/GenSave.lua it really does arrive with mon.stats nil,
|
||||
-- which crashed the HP bar draw below (#233). Redundant once
|
||||
-- SaveData.validate has run over a loaded save, but this is the site the
|
||||
-- original recomputes at, and it also covers a mon handed in by a mod.
|
||||
Stats.ensure(game.data.pokemon[mon.species], mon)
|
||||
local self = setmetatable({ game = game, mon = mon, page = 1 }, SummaryMenu)
|
||||
local Sprites = require("src.pokemon.Sprites")
|
||||
local path = Sprites.path(game.data, mon.species, "front",
|
||||
@@ -59,10 +70,31 @@ end
|
||||
-- drawn from the same HUD tiles the original loads
|
||||
local function drawLineBox(tx, ty, b, c)
|
||||
local HudTiles = require("src.render.HudTiles")
|
||||
for i = 0, b - 1 do HudTiles.tile(0x73, tx * 8, (ty + i) * 8) end
|
||||
HudTiles.tile(0x77, tx * 8, (ty + b) * 8)
|
||||
for i = 1, c do HudTiles.tile(0x76, (tx - i) * 8, (ty + b) * 8) end
|
||||
HudTiles.tile(0x6F, (tx - c - 1) * 8, (ty + b) * 8)
|
||||
-- Under the status screen's overlay the vertical is $78 -- DrawLineBox
|
||||
-- writes `ld [hl], $78` (status_screen.asm:222), and :90-93 is what puts
|
||||
-- hud_2's single bar tile there. $73 is the <ID> glyph on this screen,
|
||||
-- not a line, so the whole box has to come off statusTile (#280). The
|
||||
-- drawn shapes are unchanged: hud_2 tile 0 is the same bar the battle
|
||||
-- layout parks at $73.
|
||||
for i = 0, b - 1 do HudTiles.statusTile(0x78, tx * 8, (ty + i) * 8) end
|
||||
HudTiles.statusTile(0x77, tx * 8, (ty + b) * 8)
|
||||
for i = 1, c do HudTiles.statusTile(0x76, (tx - i) * 8, (ty + b) * 8) end
|
||||
HudTiles.statusTile(0x6F, (tx - c - 1) * 8, (ty + b) * 8)
|
||||
end
|
||||
|
||||
-- home/pokemon.asm:335-345 PrintLevel: the "<LV>" (":L") tile at (tx,ty)
|
||||
-- then the level LEFT_ALIGNed after it; at level 100 hl is decremented so
|
||||
-- the third digit is written back OVER the ":L" tile. Both status pages
|
||||
-- print a level this way, and src/ui/PartyMenu.lua models the same rule for
|
||||
-- its rows. #280
|
||||
local function printLevel(tx, ty, level)
|
||||
local HudTiles = require("src.render.HudTiles")
|
||||
local x = tx * 8
|
||||
if level < 100 then
|
||||
HudTiles.statusTile(0x6E, x, ty * 8)
|
||||
x = x + 8
|
||||
end
|
||||
Font.draw(tostring(level), x, ty * 8)
|
||||
end
|
||||
|
||||
function SummaryMenu:draw()
|
||||
@@ -73,21 +105,33 @@ function SummaryMenu:draw()
|
||||
local data = game.data
|
||||
local def = data.pokemon[mon.species]
|
||||
|
||||
-- shared header: pic (1,0), name (9,1), <LV> (14,2), No. (1,7)
|
||||
-- shared header: pic (1,0), name (9,1), № + dex number (1,7). The pic is
|
||||
-- MIRRORED -- status_screen.asm:170 draws it through
|
||||
-- LoadFlippedFrontSpriteByMonIndex (home/pokemon.asm sets wSpriteFlipped),
|
||||
-- the same routine the intro's NIDORINO show-off uses (OakSpeech picFlip:
|
||||
-- negative x scale anchored at the pic's right edge). #280
|
||||
if self.sprite then
|
||||
love.graphics.draw(self.sprite, 8,
|
||||
math.max(0, 56 - self.sprite:getHeight()))
|
||||
love.graphics.draw(self.sprite, 8 + self.sprite:getWidth(),
|
||||
math.max(0, 56 - self.sprite:getHeight()), 0, -1, 1)
|
||||
end
|
||||
local HudTiles = require("src.render.HudTiles")
|
||||
love.graphics.setColor(0, 0, 0, 1)
|
||||
Font.draw(mon.nickname or def.name, 72, 8)
|
||||
HudTiles.tile(0x6E, 112, 16) -- <LV>
|
||||
Font.draw(tostring(mon.level), 120, 16)
|
||||
Font.draw(("No.%03d"):format(def.dex or 0), 8, 56)
|
||||
-- status_screen.asm:109-113 backs hl up from DrawLineBox's end to write
|
||||
-- the single-tile '№' at (1,7) and '<DOT>' at (2,7); :143-146 then
|
||||
-- PrintNumbers the dex number (LEADING_ZEROES, 3 digits) at (3,7).
|
||||
-- Spelling "No." out of three letter tiles pushed every digit a column
|
||||
-- right of the original. #280
|
||||
HudTiles.statusTile(0x74, 8, 56) -- №
|
||||
Font.drawCode(0xF2, 16, 56) -- <DOT> (charmap.asm:182)
|
||||
Font.draw(("%03d"):format(def.dex or 0), 24, 56)
|
||||
|
||||
if self.page == 1 then
|
||||
-- HP bar (11,3) + numbers row 4, STATUS/ (9,6), the DrawLineBox
|
||||
-- bracket around the name/HP block
|
||||
-- bracket around the name/HP block, and PrintLevel at (14,2). The
|
||||
-- level belongs to page 1 ONLY: StatusScreen2 opens with ClearScreenArea
|
||||
-- over (9,2) 5x10 (status_screen.asm:303-305), which wipes it. #280
|
||||
printLevel(14, 2, mon.level)
|
||||
drawLineBox(19, 1, 6, 10)
|
||||
HudTiles.drawHPBar(data, 11, 3, mon, 1) -- wHPBarType 1
|
||||
Font.draw(("%3d/%3d"):format(mon.hp, mon.stats.hp), 96, 32)
|
||||
@@ -114,7 +158,12 @@ function SummaryMenu:draw()
|
||||
Font.draw(Strings("TYPE2/"), 80, 88)
|
||||
Font.draw(TypeChart.displayName(def.types[2]), 88, 96)
|
||||
end
|
||||
Font.draw(Strings("IDNo/"), 80, 104)
|
||||
-- TypesIDNoOTText's third row is "<ID>№/" (status_screen.asm:205-210):
|
||||
-- two single-tile glyphs and a slash, three columns wide, not the five
|
||||
-- letter tiles "IDNo/" this used to spell out. #280
|
||||
HudTiles.statusTile(0x73, 80, 104) -- <ID>
|
||||
HudTiles.statusTile(0x74, 88, 104) -- №
|
||||
Font.draw("/", 96, 104)
|
||||
-- the trainer ID is rolled at new game (SaveData.newGame) and
|
||||
-- backfilled on load for old saves
|
||||
Font.draw(("%05d"):format(mon.otId or game.save.player.id or 0), 96, 112)
|
||||
@@ -124,17 +173,21 @@ function SummaryMenu:draw()
|
||||
-- page 2: EXP + the moves with PP (StatusScreen2)
|
||||
drawLineBox(19, 1, 6, 10)
|
||||
Font.draw(Strings("EXP POINTS"), 72, 24)
|
||||
Font.draw(("%d"):format(mon.exp), 96, 32)
|
||||
-- StatusScreen2: "LEVEL UP" at (9,5); next-exp PrintNumber 7 cols
|
||||
-- at (7,6); space at (14,6); PrintLevel at (16,6). The old
|
||||
-- "%d to L%d" string at x=88 overflowed the DrawLineBox edge.
|
||||
-- PrintNumber at (12,4) with 7 columns: the exp is RIGHT-aligned into
|
||||
-- cols 12-18 (status_screen.asm:400-403), not left-aligned from col 12.
|
||||
-- #280
|
||||
Font.draw(("%7d"):format(mon.exp), 96, 32)
|
||||
-- StatusScreen2: "LEVEL UP" at (9,5); next-exp PrintNumber 7 cols at
|
||||
-- (7,6); the narrow '<to>' tile at (14,6); PrintLevel at (16,6)
|
||||
-- (status_screen.asm:393-403). The old "%d to L%d" string at x=88
|
||||
-- overflowed the DrawLineBox edge.
|
||||
Font.draw(Strings("LEVEL UP"), 72, 40)
|
||||
local Growth = require("src.pokemon.Growth")
|
||||
local nextExp = mon.level < 100
|
||||
and (Growth.expForLevel(def.growthRate, mon.level + 1) - mon.exp) or 0
|
||||
Font.draw(("%7d"):format(math.max(0, nextExp)), 56, 48)
|
||||
HudTiles.tile(0x6E, 128, 48) -- <LV>
|
||||
Font.draw(tostring(math.min(100, mon.level + 1)), 136, 48)
|
||||
HudTiles.statusTile(0x70, 112, 48) -- '<to>' at (14,6), was missing (#280)
|
||||
printLevel(16, 6, math.min(100, mon.level + 1))
|
||||
Font.drawBox(0, 8, 20, 10)
|
||||
for i = 1, 4 do
|
||||
local mv = mon.moves[i]
|
||||
|
||||
Reference in New Issue
Block a user