Files
gen1recomp/tests/game3_stitchcoll_move_kinds_test.lua

179 lines
6.0 KiB
Lua

#!/usr/bin/env luajit
-- pokefirered/src/event_object_movement.c:6772 MovementAction_FacePlayer_Step0
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 done()
if failed > 0 then
print("[test] FAILED " .. failed)
os.exit(1)
end
print("[test] all passed")
os.exit(0)
end
require("src.core.GameVersion").set("firered")
local Cache = require("tests.game3_cache")
local cacheRoot = Cache.mount("scripts/events.lua", { native = true })
if not cacheRoot then
print("[skip] game3_stitchcoll_move_kinds_test: " .. tostring(Cache.reason))
done()
end
local Dataset = require("src.core.game3.dataset")
local MapCatalog = require("src.import.gba.map_catalog")
local Collision = require("src.core.game3.collision")
local Objects = require("src.core.game3.objects")
local Player = require("src.core.game3.player")
local Field = require("src.core.game3.field")
local Space = require("src.core.game3.scripting.space")
local MAP = MapCatalog.pretToEngine("PalletTown")
local PX, PY = 12, 20
local game = { data = {} }
Dataset.hydrate(game)
local session = { map = MAP, flags = {}, vars = {}, party = {} }
game.session = session
local def = game.data.maps[MAP]
check(def ~= nil, tostring(MAP) .. " is in the cache")
if not def then done() end
Field._game = game
Field._session = session
Field.running = true
Field.locked = false
Space.activate(nil, MAP, game, nil)
Collision.bindMap(game, MAP, def)
Objects.loadMap(game, MAP, def)
Player.reset(PX, PY, "down")
local eo, lid
for i = 1, 16 do
local o = Objects.find(i)
if o and o.cellX and not Objects.isPlayer(i) then eo, lid = o, i break end
end
check(eo ~= nil, "Pallet Town has an object to drive")
if not eo then done() end
local function run(bytes, maxFrames)
Objects.clearMovements()
eo.moving = false
local finished = false
Objects.applyMovement(lid, bytes, function() finished = true end)
local frames = 0
while not finished and frames < (maxFrames or 300) do
Objects.update(game)
frames = frames + 1
end
return frames, finished
end
local function place(x, y, facing)
Objects.setObjectXY(lid, x, y)
eo.facingLocked = false
eo.inanimate = false
eo.facing = facing
end
print("[test] 1. face_player (0x4A) turns the object toward the player")
place(PX + 1, PY, "down")
local _, finished = run({ 0x4A, 0xFE })
check(finished, "the face_player stream finished")
eq(eo.facing, "left", "an object east of the player faces west")
place(PX - 1, PY, "down")
run({ 0x4A, 0xFE })
eq(eo.facing, "right", "an object west of the player faces east")
place(PX, PY + 1, "left")
run({ 0x4A, 0xFE })
eq(eo.facing, "up", "an object south of the player, same column, faces north")
place(PX, PY - 1, "left")
run({ 0x4A, 0xFE })
eq(eo.facing, "down", "an object north of the player, same column, faces south")
print("[test] 2. GetDirectionToFace is x first, not the largest delta")
-- pokefirered/src/event_object_movement.c:4789 GetDirectionToFace
place(PX + 1, PY - 9, "down")
run({ 0x4A, 0xFE })
eq(eo.facing, "left", "one cell east and nine cells north still faces west")
place(PX - 1, PY + 9, "down")
run({ 0x4A, 0xFE })
eq(eo.facing, "right", "one cell west and nine cells south still faces east")
print("[test] 3. face_away_player (0x4B) takes the opposite direction")
-- pokefirered/src/event_object_movement.c:6784 MovementAction_FaceAwayPlayer_Step0
place(PX + 1, PY, "down")
run({ 0x4B, 0xFE })
eq(eo.facing, "right", "an object east of the player faces further east")
place(PX, PY - 1, "left")
run({ 0x4B, 0xFE })
eq(eo.facing, "up", "an object north of the player faces further north")
print("[test] 4. lock_facing (0x4C / 0x4D) walks the object without turning it")
place(PX + 4, PY, "down")
local startX = eo.cellX
run({ 0x4C, 0x12, 0xFE }, 120)
eq(eo.facing, "down", "the locked object still faces the way it started")
eq(eo.cellX, startX - 1, "and it still moved one cell west")
check(eo.facingLocked == true, "the lock is still on after the stream")
run({ 0x4D, 0x12, 0xFE }, 120)
eq(eo.facingLocked, false, "0x4D unlocks it")
eq(eo.facing, "left", "and the next walk turns it again")
print("[test] 5. disable / restore animation (0x5E / 0x5F)")
place(PX + 4, PY, "down")
Objects.clearMovements()
local fin = false
Objects.applyMovement(lid, { 0x5E, 0x12, 0x5F, 0x12, 0xFE }, function() fin = true end)
local phaseWhileOff, phaseWhileOn = 0, 0
local walks, wasMoving, disabledDuringFirstWalk = 0, false, false
for _ = 1, 200 do
if fin then break end
Objects.update(game)
if eo.moving and not wasMoving then walks = walks + 1 end
wasMoving = eo.moving and true or false
if eo.moving then
local p = Objects.walkPhase(eo)
if walks == 1 then
if eo.inanimate then disabledDuringFirstWalk = true end
if p == 1 then phaseWhileOff = phaseWhileOff + 1 end
elseif walks == 2 and p == 1 then
phaseWhileOn = phaseWhileOn + 1
end
end
end
check(fin, "the animation stream finished")
eq(walks, 2, "the stream walked twice")
check(disabledDuringFirstWalk, "0x5E marks the object inanimate for the first walk")
eq(phaseWhileOff, 0, "the walk frame never advances while the animation is disabled")
check(phaseWhileOn > 0,
"and it advances again once restored (" .. phaseWhileOn .. " frames on phase 1)")
eq(eo.inanimate, false, "0x5F restores the object's own animation")
print("[test] 6. remove_obstacle (0x68 / 0x69) burns its animation frames")
-- pokefirered/src/event_object_movement.c:7135 MovementAction_RockSmashBreak_Step0
place(PX + 4, PY, "down")
local smashFrames = run({ 0x68, 0xFE }, 300)
eq(smashFrames, 64, "rock_smash_break holds the track for its 64 frames")
-- pokefirered/src/event_object_movement.c:7163 MovementAction_CutTree_Step0
local cutFrames = run({ 0x69, 0xFE }, 300)
eq(cutFrames, 56, "cut_tree holds the track for its 56 frames")
done()