Files
gen1recomp/tests/game3_item_use_and_parcel_test.lua

305 lines
11 KiB
Lua

-- Test suite for ItemUse and Oak's Lab parcel delivery scene.
require("tests.game3_cache").requireData("game3_item_use_and_parcel_test")
local GameVersion = require("src.core.GameVersion")
GameVersion.set("firered")
local ItemUse = require("src.core.game3.item_use")
local ItemsData = require("src.core.game3.items_data")
local Bag = require("src.core.game3.bag")
local Pokemon = require("src.core.game3.pokemon")
local Evolution = require("src.core.game3.evolution")
local Flags = require("src.core.game3.scripting.flags")
local Ctx = require("src.core.game3.scripting.ctx")
local Vm = require("src.core.game3.scripting.vm")
local Space = require("src.core.game3.scripting.space")
print("[test] 1. ItemUse.needsPartyTarget")
assert(ItemUse.needsPartyTarget("POTION") == true, "Potion needs party target")
assert(ItemUse.needsPartyTarget(13) == true, "Potion numeric ID needs party target")
assert(ItemUse.needsPartyTarget("SUPER_POTION") == true, "Super Potion needs party target")
assert(ItemUse.needsPartyTarget("ANTIDOTE") == true, "Antidote needs party target")
assert(ItemUse.needsPartyTarget("REVIVE") == true, "Revive needs party target")
assert(ItemUse.needsPartyTarget("RARE_CANDY") == true, "Rare Candy needs party target")
assert(ItemUse.needsPartyTarget("FIRE_STONE") == true, "Fire Stone needs party target")
assert(ItemUse.needsPartyTarget("TM01") == true, "TM01 needs party target")
assert(ItemUse.needsPartyTarget("BICYCLE") == false, "Bicycle does not need party target")
assert(ItemUse.needsPartyTarget("ESCAPE_ROPE") == false, "Escape rope does not need party target")
assert(ItemUse.needsPartyTarget("REPEL") == false, "Repel does not need party target")
print("[ok] needsPartyTarget correctly identifies party items vs field items")
print("[test] 2. ItemUse on party Pokémon")
Pokemon.install(nil)
local session = {
name = "RED",
party = {
{
species = 1, -- Bulbasaur
speciesId = 1,
level = 5,
hp = 10,
maxHp = 20,
status = "PSN",
sleep = 0,
moves = { { id = 33, pp = 35 } },
},
{
species = 4, -- Charmander
speciesId = 4,
level = 5,
hp = 0,
maxHp = 19,
moves = { { id = 10, pp = 35 } },
}
}
}
local bag = Bag.new()
Bag.add(bag, "POTION", 2)
Bag.add(bag, "ANTIDOTE", 1)
Bag.add(bag, "REVIVE", 1)
Bag.add(bag, "RARE_CANDY", 1)
-- Heal Mon 1 with Potion
local ok, res = ItemUse.useField(session, bag, "POTION", 1)
assert(ok == true, "Potion used successfully")
assert(session.party[1].hp == 20, "HP restored to max (10+20 capped at 20)")
assert(Bag.get(bag, "POTION") == 1, "1 Potion remaining in bag")
-- Clear status on Mon 1 with Antidote
ok, res = ItemUse.useField(session, bag, "ANTIDOTE", 1)
assert(ok == true, "Antidote used successfully")
assert(session.party[1].status == nil, "Poison cleared")
assert(Bag.get(bag, "ANTIDOTE") == 0, "Antidote consumed")
-- Revive Mon 2
ok, res = ItemUse.useField(session, bag, "REVIVE", 2)
assert(ok == true, "Revive used successfully")
assert(session.party[2].hp == 9, "Half HP restored on faint mon")
assert(Bag.get(bag, "REVIVE") == 0, "Revive consumed")
-- Rare Candy Mon 1
ok, res = ItemUse.useField(session, bag, "RARE_CANDY", 1)
assert(ok == true, "Rare Candy used successfully")
assert(session.party[1].level == 6, "Level increased to 6")
assert(Bag.get(bag, "RARE_CANDY") == 0, "Rare Candy consumed")
print("[ok] party healing, status cure, reviving, and rare candy all passed")
print("[test] 3. VAR_FACING preservation in Vm")
local vm = Vm.new({
store = Flags.newStore(),
scripts = {
test_facing = {
{ op = "compare_var_to_value", var = Ctx.VAR_FACING, value = 2 },
{ op = "call_if", cond = 1, target = "matched_north" },
{ op = "end" },
},
matched_north = {
{ op = "setvar", var = 0x4000, value = 99 },
{ op = "return" },
}
}
})
vm:startTalk("test_facing", 1, 2) -- Facing DIR_NORTH (2)
assert(Flags.getVar(vm.store, vm.ctx, 0x4000) == 99, "VAR_FACING branch correctly executed")
print("[ok] VAR_FACING correctly passed and matched conditional branches")
print("[test] 4. Script extraction chunk size and complete ReceiveDexScene")
local Dataset = require("src.core.game3.dataset")
Dataset.mountExtractRoots()
local bundle = Space.ensureBundle(nil)
assert(bundle and bundle.scripts, "Bundle scripts exist")
local receiveDex = bundle.scripts["g3:0816961e"]
assert(receiveDex, "ReceiveDexScene script exists in bundle")
local lastOp = receiveDex[#receiveDex]
assert(lastOp and lastOp.op == "end", "ReceiveDexScene ends with 'end' (not truncated mid-script)")
-- Check that the end of ReceiveDexScene contains removeobject (8) and setvar (PALLET_TOWN_PROFESSOR_OAKS_LAB = 6)
local hasRemoveObject8 = false
local hasSetVarOakLab = false
for _, row in ipairs(receiveDex) do
if row.op == "removeobject" and (row.localId == 8 or row[1] == 8) then
hasRemoveObject8 = true
end
if row.op == "setvar" and (row.var == 0x4055 or row[1] == 0x4055 or row.var == 16469 or row[1] == 16469) and (row.value == 6 or row[2] == 6) then
hasSetVarOakLab = true
end
end
assert(hasRemoveObject8, "ReceiveDexScene contains removeobject 8 (Rival exits)")
assert(hasSetVarOakLab, "ReceiveDexScene contains setvar VAR_MAP_SCENE_PALLET_TOWN_PROFESSOR_OAKS_LAB, 6")
print("[ok] ReceiveDexScene is fully extracted and non-truncated")
print("[test] 5. PartyMenu item usage, give, take, and message dismiss")
local PartyMenu = require("src.ui.game3.party_menu")
local BagMenu = require("src.ui.game3.bag_menu")
-- Setup mock input
local mockInput = {
_pressed = {},
wasPressed = function(self, key) return self._pressed[key] == true end,
press = function(self, key) self._pressed = { [key] = true } end,
clear = function(self) self._pressed = {} end,
}
-- Test Give to Mon
local testParty = {
{ species = 1, hp = 10, maxHp = 20, level = 5 },
{ species = 4, hp = 19, maxHp = 19, level = 5 },
}
local testBag = Bag.new()
Bag.add(testBag, "ORAN_BERRY", 2)
Bag.add(testBag, "POTION", 1)
local closed = false
PartyMenu.show(testParty, nil, {
session = { party = testParty, bag = testBag },
bag = testBag,
item = "ORAN_BERRY",
mode = "give",
onClose = function() closed = true end,
})
assert(PartyMenu.isOpen() == true, "PartyMenu is open")
assert(PartyMenu.mode == "give", "PartyMenu is in give mode")
-- Press A on Mon 1 to give item
mockInput:press("a")
PartyMenu.handleInput(mockInput)
assert(PartyMenu.mode == "message", "Transitions to message mode with give result")
assert(testParty[1].heldItem ~= nil, "Mon 1 now holds item")
assert(Bag.get(testBag, "ORAN_BERRY") == 1, "1 berry consumed from bag")
-- Press A to dismiss message -> should close and invoke onClose
mockInput:press("a")
PartyMenu.handleInput(mockInput)
assert(PartyMenu.isOpen() == false, "PartyMenu closed after give message dismissed")
assert(closed == true, "onClose invoked")
-- Test Take from Mon via ItemUse
local takeOk, _, takeMsg = ItemUse.takeFromMon({ party = testParty }, testBag, 1)
assert(takeOk == true, "takeFromMon succeeds")
assert(testParty[1].heldItem == nil, "Item taken from Mon 1")
assert(Bag.get(testBag, "ORAN_BERRY") == 2, "Berry returned to bag")
-- Test Use Potion via PartyMenu
PartyMenu.show(testParty, nil, {
session = { party = testParty, bag = testBag },
bag = testBag,
item = "POTION",
mode = "use",
})
assert(PartyMenu.mode == "use", "PartyMenu is in use mode")
mockInput:press("a")
PartyMenu.handleInput(mockInput)
assert(PartyMenu._hpAnim ~= nil, "HP bar animation started")
-- Step animation to completion
PartyMenu.update(1.0)
assert(PartyMenu._hpAnim == nil, "HP bar animation completed")
assert(PartyMenu.mode == "message", "Transitions to message mode showing heal")
assert(testParty[1].hp == 20, "Mon 1 healed to full 20 HP")
assert(Bag.get(testBag, "POTION") == 0, "Potion consumed")
-- Dismiss message (depleted potion -> closes)
mockInput:press("a")
PartyMenu.handleInput(mockInput)
assert(PartyMenu.isOpen() == false, "PartyMenu closed after single potion used")
print("[ok] PartyMenu item use, give, take, and message lifecycle passed")
print("[test] 6. BagMenu USE Potion opens PartyMenu and heals with HP lerp")
testParty[1].hp = 5
Bag.add(testBag, "POTION", 1)
BagMenu.show({ party = testParty, bag = testBag }, { bag = testBag })
assert(BagMenu.isOpen() == true, "BagMenu is open")
BagMenu.settle()
BagMenu.mode = "action"
BagMenu.actionCursor = 1 -- USE
mockInput:press("a")
BagMenu.handleInput(mockInput)
BagMenu.settle()
assert(PartyMenu.isOpen() == true, "PartyMenu opened from BagMenu USE")
assert(PartyMenu.mode == "use", "PartyMenu is in use mode")
-- Select Mon 1 with A
mockInput:press("a")
PartyMenu.handleInput(mockInput)
assert(PartyMenu._hpAnim ~= nil, "PartyMenu HP animation active")
PartyMenu.update(1.0)
assert(PartyMenu.mode == "message", "PartyMenu shows heal message")
assert(testParty[1].hp == 20, "Mon 1 healed to 20")
-- Dismiss message
mockInput:press("a")
PartyMenu.handleInput(mockInput)
assert(PartyMenu.isOpen() == false, "PartyMenu closed after potion used")
assert(BagMenu.mode == "list", "BagMenu returned to list mode")
print("[ok] BagMenu USE Potion -> PartyMenu -> heal passed")
print("[test] 7. Ground item ball pickup via std:1 sets object flag and removes object")
local Space = require("src.core.game3.scripting.space")
local Objects = require("src.core.game3.objects")
local Flags = require("src.core.game3.scripting.flags")
Space.store = Flags.newStore()
Space.active = true
local itemBallDef = {
localId = 5,
index = 5,
x = 17,
y = 54,
flag = 340,
graphics = 92,
graphicsId = 92,
sprite = "SPRITE_POKE_BALL",
scriptKey = "test_item_ball",
}
Objects.loadMap(nil, "TEST_MAP", { objects = { itemBallDef } })
local spawned = Objects.find(5)
assert(spawned ~= nil, "Item ball spawned")
assert(spawned.visible == true, "Item ball initially visible")
assert(Space.objectVisible(itemBallDef) == true, "objectVisible true before pickup")
local testItemScript = {
{ op = "setorcopyvar", [1] = 0x8000, [2] = 34 }, -- Great Ball
{ op = "setorcopyvar", [1] = 0x8001, [2] = 1 },
{ op = "callstd", [1] = 1, std = 1 },
{ op = "end" },
}
local Field = require("src.core.game3.field")
Field._session = { bag = testBag }
local Adapters = require("src.core.game3.scripting.adapters")
local adapters = Adapters.host(nil, nil, nil)
Space.vm = Vm.new({
store = Space.store,
scripts = {
test_item_ball = testItemScript,
-- data/scripts/obtain_item.inc:106
["std:1"] = {
{ op = "copyvar", [1] = 0x8004, [2] = 0x8000 },
{ op = "copyvar", [1] = 0x8005, [2] = 0x8001 },
{ op = "checkitemspace", [1] = 0x8000, [2] = 0x8001 },
{ op = "copyvar", [1] = 0x8007, [2] = 0x800D },
{ op = "compare_var_to_value", var = 0x8007, value = 1 },
{ op = "call_if", cond = 1, target = "EventScript_PickUpItem" },
{ op = "return" },
},
-- data/scripts/obtain_item.inc:122
EventScript_PickUpItem = {
{ op = "removeobject", [1] = 0x800F },
{ op = "additem", [1] = 0x8004, [2] = 0x8005 },
{ op = "return" },
},
},
adapters = adapters,
})
local prevCount = Bag.get(testBag, 34) or 0
Space.startScript("test_item_ball", 5, 1)
assert(Bag.get(testBag, 34) == prevCount + 1, "Great Ball added to bag")
assert(Flags.getFlag(Space.store, nil, 340) == true, "Flag 340 set by item pickup")
assert(spawned.visible == false, "Item ball object hidden")
assert(Space.objectVisible(itemBallDef) == false, "objectVisible false after pickup")
print("[ok] Ground item pickup sets flag and removes object")
print("[test] all passed successfully!")