add sideway stairs handling

This commit is contained in:
cawtds
2025-03-22 14:01:52 +01:00
parent 09eeb0ad51
commit 0de9ae1f9c
14 changed files with 708 additions and 159 deletions

View File

@@ -2266,3 +2266,9 @@
callnative ScriptHideItemDescription
.endm
@ Hides any follower Pokémon if present, putting them into their Poké Ball; by default waits for their movement to finish.
.macro hidefollower wait=1
callnative ScrFunc_hidefollower
.2byte \wait
.endm

View File

@@ -19,7 +19,8 @@ EventScript_PkmnCenterNurse_HealPkmn::
end
EventScript_PkmnCenterNurse_TakeAndHealPkmn::
applymovement VAR_LAST_TALKED, Movement_PkmnCenterNurse_Turn @ Changed from Common_Movement_WalkInPlaceFasterLeft to force the follower to enter their Poké Ball
hidefollower 0
applymovement VAR_LAST_TALKED, Movement_PkmnCenterNurse_Turn
waitmovement 0
dofieldeffect FLDEFF_POKECENTER_HEAL
waitfieldeffect FLDEFF_POKECENTER_HEAL

View File

@@ -4,7 +4,7 @@
// Movement config
#define OW_RUNNING_INDOORS GEN_LATEST // In Gen4+, players are allowed to run indoors.
#define OW_AUTO_SIGNPOST FALSE // When enabled, if the tile that the player is facing has MB_SIGNPOST, MB_POKEMART_SIGN, or MB_POKEMON_CENTER_SIGN, the player will automatically read the signpost, as seen in FRLG.
#define SLOW_MOVEMENT_ON_STAIRS FALSE // If enabled, the player will move slower up/down stairs like in FR
#define SLOW_MOVEMENT_ON_STAIRS TRUE // If enabled, the player will move slower up/down stairs like in FR
// Other settings
#define OW_POISON_DAMAGE GEN_LATEST // In Gen4, Pokémon no longer faint from Poison in the overworld. In Gen5+, they no longer take damage at all.

View File

@@ -58,6 +58,12 @@
#define MB_SLIDE_NORTH 0x46
#define MB_SLIDE_SOUTH 0x47
#define MB_TRICK_HOUSE_PUZZLE_8_FLOOR 0x48
#define MB_SIDEWAYS_STAIRS_RIGHT_SIDE 0x49
#define MB_SIDEWAYS_STAIRS_LEFT_SIDE 0x4A
#define MB_SIDEWAYS_STAIRS_RIGHT_SIDE_TOP 0x4B
#define MB_SIDEWAYS_STAIRS_LEFT_SIDE_TOP 0x4C
#define MB_SIDEWAYS_STAIRS_RIGHT_SIDE_BOTTOM 0x4D
#define MB_SIDEWAYS_STAIRS_LEFT_SIDE_BOTTOM 0x4E
//
#define MB_EASTWARD_CURRENT 0x50
#define MB_WESTWARD_CURRENT 0x51

View File

@@ -206,6 +206,8 @@ u8 DoJumpSpecialSpriteMovement(struct Sprite *);
void TurnVirtualObject(u8 virtualObjId, u8 direction);
const u8 *GetObjectEventScriptPointerByObjectEventId(u8 objectEventId);
u8 GetFirstInactiveObjectEventId(void);
u8 GetObjectEventIdByLocalId(u8);
bool32 IsFollowerVisible(void);
u8 GetCollisionFlagsAtCoords(struct ObjectEvent * objectEvent, s16 x, s16 y, u8 direction);
void OverrideTemplateCoordsForObjectEvent(const struct ObjectEvent *objectEvent);
void CancelPlayerForcedMovement(void);

View File

