mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-05 08:57:49 -04:00
196 lines
5.7 KiB
Lua
196 lines
5.7 KiB
Lua
-- Async movement tracker + FRLG movement-action decode.
|
||
-- activeMoves[localId]; done on step_end 0xFE. LocalId 0xFF = player.
|
||
|
||
local Opcodes = require("src.core.game3.scripting.opcodes")
|
||
|
||
local Movement = {}
|
||
|
||
Movement.LOCALID_PLAYER = Opcodes.LOCALID_PLAYER
|
||
Movement.STEP_END = Opcodes.STEP_END
|
||
|
||
-- FRLG MOVEMENT_ACTION_* names used by curated fallback content / tests.
|
||
Movement.CMD = {
|
||
FACE_DOWN = 0x00,
|
||
FACE_UP = 0x01,
|
||
FACE_LEFT = 0x02,
|
||
FACE_RIGHT = 0x03,
|
||
WALK_SLOWER_DOWN = 0x08,
|
||
WALK_SLOWER_UP = 0x09,
|
||
WALK_SLOWER_LEFT = 0x0A,
|
||
WALK_SLOWER_RIGHT = 0x0B,
|
||
WALK_SLOW_DOWN = 0x0C,
|
||
WALK_SLOW_UP = 0x0D,
|
||
WALK_SLOW_LEFT = 0x0E,
|
||
WALK_SLOW_RIGHT = 0x0F,
|
||
WALK_DOWN = 0x10, -- WALK_NORMAL_*
|
||
WALK_UP = 0x11,
|
||
WALK_LEFT = 0x12,
|
||
WALK_RIGHT = 0x13,
|
||
DELAY_16 = 0x1C,
|
||
WALK_FAST_DOWN = 0x1D,
|
||
WALK_FAST_UP = 0x1E,
|
||
WALK_FAST_LEFT = 0x1F,
|
||
WALK_FAST_RIGHT = 0x20,
|
||
SET_INVISIBLE = 0x60,
|
||
SET_VISIBLE = 0x61,
|
||
EMOTE_EXCLAMATION = 0x62,
|
||
EMOTE_QUESTION = 0x63,
|
||
EMOTE_X = 0x64,
|
||
EMOTE_DOUBLE_EXCLAMATION = 0x65,
|
||
EMOTE_SMILE = 0x66,
|
||
STEP_END = 0xFE,
|
||
}
|
||
|
||
local DIR = { [0] = "down", [1] = "up", [2] = "left", [3] = "right" }
|
||
|
||
-- FRLG MOVEMENT_ACTION_* (include/constants/event_object_movement.h).
|
||
-- Older Emerald-ish 0x08 walk_normal tables are wrong for FireRed.
|
||
function Movement.decodeAction(b)
|
||
b = tonumber(b) or 0
|
||
if b == Movement.STEP_END or b == 0xFF then
|
||
return { kind = "end" }
|
||
end
|
||
if b <= 0x07 then
|
||
return { kind = "turn", dir = DIR[b % 4] }
|
||
end
|
||
-- Walk slower / slow / normal (0x08–0x13): four dirs each.
|
||
if b >= 0x08 and b <= 0x13 then
|
||
return { kind = "step", dir = DIR[(b - 0x08) % 4] }
|
||
end
|
||
-- Delay 1 / 2 / 4 / 8 / 16 frames (scaled up for host step rate).
|
||
if b >= 0x18 and b <= 0x1C then
|
||
local frames = ({ [0x18] = 2, [0x19] = 4, [0x1A] = 8, [0x1B] = 16, [0x1C] = 32 })[b]
|
||
return { kind = "sleep", frames = frames or 8 }
|
||
end
|
||
-- Walk fast / in-place / faster walks → step or turn-in-place.
|
||
if b >= 0x1D and b <= 0x20 then
|
||
return { kind = "step", dir = DIR[b - 0x1D] }
|
||
end
|
||
if b >= 0x21 and b <= 0x30 then
|
||
return { kind = "turn", dir = DIR[(b - 0x21) % 4] }
|
||
end
|
||
if b >= 0x35 and b <= 0x38 then
|
||
return { kind = "step", dir = DIR[b - 0x35] }
|
||
end
|
||
if b >= 0x39 and b <= 0x3C then
|
||
return { kind = "step", dir = DIR[b - 0x39] }
|
||
end
|
||
if b == 0x60 then return { kind = "hide" } end
|
||
if b == 0x61 then return { kind = "show" } end
|
||
-- Emotes: 60 frames animation in pokefirered (sAnimCmd_ExclamationMark1 etc.)
|
||
if b == 0x62 then return { kind = "emote", emoteType = "exclamation", frames = 60 } end
|
||
if b == 0x63 then return { kind = "emote", emoteType = "question", frames = 60 } end
|
||
if b == 0x64 then return { kind = "emote", emoteType = "x", frames = 60 } end
|
||
if b == 0x65 then return { kind = "emote", emoteType = "double_exclamation", frames = 60 } end
|
||
if b == 0x66 then return { kind = "emote", emoteType = "smile", frames = 60 } end
|
||
-- MOVEMENT_ACTION_NURSE_JOY_BOW_DOWN (0x5B): ANIM_NURSE_BOW ≈ 48 frames.
|
||
if b == 0x5B then
|
||
return { kind = "bow", frames = 48 }
|
||
end
|
||
return { kind = "nop" }
|
||
end
|
||
|
||
function Movement.actionsFromBytes(bytes)
|
||
local out = {}
|
||
if type(bytes) ~= "table" then return out end
|
||
for i = 1, #bytes do
|
||
local act = Movement.decodeAction(bytes[i])
|
||
if act.kind == "end" then break end
|
||
if act.kind ~= "nop" then out[#out + 1] = act end
|
||
end
|
||
return out
|
||
end
|
||
|
||
function Movement.start(ctx, localId, bytes, adapters)
|
||
localId = tonumber(localId) or 0
|
||
local stream = bytes
|
||
if type(bytes) == "string" then
|
||
stream = adapters and adapters.lookupMovement and adapters.lookupMovement(bytes)
|
||
end
|
||
if type(stream) ~= "table" then
|
||
stream = { Movement.STEP_END }
|
||
end
|
||
local entry = {
|
||
bytes = stream,
|
||
index = 1,
|
||
done = false,
|
||
hostHandle = nil,
|
||
}
|
||
ctx.activeMoves[localId] = entry
|
||
if adapters and adapters.applyMovement then
|
||
entry.hostHandle = adapters.applyMovement(localId, stream, function()
|
||
entry.done = true
|
||
end)
|
||
else
|
||
-- Instant-complete stub (unit tests / no host).
|
||
for i = 1, #stream do
|
||
if stream[i] == Movement.STEP_END then
|
||
entry.done = true
|
||
break
|
||
end
|
||
end
|
||
if not entry.done and #stream == 0 then entry.done = true end
|
||
end
|
||
return entry
|
||
end
|
||
|
||
function Movement.tick(ctx, adapters)
|
||
for localId, entry in pairs(ctx.activeMoves) do
|
||
if not entry.done and adapters and adapters.pollMovement then
|
||
if adapters.pollMovement(localId, entry) then
|
||
entry.done = true
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
function Movement.isDone(ctx, localId)
|
||
localId = tonumber(localId) or 0
|
||
-- waitmovement 0 waits for all
|
||
if localId == 0 then
|
||
for _, entry in pairs(ctx.activeMoves) do
|
||
if not entry.done then return false end
|
||
end
|
||
return true
|
||
end
|
||
local entry = ctx.activeMoves[localId]
|
||
if not entry then return true end
|
||
return entry.done == true
|
||
end
|
||
|
||
--- Drop finished tracks so the next waitmovement 0 is not confused by leftovers.
|
||
function Movement.pruneDone(ctx)
|
||
if not ctx or not ctx.activeMoves then return end
|
||
local keep = {}
|
||
for lid, entry in pairs(ctx.activeMoves) do
|
||
if entry and not entry.done then
|
||
keep[lid] = entry
|
||
end
|
||
end
|
||
ctx.activeMoves = keep
|
||
end
|
||
|
||
function Movement.makePoll(ctx, localId, adapters)
|
||
return function()
|
||
Movement.tick(ctx, adapters)
|
||
local done = Movement.isDone(ctx, localId)
|
||
if done then Movement.pruneDone(ctx) end
|
||
return done
|
||
end
|
||
end
|
||
|
||
--- Ensure extracted movement ends with 0xFE (include terminator).
|
||
function Movement.ensureTerminated(bytes)
|
||
local out = {}
|
||
for i = 1, #bytes do
|
||
out[i] = bytes[i]
|
||
if bytes[i] == Movement.STEP_END then
|
||
return out
|
||
end
|
||
end
|
||
out[#out + 1] = Movement.STEP_END
|
||
return out
|
||
end
|
||
|
||
return Movement
|