diff options
author | ghoulslash <41651341+ghoulslash@users.noreply.github.com> | 2020-07-20 12:31:45 -0600 |
---|---|---|
committer | ghoulslash <41651341+ghoulslash@users.noreply.github.com> | 2020-07-20 12:31:45 -0600 |
commit | 2e5fc4069c27e42c020a35096e069319bbeb6c9f (patch) | |
tree | fcf9858d94848dbe3f883e4d83137b355447671b | |
parent | 6775505cbd8203a021cf01033db39f4d42df7328 (diff) |
Created Stair Warps (markdown)
-rw-r--r-- | Stair-Warps.md | 392 |
1 files changed, 392 insertions, 0 deletions
diff --git a/Stair-Warps.md b/Stair-Warps.md new file mode 100644 index 0000000..a890ef9 --- /dev/null +++ b/Stair-Warps.md @@ -0,0 +1,392 @@ +## Stair Warps + +Credit to ghoulslash. The easy implementation is to pull from the [repo](https://github.com/ghoulslash/pokeemerald/tree/stair_warps) + +FRLG include a stair warp effect where the player walks up/down stairs at the beginning/end of the warp sequence. This ports that feature from FRLG to emerald: + +<a href="https://imgur.com/q9SzXmW"><img src="https://i.imgur.com/q9SzXmW.gif" title="source: imgur.com" /></a> + +### Add new metatile behaviors +**1.** First, open [include/constants/metatile_behaviors.h](../blob/master/include/constants/metatile_behaviors.h). Replace any 4 unused metatile behaviors (I chose MB_UNUSED_EB through MB_UNUSED_EE): +```c +#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 +``` + +**2.** Next, open [src/metatile_behavior.c](../blob/master/src/metatile_behavior.c). Replace the elements in `sTileBitAttributes` for your metatile behaviours: +```c + [MB_UP_RIGHT_STAIR_WARP] = TILE_ATTRIBUTES(FALSE, FALSE, FALSE), + [MB_UP_LEFT_STAIR_WARP] = TILE_ATTRIBUTES(FALSE, FALSE, FALSE), + [MB_DOWN_RIGHT_STAIR_WARP] = TILE_ATTRIBUTES(FALSE, FALSE, FALSE), + [MB_DOWN_LEFT_STAIR_WARP] = TILE_ATTRIBUTES(FALSE, FALSE, FALSE), +``` + +**3.** At the bottom of the file, add the following 5 functions: +```c +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; +} +``` + +### Globally define the metatile behavior functions +Open [include/metatile_behavior.h](../blob/master/include/metatile_behavior.h). Add the following to the bottom: +```c +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); +``` + +### Add a new stair warp collision +This allows us to ignore the impassable tiles when we try to warp on the stairs. Open [include/global.fieldmap.h](../blob/master/include/global.fieldmap.h). Add `COLLISION_STAIR_WARP` after the enum define `COLLISION_HORIZONTAL_RAIL,` + +### Add the collision exclusion +Open [src/field_player/avatar.c](../blob/master/src/field_player_avatar.c). + +**1.** First, let's add `#include "field_screen_effect.h"` to the top of the file. + +**2.** Next, find `static u8 CheckForPlayerAvatarCollision(u8 direction)`. Before the line, `MoveCoords(direction, &x, &y);`, add the following two lines: +```c + if (IsDirectionalStairWarpMetatileBehavior(MapGridGetMetatileBehaviorAt(x, y), direction)) + return COLLISION_STAIR_WARP; +``` + +**3.** Finally, find `static void PlayerNotOnBikeMoving(u8 direction, u16 heldKeys)`. Replace the `if (collision)` code block with: +```c + if (collision) + { + if (collision == COLLISION_LEDGE_JUMP) + { + PlayerJumpLedge(direction); + return; + } + else if (collision == COLLISION_OBJECT_EVENT && IsPlayerCollidingWithFarawayIslandMew(direction)) + { + PlayerNotOnBikeCollideWithFarawayIslandMew(direction); + return; + } + else if (collision == COLLISION_STAIR_WARP) + { + PlayerFaceDirection(direction); + } + else + { + u8 adjustedCollision = collision - COLLISION_STOP_SURFING; + if (adjustedCollision > 3) + PlayerNotOnBikeCollide(direction); + return; + } + } +``` + +### Add the warp arrow +Open [src/field_control_avatar.c](../blob/master/src/field_control_avatar.c) and find the function `TryArrowWarp`. Replace everything with the following: +```c +static bool8 TryArrowWarp(struct MapPosition *position, u16 metatileBehavior, u8 direction) +{ + s8 warpEventId = GetWarpEventAtMapPosition(&gMapHeader, position); + u16 delay; + + if (warpEventId != -1) + { + 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; +} +``` + +### Add `DoStairWarp` +This is the meat of the code implementation. Let's start by opening [src/field_screen_effect.h](../blob/master/src/field_screen_effect.c). + +**1.** Find the function `static void SetUpWarpExitTask(void)`. Before `else if (MetatileBehavior_IsNonAnimDoor(behavior) == TRUE)`, Add the following: +```c + else if (MetatileBehavior_IsDirectionalStairWarp(behavior) == TRUE) + func = Task_ExitStairs; +``` + +**2.** Also, add `static void Task_ExitStairs(u8 taskId);` to the top of the file underneath `static void Task_EnableScriptAfterMusicFade(u8 taskId);`. + +**3.** At the bottom of the file, let's add a bunch of functions: +```c +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->pos2.x = *a2 >> 5; + sprite->pos2.y = *a3 >> 5; + (*a4)--; + return TRUE; + } + else + { + sprite->pos2.x = 0; + sprite->pos2.y = 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->pos2.x = *a2 >> 5; + sprite->pos2.y = *a3 >> 5; + *a0 *= -1; + *a1 *= -1; +} + +static void Task_ExitStairs(u8 taskId) +{ + s16 * data = gTasks[taskId].data; + switch (data[0]) + { + default: + if (WaitForWeatherFadeIn() == TRUE) + { + CameraObjectReset1(); + ScriptContext2_Disable(); + DestroyTask(taskId); + } + break; + case 0: + Overworld_PlaySpecialMapMusic(); + WarpFadeInScreen(); + ScriptContext2_Enable(); + ExitStairsMovement(&data[1], &data[2], &data[3], &data[4], &data[5]); + data[0]++; + break; + case 1: + 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; +} + +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->pos2.x = *a2 >> 5; + playerSpr->pos2.y = *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: + ScriptContext2_Enable(); + FreezeObjectEvents(); + CameraObjectReset2(); + 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_KAIDAN); + 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); +} +``` + +### Globally define two stair warp functions +Open [include/field_screen_effect.h](../blob/master/include/field_screen_effect.h). At the bottom, add the following: +```c +void DoStairWarp(u16 metatileBehavior, u16 delay); +bool8 IsDirectionalStairWarpMetatileBehavior(u16 metatileBehavior, u8 playerDirection); +``` + + +### Adjust the player's movement direction on stair warps +Open [src/overworld.c](../blob/master/src/overworld.c) and find the function `GetAdjustedInitialDirection`. Before the line `else if ((playerStruct->transitionFlags == PLAYER_AVATAR_FLAG_UNDERWATER && (etc...)`, add the following: +```c + 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; +``` |