mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-15 07:41:21 +02:00
1586aec9f6
Selecting a TM in the mart's SELL pack (and the item PC's DEPOSIT pack) opened the teach-party screen: PackMenu:useSelected taught any item carrying `teaches`, even when the pack was built as a chooser (DepositSellPack, world = {}). On the cart that chooser's jumptable is four ScrollingMenus and never reaches tmhm.asm, so the row must hand back to the caller instead.
Gate the teach and field-NOUSE branches behind world.useFieldItem so only the real field PACK teaches; choosers now hand TMs to onChoose. Selling then prices a TM at half its ItemAttributes price -- exactly half of what the Goldenrod/Celadon TM shelves charge, and half of the hidden price for the rest, the way SelectQuantityToSell -> GetItemPrice -> Sell_HalvePrice does on the cart.
Closes #1243
1007 lines
38 KiB
Lua
1007 lines
38 KiB
Lua
-- Gen 2 POKeMART: the whole clerk conversation (engine/items/mart.asm).
|
||
--
|
||
-- `pokemart dialog_id, mart_id` (macros/scripts/events.asm) is a BLOCKING
|
||
-- script command. Script_pokemart farcalls OpenMartDialog, which runs the top
|
||
-- menu, the buy list and the sell list to completion before the script's next
|
||
-- byte is read, so this screen owns the stack while it is up and the VM is
|
||
-- parked on its resume exactly the way it is for a battle.
|
||
--
|
||
-- Four dialog kinds share one buy list (MartTypeDialogs):
|
||
-- MARTTYPE_STANDARD MartDialog BUY / SELL / QUIT, StandardMart's loop
|
||
-- MARTTYPE_BITTER HerbShop intro -> BuyMenu -> come again
|
||
-- MARTTYPE_BARGAIN BargainShop one of each item, at its own prices
|
||
-- MARTTYPE_PHARMACY Pharmacist intro -> BuyMenu -> come again
|
||
-- Only STANDARD sells: SellMenu is reached from its top menu and nowhere else.
|
||
--
|
||
-- The screen is transcribed from the ASM's own coordinates rather than laid
|
||
-- out by eye, because that is what makes it land on the 8px grid:
|
||
--
|
||
-- MenuHeader_BuySell menu_coords 0, 0, 11, 8 -- a 12x9 box. Its flags
|
||
-- are STATICMENU_CURSOR with no
|
||
-- STATICMENU_NO_TOP_SPACING, so GetMenuTextStartCoord
|
||
-- derives BUY at (2,2) with the cursor column at 1,
|
||
-- and the three labels are two rows apart.
|
||
-- 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).
|
||
-- 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
|
||
-- SellItem_MenuHeader the same box. BuySellToss_UpdateQuantityDisplay
|
||
-- writes '×' at (8,16) and two leading-zero digits
|
||
-- after it; BuySell_DisplaySubtotal's `inc hl` then
|
||
-- puts the running total at (12,16).
|
||
-- YesNoBox `lb bc, SCREEN_WIDTH - 6, 7` -- a 6x5 box at
|
||
-- (14,7). YesNoMenuHeader sets STATICMENU_CURSOR and
|
||
-- STATICMENU_NO_TOP_SPACING, so YES is at (16,8) and
|
||
-- NO at (16,10), cursor column 15.
|
||
-- MoneyBottomLeftMenuHeader
|
||
-- menu_coords 0, 11, 8, 13 -- a 9x3 box with the
|
||
-- amount at (1,12). The sell flow's two money boxes
|
||
-- are both this rect: PlaceMoneyAtTopLeftOfTextbox
|
||
-- offsets the top-right header by lb de, 0, 11, which
|
||
-- lands on exactly these coordinates.
|
||
--
|
||
-- Every string below is transcribed literally from data/text/common_2.asm and
|
||
-- paired with its pokegold label in LABELS. Nothing in the ROM's script
|
||
-- bytecode points at any of them -- the clerk's lines are printed by the
|
||
-- engine, not by a `writetext` -- so the extractor seeds its text walker at
|
||
-- the block by name (RomExtractorGen2's NAMED_TEXT) and this screen prefers
|
||
-- the cache's own characters, falling back to the transcription for a cache
|
||
-- built before that seed.
|
||
|
||
local Bag = require("src.inventory.Bag")
|
||
local Chrome = require("src.ui.gen2.Chrome")
|
||
local CommonText = require("src.core.gen2.CommonText")
|
||
local Save = require("src.core.gen2.Save")
|
||
local Screens = require("src.ui.Screens")
|
||
local Sound = require("src.core.Sound")
|
||
|
||
-- PlayTransactionSound (engine/items/mart.asm): `call WaitSFX` then
|
||
-- SFX_TRANSACTION. Both tills ring it -- the buy flow at BuyMenuLoop's
|
||
-- .proceed, just before TakeMoney and MARTTEXT_HERE_YOU_GO, and the sell flow
|
||
-- right after MartBoughtText -- so it is the money changing hands rather than
|
||
-- either message. Named the pokegold way; Sound.GEN2_ALIASES is what maps the
|
||
-- shared UI's own "Purchase" onto this same label.
|
||
local SFX_TRANSACTION = "Sfx_Transaction"
|
||
|
||
local MartMenu = {}
|
||
MartMenu.__index = MartMenu
|
||
-- engine/items/mart.asm:54
|
||
MartMenu.isOpaque = false
|
||
|
||
-- constants/mart_constants.asm. The `pokemart` macro emits this as one byte
|
||
-- ahead of the word mart id, and MartTypeDialogs is indexed by it.
|
||
local MART_TYPES = { [0] = "STANDARD", [1] = "BITTER", [2] = "BARGAIN",
|
||
[3] = "PHARMACY" }
|
||
local NUM_MARTS = 34 -- constants/mart_constants.asm, MART_UNDERGROUND is 33
|
||
|
||
-- GetMart: an id at or past NUM_MARTS is not a mart at all and the clerk sells
|
||
-- DefaultMart instead (data/items/marts.asm).
|
||
local DEFAULT_MART = { "POKE_BALL", "POTION" }
|
||
|
||
-- MAX_ITEM_STACK, the ceiling StandardMartAskPurchaseQuantity loads into
|
||
-- wItemQuantity before the selector runs.
|
||
local MAX_ITEM_STACK = 99
|
||
|
||
-- ---------------------------------------------------------------- layout
|
||
local TOP_BOX_X, TOP_BOX_Y, TOP_BOX_W, TOP_BOX_H = 0, 0, 12, 9
|
||
local TOP_LABEL_X, TOP_LABEL_Y, TOP_SPACING = 2, 2, 2
|
||
|
||
local MONEY_BOX_X, MONEY_BOX_Y, MONEY_BOX_W, MONEY_BOX_H = 11, 0, 9, 3
|
||
local MONEY_X, MONEY_Y = 12, 1
|
||
|
||
local MONEY_LOW_BOX_X, MONEY_LOW_BOX_Y = 0, 11
|
||
local MONEY_LOW_X, MONEY_LOW_Y = 1, 12
|
||
|
||
local LIST_BOX_X, LIST_BOX_Y, LIST_BOX_W, LIST_BOX_H = 1, 3, 19, 9
|
||
local LIST_X, LIST_Y, LIST_SPACING = 2, 4, 2
|
||
local PRICE_X = LIST_X + 8 -- wMenuData_ScrollingMenuWidth
|
||
local VISIBLE_ROWS = 4 -- MenuHeader_Buy's `db 4, 8 ; rows, columns`
|
||
local ARROW_X = LIST_BOX_X + LIST_BOX_W - 1
|
||
local ARROW_UP_Y, ARROW_DOWN_Y = LIST_BOX_Y, LIST_BOX_Y + LIST_BOX_H - 1
|
||
|
||
local TEXT_BOX_X, TEXT_BOX_Y, TEXT_BOX_W, TEXT_BOX_H = 0, 12, 20, 6
|
||
-- TEXTBOX_INNERY is TEXTBOX_Y + 2 and LineChar (`line`, $4f) targets
|
||
-- TEXTBOX_INNERY + 2, so a text box's two lines are TWO rows apart -- which is
|
||
-- also where <NEXT> ($4e, SCREEN_WIDTH * 2) puts an item description's second
|
||
-- line. One rule covers both.
|
||
local TEXT_X, TEXT_Y, TEXT_LINE = 1, 14, 2
|
||
|
||
local QTY_BOX_X, QTY_BOX_Y, QTY_BOX_W, QTY_BOX_H = 7, 15, 13, 3
|
||
local QTY_X, QTY_Y = 8, 16
|
||
local QTY_PRICE_X = 12
|
||
|
||
local YESNO_X, YESNO_Y, YESNO_W, YESNO_H = 14, 7, 6, 5
|
||
|
||
-- charmap.asm: × is the quantity glyph, ¥ the currency one, and ▲ / ▼ are the
|
||
-- scrolling menu's own arrow tiles (font codes $61 and $ee).
|
||
local TIMES = "\xc3\x97"
|
||
local YEN = "\xc2\xa5"
|
||
local UP_ARROW = "\xe2\x96\xb2"
|
||
local DOWN_ARROW = "\xe2\x96\xbc"
|
||
|
||
-- PrintNum with PRINTNUM_MONEY and without PRINTNUM_LEADINGZEROS
|
||
-- (home/print_num.asm .PrintYen): the ¥ is emitted just before the FIRST
|
||
-- significant digit rather than at a fixed column, and the field is six digits
|
||
-- wide -- so the string is always seven tiles and the yen sign floats.
|
||
-- PrintBCDNumber, which the buy list's prices go through, prints the same
|
||
-- shape: a leading zero becomes a space and still advances the pointer.
|
||
local function moneyText(amount)
|
||
local digits = ("%06d"):format(math.max(0, math.floor(amount or 0)))
|
||
local first = digits:find("[1-9]") or #digits
|
||
return (" "):rep(first - 1) .. YEN .. digits:sub(first)
|
||
end
|
||
MartMenu.moneyText = moneyText
|
||
|
||
-- ----------------------------------------------------------------- text
|
||
--
|
||
-- A "page" is one screenful of the speech text box: up to two lines, `line`
|
||
-- rows apart. A `para` in the ASM starts a new page; a `cont` scrolls one
|
||
-- line, which shows as a page whose first line is the previous page's second.
|
||
local function pages(...) return { ... } end
|
||
local function page(...) return { ... } end
|
||
|
||
local TEXTS = {
|
||
-- MartDialog / StandardMart (engine/items/mart.asm).
|
||
STANDARD = {
|
||
welcome = page("Welcome! How may I", "help you?"), -- MartWelcomeText
|
||
askMore = page("Can I do anything", "else for you?"), -- MartAskMoreText
|
||
comeAgain = pages(page("Please come again!")),
|
||
howMany = page("How many?"),
|
||
thanks = pages(page("Here you are.", "Thank you!")),
|
||
noMoney = pages(page("You don't have", "enough money.")),
|
||
packFull = pages(page("You can't carry", "any more items.")),
|
||
-- MartFinalPriceText: the quantity and the total are text_decimal, which
|
||
-- sets PRINTNUM_LEFTALIGN, so neither is padded; the ¥ is a literal in the
|
||
-- string rather than PrintNum's floating one.
|
||
finalPrice = function(qty, name, total)
|
||
return pages(page(("%d %s(S)"):format(qty, name),
|
||
("will be %s%d."):format(YEN, total)))
|
||
end,
|
||
},
|
||
-- HerbShop (MARTTYPE_BITTER): the Goldenrod Underground herb lady.
|
||
BITTER = {
|
||
intro = pages(
|
||
page("Hello, dear."),
|
||
page("I sell inexpensive", "herbal medicine."),
|
||
page("They're good, but", "a trifle bitter."),
|
||
-- `#` is the four-tile POKé compression byte, which the extractor
|
||
-- expands everywhere else in the cache; spelled out it is the same
|
||
-- sixteen columns.
|
||
page("Your POKéMON may", "not like them."),
|
||
page("Hehehehe…")),
|
||
comeAgain = pages(page("Come again, dear.", "Hehehehe…")),
|
||
howMany = page("How many?"),
|
||
thanks = pages(page("Thank you, dear.", "Hehehehe…")),
|
||
noMoney = pages(page("Hehehe… You don't", "have the money.")),
|
||
packFull = pages(page("Oh? Your PACK is", "full, dear.")),
|
||
finalPrice = function(qty, name, total)
|
||
return pages(page(("%d %s(S)"):format(qty, name),
|
||
("will be %s%d."):format(YEN, total)))
|
||
end,
|
||
},
|
||
-- BargainShop (MARTTYPE_BARGAIN): one of each item, at prices carried by
|
||
-- BargainShopData rather than by ItemAttributes.
|
||
BARGAIN = {
|
||
intro = pages(
|
||
page("Hiya! Care to see", "some bargains?"),
|
||
page("I sell rare items", "that nobody else"),
|
||
page("carries--but only", "one of each item.")),
|
||
comeAgain = pages(page("Come by again", "sometime.")),
|
||
thanks = pages(page("Thanks.")),
|
||
noMoney = pages(page("Uh-oh, you're", "short on funds.")),
|
||
packFull = pages(page("Uh-oh, your PACK", "is chock-full.")),
|
||
-- `cont` scrolls the box one line instead of clearing it, so the second
|
||
-- page opens on the first page's second line.
|
||
soldOut = pages(
|
||
page("You bought that", "already. I'm all"),
|
||
page("already. I'm all", "sold out of it.")),
|
||
finalPrice = function(_qty, name, total)
|
||
return pages(page(("%s costs"):format(name),
|
||
("%s%d. Want it?"):format(YEN, total)))
|
||
end,
|
||
},
|
||
-- Pharmacist (MARTTYPE_PHARMACY): Cianwood.
|
||
PHARMACY = {
|
||
intro = pages(page("What's up? Need", "some medicine?")),
|
||
comeAgain = pages(page("All right.", "See you around.")),
|
||
howMany = page("How many?"),
|
||
thanks = pages(page("Thanks much!")),
|
||
noMoney = pages(page("Huh? That's not", "enough money.")),
|
||
packFull = pages(page("You don't have any", "more space.")),
|
||
finalPrice = function(qty, name, total)
|
||
return pages(page(("%d %s(S)"):format(qty, name),
|
||
("will cost %s%d."):format(YEN, total)))
|
||
end,
|
||
},
|
||
}
|
||
|
||
-- SellMenu's own strings. Only MARTTYPE_STANDARD ever reaches them.
|
||
local SELL_TEXTS = {
|
||
cantBuy = pages(page("Sorry, I can't buy", "that from you.")),
|
||
howMany = page("How many?"),
|
||
-- MartSellPriceText's `para` is a real page break, and the YES/NO box comes
|
||
-- up on the second page without a further press.
|
||
price = function(total)
|
||
return pages(page("I can pay you", ("%s%d."):format(YEN, total)),
|
||
page("Is that OK?"))
|
||
end,
|
||
bought = function(name, total)
|
||
return pages(page(("Got %s%d for"):format(YEN, total),
|
||
("%s(S)."):format(name)))
|
||
end,
|
||
}
|
||
|
||
MartMenu.MART_TYPES = MART_TYPES
|
||
MartMenu.TEXTS = TEXTS
|
||
|
||
-- ...and the data/text/common_2.asm label behind each of those entries.
|
||
-- Nothing in the ROM's bytecode points at any of them -- the clerk's lines are
|
||
-- printed by engine/items/mart.asm itself -- so the extractor seeds its text
|
||
-- walker at the whole block by name (RomExtractorGen2's NAMED_TEXT) and this
|
||
-- screen prefers the cache's own characters. The transcriptions above are
|
||
-- what a cache built before that seed falls back to.
|
||
local LABELS = {
|
||
STANDARD = {
|
||
welcome = "_MartWelcomeText", askMore = "_MartAskMoreText",
|
||
comeAgain = "_MartComeAgainText", howMany = "_MartHowManyText",
|
||
thanks = "_MartThanksText", noMoney = "_MartNoMoneyText",
|
||
packFull = "_MartPackFullText", finalPrice = "_MartFinalPriceText",
|
||
},
|
||
BITTER = {
|
||
intro = "_HerbShopLadyIntroText", comeAgain = "_HerbalLadyComeAgainText",
|
||
howMany = "_HerbalLadyHowManyText", thanks = "_HerbalLadyThanksText",
|
||
noMoney = "_HerbalLadyNoMoneyText", packFull = "_HerbalLadyPackFullText",
|
||
finalPrice = "_HerbalLadyFinalPriceText",
|
||
},
|
||
BARGAIN = {
|
||
intro = "_BargainShopIntroText", comeAgain = "_BargainShopComeAgainText",
|
||
thanks = "_BargainShopThanksText", noMoney = "_BargainShopNoFundsText",
|
||
packFull = "_BargainShopPackFullText", soldOut = "_BargainShopSoldOutText",
|
||
finalPrice = "_BargainShopFinalPriceText",
|
||
},
|
||
PHARMACY = {
|
||
intro = "_PharmacyIntroText", comeAgain = "_PharmacyComeAgainText",
|
||
howMany = "_PharmacyHowManyText", thanks = "_PharmacyThanksText",
|
||
noMoney = "_PharmacyNoMoneyText", packFull = "_PharmacyPackFullText",
|
||
finalPrice = "_PharmacyFinalPriceText",
|
||
},
|
||
SELL = {
|
||
cantBuy = "_MartCantBuyText", howMany = "_MartSellHowManyText",
|
||
price = "_MartSellPriceText", bought = "_MartBoughtText",
|
||
},
|
||
}
|
||
|
||
MartMenu.LABELS = LABELS
|
||
|
||
-- The three entries that are ONE screenful of lines rather than a list of
|
||
-- pages, because they sit under a menu instead of paging: the welcome line
|
||
-- the BUY/SELL/QUIT box opens over, the one it reopens over, and the "How
|
||
-- many?" prompt the quantity box sits under.
|
||
local SINGLE_PAGE = { welcome = true, askMore = true, howMany = true }
|
||
|
||
-- The formatted entries, keyed by LABEL rather than by entry name, because
|
||
-- the markers come in the string's order and the bargain shop names its item
|
||
-- FIRST ("X costs ¥N. Want it?") where the other three clerks lead with the
|
||
-- quantity.
|
||
local FILL = {
|
||
_MartFinalPriceText = function(qty, name, total)
|
||
return { qty, name, total }
|
||
end,
|
||
_HerbalLadyFinalPriceText = function(qty, name, total)
|
||
return { qty, name, total }
|
||
end,
|
||
_PharmacyFinalPriceText = function(qty, name, total)
|
||
return { qty, name, total }
|
||
end,
|
||
_BargainShopFinalPriceText = function(_qty, name, total)
|
||
return { name, total }
|
||
end,
|
||
_MartSellPriceText = function(total) return { total } end,
|
||
_MartBoughtText = function(name, total) return { total, name } end,
|
||
}
|
||
|
||
-- One dialog's table with every entry the cache carries replaced by the
|
||
-- extracted string. Anything missing falls through the metatable to the
|
||
-- transcription, so a partial cache is a mix rather than a hole.
|
||
local function extractedText(text, base, labels)
|
||
local out = setmetatable({}, { __index = base })
|
||
for key, label in pairs(labels or {}) do
|
||
local list = CommonText.of(text, label)
|
||
if list then
|
||
local fill = FILL[label]
|
||
if fill then
|
||
out[key] = function(...) return CommonText.fill(list, fill(...)) end
|
||
elseif SINGLE_PAGE[key] then
|
||
out[key] = list[1]
|
||
else
|
||
out[key] = list
|
||
end
|
||
end
|
||
end
|
||
return out
|
||
end
|
||
|
||
-- data/generated/marts.lua is what the ROM extractor will write out of `Marts`
|
||
-- (data/items/marts.asm): `lists` is a 1-based array in MART_* order, each
|
||
-- entry an array of item ids, and `bargain` is BargainShopData's own
|
||
-- item/price rows. A flat top-level array is accepted too, because that is
|
||
-- the shape items.lua uses and an extractor may well write it the same way.
|
||
--
|
||
-- Nothing is guessed when the table is absent: an id inside the table's range
|
||
-- with no row falls back to DefaultMart the way GetMart does, and a missing
|
||
-- table leaves the shelf empty rather than inventing stock.
|
||
function MartMenu.inventory(marts, martId)
|
||
martId = martId or 0
|
||
if martId >= NUM_MARTS then return DEFAULT_MART end
|
||
if type(marts) ~= "table" then return {} end
|
||
local lists = marts.lists or marts
|
||
local list = lists[martId + 1]
|
||
if type(list) ~= "table" then return DEFAULT_MART end
|
||
return list
|
||
end
|
||
|
||
-- BargainShopData is loaded by BargainShop itself, not by the mart id, so the
|
||
-- opcode's mart id is ignored for MARTTYPE_BARGAIN.
|
||
function MartMenu.bargainRows(marts)
|
||
local rows = type(marts) == "table" and marts.bargain
|
||
if type(rows) ~= "table" then return {} end
|
||
return rows
|
||
end
|
||
|
||
-- opts: save, items (items.lua), marts (marts.lua), martType (MARTTYPE_*
|
||
-- number or name), martId (number), text (text.lua, for the extracted
|
||
-- strings), onClose()
|
||
function MartMenu.new(game, opts)
|
||
opts = opts or {}
|
||
local self = setmetatable({}, MartMenu)
|
||
self.game = game
|
||
self.save = opts.save or (game and game.save)
|
||
self.items = opts.items or (game and game.data and game.data.items)
|
||
self.marts = opts.marts or (game and game.data and game.data.gen2Marts)
|
||
-- The dialog byte, or the MARTTYPE_* name spelled out (which is what a test
|
||
-- reads more clearly). Anything unrecognised is MartDialog, the way
|
||
-- MartTypeDialogs' jumptable would land on entry 0.
|
||
local kind = opts.martType or 0
|
||
if type(kind) == "string" then
|
||
self.martType = TEXTS[kind] and kind or "STANDARD"
|
||
else
|
||
self.martType = MART_TYPES[kind] or "STANDARD"
|
||
end
|
||
self.martId = opts.martId or 0
|
||
self.onClose = opts.onClose
|
||
-- text.lua rides on the world, the way the Pokegear's phone strings do: the
|
||
-- mart is opened by a script running on a map and never without one.
|
||
self.textData = opts.text or (game and game.world and game.world.text)
|
||
self.text = extractedText(self.textData,
|
||
TEXTS[self.martType], LABELS[self.martType])
|
||
self.sellText = extractedText(self.textData, SELL_TEXTS, LABELS.SELL)
|
||
self.index = 1
|
||
self.scroll = 0
|
||
self:buildEntries()
|
||
if self.martType == "STANDARD" then
|
||
-- .HowMayIHelpYou prints into the speech box and returns TOPMENU without
|
||
-- waiting: the welcome line is still on screen under the BUY/SELL/QUIT
|
||
-- menu, which is what the mart looks like on the cart.
|
||
self.phase = "top"
|
||
self.topLines = self.text.welcome
|
||
self.topIndex = 1
|
||
else
|
||
-- HerbShop / BargainShop / Pharmacist: intro, then straight into BuyMenu.
|
||
self.phase = "intro"
|
||
self:say(self.text.intro, function() self:enterBuy() end)
|
||
end
|
||
return self
|
||
end
|
||
|
||
-- The shelf. A standard mart prices each row out of ItemAttributes
|
||
-- (GetMartItemPrice -> GetItemPrice); the bargain shop carries its own price
|
||
-- per row and sells one of each, tracked by wBargainShopFlags.
|
||
function MartMenu:buildEntries()
|
||
local entries = {}
|
||
if self.martType == "BARGAIN" then
|
||
local sold = (self.save and self.save.bargainShop) or {}
|
||
for _, row in ipairs(MartMenu.bargainRows(self.marts)) do
|
||
local id = row.item or row.id or row[1]
|
||
local def = id and self.items and self.items[id]
|
||
entries[#entries + 1] = {
|
||
id = id,
|
||
name = (def and def.name) or id or "?",
|
||
price = row.price or row[2] or 0,
|
||
soldOut = sold[id] == true,
|
||
}
|
||
end
|
||
else
|
||
for _, id in ipairs(MartMenu.inventory(self.marts, self.martId)) do
|
||
local def = self.items and self.items[id]
|
||
entries[#entries + 1] = {
|
||
id = id,
|
||
name = (def and def.name) or id,
|
||
price = (def and def.price) or 0,
|
||
}
|
||
end
|
||
end
|
||
self.entries = entries
|
||
end
|
||
|
||
-- ----------------------------------------------------------------- money
|
||
function MartMenu:money()
|
||
local player = self.save and self.save.player
|
||
return (player and player.money) or 0
|
||
end
|
||
|
||
-- GiveMoney clamps at MaxMoney and TakeMoney clamps at zero
|
||
-- (engine/events/money.asm); one clamp covers both directions.
|
||
function MartMenu:setMoney(amount)
|
||
local player = self.save and self.save.player
|
||
if not player then return end
|
||
player.money = math.max(0, math.min(math.floor(amount or 0), Save.MAX_MONEY))
|
||
end
|
||
|
||
-- BuySell_MultiplyPrice: the running total is unit * quantity.
|
||
function MartMenu.buyPrice(unit, qty)
|
||
return (unit or 0) * (qty or 1)
|
||
end
|
||
|
||
-- Sell_HalvePrice shifts the 24-bit PRODUCT right once, so the halving happens
|
||
-- after the multiply -- selling two of an odd-priced item is not the same as
|
||
-- twice half its price, and this is the half a player notices.
|
||
function MartMenu.sellPrice(unit, qty)
|
||
return math.floor((unit or 0) * (qty or 1) / 2)
|
||
end
|
||
|
||
-- ---------------------------------------------------------------- overlays
|
||
--
|
||
-- `say` is PrintText plus the JoyWaitAorB that follows it everywhere in
|
||
-- mart.asm: the box holds until a button, and multi-page strings advance a
|
||
-- page per press.
|
||
function MartMenu:say(list, onDone)
|
||
self.message = { pages = list or {}, page = 1, onDone = onDone }
|
||
end
|
||
|
||
function MartMenu:updateMessage(input)
|
||
if not (input:wasPressed("a") or input:wasPressed("b")) then return end
|
||
local message = self.message
|
||
if message.page < #message.pages then
|
||
message.page = message.page + 1
|
||
return
|
||
end
|
||
self.message = nil
|
||
if message.onDone then message.onDone() end
|
||
end
|
||
|
||
-- MartConfirmPurchase / the sell flow's YesNoBox. The prompt's last page is
|
||
-- the one the box sits on; earlier pages advance on a press first.
|
||
function MartMenu:ask(list, onYes, onNo)
|
||
self.confirm = { pages = list or {}, page = 1, choice = 1,
|
||
onYes = onYes, onNo = onNo }
|
||
end
|
||
|
||
function MartMenu:updateConfirm(input)
|
||
local confirm = self.confirm
|
||
if confirm.page < #confirm.pages then
|
||
if input:wasPressed("a") or input:wasPressed("b") then
|
||
confirm.page = confirm.page + 1
|
||
end
|
||
return
|
||
end
|
||
if input:wasPressed("up") or input:wasPressed("down") then
|
||
confirm.choice = confirm.choice == 1 and 2 or 1
|
||
return
|
||
end
|
||
if input:wasPressed("b") then
|
||
self.confirm = nil
|
||
if confirm.onNo then confirm.onNo() end
|
||
return
|
||
end
|
||
if input:wasPressed("a") then
|
||
local yes = confirm.choice == 1
|
||
self.confirm = nil
|
||
if yes then
|
||
if confirm.onYes then confirm.onYes() end
|
||
elseif confirm.onNo then
|
||
confirm.onNo()
|
||
end
|
||
end
|
||
end
|
||
|
||
-- ---------------------------------------------------------------- top menu
|
||
local TOP_ITEMS = { "BUY", "SELL", "QUIT" }
|
||
|
||
-- .TopMenu copies MenuHeader_BuySell fresh every pass, and its `db 1 ; default
|
||
-- option` means the cursor is back on BUY each time the loop returns here.
|
||
function MartMenu:enterTop(lines)
|
||
self.phase = "top"
|
||
self.topLines = lines or self.text.askMore
|
||
self.topIndex = 1
|
||
end
|
||
|
||
function MartMenu:updateTop(input)
|
||
if input:wasPressed("up") then
|
||
self.topIndex = self.topIndex > 1 and self.topIndex - 1 or #TOP_ITEMS
|
||
return
|
||
elseif input:wasPressed("down") then
|
||
self.topIndex = self.topIndex < #TOP_ITEMS and self.topIndex + 1 or 1
|
||
return
|
||
elseif input:wasPressed("b") then
|
||
self:quit()
|
||
return
|
||
elseif input:wasPressed("a") then
|
||
if self.topIndex == 1 then
|
||
self:enterBuy()
|
||
elseif self.topIndex == 2 then
|
||
self:enterSell()
|
||
else
|
||
self:quit()
|
||
end
|
||
end
|
||
end
|
||
|
||
-- .Quit: the come-again line, then STANDARDMART_EXIT.
|
||
function MartMenu:quit()
|
||
self.phase = "outro"
|
||
self:say(self.text.comeAgain, function()
|
||
if self.onClose then self.onClose() end
|
||
end)
|
||
end
|
||
|
||
-- ---------------------------------------------------------------- buy list
|
||
--
|
||
-- BuyMenu resets wMenuCursorPositionBackup / wMenuScrollPositionBackup on
|
||
-- entry and restores them around every purchase, so the cursor survives a sale
|
||
-- but not a trip back through the top menu.
|
||
function MartMenu:enterBuy()
|
||
self.phase = "buy"
|
||
self.index = 1
|
||
self.scroll = 0
|
||
end
|
||
|
||
function MartMenu:total()
|
||
return #self.entries + 1 -- the -1 terminator draws as CANCEL
|
||
end
|
||
|
||
function MartMenu:isCancel()
|
||
return self.index > #self.entries
|
||
end
|
||
|
||
function MartMenu:selected()
|
||
return self.entries[self.index]
|
||
end
|
||
|
||
function MartMenu:ensureVisible()
|
||
if self.index <= self.scroll then
|
||
self.scroll = self.index - 1
|
||
elseif self.index > self.scroll + VISIBLE_ROWS then
|
||
self.scroll = self.index - VISIBLE_ROWS
|
||
end
|
||
self.scroll = math.max(0, math.min(self.scroll,
|
||
math.max(0, self:total() - VISIBLE_ROWS)))
|
||
end
|
||
|
||
function MartMenu:updateBuy(input)
|
||
-- _2DMENU_EXIT_UP / _2DMENU_EXIT_DOWN with .d_up refusing to move at scroll
|
||
-- zero: the buy list does NOT wrap the way the PACK's does.
|
||
if input:wasPressed("up") then
|
||
if self.index > 1 then
|
||
self.index = self.index - 1
|
||
self:ensureVisible()
|
||
end
|
||
return
|
||
elseif input:wasPressed("down") then
|
||
if self.index < self:total() then
|
||
self.index = self.index + 1
|
||
self:ensureVisible()
|
||
end
|
||
return
|
||
elseif input:wasPressed("b") then
|
||
self:leaveBuy()
|
||
return
|
||
elseif input:wasPressed("a") then
|
||
-- .a_button treats a -1 selection as B, so A on CANCEL leaves the list.
|
||
if self:isCancel() then
|
||
self:leaveBuy()
|
||
else
|
||
self:offerToBuy()
|
||
end
|
||
end
|
||
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()
|
||
if self.martType == "STANDARD" then
|
||
self:enterTop(self.text.askMore)
|
||
else
|
||
self:quit()
|
||
end
|
||
end
|
||
|
||
function MartMenu:offerToBuy()
|
||
local entry = self:selected()
|
||
if not entry then return end
|
||
if self.martType == "BARGAIN" then
|
||
-- BargainShopAskPurchaseQuantity: one of each, no quantity selector, and a
|
||
-- CHECK_FLAG on wBargainShopFlags ahead of everything else.
|
||
if entry.soldOut then
|
||
self:say(self.text.soldOut)
|
||
return
|
||
end
|
||
self.qtyItem = entry
|
||
self.qty = 1
|
||
self.qtyMax = 1
|
||
self:confirmPurchase()
|
||
return
|
||
end
|
||
self.qtyItem = entry
|
||
self.qty = 1
|
||
self.qtyMax = MAX_ITEM_STACK
|
||
self.phase = "buyQuantity"
|
||
end
|
||
|
||
function MartMenu:confirmPurchase()
|
||
local entry = self.qtyItem
|
||
local total = MartMenu.buyPrice(entry.price, self.qty)
|
||
self:ask(self.text.finalPrice(self.qty, entry.name, total),
|
||
function() self:completePurchase(total) end,
|
||
function() self:cancelPurchase() end)
|
||
end
|
||
|
||
-- .cancel simply redraws the speech box and drops back into the buy loop with
|
||
-- the cursor where it was, so this keeps index/scroll.
|
||
function MartMenu:cancelPurchase()
|
||
self.phase = "buy"
|
||
end
|
||
|
||
-- PlayTransactionSound. A driver can run this screen with no audio table at
|
||
-- all, so a missing one is silence rather than an error.
|
||
function MartMenu:playTransaction()
|
||
local data = self.game and self.game.data
|
||
if data then Sound.play(data, SFX_TRANSACTION) end
|
||
end
|
||
|
||
-- BuyMenuLoop's order matters: money is compared BEFORE the bag is asked for
|
||
-- room, so a broke player is told about the money and never about the PACK.
|
||
function MartMenu:completePurchase(total)
|
||
local entry = self.qtyItem
|
||
if self:money() < total then
|
||
self.phase = "buy"
|
||
self:say(self.text.noMoney)
|
||
return
|
||
end
|
||
local ok = Bag.add(self.save, entry.id, self.qty,
|
||
self.game and self.game.data)
|
||
if not ok then
|
||
self.phase = "buy"
|
||
self:say(self.text.packFull)
|
||
return
|
||
end
|
||
if self.martType == "BARGAIN" then
|
||
-- FlagAction SET_FLAG on wBargainShopFlags. The cart mirrors this into
|
||
-- DAILYFLAGS1_GOLDENROD_UNDERGROUND_BARGAIN on the way out so the stock
|
||
-- comes back with the day; the daily rollover is not modelled here, so the
|
||
-- flag simply lives on the save.
|
||
self.save.bargainShop = self.save.bargainShop or {}
|
||
self.save.bargainShop[entry.id] = true
|
||
entry.soldOut = true
|
||
end
|
||
-- .proceed's order: the bargain flag, PlayTransactionSound, TakeMoney, and
|
||
-- only then MARTTEXT_HERE_YOU_GO. The till rings on the money moving, not
|
||
-- on the clerk's line.
|
||
self:playTransaction()
|
||
self:setMoney(self:money() - total)
|
||
self.phase = "buy"
|
||
self:say(self.text.thanks)
|
||
end
|
||
|
||
-- --------------------------------------------------------- quantity picker
|
||
--
|
||
-- BuySellToss_InterpretJoypad (engine/items/buy_sell_toss.asm). Up and down
|
||
-- WRAP through the ends; left and right step by ten and CLAMP instead -- left
|
||
-- past 1 lands on 1, right past the ceiling lands on the ceiling.
|
||
function MartMenu:quantityStep(delta)
|
||
local n = self.qty + delta
|
||
if delta == 1 then
|
||
if n > self.qtyMax then n = 1 end
|
||
elseif delta == -1 then
|
||
if n < 1 then n = self.qtyMax end
|
||
elseif delta > 0 then
|
||
if n > self.qtyMax then n = self.qtyMax end
|
||
else
|
||
if n <= 0 then n = 1 end
|
||
end
|
||
self.qty = n
|
||
end
|
||
|
||
function MartMenu:updateQuantity(input, onAccept, onCancel)
|
||
if input:wasPressed("up") then
|
||
self:quantityStep(1)
|
||
elseif input:wasPressed("down") then
|
||
self:quantityStep(-1)
|
||
elseif input:wasPressed("right") then
|
||
self:quantityStep(10)
|
||
elseif input:wasPressed("left") then
|
||
self:quantityStep(-10)
|
||
elseif input:wasPressed("b") then
|
||
onCancel()
|
||
elseif input:wasPressed("a") then
|
||
onAccept()
|
||
end
|
||
end
|
||
|
||
-- --------------------------------------------------------------- sell flow
|
||
--
|
||
-- SellMenu opens DepositSellPack -- the PACK itself, in a mode where A means
|
||
-- "sell this" rather than "use this". `world = {}` is a world with no
|
||
-- useFieldItem, so PackMenu:useSelected falls straight through to onChoose and
|
||
-- no field effect can fire from inside a shop.
|
||
--
|
||
-- The PACK is held rather than stacked, so this is Screens.build, not
|
||
-- Screens.push: same id, same registry lookup and same mod-screen degrade, but
|
||
-- the mart keeps drawing and updating it itself.
|
||
function MartMenu:enterSell()
|
||
self.phase = "sell"
|
||
self.pack = Screens.build(self.game, "Gen2PackMenu", {
|
||
save = self.save,
|
||
items = self.items,
|
||
world = {},
|
||
onChoose = function(itemId, count) self:offerToSell(itemId, count) end,
|
||
onClose = function() self:leaveSell() end,
|
||
})
|
||
end
|
||
|
||
function MartMenu:leaveSell()
|
||
self.pack = nil
|
||
self:enterTop(self.text.askMore)
|
||
end
|
||
|
||
-- .TryToSellItem: CheckItemMenu's field-menu value routes everything the PACK
|
||
-- can hold to .try_sell, and _CheckTossableItem is the real gate -- a CANT_TOSS
|
||
-- item (every KEY ITEM, and the HMs) gets MartCantBuyText and nothing else.
|
||
function MartMenu:offerToSell(itemId, count)
|
||
local def = self.items and self.items[itemId]
|
||
if not def or def.canToss == false then
|
||
self:say(self.sellText.cantBuy)
|
||
return
|
||
end
|
||
-- Nothing in stock is not a sale: the PACK only lists rows it holds, and a
|
||
-- zero count would otherwise walk the selector's ceiling down to nothing.
|
||
if (count or 0) < 1 then
|
||
self:say(self.sellText.cantBuy)
|
||
return
|
||
end
|
||
-- ScrollingMenu's .a_button copies the row's own quantity into
|
||
-- wItemQuantity, so the selector's ceiling is how many you hold.
|
||
-- The unit price is the item's own ItemAttributes price: GetItemPrice is
|
||
-- what SelectQuantityToSell halves AND what the buy list's
|
||
-- GetMartItemPrice charges, so a TM sells for exactly half of what the
|
||
-- Goldenrod/Celadon TM shelves ask for it, and half of its hidden price
|
||
-- (usually ¥1,500) anywhere else (issue #1243).
|
||
self.qtyItem = { id = itemId, name = def.name or itemId,
|
||
price = def.price or 0 }
|
||
self.qty = 1
|
||
self.qtyMax = count
|
||
self.phase = "sellQuantity"
|
||
end
|
||
|
||
function MartMenu:confirmSale()
|
||
local total = MartMenu.sellPrice(self.qtyItem.price, self.qty)
|
||
self:ask(self.sellText.price(total),
|
||
function() self:completeSale(total) end,
|
||
function() self.phase = "sell" end)
|
||
end
|
||
|
||
function MartMenu:completeSale(total)
|
||
local entry = self.qtyItem
|
||
self:setMoney(self:money() + total)
|
||
Bag.remove(self.save, entry.id, self.qty)
|
||
if self.pack then self.pack:rebuild() end
|
||
self.phase = "sell"
|
||
-- The sell side calls PlayTransactionSound after MartBoughtText rather than
|
||
-- before it, but both land on the same frame here: the say() only queues the
|
||
-- page the update loop draws.
|
||
self:playTransaction()
|
||
self:say(self.sellText.bought(entry.name, total))
|
||
end
|
||
|
||
-- ----------------------------------------------------------------- update
|
||
function MartMenu:update(dt)
|
||
local input = self.game and self.game.input
|
||
if not input then return end
|
||
if self.message then
|
||
self:updateMessage(input)
|
||
return
|
||
end
|
||
if self.confirm then
|
||
self:updateConfirm(input)
|
||
return
|
||
end
|
||
local phase = self.phase
|
||
if phase == "top" then
|
||
self:updateTop(input)
|
||
elseif phase == "buy" then
|
||
self:updateBuy(input)
|
||
elseif phase == "buyQuantity" then
|
||
self:updateQuantity(input,
|
||
function() self:confirmPurchase() end,
|
||
function() self.phase = "buy" end)
|
||
elseif phase == "sell" then
|
||
if self.pack then self.pack:update(dt) end
|
||
elseif phase == "sellQuantity" then
|
||
self:updateQuantity(input,
|
||
function() self:confirmSale() end,
|
||
function() self.phase = "sell" end)
|
||
end
|
||
end
|
||
|
||
-- ------------------------------------------------------------------- draw
|
||
function MartMenu:drawTextBox(lines)
|
||
Chrome.box(TEXT_BOX_X, TEXT_BOX_Y, TEXT_BOX_W, TEXT_BOX_H)
|
||
for i, line in ipairs(lines or {}) do
|
||
Chrome.print(line, TEXT_X, TEXT_Y + (i - 1) * TEXT_LINE)
|
||
end
|
||
end
|
||
|
||
function MartMenu:drawMoneyBox()
|
||
Chrome.box(MONEY_BOX_X, MONEY_BOX_Y, MONEY_BOX_W, MONEY_BOX_H)
|
||
Chrome.print(moneyText(self:money()), MONEY_X, MONEY_Y)
|
||
end
|
||
|
||
-- PlaceMoneyBottomLeft / PlaceMoneyAtTopLeftOfTextbox land on the same rect.
|
||
function MartMenu:drawMoneyBoxLow()
|
||
Chrome.box(MONEY_LOW_BOX_X, MONEY_LOW_BOX_Y, MONEY_BOX_W, MONEY_BOX_H)
|
||
Chrome.print(moneyText(self:money()), MONEY_LOW_X, MONEY_LOW_Y)
|
||
end
|
||
|
||
function MartMenu:drawTopMenu()
|
||
Chrome.box(TOP_BOX_X, TOP_BOX_Y, TOP_BOX_W, TOP_BOX_H)
|
||
for i, label in ipairs(TOP_ITEMS) do
|
||
local ty = TOP_LABEL_Y + (i - 1) * TOP_SPACING
|
||
if i == self.topIndex then Chrome.cursor(TOP_LABEL_X - 1, ty) end
|
||
Chrome.print(label, TOP_LABEL_X, ty)
|
||
end
|
||
end
|
||
|
||
-- The description under the buy list. A TM prints the MOVE's description on
|
||
-- the cart (PrintItemDescription branches at TM01 into PrintMoveDescription),
|
||
-- which is the same rule the PACK follows.
|
||
function MartMenu:description()
|
||
local entry = self:selected()
|
||
if not entry then return nil end
|
||
local def = self.items and self.items[entry.id]
|
||
if def and def.teaches then
|
||
local moves = self.game and self.game.data and self.game.data.moves
|
||
local moveDef = moves and moves[def.teaches]
|
||
if moveDef and moveDef.description then return moveDef.description end
|
||
end
|
||
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.
|
||
local function printPriceOpaque(amount, ty)
|
||
local G = love.graphics
|
||
G.setColor(1, 1, 1, 1)
|
||
G.rectangle("fill", PRICE_X * 8, ty * 8, 7 * 8, 8)
|
||
G.setColor(0, 0, 0, 1)
|
||
Chrome.print(moneyText(amount), PRICE_X, ty)
|
||
end
|
||
|
||
function MartMenu:drawBuyList()
|
||
-- engine/items/mart.asm:542
|
||
Chrome.box(LIST_BOX_X, LIST_BOX_Y, LIST_BOX_W, LIST_BOX_H)
|
||
for row = 1, VISIBLE_ROWS do
|
||
local i = row + self.scroll
|
||
local ty = LIST_Y + (row - 1) * LIST_SPACING
|
||
if i <= #self.entries then
|
||
local entry = self.entries[i]
|
||
if i == self.index then Chrome.cursor(LIST_X - 1, ty) end
|
||
Chrome.print(entry.name, LIST_X, ty)
|
||
printPriceOpaque(entry.price, ty + 1)
|
||
elseif i == self:total() then
|
||
if i == self.index then Chrome.cursor(LIST_X - 1, ty) end
|
||
Chrome.print("CANCEL", LIST_X, ty)
|
||
end
|
||
end
|
||
-- SCROLLINGMENU_DISPLAY_ARROWS: the ▲ only appears once the list has been
|
||
-- scrolled, but the ▼ is written every pass whether or not there is more
|
||
-- below it.
|
||
if self.scroll > 0 then
|
||
Chrome.print(UP_ARROW, ARROW_X, ARROW_UP_Y)
|
||
end
|
||
Chrome.print(DOWN_ARROW, ARROW_X, ARROW_DOWN_Y)
|
||
end
|
||
|
||
function MartMenu:drawDescription()
|
||
Chrome.box(TEXT_BOX_X, TEXT_BOX_Y, TEXT_BOX_W, TEXT_BOX_H)
|
||
if self:isCancel() then return end
|
||
local description = self:description()
|
||
if not description then return end
|
||
-- The extractor leaves the description's own '<NEXT>' in place, and $4e
|
||
-- steps SCREEN_WIDTH * 2 from the line's start (home/text.asm
|
||
-- NextLineChar) -- which is the TEXT_LINE both rows already use. '\n'
|
||
-- covers hand-written data.
|
||
local first, second = description:match("^(.-)<NEXT>(.*)$")
|
||
if not first then first, second = description:match("^(.-)\n(.*)$") end
|
||
Chrome.print(first or description, TEXT_X, TEXT_Y)
|
||
if second then Chrome.print(second, TEXT_X, TEXT_Y + TEXT_LINE) end
|
||
end
|
||
|
||
function MartMenu:drawQuantityBox(total)
|
||
Chrome.box(QTY_BOX_X, QTY_BOX_Y, QTY_BOX_W, QTY_BOX_H)
|
||
Chrome.print(TIMES, QTY_X, QTY_Y)
|
||
-- `lb bc, PRINTNUM_LEADINGZEROS | 1, 2`: two digits, zero padded.
|
||
Chrome.print(Chrome.number(self.qty, 2, true), QTY_X + 1, QTY_Y)
|
||
Chrome.print(moneyText(total), QTY_PRICE_X, QTY_Y)
|
||
end
|
||
|
||
function MartMenu:drawYesNo(choice)
|
||
Chrome.box(YESNO_X, YESNO_Y, YESNO_W, YESNO_H)
|
||
Chrome.print("YES", YESNO_X + 2, YESNO_Y + 1)
|
||
Chrome.print("NO", YESNO_X + 2, YESNO_Y + 3)
|
||
Chrome.cursor(YESNO_X + 1, YESNO_Y + (choice == 1 and 1 or 3))
|
||
end
|
||
|
||
-- Whatever the overlays sit on top of.
|
||
function MartMenu:drawUnder()
|
||
local phase = self.phase
|
||
if phase == "top" then
|
||
self:drawTopMenu()
|
||
self:drawTextBox(self.topLines)
|
||
elseif phase == "buy" or phase == "buyQuantity" then
|
||
self:drawMoneyBox()
|
||
self:drawBuyList()
|
||
self:drawDescription()
|
||
elseif phase == "sell" or phase == "sellQuantity" then
|
||
if self.pack then self.pack:drawPanel() end
|
||
end
|
||
end
|
||
|
||
function MartMenu:drawPanel()
|
||
self:drawUnder()
|
||
|
||
if self.phase == "buyQuantity" and not (self.message or self.confirm) then
|
||
-- StandardMartAskPurchaseQuantity prints MARTTEXT_HOW_MANY first, then the
|
||
-- selector is drawn over the bottom of that box.
|
||
self:drawTextBox(self.text.howMany)
|
||
self:drawQuantityBox(MartMenu.buyPrice(self.qtyItem.price, self.qty))
|
||
elseif self.phase == "sellQuantity" and not (self.message or self.confirm) then
|
||
self:drawTextBox(self.sellText.howMany)
|
||
-- .okay_to_sell prints the question, THEN drops the money box on its top
|
||
-- left corner, so the box wins where they overlap.
|
||
self:drawMoneyBoxLow()
|
||
self:drawQuantityBox(MartMenu.sellPrice(self.qtyItem.price, self.qty))
|
||
end
|
||
|
||
if self.message then
|
||
self:drawTextBox(self.message.pages[self.message.page])
|
||
-- LoadBlinkingCursor puts the ▼ at hlcoord 18, 17 while a `para` waits.
|
||
if self.message.page < #self.message.pages then
|
||
Chrome.print(DOWN_ARROW, 18, 17)
|
||
end
|
||
elseif self.confirm then
|
||
self:drawTextBox(self.confirm.pages[self.confirm.page])
|
||
if self.confirm.page >= #self.confirm.pages then
|
||
self:drawYesNo(self.confirm.choice)
|
||
end
|
||
end
|
||
love.graphics.setColor(1, 1, 1, 1)
|
||
end
|
||
|
||
function MartMenu:draw()
|
||
self:drawPanel()
|
||
end
|
||
|
||
return MartMenu
|