Files
gen1recomp/tests/game3_emote_movement_test.lua

247 lines
9.0 KiB
Lua

-- Test suite for Game 3 Fanfare cues (Oak's Parcel / std:9) and Emote visual movement animations.
local GameVersion = require("src.core.GameVersion")
GameVersion.set("firered")
local Movement = require("src.core.game3.scripting.movement")
local Objects = require("src.core.game3.objects")
local FieldEffects = require("src.core.game3.field_effects")
local Flags = require("src.core.game3.scripting.flags")
local Ctx = require("src.core.game3.scripting.ctx")
local Vm = require("src.core.game3.scripting.vm")
local Adapters = require("src.core.game3.scripting.adapters")
local Audio = require("src.core.game3.audio")
print("=== Game 3 Fanfare & Emote Parity Tests ===")
-- -----------------------------------------------------------------------------
-- 1. Movement bytecode decoding for Emotes (0x62 - 0x66)
-- -----------------------------------------------------------------------------
print("[test] 1. Movement.decodeAction for emote actions")
local a62 = Movement.decodeAction(0x62)
assert(a62.kind == "emote" and a62.emoteType == "exclamation" and a62.frames == 60, "0x62 decodes to exclamation emote with 60 frames")
local a63 = Movement.decodeAction(0x63)
assert(a63.kind == "emote" and a63.emoteType == "question" and a63.frames == 60, "0x63 decodes to question emote with 60 frames")
local a64 = Movement.decodeAction(0x64)
assert(a64.kind == "emote" and a64.emoteType == "x" and a64.frames == 60, "0x64 decodes to x emote with 60 frames")
local a65 = Movement.decodeAction(0x65)
assert(a65.kind == "emote" and a65.emoteType == "double_exclamation" and a65.frames == 60, "0x65 decodes to double exclamation emote with 60 frames")
local a66 = Movement.decodeAction(0x66)
assert(a66.kind == "emote" and a66.emoteType == "smile" and a66.frames == 60, "0x66 decodes to smile emote with 60 frames")
local actions = Movement.actionsFromBytes({ 0x62, 0xFE })
assert(#actions == 1 and actions[1].kind == "emote", "Common_Movement_ExclamationMark contains 1 emote action")
print("[ok] Movement decoding for emotes passed")
-- -----------------------------------------------------------------------------
-- 2. Objects.applyMovement with Emotes & 60-frame blocking (waitmovement)
-- -----------------------------------------------------------------------------
print("[test] 2. Objects.applyMovement with Emote action blocks for 60 frames")
local oakDef = {
localId = 3,
index = 3,
x = 10,
y = 12,
graphicsId = 10,
sprite = "SPRITE_PROF_OAK",
}
Objects.loadMap(nil, "PALLET_LAB", { objects = { oakDef } })
local oak = Objects.find(3)
assert(oak ~= nil, "Oak spawned")
FieldEffects._anims = {}
local doneCalled = false
Objects.applyMovement(3, { 0x62, 0xFE }, function()
doneCalled = true
end)
assert(#FieldEffects._anims == 1, "FieldEffects animation spawned on emote movement")
local anim = FieldEffects._anims[1]
assert(anim.kind == "emote" and anim.targetObj == oak, "Emote targetObj is Oak")
assert(anim.baseFrame == 0, "Exclamation base frame is 0")
assert(Objects.pollMovement(3) == false, "Objects.pollMovement(3) is false while emote is playing")
assert(doneCalled == false, "onDone not called immediately")
-- Step 59 frames -> should still be active
for f = 1, 59 do
Objects.update(nil)
FieldEffects.step()
assert(Objects.pollMovement(3) == false, "Movement still pending at frame " .. f)
assert(doneCalled == false, "onDone not called at frame " .. f)
end
-- Step 60th frame -> movement finishes
Objects.update(nil)
FieldEffects.step()
assert(Objects.pollMovement(3) == true, "Movement reports finished after 60 frames")
assert(doneCalled == true, "onDone invoked after 60 frames")
print("[ok] Emote movement blocks for 60 frames exactly")
-- -----------------------------------------------------------------------------
-- 3. FieldEffects animation progression across emote types
-- -----------------------------------------------------------------------------
print("[test] 3. FieldEffects animation frames and baseFrame offsets")
local testCases = {
{ type = "exclamation", base = 0 },
{ type = "double_exclamation", base = 6 },
{ type = "x", base = 3 },
{ type = "smile", base = 9 },
{ type = "question", base = 12 },
}
for _, tc in ipairs(testCases) do
FieldEffects._anims = {}
local a = FieldEffects.startEmote(oak, tc.type)
assert(a.baseFrame == tc.base, "Base frame matches for " .. tc.type)
assert(a.frame == tc.base, "Initial frame is base frame")
-- Frame 0..3: base
FieldEffects.step() -- timer = 1
assert(a.frame == tc.base, "Frame at t=1 is base")
for _ = 2, 4 do FieldEffects.step() end -- timer = 4
assert(a.frame == tc.base + 1, "Frame at t=4 is base+1")
for _ = 5, 8 do FieldEffects.step() end -- timer = 8
assert(a.frame == tc.base + 2, "Frame at t=8 is base+2")
for _ = 9, 60 do FieldEffects.step() end -- timer = 60
assert(#FieldEffects._anims == 0, "Emote cleaned up after 60 frames for " .. tc.type)
end
print("[ok] All emote types animate through frames and clean up")
-- -----------------------------------------------------------------------------
-- 4. std:9 (STD_RECEIVED_ITEM) fanfare playback and audio cues
-- -----------------------------------------------------------------------------
print("[test] 4. STD_RECEIVED_ITEM (std:9) plays correct fanfare and displays put-away message")
local lastFanfarePlayed = nil
local fanfarePlaying = false
local mockAudio = {
playFanfare = function(id)
lastFanfarePlayed = id
fanfarePlaying = true
return true
end,
isFanfareFinished = function()
return not fanfarePlaying
end,
waitFanfare = function(cb)
if not fanfarePlaying and cb then cb() end
end,
}
local store = Flags.newStore()
local adapters = Adapters.host(nil, nil, nil)
adapters.playSe = function(id, isFanfare)
if isFanfare then
mockAudio.playFanfare(id)
end
end
adapters.isFanfareFinished = function()
return mockAudio.isFanfareFinished()
end
adapters.waitFanfare = function(cb)
mockAudio.waitFanfare(cb)
end
local scripts = {}
-- data/scripts/std_msgbox.inc:30
scripts["std:9"] = {
{ op = "textcolor", color = 3 },
{ op = "compare_var_to_value", var = 0x8002, value = 257 },
{ op = "call_if", cond = 1, target = "EventScript_ReceivedItemFanfare1" },
{ op = "compare_var_to_value", var = 0x8002, value = 318 },
{ op = "call_if", cond = 1, target = "EventScript_ReceivedItemFanfare2" },
{ op = "message", ptr = 0 },
{ op = "waitmessage" },
{ op = "waitfanfare" },
{ op = "return" },
}
scripts.EventScript_ReceivedItemFanfare1 = {
{ op = "playfanfare", [1] = 257 },
{ op = "return" },
}
scripts.EventScript_ReceivedItemFanfare2 = {
{ op = "playfanfare", [1] = 318 },
{ op = "return" },
}
-- Test 4A: Oak's Parcel (MUS_OBTAIN_KEY_ITEM = 318)
scripts.test_parcel = {
{ op = "setorcopyvar", [1] = 0x8000, [2] = 360 }, -- ITEM_OAKS_PARCEL
{ op = "setorcopyvar", [1] = 0x8001, [2] = 1 },
{ op = "setorcopyvar", [1] = 0x8002, [2] = 318 }, -- MUS_OBTAIN_KEY_ITEM
{ op = "loadword", dest = 0, value = "Test_Parcel_Text" },
{ op = "callstd", std = 9 },
{ op = "end" },
}
local vm = Vm.new({
store = store,
scripts = scripts,
adapters = adapters,
})
lastFanfarePlayed = nil
fanfarePlaying = false
vm:start("test_parcel", 1)
assert(lastFanfarePlayed == 318, "Oak's Parcel triggered MUS_OBTAIN_KEY_ITEM (318) fanfare")
print("[ok] Oak's Parcel played key item fanfare (318)")
-- Test 4B: Standard Item (MUS_OBTAIN_ITEM = 258)
scripts.test_potion = {
{ op = "setorcopyvar", [1] = 0x8000, [2] = 13 }, -- ITEM_POTION
{ op = "setorcopyvar", [1] = 0x8001, [2] = 1 },
{ op = "setorcopyvar", [1] = 0x8002, [2] = 258 }, -- MUS_OBTAIN_ITEM
{ op = "loadword", dest = 0, value = "Test_Potion_Text" },
{ op = "callstd", std = 9 },
{ op = "end" },
}
lastFanfarePlayed = nil
fanfarePlaying = false
vm:start("test_potion", 1)
assert(lastFanfarePlayed == nil, "std:9 plays no fanfare for MUS_OBTAIN_ITEM (258)")
print("[ok] std:9 leaves other fanfare ids to the caller")
-- Test 4C: Level Up Fanfare (MUS_LEVEL_UP = 257)
scripts.test_levelup = {
{ op = "setorcopyvar", [1] = 0x8000, [2] = 1 },
{ op = "setorcopyvar", [1] = 0x8001, [2] = 1 },
{ op = "setorcopyvar", [1] = 0x8002, [2] = 257 }, -- MUS_LEVEL_UP
{ op = "loadword", dest = 0, value = "Test_LevelUp_Text" },
{ op = "callstd", std = 9 },
{ op = "end" },
}
lastFanfarePlayed = nil
fanfarePlaying = false
vm:start("test_levelup", 1)
assert(lastFanfarePlayed == 257, "Level up triggered MUS_LEVEL_UP (257) fanfare")
print("[ok] Level up fanfare (257) handled")
-- Test 4D: Dynamic/Variable Fanfare Fallback in playfanfare
scripts.test_var_fanfare = {
{ op = "setorcopyvar", [1] = 0x8000, [2] = 1 },
{ op = "setorcopyvar", [1] = 0x8001, [2] = 1 },
{ op = "setorcopyvar", [1] = 0x8002, [2] = 317 }, -- MUS_DEX_RATING
{ op = "loadword", dest = 0, value = "Test_Rating_Text" },
{ op = "callstd", std = 9 },
{ op = "end" },
}
lastFanfarePlayed = nil
fanfarePlaying = false
vm:start("test_var_fanfare", 1)
assert(lastFanfarePlayed == nil, "std:9 plays no fanfare for MUS_DEX_RATING (317)")
print("[ok] std:9 has no dynamic fanfare branch")
print("All Game 3 Fanfare & Emote Parity Tests passed successfully!")