mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-22 21:46:51 +02:00
Fix Gen 1/2 battle, menu, PC and audio parity gaps; wire luacheck into CI
CLOSES #1484, CLOSES #1486, CLOSES #1513, CLOSES #1517, CLOSES #1556, CLOSES #1564, CLOSES #1567
This commit is contained in:
@@ -2707,7 +2707,9 @@ function BattleState:pushCaught(enemy, itemId)
|
||||
-- so the contest branch is BELOW them (item_effects.asm:528-546), and
|
||||
-- CheckReceivedDex gates the pair (:532-533).
|
||||
if not knew and self:hasPokedex() then
|
||||
-- data/text/common_3.asm:285
|
||||
self:push({ kind = "message",
|
||||
sfx = "Sfx_SlotMachineStart", waitSfx = true,
|
||||
text = self:name(enemy) .. "'s data was newly added to the #DEX." })
|
||||
self:push({ kind = "dex-entry", species = enemy.species })
|
||||
end
|
||||
|
||||
+86
-12
@@ -47,6 +47,7 @@ local Font = require("src.render.Font")
|
||||
local GbcPalette = require("src.render.GbcPalette")
|
||||
local Mail = require("src.core.gen2.Mail")
|
||||
local Palettes = require("src.world.gen2.Palettes")
|
||||
local PartyMenu = require("src.ui.gen2.PartyMenu")
|
||||
local Screens = require("src.ui.Screens")
|
||||
local Sound = require("src.core.Sound")
|
||||
local Unown = require("src.core.gen2.Unown")
|
||||
@@ -65,6 +66,15 @@ local PIC_X, PIC_Y = 1, 4
|
||||
-- 7-size blank tiles per column (engine/gfx/load_pics.asm:342-386).
|
||||
local PIC_PAD = { [7] = { 0, 0 }, [6] = { 1, 1 }, [5] = { 1, 2 } }
|
||||
|
||||
-- gfx/pc/orange.pal, for a cache that predates menu_gfx.billsPc.
|
||||
local BILLS_PC_ORANGE = {
|
||||
{ 255, 123, 0 }, { 189, 99, 0 }, { 123, 58, 0 }, { 0, 0, 0 },
|
||||
}
|
||||
|
||||
-- PCMonInfo prints the held-item icon at hlcoord 7, 12
|
||||
-- (engine/pokemon/bills_pc.asm:1093).
|
||||
local ICON_X, ICON_Y = 7, 12
|
||||
|
||||
-- wBillsPC_LoadedBox: 0 is the PARTY, 1..NUM_BOXES are the boxes. Only the
|
||||
-- MOVE screen ever loads box 0; the withdraw and deposit lists are one list
|
||||
-- each (BillsPC_BoxName reads the same byte for all three).
|
||||
@@ -272,6 +282,8 @@ function BoxMenu:beginMove()
|
||||
-- to the submenu; here the message holds until a button clears it and the
|
||||
-- list comes back, which is the same place the player ends up.
|
||||
self.phase = nil
|
||||
-- engine/pokemon/bills_pc.asm:1607
|
||||
self:playSfx("Sfx_Wrong")
|
||||
self.message = reason
|
||||
return
|
||||
end
|
||||
@@ -299,9 +311,13 @@ end
|
||||
function BoxMenu:doWithdraw()
|
||||
local ok, result = Boxes.withdraw(self.save, self.boxIndex, self.index)
|
||||
if not ok then
|
||||
-- engine/pokemon/bills_pc.asm:1845
|
||||
self:playSfx("Sfx_Wrong")
|
||||
self.message = result
|
||||
return
|
||||
end
|
||||
-- engine/pokemon/bills_pc.asm:1817
|
||||
self:playMonCry(result)
|
||||
self.message = nil
|
||||
self.phase = nil
|
||||
self:clampIndex()
|
||||
@@ -311,9 +327,13 @@ end
|
||||
function BoxMenu:doDeposit()
|
||||
local ok, result = Boxes.deposit(self.save, self.index, self.boxIndex)
|
||||
if not ok then
|
||||
-- engine/pokemon/bills_pc.asm:1790
|
||||
self:playSfx("Sfx_Wrong")
|
||||
self.message = result
|
||||
return
|
||||
end
|
||||
-- engine/pokemon/bills_pc.asm:1762
|
||||
self:playMonCry(result)
|
||||
self.message = nil
|
||||
self.phase = nil
|
||||
self.index, self.scroll = 1, 0
|
||||
@@ -486,6 +506,8 @@ function BoxMenu:update(_dt)
|
||||
if not ok then
|
||||
-- .no_space: `dec [hl]` puts the jumptable back on .PrepInsertCursor,
|
||||
-- so the refusal leaves the cursor exactly where it was.
|
||||
-- engine/pokemon/bills_pc.asm:1567
|
||||
self:playSfx("Sfx_Wrong")
|
||||
self.message = reason
|
||||
else
|
||||
self:insertMon()
|
||||
@@ -528,6 +550,14 @@ function BoxMenu:playSfx(name)
|
||||
if sfx and sfx[Sound.resolve(data, name)] then Sound.play(data, name) end
|
||||
end
|
||||
|
||||
-- PlayMonCry: `call GetCryIndex / jr c, .done` (home/pokemon.asm:101)
|
||||
function BoxMenu:playMonCry(mon)
|
||||
local data = self.game and self.game.data
|
||||
if not (data and mon and mon.species) or mon.isEgg then return end
|
||||
local cries = data.audio and data.audio.cries
|
||||
if cries and cries[mon.species] then Sound.playCry(data, mon.species) end
|
||||
end
|
||||
|
||||
-- BillsPC's RELEASE, which the model has always supported and nothing on
|
||||
-- screen reached. The cart asks first and starts the prompt on NO, the way
|
||||
-- every irreversible choice in the game does.
|
||||
@@ -541,6 +571,8 @@ function BoxMenu:askRelease()
|
||||
local allowed, refusal = self:checkMailPreventBlackout()
|
||||
if not allowed then
|
||||
self.phase = nil
|
||||
-- engine/pokemon/bills_pc.asm:1607
|
||||
self:playSfx("Sfx_Wrong")
|
||||
self.message = refusal
|
||||
return
|
||||
end
|
||||
@@ -568,6 +600,8 @@ function BoxMenu:askRelease()
|
||||
self.message = err
|
||||
return
|
||||
end
|
||||
-- engine/pokemon/bills_pc.asm:1866
|
||||
self:playMonCry(mon)
|
||||
self.message = name .. " was released."
|
||||
self.phase = nil
|
||||
self:clampIndex()
|
||||
@@ -627,14 +661,31 @@ function BoxMenu:picFor(mon)
|
||||
return self:image(path)
|
||||
end
|
||||
|
||||
-- engine/gfx/cgb_layouts.asm:284-300, engine/pokemon/bills_pc.asm:356-369
|
||||
function BoxMenu:panelColors(speciesId, shiny)
|
||||
if self.phase == "submenu" or self.phase == "insert" then
|
||||
return self.palettes
|
||||
and Palettes.monColors(self.palettes, speciesId, shiny)
|
||||
end
|
||||
local gfx = (self.menuGfx or {}).billsPc
|
||||
return (gfx and gfx.orangePalette) or BILLS_PC_ORANGE
|
||||
end
|
||||
|
||||
-- ClearBox runs before `cp -1 / ret z` (engine/pokemon/bills_pc.asm:1009-1021)
|
||||
function BoxMenu:fillPicBlock(colors)
|
||||
local G = love.graphics
|
||||
local blank = colors and GbcPalette.color(colors, 1) or { 255, 255, 255 }
|
||||
G.setColor(blank[1] / 255, blank[2] / 255, blank[3] / 255, 1)
|
||||
G.rectangle("fill", PIC_X * 8, PIC_Y * 8, 7 * 8, 7 * 8)
|
||||
G.setColor(1, 1, 1, 1)
|
||||
end
|
||||
|
||||
-- PCMonInfo lays the padded pic as one 7x7 block at hlcoord 1, 4
|
||||
-- (engine/pokemon/bills_pc.asm:1023-1042), the pad tiles at the palette's 0.
|
||||
function BoxMenu:drawPicBlock(image, colors)
|
||||
if not image then return end
|
||||
local G = love.graphics
|
||||
local blank = colors and GbcPalette.color(colors, 1) or { 255, 255, 255 }
|
||||
G.setColor(blank[1] / 255, blank[2] / 255, blank[3] / 255, 1)
|
||||
G.rectangle("fill", PIC_X * 8, PIC_Y * 8, 7 * 8, 7 * 8)
|
||||
self:fillPicBlock(colors)
|
||||
|
||||
local pad = PIC_PAD[math.floor(image:getWidth() / 8)] or PIC_PAD[7]
|
||||
G.setColor(1, 1, 1, 1)
|
||||
@@ -650,12 +701,12 @@ function BoxMenu:drawPicBlock(image, colors)
|
||||
end
|
||||
|
||||
function BoxMenu:drawPic(mon)
|
||||
local image = self:picFor(mon)
|
||||
if not image then return end
|
||||
-- _CGB_BillsPC hands wTempMonDVs to GetPlayerOrMonPalettePointer, so the box
|
||||
-- pic takes the shiny row (engine/gfx/cgb_layouts.asm:292-293).
|
||||
local colors = self.palettes
|
||||
and Palettes.monColors(self.palettes, mon.species, mon.shiny)
|
||||
local colors = self:panelColors(mon.species, mon.shiny)
|
||||
local image = self:picFor(mon)
|
||||
-- engine/pokemon/bills_pc.asm:1009-1011
|
||||
if not image then return self:fillPicBlock(colors) end
|
||||
self:drawPicBlock(image, colors)
|
||||
end
|
||||
|
||||
@@ -664,17 +715,14 @@ end
|
||||
-- with the party list's ICON_EGG standing in for a cache built before that.
|
||||
function BoxMenu:drawEggPic(mon)
|
||||
local G = love.graphics
|
||||
local colors = self.palettes
|
||||
and Palettes.monColors(self.palettes, "EGG", mon and mon.shiny)
|
||||
local colors = self:panelColors("EGG", mon and mon.shiny)
|
||||
local gfx = (self.menuGfx or {}).eggHatch
|
||||
local image = self:image(gfx and gfx.egg)
|
||||
if image then return self:drawPicBlock(image, colors) end
|
||||
self:fillPicBlock(colors)
|
||||
local entry = self.icons and self.icons.icons and self.icons.icons.ICON_EGG
|
||||
image = self:image(entry and entry.image)
|
||||
if not image then return end
|
||||
local blank = colors and GbcPalette.color(colors, 1) or { 255, 255, 255 }
|
||||
G.setColor(blank[1] / 255, blank[2] / 255, blank[3] / 255, 1)
|
||||
G.rectangle("fill", PIC_X * 8, PIC_Y * 8, 7 * 8, 7 * 8)
|
||||
-- The ICON_EGG sheet stacks its frames; the first is the egg at rest.
|
||||
local w = entry.width or 16
|
||||
local h = math.min(entry.height or 16, image:getHeight())
|
||||
@@ -694,6 +742,29 @@ function BoxMenu:drawEggPic(mon)
|
||||
G.setColor(1, 1, 1, 1)
|
||||
end
|
||||
|
||||
-- ItemIsMail picks $5c over $5d at hlcoord 7, 12
|
||||
-- (engine/pokemon/bills_pc.asm:1079-1094)
|
||||
function BoxMenu:drawHeldIcon(mon)
|
||||
local row = PartyMenu.heldMarkerRow(mon)
|
||||
if not row then return end
|
||||
local gfx = (self.menuGfx or {}).billsPc
|
||||
local image = self:image(gfx and gfx.icons)
|
||||
if not image then return end
|
||||
local ok, quad = pcall(love.graphics.newQuad, row * 8, 0, 8, 8,
|
||||
image:getDimensions())
|
||||
if not ok then return end
|
||||
local G = love.graphics
|
||||
G.setColor(1, 1, 1, 1)
|
||||
local function body() G.draw(image, quad, ICON_X * 8, ICON_Y * 8) end
|
||||
local colors = gfx and gfx.palette
|
||||
if colors and GbcPalette.available() then
|
||||
GbcPalette.with(colors, body)
|
||||
else
|
||||
body()
|
||||
end
|
||||
G.setColor(1, 1, 1, 1)
|
||||
end
|
||||
|
||||
-- The PC does not mark the selected row with a ▶: BillsPC_UpdateSelectionCursor
|
||||
-- lays 20 OBJs as a frame *around* the row -- ten tiles wide by two tall, top
|
||||
-- left at pixel (71, 25), stepping 16 pixels per row. Those cursor tiles are
|
||||
@@ -793,7 +864,10 @@ function BoxMenu:drawPanel()
|
||||
Chrome.print("\xe2\x99\x80", 5, 12)
|
||||
end
|
||||
Chrome.print(mon.name or mon.species or "?", PIC_X, 14)
|
||||
self:drawHeldIcon(mon)
|
||||
end
|
||||
else
|
||||
self:fillPicBlock(self:panelColors())
|
||||
end
|
||||
|
||||
-- BillsPC_PlaceString: Textbox at (0,15) with a one-row interior, string at
|
||||
|
||||
@@ -320,12 +320,15 @@ function ItemPcMenu:leaveDeposit()
|
||||
end
|
||||
|
||||
function ItemPcMenu:offerToDeposit(id, count)
|
||||
-- .TryDepositItem's `.no_toss` arm is a bare ret: a KEY ITEM or HM stays in
|
||||
-- the bag with no message at all.
|
||||
if self:cantToss(id) then return end
|
||||
if (count or 0) < 1 then return end
|
||||
local def = self:def(id)
|
||||
local name = (def and def.name) or id
|
||||
-- .DepositItem (engine/events/pokecenter_pc.asm:504): an item with no
|
||||
-- quantity is always x1 and never reaches .AskQuantity.
|
||||
if self:cantToss(id) then
|
||||
self:deposit(id, name, 1)
|
||||
return
|
||||
end
|
||||
self:askQuantity(count,
|
||||
{ "How many do you", "want to deposit?" },
|
||||
function(qty) self:deposit(id, name, qty) end)
|
||||
@@ -519,8 +522,11 @@ function ItemPcMenu:drawList()
|
||||
if i == self.listIndex then Chrome.cursor(5, ty) end
|
||||
Chrome.print(entry.name, 6, ty)
|
||||
-- PlaceMenuItemQuantity (engine/menus/menu_2.asm:24): the xNN is the
|
||||
-- entry's second line, right-aligned in a blank-padded 2-digit field.
|
||||
Chrome.print(TIMES .. Chrome.number(entry.count, 2), 7, ty + 1)
|
||||
-- entry's second line, right-aligned in a blank-padded 2-digit field,
|
||||
-- and an item with no quantity draws none at all (menu_2.asm:18).
|
||||
if not self:cantToss(entry.id) then
|
||||
Chrome.print(TIMES .. Chrome.number(entry.count, 2), 7, ty + 1)
|
||||
end
|
||||
elseif i == self:listTotal() then
|
||||
if i == self.listIndex then Chrome.cursor(5, ty) end
|
||||
Chrome.print("CANCEL", 6, ty)
|
||||
|
||||
@@ -192,6 +192,14 @@ function PackMenu:pocketOf(itemId)
|
||||
return (def and def.pocket) or "ITEM"
|
||||
end
|
||||
|
||||
-- engine/items/tmhm.asm:341
|
||||
function PackMenu:tmhmKey(itemId)
|
||||
local def = self.items and self.items[itemId]
|
||||
local n = def and tonumber(def.tmNumber)
|
||||
if n then return n end
|
||||
return 1000 + ((def and tonumber(def.index)) or 0)
|
||||
end
|
||||
|
||||
-- The name on the row. An inventory key with no ItemAttributes row behind it
|
||||
-- (an older cache, a mod's own item, a driver seeding an id that is not in
|
||||
-- items.lua) still has to draw something a person can read, so the id stands
|
||||
@@ -239,6 +247,15 @@ function PackMenu:rebuild()
|
||||
}
|
||||
end
|
||||
end
|
||||
-- engine/items/tmhm.asm:341 -- wTMsHMs is walked 1..57, so the TM/HM pocket
|
||||
-- has no acquisition order to preserve.
|
||||
if pocket == "TM_HM" then
|
||||
table.sort(rows, function(a, b)
|
||||
local ka, kb = self:tmhmKey(a.id), self:tmhmKey(b.id)
|
||||
if ka ~= kb then return ka < kb end
|
||||
return a.id < b.id
|
||||
end)
|
||||
end
|
||||
self.rows = rows
|
||||
self.index = math.min(self.index, #rows + 1)
|
||||
if self.index < 1 then self.index = 1 end
|
||||
@@ -359,6 +376,9 @@ function PackMenu:useSelected()
|
||||
-- with sound_dex_fanfare_50_79 between them; the PACK's box here holds
|
||||
-- all four rows at once, the way OAK_THIS_ISNT_THE_TIME's three fit.
|
||||
-- World has already set the decoration's flag and taken the box.
|
||||
if world.playSfxNamed then
|
||||
world:playSfxNamed("Sfx_DexFanfare5079")
|
||||
end
|
||||
self.message = { "There was a trophy", "inside!",
|
||||
"{PLAYER} sent the", "trophy home." }
|
||||
self:rebuild()
|
||||
@@ -641,6 +661,9 @@ function PackMenu:openTeachParty(row)
|
||||
if id == moveId then allowed = true end
|
||||
end
|
||||
if not allowed then
|
||||
-- engine/items/tmhm.asm:131
|
||||
local world = game.world
|
||||
if world and world.playSfxNamed then world:playSfxNamed("Sfx_Wrong") end
|
||||
if game.say then
|
||||
game:say(("%s can't learn %s!"):format(
|
||||
require("src.battle.gen2.Mon").displayName(mon), moveName))
|
||||
@@ -740,7 +763,9 @@ function PackMenu:update(_dt)
|
||||
end
|
||||
|
||||
-- engine/items/pack.asm:1290 Pack_InterpretJoypad .select
|
||||
-- engine/items/tmhm.asm:207 -- the TM/HM pocket's joypad filter drops SELECT.
|
||||
function PackMenu:armSwitch()
|
||||
if self:pocket().id == "TM_HM" then return end
|
||||
if self:isCancel() then return end
|
||||
if not self.rows[self.index] then return end
|
||||
self.switching = self.index
|
||||
|
||||
Reference in New Issue
Block a user