mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 05:32:07 -04:00
228 lines
9.7 KiB
Lua
228 lines
9.7 KiB
Lua
#!/usr/bin/env luajit
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
require("tests.fixture_data.game3_items").install()
|
|
|
|
local failed = 0
|
|
local function check(cond, msg)
|
|
if cond then
|
|
print("[ok] " .. msg)
|
|
else
|
|
failed = failed + 1
|
|
print("[FAIL] " .. msg)
|
|
end
|
|
end
|
|
|
|
local Flags = require("src.core.game3.scripting.flags")
|
|
local store = Flags.newStore()
|
|
package.loaded["src.core.game3.scripting.space"] = { store = store }
|
|
|
|
local Rng = require("src.core.game3.rng")
|
|
local Data = require("src.core.game3.vs_seeker_data")
|
|
local VsSeeker = require("src.core.game3.vs_seeker")
|
|
|
|
local session = { flags = {}, vars = {} }
|
|
local s = VsSeeker.state(session)
|
|
|
|
local function fightFlag(id, on) Flags.setFlag(store, nil, Flags.trainerFlagId(id), on ~= false) end
|
|
local function sysFlag(id, on) Flags.setFlag(store, nil, id, on ~= false) end
|
|
local function resetFlags()
|
|
store.flags = {}
|
|
end
|
|
|
|
local BEN, BEN_2, BEN_3 = 89, 101, 498
|
|
local MEGAN, MEGAN_2 = 130, 648
|
|
local TWINS, TWINS_2 = 484, 533
|
|
|
|
print("=== table ===")
|
|
check(#Data.REMATCHES == 221, "sRematches has 221 rows")
|
|
check(Data.REMATCHES[Data.byBase[TWINS]][4] == TWINS_2, "twins row carries TWINS_ELI_ANNE_2 in column 3")
|
|
check(Data.byAny[TWINS_2] == Data.byBase[TWINS], "rematch id maps back to its base row")
|
|
|
|
print("=== GetNextAvailableRematchTrainer ===")
|
|
local calvin = Data.REMATCHES[2][1]
|
|
check(Data.REMATCHES[2][2] == calvin, "row 2 is a {X, X} row")
|
|
fightFlag(calvin)
|
|
check(VsSeeker.nextAvailable(calvin, store) == 1, "{X, X} row returns 1")
|
|
check(VsSeeker.rematchTrainerId(calvin, store) == calvin, "{X, X} rematch without the VS SEEKER flag steps down to column 0")
|
|
sysFlag(0x292)
|
|
check(VsSeeker.rematchTrainerId(calvin, store) == calvin, "{X, X} rematch with VS SEEKER flag uses the same id")
|
|
|
|
fightFlag(BEN)
|
|
check(VsSeeker.nextAvailable(BEN, store) == 1, "BEN row returns 1 after the base fight")
|
|
fightFlag(BEN_2)
|
|
check(VsSeeker.nextAvailable(BEN, store) == 3, "BEN row skips SKIP and returns 3 after BEN_2")
|
|
check(VsSeeker.rematchTrainerId(BEN, store) == BEN_2, "FUCHSIA flag unset steps tier 3 down to BEN_2")
|
|
sysFlag(0x897)
|
|
check(VsSeeker.rematchTrainerId(BEN, store) == BEN_3, "FUCHSIA flag set gives BEN_3")
|
|
check(VsSeeker.nextAvailable(9999, store) == 0, "trainer outside the table returns 0")
|
|
|
|
fightFlag(MEGAN)
|
|
sysFlag(0x896)
|
|
check(VsSeeker.rematchTrainerId(MEGAN, store) == MEGAN_2, "MEGAN tier 2 with CELADON flag is MEGAN_2")
|
|
sysFlag(0x896, false)
|
|
check(VsSeeker.rematchTrainerId(MEGAN, store) == MEGAN, "MEGAN tier 2 without CELADON steps down to the base party")
|
|
|
|
print("=== ShouldTryRematchBattle / IsTrainerReadyForRematch ===")
|
|
resetFlags()
|
|
check(not VsSeeker.shouldTryRematchBattle(BEN, 5, store, session), "unfought, not ready: no rematch branch")
|
|
fightFlag(BEN)
|
|
check(VsSeeker.shouldTryRematchBattle(BEN, 5, store, session), "base flag set: rematch branch taken")
|
|
check(not VsSeeker.shouldTryRematchBattle(9999, 5, store, session), "trainer outside the table never branches")
|
|
check(not VsSeeker.shouldTryRematchBattle(BEN_2, 5, store, session), "only column-0 ids branch")
|
|
s.rematches = { [5] = 1 }
|
|
check(VsSeeker.isTrainerReadyForRematch(BEN, 5, session), "ready when lastTalked has a rematch state")
|
|
check(not VsSeeker.isTrainerReadyForRematch(BEN, 6, session), "not ready for another localId")
|
|
check(VsSeeker.isTrainerReadyForRematch(BEN_2, 5, session), "rematch ids hit the table in any column")
|
|
VsSeeker.clearRematchStateOfLastTalked(5, BEN_2, store, session)
|
|
check(VsSeeker.getRematch(s, 5) == 0, "ClearRematchStateOfLastTalked clears the state")
|
|
check(Flags.getFlag(store, nil, Flags.trainerFlagId(BEN_2)), "and sets the rematch trainer flag")
|
|
|
|
print("=== GetVsSeekerResponseInArea ===")
|
|
local function firstRoll(target)
|
|
for seed = 0, 65535 do
|
|
Rng.SeedRng(seed)
|
|
if Rng.Random() % 100 == target then return seed end
|
|
end
|
|
end
|
|
|
|
resetFlags()
|
|
fightFlag(BEN)
|
|
sysFlag(0x292)
|
|
local single = { { localId = 3, trainerIdx = BEN, x = 2, y = 2, graphicsId = 18, sane = true } }
|
|
local seed29, seed30 = firstRoll(29), firstRoll(30)
|
|
s.rematches = {}
|
|
Rng.SeedRng(seed29)
|
|
local code = VsSeeker.computeResponse(single, 0, 0, s, store)
|
|
check(code == VsSeeker.RESPONSE_NO_RESPONSE and VsSeeker.getRematch(s, 3) == 0, "rval 29 refuses")
|
|
check(not Flags.getFlag(store, nil, 0x801), "refusal does not set the charging flag")
|
|
s.rematches = {}
|
|
s.charging = 7
|
|
Rng.SeedRng(seed30)
|
|
local actions, responders
|
|
code, actions, responders = VsSeeker.computeResponse(single, 0, 0, s, store)
|
|
check(code == VsSeeker.RESPONSE_FOUND_REMATCHES and VsSeeker.getRematch(s, 3) == 1, "rval 30 accepts and stamps j")
|
|
check(Flags.getFlag(store, nil, 0x801) and s.charging == 0, "accept sets the charging flag and resets the counter")
|
|
check(actions[1].rematch and responders[1].behavior == 0x4E, "youngster responds with RAISE_HAND_AND_JUMP")
|
|
|
|
local far = { { localId = 3, trainerIdx = BEN, x = 8, y = 0, graphicsId = 18, sane = true },
|
|
{ localId = 4, trainerIdx = BEN, x = 0, y = 6, graphicsId = 18, sane = true } }
|
|
Rng.SeedRng(seed30)
|
|
code = VsSeeker.computeResponse(far, 0, 0, s, store)
|
|
check(code == VsSeeker.RESPONSE_NO_RESPONSE and Rng._value == seed30, "outside the 7x5 box: skipped, no RNG")
|
|
|
|
resetFlags()
|
|
local unfought = { { localId = 3, trainerIdx = BEN, x = 1, y = 1, graphicsId = 18, sane = true } }
|
|
Rng.SeedRng(1)
|
|
code, actions = VsSeeker.computeResponse(unfought, 0, 0, s, store)
|
|
check(code == VsSeeker.RESPONSE_UNFOUGHT_TRAINERS and actions[1].movement[1] == 0x62, "unfought trainer gets ! and no RNG")
|
|
check(Rng._value == 1, "unfought branch does not consume RNG")
|
|
|
|
resetFlags()
|
|
fightFlag(TWINS)
|
|
sysFlag(0x292)
|
|
sysFlag(0x896)
|
|
sysFlag(0x897)
|
|
local twins = {
|
|
{ localId = 10, trainerIdx = TWINS, x = 0, y = -2, graphicsId = 17, sane = true },
|
|
{ localId = 11, trainerIdx = TWINS, x = 1, y = -2, graphicsId = 17, sane = true },
|
|
}
|
|
local mirrored, sawYes, sawNo = true, false, false
|
|
for seed = 1, 40 do
|
|
s.rematches = {}
|
|
Rng.SeedRng(seed)
|
|
local c, acts = VsSeeker.computeResponse(twins, 0, 0, s, store)
|
|
local a1, a2 = acts[1].rematch == true, acts[2].rematch == true
|
|
if a1 ~= a2 then mirrored = false end
|
|
if a1 then
|
|
sawYes = true
|
|
if VsSeeker.getRematch(s, 10) ~= 3 or VsSeeker.getRematch(s, 11) ~= 3 then mirrored = false end
|
|
if c ~= VsSeeker.RESPONSE_FOUND_REMATCHES then mirrored = false end
|
|
else
|
|
sawNo = true
|
|
end
|
|
Rng.SeedRng(seed)
|
|
Rng.Random()
|
|
Rng.Random()
|
|
local expect = Rng._value
|
|
Rng.SeedRng(seed)
|
|
s.rematches = {}
|
|
VsSeeker.computeResponse(twins, 0, 0, s, store)
|
|
if Rng._value ~= expect then mirrored = false end
|
|
end
|
|
check(mirrored, "twin pair mirrors the first twin's answer and consumes one Random() each")
|
|
check(sawYes and sawNo, "seed sweep covered both answers")
|
|
|
|
print("=== CanUseVsSeeker ===")
|
|
s.steps = 99
|
|
check(VsSeeker.canUse(twins, 0, 0, s, store) == VsSeeker.NOT_CHARGED, "99 steps is not charged")
|
|
s.steps = 100
|
|
check(VsSeeker.canUse(twins, 0, 0, s, store) == VsSeeker.CAN_USE, "charged with a rematchable twin in range")
|
|
check(VsSeeker.canUse(twins, 20, 0, s, store) == VsSeeker.NO_ONE_IN_RANGE, "no one in range")
|
|
|
|
print("=== step counter ===")
|
|
local Bag = require("src.core.game3.bag")
|
|
session.bag = Bag.new()
|
|
-- pokefirered/include/constants/items.h:434 ITEM_VS_SEEKER
|
|
Bag.add(session.bag, 362, 1)
|
|
s.steps = 99
|
|
s.charging = 98
|
|
s.rematches = { [10] = 3 }
|
|
sysFlag(0x801)
|
|
check(not VsSeeker.onStep(session, store) and s.steps == 100 and s.charging == 99, "step 99 charges both bytes")
|
|
check(VsSeeker.onStep(session, store), "charging reaches 100 and fires the charging-done event")
|
|
check(not Flags.getFlag(store, nil, 0x801) and s.charging == 0, "charging flag and counter cleared")
|
|
check(next(s.rematches) == nil, "all rematch states cleared")
|
|
check(s.steps == 100, "in-bag charge caps at 100")
|
|
|
|
print("=== object movement ===")
|
|
check(VsSeeker.runningBehavior(22) == 0x4E and VsSeeker.runningBehavior(43) == 0x4F
|
|
and VsSeeker.runningBehavior(5) == 0x4D, "GetRunningBehaviorFromGraphicsId")
|
|
|
|
local live, template = {}, {}
|
|
local stubObjects = {
|
|
_defs = {
|
|
{ localId = 10, trainerType = 1, trainerId = TWINS, movementType = 8, graphicsId = 17 },
|
|
{ localId = 11, trainerType = 1, trainerId = TWINS, movementType = 8, graphicsId = 17 },
|
|
{ localId = 12, trainerType = 0, trainerId = TWINS, movementType = 8, graphicsId = 17 },
|
|
},
|
|
_byId = {
|
|
[10] = { localId = 10, facing = "left", visible = true },
|
|
[11] = { localId = 11, facing = "down", visible = true },
|
|
},
|
|
_order = { 10, 11 },
|
|
setTrainerMovementType = function(lid, mt)
|
|
if type(lid) == "table" then lid = lid.localId end
|
|
live[lid] = mt
|
|
end,
|
|
overrideTemplateMovementType = function(lid, mt) template[lid] = mt end,
|
|
templateMovementType = function(lid) return template[lid] or 8 end,
|
|
}
|
|
|
|
s.rematches = { [10] = 3, [11] = 3 }
|
|
Rng.SeedRng(77)
|
|
VsSeeker.clearRematchStateByTrainerId(TWINS_2, 10, store, session, stubObjects)
|
|
Rng.SeedRng(77)
|
|
Rng.Random()
|
|
Rng.Random()
|
|
local afterTwo = Rng._value
|
|
Rng.SeedRng(77)
|
|
VsSeeker.clearRematchStateByTrainerId(TWINS_2, 10, store, session, stubObjects)
|
|
check(Rng._value == afterTwo, "ClearRematchStateByTrainerId consumes one Random() per matching trainer template")
|
|
check(VsSeeker.getRematch(s, 10) == 0 and VsSeeker.getRematch(s, 11) == 0, "both twins cleared by the rematch id")
|
|
check(live[10] == 9 and live[11] == 8, "selected twin keeps its facing type, the other gets FACE_DOWN")
|
|
check(template[10] == 9 and template[11] == 8, "templates take each twin's facing type")
|
|
|
|
live, template = {}, { [10] = 0x4E, [11] = 0x4E }
|
|
Rng.SeedRng(5)
|
|
VsSeeker.resetObjectMovementAfterChargeComplete(stubObjects)
|
|
local face = { [7] = true, [8] = true, [9] = true, [10] = true }
|
|
check(face[live[10]] and face[live[11]] and face[template[10]] and face[template[11]],
|
|
"charge-complete reset replaces raise-hand types with random face types")
|
|
|
|
if failed == 0 then
|
|
print("ALL TESTS PASSED")
|
|
else
|
|
print(string.format("TESTS FAILED: %d", failed))
|
|
os.exit(1)
|
|
end
|