From 4056804575c064ff3f4e85ebbb877950b5cd167e Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Sun, 20 Oct 2024 03:15:38 -0400 Subject: [PATCH 01/11] feat: improved follower handling during scripted movements --- asm/macros/event.inc | 8 +++ data/scripts/pkmn_center_nurse.inc | 1 + include/constants/event_objects.h | 6 ++ include/constants/flags.h | 4 +- include/event_object_movement.h | 1 + src/event_object_movement.c | 88 ++++++++++++++++++------------ src/scrcmd.c | 48 ++++++++++++---- src/script.c | 3 + src/script_movement.c | 28 +++++++++- 9 files changed, 139 insertions(+), 48 deletions(-) diff --git a/asm/macros/event.inc b/asm/macros/event.inc index ef1ba30270..aa8ff5773d 100644 --- a/asm/macros/event.inc +++ b/asm/macros/event.inc @@ -1944,3 +1944,11 @@ setvar VAR_0x8006, \item special CreateEnemyEventMon .endm + + @ hide any follower pokemon if present, + @ putting them into their pokeball; + @ by default waits for their movement to finish + .macro hidefollower wait=1 + callfunc ScrFunc_hidefollower + .2byte \wait + .endm diff --git a/data/scripts/pkmn_center_nurse.inc b/data/scripts/pkmn_center_nurse.inc index 9c5afba8d6..b154d290b0 100644 --- a/data/scripts/pkmn_center_nurse.inc +++ b/data/scripts/pkmn_center_nurse.inc @@ -33,6 +33,7 @@ EventScript_PkmnCenterNurse_IllTakeYourPkmn2:: return EventScript_PkmnCenterNurse_TakeAndHealPkmn:: + hidefollower 0 applymovement VAR_0x800B, Movement_PkmnCenterNurse_Turn waitmovement 0 dofieldeffect FLDEFF_POKECENTER_HEAL diff --git a/include/constants/event_objects.h b/include/constants/event_objects.h index 59f34334cb..1d0f5a22e8 100644 --- a/include/constants/event_objects.h +++ b/include/constants/event_objects.h @@ -289,6 +289,12 @@ // instead of a normal pokeball #define OW_MON_POKEBALLS TRUE +// New/old handling for followers during scripts; +// TRUE: Script collisions hide follower, FLAG_SAFE_FOLLOWER_MOVEMENT on by default +// (scripted player movement moves follower too!) +// FALSE: Script collisions unhandled, FLAG_SAFE_FOLLOWER_MOVEMENT off by default +#define OW_MON_SCRIPT_MOVEMENT TRUE + #define SHADOW_SIZE_S 0 #define SHADOW_SIZE_M 1 #define SHADOW_SIZE_L 2 diff --git a/include/constants/flags.h b/include/constants/flags.h index f0d80c5498..161d39fcaf 100644 --- a/include/constants/flags.h +++ b/include/constants/flags.h @@ -1643,7 +1643,9 @@ #define FLAG_ENABLE_MULTI_CORRIDOR_DOOR (SPECIAL_FLAGS_START + 0x2) #define FLAG_SPECIAL_FLAG_UNUSED_0x4003 (SPECIAL_FLAGS_START + 0x3) // Unused Flag #define FLAG_STORING_ITEMS_IN_PYRAMID_BAG (SPECIAL_FLAGS_START + 0x4) -#define FLAG_SAFE_FOLLOWER_MOVEMENT (SPECIAL_FLAGS_START + 0x5) // When set, applymovement does not put the follower inside a pokeball +// When set, `applymovement` does not hide follower pokemon; +// Also, scripted movements on the player will move follower(s), too +#define FLAG_SAFE_FOLLOWER_MOVEMENT (SPECIAL_FLAGS_START + 0x5) // FLAG_SPECIAL_FLAG_0x4005 - 0x407F also exist and are unused #define SPECIAL_FLAGS_END (SPECIAL_FLAGS_START + 0x7F) #define NUM_SPECIAL_FLAGS (SPECIAL_FLAGS_END - SPECIAL_FLAGS_START + 1) diff --git a/include/event_object_movement.h b/include/event_object_movement.h index 6a1e2df766..45a3c6863a 100644 --- a/include/event_object_movement.h +++ b/include/event_object_movement.h @@ -161,6 +161,7 @@ u8 GetWalkInPlaceFastMovementAction(u32); u8 GetWalkInPlaceNormalMovementAction(u32); u8 GetWalkInPlaceSlowMovementAction(u32); u8 GetCollisionAtCoords(struct ObjectEvent *, s16 x, s16 y, u32 dir); +u32 GetObjectObjectCollidesWith(struct ObjectEvent *objectEvent, s16 x, s16 y, bool32 addCoords); void MoveCoords(u8 direction, s16 *x, s16 *y); bool8 ObjectEventIsHeldMovementActive(struct ObjectEvent *); u8 ObjectEventClearHeldMovementIfFinished(struct ObjectEvent *); diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 436f048a5a..26127caab9 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -5222,9 +5222,7 @@ bool8 FollowablePlayerMovement_Step(struct ObjectEvent *objectEvent, struct Spri s16 y; s16 targetX; s16 targetY; - #ifdef MB_SIDEWAYS_STAIRS_RIGHT_SIDE u32 playerAction = gObjectEvents[gPlayerAvatar.objectEventId].movementActionId; - #endif targetX = gObjectEvents[gPlayerAvatar.objectEventId].previousCoords.x; targetY = gObjectEvents[gPlayerAvatar.objectEventId].previousCoords.y; @@ -5240,8 +5238,8 @@ bool8 FollowablePlayerMovement_Step(struct ObjectEvent *objectEvent, struct Spri if (objectEvent->invisible) { // Animate exiting pokeball - // Player is jumping, but follower is invisible - if (PlayerGetCopyableMovement() == COPY_MOVE_JUMP2) { + // don't emerge if player is jumping or moving via script + if (PlayerGetCopyableMovement() == COPY_MOVE_JUMP2 || ArePlayerFieldControlsLocked()) { sprite->sTypeFuncId = 0; // return to shadowing state return FALSE; } @@ -5262,43 +5260,25 @@ bool8 FollowablePlayerMovement_Step(struct ObjectEvent *objectEvent, struct Spri MoveCoords(direction, &x, &y); #ifdef MB_SIDEWAYS_STAIRS_RIGHT_SIDE // https://github.com/ghoulslash/pokeemerald/tree/sideways_stairs GetCollisionAtCoords(objectEvent, x, y, direction); // Sets directionOverwrite for stairs - if (GetLedgeJumpDirection(x, y, direction) != DIR_NONE) { + #endif + if (GetLedgeJumpDirection(x, y, direction) != DIR_NONE) // InitJumpRegular will set the proper speed ObjectEventSetSingleMovement(objectEvent, sprite, GetJump2MovementAction(direction)); - } else if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_DASH)) { - // Set follow speed according to player's speed - if (playerAction >= MOVEMENT_ACTION_RUN_DOWN_SLOW && playerAction <= MOVEMENT_ACTION_RUN_RIGHT_SLOW) + else if (playerAction >= MOVEMENT_ACTION_WALK_SLOW_DOWN && playerAction <= MOVEMENT_ACTION_WALK_SLOW_RIGHT) { + if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_DASH)) // on sideways stairs objectEvent->movementActionId = GetWalkNormalMovementAction(direction); else - objectEvent->movementActionId = GetWalkFastMovementAction(direction); - } else if (PlayerGetCopyableMovement() == COPY_MOVE_JUMP2) { - ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkSlowMovementAction(direction)); - } else { - if (playerAction >= MOVEMENT_ACTION_WALK_SLOW_DOWN && playerAction <= MOVEMENT_ACTION_WALK_SLOW_RIGHT) { ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkSlowMovementAction(direction)); - } else { - objectEvent->movementActionId = GetWalkNormalMovementAction(direction); - if (OW_MON_BOBBING == TRUE) - sprite->y2 = -1; - } - } - sprite->sActionFuncId = 0; - #else - if (GetLedgeJumpDirection(x, y, direction) != DIR_NONE) { - // InitJumpRegular will set the proper speed - ObjectEventSetSingleMovement(objectEvent, sprite, GetJump2MovementAction(direction)); - } else if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_DASH)) { - // Set follow speed according to player's speed - ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkFastMovementAction(direction)); } else if (PlayerGetCopyableMovement() == COPY_MOVE_JUMP2) { - // If *player* jumps, make step take twice as long ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkSlowMovementAction(direction)); + } else if (gSprites[gPlayerAvatar.spriteId].data[4] == MOVE_SPEED_FAST_1) { + objectEvent->movementActionId = GetWalkFastMovementAction(direction); } else { - ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkNormalMovementAction(direction)); + objectEvent->movementActionId = GetWalkNormalMovementAction(direction); if (OW_MON_BOBBING == TRUE) sprite->y2 = -1; } - #endif + sprite->sActionFuncId = 0; objectEvent->singleMovementActive = 1; sprite->sTypeFuncId = 2; return TRUE; @@ -5823,13 +5803,17 @@ static bool8 IsMetatileDirectionallyImpassable(struct ObjectEvent *objectEvent, return FALSE; } -static bool8 DoesObjectCollideWithObjectAt(struct ObjectEvent *objectEvent, s16 x, s16 y) -{ +u32 GetObjectObjectCollidesWith(struct ObjectEvent *objectEvent, s16 x, s16 y, bool32 addCoords) { u8 i; struct ObjectEvent *curObject; if (objectEvent->localId == OBJ_EVENT_ID_FOLLOWER) - return FALSE; // follower cannot collide with other objects, but they can collide with it + return OBJECT_EVENTS_COUNT; // follower cannot collide with other objects, but they can collide with it + + if (addCoords) { + x += objectEvent->currentCoords.x; + y += objectEvent->currentCoords.y; + } for (i = 0; i < OBJECT_EVENTS_COUNT; i++) { @@ -5839,11 +5823,16 @@ static bool8 DoesObjectCollideWithObjectAt(struct ObjectEvent *objectEvent, s16 if ((curObject->currentCoords.x == x && curObject->currentCoords.y == y) || (curObject->previousCoords.x == x && curObject->previousCoords.y == y)) { if (AreElevationsCompatible(objectEvent->currentElevation, curObject->currentElevation)) - return TRUE; + return i; } } } - return FALSE; + return OBJECT_EVENTS_COUNT; +} + +static bool8 DoesObjectCollideWithObjectAt(struct ObjectEvent *objectEvent, s16 x, s16 y) +{ + return (GetObjectObjectCollidesWith(objectEvent, x, y, FALSE) < OBJECT_EVENTS_COUNT); } bool8 IsBerryTreeSparkling(u8 localId, u8 mapNum, u8 mapGroup) @@ -5972,6 +5961,16 @@ bool8 ObjectEventIsHeldMovementActive(struct ObjectEvent *objectEvent) return FALSE; } +static const u8 sActionIdToCopyableMovement[] = { + [MOVEMENT_ACTION_FACE_DOWN ... MOVEMENT_ACTION_FACE_RIGHT] = COPY_MOVE_FACE, + [MOVEMENT_ACTION_WALK_SLOW_DOWN ... MOVEMENT_ACTION_WALK_NORMAL_RIGHT] = COPY_MOVE_WALK, + [MOVEMENT_ACTION_JUMP_2_DOWN ... MOVEMENT_ACTION_JUMP_2_RIGHT] = COPY_MOVE_JUMP2, + [MOVEMENT_ACTION_WALK_FAST_DOWN ... MOVEMENT_ACTION_WALK_FAST_RIGHT] = COPY_MOVE_WALK, + [MOVEMENT_ACTION_RIDE_WATER_CURRENT_DOWN ... MOVEMENT_ACTION_PLAYER_RUN_RIGHT] = COPY_MOVE_WALK, + + [MOVEMENT_ACTION_NONE] = COPY_MOVE_NONE, +}; + bool8 ObjectEventSetHeldMovement(struct ObjectEvent *objectEvent, u8 movementActionId) { if (ObjectEventIsMovementOverridden(objectEvent)) @@ -5982,6 +5981,16 @@ bool8 ObjectEventSetHeldMovement(struct ObjectEvent *objectEvent, u8 movementAct objectEvent->heldMovementActive = TRUE; objectEvent->heldMovementFinished = FALSE; gSprites[objectEvent->spriteId].sActionFuncId = 0; + + // When player is moved via script, set copyable movement + // for any followers via a lookup table + if (ArePlayerFieldControlsLocked() && + objectEvent->isPlayer && + FlagGet(FLAG_SAFE_FOLLOWER_MOVEMENT)) + { + objectEvent->extra.playerCopyableMovement = sActionIdToCopyableMovement[objectEvent->movementActionId]; + } + return FALSE; } @@ -6004,6 +6013,15 @@ void ObjectEventClearHeldMovement(struct ObjectEvent *objectEvent) objectEvent->heldMovementFinished = FALSE; gSprites[objectEvent->spriteId].sTypeFuncId = 0; gSprites[objectEvent->spriteId].sActionFuncId = 0; + + // When player is moved via script, set copyable movement + // for any followers via a lookup table + if (ArePlayerFieldControlsLocked() && + objectEvent->isPlayer && + FlagGet(FLAG_SAFE_FOLLOWER_MOVEMENT)) + { + objectEvent->extra.playerCopyableMovement = sActionIdToCopyableMovement[objectEvent->movementActionId]; + } } u8 ObjectEventCheckHeldMovementStatus(struct ObjectEvent *objectEvent) diff --git a/src/scrcmd.c b/src/scrcmd.c index d29df1748d..79b13534ab 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -997,6 +997,20 @@ 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)); @@ -1010,17 +1024,11 @@ bool8 ScrCmd_applymovement(struct ScriptContext *ctx) } 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) + if (localId != OBJ_EVENT_ID_FOLLOWER && + !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; } @@ -2369,3 +2377,23 @@ bool8 ScrCmd_warpwhitefade(struct ScriptContext *ctx) ResetInitialPlayerAvatarState(); return TRUE; } + +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; +} diff --git a/src/script.c b/src/script.c index c252c95f04..2008b83424 100644 --- a/src/script.c +++ b/src/script.c @@ -4,6 +4,7 @@ #include "mystery_gift.h" #include "util.h" #include "constants/event_objects.h" +#include "constants/flags.h" #include "constants/map_scripts.h" #define RAM_SCRIPT_MAGIC 51 @@ -243,6 +244,8 @@ void ScriptContext_SetupScript(const u8 *ptr) InitScriptContext(&sGlobalScriptContext, gScriptCmdTable, gScriptCmdTableEnd); SetupBytecodeScript(&sGlobalScriptContext, ptr); LockPlayerFieldControls(); + if (OW_MON_SCRIPT_MOVEMENT) + FlagSet(FLAG_SAFE_FOLLOWER_MOVEMENT); sGlobalScriptContextStatus = CONTEXT_RUNNING; } diff --git a/src/script_movement.c b/src/script_movement.c index 10517dfc8e..fdec56c081 100644 --- a/src/script_movement.c +++ b/src/script_movement.c @@ -1,6 +1,7 @@ #include "global.h" #include "script_movement.h" #include "event_object_movement.h" +#include "event_scripts.h" #include "task.h" #include "util.h" #include "constants/event_objects.h" @@ -205,13 +206,34 @@ static void ScriptMovement_MoveObjects(u8 taskId) } } +// from event_object_movement +#define sTypeFuncId data[1] +#define sTimer data[5] + static void ScriptMovement_TakeStep(u8 taskId, u8 moveScrId, u8 objEventId, const u8 *movementScript) { u8 nextMoveActionId; + struct ObjectEvent *obj = &gObjectEvents[objEventId]; - if (ObjectEventIsHeldMovementActive(&gObjectEvents[objEventId]) - && !ObjectEventClearHeldMovementIfFinished(&gObjectEvents[objEventId])) + if (ObjectEventIsHeldMovementActive(obj) && + !ObjectEventClearHeldMovementIfFinished(obj)) + { + // If, while undergoing scripted movement, + // a non-player object collides with an active follower pokemon, + // put that follower into a pokeball + // (sTimer helps limit this expensive check to once per step) + if (OW_MON_SCRIPT_MOVEMENT && + gSprites[obj->spriteId].sTimer == 1 && + (objEventId = GetObjectObjectCollidesWith(obj, 0, 0, TRUE)) < OBJECT_EVENTS_COUNT && + // switch `obj` to follower + ((obj = &gObjectEvents[objEventId])->movementType == MOVEMENT_TYPE_FOLLOW_PLAYER) && + gSprites[obj->spriteId].sTypeFuncId != 0) + { + ClearObjectEventMovement(obj, &gSprites[obj->spriteId]); + ScriptMovement_StartObjectMovementScript(obj->localId, obj->mapNum, obj->mapGroup, EnterPokeballMovement); + } return; + } nextMoveActionId = *movementScript; if (nextMoveActionId == MOVEMENT_ACTION_STEP_END) @@ -229,3 +251,5 @@ static void ScriptMovement_TakeStep(u8 taskId, u8 moveScrId, u8 objEventId, cons } } +#undef sTypeFuncId +#undef sTimer From d12a24aef2f41d82288422fa3a41b18cbc2cecd4 Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Sat, 14 Dec 2024 23:07:02 -0500 Subject: [PATCH 02/11] fix: gave MOVEMENT_TYPE_FOLLOW_PLAYER an initial facing direction --- src/event_object_movement.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 26127caab9..acecd5d61d 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -362,7 +362,7 @@ static const bool8 sMovementTypeHasRange[NUM_MOVEMENT_TYPES] = { [MOVEMENT_TYPE_COPY_PLAYER_CLOCKWISE_IN_GRASS] = TRUE, }; -const u8 gInitialMovementTypeFacingDirections[] = { +const u8 gInitialMovementTypeFacingDirections[NUM_MOVEMENT_TYPES] = { [MOVEMENT_TYPE_NONE] = DIR_SOUTH, [MOVEMENT_TYPE_LOOK_AROUND] = DIR_SOUTH, [MOVEMENT_TYPE_WANDER_AROUND] = DIR_SOUTH, @@ -444,6 +444,7 @@ const u8 gInitialMovementTypeFacingDirections[] = { [MOVEMENT_TYPE_WALK_SLOWLY_IN_PLACE_UP] = DIR_NORTH, [MOVEMENT_TYPE_WALK_SLOWLY_IN_PLACE_LEFT] = DIR_WEST, [MOVEMENT_TYPE_WALK_SLOWLY_IN_PLACE_RIGHT] = DIR_EAST, + [MOVEMENT_TYPE_FOLLOW_PLAYER] = DIR_SOUTH, }; #define OBJ_EVENT_PAL_TAG_BRENDAN 0x1100 From 5536a5535c5dd958896df5204c3f98a51266ef89 Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Mon, 30 Dec 2024 20:52:05 -0500 Subject: [PATCH 03/11] fix: made slitherTracks_Transitions a proper 2D array. Credit: jaizu --- src/event_object_movement.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/event_object_movement.c b/src/event_object_movement.c index acecd5d61d..8d682bce63 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -9309,10 +9309,10 @@ static void DoTracksGroundEffect_SlitherTracks(struct ObjectEvent *objEvent, str // each byte in that row is for the next direction of the bike in the order // of down, up, left, right. static const u8 slitherTracks_Transitions[4][4] = { - 1, 2, 7, 8, - 1, 2, 6, 5, - 5, 8, 3, 4, - 6, 7, 3, 4, + {1, 2, 7, 8}, + {1, 2, 6, 5}, + {5, 8, 3, 4}, + {6, 7, 3, 4}, }; if (objEvent->currentCoords.x != objEvent->previousCoords.x || objEvent->currentCoords.y != objEvent->previousCoords.y) From 57a198bca0adb08ad0f1d7d4c520f3203f3ceb6c Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Mon, 30 Dec 2024 20:44:07 -0500 Subject: [PATCH 04/11] feat: make follower sidestep or backstep with player during scripts --- src/event_object_movement.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 8d682bce63..5f1b3eba77 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -5189,6 +5189,7 @@ bool8 MovementType_FollowPlayer_Moving(struct ObjectEvent *objectEvent, struct S if (ObjectEventExecSingleMovementAction(objectEvent, sprite)) { #endif objectEvent->singleMovementActive = 0; + objectEvent->facingDirectionLocked = FALSE; if (sprite->sTypeFuncId) // restore nonzero state sprite->sTypeFuncId = 1; } else if (objectEvent->movementActionId < MOVEMENT_ACTION_EXIT_POKEBALL) { @@ -5258,6 +5259,15 @@ bool8 FollowablePlayerMovement_Step(struct ObjectEvent *objectEvent, struct Spri // Follow player direction = GetDirectionToFace(x, y, targetX, targetY); + // During a script, if player sidesteps or backsteps, + // mirror player's direction instead + if (ArePlayerFieldControlsLocked() + && gObjectEvents[gPlayerAvatar.objectEventId].facingDirection != gObjectEvents[gPlayerAvatar.objectEventId].movementDirection + ) { + direction = gObjectEvents[gPlayerAvatar.objectEventId].movementDirection; + objectEvent->facingDirectionLocked = TRUE; + } + MoveCoords(direction, &x, &y); #ifdef MB_SIDEWAYS_STAIRS_RIGHT_SIDE // https://github.com/ghoulslash/pokeemerald/tree/sideways_stairs GetCollisionAtCoords(objectEvent, x, y, direction); // Sets directionOverwrite for stairs From 3fffb9fac6c3eb9c232609565f5e84957630a32c Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Mon, 30 Dec 2024 21:33:17 -0500 Subject: [PATCH 05/11] feat: added configuration to restrict following pokemon. Closes #42 --- include/constants/event_objects.h | 21 +++++++++++++++++++++ src/event_object_movement.c | 10 +++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/include/constants/event_objects.h b/include/constants/event_objects.h index 1d0f5a22e8..63d5f9c733 100644 --- a/include/constants/event_objects.h +++ b/include/constants/event_objects.h @@ -295,6 +295,27 @@ // FALSE: Script collisions unhandled, FLAG_SAFE_FOLLOWER_MOVEMENT off by default #define OW_MON_SCRIPT_MOVEMENT TRUE +// If set, the only pokemon allowed to follow you +// will be those matching species, met location, +// and/or met level; +// These accept vars, too: VAR_TEMP_1, etc +#define OW_MON_ALLOWED_SPECIES (0) +#define OW_MON_ALLOWED_MET_LVL (0) +#define OW_MON_ALLOWED_MET_LOC (0) +// Examples: +// Yellow Pikachu: +// #define OW_MON_ALLOWED_SPECIES (SPECIES_PIKACHU) +// #define OW_MON_ALLOWED_MET_LVL (0) +// #define OW_MON_ALLOWED_MET_LOC (MAPSEC_PALLET_TOWN) +// Hoenn Starter: +// #define OW_MON_ALLOWED_SPECIES (0) +// #define OW_MON_ALLOWED_MET_LVL (5) +// #define OW_MON_ALLOWED_MET_LOC (MAPSEC_ROUTE_101) +// Species set in VAR_XXXX: +// #define OW_MON_ALLOWED_SPECIES (VAR_XXXX) +// #define OW_MON_ALLOWED_MET_LVL (0) +// #define OW_MON_ALLOWED_MET_LOC (0) + #define SHADOW_SIZE_S 0 #define SHADOW_SIZE_M 1 #define SHADOW_SIZE_L 2 diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 5f1b3eba77..6498ceb455 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -1785,8 +1785,16 @@ u8 CreateVirtualObject(u8 graphicsId, u8 virtualObjId, s16 x, s16 y, u8 elevatio struct Pokemon *GetFirstLiveMon(void) { // Return address of first conscious party mon or NULL u32 i; for (i = 0; i < PARTY_SIZE; i++) { + struct Pokemon *mon = &gPlayerParty[i]; + if ((OW_MON_ALLOWED_SPECIES && GetMonData(mon, MON_DATA_SPECIES_OR_EGG) != VarGet(OW_MON_ALLOWED_SPECIES)) + || (OW_MON_ALLOWED_MET_LVL && GetMonData(mon, MON_DATA_MET_LEVEL) != VarGet(OW_MON_ALLOWED_MET_LVL)) + || (OW_MON_ALLOWED_MET_LOC && GetMonData(mon, MON_DATA_MET_LOCATION) != VarGet(OW_MON_ALLOWED_MET_LOC)) + ) { + continue; + } + if (gPlayerParty[i].hp > 0 && !(gPlayerParty[i].box.isEgg || gPlayerParty[i].box.isBadEgg)) - return &gPlayerParty[i]; + return &gPlayerParty[i]; } return NULL; } From 9e2057a9d473ac949f9dfde20a3df6b7a404a1c1 Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Mon, 30 Dec 2024 21:35:13 -0500 Subject: [PATCH 06/11] meta: updated README with new config settings --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 07427e2bd5..1dd20434ee 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,20 @@ A: Configuration for the follower system is mostly in [event_objects.h](include/ // Followers will emerge from the pokeball they are stored in, // instead of a normal pokeball #define OW_MON_POKEBALLS TRUE + +// New/old handling for followers during scripts; +// TRUE: Script collisions hide follower, FLAG_SAFE_FOLLOWER_MOVEMENT on by default +// (scripted player movement moves follower too!) +// FALSE: Script collisions unhandled, FLAG_SAFE_FOLLOWER_MOVEMENT off by default +#define OW_MON_SCRIPT_MOVEMENT TRUE + +// If set, the only pokemon allowed to follow you +// will be those matching species, met location, +// and/or met level; +// These accept vars, too: VAR_TEMP_1, etc +#define OW_MON_ALLOWED_SPECIES (0) +#define OW_MON_ALLOWED_MET_LVL (0) +#define OW_MON_ALLOWED_MET_LOC (0) ``` ### `(lighting)` Q: How do I mark certain colors in a palette as light-blended? From 01335fa453a90d9f48ce6d8f92a947cd1782b23c Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Thu, 23 Jun 2022 22:13:21 -0400 Subject: [PATCH 07/11] Unfreeze follower object during lockall (if safe movement flag set). MSGBOX_SIGN no longer freezes follower. --- data/scripts/std_msgbox.inc | 2 ++ src/scrcmd.c | 3 +++ 2 files changed, 5 insertions(+) diff --git a/data/scripts/std_msgbox.inc b/data/scripts/std_msgbox.inc index c46da56cbf..40026c8ea0 100644 --- a/data/scripts/std_msgbox.inc +++ b/data/scripts/std_msgbox.inc @@ -8,11 +8,13 @@ Std_MsgboxNPC: return Std_MsgboxSign: + setflag FLAG_SAFE_FOLLOWER_MOVEMENT lockall message NULL waitmessage waitbuttonpress releaseall + clearflag FLAG_SAFE_FOLLOWER_MOVEMENT return Std_MsgboxDefault: diff --git a/src/scrcmd.c b/src/scrcmd.c index 79b13534ab..a648bf105c 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -1248,8 +1248,11 @@ bool8 ScrCmd_lockall(struct ScriptContext *ctx) } else { + struct ObjectEvent *followerObj = GetFollowerObject(); FreezeObjects_WaitForPlayer(); SetupNativeScript(ctx, IsFreezePlayerFinished); + if (FlagGet(FLAG_SAFE_FOLLOWER_MOVEMENT) && followerObj) // Unfreeze follower object (conditionally) + UnfreezeObjectEvent(followerObj); return TRUE; } } From c492bb3fecd64c69a6a7d03d3a77e739451c871a Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Tue, 21 Jan 2025 23:27:28 -0500 Subject: [PATCH 08/11] fix: follower field movements after scripted movement update --- data/scripts/field_move_scripts.inc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/data/scripts/field_move_scripts.inc b/data/scripts/field_move_scripts.inc index dc161eb560..1db60ea0b8 100644 --- a/data/scripts/field_move_scripts.inc +++ b/data/scripts/field_move_scripts.inc @@ -126,7 +126,6 @@ EventScript_FollowerSwap: return EventScript_FollowerMoveNorth: - applymovement OBJ_EVENT_ID_FOLLOWER, Movement_WalkUp applymovement OBJ_EVENT_ID_PLAYER, Movement_WalkDown waitmovement 0 applymovement OBJ_EVENT_ID_PLAYER, Common_Movement_FaceUp @@ -134,7 +133,6 @@ EventScript_FollowerMoveNorth: return EventScript_FollowerMoveEast: - applymovement OBJ_EVENT_ID_FOLLOWER, Movement_WalkRight applymovement OBJ_EVENT_ID_PLAYER, Movement_WalkLeft waitmovement 0 applymovement OBJ_EVENT_ID_PLAYER, Common_Movement_FaceRight @@ -142,7 +140,6 @@ EventScript_FollowerMoveEast: return EventScript_FollowerMoveSouth: - applymovement OBJ_EVENT_ID_FOLLOWER, Movement_WalkDown applymovement OBJ_EVENT_ID_PLAYER, Movement_WalkUp waitmovement 0 applymovement OBJ_EVENT_ID_PLAYER, Common_Movement_FaceDown @@ -150,7 +147,6 @@ EventScript_FollowerMoveSouth: return EventScript_FollowerMoveWest: - applymovement OBJ_EVENT_ID_FOLLOWER, Movement_WalkLeft applymovement OBJ_EVENT_ID_PLAYER, Movement_WalkRight waitmovement 0 applymovement OBJ_EVENT_ID_PLAYER, Common_Movement_FaceLeft From ce9f3b95f38dc741d671f82b61c0c0f01ae50b73 Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Tue, 21 Jan 2025 23:27:57 -0500 Subject: [PATCH 09/11] fix: non-following pokemon using slide-in battle animation --- src/battle_controller_player.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index bdff3ad77d..417ee70105 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -2198,17 +2198,24 @@ static void PlayerHandleSwitchInAnim(void) gBattlerControllerFuncs[gActiveBattler] = SwitchIn_TryShinyAnimShowHealthbox; } -// In normal singles, if follower pokemon is out, have it slide in instead of being thrown +// In normal singles, if follower pokemon exists, +// and the pokemon following is being sent out, +// have it slide in instead of being thrown static bool8 ShouldDoSlideInAnim(void) { struct ObjectEvent *followerObj = GetFollowerObject(); if (!followerObj || followerObj->invisible) return FALSE; + if (gBattleTypeFlags & ( BATTLE_TYPE_LINK | BATTLE_TYPE_DOUBLE | BATTLE_TYPE_FRONTIER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_WALLY_TUTORIAL | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_TWO_OPPONENTS | BATTLE_TYPE_INGAME_PARTNER | BATTLE_TYPE_RECORDED | BATTLE_TYPE_TRAINER_HILL) ) return FALSE; + + if (GetFirstLiveMon() != &gPlayerParty[gBattlerPartyIndexes[gActiveBattler]]) + return FALSE; + return TRUE; } From d73167e262beb20eb529357692365ab9ed8414e6 Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Sat, 25 Jan 2025 21:37:16 -0500 Subject: [PATCH 10/11] fix: treat 1-tile jumps as slow copyable movement for follower pokemon --- src/event_object_movement.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 6498ceb455..30f81e0c81 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -5986,6 +5986,9 @@ static const u8 sActionIdToCopyableMovement[] = { [MOVEMENT_ACTION_JUMP_2_DOWN ... MOVEMENT_ACTION_JUMP_2_RIGHT] = COPY_MOVE_JUMP2, [MOVEMENT_ACTION_WALK_FAST_DOWN ... MOVEMENT_ACTION_WALK_FAST_RIGHT] = COPY_MOVE_WALK, [MOVEMENT_ACTION_RIDE_WATER_CURRENT_DOWN ... MOVEMENT_ACTION_PLAYER_RUN_RIGHT] = COPY_MOVE_WALK, + // Not a typo; follower needs to take an action with a duration == JUMP's, + // and JUMP2 here will lead to WALK_SLOW later + [MOVEMENT_ACTION_JUMP_DOWN ... MOVEMENT_ACTION_JUMP_RIGHT] = COPY_MOVE_JUMP2, [MOVEMENT_ACTION_NONE] = COPY_MOVE_NONE, }; From bc421740e8b3fea023fd0fbc66f079e638e7f60c Mon Sep 17 00:00:00 2001 From: Ariel A <24759293+aarant@users.noreply.github.com> Date: Wed, 29 Jan 2025 23:12:45 -0500 Subject: [PATCH 11/11] fix: fixed compilation error when OW_GFX_COMPRESS is FALSE --- src/event_object_movement.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 059e64bb07..5353acbb8b 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -2041,8 +2041,9 @@ static void RefreshFollowerGraphics(struct ObjectEvent *objEvent) { sprite->y += -(graphicsInfo->height >> 1) - sprite->centerToCornerVecY; } - if (OW_GFX_COMPRESS) - LoadSheetGraphicsInfo(graphicsInfo, objEvent->graphicsId, sprite); + #if OW_GFX_COMPRESS + LoadSheetGraphicsInfo(graphicsInfo, objEvent->graphicsId, sprite); + #endif sprite->oam.shape = graphicsInfo->oam->shape; sprite->oam.size = graphicsInfo->oam->size;