Merge pull request #140 from MiyazakiTheFalse/codex/add-chase-overworld-subsystem-for-chaser-events-eoy8ez

Add overworld chaser visuals and chase_overworld module; integrate with chase stamina
This commit is contained in:
MiyazakiTheFalse 2026-03-07 01:06:39 +00:00 committed by GitHub
commit e9ff05c45c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -10,17 +10,30 @@
#define CHASE_OVERWORLD_MAX_CHASERS 2
#define CHASE_OVERWORLD_LOCAL_ID_BASE 230
#define CHASE_OVERWORLD_GFX_ID OBJ_EVENT_GFX_MEOWTH
#define CHASE_OVERWORLD_MAX_STALLED_FRAMES 30
static const u8 sAllMoveDirections[] =
{
DIR_NORTH,
DIR_SOUTH,
DIR_WEST,
DIR_EAST,
};
static EWRAM_DATA bool8 sChasersSpawned = FALSE;
static EWRAM_DATA u8 sSpawnedMapGroup = MAP_GROUP(MAP_UNDEFINED);
static EWRAM_DATA u8 sSpawnedMapNum = MAP_NUM(MAP_UNDEFINED);
static EWRAM_DATA u8 sChaserStalledFrames[CHASE_OVERWORLD_MAX_CHASERS];
static void DespawnChasers(void)
{
u8 i;
for (i = 0; i < CHASE_OVERWORLD_MAX_CHASERS; i++)
{
RemoveObjectEventByLocalIdAndMap(CHASE_OVERWORLD_LOCAL_ID_BASE + i, sSpawnedMapNum, sSpawnedMapGroup);
sChaserStalledFrames[i] = 0;
}
sChasersSpawned = FALSE;
sSpawnedMapGroup = MAP_GROUP(MAP_UNDEFINED);
@ -34,89 +47,88 @@ static bool8 IsSpawnContextValid(void)
&& sSpawnedMapNum == gSaveBlock1Ptr->location.mapNum;
}
static u8 GetMoveDirectionTowardTarget(s16 fromX, s16 fromY, s16 toX, s16 toY, u8 preferredDir)
static u16 GetDistanceScore(s16 fromX, s16 fromY, s16 toX, s16 toY, u8 direction)
{
if (fromX < toX)
return DIR_EAST;
if (fromX > toX)
return DIR_WEST;
if (fromY < toY)
return DIR_SOUTH;
if (fromY > toY)
return DIR_NORTH;
s16 testX = fromX;
s16 testY = fromY;
s16 dx;
s16 dy;
return preferredDir;
MoveCoords(direction, &testX, &testY);
dx = testX - toX;
if (dx < 0)
dx = -dx;
dy = testY - toY;
if (dy < 0)
dy = -dy;
return dx + dy;
}
static bool8 TryQueueChaserStep(struct ObjectEvent *objectEvent, s16 targetX, s16 targetY)
{
s16 dx;
s16 dy;
u8 i;
u8 primaryDir;
u8 secondaryDir;
u8 fallbackDirs[4];
u8 dirOrder[ARRAY_COUNT(sAllMoveDirections)];
primaryDir = DIR_NONE;
secondaryDir = DIR_NONE;
dx = objectEvent->currentCoords.x - targetX;
if (dx < 0)
dx = -dx;
dy = objectEvent->currentCoords.y - targetY;
if (dy < 0)
dy = -dy;
for (i = 0; i < ARRAY_COUNT(dirOrder); i++)
dirOrder[i] = sAllMoveDirections[i];
if (dx >= dy)
for (i = 0; i < ARRAY_COUNT(dirOrder); i++)
{
if (objectEvent->currentCoords.x < targetX)
primaryDir = DIR_EAST;
else if (objectEvent->currentCoords.x > targetX)
primaryDir = DIR_WEST;
u8 j;
if (objectEvent->currentCoords.y < targetY)
secondaryDir = DIR_SOUTH;
else if (objectEvent->currentCoords.y > targetY)
secondaryDir = DIR_NORTH;
}
else
{
if (objectEvent->currentCoords.y < targetY)
primaryDir = DIR_SOUTH;
else if (objectEvent->currentCoords.y > targetY)
primaryDir = DIR_NORTH;
if (objectEvent->currentCoords.x < targetX)
secondaryDir = DIR_EAST;
else if (objectEvent->currentCoords.x > targetX)
secondaryDir = DIR_WEST;
for (j = i + 1; j < ARRAY_COUNT(dirOrder); j++)
{
if (GetDistanceScore(objectEvent->currentCoords.x, objectEvent->currentCoords.y, targetX, targetY, dirOrder[j])
< GetDistanceScore(objectEvent->currentCoords.x, objectEvent->currentCoords.y, targetX, targetY, dirOrder[i]))
{
u8 tmp = dirOrder[i];
dirOrder[i] = dirOrder[j];
dirOrder[j] = tmp;
}
}
}
if (primaryDir == DIR_NONE)
primaryDir = GetMoveDirectionTowardTarget(objectEvent->currentCoords.x, objectEvent->currentCoords.y, targetX, targetY, objectEvent->facingDirection);
fallbackDirs[0] = primaryDir;
fallbackDirs[1] = secondaryDir;
fallbackDirs[2] = objectEvent->facingDirection;
fallbackDirs[3] = GetOppositeDirection(objectEvent->facingDirection);
for (i = 0; i < ARRAY_COUNT(fallbackDirs); i++)
for (i = 0; i < ARRAY_COUNT(dirOrder); i++)
{
u8 direction = fallbackDirs[i];
u8 direction = dirOrder[i];
s16 testX = objectEvent->currentCoords.x;
s16 testY = objectEvent->currentCoords.y;
if (direction == DIR_NONE)
continue;
MoveCoords(direction, &testX, &testY);
if (GetCollisionAtCoords(objectEvent, testX, testY, direction) == COLLISION_NONE)
return ObjectEventSetHeldMovement(objectEvent, GetWalkNormalMovementAction(direction));
}
ObjectEventTurn(objectEvent, GetMoveDirectionTowardTarget(objectEvent->currentCoords.x, objectEvent->currentCoords.y, targetX, targetY, objectEvent->facingDirection));
return FALSE;
}
static void PlaceChaserNearPlayer(u8 localId, u8 objectEventId, s16 playerX, s16 playerY)
{
static const s8 sRespawnOffsets[][2] =
{
{-2, 0},
{2, 0},
{0, -2},
{0, 2},
{-2, 2},
{2, 2},
};
u8 i;
for (i = 0; i < ARRAY_COUNT(sRespawnOffsets); i++)
{
s16 candidateX = playerX + sRespawnOffsets[i][0];
s16 candidateY = playerY + sRespawnOffsets[i][1];
TryMoveObjectEventToMapCoords(localId, sSpawnedMapNum, sSpawnedMapGroup, candidateX, candidateY);
if (gObjectEvents[objectEventId].currentCoords.x == candidateX
&& gObjectEvents[objectEventId].currentCoords.y == candidateY)
return;
}
}
static void SpawnOrSyncChasers(void)
{
u8 i;
@ -149,6 +161,7 @@ static void SpawnOrSyncChasers(void)
if (i >= activeChasers)
{
RemoveObjectEventByLocalIdAndMap(localId, sSpawnedMapNum, sSpawnedMapGroup);
sChaserStalledFrames[i] = 0;
continue;
}
@ -161,6 +174,7 @@ static void SpawnOrSyncChasers(void)
SpawnSpecialObjectEventParameterized(CHASE_OVERWORLD_GFX_ID, MOVEMENT_TYPE_FACE_DOWN, localId, spawnX + MAP_OFFSET, spawnY + MAP_OFFSET, elevation);
if (!TryGetObjectEventIdByLocalIdAndMap(localId, sSpawnedMapNum, sSpawnedMapGroup, &objectEventId))
continue;
sChaserStalledFrames[i] = 0;
}
if (ObjectEventIsHeldMovementActive(&gObjectEvents[objectEventId]))
@ -169,7 +183,21 @@ static void SpawnOrSyncChasers(void)
continue;
}
TryQueueChaserStep(&gObjectEvents[objectEventId], playerX, playerY);
if (TryQueueChaserStep(&gObjectEvents[objectEventId], playerX, playerY))
{
sChaserStalledFrames[i] = 0;
}
else
{
if (sChaserStalledFrames[i] < 0xFF)
sChaserStalledFrames[i]++;
if (sChaserStalledFrames[i] >= CHASE_OVERWORLD_MAX_STALLED_FRAMES)
{
PlaceChaserNearPlayer(localId, objectEventId, playerX, playerY);
sChaserStalledFrames[i] = 0;
}
}
}
}