diff --git a/include/constants/metatile_behaviors.h b/include/constants/metatile_behaviors.h index 1f6d4e87cb..046493dd77 100755 --- a/include/constants/metatile_behaviors.h +++ b/include/constants/metatile_behaviors.h @@ -236,10 +236,10 @@ #define MB_WIRELESS_BOX_RESULTS 0xE8 #define MB_TRAINER_HILL_TIMER 0xE9 #define MB_SKY_PILLAR_CLOSED_DOOR 0xEA -#define MB_UNUSED_EB 0xEB -#define MB_UNUSED_EC 0xEC -#define MB_UNUSED_ED 0xED -#define MB_UNUSED_EE 0xEE +#define MB_UP_RIGHT_STAIR_WARP 0xEB +#define MB_UP_LEFT_STAIR_WARP 0xEC +#define MB_DOWN_RIGHT_STAIR_WARP 0xED +#define MB_DOWN_LEFT_STAIR_WARP 0xEE #define MB_UNUSED_EF 0xEF #define NUM_METATILE_BEHAVIORS 0xF0 diff --git a/include/field_screen_effect.h b/include/field_screen_effect.h index 9b3aa6229e..b9a2ef9d5b 100644 --- a/include/field_screen_effect.h +++ b/include/field_screen_effect.h @@ -42,5 +42,7 @@ void DoOrbEffect(void); void FadeOutOrbEffect(void); void WriteFlashScanlineEffectBuffer(u8 flashLevel); bool8 IsPlayerStandingStill(void); +void DoStairWarp(u16 metatileBehavior, u16 delay); +bool8 IsDirectionalStairWarpMetatileBehavior(u16 metatileBehavior, u8 playerDirection); #endif // GUARD_FIELD_SCREEN_EFFECT_H diff --git a/include/global.fieldmap.h b/include/global.fieldmap.h index 29249712b6..5fe34f1381 100644 --- a/include/global.fieldmap.h +++ b/include/global.fieldmap.h @@ -297,6 +297,7 @@ enum COLLISION_ISOLATED_HORIZONTAL_RAIL, COLLISION_VERTICAL_RAIL, COLLISION_HORIZONTAL_RAIL, + COLLISION_STAIR_WARP, }; // player running states diff --git a/include/metatile_behavior.h b/include/metatile_behavior.h index c2d0d23ee3..9cae091472 100644 --- a/include/metatile_behavior.h +++ b/include/metatile_behavior.h @@ -148,5 +148,10 @@ bool8 MetatileBehavior_IsQuestionnaire(u8); bool8 MetatileBehavior_IsLongGrass_Duplicate(u8); bool8 MetatileBehavior_IsLongGrassSouthEdge(u8); bool8 MetatileBehavior_IsTrainerHillTimer(u8); +bool8 MetatileBehavior_IsDirectionalUpRightStairWarp(u8 metatileBehavior); +bool8 MetatileBehavior_IsDirectionalUpLeftStairWarp(u8 metatileBehavior); +bool8 MetatileBehavior_IsDirectionalDownRightStairWarp(u8 metatileBehavior); +bool8 MetatileBehavior_IsDirectionalDownLeftStairWarp(u8 metatileBehavior); +bool8 MetatileBehavior_IsDirectionalStairWarp(u8 metatileBehavior); #endif // GUARD_METATILE_BEHAVIOR_H diff --git a/include/overworld.h b/include/overworld.h index bda2046ec7..8fb3630d2b 100644 --- a/include/overworld.h +++ b/include/overworld.h @@ -51,6 +51,7 @@ extern void (*gFieldCallback)(void); extern bool8 (*gFieldCallback2)(void); extern u8 gLocalLinkPlayerId; extern u8 gFieldLinkPlayerCount; +extern bool8 gExitStairsMovementDisabled; extern const struct UCoords32 gDirectionToVectors[]; diff --git a/src/field_control_avatar.c b/src/field_control_avatar.c index 582617d555..58afd16310 100644 --- a/src/field_control_avatar.c +++ b/src/field_control_avatar.c @@ -730,13 +730,31 @@ static bool8 CheckStandardWildEncounter(u16 metatileBehavior) static bool8 TryArrowWarp(struct MapPosition *position, u16 metatileBehavior, u8 direction) { s8 warpEventId = GetWarpEventAtMapPosition(&gMapHeader, position); + u16 delay; - if (IsArrowWarpMetatileBehavior(metatileBehavior, direction) == TRUE && warpEventId != WARP_ID_NONE) + if (warpEventId != WARP_ID_NONE) { - StoreInitialPlayerAvatarState(); - SetupWarp(&gMapHeader, warpEventId, position); - DoWarp(); - return TRUE; + if (IsArrowWarpMetatileBehavior(metatileBehavior, direction) == TRUE) + { + StoreInitialPlayerAvatarState(); + SetupWarp(&gMapHeader, warpEventId, position); + DoWarp(); + return TRUE; + } + else if (IsDirectionalStairWarpMetatileBehavior(metatileBehavior, direction) == TRUE) + { + delay = 0; + if (gPlayerAvatar.flags & (PLAYER_AVATAR_FLAG_MACH_BIKE | PLAYER_AVATAR_FLAG_ACRO_BIKE)) + { + SetPlayerAvatarTransitionFlags(PLAYER_AVATAR_FLAG_ON_FOOT); + delay = 12; + } + + StoreInitialPlayerAvatarState(); + SetupWarp(&gMapHeader, warpEventId, position); + DoStairWarp(metatileBehavior, delay); + return TRUE; + } } return FALSE; } diff --git a/src/field_player_avatar.c b/src/field_player_avatar.c index 0b30a1d7bb..252fe42113 100644 --- a/src/field_player_avatar.c +++ b/src/field_player_avatar.c @@ -6,6 +6,7 @@ #include "field_camera.h" #include "field_effect.h" #include "field_effect_helpers.h" +#include "field_screen_effect.h" #include "field_player_avatar.h" #include "fieldmap.h" #include "menu.h" @@ -634,6 +635,10 @@ static void PlayerNotOnBikeMoving(u8 direction, u16 heldKeys) PlayerNotOnBikeCollideWithFarawayIslandMew(direction); return; } + else if (collision == COLLISION_STAIR_WARP) + { + PlayerFaceDirection(direction); + } else { u8 adjustedCollision = collision - COLLISION_STOP_SURFING; @@ -670,6 +675,9 @@ static u8 CheckForPlayerAvatarCollision(u8 direction) x = playerObjEvent->currentCoords.x; y = playerObjEvent->currentCoords.y; + if (IsDirectionalStairWarpMetatileBehavior(MapGridGetMetatileBehaviorAt(x, y), direction)) + return COLLISION_STAIR_WARP; + MoveCoords(direction, &x, &y); return CheckForObjectEventCollision(playerObjEvent, x, y, direction, MapGridGetMetatileBehaviorAt(x, y)); } diff --git a/src/field_screen_effect.c b/src/field_screen_effect.c index e39a0c6c1f..0cce757f56 100644 --- a/src/field_screen_effect.c +++ b/src/field_screen_effect.c @@ -50,6 +50,7 @@ static void Task_SpinEnterWarp(u8 taskId); static void Task_WarpAndLoadMap(u8 taskId); static void Task_DoDoorWarp(u8 taskId); static void Task_EnableScriptAfterMusicFade(u8 taskId); +static void Task_ExitStairs(u8 taskId); // data[0] is used universally by tasks in this file as a state for switches #define tState data[0] @@ -268,10 +269,14 @@ static void SetUpWarpExitTask(void) behavior = MapGridGetMetatileBehaviorAt(x, y); if (MetatileBehavior_IsDoor(behavior) == TRUE) func = Task_ExitDoor; + else if (MetatileBehavior_IsDirectionalStairWarp(behavior) == TRUE && !gExitStairsMovementDisabled) + func = Task_ExitStairs; else if (MetatileBehavior_IsNonAnimDoor(behavior) == TRUE) func = Task_ExitNonAnimDoor; else func = Task_ExitNonDoor; + + gExitStairsMovementDisabled = FALSE; CreateTask(func, 10); } @@ -1304,20 +1309,143 @@ static bool32 PrintWhiteOutRecoveryMessage(u8 taskId, const u8 *text, u32 x, u32 switch (gTasks[taskId].tPrintState) { + case 0: + FillWindowPixelBuffer(windowId, PIXEL_FILL(0)); + StringExpandPlaceholders(gStringVar4, text); + AddTextPrinterParameterized4(windowId, FONT_NORMAL, x, y, 1, 0, sWhiteoutTextColors, 1, gStringVar4); + gTextFlags.canABSpeedUpPrint = FALSE; + gTasks[taskId].tPrintState = 1; + break; + case 1: + RunTextPrinters(); + if (!IsTextPrinterActive(windowId)) + { + gTasks[taskId].tPrintState = 0; + return TRUE; + } + break; + } + return FALSE; +} + +static void GetStairsMovementDirection(u8 a0, s16 *a1, s16 *a2) +{ + if (MetatileBehavior_IsDirectionalUpRightStairWarp(a0)) + { + *a1 = 16; + *a2 = -10; + } + else if (MetatileBehavior_IsDirectionalUpLeftStairWarp(a0)) + { + *a1 = -17; + *a2 = -10; + } + else if (MetatileBehavior_IsDirectionalDownRightStairWarp(a0)) + { + *a1 = 17; + *a2 = 3; + } + else if (MetatileBehavior_IsDirectionalDownLeftStairWarp(a0)) + { + *a1 = -17; + *a2 = 3; + } + else + { + *a1 = 0; + *a2 = 0; + } +} + +static bool8 WaitStairExitMovementFinished(s16 *a0, s16 *a1, s16 *a2, s16 *a3, s16 *a4) +{ + struct Sprite *sprite; + sprite = &gSprites[gPlayerAvatar.spriteId]; + if (*a4 != 0) + { + *a2 += *a0; + *a3 += *a1; + sprite->x2 = *a2 >> 5; + sprite->y2 = *a3 >> 5; + (*a4)--; + return TRUE; + } + else + { + sprite->x2 = 0; + sprite->y2 = 0; + return FALSE; + } +} + +static void ExitStairsMovement(s16 *a0, s16 *a1, s16 *a2, s16 *a3, s16 *a4) +{ + s16 x, y; + u8 behavior; + s32 r1; + struct Sprite *sprite; + + PlayerGetDestCoords(&x, &y); + behavior = MapGridGetMetatileBehaviorAt(x, y); + if (MetatileBehavior_IsDirectionalDownRightStairWarp(behavior) || MetatileBehavior_IsDirectionalUpRightStairWarp(behavior)) + r1 = 3; + else + r1 = 4; + + ObjectEventForceSetHeldMovement(&gObjectEvents[gPlayerAvatar.objectEventId], GetWalkInPlaceSlowMovementAction(r1)); + GetStairsMovementDirection(behavior, a0, a1); + *a2 = *a0 * 16; + *a3 = *a1 * 16; + *a4 = 16; + sprite = &gSprites[gPlayerAvatar.spriteId]; + sprite->x2 = *a2 >> 5; + sprite->y2 = *a3 >> 5; + *a0 *= -1; + *a1 *= -1; +} + +static void Task_ExitStairs(u8 taskId) +{ + s16 * data = gTasks[taskId].data; + switch (data[0]) + { + default: + if (WaitForWeatherFadeIn() == TRUE) + { + CameraObjectReset(); + UnlockPlayerFieldControls(); + DestroyTask(taskId); + } + break; case 0: - FillWindowPixelBuffer(windowId, PIXEL_FILL(0)); - StringExpandPlaceholders(gStringVar4, text); - AddTextPrinterParameterized4(windowId, FONT_NORMAL, x, y, 1, 0, sWhiteoutTextColors, 1, gStringVar4); - gTextFlags.canABSpeedUpPrint = FALSE; - gTasks[taskId].tPrintState = 1; + Overworld_PlaySpecialMapMusic(); + WarpFadeInScreen(); + LockPlayerFieldControls(); + ExitStairsMovement(&data[1], &data[2], &data[3], &data[4], &data[5]); + data[0]++; break; case 1: - RunTextPrinters(); - if (!IsTextPrinterActive(windowId)) - { - gTasks[taskId].tPrintState = 0; + if (!WaitStairExitMovementFinished(&data[1], &data[2], &data[3], &data[4], &data[5])) + data[0]++; + break; + } +} + +bool8 IsDirectionalStairWarpMetatileBehavior(u16 metatileBehavior, u8 playerDirection) +{ + switch (playerDirection) + { + case DIR_WEST: + if (MetatileBehavior_IsDirectionalUpLeftStairWarp(metatileBehavior)) + return TRUE; + if (MetatileBehavior_IsDirectionalDownLeftStairWarp(metatileBehavior)) + return TRUE; + break; + case DIR_EAST: + if (MetatileBehavior_IsDirectionalUpRightStairWarp(metatileBehavior)) + return TRUE; + if (MetatileBehavior_IsDirectionalDownRightStairWarp(metatileBehavior)) return TRUE; - } break; } return FALSE; @@ -1388,3 +1516,87 @@ void FieldCB_RushInjuredPokemonToCenter(void) taskId = CreateTask(Task_RushInjuredPokemonToCenter, 10); gTasks[taskId].tState = FRLG_WHITEOUT_ENTER_MSG_SCREEN; } + +static void ForceStairsMovement(u16 a0, s16 *a1, s16 *a2) +{ + ObjectEventForceSetHeldMovement(&gObjectEvents[gPlayerAvatar.objectEventId], GetWalkInPlaceNormalMovementAction(GetPlayerFacingDirection())); + GetStairsMovementDirection(a0, a1, a2); +} + +static void UpdateStairsMovement(s16 a0, s16 a1, s16 *a2, s16 *a3, s16 *a4) +{ + struct Sprite *playerSpr = &gSprites[gPlayerAvatar.spriteId]; + struct ObjectEvent *playerObj = &gObjectEvents[gPlayerAvatar.objectEventId]; + + if (a1 > 0 || *a4 > 6) + *a3 += a1; + + *a2 += a0; + (*a4)++; + playerSpr->x2 = *a2 >> 5; + playerSpr->y2 = *a3 >> 5; + if (playerObj->heldMovementFinished) + ObjectEventForceSetHeldMovement(playerObj, GetWalkInPlaceNormalMovementAction(GetPlayerFacingDirection())); +} + +static void Task_StairWarp(u8 taskId) +{ + s16 * data = gTasks[taskId].data; + struct ObjectEvent *playerObj = &gObjectEvents[gPlayerAvatar.objectEventId]; + struct Sprite *playerSpr = &gSprites[gPlayerAvatar.spriteId]; + + switch (data[0]) + { + case 0: + LockPlayerFieldControls(); + FreezeObjectEvents(); + CameraObjectFreeze(); + data[0]++; + break; + case 1: + if (!ObjectEventIsMovementOverridden(playerObj) || ObjectEventClearHeldMovementIfFinished(playerObj)) + { + if (data[15] != 0) + data[15]--; + else + { + TryFadeOutOldMapMusic(); + PlayRainStoppingSoundEffect(); + playerSpr->oam.priority = 1; + ForceStairsMovement(data[1], &data[2], &data[3]); + PlaySE(SE_EXIT); + data[0]++; + } + } + break; + case 2: + UpdateStairsMovement(data[2], data[3], &data[4], &data[5], &data[6]); + data[15]++; + if (data[15] >= 12) + { + WarpFadeOutScreen(); + data[0]++; + } + break; + case 3: + UpdateStairsMovement(data[2], data[3], &data[4], &data[5], &data[6]); + if (!PaletteFadeActive() && BGMusicStopped()) + data[0]++; + break; + default: + gFieldCallback = FieldCB_DefaultWarpExit; + WarpIntoMap(); + SetMainCallback2(CB2_LoadMap); + DestroyTask(taskId); + break; + } +} + +void DoStairWarp(u16 metatileBehavior, u16 delay) +{ + u8 taskId = CreateTask(Task_StairWarp, 10); + gTasks[taskId].data[1] = metatileBehavior; + gTasks[taskId].data[15] = delay; + Task_StairWarp(taskId); +} + diff --git a/src/metatile_behavior.c b/src/metatile_behavior.c index 924428aea4..f5e438042a 100644 --- a/src/metatile_behavior.c +++ b/src/metatile_behavior.c @@ -1400,3 +1400,43 @@ bool8 MetatileBehavior_IsTrainerHillTimer(u8 metatileBehavior) else return FALSE; } + +bool8 MetatileBehavior_IsDirectionalUpRightStairWarp(u8 metatileBehavior) +{ + if(metatileBehavior == MB_UP_RIGHT_STAIR_WARP) + return TRUE; + else + return FALSE; +} + +bool8 MetatileBehavior_IsDirectionalUpLeftStairWarp(u8 metatileBehavior) +{ + if (metatileBehavior == MB_UP_LEFT_STAIR_WARP) + return TRUE; + else + return FALSE; +} + +bool8 MetatileBehavior_IsDirectionalDownRightStairWarp(u8 metatileBehavior) +{ + if (metatileBehavior == MB_DOWN_RIGHT_STAIR_WARP) + return TRUE; + else + return FALSE; +} + +bool8 MetatileBehavior_IsDirectionalDownLeftStairWarp(u8 metatileBehavior) +{ + if (metatileBehavior == MB_DOWN_LEFT_STAIR_WARP) + return TRUE; + else + return FALSE; +} + +bool8 MetatileBehavior_IsDirectionalStairWarp(u8 metatileBehavior) +{ + if (metatileBehavior >= MB_UP_RIGHT_STAIR_WARP && metatileBehavior <= MB_DOWN_LEFT_STAIR_WARP) + return TRUE; + else + return FALSE; +} diff --git a/src/overworld.c b/src/overworld.c index babdfad7b6..2564ee5d62 100644 --- a/src/overworld.c +++ b/src/overworld.c @@ -201,6 +201,7 @@ EWRAM_DATA static struct InitialPlayerAvatarState sInitialPlayerAvatarState = {0 EWRAM_DATA static u16 sAmbientCrySpecies = 0; EWRAM_DATA static bool8 sIsAmbientCryWaterMon = FALSE; EWRAM_DATA struct LinkPlayerObjectEvent gLinkPlayerObjectEvents[4] = {0}; +EWRAM_DATA bool8 gExitStairsMovementDisabled = FALSE; static const struct WarpData sDummyWarpData = { @@ -1000,6 +1001,10 @@ static u8 GetAdjustedInitialDirection(struct InitialPlayerAvatarState *playerStr return DIR_EAST; else if (MetatileBehavior_IsEastArrowWarp(metatileBehavior) == TRUE) return DIR_WEST; + else if (MetatileBehavior_IsDirectionalUpRightStairWarp(metatileBehavior) == TRUE || MetatileBehavior_IsDirectionalDownRightStairWarp(metatileBehavior) == TRUE) + return DIR_WEST; + else if (MetatileBehavior_IsDirectionalUpLeftStairWarp(metatileBehavior) == TRUE || MetatileBehavior_IsDirectionalDownLeftStairWarp(metatileBehavior) == TRUE) + return DIR_EAST; else if ((playerStruct->transitionFlags == PLAYER_AVATAR_FLAG_UNDERWATER && transitionFlags == PLAYER_AVATAR_FLAG_SURFING) || (playerStruct->transitionFlags == PLAYER_AVATAR_FLAG_SURFING && transitionFlags == PLAYER_AVATAR_FLAG_UNDERWATER)) return playerStruct->direction; @@ -1799,6 +1804,7 @@ void CB2_ContinueSavedGame(void) PlayTimeCounter_Start(); ScriptContext_Init(); UnlockPlayerFieldControls(); + gExitStairsMovementDisabled = TRUE; InitMatchCallCounters(); if (UseContinueGameWarp() == TRUE) { @@ -1884,6 +1890,7 @@ static bool32 LoadMapInStepsLink(u8 *state) (*state)++; break; case 1: + gExitStairsMovementDisabled = FALSE; LoadMapFromWarp(TRUE); (*state)++; break;