#!/usr/bin/env luajit -- Game3 battle bag: catch odds, medicine, X items, doll. package.path = "./?.lua;./?/init.lua;" .. package.path require("tests.game3_cache").mountOrSkip("game3_battle_bag_test") require("tests.fixture_data.game3_items").install() local failed = 0 local function check(cond, msg) if cond then print("[ok] " .. msg) else failed = failed + 1 print("[FAIL] " .. msg) end end local Bag = require("src.core.game3.bag") local ItemsData = require("src.core.game3.items_data") local BattleItems = require("src.core.game3.battle.items") local Pokemon = require("src.core.game3.pokemon") local Battle = require("src.core.game3.battle") local State = require("src.core.game3.battle.state") local Adapter = require("src.core.game3.battle.adapter") require("tests.fixture_data.game3_items").install() Pokemon.install(nil) print("[test] 1. Catch odds / Master Ball") local foe = State.makeBattler({ species = 16, level = 5, hp = 20, maxHp = 20, moves = { 33 }, pp = { 35 }, }, "enemy") local odds = BattleItems.catchOdds(4, foe, { turn = 1 }) check(odds > 0, "POKE BALL odds > 0 (" .. tostring(odds) .. ")") local lowHp = State.makeBattler({ species = 16, level = 5, hp = 1, maxHp = 20, moves = { 33 }, pp = { 35 }, }, "enemy") local oddsLow = BattleItems.catchOdds(4, lowHp, { turn = 1 }) check(oddsLow > odds, "lower HP raises catch odds") local caught, shakes = BattleItems.tryCatch(1, foe, { turn = 1 }, function() return 0 end) check(caught == true and shakes == 4, "MASTER BALL always catches") print("[test] 2. Battle medicine / X item / doll") local bag = Bag.new() Bag.add(bag, 13, 2) Bag.add(bag, 75, 1) Bag.add(bag, 80, 1) Bag.add(bag, 4, 5) local session = { name = "RED", bag = bag, party = { { species = 1, name = "BULBASAUR", hp = 5, maxHp = 20, moves = { 33 }, pp = { 35 } }, }, dex = { seen = {}, owned = {} }, } local player = State.makeBattler(session.party[1], "player", { partyIndex = 1 }) local st = { wild = true, turn = 3, player = player, enemy = foe, playerParty = session.party, } local msgs = {} local ad = Adapter.new(st, function(t) msgs[#msgs + 1] = t end) local r1 = BattleItems.use(st, ad, bag, session, 13, 1) check(r1 == "heal", "POTION heals in battle") check(session.party[1].hp > 5, "HP restored") check(Bag.get(bag, 13) == 1, "POTION consumed") local r2 = BattleItems.use(st, ad, bag, session, 75, nil) check(r2 == "xitem", "X ATTACK used") check((player.stages.attack or 0) >= 1, "attack stage rose") local r3 = BattleItems.use(st, ad, bag, session, 80, nil) check(r3 == "doll", "POKE DOLL flees wild") print("[test] 3. Catch stores mon") bag = Bag.new() Bag.add(bag, 1, 1) -- Master Ball session = { name = "RED", bag = bag, party = { { species = 1, name = "BULBASAUR", hp = 20, maxHp = 20, moves = { 33 }, pp = { 35 } }, }, dex = { seen = {}, owned = {} }, } st = { wild = true, turn = 1, player = State.makeBattler(session.party[1], "player", { partyIndex = 1 }), enemy = State.makeBattler({ species = 19, level = 3, hp = 10, maxHp = 15, moves = { 33 }, pp = { 35 }, }, "enemy"), playerParty = session.party, } ad = Adapter.new(st, function() end) local rc = BattleItems.use(st, ad, bag, session, 1, nil) check(rc == "catch", "Master Ball catch result") check(#session.party == 2, "caught mon added to party") check(session.dex.owned[19] == true, "dex owned RATTATA") check(Bag.get(bag, 1) == 0, "Master Ball consumed") print("[test] 4. Trainer blocks balls") st.wild = false Bag.add(bag, 4, 1) local rt = BattleItems.use(st, ad, bag, session, 4, nil) check(rt == "error", "ball blocked in trainer battle") check(Bag.get(bag, 4) == 1, "ball not consumed when blocked") print("[test] 5. Meta bag catch ends battle") local Runtime = { _session = { name = "RED", bag = Bag.new(), party = { { species = 1, level = 5, hp = 20, maxHp = 20, moves = { 33, 45 }, pp = { 35, 40 } }, }, dex = { seen = {}, owned = {} }, }, } function Runtime.getSession() return Runtime._session end package.loaded["src.core.game3.runtime"] = Runtime Bag.add(Runtime._session.bag, 1, 1) if Battle.isActive() then -- force clean Battle._active = false end local Ui = require("src.core.game3.battle.ui") local started = Battle.start({ wild = true, playerParty = Runtime._session.party, foe = { species = 16, level = 2, hp = 5, maxHp = 15, moves = { 33 }, pp = { 35 } }, onDone = function(r) Runtime._lastResult = r end, }) check(started == true, "battle started (interactive)") Ui._headless = true Ui._queue = {} Ui._showing = false Battle._phase = "actions" Battle._actions = {} Battle._actionI = 1 Battle._metaAct = { kind = "bag", itemId = 1, user = "player" } Battle.update(0, nil) check(Battle._pendingEnd == "catch" or Battle.getResult() == "catch" or #Runtime._session.party >= 2, "catch meta sets catch / stores mon (pending=" .. tostring(Battle._pendingEnd) .. " party=" .. tostring(#Runtime._session.party) .. ")") for _ = 1, 20 do if not Battle.isActive() then break end Battle._phase = Battle._phase == "ending" and "ending" or "ending" Ui._queue = {} Battle.update(0, nil) end check(Runtime._lastResult == "catch" or #Runtime._session.party >= 2, "onDone catch or mon stored") print("[test] 6. Battle Bag exit restores menu mode cleanly") local BagMenu = require("src.ui.game3.bag_menu") Bag.add(Runtime._session.bag, 13, 5) -- Add potions so bag is not empty Ui._session = Runtime._session Ui._queue = {} Ui._showing = false Ui._linger = false Ui._timed = nil Ui._headless = false Battle._active = true Battle._phase = "command" Battle._st = { player = State.makeBattler(Runtime._session.party[1], "player"), playerParty = Runtime._session.party } Ui.bindState(Battle._st) Ui.openMenu() check(Ui._mode == "menu", "initial battle UI mode is menu") local fakeInputA = { wasPressed = function(self, k) local key = (k ~= nil) and k or self; return key == "a" end } local fakeInputB = { wasPressed = function(self, k) local key = (k ~= nil) and k or self; return key == "b" end } local fakeInputNone = { wasPressed = function() return false end } -- Select BAG Ui._menuIndex = 2 Ui.handleInput(fakeInputA) check(Ui._mode == "bag", "Ui._mode transitioned to bag") check(BagMenu.isOpen() == true, "BagMenu is open") -- Allow opening curtain animation to finish BagMenu.settle() -- Simulate pressing B in BagMenu BagMenu.handleInput(fakeInputB) -- Allow closing transition to finish BagMenu.settle() check(BagMenu.isOpen() == false, "BagMenu closed after B press") check(Ui._mode == "menu", "Ui._mode restored to menu after bag exit") -- Verify battle update accepts subsequent command input without freeze Battle.update(0, { input = fakeInputNone }) check(Ui._mode == "menu", "Ui._mode remains menu in command phase") print("[test] 7. In-battle Party Item Selection validation") Bag.add(Runtime._session.bag, 13, 1) -- Potion local monFull = { species = 1, hp = 20, maxHp = 20 } local monHurt = { species = 1, hp = 5, maxHp = 20 } local monFaint = { species = 1, hp = 0, maxHp = 20 } local canHurt, _ = BattleItems.canUseOn(nil, 13, 1, monHurt) local canFull, _ = BattleItems.canUseOn(nil, 13, 1, monFull) local canFaint, _ = BattleItems.canUseOn(nil, 13, 1, monFaint) check(canHurt == true, "Potion can be used on hurt mon") check(canFull == false, "Potion cannot be used on full HP mon") check(canFaint == false, "Potion cannot be used on fainted mon") local canReviveFaint, _ = BattleItems.canUseOn(nil, 24, 1, monFaint) -- Revive local canReviveHurt, _ = BattleItems.canUseOn(nil, 24, 1, monHurt) check(canReviveFaint == true, "Revive can be used on fainted mon") check(canReviveHurt == false, "Revive cannot be used on alive mon") Battle._active = false if failed > 0 then print(string.format("\n%d FAILED", failed)) os.exit(1) end print("\nAll battle-bag tests passed.") os.exit(0)