mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-28 23:00:21 -04:00
59c6629457
- Fix held item (berries, herbs, pinch stat items) activation and permanent consumption on battler and party data during battles. - Enforce mandatory bicycle riding on Cycling Road (Route 16-18) when dismounting Surf, entering map, loading saves, and prevent bike dismount attempts. - Fix warp and door exit arrivals (e.g. Safari Zone gate) to execute warpExitArrival post-fade step so the player is placed on the correct forward tile and does not bypass triggers. - Optimize field rendering pipeline by caching animated tileset pieces, pooling native metatile cell allocations, and hoisting dynamic module lookups to fast accessors.
198 lines
8.3 KiB
Lua
198 lines
8.3 KiB
Lua
#!/usr/bin/env luajit
|
|
-- ROM-free comprehensive unit tests for Game 3 (FireRed / LeafGreen) held item behaviors:
|
|
-- HP restore berries, pinch stat berries, status berries, herbs, leftovers, focus band, etc.
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local failed, passed = 0, 0
|
|
local function check(cond, msg)
|
|
if cond then
|
|
passed = passed + 1
|
|
print("[ok] " .. msg)
|
|
else
|
|
failed = failed + 1
|
|
print("[FAIL] " .. msg)
|
|
end
|
|
end
|
|
|
|
local Pokemon = require("src.core.game3.pokemon")
|
|
Pokemon._names = { [1] = "BULBASAUR", [4] = "CHARMANDER", [7] = "SQUIRTLE", [25] = "PIKACHU" }
|
|
Pokemon._types = { [1] = { 12, 3 }, [4] = { 10, 10 }, [7] = { 11, 11 }, [25] = { 13, 13 } }
|
|
Pokemon._stats = {
|
|
[1] = { hp = 45, atk = 49, def = 49, spe = 45, spa = 65, spd = 65 },
|
|
[4] = { hp = 39, atk = 52, def = 43, spe = 65, spa = 60, spd = 50 },
|
|
[7] = { hp = 44, atk = 48, def = 65, spe = 43, spa = 50, spd = 64 },
|
|
[25] = { hp = 35, atk = 55, def = 40, spe = 90, spa = 50, spd = 50 },
|
|
}
|
|
Pokemon._abilities = { [1] = { 65, 65 }, [4] = { 66, 66 }, [7] = { 67, 67 }, [25] = { 9, 9 } }
|
|
|
|
local RomText = require("src.core.game3.rom_text")
|
|
RomText.plain = function(key) return tostring(key) end
|
|
RomText.box = function(key) return tostring(key) end
|
|
RomText.ascii = function(key) return tostring(key) end
|
|
RomText.ir = function(key) return {} end
|
|
|
|
local BattleText = require("src.core.game3.battle.battle_text")
|
|
BattleText.get = function(id, fill)
|
|
fill = fill or {}
|
|
return string.format("[%s: item=%s buff1=%s]", id, tostring(fill.lastItem or ""), tostring(fill.buff1 or ""))
|
|
end
|
|
|
|
local Moves = require("src.core.game3.battle.moves")
|
|
local ROM_MOVES = {
|
|
[1] = { name = "POUND", effect = 0, power = 40, type = 0, accuracy = 100, pp = 35, secondaryChance = 0, target = 0, priority = 0, flags = 51 },
|
|
[33] = { name = "TACKLE", effect = 0, power = 35, type = 0, accuracy = 95, pp = 35, secondaryChance = 0, target = 0, priority = 0, flags = 51 },
|
|
[45] = { name = "GROWL", effect = 18, power = 0, type = 0, accuracy = 100, pp = 40, secondaryChance = 0, target = 8, priority = 0, flags = 22 },
|
|
[86] = { name = "THUNDER WAVE", effect = 67, power = 0, type = 13, accuracy = 100, pp = 20, secondaryChance = 0, target = 0, priority = 0, flags = 22 },
|
|
}
|
|
for id, row in pairs(ROM_MOVES) do row.numId = id end
|
|
Moves._romLoaded = true
|
|
Moves._rom = ROM_MOVES
|
|
Moves._names = { [1] = "POUND", [33] = "TACKLE", [45] = "GROWL", [86] = "THUNDER WAVE" }
|
|
Pokemon._moveNames = Moves._names
|
|
Pokemon._romMoveNames = Moves._names
|
|
|
|
local State = require("src.core.game3.battle.state")
|
|
local Engine = require("src.core.game3.battle.engine")
|
|
local Adapter = require("src.core.game3.battle.adapter")
|
|
local HeldItems = require("src.core.game3.battle.held_items")
|
|
local Damage = require("src.core.game3.battle.damage")
|
|
|
|
local function mon(o)
|
|
return {
|
|
species = o.species or 1, level = o.level or 50,
|
|
hp = o.hp or 100, maxHp = o.maxHp or 100,
|
|
attack = o.attack or 50, defense = o.defense or 50,
|
|
spAtk = o.spAtk or 50, spDef = o.spDef or 50,
|
|
speed = o.speed or 50, ability = o.ability or 0,
|
|
nickname = o.nickname, moves = o.moves or { 33 },
|
|
pp = o.pp or { 20, 20, 20, 20 }, maxPp = o.maxPp,
|
|
status = o.status, item = o.item, heldItem = o.item,
|
|
gender = o.gender, personality = o.personality or 0,
|
|
}
|
|
end
|
|
|
|
local function battle(p, e)
|
|
local party = { mon(p) }
|
|
local foeParty = { mon(e) }
|
|
local st = State.new({ wild = true, playerParty = party, foeParty = foeParty })
|
|
st.rng = function(lo, hi) return lo end
|
|
local ad = Adapter.new(st)
|
|
return st, ad
|
|
end
|
|
|
|
local function useMove(st, ad, user, move, slot)
|
|
local target = (user == st.player) and st.enemy or st.player
|
|
local out = {}
|
|
Engine.resolveMove(user, target, move, slot or 1, ad, st, out)
|
|
return out
|
|
end
|
|
|
|
local function eot(st, ad)
|
|
return Engine.collectResidualEvents(st, ad)
|
|
end
|
|
|
|
print("=== 1. Oran Berry mid-turn damage consumption ===")
|
|
do
|
|
-- Player starts with Oran Berry (139) at 60/100 HP.
|
|
-- Enemy attacks with Tackle (power 35).
|
|
local st, ad = battle({ item = 139, hp = 60, maxHp = 100 }, { moves = { 33 } })
|
|
check(st.player.item == 139, "player battler holds Oran Berry before attack")
|
|
check(st.playerParty[1].item == 139, "player party mon holds Oran Berry before attack")
|
|
|
|
useMove(st, ad, st.enemy, 33)
|
|
|
|
-- After taking damage, HP dropped below 50, berry activated, healed 10 HP, and was consumed.
|
|
check(st.player.item == 0, "player battler item is cleared (0)")
|
|
check(st.playerParty[1].item == nil, "player party mon item is nil")
|
|
check(st.playerParty[1].heldItem == nil, "player party mon heldItem is nil")
|
|
check(st.playerSide.expUsedHeldItem == 139, "expUsedHeldItem recorded for Recycle")
|
|
end
|
|
|
|
print("=== 2. Sitrus Berry mid-turn damage consumption ===")
|
|
do
|
|
-- Sitrus Berry (142) heals 30 HP when HP <= 50%
|
|
local st, ad = battle({ item = 142, hp = 60, maxHp = 100 }, { moves = { 33 } })
|
|
useMove(st, ad, st.enemy, 33)
|
|
check(st.player.item == 0, "Sitrus Berry consumed on battler")
|
|
check(st.playerParty[1].item == nil, "Sitrus Berry consumed on party mon")
|
|
check(st.playerSide.expUsedHeldItem == 142, "Sitrus Berry recorded for Recycle")
|
|
end
|
|
|
|
print("=== 3. Enemy mon consuming held berry mid-turn ===")
|
|
do
|
|
-- Foe holds Oran Berry, player attacks foe
|
|
local st, ad = battle({ moves = { 33 } }, { item = 139, hp = 60, maxHp = 100 })
|
|
check(st.enemy.item == 139, "enemy battler holds Oran Berry")
|
|
check(st.foeParty[1].item == 139, "enemy party mon holds Oran Berry")
|
|
|
|
useMove(st, ad, st.player, 33)
|
|
|
|
check(st.enemy.item == 0, "enemy battler item cleared after consumption")
|
|
check(st.foeParty[1].item == nil, "enemy party mon item cleared after consumption")
|
|
check(st.enemySide.expUsedHeldItem == 139, "enemy side recorded used held item")
|
|
end
|
|
|
|
print("=== 4. Status curing berries (Cheri / Pecha / Lum) at move end ===")
|
|
do
|
|
-- Cheri Berry (133) cures paralysis immediately when Thunder Wave is used
|
|
local st, ad = battle({ item = 133 }, { moves = { 86 } })
|
|
useMove(st, ad, st.enemy, 86)
|
|
check(ad:status(st.player) == nil, "paralysis was cured immediately")
|
|
check(st.player.item == 0, "Cheri Berry was consumed")
|
|
check(st.playerParty[1].item == nil, "party mon Cheri Berry consumed")
|
|
end
|
|
|
|
print("=== 5. Stat boosting pinch berries (Liechi Berry) mid-turn ===")
|
|
do
|
|
-- Liechi Berry (168) boosts Attack when HP <= 25% (1/4 maxHp)
|
|
local st, ad = battle({ item = 168, hp = 30, maxHp = 100 }, { moves = { 33 } })
|
|
useMove(st, ad, st.enemy, 33)
|
|
check(st.player.stages.attack == 1, "Attack rose by 1 stage from Liechi Berry")
|
|
check(st.player.item == 0, "Liechi Berry consumed on battler")
|
|
check(st.playerParty[1].item == nil, "Liechi Berry consumed on party mon")
|
|
end
|
|
|
|
print("=== 6. White Herb restores stat drop mid-turn ===")
|
|
do
|
|
-- White Herb (180) restores lowered stats when Growl is used
|
|
local st, ad = battle({ item = 180 }, { moves = { 45 } })
|
|
useMove(st, ad, st.enemy, 45)
|
|
check(st.player.stages.attack == 0, "Attack stat stage restored to 0 by White Herb")
|
|
check(st.player.item == 0, "White Herb consumed on battler")
|
|
check(st.playerParty[1].item == nil, "White Herb consumed on party mon")
|
|
end
|
|
|
|
print("=== 7. Leftovers activates only at end of turn (not mid-turn) ===")
|
|
do
|
|
-- Leftovers (200) heals at end of turn, not during move resolution
|
|
local st, ad = battle({ item = 200, hp = 80, maxHp = 100 }, { moves = { 45 } })
|
|
-- Growl does no damage
|
|
useMove(st, ad, st.enemy, 45)
|
|
check(st.player.mon.hp == 80, "Leftovers did not trigger mid-turn")
|
|
check(st.player.item == 200, "Leftovers is not consumed")
|
|
|
|
eot(st, ad)
|
|
check(st.player.mon.hp == 86, "Leftovers healed maxHp/16 (6 HP) at end of turn")
|
|
check(st.player.item == 200, "Leftovers remains held after end of turn")
|
|
end
|
|
|
|
print("=== 8. Leppa Berry restores PP ===")
|
|
do
|
|
-- Leppa Berry (138) restores 10 PP when a move is at 0 PP
|
|
local st, ad = battle({ item = 138, moves = { 33 }, pp = { 0 }, maxPp = { 35 } }, {})
|
|
Engine.afterAction(st, ad)
|
|
check(st.player.mon.pp[1] == 10, "Leppa Berry restored 10 PP")
|
|
check(st.player.item == 0, "Leppa Berry consumed")
|
|
check(st.playerParty[1].item == nil, "party mon Leppa Berry consumed")
|
|
end
|
|
|
|
print("-----------------------------------------")
|
|
if failed > 0 then
|
|
print(string.format("[result] %d test(s) FAILED, %d passed", failed, passed))
|
|
os.exit(1)
|
|
else
|
|
print(string.format("[result] ALL %d tests PASSED", passed))
|
|
os.exit(0)
|
|
end
|