mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
569 lines
23 KiB
Lua
569 lines
23 KiB
Lua
#!/usr/bin/env luajit
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local failed = 0
|
|
local function check(cond, msg)
|
|
if cond then
|
|
print("[ok] " .. msg)
|
|
else
|
|
failed = failed + 1
|
|
print("[FAIL] " .. msg)
|
|
end
|
|
end
|
|
|
|
local function eq(a, b, msg)
|
|
check(a == b, string.format("%s (%s == %s)", msg, tostring(a), tostring(b)))
|
|
end
|
|
|
|
local function finish()
|
|
if failed > 0 then
|
|
print("[test] FAILED " .. failed)
|
|
os.exit(1)
|
|
end
|
|
print("[test] all passed")
|
|
os.exit(0)
|
|
end
|
|
|
|
local Cache = require("tests.game3_cache")
|
|
local cacheRoot = Cache.mount("pokemon/meta.lua")
|
|
if not cacheRoot then
|
|
print("[skip] game3_growth_test: " .. tostring(Cache.reason))
|
|
finish()
|
|
end
|
|
print("[info] FireRed cache at " .. cacheRoot)
|
|
|
|
local Pokemon = require("src.core.game3.pokemon")
|
|
Pokemon.install(nil)
|
|
local ItemsData = require("src.core.game3.items_data")
|
|
ItemsData.ensureLoaded()
|
|
|
|
local BULBASAUR, PIKACHU, CHANSEY, MAGIKARP, SHEDINJA = 1, 25, 113, 129, 303
|
|
local ITEM_SOOTHE_BELL, ITEM_MACHO_BRACE = 184, 181
|
|
local ITEM_HP_UP, ITEM_PROTEIN, ITEM_CALCIUM = 63, 64, 67
|
|
|
|
local function mon(species, level, overrides)
|
|
local m = {
|
|
species = species,
|
|
speciesId = species,
|
|
level = level or 10,
|
|
personality = 0,
|
|
ivs = { hp = 0, atk = 0, def = 0, spe = 0, spa = 0, spd = 0 },
|
|
evs = { hp = 0, atk = 0, def = 0, spe = 0, spa = 0, spd = 0 },
|
|
pokeball = 4,
|
|
}
|
|
for k, v in pairs(overrides or {}) do m[k] = v end
|
|
Pokemon.applyStats(m)
|
|
return m
|
|
end
|
|
|
|
print("[test] 0. The growth API exists")
|
|
do
|
|
local missing = false
|
|
for _, name in ipairs({
|
|
"evYield", "gainEVs", "evCount", "evsOf", "friendshipOf", "setFriendship",
|
|
"adjustFriendship", "adjustFriendshipOnBattleFaint", "itemFriendship",
|
|
"raiseEvFromItem", "hasPokerus", "hasHadPokerus", "checkPartyPokerus",
|
|
"checkPartyHasHadPokerus", "randomlyGivePartyPokerus", "partySpreadPokerus",
|
|
"updatePartyPokerusTime", "baseFriendship", "currentMapSec",
|
|
"isLeagueTrainerClass",
|
|
}) do
|
|
if type(Pokemon[name]) ~= "function" then
|
|
missing = true
|
|
check(false, "Pokemon." .. name .. " is missing")
|
|
end
|
|
end
|
|
if type(require("src.core.game3.item_use").useVitamin) ~= "function" then
|
|
missing = true
|
|
check(false, "ItemUse.useVitamin is missing")
|
|
end
|
|
if missing then finish() end
|
|
check(true, "every AdjustFriendship / MonGainEVs / Pokerus entry point is present")
|
|
end
|
|
|
|
print("[test] 1. ROM EV yields reach the engine (pokemon.h:219 evYield bitfields)")
|
|
eq(Pokemon.evYield(BULBASAUR).spa, 1, "BULBASAUR yields 1 SP. ATK EV")
|
|
eq(Pokemon.evYield(BULBASAUR).hp, 0, "BULBASAUR yields 0 HP EVs")
|
|
eq(Pokemon.evYield(PIKACHU).spe, 2, "PIKACHU yields 2 SPEED EVs")
|
|
eq(Pokemon.evYield(MAGIKARP).spe, 1, "MAGIKARP yields 1 SPEED EV")
|
|
eq(Pokemon.evYield(CHANSEY).hp, 2, "CHANSEY yields 2 HP EVs")
|
|
|
|
print("[test] 2. MonGainEVs (pokemon.c:5512)")
|
|
do
|
|
local m = mon(BULBASAUR, 10)
|
|
Pokemon.gainEVs(m, PIKACHU)
|
|
eq(m.evs.spe, 2, "beating a PIKACHU gives 2 SPEED EVs")
|
|
eq(Pokemon.evCount(m), 2, "and nothing else")
|
|
Pokemon.gainEVs(m, CHANSEY)
|
|
eq(m.evs.hp, 2, "beating a CHANSEY gives 2 HP EVs")
|
|
eq(Pokemon.evCount(m), 4, "EV count accumulates")
|
|
end
|
|
|
|
print("[test] 3. Macho Brace doubles, Pokerus doubles, both stack (pokemon.c:5537,5578)")
|
|
do
|
|
local m = mon(BULBASAUR, 10, { item = ITEM_MACHO_BRACE })
|
|
Pokemon.gainEVs(m, PIKACHU)
|
|
eq(m.evs.spe, 4, "HOLD_EFFECT_MACHO_BRACE doubles the yield")
|
|
|
|
local p = mon(BULBASAUR, 10, { pokerus = 0x41 })
|
|
check(Pokemon.hasPokerus(p), "pokerus 0x41 is an active infection")
|
|
check(Pokemon.hasHadPokerus(p), "pokerus 0x41 has had pokerus")
|
|
Pokemon.gainEVs(p, PIKACHU)
|
|
eq(p.evs.spe, 4, "Pokerus doubles the yield")
|
|
|
|
local cured = mon(BULBASAUR, 10, { pokerus = 0x40 })
|
|
check(not Pokemon.hasPokerus(cured), "pokerus 0x40 is cured, not infected")
|
|
check(Pokemon.hasHadPokerus(cured), "a cured mon still counts for MonGainEVs")
|
|
Pokemon.gainEVs(cured, PIKACHU)
|
|
eq(cured.evs.spe, 4, "a cured mon keeps the doubling")
|
|
|
|
local both = mon(BULBASAUR, 10, { pokerus = 0x41, item = ITEM_MACHO_BRACE })
|
|
Pokemon.gainEVs(both, PIKACHU)
|
|
eq(both.evs.spe, 8, "Pokerus and Macho Brace stack")
|
|
end
|
|
|
|
print("[test] 4. 255 per stat / 510 total caps (pokemon.c:5581)")
|
|
do
|
|
local m = mon(BULBASAUR, 10, { evs = { hp = 254, atk = 0, def = 0, spe = 0, spa = 0, spd = 0 } })
|
|
Pokemon.gainEVs(m, CHANSEY)
|
|
eq(m.evs.hp, 255, "a 2 HP yield onto 254 clamps at MAX_PER_STAT_EVS")
|
|
|
|
local full = mon(BULBASAUR, 10, { evs = { hp = 255, atk = 255, def = 0, spe = 0, spa = 0, spd = 0 } })
|
|
eq(Pokemon.evCount(full), 510, "510 total already reached")
|
|
Pokemon.gainEVs(full, PIKACHU)
|
|
eq(full.evs.spe, 0, "MAX_TOTAL_EVS stops any further gain")
|
|
|
|
local near = mon(BULBASAUR, 10, { evs = { hp = 255, atk = 254, def = 0, spe = 0, spa = 0, spd = 0 } })
|
|
Pokemon.gainEVs(near, PIKACHU)
|
|
eq(Pokemon.evCount(near), 510, "the last point is trimmed to the 510 total")
|
|
eq(near.evs.spe, 1, "and lands on the stat pret was filling")
|
|
end
|
|
|
|
print("[test] 5. EVs move the stat formula (pokemon.c:2130 CalculateMonStats)")
|
|
do
|
|
local bare = Pokemon.calcStats(PIKACHU, 50, {}, {}, 0)
|
|
local maxed = Pokemon.calcStats(PIKACHU, 50, {}, { spe = 252 }, 0)
|
|
eq(maxed.speed - bare.speed, 31, "252 SPEED EVs at Lv50 are +31 SPEED")
|
|
local hp = Pokemon.calcStats(PIKACHU, 50, {}, { hp = 252 }, 0)
|
|
local hp0 = Pokemon.calcStats(PIKACHU, 50, {}, {}, 0)
|
|
eq(hp.maxHp - hp0.maxHp, 31, "252 HP EVs at Lv50 are +31 HP")
|
|
end
|
|
|
|
print("[test] 6. AdjustFriendship deltas by tier (pokemon.c:1618, :5440)")
|
|
do
|
|
local low = mon(BULBASAUR, 10, { friendship = 70 })
|
|
Pokemon.adjustFriendship(low, Pokemon.FRIENDSHIP_EVENT_GROW_LEVEL)
|
|
eq(low.friendship, 75, "level up under 100 friendship is +5")
|
|
eq(low.happiness, 75, "happiness alias tracks friendship")
|
|
|
|
local mid = mon(BULBASAUR, 10, { friendship = 150 })
|
|
Pokemon.adjustFriendship(mid, Pokemon.FRIENDSHIP_EVENT_GROW_LEVEL)
|
|
eq(mid.friendship, 153, "level up in 100..199 is +3")
|
|
|
|
local high = mon(BULBASAUR, 10, { friendship = 220 })
|
|
Pokemon.adjustFriendship(high, Pokemon.FRIENDSHIP_EVENT_GROW_LEVEL)
|
|
eq(high.friendship, 222, "level up at 200+ is +2")
|
|
|
|
local faintLow = mon(BULBASAUR, 10, { friendship = 70 })
|
|
Pokemon.adjustFriendship(faintLow, Pokemon.FRIENDSHIP_EVENT_FAINT_LARGE)
|
|
eq(faintLow.friendship, 65, "a big-level-gap faint under 200 is -5")
|
|
|
|
local faintHigh = mon(BULBASAUR, 10, { friendship = 220 })
|
|
Pokemon.adjustFriendship(faintHigh, Pokemon.FRIENDSHIP_EVENT_FAINT_LARGE)
|
|
eq(faintHigh.friendship, 210, "a big-level-gap faint at 200+ is -10")
|
|
|
|
local small = mon(BULBASAUR, 10, { friendship = 220 })
|
|
Pokemon.adjustFriendship(small, Pokemon.FRIENDSHIP_EVENT_FAINT_SMALL)
|
|
eq(small.friendship, 219, "an ordinary faint is -1 at every tier")
|
|
|
|
local tmhm = mon(BULBASAUR, 10, { friendship = 220 })
|
|
Pokemon.adjustFriendship(tmhm, Pokemon.FRIENDSHIP_EVENT_LEARN_TMHM)
|
|
eq(tmhm.friendship, 220, "LEARN_TMHM is 0 at the top tier")
|
|
end
|
|
|
|
print("[test] 7. Floor / ceiling and the egg guard (pokemon.c:5458, :5503)")
|
|
do
|
|
local zero = mon(BULBASAUR, 10, { friendship = 2 })
|
|
Pokemon.adjustFriendship(zero, Pokemon.FRIENDSHIP_EVENT_FAINT_SMALL)
|
|
Pokemon.adjustFriendship(zero, Pokemon.FRIENDSHIP_EVENT_FAINT_SMALL)
|
|
Pokemon.adjustFriendship(zero, Pokemon.FRIENDSHIP_EVENT_FAINT_SMALL)
|
|
eq(zero.friendship, 0, "friendship never goes below 0")
|
|
|
|
local cap = mon(BULBASAUR, 10, { friendship = 254 })
|
|
Pokemon.adjustFriendship(cap, Pokemon.FRIENDSHIP_EVENT_GROW_LEVEL)
|
|
eq(cap.friendship, 255, "friendship never goes above MAX_FRIENDSHIP")
|
|
|
|
local egg = mon(BULBASAUR, 5, { friendship = 70, isEgg = true })
|
|
check(not Pokemon.adjustFriendship(egg, Pokemon.FRIENDSHIP_EVENT_GROW_LEVEL),
|
|
"an egg is skipped")
|
|
eq(egg.friendship, 70, "and its friendship is untouched")
|
|
end
|
|
|
|
print("[test] 8. Soothe Bell, Luxury Ball, met location (pokemon.c:5490, :5497)")
|
|
do
|
|
local bell = mon(BULBASAUR, 10, { friendship = 70, item = ITEM_SOOTHE_BELL })
|
|
Pokemon.adjustFriendship(bell, Pokemon.FRIENDSHIP_EVENT_GROW_LEVEL)
|
|
eq(bell.friendship, 77, "HOLD_EFFECT_FRIENDSHIP_UP turns +5 into +7")
|
|
|
|
local bellDown = mon(BULBASAUR, 10, { friendship = 70, item = ITEM_SOOTHE_BELL })
|
|
Pokemon.adjustFriendship(bellDown, Pokemon.FRIENDSHIP_EVENT_FAINT_SMALL)
|
|
eq(bellDown.friendship, 69, "Soothe Bell never softens a loss")
|
|
|
|
local luxury = mon(BULBASAUR, 10, { friendship = 70, pokeball = 11 })
|
|
Pokemon.adjustFriendship(luxury, Pokemon.FRIENDSHIP_EVENT_GROW_LEVEL)
|
|
eq(luxury.friendship, 76, "ITEM_LUXURY_BALL adds 1 on a gain")
|
|
|
|
local luxuryDown = mon(BULBASAUR, 10, { friendship = 70, pokeball = 11 })
|
|
Pokemon.adjustFriendship(luxuryDown, Pokemon.FRIENDSHIP_EVENT_FAINT_SMALL)
|
|
eq(luxuryDown.friendship, 69, "and nothing on a loss")
|
|
|
|
local home = mon(BULBASAUR, 10, { friendship = 70, metLocation = 12 })
|
|
Pokemon.adjustFriendship(home, Pokemon.FRIENDSHIP_EVENT_GROW_LEVEL, { mapSec = 12 })
|
|
eq(home.friendship, 76, "standing in the met location adds 1")
|
|
|
|
local away = mon(BULBASAUR, 10, { friendship = 70, metLocation = 12 })
|
|
Pokemon.adjustFriendship(away, Pokemon.FRIENDSHIP_EVENT_GROW_LEVEL, { mapSec = 13 })
|
|
eq(away.friendship, 75, "and nowhere else")
|
|
|
|
local all = mon(BULBASAUR, 10, {
|
|
friendship = 70, item = ITEM_SOOTHE_BELL, pokeball = 11, metLocation = 12,
|
|
})
|
|
Pokemon.adjustFriendship(all, Pokemon.FRIENDSHIP_EVENT_GROW_LEVEL, { mapSec = 12 })
|
|
eq(all.friendship, 79, "Soothe Bell then Luxury Ball then met location")
|
|
end
|
|
|
|
print("[test] 9. The WALKING coin flip and the LEAGUE_BATTLE gate (pokemon.c:5472, :5478)")
|
|
do
|
|
local Rng = require("src.core.game3.rng")
|
|
Rng.SeedRng(0x1234)
|
|
local m = mon(BULBASAUR, 10, { friendship = 70 })
|
|
local moved = 0
|
|
for _ = 1, 200 do
|
|
local before = m.friendship
|
|
Pokemon.adjustFriendship(m, Pokemon.FRIENDSHIP_EVENT_WALKING)
|
|
if m.friendship ~= before then moved = moved + 1 end
|
|
end
|
|
check(moved > 60 and moved < 140,
|
|
"WALKING lands about half the time (" .. moved .. "/200)")
|
|
|
|
local league = mon(BULBASAUR, 10, { friendship = 70 })
|
|
check(not Pokemon.adjustFriendship(league, Pokemon.FRIENDSHIP_EVENT_LEAGUE_BATTLE),
|
|
"LEAGUE_BATTLE does nothing outside a leader / E4 / champion fight")
|
|
eq(league.friendship, 70, "and leaves friendship alone")
|
|
Pokemon.adjustFriendship(league, Pokemon.FRIENDSHIP_EVENT_LEAGUE_BATTLE,
|
|
{ leagueBattle = true })
|
|
eq(league.friendship, 73, "and is +3 against one")
|
|
end
|
|
|
|
print("[test] 10. AdjustFriendshipOnBattleFaint level gap (battle_util2.c:78)")
|
|
do
|
|
local near = mon(BULBASAUR, 50, { friendship = 220 })
|
|
Pokemon.adjustFriendshipOnBattleFaint(near, 50, 79)
|
|
eq(near.friendship, 219, "a 29-level gap is FAINT_SMALL")
|
|
|
|
local far = mon(BULBASAUR, 50, { friendship = 220 })
|
|
Pokemon.adjustFriendshipOnBattleFaint(far, 50, 80)
|
|
eq(far.friendship, 210, "a 30-level gap is FAINT_LARGE")
|
|
|
|
local weaker = mon(BULBASAUR, 50, { friendship = 220 })
|
|
Pokemon.adjustFriendshipOnBattleFaint(weaker, 50, 5)
|
|
eq(weaker.friendship, 219, "losing to a weaker foe is FAINT_SMALL")
|
|
end
|
|
|
|
print("[test] 11. Pokerus is stubbed out of FRLG (pokemon.c:5612, :5674, :5680)")
|
|
do
|
|
local party = { mon(BULBASAUR, 10), mon(PIKACHU, 10) }
|
|
Pokemon.randomlyGivePartyPokerus(party)
|
|
Pokemon.partySpreadPokerus(party)
|
|
Pokemon.updatePartyPokerusTime(party)
|
|
eq(party[1].pokerus, nil, "RandomlyGivePartyPokerus never infects anything")
|
|
eq(Pokemon.checkPartyPokerus(party, 0), 0, "CheckPartyPokerus is 0")
|
|
party[1].pokerus = 0x41
|
|
party[2].pokerus = 0x40
|
|
eq(Pokemon.checkPartyPokerus(party, 3), 1, "selection 3: only slot 1 is infected")
|
|
eq(Pokemon.checkPartyHasHadPokerus(party, 3), 3, "but both have had it")
|
|
end
|
|
|
|
print("[test] 12. Vitamins: +10 EV to 100, friendship, Shedinja (pokemon.c:4229, party_menu.c:4405)")
|
|
do
|
|
local ItemUse = require("src.core.game3.item_use")
|
|
local session = { party = {}, map = nil }
|
|
local m = mon(PIKACHU, 50, { friendship = 70 })
|
|
session.party[1] = m
|
|
local beforeSpeed = m.speed
|
|
|
|
local ok, kind, text = ItemUse.useVitamin(session, m, ITEM_PROTEIN)
|
|
check(ok, "PROTEIN is usable")
|
|
eq(kind, "vitamin", "and reports as a vitamin")
|
|
eq(m.evs.atk, 10, "PROTEIN adds ITEM6_ADD_EV = 10 ATTACK EVs")
|
|
eq(m.friendship, 75, "and the vitamin friendship change is +5")
|
|
check(type(text) == "string" and text:find("base"), "the message names the base stat")
|
|
|
|
m.evs.spe = 95
|
|
local okS = ItemUse.useVitamin(session, m, 66)
|
|
check(okS, "CARBOS at 95 SPEED EVs still works")
|
|
eq(m.evs.spe, 100, "and stops at EV_ITEM_RAISE_LIMIT")
|
|
local okS2, kindS2 = ItemUse.useVitamin(session, m, 66)
|
|
check(not okS2, "CARBOS at 100 SPEED EVs has no effect")
|
|
eq(kindS2, "no_effect", "and says so")
|
|
check(m.speed > beforeSpeed, "SPEED went up from the EVs")
|
|
|
|
local hpMon = mon(PIKACHU, 50, { friendship = 70 })
|
|
local beforeHp, beforeMax = hpMon.hp, hpMon.maxHp
|
|
ItemUse.useVitamin(session, hpMon, ITEM_HP_UP)
|
|
eq(hpMon.evs.hp, 10, "HP UP adds 10 HP EVs")
|
|
eq(hpMon.maxHp - beforeMax, 1, "max HP rises by 1")
|
|
eq(hpMon.hp - beforeHp, 1, "and CalculateMonStats carries it onto live HP")
|
|
|
|
local shed = mon(SHEDINJA, 50, { friendship = 70 })
|
|
local okSh, kindSh = ItemUse.useVitamin(session, shed, ITEM_HP_UP)
|
|
check(not okSh, "HP UP on SHEDINJA has no effect")
|
|
eq(kindSh, "no_effect", "NotUsingHPEVItemOnShedinja blocks it")
|
|
eq(shed.evs.hp, 0, "and no EV is spent")
|
|
local okSh2 = ItemUse.useVitamin(session, shed, ITEM_CALCIUM)
|
|
check(okSh2, "but SHEDINJA can still take CALCIUM")
|
|
end
|
|
|
|
print("[test] 13. Rare Candy carries the vitamin friendship change (item_effects.h:200)")
|
|
do
|
|
local ItemUse = require("src.core.game3.item_use")
|
|
local m = mon(PIKACHU, 10, { friendship = 70 })
|
|
local ok = ItemUse.useRareCandy({ party = { m } }, m)
|
|
check(ok, "RARE CANDY works")
|
|
eq(m.level, 11, "and levels the mon")
|
|
eq(m.friendship, 75, "and gives ITEM5_FRIENDSHIP_ALL +5")
|
|
end
|
|
|
|
print("[test] 14. Saves written before these fields load with pret's defaults")
|
|
do
|
|
local old = { species = CHANSEY, speciesId = CHANSEY, level = 20, personality = 0 }
|
|
eq(Pokemon.friendshipOf(old), 140,
|
|
"no friendship field reads as the species base friendship")
|
|
eq(Pokemon.evCount(old), 0, "no evs field reads as 0 EVs")
|
|
eq(type(old.evs), "table", "and evsOf materializes the table")
|
|
check(not Pokemon.hasHadPokerus(old), "no pokerus field is no pokerus")
|
|
Pokemon.adjustFriendship(old, Pokemon.FRIENDSHIP_EVENT_GROW_LEVEL)
|
|
eq(old.friendship, 143, "and AdjustFriendship starts from that base")
|
|
end
|
|
|
|
print("[test] 15. The friendship checker NPC reads the real value (field_specials.c:163)")
|
|
do
|
|
local Natives = require("src.core.game3.scripting.natives")
|
|
local Std = require("src.core.game3.scripting.stdscripts")
|
|
eq(Std.SPECIAL.GetLeadMonFriendship, 0xE6, "special id matches specials.inc:241")
|
|
local key = "special:" .. Std.SPECIAL.GetLeadMonFriendship
|
|
local handler = Natives.ALLOW and Natives.ALLOW[key]
|
|
check(type(handler) == "function", "special 0xE6 has a handler")
|
|
if type(handler) == "function" then
|
|
local Runtime = require("src.core.game3.runtime")
|
|
local session = { party = { mon(BULBASAUR, 10, { friendship = 255 }) }, vars = {} }
|
|
local prev = Runtime.session
|
|
Runtime.session = session
|
|
local ctx = { vars = {}, session = session }
|
|
local _, score = handler(ctx, nil)
|
|
eq(score, 6, "friendship 255 scores 6")
|
|
session.party[1].friendship = 150
|
|
local _, s4 = handler(ctx, nil)
|
|
eq(s4, 4, "friendship 150 scores 4")
|
|
session.party[1].friendship = 0
|
|
local _, s0 = handler(ctx, nil)
|
|
eq(s0, 0, "friendship 0 scores 0")
|
|
Runtime.session = prev
|
|
end
|
|
end
|
|
|
|
print("[test] 16. Winning a battle awards EVs and level-up friendship (battle_script_commands.c:3271, :3314)")
|
|
do
|
|
local Experience = require("src.core.game3.battle.experience")
|
|
local winner = mon(BULBASAUR, 5, { friendship = 70 })
|
|
winner.exp = Experience.expForLevel(winner, 5)
|
|
local party = { winner }
|
|
local foe = { mon = mon(CHANSEY, 30), species = CHANSEY, level = 30 }
|
|
local st = {
|
|
wild = true,
|
|
playerParty = party,
|
|
player = { mon = winner, partyIndex = 1, side = "player" },
|
|
session = { party = party },
|
|
}
|
|
local out = Experience.awardFoe(st, foe, { trainer = false, getOpts = function()
|
|
return { luckyEgg = false, traded = false }
|
|
end })
|
|
eq(#out, 1, "the sent-in mon got the award")
|
|
eq(winner.evs.hp, 2, "and CHANSEY's 2 HP EVs")
|
|
check(winner.level > 5, "a Lv5 BULBASAUR levels off a Lv30 CHANSEY")
|
|
check(winner.friendship > 70,
|
|
"and FRIENDSHIP_EVENT_GROW_LEVEL ran (" .. tostring(winner.friendship) .. ")")
|
|
|
|
local writeBack = { species = BULBASAUR, speciesId = BULBASAUR, level = 5 }
|
|
local Party = require("src.core.game3.party")
|
|
Party.applyBattleFields(writeBack, {
|
|
evs = winner.evs, friendship = winner.friendship, pokerus = winner.pokerus,
|
|
})
|
|
eq(writeBack.evs.hp, 2, "the battle writeback carries EVs back to the save")
|
|
eq(writeBack.friendship, winner.friendship, "and friendship")
|
|
eq(writeBack.happiness, winner.friendship, "and keeps the happiness alias in step")
|
|
end
|
|
|
|
print("[test] 17. MonGainEVs runs before the level-up stats (battle_script_commands.c:3271, :3283)")
|
|
do
|
|
local Experience = require("src.core.game3.battle.experience")
|
|
local winner = mon(BULBASAUR, 49, { friendship = 70 })
|
|
winner.ivs.hp = 1
|
|
winner.evs.hp = 2
|
|
Pokemon.applyStats(winner)
|
|
winner.exp = Experience.expForLevel(winner, 50) - 1
|
|
winner.hp = winner.maxHp
|
|
local party = { winner }
|
|
local foe = { mon = mon(CHANSEY, 30), species = CHANSEY, level = 30 }
|
|
local st = {
|
|
wild = true, playerParty = party,
|
|
player = { mon = winner, partyIndex = 1, side = "player" },
|
|
session = { party = party },
|
|
}
|
|
Experience.awardFoe(st, foe, { trainer = false, getOpts = function()
|
|
return { luckyEgg = false, traded = false }
|
|
end })
|
|
eq(winner.level, 50, "the KO levels the mon")
|
|
eq(winner.evs.hp, 4, "and CHANSEY's 2 HP EVs land on top of the 2 it had")
|
|
local want = mon(BULBASAUR, 50, { friendship = 70 })
|
|
want.ivs.hp = 1
|
|
want.evs.hp = 4
|
|
Pokemon.applyStats(want)
|
|
eq(winner.maxHp, want.maxHp, "the level-up stats already include this KO's EVs")
|
|
end
|
|
|
|
print("[test] 18. Bitter medicine (item_effects.h:81-113)")
|
|
do
|
|
local ItemUse = require("src.core.game3.item_use")
|
|
local Bag = require("src.core.game3.bag")
|
|
local function useOn(m, itemId)
|
|
local session = { party = { m }, bag = Bag.new(), vars = {}, flags = {} }
|
|
Bag.add(session.bag, itemId, 1)
|
|
return ItemUse.useField(session, session.bag, itemId, 1)
|
|
end
|
|
|
|
local powder = mon(PIKACHU, 50, { friendship = 120 })
|
|
powder.hp = 1
|
|
local okP = useOn(powder, 30)
|
|
check(okP, "ENERGYPOWDER works on a hurt mon")
|
|
eq(powder.hp, 51, "and restores 50 HP")
|
|
eq(powder.friendship, 115, "and costs 5 friendship at the mid tier")
|
|
|
|
local root = mon(CHANSEY, 50, { friendship = 220 })
|
|
root.hp = 1
|
|
local okR = useOn(root, 31)
|
|
check(okR, "ENERGY ROOT works")
|
|
eq(root.hp, 201, "and restores 200 HP")
|
|
eq(root.friendship, 205, "and costs 15 friendship at the high tier")
|
|
|
|
local heal = mon(PIKACHU, 50, { friendship = 50, status = "PSN" })
|
|
local okH = useOn(heal, 32)
|
|
check(okH, "HEAL POWDER cures a status")
|
|
eq(heal.status, nil, "ITEM3_STATUS_ALL clears it")
|
|
eq(heal.friendship, 45, "and costs 5 friendship at the low tier")
|
|
|
|
local herb = mon(PIKACHU, 50, { friendship = 120 })
|
|
herb.hp = 0
|
|
local okV = useOn(herb, 33)
|
|
check(okV, "REVIVAL HERB works on a fainted mon")
|
|
eq(herb.hp, herb.maxHp, "ITEM6_HEAL_HP_FULL revives it to full")
|
|
eq(herb.friendship, 105, "and costs 15 friendship")
|
|
|
|
local healthy = mon(PIKACHU, 50, { friendship = 120 })
|
|
local okN = useOn(healthy, 30)
|
|
check(not okN, "ENERGYPOWDER on a full-HP mon does nothing")
|
|
eq(healthy.friendship, 120, "and UPDATE_FRIENDSHIP_FROM_ITEM does not run")
|
|
end
|
|
|
|
print("[test] 19. League trainer classes (trainers.h:267, pokemon.c:5480)")
|
|
do
|
|
check(Pokemon.isLeagueTrainerClass(84), "LEADER is a league class")
|
|
check(Pokemon.isLeagueTrainerClass(87), "ELITE FOUR is a league class")
|
|
check(Pokemon.isLeagueTrainerClass(90), "CHAMPION is a league class")
|
|
check(not Pokemon.isLeagueTrainerClass(81), "RIVAL is not")
|
|
check(not Pokemon.isLeagueTrainerClass(91), "CHANNELER is not")
|
|
check(not Pokemon.isLeagueTrainerClass(nil), "and no class at all is not")
|
|
local names
|
|
local f = io.open(cacheRoot .. "/trainers.lua", "rb")
|
|
if f then
|
|
local src = f:read("*a")
|
|
f:close()
|
|
local chunk = load(src, "@trainers.lua", "t", {})
|
|
local okP, pack = pcall(chunk)
|
|
names = okP and type(pack) == "table" and pack.classNames or nil
|
|
end
|
|
check(names ~= nil, "the extracted trainer class table loaded")
|
|
if names then
|
|
eq(names[84], "LEADER", "the extracted class table agrees on 84")
|
|
eq(names[87], "ELITE FOUR", "and on 87")
|
|
eq(names[90], "CHAMPION", "and on 90")
|
|
end
|
|
local m = mon(BULBASAUR, 10, { friendship = 120 })
|
|
Pokemon.adjustFriendship(m, Pokemon.FRIENDSHIP_EVENT_LEAGUE_BATTLE, {})
|
|
eq(m.friendship, 120, "a non-league battle changes nothing")
|
|
Pokemon.adjustFriendship(m, Pokemon.FRIENDSHIP_EVENT_LEAGUE_BATTLE, { leagueBattle = true })
|
|
eq(m.friendship, 122, "a league battle gives +2 at the mid tier")
|
|
end
|
|
|
|
print("[test] 20. A player mon fainting in battle loses friendship (battle_script_commands.c:2878)")
|
|
do
|
|
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 player = mon(BULBASAUR, 5, { friendship = 100 })
|
|
player.hp = 1
|
|
player.moves, player.pp = { 33 }, { 35 }
|
|
local foeMon = mon(CHANSEY, 40)
|
|
foeMon.moves, foeMon.pp = { 33 }, { 35 }
|
|
foeMon.attack = 200
|
|
local st = State.new({ wild = true, playerParty = { player }, foeMon = foeMon })
|
|
st.session = { party = { player } }
|
|
local ad = Adapter.new(st)
|
|
st.rng = function(a, b)
|
|
if a == 1 and b == 100 then return 1 end
|
|
if type(a) == "number" and type(b) == "number" then return b end
|
|
return a or 0
|
|
end
|
|
ad.rng = function(_, lo, hi)
|
|
if lo == 1 and hi == 100 then return 1 end
|
|
if lo and hi then return hi end
|
|
return lo or 0
|
|
end
|
|
Engine.resolveMove(st.enemy, st.player, 33, 1, ad, st, {})
|
|
check(st.player.fainted == true, "the player mon fainted")
|
|
eq(Pokemon.friendshipOf(st.player.mon), 95,
|
|
"a 35 level gap is FRIENDSHIP_EVENT_FAINT_LARGE, -5 at the mid tier")
|
|
end
|
|
|
|
print("[test] 21. Daisy's massage (field_specials.c:2075)")
|
|
do
|
|
local Natives = require("src.core.game3.scripting.natives")
|
|
local Std = require("src.core.game3.scripting.stdscripts")
|
|
eq(Std.SPECIAL.DaisyMassageServices, 0x197, "special id matches specials.inc:418")
|
|
local handler = Natives.ALLOW and Natives.ALLOW["special:" .. Std.SPECIAL.DaisyMassageServices]
|
|
check(type(handler) == "function", "special 0x197 has a handler")
|
|
if type(handler) == "function" then
|
|
local Runtime = require("src.core.game3.runtime")
|
|
local Flags = require("src.core.game3.scripting.flags")
|
|
local m = mon(BULBASAUR, 10, { friendship = 220 })
|
|
local session = { party = { m }, vars = {} }
|
|
local prev = Runtime.session
|
|
Runtime.session = session
|
|
local ctx = { vars = {}, session = session }
|
|
Flags.setVar(nil, ctx, 0x8004, 0)
|
|
Flags.setVar(nil, ctx, 0x4025, 500)
|
|
handler(ctx, nil)
|
|
eq(m.friendship, 223, "FRIENDSHIP_EVENT_MASSAGE is +3 at every tier")
|
|
eq(tonumber(Flags.getVar(nil, ctx, 0x4025)), 0, "and the cooldown counter resets")
|
|
Runtime.session = prev
|
|
end
|
|
local StepEvents = require("src.core.game3.step_events")
|
|
local session = { party = {}, vars = { [0x4025] = 498 }, flags = {} }
|
|
StepEvents.onStepTaken(session, nil)
|
|
StepEvents.onStepTaken(session, nil)
|
|
StepEvents.onStepTaken(session, nil)
|
|
eq(session.vars[0x4025], 500, "RunMassageCooldownStepCounter stops at 500")
|
|
end
|
|
|
|
finish()
|