-- Mart shop (engine/events/pokemart.asm DisplayPokemartDialogue_): -- the BUY/SELL/QUIT menu loops until QUIT -- BUY and SELL keep it on -- the stack underneath their list, and QUIT hands control back to the -- caller (open_mart resumes its yielded script runner there). Both -- lists run in dialogue mode: the clerk speaks the real _Pokemart* -- strings in the bottom text box with the money box top-right, then -- the 1-99 quantity selector (DisplayChooseQuantityMenu) and a YES/NO -- price confirm. Key items and HMs can't be sold (.unsellableItem). local Bag = require("src.inventory.Bag") local ChoiceBox = require("src.ui.ChoiceBox") local Font = require("src.render.Font") local ListMenu = require("src.ui.ListMenu") local Menu = require("src.ui.Menu") local QuantityBox = require("src.ui.QuantityBox") local Strings = require("src.core.Strings") local TextBox = require("src.render.TextBox") local romText = require("src.core.RomText") local ShopMenu = {} local function txt(game, key, fallback) return game.data.text[key] or fallback end -- engine/events/pokemart.asm:204 (.returnToMainPokemartMenu) local function anythingElse(game) return txt(game, "_PokemartAnythingElseText", Strings("Is there anything\nelse I can do?")) end -- prompt refusals leave the list for the mart menu -- pokemart.asm:113 local function refuse(game, menu, list, text) if list then list:close() end game.stack:push(TextBox.new(game, text, function() menu.footer = anythingElse(game) end)) end local function buy(game, stock, menu) local items = {} for _, id in ipairs(stock) do local def = game.data.items[id] if def then table.insert(items, { value = id, label = def.name, right = ("¥%d"):format(def.price), }) end end local greet = txt(game, "_PokemartBuyingGreetingText", "Take your time.") local notEnough = txt(game, "_PokemartNotEnoughMoneyText", Strings("You don't have\nenough money.")) local bagFull = txt(game, "_PokemartItemBagFullText", Strings("You can't carry\nany more items.")) local list list = ListMenu.new(game, "BUY", items, { dialogue = true, money = function() return game.save.money end, footer = greet, onCancel = function() menu.footer = anythingElse(game) end, onChoose = function(item) local def = game.data.items[item.value] if game.save.money < def.price then list.footer = notEnough return end local affordable = math.min(99, math.floor(game.save.money / math.max(1, def.price))) game.stack:push(QuantityBox.new(game, { max = affordable, unitPrice = def.price, onDone = function(qty) if not qty then list.footer = greet return end local cost = qty * def.price -- _PokemartTellBuyPriceText + yes/no confirm list.footer = romText(game.data, "_PokemartTellBuyPriceText", "%s?\nThat will be\n¥%d. OK?", def.name, cost) game.stack:push(ChoiceBox.new(game, function(yes) if not yes then list.footer = greet return end if game.save.money < cost then refuse(game, menu, list, notEnough) return end if not Bag.add(game.save, item.value, qty, game.data) then refuse(game, menu, list, bagFull) return end game.save.money = game.save.money - cost -- SFX_PURCHASE drains before the receipt -- pokemart.asm:193 game.stack:push(TextBox.new(game, txt(game, "_PokemartBoughtItemText", Strings("Here you are!\nThank you!")), function() list.footer = greet end, { preSound = function() return require("src.core.Sound").play(game.data, "Purchase") end })) end)) end, })) end, }) game.stack:push(list) end local function sell(game, menu) -- Sell list is ITEMLISTMENU with wPrintItemPrices cleared -- (pokemart.asm .sellMenuLoop): name + quantity only. Price shows -- in the quantity chooser. Stuffing "xN" into the label next to a -- right-aligned ¥ price made long names overlap (issue #116). local items = {} for _, id in ipairs(Bag.order(game.save)) do local def = game.data.items[id] table.insert(items, { value = id, label = def and def.name or id, right = "x" .. game.save.inventory[id], }) end -- engine/events/pokemart.asm:50 local greet = txt(game, "_PokemonSellingGreetingText", Strings("What would you\nlike to sell?")) if #items == 0 then refuse(game, menu, nil, txt(game, "_PokemartItemBagEmptyText", Strings("You don't have\nanything to sell."))) return end local unsellable = txt(game, "_PokemartUnsellableItemText", Strings("I can't put a\nprice on that.")) local list list = ListMenu.new(game, "SELL", items, { dialogue = true, money = function() return game.save.money end, footer = greet, onCancel = function() menu.footer = anythingElse(game) end, onSelectKey = function(item, l) if not item then return end if not l.swapIndex then l.swapIndex = l.index return end local order = Bag.order(game.save) order[l.swapIndex], order[l.index] = order[l.index], order[l.swapIndex] l.swapIndex = nil require("src.core.Sound").play(game.data, "Swap") local rebuilt = {} for _, id in ipairs(order) do local def = game.data.items[id] rebuilt[#rebuilt + 1] = { value = id, label = def and def.name or id, right = "x" .. game.save.inventory[id], } end l.items = rebuilt end, onChoose = function(item) local def = game.data.items[item.value] -- only key items and HMs are unsellable (pokemart.asm IsKeyItem / -- IsItemHM); zero-price items like ETHER sell for ¥0. An unknown id -- (nil def) has no price, so treat it as unsellable too rather than -- indexing nil below -- guards saves that already picked up a bogus -- ITEM_NONE "0" from Blue's House before that pickup was fixed (#11). if not def or def.keyItem or item.value:find("^HM_") then refuse(game, menu, list, unsellable) return end local unit = math.floor(def.price / 2) game.stack:push(QuantityBox.new(game, { max = game.save.inventory[item.value] or 1, unitPrice = unit, onDone = function(qty) if not qty then list.footer = greet return end -- _PokemartTellSellPriceText + yes/no confirm list.footer = romText(game.data, "_PokemartTellSellPriceText", "I can pay you\n¥%d for that.", unit * qty) game.stack:push(ChoiceBox.new(game, function(yes) if not yes then list.footer = greet return end game.save.money = game.save.money + unit * qty -- home/inventory.asm:15 AddAmountSoldToMoney sounds SFX_PURCHASE require("src.core.Sound").play(game.data, "Purchase") Bag.remove(game.save, item.value, qty) local left = game.save.inventory[item.value] if left then item.right = "x" .. left else list:removeCurrent() end -- a sale prints nothing -- engine/events/pokemart.asm:112 list.footer = greet end)) end, })) end, }) game.stack:push(list) end -- MONEY_BOX 11,0 (data/text_boxes.asm:35) over the greeting PrintText left -- in the bottom box (home/text_script.asm:143) local function drawClerk(menu) local game = menu.game love.graphics.setColor(1, 1, 1, 1) Font.drawBox(11, 0, 9, 3) love.graphics.setColor(0, 0, 0, 1) local money = ("¥%d"):format((game.save and game.save.money) or 0) Font.draw(money, 152 - Font.width(money), 8) love.graphics.setColor(1, 1, 1, 1) Font.drawBox(0, 12, 20, 6) love.graphics.setColor(0, 0, 0, 1) if menu.footer then local flat = {} for _, page in ipairs(TextBox.paginate(menu.footer)) do for _, line in ipairs(page) do flat[#flat + 1] = line end end local y = 112 for i = math.max(1, #flat - 1), #flat do Font.draw(flat[i], 8, y) y = y + 16 end end love.graphics.setColor(1, 1, 1, 1) end function ShopMenu.new(game, stock, onQuit) -- keepOpen: the mart menu stays underneath its list so closing the -- list lands back here; only QUIT (or B) leaves and fires onQuit local menu -- engine/events/pokemart.asm:220 .done, reached by QUIT and by B alike local function farewell() game.stack:push(TextBox.new(game, txt(game, "_PokemartThankYouText", "Thank you!"), onQuit)) end menu = Menu.new(game, { { label = Strings("BUY"), keepOpen = true, onSelect = function() buy(game, stock, menu) end }, { label = Strings("SELL"), keepOpen = true, onSelect = function() sell(game, menu) end }, { label = Strings("QUIT"), onSelect = farewell }, }, { tx = 0, ty = 0, tw = 8, th = 8 }) menu.onCancel = farewell menu.footer = txt(game, "_PokemartGreetingText", Strings("Hi there!\nMay I help you?")) menu.draw = function(self) drawClerk(self) Menu.draw(self) end return menu end return ShopMenu