Files
gen1recomp/tests/gen2_max_pp_test.lua
T
2026-09-23 16:34:15 -04:00

278 lines
10 KiB
Lua

-- engine/items/item_effects.asm:2752, :2801, :2836
package.path = "./?.lua;./?/init.lua;" .. package.path
local S = require("tests.harness").suite("gen2 max pp")
local check, eq = S.check, S.eq
love = require("tests.love_stub")
local Boxes = require("src.core.gen2.Boxes")
local Breeding = require("src.core.gen2.Breeding")
local ItemEffects = require("src.core.gen2.ItemEffects")
local Mon = require("src.battle.gen2.Mon")
local World = require("src.world.gen2.World")
local MOVES = {
WATER_GUN = { id = "WATER_GUN", name = "WATER GUN", pp = 25 },
RAGE = { id = "RAGE", name = "RAGE", pp = 20 },
LEER = { id = "LEER", name = "LEER", pp = 30 },
SCRATCH = { id = "SCRATCH", name = "SCRATCH", pp = 35 },
MEAN_LOOK = { id = "MEAN_LOOK", name = "MEAN LOOK", pp = 5 },
GROWL = { id = "GROWL", name = "GROWL", pp = 40 },
SKETCH = { id = "SKETCH", name = "SKETCH", pp = 1 },
}
local POKEMON = {
growthRates = {
GROWTH_MEDIUM_FAST = { numerator = 1, denominator = 1, squared = 0,
linear = 0, constant = 0 },
},
TOTODILE = {
id = "TOTODILE", index = 158, dex = 158, name = "TOTODILE",
baseStats = { hp = 50, attack = 65, defense = 64, speed = 43,
specialAttack = 44, specialDefense = 48 },
types = { "WATER", "WATER" }, growthRate = "GROWTH_MEDIUM_FAST",
genderRatio = 31, eggGroups = { "EGG_MONSTER", "EGG_WATER_1" },
evolutions = {}, levelMoves = { { level = 1, move = "SCRATCH" } },
tmhm = {},
},
}
local DATA = { pokemon = POKEMON, moves = MOVES, items = {} }
local function broken()
return {
{ id = "LEER", pp = 30, maxPp = 30 },
{ id = "SCRATCH", pp = 35, maxPp = 35 },
{ id = "RAGE", pp = 9 },
{ id = "WATER_GUN", pp = 1 },
}
end
local function toto(moves)
local mon = Mon.new(DATA, "TOTODILE", 16, {
dvs = { attack = 10, defense = 10, speed = 10, special = 10 },
moves = moves or broken(),
})
mon.hp = 1
return mon
end
do
eq(Mon.maxPpOf({ id = "WATER_GUN", pp = 1 }, DATA), 25,
"a record with no maxPp reads the move's base PP")
eq(Mon.maxPpOf({ id = "WATER_GUN", pp = 1, maxPp = 1 }, DATA), 25,
"a stored max below base cannot lower it")
eq(Mon.maxPpOf({ id = "WATER_GUN", pp = 3, maxPp = 30 }, DATA), 30,
"a stored max one PP Up above base keeps that PP Up")
eq(Mon.maxPpOf({ id = "WATER_GUN", pp = 3, maxPp = 31 }, DATA), 25,
"a stored max ComputeMaxPP cannot produce infers no PP Up")
eq(Mon.maxPpOf({ id = "GROWL", pp = 0, ppUps = 3 }, DATA), 61,
"three PP Ups on a 40 PP move cap at 61")
eq(Mon.maxPpOf({ id = "GROWL", pp = 0, ppUps = 9 }, DATA), 61,
"the PP Up count is two bits")
eq(Mon.maxPpOf({ id = "MEAN_LOOK", pp = 4, maxPp = 4 }, DATA), 5,
"MEAN LOOK 4/4 reads 5")
eq(Mon.maxPpOf({ id = "SKETCH", pp = 1, ppUps = 3 }, DATA), 1,
"a 1 PP move gains nothing per PP Up")
eq(Mon.ppUpsOf({ id = "WATER_GUN", maxPp = 40 }, DATA), 3,
"ppUpsOf infers three from a stored 40 on a 25 PP move")
end
do
local mon = toto()
local save = { party = { mon } }
World.healParty({ game = { save = save, data = DATA } })
eq(mon.moves[3].pp, 20, "HealParty refills RAGE to 20, not 9")
eq(mon.moves[4].pp, 25, "HealParty refills WATER GUN to 25, not 1")
eq(mon.moves[4].maxPp, 25, "and writes the derived max back")
eq(mon.hp, mon.maxHp, "HP is still refilled")
end
do
local keep = toto()
keep.hp = keep.maxHp
local mon = toto()
local save = { party = { keep, mon }, boxes = {}, boxNames = {},
currentBox = 1 }
local ok = Boxes.deposit(save, 2, 1, DATA)
check(ok, "the deposit went through")
eq(mon.moves[3].pp, 20, "RestorePPOfDepositedPokemon refills RAGE to 20")
eq(mon.moves[4].pp, 25, "and WATER GUN to 25")
local caught = toto()
Boxes.enterBox(caught, DATA)
eq(caught.moves[4].pp, 25, "enterBox with data refills from the move table")
end
do
local mon = toto()
mon.moves[4].pp = 0
local res = ItemEffects.usePpItem("ETHER", mon, 4, DATA)
check(res.used, "an ETHER on a spent maxPp-less move is not refused")
eq(mon.moves[4].pp, 10, "and adds 10")
res = ItemEffects.usePpItem("MAX_ETHER", mon, 4, DATA)
check(res.used, "MAX ETHER still has room")
eq(mon.moves[4].pp, 25, "and fills to 25")
local full = toto()
full.moves[3].pp, full.moves[4].pp = 20, 25
res = ItemEffects.usePpItem("MAX_ELIXER", full, nil, DATA)
eq(res.used, false, "MAX ELIXER on a full moveset is refused")
end
do
local mon = toto({ { id = "WATER_GUN", pp = 30, maxPp = 30 } })
local res = ItemEffects.usePpItem("PP_UP", mon, 1, DATA)
check(res.used, "PP UP on a move with one inferred PP Up works")
eq(mon.moves[1].ppUps, 2, "and counts from the inferred one")
eq(mon.moves[1].maxPp, 35, "to 35")
local maxed = toto({ { id = "WATER_GUN", pp = 40, maxPp = 40 } })
res = ItemEffects.usePpItem("PP_UP", maxed, 1, DATA)
eq(res.used, false, "a stored 40 on a 25 PP move is already maxed")
end
do
local save = { party = {}, player = { name = "KRIS", id = 1, money = 3000 },
pokedex = { seen = {}, caught = {} }, events = {} }
local dc = Breeding.dayCare(save)
dc.man.mon = toto()
local ok, back = Breeding.withdraw(DATA, save, "man")
check(ok, "the Day-Care withdrawal went through")
eq(back.moves[4].pp, 25, "HealPartyMon refills WATER GUN to 25, not nil")
eq(back.moves[3].pp, 20, "and RAGE to 20")
end
do
local party = toto()
local boxed = toto()
local daycare = toto()
local legacy = toto({ { id = "WATER_GUN" }, { id = "RAGE", pp = 50 } })
local upped = toto({ { id = "WATER_GUN", pp = 12, maxPp = 35 } })
local save = { party = { party, legacy, upped }, boxes = { { boxed } },
dayCare = { man = { mon = daycare }, lady = {} } }
Mon.syncSaveIdentity(save, DATA)
eq(party.moves[4].maxPp, 25, "the party's WATER GUN gets its max back")
eq(party.moves[4].pp, 1, "and keeps the PP it had left")
eq(party.moves[3].maxPp, 20, "RAGE gets 20")
eq(boxed.moves[4].maxPp, 25, "boxed mons are repaired too")
eq(daycare.moves[4].maxPp, 25, "and the Day-Care's")
eq(legacy.moves[1].pp, 25, "a record with no PP at all reads full")
eq(legacy.moves[2].pp, 20, "and PP past max is clamped")
eq(upped.moves[1].maxPp, 35, "a real PP Up max survives the repair")
eq(upped.moves[1].pp, 12, "with its current PP")
end
do
local BoxMenu = require("src.ui.gen2.BoxMenu")
local BugContest = require("src.core.gen2.BugContest")
local game = { data = DATA, input = { wasPressed = function() return false end,
isDown = function() return false end } }
local keep = toto()
keep.hp = keep.maxHp
local mon = toto()
local save = { party = { keep, mon }, boxes = {}, boxNames = {},
currentBox = 1 }
game.save = save
local menu = BoxMenu.new(game, { save = save, mode = "deposit",
onClose = function() end })
menu.index = 2
menu:doDeposit()
eq(Boxes.box(save, 1)[1], mon, "the PC DEPOSIT went through")
eq(mon.moves[4].pp, 25, "PC DEPOSIT refills WATER GUN to 25")
eq(mon.moves[3].pp, 20, "and RAGE to 20")
keep = toto()
keep.hp = keep.maxHp
mon = toto()
save = { party = { keep, mon }, boxes = {}, boxNames = {}, currentBox = 1 }
game.save = save
menu = BoxMenu.new(game, { save = save, mode = "move",
onClose = function() end })
menu.moveFrom = { box = 0, slot = 2 }
menu.boxIndex, menu.index = 1, 1
menu:insertMon()
eq(Boxes.box(save, 1)[1], mon, "MOVE party -> box went through")
eq(mon.moves[4].pp, 25, "MOVE into a box refills WATER GUN to 25")
save = { party = { toto(), toto(), toto(), toto(), toto(), toto() },
boxes = {}, boxNames = {}, currentBox = 2, playerName = "KRIS" }
BugContest.start(save)
local caught = toto()
BugContest.switchCaught(save, caught)
local result = BugContest.collectCaughtMon(save, 6, nil, DATA)
eq(result, BugContest.BOXED_MON, "a full party boxes the contest catch")
eq(caught.moves[4].pp, 25, "the boxed contest catch has WATER GUN at 25")
eq(caught.moves[3].pp, 20, "and RAGE at 20")
end
-- engine/items/item_effects.asm:2381
do
local Battle = require("src.battle.gen2.Battle")
local TDATA = {
moves = {
TRANSFORM = { id = "TRANSFORM", name = "TRANSFORM", pp = 10 },
TACKLE = { id = "TACKLE", name = "TACKLE", pp = 35 },
ROCK_THROW = { id = "ROCK_THROW", name = "ROCK THROW", pp = 15 },
},
pokemon = {
DITTO = { id = "DITTO", name = "DITTO", types = { "NORMAL", "NORMAL" } },
GEODUDE = { id = "GEODUDE", name = "GEODUDE", types = { "ROCK", "GROUND" } },
},
items = {},
}
local function transformed(transformPp)
local ditto = { species = "DITTO", hp = 30, maxHp = 30, stats = {},
moves = { { id = "TRANSFORM", pp = transformPp or 10, maxPp = 10 } } }
local geodude = { species = "GEODUDE", hp = 30, maxHp = 30, stats = {},
moves = { { id = "TACKLE", pp = 35, maxPp = 35 },
{ id = "ROCK_THROW", pp = 15, maxPp = 15 } } }
local b = setmetatable({ data = TDATA, events = {} }, Battle)
b.player, b.enemy = ditto, geodude
Battle.MOVE_EFFECTS.EFFECT_TRANSFORM(b, ditto, geodude)
return ditto
end
local d = transformed()
d.moves[1].pp = 0
local r = ItemEffects.usePpItem("ETHER", d, 1, TDATA)
eq(r.used, false, "ETHER on a transformed mon with a full TRANSFORM is refused")
eq(d.moves[1].pp, 0, "ETHER leaves the spent TACKLE copy alone")
eq(d.moves[1].maxPp, 5, "the TACKLE copy keeps its 5 max")
d = transformed()
r = ItemEffects.usePpItem("MAX_ETHER", d, 1, TDATA)
eq(r.used, false, "MAX ETHER is refused while TRANSFORM is full")
eq(d.moves[1].pp, 5, "the full TACKLE copy stays 5/5")
d = transformed()
d.moves[2].pp = 0
r = ItemEffects.usePpItem("ELIXER", d, nil, TDATA)
eq(r.used, false, "ELIXER is refused while the party moves are full")
eq(d.moves[1].pp, 5, "ELIXER leaves TACKLE's copy at 5")
eq(d.moves[2].pp, 0, "ELIXER leaves ROCK THROW's copy at 0")
d = transformed(2)
r = ItemEffects.usePpItem("ETHER", d, 1, TDATA)
eq(r.used, true, "ETHER lands on the party TRANSFORM")
eq(d.volatile.preTransform.moves[1].pp, 10, "TRANSFORM refilled to 10")
eq(d.moves[1].pp, 5, "the TACKLE copy is untouched")
eq(Mon.partyMoves(d)[1].id, "TRANSFORM", "party move list is Ditto's own")
end
do
local move = { id = "RAGE", pp = 20, maxPp = 32 }
Mon.repairMoves({ moves = { move } }, DATA)
eq(move.ppUps, 3, "repairMoves stamps inferred PP Ups")
eq(move.maxPp, 32, "and keeps the 32 max")
local plain = { id = "RAGE", pp = 20, maxPp = 20 }
Mon.repairMoves({ moves = { plain } }, DATA)
eq(plain.ppUps, nil, "no PP Ups leaves ppUps unset")
local growl = { id = "GROWL", pp = 1, maxPp = 54 }
eq(Mon.ppUpsOf(growl, DATA), 2, "GROWL 54 max infers 2 ups at 7 per up")
end
S.finish()