Files
gen1recomp/tests/game3_growth_test.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()