@@ -57,12 +57,15 @@ void PlayerTurnInPlace(u8 direction);
void PlayerJumpLedge(u8 direction);
void PlayerShakeHeadOrWalkInPlace(void);
void player_step(u8 direction, u16 newKeys, u16 heldKeys);
bool32 PlayerIsMovingOnRockStairs(u8 direction);
bool32 ObjectMovingOnRockStairs(struct ObjectEvent *objectEvent, u8 direction);
void SetPlayerAvatarWatering(u8 direction);
u8 GetPlayerAvatarFlags(void);
void UpdatePlayerAvatarTransitionState(void);
void InitPlayerAvatar(s16 x, s16 y, u8 direction, u8 gender);
void PlayerUseAcroBikeOnBumpySlope(u8 direction);
u8 GetRSAvatarGraphicsIdByGender(u8 gender);
//sideways stairs
u8 GetRightSideStairsDirection(u8 direction);
u8 GetLeftSideStairsDirection(u8 direction);
#endif //GUARD_FIELD_PLAYER_AVATAR_H

View File

@@ -355,6 +355,8 @@ enum
COLLISION_ISOLATED_HORIZONTAL_RAIL,
COLLISION_VERTICAL_RAIL,
COLLISION_HORIZONTAL_RAIL,
COLLISION_SIDEWAYS_STAIRS_TO_RIGHT,
COLLISION_SIDEWAYS_STAIRS_TO_LEFT,
COLLISION_COUNT
};

View File

@@ -136,5 +136,13 @@ bool8 MetatileBehavior_IsTrainerTowerMonitor(u8 metatileBehavior);
bool8 MetatileBehavior_IsSecretBaseJumpMat(u8 metatileBehavior);
bool8 MetatileBehavior_IsSecretBaseSpinMat(u8 metatileBehavior);
bool8 MetatileBehavior_IsSnow(u8 metatileBehavior);
bool8 MetatileBehavior_IsSidewaysStairsRightSide(u8);
bool8 MetatileBehavior_IsSidewaysStairsLeftSide(u8);
bool8 MetatileBehavior_IsSidewaysStairsRightSideTop(u8 metatileBehavior);
bool8 MetatileBehavior_IsSidewaysStairsLeftSideTop(u8 metatileBehavior);
bool8 MetatileBehavior_IsSidewaysStairsRightSideBottom(u8 metatileBehavior);
bool8 MetatileBehavior_IsSidewaysStairsLeftSideBottom(u8 metatileBehavior);
bool8 MetatileBehavior_IsSidewaysStairsRightSideAny(u8 metatileBehavior);
bool8 MetatileBehavior_IsSidewaysStairsLeftSideAny(u8 metatileBehavior);
#endif // GUARD_METATILE_BEHAVIOR

View File

