Files
gen1recomp/tests/emerald_contest_engine_test.lua
T
bryanthaboi 690f0cd9a1 TOUCHDOWN
2026-09-27 16:07:31 -04:00

230 lines
9.2 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check, eq = T.check, T.eq
local ROM_PATH = os.getenv("POKEPORT_EMERALD_ROM") or "../pokeemerald/pokeemerald.gba"
local f = io.open(ROM_PATH, "rb")
if not f then
print("emerald_contest_engine_test: skipped (no ROM at " .. ROM_PATH .. ")")
os.exit(0)
end
local data = f:read("*a")
f:close()
if data:sub(0xAD, 0xB0) ~= "BPEE" then
print("emerald_contest_engine_test: skipped (" .. ROM_PATH .. " is not Emerald)")
os.exit(0)
end
local GV = require("src.core.GameVersion")
local Versions = require("src.import.gba.versions")
local Rom = require("src.import.gba.rom")
GV.set("emerald")
Versions.select("emerald")
local sha = GV.VERSIONS.emerald.sha1
local rom = assert(Rom.open({
info = function() return { size = #data, md5 = sha } end,
read = function(_, _, off, len) return data:sub(off + 1, off + len) end,
}, GV.forSha1(sha)))
local files = {}
local cache = {
write = function(_, rel, bytes) files[rel] = bytes; return true end,
read = function(_, rel) return files[rel] end,
}
local CX = require("src.import.gba.rse.contest_extract")
CX.run(rom, cache, { cacheRoot = "gba", game = "emerald" })
local man = assert(load(files["gba/" .. CX.SUB .. "/manifest.lua"], "@m", "t", {}))()
local moves = require("src.import.gba.contest_moves_extract").extract(rom)
local Contest = require("src.core.game3.rse.contest")
local Util = require("src.core.game3.rse.contest_util")
local D = Contest.buildData(moves, man)
local E = Contest.EFFECT
local function lcg(seed)
local Rng = require("src.core.game3.rng")
local v = seed
return function()
v = (Rng.mulU32(v, 1103515245) + 24691) % 4294967296
return math.floor(v / 65536)
end
end
local function moveWith(effect, opts)
opts = opts or {}
for id = 1, moves.count - 1 do
local m = moves.moves[id]
if m.effect == effect and (opts.allowCombo or m.comboStarterId == 0) and (opts.category == nil or m.category == opts.category)
and (opts.notCategory == nil or m.category ~= opts.notCategory) then
local combos = m.comboMoves
if opts.allowCombo or (combos[1] == 0 and combos[2] == 0 and combos[3] == 0 and combos[4] == 0) then return id end
end
end
error("no plain move with contest effect " .. effect)
end
local function newContest(category, list)
local c = Contest.new({ data = D, category = category, rank = 0, rng = lcg(77) })
for i = 0, 3 do
c.mons[i] = { species = 1, aiFlags = 0, moves = { [0] = list[i], 0, 0, 0 }, cool = 0, beauty = 0, cute = 0,
smart = 0, tough = 0, sheen = 0 }
end
c:calculateRound1Points()
c:init()
return c
end
local function play(c, list)
for i = 0, 3 do c.status[i].currMove = c:isTurnDisabled(i) and 0 or list[i] end
local events = {}
for t = 0, 3 do
c.contest.turnNumber = t
local who = c:startTurn()
events[who] = c:completeTurn(who)
end
c:finishRound()
local appeals = {}
for i = 0, 3 do appeals[i] = c.status[i].appeal end
c:resetForNextRound()
c:nextRound()
return appeals, events
end
local function byOrder(c, turn)
for i = 0, 3 do if c.results.turnOrder[i] == turn then return i end end
end
-- pokeemerald/src/contest.c:4425
do
local plain = moveWith(E.HIGHLY_APPEALING)
local cat = moves.moves[plain].category
local c = newContest(cat, { [0] = plain, plain, plain, plain })
local list = { [0] = plain, plain, plain, plain }
local a = play(c, list)
for i = 0, 3 do eq(a[i], 40 + 10, "highly appealing move in its own category appeals 40 + 10 excitement") end
eq(c.contest.applauseLevel, 4, "four same-category appeals fill the meter to 4")
local a2 = play(c, list)
for i = 0, 3 do eq(a2[i], 40 - 20, "repeated move loses 20 and earns no excitement") end
eq(c.contest.applauseLevel, 4, "repeats leave the meter alone")
end
-- pokeemerald/src/contest_effect.c:522
do
local m = moveWith(E.BETTER_IF_FIRST)
local cat = (moves.moves[m].category + 2) % 5
local plain = moveWith(E.HIGHLY_APPEALING, { notCategory = cat, allowCombo = true })
local c = newContest(cat, { [0] = m, m, m, m })
local first = byOrder(c, 0)
local list = { [0] = plain, plain, plain, plain }
list[first] = m
local excite = c:moveExcitement(m)
local a = play(c, list)
local base = D.effects[E.BETTER_IF_FIRST].appeal
eq(a[first], base * 3 + (excite > 0 and 10 or 0), "better-if-first move appealing first triples its base")
end
-- pokeemerald/src/contest_effect.c:93
do
local boom = moveWith(E.GREAT_APPEAL_BUT_NO_MORE_MOVES)
local cat = moves.moves[boom].category
local plain = moveWith(E.HIGHLY_APPEALING, { notCategory = cat, allowCombo = true })
local c = newContest(cat, { [0] = boom, plain, plain, plain })
play(c, { [0] = boom, plain, plain, plain })
eq(c.status[0].noMoreTurns, 1, "great-appeal move sets noMoreTurns at round end")
check(c:isTurnDisabled(0), "exploded contestant cannot appeal again")
local a, events = play(c, { [0] = boom, plain, plain, plain })
eq(a[0], 0, "exploded contestant appeals 0 next round")
eq(events[0][2].text, "gText_MonWasWatchingOthers", "exploded contestant watches the others")
end
-- pokeemerald/src/contest_effect.c:307
do
local jam = moveWith(E.JAMS_OTHERS_BUT_MISS_ONE_TURN)
local cat = moves.moves[jam].category
local plain = moveWith(E.HIGHLY_APPEALING, { notCategory = cat, allowCombo = true })
local c = newContest(cat, { [0] = jam, plain, plain, plain })
local last = byOrder(c, 3)
local list = { [0] = plain, plain, plain, plain }
list[last] = jam
play(c, list)
eq(c.status[last].numTurnsSkipped, 1, "jam-and-rest move skips the next turn")
play(c, list)
eq(c.status[last].numTurnsSkipped, 0, "skip counter runs out after one round")
end
-- pokeemerald/src/contest_effect.c:754
do
local early = moveWith(E.NEXT_APPEAL_EARLIER)
local cat = moves.moves[early].category
local plain = moveWith(E.HIGHLY_APPEALING, { notCategory = cat, allowCombo = true })
local c = newContest(cat, { [0] = plain, plain, plain, plain })
local last = byOrder(c, 3)
local list = { [0] = plain, plain, plain, plain }
list[last] = early
play(c, list)
eq(c.results.turnOrder[last], 0, "move-up-in-line appeals first next round")
end
-- pokeemerald/src/contest_effect.c:964
do
local freeze = moveWith(E.DONT_EXCITE_AUDIENCE)
local cat = moves.moves[freeze].category
local same, sameBase
for _, e in ipairs({ E.HIGHLY_APPEALING, E.USER_LESS_EASILY_STARTLED, E.AVOID_STARTLE, E.AVOID_STARTLE_ONCE,
E.AVOID_STARTLE_SLIGHTLY, E.USER_MORE_EASILY_STARTLED }) do
local ok, id = pcall(moveWith, e, { category = cat, allowCombo = true })
if ok then same, sameBase = id, D.effects[e].appeal; break end
end
local c = newContest(cat, { [0] = same, same, same, same })
local first = byOrder(c, 0)
local list = { [0] = same, same, same, same }
list[first] = freeze
local a, events = play(c, list)
eq(c.contest.applauseLevel, 1, "only the freezer's appeal moved the meter")
for i = 0, 3 do
if i ~= first then
eq(a[i], sameBase, "appeals after a crowd freeze get no excitement bonus")
local ignored = false
for _, e in ipairs(events[i]) do if e.text == "gText_MonsMoveIsIgnored" then ignored = true end end
check(ignored, "crowd ignores appeals after a freeze")
end
end
end
-- pokeemerald/src/contest_util.c:1470
do
local c = { round1 = { [0] = 63, 0, 1, 700 }, round2 = { [0] = 80, -80, 1, -1 } }
eq(Util.preliminaryStars(c, 0, true), 1, "63 condition points = 1 star")
eq(Util.preliminaryStars(c, 1, true), 0, "0 points = 0 stars")
eq(Util.preliminaryStars(c, 2, true), 1, "any condition shows at least one star")
eq(Util.preliminaryStars(c, 3, true), 10, "stars cap at 10")
eq(Util.round2Hearts(c, 0, true), 1, "80 round-2 points = 1 heart")
eq(Util.round2Hearts(c, 1, true), -1, "negative round-2 points = negative hearts")
eq(Util.round2Hearts(c, 3, true), -1, "any negative round-2 points shows one lost heart")
end
-- pokeemerald/src/contest.c:5594
do
local session = {}
Util.clearAllWinners(session, D)
eq(#session.contestWinners, 13, "13 contest winner slots after new game")
eq(session.contestWinners[1].species, man.defaultWinners[0].species, "hall winner 1 is the ROM default")
eq(session.contestWinners[9].species, 0, "museum slots start empty")
eq(Util.countPlayerMuseumPaintings(session), 0, "no museum paintings at new game")
local plain = moveWith(E.HIGHLY_APPEALING)
local c = newContest(0, { [0] = plain, plain, plain, plain })
c.standings = { [0] = 1, 2, 3, 0 }
c.mons[3].species = 25
c.mons[3].personality = 1234
eq(Util.saveContestWinner(session, 0, c), true, "hall winner saved")
eq(session.contestWinners[1].species, 25, "newest hall winner goes to slot 1")
eq(session.contestWinners[2].species, man.defaultWinners[0].species, "older hall winners shift down")
eq(Util.saveContestWinner(session, Util.SAVE_FOR_MUSEUM, c), true, "player winner saved for the museum")
eq(session.contestWinners[9].species, 25, "cool museum painting slot 9")
check(session.contestWinners[9].contestCategory < 3, "museum caption id stays in the cool band")
eq(Util.countPlayerMuseumPaintings(session), 1, "one museum painting")
c.standings = { [0] = 0, 2, 3, 1 }
eq(Util.saveContestWinner(session, Util.SAVE_FOR_MUSEUM, c), false, "an NPC winner is never saved to the museum")
end
T.finish()