CLOSES #2411, CLOSES #2418, CLOSES #2420, CLOSES #2422, CLOSES #2423, CLOSES #2424

This commit is contained in:
bryanthaboi
2026-09-23 05:51:05 -04:00
parent 18661d45a1
commit ebbf4709c8
230 changed files with 11607 additions and 3514 deletions
+356 -100
View File
@@ -118,7 +118,7 @@ local function objectVisible(def)
return true
end
local function newEventObject(def)
local function newEventObject(def, neighbor)
-- 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 {}
@@ -129,21 +129,26 @@ local function newEventObject(def)
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 rawMt = tonumber(def.movementType)
local mt = rawMt or 0
local spec
if rawMt then
spec = GfxIds.hostMovement(rawMt, def.rangeX, def.rangeY)
else
local r = def.radius or { x = 1, y = 1 }
spec = {
movement = def.movement or "STAY", range = def.range or "DOWN",
rangeX = r.x, rangeY = r.y, radius = r,
}
end
local facing = (def.facing and facingFromDef(def))
or (rawMt and spec.face) or facingFromDef(def)
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)
local gid = Space.resolveObjectGraphicsId(def, neighbor)
if gid then resolvedGfx = gid end
end
end
@@ -162,14 +167,18 @@ local function newEventObject(def)
py = y * CELL,
homeX = x,
homeY = y,
facing = facingFromDef(def),
facing = facing,
sprite = sprite or "SPRITE_YOUNGSTER",
graphicsId = resolvedGfx,
elevation = elev,
movementType = mt,
movement = movement,
range = range,
radius = radius or { x = 1, y = 1 },
movement = spec.movement,
range = spec.range,
radius = spec.radius or { x = spec.rangeX, y = spec.rangeY },
rangeX = spec.rangeX,
rangeY = spec.rangeY,
spec = spec,
seqIndex = 0,
sight = tonumber(def.sight or def.trainerRange) or 0,
trainerType = tonumber(def.trainerType) or 0,
scriptKey = def.scriptKey,
@@ -187,7 +196,6 @@ local function newEventObject(def)
targetY = y,
stepFlip = false,
animClock = 0,
idleTimer = 30 + (lid * 17) % 60,
scriptBusy = false,
}
end
@@ -266,6 +274,21 @@ end
Objects.rememberPerm = rememberPerm
-- src/event_object_movement.c:4806
local function setSpec(eo, mt)
local spec = GfxIds.hostMovement(mt, eo.rangeX, eo.rangeY)
spec.rangeX = tonumber(eo.rangeX) or 0
spec.rangeY = tonumber(eo.rangeY) or 0
spec.radius = { x = spec.rangeX, y = spec.rangeY }
eo.movementType = tonumber(mt) or 0
eo.spec = spec
eo.movement = spec.movement
eo.range = spec.range
eo.radius = spec.radius
eo.seqIndex = 0
eo.idleTimer = nil
end
local function applyPerm(eo, mapId)
local bucket = Objects._perm[mapId]
local row = bucket and bucket[eo.localId]
@@ -285,15 +308,9 @@ local function applyPerm(eo, mapId)
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
setSpec(eo, row.movementType)
-- src/event_object_movement.c:1378
eo.facing = eo.spec.face
-- src/event_object_movement.c:1569
eo.invisible = tonumber(row.movementType) == MOVEMENT_TYPE_INVISIBLE
if eo.def then eo.def.movementType = row.movementType end
@@ -557,8 +574,10 @@ function Objects.at(tx, ty)
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
-- src/event_object_movement.c:1281
local cx = eo.moving and eo.targetX or eo.cellX
local cy = eo.moving and eo.targetY or eo.cellY
if cx == tx and cy == ty then
return eo
end
end
@@ -639,6 +658,36 @@ local function finishStep(eo, game)
end
end
-- src/event_object_movement.c:8866
local STEP_PIXELS = {
[16] = { 1 },
[8] = { 2 },
[6] = { 2, 3, 3 },
[4] = { 4 },
[2] = { 8 },
-- src/event_object_movement.c:9029
[11] = { 1, 2 },
-- src/event_object_movement.c:8984
[24] = { 1, 1, 0 },
-- src/event_object_movement.c:8959
[32] = { 1, 0 },
}
local STEP_OFFSETS = {}
for frames, pat in pairs(STEP_PIXELS) do
local cum, n = {}, 0
for k = 1, frames do
n = math.min(CELL, n + pat[(k - 1) % #pat + 1])
cum[k] = n
end
STEP_OFFSETS[frames] = cum
end
local function stepOffset(frames, progress, cells)
local cum = cells == 1 and STEP_OFFSETS[frames]
if cum then return cum[math.min(progress, frames)] end
return math.floor(cells * CELL * math.min(progress, frames) / frames)
end
local function tickMotion(eo, game)
if not eo.moving then return false end
eo.progress = eo.progress + 1
@@ -646,9 +695,9 @@ local function tickMotion(eo, game)
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
local off = stepOffset(frames, eo.progress, math.abs(dx) + math.abs(dy))
eo.px = eo.cellX * CELL + (dx > 0 and off or dx < 0 and -off or 0)
eo.py = eo.cellY * CELL + (dy > 0 and off or dy < 0 and -off or 0)
if eo.progress >= frames then
finishStep(eo, game)
return true
@@ -693,6 +742,16 @@ function Objects.scriptJump(eo, dir, distance)
return true
end
-- pokefirered/src/event_object_movement.c:5351 InitNpcForWalkSlower
function Objects.pushStep(eo, dir, frames)
local d = DELTA[dir]
if not eo or not d or eo.moving then return false end
eo.facing = dir
beginStep(eo, eo.cellX + d[1], eo.cellY + d[2])
eo.stepFrames = frames or WALK_FRAMES * 2
return true
end
function Objects.scriptFace(eo, dir)
if not eo then return end
local P = Player()
@@ -795,7 +854,9 @@ local function advanceTrack(lid, tr, game)
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)
-- src/event_object_movement.c:7016
local origFace = eo.def.movementType ~= nil and GfxIds.initialFacing(eo.movementType)
or facingFromDef(eo.def)
Objects.scriptFace(eo, origFace)
end
elseif act.kind == "bow" then
@@ -819,12 +880,18 @@ local function advanceTrack(lid, tr, game)
elseif act.kind == "sleep" then
tr.sleep = act.frames or 1
elseif act.kind == "hide" then
if eo and eo ~= Player() then
-- pokefirered/src/event_object_movement.c:7054
if eo == Player() then
Player().setVisible(false)
elseif eo then
eo.hidden = true
eo.visible = false
end
elseif act.kind == "show" then
if eo and eo ~= Player() then
-- pokefirered/src/event_object_movement.c:7061
if eo == Player() then
Player().setVisible(true)
elseif eo then
eo.hidden = false
eo.visible = true
end
@@ -939,6 +1006,142 @@ local function raiseHandTick(eo)
end
end
local function checkSight(game, eo)
local okTs, TrainerSight = pcall(require, "src.core.game3.trainer_sight")
if okTs and TrainerSight and TrainerSight.check then
TrainerSight.check(game, eo)
end
end
-- src/event_object_movement.c:4830
local function stepCollision(eo, game, ctx, dir)
local d = DELTA[dir]
local tx, ty = eo.cellX + d[1], eo.cellY + d[2]
-- src/event_object_movement.c:4861
local rx = tonumber(eo.rangeX or (eo.radius and eo.radius.x)) or 0
local ry = tonumber(eo.rangeY or (eo.radius and eo.radius.y)) or 0
if (rx ~= 0 and math.abs(tx - eo.homeX) > rx) or (ry ~= 0 and math.abs(ty - eo.homeY) > ry) then
return "range"
end
local ok
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 not ok then return "blocked" end
return nil
end
-- src/event_object_movement.c:3884
local function walkOrInPlace(eo, dir, collision)
eo.facing = dir
if collision then
beginStep(eo, eo.cellX, eo.cellY)
else
local d = DELTA[dir]
beginStep(eo, eo.cellX + d[1], eo.cellY + d[2])
end
end
-- src/event_object_movement.c:2770
local function playerCellFor(ctx)
local P = Player()
local px = P.moving and P.targetX or P.cellX
local py = P.moving and P.targetY or P.cellY
return px - (ctx and ctx.ox or 0), py - (ctx and ctx.oy or 0)
end
local function trainerCloseToRunningPlayer(eo, ctx)
local P = Player()
if not (P and P.running) or P.biking or P.surfing then return false end
local tt = tonumber(eo.trainerType) or 0
if tt ~= 1 and tt ~= 3 then return false end
local r = tonumber(eo.sight) or 0
local px, py = playerCellFor(ctx)
return math.abs(px - eo.cellX) <= r and math.abs(py - eo.cellY) <= r
end
-- src/event_object_movement.c:2801
local function vectorDirection(dx, dy)
if math.abs(dx) > math.abs(dy) then return dx < 0 and "left" or "right" end
return dy < 0 and "up" or "down"
end
local function southNorth(dy) return dy < 0 and "up" or "down" end
local function westEast(dx) return dx < 0 and "left" or "right" end
-- src/data/object_events/movement_type_func_tables.h:185
local FOLLOW = {
[0] = vectorDirection,
[1] = function(_, dy) return southNorth(dy) end,
[2] = function(dx) return westEast(dx) end,
[3] = function(dx, dy)
local d = vectorDirection(dx, dy)
if d == "down" then d = westEast(dx); if d == "right" then d = "up" end
elseif d == "right" then d = southNorth(dy); if d == "down" then d = "up" end end
return d
end,
[4] = function(dx, dy)
local d = vectorDirection(dx, dy)
if d == "down" then d = westEast(dx); if d == "left" then d = "up" end
elseif d == "left" then d = southNorth(dy); if d == "down" then d = "up" end end
return d
end,
[5] = function(dx, dy)
local d = vectorDirection(dx, dy)
if d == "up" then d = westEast(dx); if d == "right" then d = "down" end
elseif d == "right" then d = southNorth(dy); if d == "up" then d = "down" end end
return d
end,
[6] = function(dx, dy)
local d = vectorDirection(dx, dy)
if d == "up" then d = westEast(dx); if d == "left" then d = "down" end
elseif d == "left" then d = southNorth(dy); if d == "up" then d = "down" end end
return d
end,
[7] = function(dx, dy)
local d = vectorDirection(dx, dy)
if d == "right" then d = southNorth(dy) end
return d
end,
[8] = function(dx, dy)
local d = vectorDirection(dx, dy)
if d == "left" then d = southNorth(dy) end
return d
end,
[9] = function(dx, dy)
local d = vectorDirection(dx, dy)
if d == "down" then d = westEast(dx) end
return d
end,
[10] = function(dx, dy)
local d = vectorDirection(dx, dy)
if d == "up" then d = westEast(dx) end
return d
end,
}
-- src/event_object_movement.c:2992
local function followDirection(eo, follow, ctx)
local px, py = playerCellFor(ctx)
local fn = FOLLOW[follow] or FOLLOW[0]
return fn(px - eo.cellX, py - eo.cellY)
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
@@ -951,75 +1154,94 @@ local function idleTick(eo, game, ctx)
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
if mv == "STAY" then return end
local spec = eo.spec
if not spec or spec.movement ~= mv then
spec = { movement = mv, dirs = dirsForRange(eo.range), delays = "MEDIUM", follow = 0 }
end
if mv == "BACK_FORTH" then
-- src/event_object_movement.c:3849
local dir = (eo.seqIndex or 0) ~= 0 and OPPOSITE_DIR[spec.face] or spec.face
if (eo.seqIndex or 0) ~= 0 and eo.cellX == eo.homeX and eo.cellY == eo.homeY then
eo.seqIndex = 0
dir = OPPOSITE_DIR[dir]
end
local c = stepCollision(eo, game, ctx, dir)
if c == "range" then
eo.seqIndex = (eo.seqIndex or 0) + 1
dir = OPPOSITE_DIR[dir]
c = stepCollision(eo, game, ctx, dir)
end
walkOrInPlace(eo, dir, c)
return
end
if mv == "SEQUENCE" then
-- src/event_object_movement.c:3949
local idx = eo.seqIndex or 0
if idx == spec.skipFrom and ((spec.skipAxis == "x" and eo.cellX == eo.homeX)
or (spec.skipAxis == "y" and eo.cellY == eo.homeY)) then
idx = spec.skipFrom + 1
end
-- src/event_object_movement.c:3914
if idx == 3 and eo.cellX == eo.homeX and eo.cellY == eo.homeY then idx = 0 end
local dir = spec.route[idx + 1]
local c = stepCollision(eo, game, ctx, dir)
if c == "range" then
idx = (idx + 1) % 4
dir = spec.route[idx + 1]
c = stepCollision(eo, game, ctx, dir)
end
eo.seqIndex = idx
walkOrInPlace(eo, dir, c)
return
end
local Rng = require("src.core.game3.rng")
local dirs = dirsForRange(eo.range)
if mv == "LOOK" or mv == "LOOK_AROUND" then
local function pick(t) return t[(Rng.Random() % #t) + 1] end
if mv == "LOOK" or mv == "LOOK_AROUND" or mv == "ROTATE" then
-- src/event_object_movement.c:3044
local close = trainerCloseToRunningPlayer(eo, ctx)
if eo.idleTimer == nil then
eo.idleTimer = mv == "ROTATE" and 48 or pick(GfxIds.DELAYS[spec.delays or "MEDIUM"])
end
eo.idleTimer = eo.idleTimer - 1
if eo.idleTimer > 0 and not close then return end
local oldFacing = eo.facing
local pickIdx = Rng.compat(1, #dirs)
eo.facing = dirs[pickIdx] or dirs[1]
eo.idleTimer = 48 + Rng.compat(0, 47)
-- src/event_object_movement.c:2992
local dir = close and followDirection(eo, spec.follow or 0, ctx)
if not dir then
dir = mv == "ROTATE" and spec.next[eo.facing] or pick(spec.dirs)
end
eo.facing = dir
eo.idleTimer = mv == "ROTATE" and 48 or pick(GfxIds.DELAYS[spec.delays or "MEDIUM"])
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
checkSight(game, eo)
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)
-- src/event_object_movement.c:2716
if eo.idleTimer == nil then eo.idleTimer = pick(GfxIds.DELAYS.MEDIUM) end
eo.idleTimer = eo.idleTimer - 1
if eo.idleTimer > 0 then return end
-- src/event_object_movement.c:2731
local dir = pick(spec.dirs or dirsForRange(eo.range))
eo.facing = dir
eo.idleTimer = pick(GfxIds.DELAYS.MEDIUM)
if stepCollision(eo, game, ctx, dir) then
if not ctx and eo.sight and eo.sight > 0 then checkSight(game, eo) end
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)
local d = DELTA[dir]
beginStep(eo, eo.cellX + d[1], eo.cellY + d[2])
-- src/event_object_movement.c:8959
if spec.slow then eo.stepFrames = WALK_FRAMES * 2 end
end
end
@@ -1041,12 +1263,31 @@ function Objects.update(game)
end
end
function Objects.spawnFromDefs(defs, mapDef)
function Objects.spawnFromDefs(defs, mapDef, mapId)
local pool = { byId = {}, order = {}, bounds = layoutBounds(mapDef), mapDef = mapDef }
local Sp = mapId and Space()
local nb = Sp and Sp.neighborObjectState and Sp.neighborObjectState(mapId)
if mapId and not nb and not (Sp and Sp.mapId == mapId) then nb = { store = { flags = {}, vars = {} }, perm = {}, movementType = {} } end
for _, def in ipairs(defs or {}) do
local eo = newEventObject(def)
local eo = newEventObject(def, nb)
if eo.localId > 0 then
eo.mapDef = mapDef
if nb then
local p = nb.perm[eo.localId]
if p then
eo.cellX, eo.cellY, eo.homeX, eo.homeY = p.x, p.y, p.x, p.y
eo.targetX, eo.targetY = p.x, p.y
eo.px, eo.py = p.x * CELL, p.y * CELL
eo.def.x, eo.def.y = p.x, p.y
end
local mt = nb.movementType[eo.localId]
if mt then
Objects.setTrainerMovementType(eo, mt)
-- pokefirered/src/event_object_movement.c:359
eo.facing = GfxIds.initialFacing(mt)
end
if (tonumber(eo.graphicsId) or 0) >= 240 then eo.invisible = true end
end
pool.byId[eo.localId] = eo
pool.order[#pool.order + 1] = eo.localId
end
@@ -1094,14 +1335,22 @@ function Objects.adoptPool(pool)
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
local mv = live and ghost and live.movement == ghost.movement
and tostring(live.movement or "STAY"):upper()
if mv == "WALK" or mv == "BACK_FORTH" or mv == "SEQUENCE" then
live.cellX, live.cellY = ghost.cellX, ghost.cellY
live.px, live.py = ghost.px, ghost.py
live.targetX, live.targetY = ghost.targetX, ghost.targetY
live.moving, live.progress = ghost.moving, ghost.progress
live.stepFrames, live.animClock = ghost.stepFrames, ghost.animClock
live.facing = ghost.facing
live.stepFlip = ghost.stepFlip
live.idleTimer = ghost.idleTimer
live.seqIndex = ghost.seqIndex
if live.def then live.def.x, live.def.y = live.cellX, live.cellY end
elseif mv == "LOOK" or mv == "ROTATE" then
live.facing = ghost.facing
live.idleTimer = ghost.idleTimer
end
end
return true
@@ -1225,12 +1474,25 @@ function Objects.removeObject(localId)
return true
end
-- src/event_object_movement.c:2059
local function setObjectInvisibility(localId, state)
local lid = tonumber(localId) or 0
if lid >= Objects.PLAYER_LOCAL_ID then
Player().setVisible(not state)
return true
end
local eo = Objects._byId[lid]
if not eo then return false end
eo.invisible = state
return true
end
function Objects.hideObject(localId)
return Objects.removeObject(localId)
return setObjectInvisibility(localId, true)
end
function Objects.showObject(localId)
return Objects.addObject(localId)
return setObjectInvisibility(localId, false)
end
function Objects.turnObject(localId, dir)
@@ -1297,13 +1559,7 @@ 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
setSpec(eo, mt)
clearRaiseHand(eo)
-- src/event_object_movement.c:4543
if mt == MOVEMENT_TYPE_INVISIBLE then eo.invisible = true end