@@ -55,6 +55,19 @@ static u8 BikeInputHandler_Normal(u8 *direction_p, u16 newKeys, u16 heldKeys)
struct ObjectEvent *playerObjEvent = &gObjectEvents[gPlayerAvatar.objectEventId];
u8 direction = GetPlayerMovementDirection();
// fix direction when moving on sideways stairs
switch (direction)
{
case DIR_SOUTHWEST:
case DIR_NORTHWEST:
direction = DIR_WEST;
break;
case DIR_SOUTHEAST:
case DIR_NORTHEAST:
direction = DIR_EAST;
break;
}
gPlayerAvatar.bikeFrameCounter = 0;
if (MetatileBehavior_IsCyclingRoadPullDownTile(playerObjEvent->currentMetatileBehavior) == TRUE)
{
@@ -188,7 +201,7 @@ static void BikeTransition_MoveDirection(u8 direction)
if (collision == COLLISION_COUNT)
PlayerWalkFast(direction);
else if (PlayerIsMovingOnRockStairs(direction))
else if (ObjectMovingOnRockStairs(playerObjEvent, direction))
PlayerWalkFast(direction);
else
PlayerRideWaterCurrent(direction);

File diff suppressed because it is too large Load Diff

View File

@@ -464,8 +464,39 @@ static const u8 *GetInteractedObjectEventScript(struct MapPosition *position, u8
{
u8 objectEventId;
const u8 *script;
s16 currX = gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.x;
s16 currY = gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.y;
u8 currBehavior = MapGridGetMetatileBehaviorAt(currX, currY);
switch (direction)
{
case DIR_EAST:
if (MetatileBehavior_IsSidewaysStairsLeftSideAny(metatileBehavior))
// sideways stairs left-side to your right -> check northeast
objectEventId = GetObjectEventIdByPosition(currX + 1, currY - 1, position->elevation);
else if (MetatileBehavior_IsSidewaysStairsRightSideAny(currBehavior))
// on top of right-side stairs -> check southeast
objectEventId = GetObjectEventIdByPosition(currX + 1, currY + 1, position->elevation);
else
// check in front of player
objectEventId = GetObjectEventIdByPosition(position->x, position->y, position->elevation);
break;
case DIR_WEST:
if (MetatileBehavior_IsSidewaysStairsRightSideAny(metatileBehavior))
// facing sideways stairs right side -> check northwest
objectEventId = GetObjectEventIdByPosition(currX - 1, currY - 1, position->elevation);
else if (MetatileBehavior_IsSidewaysStairsLeftSideAny(currBehavior))
// on top of left-side stairs -> check southwest
objectEventId = GetObjectEventIdByPosition(currX - 1, currY + 1, position->elevation);
else
// check in front of player
objectEventId = GetObjectEventIdByPosition(position->x, position->y, position->elevation);
break;
default:
objectEventId = GetObjectEventIdByPosition(position->x, position->y, position->elevation);
break;
}
objectEventId = GetObjectEventIdByPosition(position->x, position->y, position->elevation);
if (objectEventId == OBJECT_EVENTS_COUNT || gObjectEvents[objectEventId].localId == OBJ_EVENT_ID_PLAYER)
{
if (MetatileBehavior_IsCounter(metatileBehavior) != TRUE)

View File

@@ -308,7 +308,22 @@ static bool8 ForcedMovement_None(void)
static u8 DoForcedMovement(u8 direction, MovementAction movementAction)
{
struct PlayerAvatar *playerAvatar = &gPlayerAvatar;
u8 collision = CheckForPlayerAvatarCollision(direction);
u8 collision;
// Check for sideways stairs onto ice movement.
switch (direction)
{
case DIR_NORTHWEST:
case DIR_SOUTHWEST:
direction = DIR_WEST;
break;
case DIR_NORTHEAST:
case DIR_SOUTHEAST:
direction = DIR_EAST;
break;
}
collision = CheckForPlayerAvatarCollision(direction);
playerAvatar->flags |= PLAYER_AVATAR_FLAG_FORCED;
if (collision)
@@ -541,7 +556,7 @@ static void PlayerNotOnBikeMoving(u8 direction, u16 heldKeys)
if ((heldKeys & B_BUTTON) && FlagGet(FLAG_SYS_B_DASH)
&& !IsRunningDisallowed(gObjectEvents[gPlayerAvatar.objectEventId].currentMetatileBehavior))
{
if (PlayerIsMovingOnRockStairs(direction))
if (ObjectMovingOnRockStairs(&gObjectEvents[gPlayerAvatar.objectEventId], direction))
PlayerRunSlow(direction);
else
PlayerRun(direction);
@@ -555,21 +570,25 @@ static void PlayerNotOnBikeMoving(u8 direction, u16 heldKeys)
}
else
{
if (PlayerIsMovingOnRockStairs(direction))
if (ObjectMovingOnRockStairs(&gObjectEvents[gPlayerAvatar.objectEventId], direction))
PlayerWalkSlow(direction);
else
PlayerWalkNormal(direction);
}
}
bool32 PlayerIsMovingOnRockStairs(u8 direction)
bool32 ObjectMovingOnRockStairs(struct ObjectEvent *objectEvent, u8 direction)
{
struct ObjectEvent * objectEvent;
#if SLOW_MOVEMENT_ON_STAIRS == TRUE
s16 x, y;
objectEvent = &gObjectEvents[gPlayerAvatar.objectEventId];
x = objectEvent->currentCoords.x;
y = objectEvent->currentCoords.y;
// TODO followers on sideways stairs
if (IsFollowerVisible() && GetFollowerObject() != NULL && (objectEvent->isPlayer || objectEvent->localId == OBJ_EVENT_ID_FOLLOWER))
return FALSE;
switch (direction)
{
case DIR_NORTH:
@@ -577,9 +596,21 @@ bool32 PlayerIsMovingOnRockStairs(u8 direction)
case DIR_SOUTH:
MoveCoords(DIR_SOUTH, &x, &y);
return MetatileBehavior_IsRockStairs(MapGridGetMetatileBehaviorAt(x, y));
case DIR_WEST:
case DIR_EAST:
case DIR_NORTHEAST:
case DIR_NORTHWEST:
case DIR_SOUTHWEST:
case DIR_SOUTHEAST:
// directionOverwrite is only used for sideways stairs motion
if (objectEvent->directionOverwrite)
return TRUE;
default:
return FALSE;
}
#else
return FALSE;
#endif
}
static u8 CheckForPlayerAvatarCollision(u8 direction)
@@ -2385,3 +2416,34 @@ static u8 TeleportAnim_RotatePlayer(struct ObjectEvent *object, s16 *a1)
#undef tDeltaY
#undef tRotationTimer
#undef tState
//sideways stairs
u8 GetRightSideStairsDirection(u8 direction)
{
switch (direction)
{
case DIR_WEST:
return DIR_NORTHWEST;
case DIR_EAST:
return DIR_SOUTHEAST;
default:
if (direction > DIR_EAST)
direction -= DIR_EAST;
return direction;
}
}
u8 GetLeftSideStairsDirection(u8 direction)
{
switch (direction)
{
case DIR_WEST:
return DIR_SOUTHWEST;
case DIR_EAST:
return DIR_NORTHEAST;
default:
if (direction > DIR_EAST)
direction -= DIR_EAST;
return direction;
}
}

View File

@@ -1042,3 +1042,71 @@ bool8 MetatileBehavior_IsTrainerTowerMonitor(u8 metatileBehavior)
else
return FALSE;
}
bool8 MetatileBehavior_IsSidewaysStairsRightSide(u8 metatileBehavior)
{
if (metatileBehavior == MB_SIDEWAYS_STAIRS_RIGHT_SIDE || metatileBehavior == MB_SIDEWAYS_STAIRS_RIGHT_SIDE_BOTTOM)
return TRUE;
else
return FALSE;
}
bool8 MetatileBehavior_IsSidewaysStairsLeftSide(u8 metatileBehavior)
{
if (metatileBehavior == MB_SIDEWAYS_STAIRS_LEFT_SIDE || metatileBehavior == MB_SIDEWAYS_STAIRS_LEFT_SIDE_BOTTOM)
return TRUE;
else
return FALSE;
}
bool8 MetatileBehavior_IsSidewaysStairsRightSideTop(u8 metatileBehavior)
{
if (metatileBehavior == MB_SIDEWAYS_STAIRS_RIGHT_SIDE_TOP)
return TRUE;
else
return FALSE;
}
bool8 MetatileBehavior_IsSidewaysStairsLeftSideTop(u8 metatileBehavior)
{
if (metatileBehavior == MB_SIDEWAYS_STAIRS_LEFT_SIDE_TOP)
return TRUE;
else
return FALSE;
}
bool8 MetatileBehavior_IsSidewaysStairsRightSideBottom(u8 metatileBehavior)
{
if (metatileBehavior == MB_SIDEWAYS_STAIRS_RIGHT_SIDE_BOTTOM)
return TRUE;
else
return FALSE;
}
bool8 MetatileBehavior_IsSidewaysStairsLeftSideBottom(u8 metatileBehavior)
{
if (metatileBehavior == MB_SIDEWAYS_STAIRS_LEFT_SIDE_BOTTOM)
return TRUE;
else
return FALSE;
}
bool8 MetatileBehavior_IsSidewaysStairsRightSideAny(u8 metatileBehavior)
{
if (metatileBehavior == MB_SIDEWAYS_STAIRS_RIGHT_SIDE
|| metatileBehavior == MB_SIDEWAYS_STAIRS_RIGHT_SIDE_BOTTOM
|| metatileBehavior == MB_SIDEWAYS_STAIRS_RIGHT_SIDE_TOP)
return TRUE;
else
return FALSE;
}
bool8 MetatileBehavior_IsSidewaysStairsLeftSideAny(u8 metatileBehavior)
{
if (metatileBehavior == MB_SIDEWAYS_STAIRS_LEFT_SIDE
|| metatileBehavior == MB_SIDEWAYS_STAIRS_LEFT_SIDE_BOTTOM
|| metatileBehavior == MB_SIDEWAYS_STAIRS_LEFT_SIDE_TOP)
return TRUE;
else
return FALSE;
}

View File

@@ -1251,6 +1251,21 @@ bool8 ScrCmd_fadeinbgm(struct ScriptContext * ctx)
return FALSE;
}
struct ObjectEvent *ScriptHideFollower(void)
{
struct ObjectEvent *obj = GetFollowerObject();
if (obj == NULL || obj->invisible)
return NULL;
ClearObjectEventMovement(obj, &gSprites[obj->spriteId]);
gSprites[obj->spriteId].animCmdIndex = 0; // Reset start frame of animation
// Note: ScriptMovement_ returns TRUE on error
if (ScriptMovement_StartObjectMovementScript(obj->localId, obj->mapGroup, obj->mapNum, EnterPokeballMovement))
return NULL;
return obj;
}
bool8 ScrCmd_applymovement(struct ScriptContext * ctx)
{
u16 localId = VarGet(ScriptReadHalfword(ctx));
@@ -1260,24 +1275,21 @@ bool8 ScrCmd_applymovement(struct ScriptContext * ctx)
Script_RequestEffects(SCREFF_V1 | SCREFF_HARDWARE);
// When applying script movements to follower, it may have frozen animation that must be cleared
if (localId == OBJ_EVENT_ID_FOLLOWER && (objEvent = GetFollowerObject()) && objEvent->frozen)
if ((localId == OBJ_EVENT_ID_FOLLOWER && (objEvent = GetFollowerObject()) && objEvent->frozen)
|| ((objEvent = &gObjectEvents[GetObjectEventIdByLocalId(localId)]) && IS_OW_MON_OBJ(objEvent)))
{
ClearObjectEventMovement(objEvent, &gSprites[objEvent->spriteId]);
gSprites[objEvent->spriteId].animCmdIndex = 0; // Reset start frame of animation
}
gObjectEvents[GetObjectEventIdByLocalId(localId)].directionOverwrite = DIR_NONE;
ScriptMovement_StartObjectMovementScript(localId, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, movementScript);
sMovingNpcId = localId;
objEvent = GetFollowerObject();
// Force follower into pokeball
if (localId != OBJ_EVENT_ID_FOLLOWER
&& !FlagGet(FLAG_SAFE_FOLLOWER_MOVEMENT)
&& (movementScript < Common_Movement_FollowerSafeStart || movementScript > Common_Movement_FollowerSafeEnd)
&& (objEvent = GetFollowerObject())
&& !objEvent->invisible)
&& !FlagGet(FLAG_SAFE_FOLLOWER_MOVEMENT)
&& (movementScript < Common_Movement_FollowerSafeStart || movementScript > Common_Movement_FollowerSafeEnd))
{
ClearObjectEventMovement(objEvent, &gSprites[objEvent->spriteId]);
gSprites[objEvent->spriteId].animCmdIndex = 0; // Reset start frame of animation
ScriptMovement_StartObjectMovementScript(OBJ_EVENT_ID_FOLLOWER, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, EnterPokeballMovement);
ScriptHideFollower();
}
return FALSE;
}
@@ -1291,6 +1303,7 @@ bool8 ScrCmd_applymovementat(struct ScriptContext * ctx)
Script_RequestEffects(SCREFF_V1 | SCREFF_HARDWARE);
gObjectEvents[GetObjectEventIdByLocalId(localId)].directionOverwrite = DIR_NONE;
ScriptMovement_StartObjectMovementScript(localId, mapNum, mapGroup, movementScript);
sMovingNpcId = localId;
return FALSE;
@@ -2967,3 +2980,25 @@ void SetMovingNpcId(u16 npcId)
{
sMovingNpcId = npcId;
}
bool8 ScrFunc_hidefollower(struct ScriptContext *ctx)
{
bool16 wait = VarGet(ScriptReadHalfword(ctx));
struct ObjectEvent *obj;
if ((obj = ScriptHideFollower()) != NULL && wait)
{
sMovingNpcId = obj->localId;
sMovingNpcMapGroup = obj->mapGroup;
sMovingNpcMapNum = obj->mapNum;
SetupNativeScript(ctx, WaitForMovementFinish);
}
// Just in case, prevent `applymovement`
// from hiding the follower again
if (obj)
FlagSet(FLAG_SAFE_FOLLOWER_MOVEMENT);
// execute next script command with no delay
return TRUE;
}