CLOSES #1181, CLOSES #1212, CLOSES #1214, CLOSES #1224, CLOSES #1230, CLOSES #1249, CLOSES #1271, CLOSES #1272, CLOSES #1273, CLOSES #1298, CLOSES #1305, CLOSES #1307, CLOSES #1318, CLOSES #1328, CLOSES #1330, CLOSES #1331, CLOSES #1333, CLOSES #1334, CLOSES #1335, CLOSES #1340, CLOSES #1345, CLOSES #1346, CLOSES #1360, CLOSES #1362 (#1395)

* CLOSES #1181, CLOSES #1212, CLOSES #1214, CLOSES #1224, CLOSES #1230, CLOSES #1249, CLOSES #1271, CLOSES #1272, CLOSES #1273, CLOSES #1298, CLOSES #1305, CLOSES #1307, CLOSES #1318, CLOSES #1328, CLOSES #1330, CLOSES #1331, CLOSES #1333, CLOSES #1334, CLOSES #1335, CLOSES #1340, CLOSES #1345, CLOSES #1346, CLOSES #1360, CLOSES #1362

* conv
This commit is contained in:
bryanthaboi
2026-08-16 06:55:09 -04:00
committed by GitHub
parent a34d8611bf
commit 5f2b2c616e
143 changed files with 2556 additions and 44967 deletions
+7 -5
View File
@@ -134,13 +134,15 @@ function SummaryMenu:draw()
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, 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
-- level is page 1 only: StatusScreen2 opens with ClearScreenArea over
-- (9,2) 5x10 (status_screen.asm:303-305). #280
printLevel(14, 2, mon.level)
drawLineBox(19, 1, 6, 10)
HudTiles.drawHPBar(data, 11, 3, mon, 1) -- wHPBarType 1
-- engine/pokemon/status_screen.asm:120-125
local PaletteFX = require("src.render.PaletteFX")
local barZoned = PaletteFX.shader() ~= nil
and PaletteFX.pal(data, "GREENBAR") ~= nil
HudTiles.drawHPBar(data, 11, 3, mon, 1, barZoned) -- wHPBarType 1
Font.draw(("%3d/%3d"):format(mon.hp, mon.stats.hp), 96, 32)
Font.draw(Strings("STATUS/"), 72, 48)
Font.draw(mon.status or "OK", 128, 48)
+27 -4
View File
@@ -171,6 +171,27 @@ local function markVisibleTrueColor(x, y, w, h, cover)
if ix2 < right then P.markTrueColor(ix2, iy1, right - ix2, iy2 - iy1) end
end
-- ..(engine/movie/title.asm ln 321)
local function replayObjSprite(game, image, quad, x, y)
local P = require("src.render.PaletteFX")
if not P.usesSpriteObp() then return end
local top = game.stack and game.stack:top()
local box = top and top.titleUiBox
if box then
local w, h
if quad then
w, h = select(3, quad:getViewport())
else
w, h = image:getDimensions()
end
if x < (box[3] + 1) * 8 and x + w > box[1] * 8
and y < (box[4] + 1) * 8 and y + h > box[2] * 8 then
return
end
end
P.markUiSpriteRedraw(image, quad, x, y)
end
function TitleState.new(game, opts)
opts = opts or {}
local self = setmetatable({}, TitleState)
@@ -662,12 +683,11 @@ function TitleState:draw()
local x = 40 + math.floor((56 - w) / 2) + self.monOffset
local y = 136 - h
love.graphics.draw(sprite, x, y)
-- a full-color mon keeps its own palette through the SGB pass, minus
-- the strip Red's OAM covers (#350). Yellow never reaches here: its
-- layout has no cycling mon and no Red art (title_yellow.asm).
-- SGB: the mon keeps its palette minus the strip Red's OAM covers
-- (#350); Yellow never reaches here (title_yellow.asm)
if spriteTrueColor then
local cover
if playerImage then
if playerImage and not require("src.render.PaletteFX").usesSpriteObp() then
local pw, ph = playerImage:getDimensions()
cover = { 82, 80, pw, ph }
end
@@ -678,10 +698,13 @@ function TitleState:draw()
if self.playerQuads then
for _, part in ipairs(self.playerQuads) do
love.graphics.draw(playerImage, part[1], 82 + part[2], 80 + part[3])
replayObjSprite(self.game, playerImage, part[1], 82 + part[2], 80 + part[3])
end
love.graphics.draw(playerImage, self.ballQuad, 82, self.ballY)
replayObjSprite(self.game, playerImage, self.ballQuad, 82, self.ballY)
elseif playerImage then
love.graphics.draw(playerImage, 82, 80)
replayObjSprite(self.game, playerImage, nil, 82, 80)
end
end
self:drawCopyright(136 + (preRibbon and 0 or scrollY))
+63 -25
View File
@@ -31,6 +31,7 @@ local ItemEffects = require("src.core.gen2.ItemEffects")
local Mon = require("src.battle.gen2.Mon")
local Palettes = require("src.world.gen2.Palettes")
local Pokerus = require("src.core.gen2.Pokerus")
local Prize = require("src.battle.gen2.Prize")
local Runtime = require("src.mods.Runtime")
local Screens = require("src.ui.Screens")
local Sound = require("src.core.Sound")
@@ -623,6 +624,19 @@ function BattleState:drawPic(mon, back)
and not (self.vanishAnim and self.vanishAnim == self.anim) then
return
end
-- GetSubstitutePic (engine/battle_anims/anim_commands.asm:905-960): the
-- doll sits in the mon's own pic box and takes its palette.
local doll, dollQuad
if not (trainerBack or enemyTrainer) then
local over = anim and anim.pic
local up
if over ~= nil then
up = over == "substitute"
else
up = mon and mon.volatile and (mon.volatile.substitute or 0) > 0
end
if up then doll, dollQuad = self:substituteDoll(back) end
end
local G = love.graphics
local w, h = image:getDimensions()
local px, py
@@ -662,6 +676,10 @@ function BattleState:drawPic(mon, back)
px = px + math.floor(w * (1 - scale) / 2)
py = py + math.floor(h * (1 - scale))
end
if doll then
px = (back and 32 or 112) + ((anim and anim.slide) or 0)
py = back and 80 or 40
end
G.setColor(1, 1, 1, 1)
-- No mon on this side at all in the catching tutorial, where the box holds
-- the DUDE's back-pic and nothing else for the whole battle.
@@ -684,6 +702,10 @@ function BattleState:drawPic(mon, back)
-- what is still inside the box rather than drawn over the HUD below it.
local sunk = self:faintSink(side)
local function body()
if doll then
G.draw(doll, dollQuad, px, py)
return
end
if sunk > 0 then
local visible = h - math.floor(sunk / scale)
if visible <= 0 then return end
@@ -703,6 +725,24 @@ function BattleState:drawPic(mon, back)
end
end
-- MonsterSpriteGFX (gfx/sprites.asm:82): the facing-DOWN 16x16 frame for the
-- enemy's frontpic, facing-UP for the player's backpic.
function BattleState:substituteDoll(back)
if self.subDoll == nil then
local ok, image = pcall(Assets.image, "assets/generated/sprites/monster.png")
if ok and image then
local w, h = image:getDimensions()
self.subDoll = { image = image,
down = love.graphics.newQuad(0, 0, 16, 16, w, h),
up = love.graphics.newQuad(0, 16, 16, 16, w, h) }
else
self.subDoll = false
end
end
if not self.subDoll then return nil end
return self.subDoll.image, back and self.subDoll.up or self.subDoll.down
end
-- The top `visible` rows of a pic, for the faint slide. One quad, re-aimed,
-- the way BattleAnimView keeps one blit quad rather than a new one per frame.
function BattleState:cropQuad(image, visible)
@@ -1074,16 +1114,8 @@ function BattleState:stepAnim(input)
end
end
-- NOTE on picHidden and the animation runtime. An animation that ENDS with a
-- pic box cleared could latch it here, and the tilemap argument says it should:
-- BattleAnimRestoreHuds redraws the two HUDs and nothing else. It deliberately
-- does not, because BATTLE_BG_EFFECT_REMOVE_MON and _RETURN_MON are also used
-- by moves whose user is still standing there afterwards -- SUBSTITUTE (the
-- doll takes the box over, and nothing in this port draws one yet), SKY_ATTACK,
-- BEAT_UP, BATON_PASS -- and a blanket latch would make those mons invisible
-- for the rest of the fight. The two moments the cart really does leave the
-- box empty for good are latched explicitly instead: a catch (pushCaught) and
-- a faint (MonFaintedAnimation, in update).
-- REMOVE_MON / RETURN_MON also serve SUBSTITUTE, SKY_ATTACK, BEAT_UP and
-- BATON_PASS, so picHidden is only latched by a catch and a faint.
-- Whatever Call_PlayBattleAnim was standing in front of: a send-out's cry and
-- HUD update run the moment its animation is done, cut short or not.
@@ -1103,6 +1135,7 @@ function BattleState:animPicState(side)
size = bg.picSize[side],
slide = bg.slide[side] or 0,
shade = bg.monShade[side],
pic = self.anim.picOverride[side],
}
end
@@ -1192,22 +1225,14 @@ function BattleState:advanceQueue()
if event.kind == "level" and event.index then
self.evolvable[event.index] = true
-- GiveExperiencePoints' `.skip_active_mon_update` guard
-- (engine/battle/core.asm:6999-7003): only the mon that is OUT copies its
-- recalculated HP, max HP and level into the battle struct, and only then
-- does `callfar UpdatePlayerHUD` (:7034) redraw the bar. That is a
-- REDRAW, not AnimateHPBar, so the shown HP snaps instead of chasing --
-- without it the bar kept the pre-level-up HP against the new maximum
-- until the next damage or heal event moved it.
-- (engine/battle/core.asm:6999-7003): the OUT mon's shown HP snaps.
local battle = self.battle
local mon = battle and battle.party and battle.party[event.index]
-- pokegold engine/battle/core.asm:7057-7069: every mon that leveled
-- gets the stats box, not just the mon currently on the field.
self.pendingStatsMon = mon
-- engine/battle/core.asm:7044
-- engine/battle/core.asm:7284
if mon and mon == battle.player then
event.text = nil
event.sfx = nil
event.waitSfx = nil
if self.shownHp then
self.shownHp.player = mon.hp or 0
if self.hpAnim and self.hpAnim.side == "player" then
@@ -1215,9 +1240,6 @@ function BattleState:advanceQueue()
end
end
-- `ld [wBattleMonLevel], a` in the same guarded block (:7018-7020).
-- AnimateExpBar has already walked the number up one level at a time by
-- the time this runs, so this only catches a level gained with no exp
-- crawl behind it.
self.shownLevel = mon.level or self.shownLevel
end
end
@@ -2484,7 +2506,10 @@ function BattleState:pushCaught(enemy, itemId)
self:push({ kind = "dex-entry", species = enemy.species })
end
if self.contest then
return self:contestCatch(enemy)
-- A contest catch still exits through the `.run` arm with result WIN
-- (engine/battle/core.asm:4780-4783), so CheckPayDay runs for it too.
self:contestCatch(enemy)
return self:pushPayDay()
end
save.party = save.party or {}
local toPc = #save.party >= Boxes.PARTY_SIZE
@@ -2540,6 +2565,19 @@ function BattleState:pushCaught(enemy, itemId)
self:push({ kind = "message",
text = self:name(enemy) .. " was sent to BILL's PC." })
end
self:pushPayDay()
end
-- CheckPayDay runs on a capture too: `and $f` keeps the win arm
-- (engine/battle/core.asm:7971-7976, :8014-8042).
function BattleState:pushPayDay()
local save = self.save
local coins = Prize.payDay(save, self.battle.payDay, self.battle.amuletCoin)
self.battle.payDay = nil
if coins then
self:push({ kind = "message",
text = Prize.payDayMessage(coins, save.player and save.player.name) })
end
end
-- CheckWhetherToAskSwitch: a started battle, more than one mon, no link, the
@@ -3049,7 +3087,7 @@ function BattleState:applyPartyItem(itemId, action, mon, slot)
local before = (mon and mon.hp) or 0
local result
if action == "pp" then
result = ItemEffects.usePpItem(itemId, mon, slot)
result = ItemEffects.usePpItem(itemId, mon, slot, data)
else
result = ItemEffects.useOnMon(itemId, mon, data)
end
+11
View File
@@ -172,6 +172,17 @@ local ROWS = {
text = function(options)
return require("src.render.GBCFX").levelLabel(options.gbcfx or 0)
end },
{ label = "VIDEO MODE", key = "videoMode", port = true,
cycle = function(options, delta)
local VideoMode = require("src.core.VideoMode")
options.videoMode = VideoMode.cycle(options.videoMode, delta)
VideoMode.apply(options.videoMode)
end,
text = function(options)
local VideoMode = require("src.core.VideoMode")
return VideoMode.normalize(options.videoMode) == "borderless"
and "FULL" or "WINDOWED"
end },
{ id = "touchControls", label = "TOUCH PAD", port = true,
text = function(options)
local tc = options.touchControls
+8
View File
@@ -283,6 +283,14 @@ function PartyMenu:finishSwitch()
if self.save and self.save.party == party then
Mail.swapSlots(self.save, from, to)
end
-- engine/pokemon/switchpartymons.asm:38
local data = self.game and self.game.data
local ok, Sound = pcall(require, "src.core.Sound")
if not (ok and data and Sound and Sound.play) then return end
local sfx = data.audio and data.audio.sfx
if sfx and sfx[Sound.resolve(data, "Sfx_SwitchPokemon")] then
pcall(Sound.play, data, "Sfx_SwitchPokemon")
end
end
-- The reopened list: InitPartyMenuNoCancel caps the cursor at the last mon,