From c2c7fdafcf32764167572e6c562515fac1d9c733 Mon Sep 17 00:00:00 2001 From: bryanthaboi Date: Tue, 18 Aug 2026 10:36:44 -0400 Subject: [PATCH] CLOSES #1430 --- src/ui/gen2/MartMenu.lua | 49 ++++++++++++++++--------- tests/gen2_screen_layout_test.lua | 59 +++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 16 deletions(-) diff --git a/src/ui/gen2/MartMenu.lua b/src/ui/gen2/MartMenu.lua index 2fff4764..45e7aa29 100644 --- a/src/ui/gen2/MartMenu.lua +++ b/src/ui/gen2/MartMenu.lua @@ -24,14 +24,18 @@ -- MoneyTopRightMenuHeader menu_coords 11, 0, 19, 2 -- a 9x3 box, and -- PlaceMoneyTextbox writes the amount at -- MenuBoxCoord2Tile + SCREEN_WIDTH + 1 = (12,1) --- MenuHeader_Buy menu_coords 1, 3, 19, 11 -- a 19x9 box holding 4 --- entries of two rows. ScrollingMenu_UpdateDisplay --- starts at (2,4); PlaceMenuItemName prints the name --- there and .PrintBCDPrices is handed that origin --- plus the menu's own width (8) plus SCREEN_WIDTH, so --- the price lands at (10,5). The ▲ sits on the box's --- top right corner (19,3) and the ▼ on its bottom --- right (19,11). +-- MenuHeader_Buy menu_coords 1, 3, 19, 11 -- a 19x9 rect holding 4 +-- entries of two rows. BuyMenuLoop copies that +-- header and calls ScrollingMenu; it never calls +-- InitScrollingMenu or MenuBox, so the rect is NOT +-- a framed window. ScrollingMenu_UpdateDisplay +-- calls ClearWholeMenuBox on it (spaces, no border) +-- and starts printing at (2,4); PlaceMenuItemName +-- prints the name there and .PrintBCDPrices is +-- handed that origin plus the menu's own width (8) +-- plus SCREEN_WIDTH, so the price lands at (10,5). +-- The ▲ sits on the rect's top right (19,3) and +-- the ▼ on its bottom right (19,11). -- UpdateItemDescription Textbox (0,12) interior 18x4 -- a 20x6 box -- with -- the description at (1,14) -- BuyItem_MenuHeader menu_coords 7, 15, 19, 17 -- a 13x3 box, and @@ -75,7 +79,9 @@ local SFX_TRANSACTION = "Sfx_Transaction" local MartMenu = {} MartMenu.__index = MartMenu --- engine/items/mart.asm:54 +-- The top menu overlays the mart (StandardMart .HowMayIHelpYou / +-- .AnythingElse: LoadStandardMenuHeader + PrintText). BuyMenu is +-- FadeToMenu + BlankScreen, so enterBuy shadows this for the list. MartMenu.isOpaque = false -- constants/mart_constants.asm. The `pokemart` macro emits this as one byte @@ -563,6 +569,10 @@ function MartMenu:enterBuy() self.phase = "buy" self.index = 1 self.scroll = 0 + -- BuyMenu (engine/items/mart.asm): `call FadeToMenu / farcall BlankScreen`. + -- BlankScreen fills the tilemap with spaces and the palettes with white, so + -- the mart must not keep drawing in the letterbox under the list. + self.isOpaque = (self.phase == "buy") end function MartMenu:total() @@ -618,6 +628,8 @@ end -- BuyMenu returns into StandardMart .Buy, which falls through to -- .AnythingElse; the other three dialog kinds end on their come-again line. function MartMenu:leaveBuy() + -- CloseSubmenu restores the map before .AnythingElse / the come-again line. + self.isOpaque = nil if self.martType == "STANDARD" then self:enterTop(self.text.askMore) else @@ -885,11 +897,10 @@ function MartMenu:description() return def and def.description or nil end --- The bottom visible entry's price row IS the box's bottom border row --- (entry row 10 + SCREEN_WIDTH = 11, MenuHeader_Buy's own last line), and a --- GB glyph REPLACES the tile it prints over, border and all. White under --- the seven money tiles first is that replacement; on the three rows clear --- of the border it repaints white over white. +-- The bottom visible entry's price row is MenuHeader_Buy's last line +-- (entry row 10 + SCREEN_WIDTH = 11). A GB glyph REPLACES the tile it +-- prints over, so white under the seven money tiles first is that +-- replacement. On a BlankScreen field it is white over white. local function printPriceOpaque(amount, ty) local G = love.graphics G.setColor(1, 1, 1, 1) @@ -899,8 +910,10 @@ local function printPriceOpaque(amount, ty) end function MartMenu:drawBuyList() - -- engine/items/mart.asm:542 - Chrome.box(LIST_BOX_X, LIST_BOX_Y, LIST_BOX_W, LIST_BOX_H) + -- ScrollingMenu_UpdateDisplay (engine/menus/scrolling_menu.asm) calls + -- ClearWholeMenuBox, not MenuBox: the MenuHeader_Buy rect is a cleared + -- field, not a framed window. BlankScreen already filled the tilemap + -- with spaces, so the names just land on white. for row = 1, VISIBLE_ROWS do local i = row + self.scroll local ty = LIST_Y + (row - 1) * LIST_SPACING @@ -960,6 +973,10 @@ function MartMenu:drawUnder() self:drawTopMenu() self:drawTextBox(self.topLines) elseif phase == "buy" or phase == "buyQuantity" then + -- BlankScreen (engine/overworld/player_object.asm): the whole tilemap + -- is spaces before PlaceMoneyTopRight / the scrolling list / the + -- description textbox go down. Only those last two are framed. + Chrome.clear() self:drawMoneyBox() self:drawBuyList() self:drawDescription() diff --git a/tests/gen2_screen_layout_test.lua b/tests/gen2_screen_layout_test.lua index fea37807..c5a42d89 100644 --- a/tests/gen2_screen_layout_test.lua +++ b/tests/gen2_screen_layout_test.lua @@ -179,6 +179,65 @@ check("the cursor is the column-1 arrow on the label row", check("the cursor starts on the first row", cursorAt and cursorAt.y, 2) check("FRAME still prints its literal TYPE", findPrint(":TYPE") ~= nil, true) +-- ------------------------------------------------------- mart buy list +-- +-- BuyMenu (engine/items/mart.asm) is FadeToMenu + BlankScreen, then +-- PlaceMoneyTopRight and ScrollingMenu_UpdateDisplay's ClearWholeMenuBox. +-- The list is not MenuBox'd; only the money box and UpdateItemDescription's +-- textbox are. The top menu still overlays the mart, so it must not blank. +local MartMenu = require("src.ui.gen2.MartMenu") +local Save = require("src.core.gen2.Save") + +local martBoxes, martClears = {}, 0 +local martClear, martBox = Chrome.clear, Chrome.box +local martPrint, martCursor = Chrome.print, Chrome.cursor +Chrome.clear = function() martClears = martClears + 1 end +Chrome.box = function(x, y, w, h) + martBoxes[#martBoxes + 1] = { x = x, y = y, w = w, h = h } +end +Chrome.print = function() end +Chrome.cursor = function() end + +local function martHasBox(x, y, w, h) + for _, b in ipairs(martBoxes) do + if b.x == x and b.y == y and b.w == w and b.h == h then return true end + end + return false +end + +local martSave = Save.newGame() +local martItems = { + POTION = { id = "POTION", name = "POTION", pocket = "ITEM", price = 300, + description = "Restores HP\nby 20." }, +} +local mart = MartMenu.new({ save = martSave, data = { items = martItems } }, { + save = martSave, + items = martItems, + marts = { lists = { { "POTION" } } }, +}) +mart:draw() +check("the top menu does not blank the tilemap", martClears, 0) +check("and frames BUY/SELL/QUIT", martHasBox(0, 0, 12, 9), true) +check("and the welcome speech box", martHasBox(0, 12, 20, 6), true) +check("the top menu is not opaque", mart.isOpaque, false) + +martClears, martBoxes = 0, {} +mart:enterBuy() +mart:draw() +check("BUY blanks the screen (BlankScreen)", martClears > 0, true) +check("the money box is framed", martHasBox(11, 0, 9, 3), true) +check("the description box is framed", martHasBox(0, 12, 20, 6), true) +check("the item list is not (ClearWholeMenuBox, not MenuBox)", + martHasBox(1, 3, 19, 9), false) +check("BlankScreen shadows isOpaque so the letterbox is not the mart", + mart.isOpaque, true) + +mart:leaveBuy() +check("leaving BUY unshadows isOpaque", mart.isOpaque, false) + +Chrome.clear, Chrome.box = martClear, martBox +Chrome.print, Chrome.cursor = martPrint, martCursor + -- ------------------------------------------------------- one blit scale -- -- Every Gold screen paints its 160x144 panel through the same helper. A