mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-27 22:45:34 -04:00
1336 lines
41 KiB
Lua
1336 lines
41 KiB
Lua
-- Game3 EventObject instances (localId-keyed).
|
|
-- Owns idle AI + applymovement tracks; optional host NPC mirror for adapters.
|
|
-- Player localId 0xFF delegates to game3.player. Talk is Field.interact.
|
|
|
|
local Movement = require("src.core.game3.scripting.movement")
|
|
local Opcodes = require("src.core.game3.scripting.opcodes")
|
|
local GfxIds = require("src.core.game3.scripting.gfx_ids")
|
|
local ModRuntime = require("src.mods.Runtime")
|
|
local VirtualObjects = require("src.core.game3.virtual_objects")
|
|
|
|
local Objects = {}
|
|
|
|
Objects.PLAYER_LOCAL_ID = Opcodes.LOCALID_PLAYER or 0xFF
|
|
|
|
local CELL = 16
|
|
local WALK_FRAMES = 16
|
|
-- pokefirered/src/event_object_movement.c:5333 StartRunningAnim
|
|
local RUN_FRAMES = 8
|
|
-- pokefirered/src/event_object_movement.c:9029 UpdateRunSlowAnim
|
|
local RUN_SLOW_FRAMES = 11
|
|
local DELTA = {
|
|
up = { 0, -1 },
|
|
down = { 0, 1 },
|
|
left = { -1, 0 },
|
|
right = { 1, 0 },
|
|
}
|
|
|
|
Objects._byId = {} -- [localId] = EventObject
|
|
Objects._order = {} -- stable draw/list order
|
|
Objects._tracks = {} -- [localId] = movement track
|
|
Objects._mapId = nil
|
|
Objects._defs = nil -- original mapDef.objects (for addobject)
|
|
Objects._bounds = nil
|
|
-- Permanent template overrides from setobjectxyperm / setobjectmovementtype.
|
|
-- Survives loadMap within a session (pret objectEventTemplates).
|
|
Objects._perm = {} -- [mapId] = { [localId] = { x=, y=, movementType= } }
|
|
Objects._templateMt = {}
|
|
Objects._logged = false
|
|
|
|
local function log(msg)
|
|
print("[game3/objects] " .. tostring(msg))
|
|
end
|
|
|
|
-- pokefirered/src/overworld.c
|
|
local function layoutBounds(mapDef)
|
|
local L = mapDef and mapDef.midLayout
|
|
local w = L and L.width or 0
|
|
local h = L and L.height or 0
|
|
if w < 1 or h < 1 then return nil end
|
|
return { w = w, h = h }
|
|
end
|
|
|
|
Objects.layoutBounds = layoutBounds
|
|
|
|
local function offMap(bounds, eo)
|
|
if not bounds then return false end
|
|
local x = eo.cellX or 0
|
|
local y = eo.cellY or 0
|
|
return x < 0 or y < 0 or x >= bounds.w or y >= bounds.h
|
|
end
|
|
|
|
Objects.offMap = offMap
|
|
|
|
local function Collision()
|
|
return package.loaded["src.core.game3.collision"]
|
|
or require("src.core.game3.collision")
|
|
end
|
|
|
|
local function Player()
|
|
return package.loaded["src.core.game3.player"]
|
|
or require("src.core.game3.player")
|
|
end
|
|
|
|
local function Space()
|
|
return package.loaded["src.core.game3.scripting.space"]
|
|
end
|
|
|
|
function Objects.isPlayer(localId)
|
|
local id = tonumber(localId)
|
|
return id == Objects.PLAYER_LOCAL_ID or id == 0xFF or id == 0x800F
|
|
end
|
|
|
|
local function facingFromDef(def)
|
|
local r = tostring(def.facing or def.range or "DOWN"):lower()
|
|
if r == "up" or r == "down" or r == "left" or r == "right" then
|
|
return r
|
|
end
|
|
if r == "any_dir" or r == "up_down" or r == "left_right" then
|
|
return "down"
|
|
end
|
|
return "down"
|
|
end
|
|
|
|
local function dirsForRange(range)
|
|
local r = tostring(range or "DOWN"):upper()
|
|
if r == "ANY_DIR" then return { "up", "down", "left", "right" } end
|
|
if r == "UP_DOWN" then return { "up", "down" } end
|
|
if r == "LEFT_RIGHT" then return { "left", "right" } end
|
|
if r == "UP" then return { "up" } end
|
|
if r == "DOWN" then return { "down" } end
|
|
if r == "LEFT" then return { "left" } end
|
|
if r == "RIGHT" then return { "right" } end
|
|
return { "down", "up", "left", "right" }
|
|
end
|
|
|
|
local function objectVisible(def)
|
|
local SpaceMod = Space()
|
|
if SpaceMod and SpaceMod.objectVisible then
|
|
return SpaceMod.objectVisible(def)
|
|
end
|
|
local flag = def.flag
|
|
if not flag or flag == 0 or flag == 0xFFFF or flag == 65535 then
|
|
return true
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function newEventObject(def)
|
|
-- Shallow-copy template so setobjectxy / removeobject cannot poison the
|
|
-- shared events.lua / mapDef.objects tables for the rest of the session.
|
|
local src = def or {}
|
|
def = {}
|
|
for k, v in pairs(src) do
|
|
def[k] = v
|
|
end
|
|
local lid = tonumber(def.localId or def.index) or 0
|
|
local x = tonumber(def.x) or 0
|
|
local y = tonumber(def.y) or 0
|
|
local mt = tonumber(def.movementType) or 0
|
|
local hostMv = GfxIds.hostMovement
|
|
and GfxIds.hostMovement(mt, def.radius and def.radius.x, def.radius and def.radius.y)
|
|
or nil
|
|
local movement = def.movement
|
|
or (hostMv and hostMv.movement)
|
|
or "STAY"
|
|
local range = def.range or (hostMv and hostMv.range) or "DOWN"
|
|
local radius = def.radius or (hostMv and hostMv.radius)
|
|
local sprite = def.sprite
|
|
local resolvedGfx = def.graphicsId or def.graphics
|
|
do
|
|
local okS, Space = pcall(require, "src.core.game3.scripting.space")
|
|
if okS and Space and Space.resolveObjectGraphicsId then
|
|
local gid = Space.resolveObjectGraphicsId(def)
|
|
if gid then resolvedGfx = gid end
|
|
end
|
|
end
|
|
if not sprite and resolvedGfx then
|
|
sprite = GfxIds.spriteFor(resolvedGfx)
|
|
end
|
|
local Coll = Collision()
|
|
local elev = (def.elevation and def.elevation ~= 0 and def.elevation)
|
|
or (Coll and Coll.elevationAt and Coll.elevationAt(x, y)) or 0
|
|
return {
|
|
localId = lid,
|
|
def = def,
|
|
cellX = x,
|
|
cellY = y,
|
|
px = x * CELL,
|
|
py = y * CELL,
|
|
homeX = x,
|
|
homeY = y,
|
|
facing = facingFromDef(def),
|
|
sprite = sprite or "SPRITE_YOUNGSTER",
|
|
graphicsId = resolvedGfx,
|
|
elevation = elev,
|
|
movementType = mt,
|
|
movement = movement,
|
|
range = range,
|
|
radius = radius or { x = 1, y = 1 },
|
|
sight = tonumber(def.sight or def.trainerRange) or 0,
|
|
trainerType = tonumber(def.trainerType) or 0,
|
|
scriptKey = def.scriptKey,
|
|
flag = def.flag,
|
|
visible = objectVisible(def),
|
|
hidden = not objectVisible(def),
|
|
frozen = false,
|
|
passable = def.passable and true or false,
|
|
moving = false,
|
|
progress = 0,
|
|
stepFrames = WALK_FRAMES,
|
|
targetX = x,
|
|
targetY = y,
|
|
stepFlip = false,
|
|
animClock = 0,
|
|
idleTimer = 30 + (lid * 17) % 60,
|
|
scriptBusy = false,
|
|
}
|
|
end
|
|
|
|
function Objects.clear()
|
|
Objects._byId = {}
|
|
Objects._order = {}
|
|
Objects._tracks = {}
|
|
Objects._mapId = nil
|
|
Objects._defs = nil
|
|
Objects._bounds = nil
|
|
-- src/event_object_movement.c:9225
|
|
VirtualObjects.clear()
|
|
end
|
|
|
|
-- pokefirered/src/overworld.c:405
|
|
function Objects.reset()
|
|
Objects.clear()
|
|
Objects._perm = {}
|
|
Objects._templateMt = {}
|
|
Objects._logged = false
|
|
VirtualObjects.reset()
|
|
end
|
|
|
|
function Objects.hasMap()
|
|
return Objects._mapId ~= nil
|
|
end
|
|
|
|
--- Re-resolve OBJ_EVENT_GFX_VAR_* after ON_TRANSITION sets VAR_OBJ_GFX_ID_*.
|
|
function Objects.refreshGraphics()
|
|
local okS, Space = pcall(require, "src.core.game3.scripting.space")
|
|
if not (okS and Space and Space.resolveObjectGraphicsId) then return 0 end
|
|
local n = 0
|
|
for _, eo in pairs(Objects._byId or {}) do
|
|
if eo and eo.def then
|
|
local gid = Space.resolveObjectGraphicsId(eo.def)
|
|
if gid and gid ~= eo.graphicsId then
|
|
eo.graphicsId = gid
|
|
eo.sprite = GfxIds.spriteFor(gid) or eo.sprite
|
|
n = n + 1
|
|
elseif gid then
|
|
eo.graphicsId = gid
|
|
end
|
|
end
|
|
end
|
|
local okFv, FieldView = pcall(require, "src.core.game3.field_view")
|
|
if okFv and FieldView then FieldView._nativeDirty = true end
|
|
return n
|
|
end
|
|
|
|
function Objects.hasActiveTracks()
|
|
for _, tr in pairs(Objects._tracks) do
|
|
if tr and not tr.done then return true end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function rememberPerm(mapId, localId, fields)
|
|
mapId = mapId or Objects._mapId
|
|
localId = tonumber(localId) or 0
|
|
if not mapId or localId <= 0 then return end
|
|
local bucket = Objects._perm[mapId]
|
|
if not bucket then
|
|
bucket = {}
|
|
Objects._perm[mapId] = bucket
|
|
end
|
|
local row = bucket[localId]
|
|
if not row then
|
|
row = {}
|
|
bucket[localId] = row
|
|
end
|
|
for k, v in pairs(fields) do
|
|
row[k] = v
|
|
end
|
|
end
|
|
|
|
Objects.rememberPerm = rememberPerm
|
|
|
|
local function applyPerm(eo, mapId)
|
|
local bucket = Objects._perm[mapId]
|
|
local row = bucket and bucket[eo.localId]
|
|
if not row then return end
|
|
if row.x ~= nil then
|
|
eo.cellX = row.x
|
|
eo.homeX = row.x
|
|
eo.px = row.x * CELL
|
|
eo.targetX = row.x
|
|
if eo.def then eo.def.x = row.x end
|
|
end
|
|
if row.y ~= nil then
|
|
eo.cellY = row.y
|
|
eo.homeY = row.y
|
|
eo.py = row.y * CELL
|
|
eo.targetY = row.y
|
|
if eo.def then eo.def.y = row.y end
|
|
end
|
|
if row.movementType ~= nil then
|
|
eo.movementType = row.movementType
|
|
local hostMv = GfxIds.hostMovement and GfxIds.hostMovement(row.movementType)
|
|
if hostMv then
|
|
eo.movement = hostMv.movement
|
|
eo.range = hostMv.range
|
|
if hostMv.radius then eo.radius = hostMv.radius end
|
|
end
|
|
local face = ({ [7] = "up", [8] = "down", [9] = "left", [10] = "right" })[row.movementType]
|
|
if face then eo.facing = face end
|
|
if eo.def then eo.def.movementType = row.movementType end
|
|
end
|
|
if row.facing ~= nil then
|
|
eo.facing = row.facing
|
|
if eo.def then eo.def.facing = row.facing end
|
|
end
|
|
end
|
|
|
|
local function resolveContextualMapObjects(mapId)
|
|
if mapId == "FR_OAKS_LAB" or mapId == "PalletTown_ProfessorOaksLab" then
|
|
local Sp = Space()
|
|
local store = Sp and Sp.store
|
|
local Flags = package.loaded["src.core.game3.scripting.flags"]
|
|
local Runtime = package.loaded["src.core.game3.runtime"]
|
|
local session = Runtime and Runtime.getSession and Runtime.getSession()
|
|
|
|
local oakScene = 0
|
|
if Flags and Flags.getVar and store then
|
|
oakScene = Flags.getVar(store, nil, 0x4055)
|
|
elseif session and session.vars then
|
|
oakScene = tonumber(session.vars[0x4055]) or 0
|
|
end
|
|
|
|
local starter = 0
|
|
if Flags and Flags.getVar and store then
|
|
starter = Flags.getVar(store, nil, 0x4031)
|
|
elseif session and session.vars then
|
|
starter = tonumber(session.vars[0x4031]) or 0
|
|
end
|
|
|
|
if starter == 0 and session and session.party and session.party[1] then
|
|
local sp = session.party[1].species
|
|
if sp == 4 then starter = 2 -- Charmander -> Rival has Squirtle
|
|
elseif sp == 7 then starter = 1 -- Squirtle -> Rival has Bulbasaur
|
|
elseif sp == 1 then starter = 0 -- Bulbasaur -> Rival has Charmander
|
|
end
|
|
end
|
|
|
|
if oakScene == 1 or oakScene == 2 then
|
|
rememberPerm(mapId, 4, { x = 6, y = 3, movementType = 8, facing = "down" })
|
|
rememberPerm(mapId, 8, { x = 5, y = 4, movementType = 7, facing = "up" })
|
|
elseif oakScene == 3 then
|
|
rememberPerm(mapId, 4, { x = 6, y = 3, movementType = 8, facing = "down" })
|
|
local rx, ry = 10, 5
|
|
if starter == 1 then
|
|
rx, ry = 8, 5
|
|
elseif starter == 2 then
|
|
rx, ry = 9, 5
|
|
end
|
|
rememberPerm(mapId, 8, { x = rx, y = ry, movementType = 7, facing = "up" })
|
|
elseif (oakScene >= 4 and oakScene <= 6) or oakScene >= 8 then
|
|
rememberPerm(mapId, 4, { x = 6, y = 3, movementType = 8, facing = "down" })
|
|
end
|
|
end
|
|
if mapId == "FR_PALLET_TOWN" or mapId == "PalletTown" then
|
|
local Sp = Space()
|
|
local store = Sp and Sp.store
|
|
local Flags = package.loaded["src.core.game3.scripting.flags"]
|
|
local Runtime = package.loaded["src.core.game3.runtime"]
|
|
local session = Runtime and Runtime.getSession and Runtime.getSession()
|
|
|
|
local signLadyScene = 0
|
|
if Flags and Flags.getVar and store then
|
|
signLadyScene = Flags.getVar(store, nil, 0x4070)
|
|
elseif session and session.vars then
|
|
signLadyScene = tonumber(session.vars[0x4070]) or 0
|
|
end
|
|
|
|
local hasStarter = false
|
|
if Flags and Flags.getFlag and store then
|
|
hasStarter = Flags.getFlag(store, nil, 0x291) or Flags.getFlag(store, nil, 0x828)
|
|
end
|
|
if not hasStarter and session and session.flags then
|
|
hasStarter = session.flags[0x291] == true or session.flags["0x291"] == true
|
|
or session.flags[657] == true or session.flags["657"] == true
|
|
or session.flags[0x828] == true or session.flags["0x828"] == true
|
|
or session.flags[2088] == true or session.flags["2088"] == true
|
|
end
|
|
if not hasStarter and session and session.party and #session.party > 0 then
|
|
hasStarter = true
|
|
end
|
|
|
|
if signLadyScene == 0 then
|
|
if hasStarter then
|
|
rememberPerm(mapId, 1, { x = 12, y = 2, movementType = 8, facing = "down" })
|
|
if Flags and store then
|
|
Flags.setVar(store, nil, 0x4002, 1) -- VAR_TEMP_2 = 1 (SIGN_LADY_READY)
|
|
Flags.setFlag(store, nil, 0x291, true)
|
|
Flags.setFlag(store, nil, 0x83E, false) -- FLAG_OPENED_START_MENU = false until scene completes
|
|
end
|
|
if session then
|
|
if session.vars then session.vars[0x4002] = 1 end
|
|
if session.flags then
|
|
session.flags[0x291] = true
|
|
session.flags[657] = true
|
|
session.flags[0x83E] = nil
|
|
session.flags[2110] = nil
|
|
end
|
|
end
|
|
else
|
|
rememberPerm(mapId, 1, { x = 5, y = 15, movementType = 7, facing = "up" })
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Spawn EventObjects from mapDef.objects (extract / content).
|
|
function Objects.loadMap(game, mapId, mapDef)
|
|
local sameMap = Objects._mapId == mapId
|
|
-- Never wipe in-flight applymovement on a same-map rebind (host setMap echo).
|
|
if not sameMap then
|
|
Objects._tracks = {}
|
|
Objects._templateMt = {}
|
|
-- pokefirered/src/overworld.c:405
|
|
Objects._perm = {}
|
|
end
|
|
Objects._byId = {}
|
|
Objects._order = {}
|
|
Objects._mapId = mapId
|
|
-- Prefer ROM event bundle (source of truth). mapDef.objects is the same
|
|
-- table reference after attachEventsToMaps and may have been mutated.
|
|
local defs = nil
|
|
local Sp = Space()
|
|
local ev = Sp and Sp.bundle and Sp.bundle.events and Sp.bundle.events[mapId]
|
|
if ev then
|
|
defs = ev.objects or ev.objectEvents
|
|
end
|
|
if type(defs) ~= "table" then
|
|
defs = mapDef and mapDef.objects
|
|
end
|
|
Objects._defs = defs or {}
|
|
Objects._bounds = layoutBounds(mapDef)
|
|
if mapId == "FR_PLAYERS_HOUSE_1F" then
|
|
for _, def in ipairs(Objects._defs) do
|
|
if tonumber(def.localId or def.index) == 1 then
|
|
def.x, def.y = 8, 4
|
|
end
|
|
end
|
|
end
|
|
resolveContextualMapObjects(mapId)
|
|
local announce = ModRuntime.wants("world.npc_spawned")
|
|
for _, def in ipairs(Objects._defs) do
|
|
local eo = newEventObject(def)
|
|
if eo.localId > 0 then
|
|
applyPerm(eo, mapId)
|
|
local tmt = Objects._templateMt[eo.localId]
|
|
if tmt then
|
|
Objects.setTrainerMovementType(eo, tmt)
|
|
local face = ({ [7] = "up", [8] = "down", [9] = "left", [10] = "right" })[tmt]
|
|
if face then eo.facing = face end
|
|
end
|
|
Objects._byId[eo.localId] = eo
|
|
Objects._order[#Objects._order + 1] = eo.localId
|
|
if announce then
|
|
ModRuntime.emit("world.npc_spawned", { mapId = mapId, npcId = eo.localId, runtime = eo })
|
|
end
|
|
end
|
|
end
|
|
if not Objects._logged then
|
|
log(string.format("spawned %d objects on %s", #Objects._order, tostring(mapId)))
|
|
Objects._logged = true
|
|
else
|
|
log(string.format("reloaded %d objects on %s%s", #Objects._order, tostring(mapId),
|
|
sameMap and " (kept tracks)" or ""))
|
|
end
|
|
return #Objects._order
|
|
end
|
|
|
|
function Objects.find(localId)
|
|
localId = tonumber(localId) or 0
|
|
if Objects.isPlayer(localId) then
|
|
return Player()
|
|
end
|
|
return Objects._byId[localId]
|
|
end
|
|
|
|
-- src/event_object_movement.c:2089-2116
|
|
local function on_named_map(mapGroup, mapNum)
|
|
if mapGroup == nil or mapNum == nil then return true end
|
|
local ok, MapCatalog = pcall(require, "src.import.gba.map_catalog")
|
|
if not (ok and type(MapCatalog) == "table" and MapCatalog.mapIdFor) then return true end
|
|
local engineId = MapCatalog.mapIdFor(tonumber(mapGroup), tonumber(mapNum))
|
|
if engineId == nil then return false end
|
|
return engineId == Objects._mapId
|
|
end
|
|
|
|
-- src/event_object_movement.c:2089-2101, scrcmd.c:1130
|
|
function Objects.setSubpriority(localId, mapGroup, mapNum, subpriority)
|
|
local eo = Objects._byId[tonumber(localId) or -1]
|
|
if not eo then return false end
|
|
if not on_named_map(mapGroup, mapNum) then return false end
|
|
eo.fixedPriority = true
|
|
eo.subpriority = tonumber(subpriority) or 0
|
|
eo.fixedClass = nil
|
|
return true
|
|
end
|
|
|
|
-- src/event_object_movement.c:2104-2116
|
|
function Objects.resetSubpriority(localId, mapGroup, mapNum)
|
|
local eo = Objects._byId[tonumber(localId) or -1]
|
|
if not eo then return false end
|
|
if not on_named_map(mapGroup, mapNum) then return false end
|
|
eo.fixedPriority = nil
|
|
eo.subpriority = nil
|
|
eo.fixedClass = nil
|
|
return true
|
|
end
|
|
|
|
function Objects.listActive(_mod, _game, _mapId)
|
|
local ids = {}
|
|
for _, lid in ipairs(Objects._order) do
|
|
local eo = Objects._byId[lid]
|
|
if eo and eo.visible and not eo.hidden then
|
|
ids[#ids + 1] = lid
|
|
end
|
|
end
|
|
return ids
|
|
end
|
|
|
|
local VIRT_DIR_FACE = { [1] = "down", [2] = "up", [3] = "left", [4] = "right" }
|
|
|
|
function Objects.forDraw()
|
|
local list = {}
|
|
for _, lid in ipairs(Objects._order) do
|
|
local eo = Objects._byId[lid]
|
|
if eo and eo.visible and not eo.hidden
|
|
and not offMap(Objects._bounds, eo) then
|
|
list[#list + 1] = eo
|
|
end
|
|
end
|
|
-- src/event_object_movement.c:1719
|
|
if VirtualObjects.count() > 0 then
|
|
for _, vo in ipairs(VirtualObjects.list()) do
|
|
local gid = tonumber(vo.graphicsId) or 0
|
|
local vrec = {
|
|
virtualId = vo.id,
|
|
cellX = tonumber(vo.x) or 0,
|
|
cellY = tonumber(vo.y) or 0,
|
|
elevation = tonumber(vo.elevation) or 3,
|
|
facing = VIRT_DIR_FACE[tonumber(vo.direction)] or "down",
|
|
sprite = GfxIds.spriteFor(gid),
|
|
graphicsId = gid,
|
|
visible = true,
|
|
hidden = false,
|
|
}
|
|
if not offMap(Objects._bounds, vrec) then list[#list + 1] = vrec end
|
|
end
|
|
end
|
|
return list
|
|
end
|
|
|
|
--- First visible EventObject standing on (tx, ty), or nil if moving onto it.
|
|
function Objects.at(tx, ty)
|
|
tx, ty = tonumber(tx), tonumber(ty)
|
|
if not tx or not ty then return nil end
|
|
for _, lid in ipairs(Objects._order) do
|
|
local eo = Objects._byId[lid]
|
|
if eo and eo.visible and not eo.hidden then
|
|
if eo.cellX == tx and eo.cellY == ty then
|
|
if eo.moving then return nil end
|
|
return eo
|
|
end
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
--- True if any non-passable EO occupies (tx,ty) or is stepping onto it.
|
|
function Objects.blocks(tx, ty, exceptLocalId)
|
|
exceptLocalId = tonumber(exceptLocalId)
|
|
for _, lid in ipairs(Objects._order) do
|
|
if lid ~= exceptLocalId then
|
|
local eo = Objects._byId[lid]
|
|
if eo and eo.visible and not eo.hidden and not eo.passable then
|
|
if eo.cellX == tx and eo.cellY == ty then return true end
|
|
if eo.moving and eo.targetX == tx and eo.targetY == ty then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- pokefirered/src/event_object_movement.c:4899
|
|
function Objects.playerBlocks(tx, ty)
|
|
local P = Player()
|
|
if not P then return false end
|
|
if P.cellX == tx and P.cellY == ty then return true end
|
|
if P.moving and P.targetX == tx and P.targetY == ty then return true end
|
|
return false
|
|
end
|
|
|
|
local function walkPhaseOf(eo)
|
|
if not eo.moving then return 0 end
|
|
-- pokefirered/src/event_object_movement.c:7040 MovementAction_DisableAnimation_Step0
|
|
if eo.inanimate then return 0 end
|
|
local frames = eo.stepFrames or WALK_FRAMES
|
|
local p = eo.animClock % frames
|
|
local mid = math.floor(frames / 2)
|
|
return (p >= math.floor(frames / 4) and p < mid + math.floor(frames / 4)) and 1 or 0
|
|
end
|
|
|
|
function Objects.walkPhase(eo)
|
|
return walkPhaseOf(eo)
|
|
end
|
|
|
|
local function beginStep(eo, tx, ty)
|
|
eo.moving = true
|
|
eo.progress = 0
|
|
eo.targetX = tx
|
|
eo.targetY = ty
|
|
eo.stepFrames = WALK_FRAMES
|
|
eo.animClock = 0
|
|
end
|
|
|
|
local function finishStep(eo, game)
|
|
eo.cellX = eo.targetX
|
|
eo.cellY = eo.targetY
|
|
eo.px = eo.cellX * CELL
|
|
eo.py = eo.cellY * CELL
|
|
eo.moving = false
|
|
eo.progress = 0
|
|
eo.stepFlip = not eo.stepFlip
|
|
if eo.def then
|
|
eo.def.x, eo.def.y = eo.cellX, eo.cellY
|
|
end
|
|
local Coll = Collision()
|
|
local curElev = Coll and Coll.elevationAt and Coll.elevationAt(eo.cellX, eo.cellY)
|
|
if curElev and curElev ~= 0 and curElev ~= 15 then
|
|
eo.elevation = curElev
|
|
end
|
|
if eo.sight and eo.sight > 0 and not eo.scriptBusy and not eo.frozen then
|
|
local okTs, TrainerSight = pcall(require, "src.core.game3.trainer_sight")
|
|
if okTs and TrainerSight and TrainerSight.check then
|
|
TrainerSight.check(game, eo)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function tickMotion(eo, game)
|
|
if not eo.moving then return false end
|
|
eo.progress = eo.progress + 1
|
|
eo.animClock = eo.animClock + 1
|
|
local frames = eo.stepFrames or WALK_FRAMES
|
|
local dx = eo.targetX - eo.cellX
|
|
local dy = eo.targetY - eo.cellY
|
|
local t = math.min(1, eo.progress / frames)
|
|
eo.px = eo.cellX * CELL + dx * CELL * t
|
|
eo.py = eo.cellY * CELL + dy * CELL * t
|
|
if eo.progress >= frames then
|
|
finishStep(eo, game)
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
--- Scripted one-cell step (no collision — FRLG applymovement forces).
|
|
function Objects.scriptStep(eo, dir, run, slow)
|
|
if not eo then return false end
|
|
local P = Player()
|
|
if eo == P then
|
|
return P.scriptStep and P.scriptStep(dir, run, slow)
|
|
end
|
|
if eo.moving then return false end
|
|
local d = DELTA[dir]
|
|
if not d then return false end
|
|
-- pokefirered/src/event_object_movement.c:6796 MovementAction_LockFacingDirection_Step0
|
|
if not eo.facingLocked then eo.facing = dir end
|
|
beginStep(eo, eo.cellX + d[1], eo.cellY + d[2])
|
|
-- pokefirered/src/event_object_movement.c:5333 StartRunningAnim
|
|
if run then eo.stepFrames = slow and RUN_SLOW_FRAMES or RUN_FRAMES end
|
|
eo.frozen = true
|
|
eo.scriptBusy = true
|
|
return true
|
|
end
|
|
|
|
function Objects.scriptJump(eo, dir, distance)
|
|
if not eo then return false end
|
|
local P = Player()
|
|
if eo == P then
|
|
return P.scriptJump and P.scriptJump(dir, distance)
|
|
end
|
|
if eo.moving then return false end
|
|
distance = distance or 1
|
|
local d = DELTA[dir]
|
|
if not d then return false end
|
|
eo.facing = dir
|
|
beginStep(eo, eo.cellX + d[1] * distance, eo.cellY + d[2] * distance)
|
|
eo.frozen = true
|
|
eo.scriptBusy = true
|
|
return true
|
|
end
|
|
|
|
function Objects.scriptFace(eo, dir)
|
|
if not eo then return end
|
|
local P = Player()
|
|
if eo == P then
|
|
if P.scriptFace then P.scriptFace(dir) else P.facing = dir end
|
|
return
|
|
end
|
|
-- pokefirered/src/event_object_movement.c:2501 SetObjectEventDirection
|
|
if eo.facingLocked then return end
|
|
eo.facing = dir
|
|
end
|
|
|
|
-- pokefirered/src/event_object_movement.c:5208 GetOppositeDirection
|
|
local OPPOSITE_DIR = { down = "up", up = "down", left = "right", right = "left" }
|
|
|
|
-- pokefirered/src/event_object_movement.c:4789 GetDirectionToFace
|
|
local function directionToFace(x1, y1, x2, y2)
|
|
if x1 > x2 then return "left" end
|
|
if x1 < x2 then return "right" end
|
|
if y1 > y2 then return "up" end
|
|
return "down"
|
|
end
|
|
|
|
local function advanceTrack(lid, tr, game)
|
|
if tr.done then return end
|
|
local eo = Objects.find(lid)
|
|
if tr.sleep and tr.sleep > 0 then
|
|
tr.sleep = tr.sleep - 1
|
|
if tr.sleep > 0 then return end
|
|
end
|
|
-- Wait until current step finishes.
|
|
if eo and eo.moving then return end
|
|
if eo == Player() and Player().moving then return end
|
|
|
|
local act = tr.actions[tr.i]
|
|
if not act then
|
|
tr.done = true
|
|
if eo and eo ~= Player() then
|
|
eo.scriptBusy = false
|
|
end
|
|
if tr.onDone then
|
|
local cb = tr.onDone
|
|
tr.onDone = nil
|
|
cb()
|
|
end
|
|
return
|
|
end
|
|
tr.i = tr.i + 1
|
|
if type(act) == "string" then
|
|
local sdir = act:match("^walk_(.*)$") or act:match("^step_(.*)$")
|
|
if sdir then
|
|
act = { kind = "step", dir = sdir }
|
|
else
|
|
local tdir = act:match("^turn_(.*)$") or act:match("^face_(.*)$")
|
|
if tdir then
|
|
act = { kind = "turn", dir = tdir }
|
|
end
|
|
end
|
|
end
|
|
if type(act) == "table" then
|
|
if act.kind == "step" then
|
|
if eo == Player() then
|
|
if Player().scriptStep then Player().scriptStep(act.dir, act.run, act.slow) end
|
|
elseif eo then
|
|
Objects.scriptStep(eo, act.dir, act.run, act.slow)
|
|
end
|
|
elseif act.kind == "jump" then
|
|
if eo == Player() then
|
|
if Player().scriptJump then
|
|
Player().scriptJump(act.dir, act.distance or 1)
|
|
elseif Player().scriptStep then
|
|
for _ = 1, (act.distance or 1) do
|
|
Player().scriptStep(act.dir)
|
|
end
|
|
end
|
|
elseif eo then
|
|
if Objects.scriptJump then
|
|
Objects.scriptJump(eo, act.dir, act.distance or 1)
|
|
else
|
|
Objects.scriptStep(eo, act.dir)
|
|
end
|
|
end
|
|
elseif act.kind == "turn" then
|
|
Objects.scriptFace(eo, act.dir)
|
|
elseif act.kind == "face_player" then
|
|
-- pokefirered/src/event_object_movement.c:6772 MovementAction_FacePlayer_Step0
|
|
if eo and eo ~= Player() then
|
|
local dir = directionToFace(eo.cellX, eo.cellY, Player().cellX, Player().cellY)
|
|
if act.away then dir = OPPOSITE_DIR[dir] end
|
|
Objects.scriptFace(eo, dir)
|
|
end
|
|
elseif act.kind == "lock_facing" then
|
|
-- pokefirered/src/event_object_movement.c:6796 MovementAction_LockFacingDirection_Step0
|
|
if eo and eo ~= Player() then eo.facingLocked = act.locked and true or false end
|
|
elseif act.kind == "animate" then
|
|
-- pokefirered/src/event_object_movement.c:7040 MovementAction_DisableAnimation_Step0
|
|
if eo and eo ~= Player() then eo.inanimate = act.inanimate and true or false end
|
|
elseif act.kind == "remove_obstacle" then
|
|
-- pokefirered/src/event_object_movement.c:7135 MovementAction_RockSmashBreak_Step0
|
|
tr.sleep = act.frames or 32
|
|
elseif act.kind == "face_original" then
|
|
if eo and eo ~= Player() and eo.def then
|
|
local origFace = facingFromDef(eo.def)
|
|
Objects.scriptFace(eo, origFace)
|
|
end
|
|
elseif act.kind == "bow" then
|
|
if eo and eo ~= Player() then
|
|
eo.bowFrames = act.frames or 48
|
|
eo.facing = "down"
|
|
end
|
|
tr.sleep = act.frames or 48
|
|
elseif act.kind == "emote" then
|
|
if eo then
|
|
local okFx, FieldEffects = pcall(require, "src.core.game3.field_effects")
|
|
if okFx and FieldEffects then
|
|
if FieldEffects.startEmote then
|
|
FieldEffects.startEmote(eo, act.emoteType or "exclamation")
|
|
elseif FieldEffects.startExclamation then
|
|
FieldEffects.startExclamation(eo)
|
|
end
|
|
end
|
|
end
|
|
tr.sleep = act.frames or 60
|
|
elseif act.kind == "sleep" then
|
|
tr.sleep = act.frames or 1
|
|
elseif act.kind == "hide" then
|
|
if eo and eo ~= Player() then
|
|
eo.hidden = true
|
|
eo.visible = false
|
|
end
|
|
elseif act.kind == "show" then
|
|
if eo and eo ~= Player() then
|
|
eo.hidden = false
|
|
eo.visible = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function Objects.applyMovement(localId, stream, onDone)
|
|
local lid = tonumber(localId) or localId
|
|
local actions = Movement.actionsFromBytes(stream)
|
|
local eo = Objects.find(lid)
|
|
if eo and eo ~= Player() then
|
|
eo.frozen = true
|
|
eo.scriptBusy = true
|
|
end
|
|
Objects._tracks[lid] = {
|
|
actions = actions,
|
|
i = 1,
|
|
sleep = 0,
|
|
done = false,
|
|
onDone = onDone,
|
|
}
|
|
advanceTrack(lid, Objects._tracks[lid], nil)
|
|
end
|
|
|
|
function Objects.startTrack(localId, actions, onDone)
|
|
local lid = tonumber(localId) or localId
|
|
local eo = Objects.find(lid)
|
|
if eo and eo ~= Player() then
|
|
eo.frozen = true
|
|
eo.scriptBusy = true
|
|
end
|
|
if not actions or #actions == 0 then
|
|
if eo and eo ~= Player() then
|
|
eo.scriptBusy = false
|
|
end
|
|
if onDone then onDone() end
|
|
return
|
|
end
|
|
Objects._tracks[lid] = {
|
|
actions = actions,
|
|
i = 1,
|
|
sleep = 0,
|
|
done = false,
|
|
onDone = onDone,
|
|
}
|
|
advanceTrack(lid, Objects._tracks[lid], nil)
|
|
end
|
|
|
|
function Objects.pollMovement(localId)
|
|
-- Tracks advance in Objects.update. Missing track ≠ done: returning true
|
|
-- here after loadMap wiped tracks was skipping Bill's walk_up / MeetCelio.
|
|
local lid = tonumber(localId) or localId
|
|
if lid == 0 then
|
|
local any = false
|
|
for _, tr in pairs(Objects._tracks) do
|
|
any = true
|
|
if not tr.done then return false end
|
|
end
|
|
return any
|
|
end
|
|
local tr = Objects._tracks[lid]
|
|
if not tr then return false end
|
|
return tr.done == true
|
|
end
|
|
|
|
function Objects.clearMovements()
|
|
Objects._tracks = {}
|
|
end
|
|
|
|
local function sine(i)
|
|
local v = 256 * math.sin((i % 256) * math.pi / 128)
|
|
return v >= 0 and math.floor(v + 0.5) or -math.floor(-v + 0.5)
|
|
end
|
|
|
|
-- src/event_object_movement.c:7812
|
|
local function raiseHandTick(eo)
|
|
local rh = eo.raiseHandState
|
|
if not rh then
|
|
rh = { mode = 0, angle = 0, hops = 0, timer = 0, swing = 0 }
|
|
eo.raiseHandState = rh
|
|
eo.raiseHand = true
|
|
end
|
|
local mt = tonumber(eo.movementType) or 0
|
|
if mt == 0x4F then
|
|
rh.swing = (rh.swing + 4) % 256
|
|
eo.raiseX = math.floor(sine(rh.swing) / 128)
|
|
return
|
|
end
|
|
if mt ~= 0x4E then return end
|
|
if rh.mode == 0 then
|
|
rh.angle = rh.angle + 10
|
|
if rh.angle > 127 then
|
|
rh.angle = 0
|
|
rh.hops = rh.hops + 1
|
|
rh.mode = rh.hops
|
|
eo.raiseHand = false
|
|
end
|
|
eo.raiseY = -math.floor(3 * sine(rh.angle) / 128)
|
|
elseif rh.mode == 1 then
|
|
rh.timer = rh.timer + 1
|
|
if rh.timer > 16 then
|
|
rh.timer = 0
|
|
eo.raiseHand = true
|
|
rh.mode = 0
|
|
end
|
|
else
|
|
rh.timer = rh.timer + 1
|
|
if rh.timer > 80 then
|
|
eo.raiseHandState = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
local function idleTick(eo, game, ctx)
|
|
if eo.frozen or eo.scriptBusy or eo.moving or eo.hidden or not eo.visible then
|
|
return
|
|
end
|
|
local Field = package.loaded["src.core.game3.field"]
|
|
if Field and Field.locked then return end
|
|
local Hud = package.loaded["src.ui.game3.hud"]
|
|
if Hud and Hud.isMenuOpen and Hud.isMenuOpen() then return end
|
|
if eo.movement == "RAISE_HAND" then
|
|
raiseHandTick(eo)
|
|
return
|
|
end
|
|
eo.idleTimer = (eo.idleTimer or 0) - 1
|
|
if eo.idleTimer > 0 then return end
|
|
|
|
local mv = tostring(eo.movement or "STAY"):upper()
|
|
if mv == "STAY" then
|
|
eo.idleTimer = 120
|
|
return
|
|
end
|
|
|
|
local Rng = require("src.core.game3.rng")
|
|
local dirs = dirsForRange(eo.range)
|
|
if mv == "LOOK" or mv == "LOOK_AROUND" then
|
|
local oldFacing = eo.facing
|
|
local pickIdx = Rng.compat(1, #dirs)
|
|
eo.facing = dirs[pickIdx] or dirs[1]
|
|
eo.idleTimer = 48 + Rng.compat(0, 47)
|
|
if not ctx and eo.facing ~= oldFacing and eo.sight and eo.sight > 0 then
|
|
local okTs, TrainerSight = pcall(require, "src.core.game3.trainer_sight")
|
|
if okTs and TrainerSight and TrainerSight.check then
|
|
TrainerSight.check(game, eo)
|
|
end
|
|
end
|
|
return
|
|
end
|
|
|
|
if mv == "WALK" then
|
|
local pickIdx = Rng.compat(1, #dirs)
|
|
local dir = dirs[pickIdx] or dirs[1]
|
|
local d = DELTA[dir]
|
|
local tx, ty = eo.cellX + d[1], eo.cellY + d[2]
|
|
local rx = (eo.radius and eo.radius.x) or 1
|
|
local ry = (eo.radius and eo.radius.y) or 1
|
|
if math.abs(tx - eo.homeX) > rx or math.abs(ty - eo.homeY) > ry then
|
|
eo.idleTimer = 30 + Rng.compat(0, 29)
|
|
return
|
|
end
|
|
local ok
|
|
-- pokefirered/src/event_object_movement.c:4830 GetCollisionAtCoords
|
|
if ctx then
|
|
local Coll = Collision()
|
|
ok = ctx.canEnter(tx, ty, eo.cellX, eo.cellY, dir)
|
|
and not ctx.blocks(tx, ty, eo.localId)
|
|
if ok and eo.mapDef and Coll.directionallyImpassableOn(
|
|
eo.mapDef, eo.cellX, eo.cellY, tx, ty, dir) then
|
|
ok = false
|
|
end
|
|
else
|
|
local Coll = Collision()
|
|
-- pokefirered/src/event_object_movement.c:8346 IsElevationMismatchAt
|
|
local onWater = Coll.isWater(eo.cellX, eo.cellY)
|
|
ok = Coll.canEnter(game, tx, ty,
|
|
{ fromX = eo.cellX, fromY = eo.cellY, dir = dir, surfing = onWater })
|
|
if ok and Coll.isWater(tx, ty) ~= onWater then ok = false end
|
|
if Objects.playerBlocks(tx, ty) then ok = false end
|
|
if Objects.blocks(tx, ty, eo.localId) then ok = false end
|
|
end
|
|
if ok then
|
|
eo.facing = dir
|
|
beginStep(eo, tx, ty)
|
|
else
|
|
eo.facing = dir -- turn toward blocked anyway
|
|
if not ctx and eo.sight and eo.sight > 0 then
|
|
local okTs, TrainerSight = pcall(require, "src.core.game3.trainer_sight")
|
|
if okTs and TrainerSight and TrainerSight.check then
|
|
TrainerSight.check(game, eo)
|
|
end
|
|
end
|
|
end
|
|
eo.idleTimer = 40 + Rng.compat(0, 49)
|
|
end
|
|
end
|
|
|
|
function Objects.update(game)
|
|
-- Advance script tracks then motion + idle.
|
|
for lid, tr in pairs(Objects._tracks) do
|
|
advanceTrack(lid, tr, game)
|
|
end
|
|
for _, lid in ipairs(Objects._order) do
|
|
local eo = Objects._byId[lid]
|
|
if eo then
|
|
if eo.bowFrames and eo.bowFrames > 0 then
|
|
eo.bowFrames = eo.bowFrames - 1
|
|
if eo.bowFrames <= 0 then eo.bowFrames = nil end
|
|
end
|
|
tickMotion(eo, game)
|
|
idleTick(eo, game)
|
|
end
|
|
end
|
|
end
|
|
|
|
function Objects.spawnFromDefs(defs, mapDef)
|
|
local pool = { byId = {}, order = {}, bounds = layoutBounds(mapDef), mapDef = mapDef }
|
|
for _, def in ipairs(defs or {}) do
|
|
local eo = newEventObject(def)
|
|
if eo.localId > 0 then
|
|
eo.mapDef = mapDef
|
|
pool.byId[eo.localId] = eo
|
|
pool.order[#pool.order + 1] = eo.localId
|
|
end
|
|
end
|
|
return pool
|
|
end
|
|
|
|
function Objects.tickPool(pool, game, ctx)
|
|
if type(pool) ~= "table" then return end
|
|
for _, lid in ipairs(pool.order or {}) do
|
|
local eo = pool.byId[lid]
|
|
if eo then
|
|
if eo.bowFrames and eo.bowFrames > 0 then
|
|
eo.bowFrames = eo.bowFrames - 1
|
|
if eo.bowFrames <= 0 then eo.bowFrames = nil end
|
|
end
|
|
tickMotion(eo, game)
|
|
idleTick(eo, game, ctx)
|
|
end
|
|
end
|
|
end
|
|
|
|
function Objects.poolForDraw(pool)
|
|
local list = {}
|
|
if type(pool) ~= "table" then return list end
|
|
for _, lid in ipairs(pool.order or {}) do
|
|
local eo = pool.byId[lid]
|
|
if eo and eo.visible and not eo.hidden and not offMap(pool.bounds, eo) then
|
|
list[#list + 1] = eo
|
|
end
|
|
end
|
|
return list
|
|
end
|
|
|
|
function Objects.snapshotPool()
|
|
return {
|
|
byId = Objects._byId, order = Objects._order,
|
|
mapId = Objects._mapId, bounds = Objects._bounds,
|
|
}
|
|
end
|
|
|
|
function Objects.adoptPool(pool)
|
|
if type(pool) ~= "table" then return false end
|
|
for _, lid in ipairs(pool.order or {}) do
|
|
local live = Objects._byId[lid]
|
|
local ghost = pool.byId[lid]
|
|
local wanders = live and tostring(live.movement or "STAY"):upper() == "WALK"
|
|
if live and ghost and wanders and not ghost.moving then
|
|
live.cellX, live.cellY = ghost.cellX, ghost.cellY
|
|
live.px, live.py = ghost.px, ghost.py
|
|
live.facing = ghost.facing
|
|
live.stepFlip = ghost.stepFlip
|
|
live.idleTimer = ghost.idleTimer
|
|
if live.def then live.def.x, live.def.y = live.cellX, live.cellY end
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
function Objects.addObject(localId)
|
|
localId = tonumber(localId) or 0
|
|
local eo = Objects._byId[localId]
|
|
if eo then
|
|
applyPerm(eo, Objects._mapId)
|
|
eo.hidden = false
|
|
eo.visible = true
|
|
if eo.def then eo.def.hidden = false end
|
|
return true
|
|
end
|
|
-- Respawn from template defs.
|
|
for _, def in ipairs(Objects._defs or {}) do
|
|
if tonumber(def.localId or def.index) == localId then
|
|
eo = newEventObject(def)
|
|
applyPerm(eo, Objects._mapId)
|
|
eo.hidden = false
|
|
eo.visible = true
|
|
Objects._byId[localId] = eo
|
|
Objects._order[#Objects._order + 1] = localId
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
--- Re-evaluate hide flags after sidecar load / setflag mid-map.
|
|
function Objects.refreshVisibility()
|
|
for _, lid in ipairs(Objects._order) do
|
|
local eo = Objects._byId[lid]
|
|
if eo and eo.def then
|
|
local vis = objectVisible(eo.def)
|
|
eo.visible = vis
|
|
eo.hidden = not vis
|
|
end
|
|
end
|
|
end
|
|
|
|
--- pret FlagClear/FlagSet on an object template hide flag.
|
|
-- clearflag after removeobject must bring the NPC back at perm coords.
|
|
function Objects.syncFlagVisibility(flagId, hidden)
|
|
flagId = tonumber(flagId) or 0
|
|
if flagId == 0 or flagId == 0xFFFF or flagId == 65535 then return end
|
|
for _, lid in ipairs(Objects._order) do
|
|
local eo = Objects._byId[lid]
|
|
if eo then
|
|
local f = tonumber(eo.flag) or (eo.def and tonumber(eo.def.flag or eo.def.flagId)) or 0
|
|
if f == flagId then
|
|
if hidden then
|
|
eo.hidden = true
|
|
eo.visible = false
|
|
if eo.def then eo.def.hidden = true end
|
|
else
|
|
applyPerm(eo, Objects._mapId)
|
|
eo.hidden = false
|
|
eo.visible = true
|
|
if eo.def then eo.def.hidden = false end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- Template exists in defs but was never spawned (or despawned from order).
|
|
if not hidden then
|
|
for _, def in ipairs(Objects._defs or {}) do
|
|
local f = tonumber(def.flag or def.flagId) or 0
|
|
local lid = tonumber(def.localId or def.index) or 0
|
|
if f == flagId and lid > 0 and not Objects._byId[lid] then
|
|
Objects.addObject(lid)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function Objects.removeObject(localId)
|
|
localId = tonumber(localId) or 0
|
|
local eo = Objects._byId[localId]
|
|
if not eo then return false end
|
|
local flag = eo.def and (eo.def.flag or eo.def.flagId)
|
|
if flag and flag ~= 0 and flag ~= 0xFFFF and flag ~= 65535 then
|
|
local Space = package.loaded["src.core.game3.scripting.space"]
|
|
if Space and Space.store then
|
|
local Flags = require("src.core.game3.scripting.flags")
|
|
Flags.setFlag(Space.store, nil, flag, true)
|
|
end
|
|
end
|
|
eo.hidden = true
|
|
eo.visible = false
|
|
if eo.def then eo.def.hidden = true end
|
|
Objects._tracks[localId] = nil
|
|
return true
|
|
end
|
|
|
|
function Objects.hideObject(localId)
|
|
return Objects.removeObject(localId)
|
|
end
|
|
|
|
function Objects.showObject(localId)
|
|
return Objects.addObject(localId)
|
|
end
|
|
|
|
function Objects.turnObject(localId, dir)
|
|
local dirs = { [1] = "down", [2] = "up", [3] = "left", [4] = "right" }
|
|
local facing = dirs[tonumber(dir) or 0]
|
|
or ({ down = "down", up = "up", left = "left", right = "right" })[tostring(dir or ""):lower()]
|
|
local eo = Objects.find(localId)
|
|
if eo and facing then
|
|
Objects.scriptFace(eo, facing)
|
|
end
|
|
end
|
|
|
|
function Objects.setObjectXY(localId, x, y)
|
|
local lid = tonumber(localId) or 0
|
|
local eo = Objects._byId[lid]
|
|
x, y = tonumber(x) or 0, tonumber(y) or 0
|
|
-- Key perm by script map (Space.mapId) when active — not the previous map's
|
|
-- Objects._mapId if enter order ever regresses.
|
|
local Sp = Space()
|
|
local mapKey = (Sp and Sp.mapId) or Objects._mapId
|
|
rememberPerm(mapKey, lid, { x = x, y = y })
|
|
if not eo then return end
|
|
eo.cellX, eo.cellY = x, y
|
|
eo.homeX, eo.homeY = x, y
|
|
eo.px, eo.py = eo.cellX * CELL, eo.cellY * CELL
|
|
eo.targetX, eo.targetY = eo.cellX, eo.cellY
|
|
eo.moving = false
|
|
if eo.def then eo.def.x, eo.def.y = eo.cellX, eo.cellY end
|
|
end
|
|
|
|
function Objects.copyObjectXYToPerm(localId)
|
|
local lid = tonumber(localId) or 0
|
|
local eo = Objects._byId[lid]
|
|
if not eo then return end
|
|
local Sp = Space()
|
|
local mapKey = (Sp and Sp.mapId) or Objects._mapId
|
|
rememberPerm(mapKey, lid, { x = eo.cellX, y = eo.cellY })
|
|
eo.homeX, eo.homeY = eo.cellX, eo.cellY
|
|
if eo.def then eo.def.x, eo.def.y = eo.cellX, eo.cellY end
|
|
end
|
|
|
|
function Objects.setMovementType(localId, mt)
|
|
local lid = tonumber(localId) or 0
|
|
local eo = Objects._byId[lid]
|
|
mt = tonumber(mt) or 0
|
|
local Sp = Space()
|
|
local mapKey = (Sp and Sp.mapId) or Objects._mapId
|
|
rememberPerm(mapKey, lid, { movementType = mt })
|
|
if not eo then return end
|
|
Objects.setTrainerMovementType(eo, mt)
|
|
local face = ({ [7] = "up", [8] = "down", [9] = "left", [10] = "right" })[mt]
|
|
if face then eo.facing = face end
|
|
end
|
|
|
|
local function clearRaiseHand(eo)
|
|
eo.raiseHandState = nil
|
|
eo.raiseHand = nil
|
|
eo.raiseX = nil
|
|
eo.raiseY = nil
|
|
end
|
|
|
|
-- src/event_object_movement.c:4806
|
|
function Objects.setTrainerMovementType(localId, mt)
|
|
local eo = type(localId) == "table" and localId or Objects._byId[tonumber(localId) or 0]
|
|
if not eo then return end
|
|
mt = tonumber(mt) or 0
|
|
eo.movementType = mt
|
|
local hostMv = GfxIds.hostMovement and GfxIds.hostMovement(mt)
|
|
if hostMv then
|
|
eo.movement = hostMv.movement
|
|
eo.range = hostMv.range
|
|
if hostMv.radius then eo.radius = hostMv.radius end
|
|
end
|
|
clearRaiseHand(eo)
|
|
if eo.movement == "RAISE_HAND" then
|
|
eo.facing = "down"
|
|
end
|
|
end
|
|
|
|
-- src/event_object_movement.c:2640
|
|
function Objects.overrideTemplateMovementType(localId, mt)
|
|
local lid = tonumber(localId) or 0
|
|
if lid <= 0 then return end
|
|
Objects._templateMt[lid] = tonumber(mt)
|
|
end
|
|
|
|
function Objects.templateMovementType(localId)
|
|
local lid = tonumber(localId) or 0
|
|
local o = Objects._templateMt[lid]
|
|
if o then return o end
|
|
for _, def in ipairs(Objects._defs or {}) do
|
|
if tonumber(def.localId or def.index) == lid then
|
|
return tonumber(def.movementType) or 0
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function Objects.facePlayer(localId, game)
|
|
local eo = Objects._byId[tonumber(localId) or 0]
|
|
local P = Player()
|
|
if not eo or not P then return end
|
|
local dx = P.cellX - eo.cellX
|
|
local dy = P.cellY - eo.cellY
|
|
if math.abs(dx) > math.abs(dy) then
|
|
eo.facing = dx > 0 and "right" or "left"
|
|
else
|
|
eo.facing = dy > 0 and "down" or "up"
|
|
end
|
|
end
|
|
|
|
function Objects.freeze(localId)
|
|
local eo = Objects._byId[tonumber(localId) or 0]
|
|
if eo then eo.frozen = true end
|
|
end
|
|
|
|
function Objects.unfreeze(localId)
|
|
local eo = Objects._byId[tonumber(localId) or 0]
|
|
if eo and not eo.scriptBusy then eo.frozen = false end
|
|
end
|
|
|
|
--- No-op. EventObjects are owned by game3; Field.interact + adapters read them
|
|
-- directly via Objects.find / forDraw. Kept for call-site compatibility.
|
|
function Objects.syncToHost(_game)
|
|
end
|
|
|
|
-- Back-compat thin wrappers used by older Objects.addObject(adapters, id) calls.
|
|
function Objects.addObjectVia(adapters, localId)
|
|
if Objects.addObject(localId) then return true end
|
|
if adapters and adapters.addObject then return adapters.addObject(localId) end
|
|
return false
|
|
end
|
|
|
|
function Objects.removeObjectVia(adapters, localId)
|
|
Objects.removeObject(localId)
|
|
if adapters and adapters.removeObject then return adapters.removeObject(localId) end
|
|
return true
|
|
end
|
|
|
|
return Objects
|