fix(gen2): sell TMs at Poké Marts instead of opening the teach party

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
This commit is contained in:
ShaneMcGovernIE
2026-08-13 23:55:31 +01:00
parent 26e9e1d597
commit 1586aec9f6
5 changed files with 94 additions and 12 deletions
+21
View File
@@ -549,6 +549,27 @@ do
dudePack:useSelected()
eq(thrown, "POKE_BALL", "the tutorial pack still throws")
eq(dudePack.message, nil, "with no Oak line in the way")
-- DepositSellPack passes the same empty world (the mart's SELL and the
-- item PC's DEPOSIT both do): a TM row must hand its id back rather than
-- open the teach party, which is issue #1243's "Teach which PKMN?" in
-- the middle of a sale.
local sellGame = { input = newInput(), save = {
player = { name = "GOLD" },
inventory = { TM01 = 1 },
options = {},
}, data = DATA, stack = newStack() }
local sold
local sellPack = PackMenu.new(sellGame, {
save = sellGame.save, items = DATA.items, world = {},
onChoose = function(id) sold = id end,
})
sellPack.pocketIndex = 4 -- TM_HM
sellPack:rebuild()
sellPack:useSelected()
eq(sold, "TM01", "a chooser pack hands a TM to its caller")
eq(sellPack.message, nil, "with no teach refusal in the way")
eq(#sellGame.stack._items, 0, "and nothing pushed over the pack")
end
-- ------------------------------------------------ say(): the teardown rule
+34
View File
@@ -1481,6 +1481,11 @@ local martItems = {
price = 100, canToss = true },
NUGGET = { id = "NUGGET", name = "NUGGET", pocket = "ITEM", index = 36,
price = 10000, canToss = true },
-- A TM carries the move it teaches; the SELL pack must hand it to the
-- mart rather than opening the teach party (issue #1243).
TM_HEADBUTT = { id = "TM_HEADBUTT", name = "TM02", pocket = "TM_HM",
index = 234, price = 2000, canToss = true, teaches = "HEADBUTT",
tmNumber = 2 },
-- Every KEY ITEM carries CANT_TOSS, which is the flag SellMenu's
-- _CheckTossableItem actually refuses on.
BICYCLE = { id = "BICYCLE", name = "BICYCLE", pocket = "KEY_ITEM", index = 7,
@@ -1732,6 +1737,35 @@ sellInput:press("a") sell:update(0) -- YES
check("the wallet clamps at MAX_MONEY", sellSave.player.money, 999999)
check("and the nugget is gone", sellSave.inventory.NUGGET, nil)
-- A TM is not a key item: DepositSellPack's jumptable is four ScrollingMenus
-- and has no teach arm, so picking a TM from the SELL pack must ask "How
-- many?" and price it at half of its ItemAttributes price -- never open the
-- teach party (issue #1243).
local tmSave = Save.newGame()
tmSave.inventory = { TM_HEADBUTT = 1 }
tmSave.player.money = 0
local tm, tmInput = newMart(tmSave)
tmInput:press("down") tm:update(0)
tmInput:press("a") tm:update(0) -- SELL
check("SELL builds the pack", tm.pack ~= nil, true)
-- The PACK opens on the ITEM pocket; cross to TM/HM the way the player would.
tm.pack.pocketIndex = 4
tm.pack:rebuild()
check("the TM is the TM pocket's row", tm.pack.rows[1].id, "TM_HEADBUTT")
tmInput:press("a") tm:update(0)
check("picking a TM asks how many", tm.phase, "sellQuantity")
check("with no teach screen opened", #tm.game.stack._items, 0)
check("and no pack refusal printed", tm.pack.message, nil)
check("at the TM's own price", tm.qtyItem.price, 2000)
check("and the ceiling is what you hold", tm.qtyMax, 1)
tmInput:press("a") tm:update(0)
check("the offer prices half of the TM", tm.confirm.pages[1][2],
"\xc2\xa51000.")
tmInput:press("a") tm:update(0) -- page 2
tmInput:press("a") tm:update(0) -- YES
check("the TM leaves the bag", tmSave.inventory.TM_HEADBUTT, nil)
check("and the money arrives", tmSave.player.money, 1000)
-- ------------------------------------------------- PlayTransactionSound
--
-- engine/items/mart.asm rings SFX_TRANSACTION in exactly two places, both of
+19
View File
@@ -94,6 +94,8 @@ local ITEMS = {
canToss = false },
HM_CUT = { id = "HM_CUT", name = "HM01", pocket = "TM_HM", index = 0xf3,
canToss = false },
TM_HEADBUTT = { id = "TM_HEADBUTT", name = "TM02", pocket = "TM_HM",
index = 0xea, canToss = true, teaches = "HEADBUTT" },
}
local function newGame(save, items)
@@ -291,6 +293,23 @@ do
eq(pc.phase, "menu", "B drops back to the item PC menu")
end
-- The same DepositSellPack chooser holds a TM: it must reach the quantity
-- selector like everything tossable, never the teach party (issue #1243).
do
local save = newSave(1)
local game, input = newGame(save)
Bag.add(save, "TM_HEADBUTT", 1, game.data)
local pc = ItemPcMenu.new(game, { save = save, items = ITEMS })
press(pc, input, "down", "a") -- DEPOSIT ITEM
press(pc, input, "right", "right", "right") -- ITEM -> BALL -> KEY_ITEM -> TM_HM
press(pc, input, "a") -- choose the TM
check(pc.qtyState ~= nil, "a TM asks how many to deposit")
eq(#game.stack._items, 0, "with no teach screen pushed over the pack")
press(pc, input, "a") -- deposit x1
eq(save.pcItems.TM_HEADBUTT, 1, "the TM lands in the PC")
eq(save.inventory.TM_HEADBUTT, nil, "and leaves the bag")
end
-- .TryDepositItem's .no_toss: a KEY ITEM stays in the bag, silently.
do
local save = newSave(1)