mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
247 lines
9.0 KiB
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!")
|