Bug fix: add handling for forced movements into/after ledge jumps with follower NPCs (#7055)

This commit is contained in:
Bivurnum 2025-08-04 08:10:20 -05:00 committed by GitHub
parent c037ba2522
commit f6a120310e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 38 additions and 44 deletions

View File

@ -20,6 +20,7 @@ enum FollowerNPCDataTypes
FNPC_DATA_WARP_END,
FNPC_DATA_SURF_BLOB,
FNPC_DATA_COME_OUT_DOOR,
FNPC_DATA_FORCED_MOVEMENT,
FNPC_DATA_OBJ_ID,
FNPC_DATA_CURRENT_SPRITE,
FNPC_DATA_DELAYED_STATE,
@ -62,7 +63,8 @@ enum FollowerNPCSurfBlobStates
FNPC_SURF_BLOB_DESTROY
};
enum FollowerNPCOutOfDoorTaskStates{
enum FollowerNPCOutOfDoorTaskStates
{
OPEN_DOOR,
NPC_WALK_OUT,
CLOSE_DOOR,
@ -70,7 +72,8 @@ enum FollowerNPCOutOfDoorTaskStates{
REALLOW_MOVEMENT
};
enum FollowerNPCHandleEscalatorFinishTaskStates{
enum FollowerNPCHandleEscalatorFinishTaskStates
{
MOVE_TO_PLAYER_POS,
WAIT_FOR_PLAYER_MOVE,
SHOW_FOLLOWER_DOWN,

View File

@ -219,7 +219,8 @@ struct NPCFollower
u8 inProgress:1;
u8 warpEnd:1;
u8 createSurfBlob:3;
u8 comeOutDoorStairs:3;
u8 comeOutDoorStairs:2;
u8 forcedMovement:1;
u8 objId;
u8 currentSprite;
u8 delayedState;

View File

@ -382,8 +382,16 @@ void PlayerStep(u8 direction, u16 newKeys, u16 heldKeys)
DoPlayerAvatarTransition();
if (TryDoMetatileBehaviorForcedMovement() == 0)
{
MovePlayerAvatarUsingKeypadInput(direction, newKeys, heldKeys);
PlayerAllowForcedMovementIfMovingSameDirection();
if (GetFollowerNPCData(FNPC_DATA_FORCED_MOVEMENT) != FALSE)
{
gPlayerAvatar.preventStep = TRUE;
CreateTask(Task_MoveNPCFollowerAfterForcedMovement, 1);
}
else
{
MovePlayerAvatarUsingKeypadInput(direction, newKeys, heldKeys);
PlayerAllowForcedMovementIfMovingSameDirection();
}
}
}
}
@ -510,7 +518,11 @@ static bool8 DoForcedMovement(u8 direction, void (*moveFunc)(u8))
else
{
if (collision == COLLISION_LEDGE_JUMP)
{
SetFollowerNPCData(FNPC_DATA_FORCED_MOVEMENT, FALSE);
PlayerJumpLedge(direction);
}
playerAvatar->flags |= PLAYER_AVATAR_FLAG_FORCED_MOVE;
playerAvatar->runningState = MOVING;
return TRUE;
@ -518,12 +530,11 @@ static bool8 DoForcedMovement(u8 direction, void (*moveFunc)(u8))
}
else
{
if (PlayerHasFollowerNPC())
SetFollowerNPCData(FNPC_DATA_FORCED_MOVEMENT, TRUE);
playerAvatar->runningState = MOVING;
moveFunc(direction);
if (PlayerHasFollowerNPC()
&& gObjectEvents[GetFollowerNPCObjectId()].invisible == FALSE
&& FindTaskIdByFunc(Task_MoveNPCFollowerAfterForcedMovement) == TASK_NONE)
CreateTask(Task_MoveNPCFollowerAfterForcedMovement, 3);
return TRUE;
}
}

View File

@ -83,6 +83,9 @@ void SetFollowerNPCData(enum FollowerNPCDataTypes type, u32 value)
case FNPC_DATA_COME_OUT_DOOR:
gSaveBlock3Ptr->NPCfollower.comeOutDoorStairs = value;
break;
case FNPC_DATA_FORCED_MOVEMENT:
gSaveBlock3Ptr->NPCfollower.forcedMovement = value;
break;
case FNPC_DATA_OBJ_ID:
gSaveBlock3Ptr->NPCfollower.objId = value;
break;
@ -155,6 +158,8 @@ u32 GetFollowerNPCData(enum FollowerNPCDataTypes type)
return gSaveBlock3Ptr->NPCfollower.createSurfBlob;
case FNPC_DATA_COME_OUT_DOOR:
return gSaveBlock3Ptr->NPCfollower.comeOutDoorStairs;
case FNPC_DATA_FORCED_MOVEMENT:
return gSaveBlock3Ptr->NPCfollower.forcedMovement;
case FNPC_DATA_OBJ_ID:
return gSaveBlock3Ptr->NPCfollower.objId;
case FNPC_DATA_CURRENT_SPRITE:
@ -720,9 +725,7 @@ u32 DetermineFollowerNPCState(struct ObjectEvent *follower, u32 state, u32 direc
MoveCoords(direction, &followerX, &followerY);
nextBehavior = MapGridGetMetatileBehaviorAt(followerX, followerY);
if (FindTaskIdByFunc(Task_MoveNPCFollowerAfterForcedMovement) == TASK_NONE)
follower->facingDirectionLocked = FALSE;
follower->facingDirectionLocked = FALSE;
// Follower won't do delayed movement until player does a movement.
if (!IsStateMovement(state) && delayedState)
@ -777,10 +780,6 @@ u32 DetermineFollowerNPCState(struct ObjectEvent *follower, u32 state, u32 direc
RETURN_STATE(MOVEMENT_ACTION_WALK_NORMAL_DOWN, direction);
case MOVEMENT_ACTION_WALK_FAST_DOWN ... MOVEMENT_ACTION_WALK_FAST_RIGHT:
// Handle player on waterfall.
if (PlayerIsUnderWaterfall(&gObjectEvents[gPlayerAvatar.objectEventId]) && (state == MOVEMENT_ACTION_WALK_FAST_UP))
return MOVEMENT_INVALID;
// Handle ice tile (some walking animation).
if (MetatileBehavior_IsIce(follower->currentMetatileBehavior) || MetatileBehavior_IsTrickHouseSlipperyFloor(follower->currentMetatileBehavior))
follower->disableAnim = TRUE;
@ -789,6 +788,9 @@ u32 DetermineFollowerNPCState(struct ObjectEvent *follower, u32 state, u32 direc
if (GetFollowerNPCData(FNPC_DATA_CURRENT_SPRITE) == FOLLOWER_NPC_SPRITE_INDEX_SURF && GetFollowerNPCSprite() == GetFollowerNPCData(FNPC_DATA_GFX_ID))
RETURN_STATE(MOVEMENT_ACTION_SURF_STILL_DOWN, direction);
if (MetatileBehavior_IsMuddySlope(follower->currentMetatileBehavior))
follower->facingDirectionLocked = TRUE;
RETURN_STATE(MOVEMENT_ACTION_WALK_FAST_DOWN, direction);
case MOVEMENT_ACTION_WALK_FASTER_DOWN ... MOVEMENT_ACTION_WALK_FASTER_RIGHT:
@ -798,10 +800,6 @@ u32 DetermineFollowerNPCState(struct ObjectEvent *follower, u32 state, u32 direc
RETURN_STATE(MOVEMENT_ACTION_WALK_FASTER_DOWN, direction);
case MOVEMENT_ACTION_RIDE_WATER_CURRENT_DOWN ... MOVEMENT_ACTION_RIDE_WATER_CURRENT_RIGHT:
// Handle player on waterfall.
if (PlayerIsUnderWaterfall(&gObjectEvents[gPlayerAvatar.objectEventId]) && IsPlayerSurfingNorth())
return MOVEMENT_INVALID;
RETURN_STATE(MOVEMENT_ACTION_RIDE_WATER_CURRENT_DOWN, direction);
// Acro bike.
@ -1544,37 +1542,20 @@ void FollowerNPC_TryRemoveFollowerOnWhiteOut(void)
#undef tDoorY
// Task data
#define PREVENT_PLAYER_STEP 0
#define DO_ALL_FORCED_MOVEMENTS 1
#define NPC_INTO_PLAYER 2
#define ENABLE_PLAYER_STEP 3
#define NPC_INTO_PLAYER 0
#define ENABLE_PLAYER_STEP 1
void Task_MoveNPCFollowerAfterForcedMovement(u8 taskId)
{
struct ObjectEvent *follower = &gObjectEvents[GetFollowerNPCObjectId()];
struct ObjectEvent *player = &gObjectEvents[gPlayerAvatar.objectEventId];
// Prevent player input until all forced mmovements are done and the follower is hidden.
if (gTasks[taskId].tState == PREVENT_PLAYER_STEP)
// The NPC will take an extra step and be on the same tile as the player.
if (gTasks[taskId].tState == NPC_INTO_PLAYER && ObjectEventClearHeldMovementIfFinished(player) != 0 && ObjectEventClearHeldMovementIfFinished(follower) != 0)
{
gPlayerAvatar.preventStep = TRUE;
gTasks[taskId].tState = DO_ALL_FORCED_MOVEMENTS;
}
// The player will keep doing forced movments until they land on a non-forced-move metatile or hit collision.
else if (gTasks[taskId].tState == DO_ALL_FORCED_MOVEMENTS && ObjectEventClearHeldMovementIfFinished(player) != 0)
{
// Lock follower facing direction for muddy slope.
if (follower->currentMetatileBehavior == MB_MUDDY_SLOPE)
follower->facingDirectionLocked = TRUE;
if (TryDoMetatileBehaviorForcedMovement() == 0)
gTasks[taskId].tState = NPC_INTO_PLAYER;
return;
}
// The NPC will take an extra step and be on the same tile as the player.
else if (gTasks[taskId].tState == NPC_INTO_PLAYER && ObjectEventClearHeldMovementIfFinished(player) != 0 && ObjectEventClearHeldMovementIfFinished(follower) != 0)
{
ObjectEventSetHeldMovement(follower, GetWalkFastMovementAction(DetermineFollowerNPCDirection(player, follower)));
gTasks[taskId].tState = ENABLE_PLAYER_STEP;
return;
@ -1585,14 +1566,13 @@ void Task_MoveNPCFollowerAfterForcedMovement(u8 taskId)
follower->facingDirectionLocked = FALSE;
HideNPCFollower();
SetFollowerNPCData(FNPC_DATA_WARP_END, FNPC_WARP_REAPPEAR);
SetFollowerNPCData(FNPC_DATA_FORCED_MOVEMENT, FALSE);
gPlayerAvatar.preventStep = FALSE;
DestroyTask(taskId);
}
}
#undef tState
#undef PREVENT_PLAYER_STEP
#undef DO_ALL_FORCED_MOVEMENTS
#undef NPC_INTO_PLAYER
#undef ENABLE_PLAYER_STEP
@ -1628,7 +1608,6 @@ void ScriptDestroyFollowerNPC(struct ScriptContext *ctx)
return;
RemoveObjectEvent(&gObjectEvents[GetFollowerNPCData(FNPC_DATA_OBJ_ID)]);
FlagSet(GetFollowerNPCData(FNPC_DATA_EVENT_FLAG));
ClearFollowerNPCData();
UpdateFollowingPokemon();
}