mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-21 13:09:54 +02:00
Merge pull request #818 from johnjohto/fix-rare-candy-menu-796
Keep the bag open after using a Rare Candy
This commit is contained in:
+14
-10
@@ -255,6 +255,15 @@ local function useOn(game, battle, id, target, list, moveIndex, picker)
|
|||||||
|
|
||||||
if result == "consumed" then
|
if result == "consumed" then
|
||||||
consume(game, id)
|
consume(game, id)
|
||||||
|
-- refresh counts in the list
|
||||||
|
for i, it in ipairs(list.items) do
|
||||||
|
if it.value == id then
|
||||||
|
local left = game.save.inventory[id]
|
||||||
|
if left then it.right = "x" .. left else table.remove(list.items, i) end
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
list.index = math.min(list.index, math.max(1, #list.items))
|
||||||
if extra and extra.evolveTo then
|
if extra and extra.evolveTo then
|
||||||
list:close()
|
list:close()
|
||||||
local Evolution = require("src.pokemon.Evolution")
|
local Evolution = require("src.pokemon.Evolution")
|
||||||
@@ -265,7 +274,11 @@ local function useOn(game, battle, id, target, list, moveIndex, picker)
|
|||||||
-- moves and a level evolution follow (item_effects.asm .useRareCandy
|
-- moves and a level evolution follow (item_effects.asm .useRareCandy
|
||||||
-- runs PrintStatsBox, LearnMoveFromLevelUp and TryEvolvingMon)
|
-- runs PrintStatsBox, LearnMoveFromLevelUp and TryEvolvingMon)
|
||||||
if extra and extra.leveledTo and target then
|
if extra and extra.leveledTo and target then
|
||||||
list:close()
|
-- ...but the bag stays open underneath it all: RARE_CANDY is in
|
||||||
|
-- pokered's UsableItems_PartyMenu (data/items/use_party.asm), and
|
||||||
|
-- .useItem_partyMenu jumps back to StartMenu_Item once UseItem
|
||||||
|
-- returns, cursor still on the candy (start_sub_menus.asm) -- so
|
||||||
|
-- mashing A burns through a stack of them (#796)
|
||||||
showMessages(game, payload, function()
|
showMessages(game, payload, function()
|
||||||
local StatBox = require("src.battle.BattleState").StatBox
|
local StatBox = require("src.battle.BattleState").StatBox
|
||||||
game.stack:push(StatBox.new(game, target, function()
|
game.stack:push(StatBox.new(game, target, function()
|
||||||
@@ -304,15 +317,6 @@ local function useOn(game, battle, id, target, list, moveIndex, picker)
|
|||||||
end)
|
end)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
-- refresh counts in the list
|
|
||||||
for i, it in ipairs(list.items) do
|
|
||||||
if it.value == id then
|
|
||||||
local left = game.save.inventory[id]
|
|
||||||
if left then it.right = "x" .. left else table.remove(list.items, i) end
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
list.index = math.min(list.index, math.max(1, #list.items))
|
|
||||||
-- HP medicine: fill the bar in the still-open picker first, then print
|
-- HP medicine: fill the bar in the still-open picker first, then print
|
||||||
-- and close, the order item_effects.asm .doneHealing runs in
|
-- and close, the order item_effects.asm .doneHealing runs in
|
||||||
-- (SFX_HEAL_HP -> UpdateHPBar2 -> RedrawPartyMenu prints the message).
|
-- (SFX_HEAL_HP -> UpdateHPBar2 -> RedrawPartyMenu prints the message).
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
-- Parity test: using a RARE CANDY from the bag returns to the bag (#796).
|
||||||
|
--
|
||||||
|
-- pokered files RARE_CANDY under UsableItems_PartyMenu (data/items/
|
||||||
|
-- use_party.asm), so after UseItem runs, start_sub_menus.asm's
|
||||||
|
-- .useItem_partyMenu jumps back to StartMenu_Item with wBagSavedMenuItem
|
||||||
|
-- still pointing at the candy -- the level text, PrintStatsBox, the
|
||||||
|
-- level-up moves and any level evolution all happen first, then the item
|
||||||
|
-- list is back with the cursor on the candy, which is what lets the
|
||||||
|
-- original button-mash through a stack of them. The port closed the bag
|
||||||
|
-- list before the level-up sequence and never reopened it, so every candy
|
||||||
|
-- cost a full START -> ITEM trip.
|
||||||
|
--
|
||||||
|
-- Self-contained; run via `luajit tests/parity_rare_candy_menu.lua`.
|
||||||
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||||
|
if not _G.love then _G.love = require("tests.love_stub") end
|
||||||
|
local S = require("tests.harness").suite("parity rare candy menu")
|
||||||
|
local check, eq = S.check, S.eq
|
||||||
|
|
||||||
|
local Data = require("src.core.Data")
|
||||||
|
Data:load()
|
||||||
|
|
||||||
|
local Pokemon = require("src.pokemon.Pokemon")
|
||||||
|
local Bag = require("src.inventory.Bag")
|
||||||
|
|
||||||
|
-- Real TextBoxes want a Font atlas; the flow under test only cares which
|
||||||
|
-- states land on the stack and what each onDone does. BagMenu binds
|
||||||
|
-- TextBox at require time, so it is reloaded against the stub here and
|
||||||
|
-- dropped again at the bottom.
|
||||||
|
local realTextBox = package.loaded["src.render.TextBox"]
|
||||||
|
local realBag = package.loaded["src.ui.BagMenu"]
|
||||||
|
local realParty = package.loaded["src.ui.PartyMenu"]
|
||||||
|
package.loaded["src.render.TextBox"] = {
|
||||||
|
new = function(_, text, done) return { textBox = true, text = text, done = done } end,
|
||||||
|
}
|
||||||
|
package.loaded["src.ui.BagMenu"] = nil
|
||||||
|
package.loaded["src.ui.PartyMenu"] = nil
|
||||||
|
local BagMenu = require("src.ui.BagMenu")
|
||||||
|
local PartyMenu = require("src.ui.PartyMenu")
|
||||||
|
require("src.ui.Screens").invalidate()
|
||||||
|
|
||||||
|
-- A stack that behaves like StateStack for the two things this flow reads:
|
||||||
|
-- top() identity (ListMenu:close) and push/pop ordering.
|
||||||
|
local function newStack()
|
||||||
|
local stack = { states = {} }
|
||||||
|
function stack:push(s) self.states[#self.states + 1] = s end
|
||||||
|
function stack:pop() return table.remove(self.states) end
|
||||||
|
function stack:top() return self.states[#self.states] end
|
||||||
|
return stack
|
||||||
|
end
|
||||||
|
|
||||||
|
-- One button per call: PartyMenu:update reads game.input once per fixed step.
|
||||||
|
local function newInput()
|
||||||
|
local input = { pressed = nil }
|
||||||
|
function input:wasPressed(b) return self.pressed == b end
|
||||||
|
return input
|
||||||
|
end
|
||||||
|
|
||||||
|
-- CHARIZARD learns nothing at level 51 and has no evolution left, so a
|
||||||
|
-- candy on it ends after the stat window -- no MoveLearnMenu, no
|
||||||
|
-- EvolutionState clouding which state the stack returns to.
|
||||||
|
local function freshGame(candies)
|
||||||
|
local lead = Pokemon.new(Data, "CHARIZARD", 50)
|
||||||
|
local game = {
|
||||||
|
data = Data,
|
||||||
|
stack = newStack(),
|
||||||
|
input = newInput(),
|
||||||
|
save = {
|
||||||
|
party = { lead },
|
||||||
|
player = { name = "RED" },
|
||||||
|
inventory = {},
|
||||||
|
options = { battleStyle = "set", battleAnim = "on" },
|
||||||
|
pokedex = { seen = {}, owned = {} },
|
||||||
|
flags = {},
|
||||||
|
money = 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Bag.add(game.save, "RARE_CANDY", candies or 3)
|
||||||
|
return game, lead
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isPicker(s) return getmetatable(s) == PartyMenu end
|
||||||
|
local function isBox(s) return type(s) == "table" and s.textBox == true end
|
||||||
|
|
||||||
|
-- TextBox pops itself BEFORE firing onDone.
|
||||||
|
local function dismiss(stack, box)
|
||||||
|
if stack:top() == box then stack:pop() end
|
||||||
|
if box.done then box.done() end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function rowFor(list, id)
|
||||||
|
for i, r in ipairs(list.items) do
|
||||||
|
if r.value == id then return i end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- From an already-open bag list: choose the candy, take USE off the
|
||||||
|
-- submenu, press A on the party picker. Returns the level-up text box.
|
||||||
|
local function useCandy(game, list)
|
||||||
|
local row = rowFor(list, "RARE_CANDY")
|
||||||
|
if not row then return nil, "no RARE CANDY row in the bag" end
|
||||||
|
list.index = row
|
||||||
|
list.onChoose(list.items[row], list)
|
||||||
|
-- out of battle the bag offers USE / TOSS first (start_sub_menus.asm)
|
||||||
|
local sub = game.stack:top()
|
||||||
|
if sub and sub.items and sub.items[1] and sub.items[1].onSelect then
|
||||||
|
game.stack:pop()
|
||||||
|
sub.items[1].onSelect()
|
||||||
|
end
|
||||||
|
local picker = game.stack:top()
|
||||||
|
if not isPicker(picker) then return nil, "party picker never opened" end
|
||||||
|
game.input.pressed = "a"
|
||||||
|
picker:update(1 / 60)
|
||||||
|
game.input.pressed = nil
|
||||||
|
return game.stack:top()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Walk the rest of the level-up sequence: dismiss the level text, then A
|
||||||
|
-- through the stat window (PrintStatsBox).
|
||||||
|
local function finishLevelUp(game, box)
|
||||||
|
dismiss(game.stack, box)
|
||||||
|
local top = game.stack:top()
|
||||||
|
if isBox(top) then return end -- a "learned MOVE" line, dismissed by caller
|
||||||
|
if top and top.update then
|
||||||
|
game.input.pressed = "a"
|
||||||
|
top:update(1 / 60)
|
||||||
|
game.input.pressed = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ---- the report: one candy closed the whole menu -------------------------
|
||||||
|
do
|
||||||
|
local game, lead = freshGame(3)
|
||||||
|
local list = BagMenu.new(game, {})
|
||||||
|
game.stack:push(list)
|
||||||
|
local row = rowFor(list, "RARE_CANDY")
|
||||||
|
check(row ~= nil, "the candy is in the bag")
|
||||||
|
|
||||||
|
local box = useCandy(game, list)
|
||||||
|
check(isBox(box), "the level text opened")
|
||||||
|
eq(lead.level, 51, "the candy leveled the mon")
|
||||||
|
eq(game.save.inventory.RARE_CANDY, 2, "the candy was consumed")
|
||||||
|
check(game.stack.states[1] == list,
|
||||||
|
"the bag list is STILL on the stack under the level text (#796)")
|
||||||
|
|
||||||
|
finishLevelUp(game, box)
|
||||||
|
eq(game.stack:top(), list,
|
||||||
|
"after the stat window the bag is back on top (StartMenu_Item)")
|
||||||
|
eq(list.index, row, "the cursor is still on the RARE CANDY row")
|
||||||
|
eq(list.items[row] and list.items[row].right, "x2",
|
||||||
|
"the count refreshed in place")
|
||||||
|
|
||||||
|
-- the point of the original's behavior: a second candy needs no menu trip
|
||||||
|
local box2 = useCandy(game, list)
|
||||||
|
check(isBox(box2), "a second candy fires from the still-open bag")
|
||||||
|
eq(lead.level, 52, "and it levels the mon again")
|
||||||
|
finishLevelUp(game, box2)
|
||||||
|
eq(game.stack:top(), list, "still on the bag after the second candy")
|
||||||
|
eq(game.save.inventory.RARE_CANDY, 1, "two candies spent")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ---- the last candy: the row empties but the bag still stays up ----------
|
||||||
|
do
|
||||||
|
local game = freshGame(1)
|
||||||
|
local list = BagMenu.new(game, {})
|
||||||
|
game.stack:push(list)
|
||||||
|
local box = useCandy(game, list)
|
||||||
|
check(isBox(box), "the last candy levels too")
|
||||||
|
finishLevelUp(game, box)
|
||||||
|
eq(game.stack:top(), list, "the bag stays open after the last candy")
|
||||||
|
eq(#list.items, 0, "the emptied row left the list")
|
||||||
|
eq(game.save.inventory.RARE_CANDY, nil, "no candies left in the inventory")
|
||||||
|
end
|
||||||
|
|
||||||
|
package.loaded["src.render.TextBox"] = realTextBox
|
||||||
|
package.loaded["src.ui.BagMenu"] = realBag
|
||||||
|
package.loaded["src.ui.PartyMenu"] = realParty
|
||||||
|
require("src.ui.Screens").invalidate()
|
||||||
|
S.finish()
|
||||||
Reference in New Issue
Block a user