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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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; From a859f40e376d1048017d440d3215cb201787bc53 Mon Sep 17 00:00:00 2001 From: Hedara Date: Wed, 12 Feb 2025 14:31:42 +0100 Subject: [PATCH 12/18] Review comments --- data/scripts/std_msgbox.inc | 4 ++-- include/config/overworld.h | 8 +++++++- include/constants/event_objects.h | 20 -------------------- src/event_object_movement.c | 12 ++++++------ src/scrcmd.c | 9 ++++++--- src/script.c | 2 +- src/script_movement.c | 2 +- 7 files changed, 23 insertions(+), 34 deletions(-) diff --git a/data/scripts/std_msgbox.inc b/data/scripts/std_msgbox.inc index 40026c8ea0..8d5e4167cb 100644 --- a/data/scripts/std_msgbox.inc +++ b/data/scripts/std_msgbox.inc @@ -8,13 +8,13 @@ Std_MsgboxNPC: return Std_MsgboxSign: - setflag FLAG_SAFE_FOLLOWER_MOVEMENT + setflag FLAG_SAFE_FOLLOWER_MOVEMENT lockall message NULL waitmessage waitbuttonpress releaseall - clearflag FLAG_SAFE_FOLLOWER_MOVEMENT + clearflag FLAG_SAFE_FOLLOWER_MOVEMENT return Std_MsgboxDefault: diff --git a/include/config/overworld.h b/include/config/overworld.h index ff5937743c..7f5be368c2 100644 --- a/include/config/overworld.h +++ b/include/config/overworld.h @@ -54,10 +54,16 @@ // 16x32, 32x32, 64x64 etc are fine #define OW_MON_WANDER_WALK TRUE // If true, OW pokemon with MOVEMENT_TYPE_WANDER will walk-in-place in between steps. // Follower Pokémon -#define OW_FOLLOWERS_ENABLED TRUE // Enables follower Pokémon, HGSS style. Requires OW_POKEMON_OBJECT_EVENTS. Note that additional scripting may be required for them to be fully supported! +#define OW_FOLLOWERS_ENABLED FALSE // Enables follower Pokémon, HGSS style. Requires OW_POKEMON_OBJECT_EVENTS. Note that additional scripting may be required for them to be fully supported! #define OW_FOLLOWERS_BOBBING TRUE // If true, follower pokemon will bob up and down during their idle & walking animations #define OW_FOLLOWERS_POKEBALLS TRUE // Followers will emerge from the pokeball they are stored in, instead of a normal pokeball +// 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_FOLLOWERS_SCRIPT_MOVEMENT TRUE + // Out-of-battle Ability effects #define OW_SYNCHRONIZE_NATURE GEN_LATEST // In Gen8+, if a Pokémon with Synchronize leads the party, wild Pokémon will always have their same Nature as opposed to the 50% chance in previous games. Gift Pokémon excluded. // In USUM (here GEN_7), if a Pokémon with Synchronize leads the party, gift Pokémon will always have their same Nature regardless of their Egg Group. diff --git a/include/constants/event_objects.h b/include/constants/event_objects.h index 63849ec676..b9f2622b54 100644 --- a/include/constants/event_objects.h +++ b/include/constants/event_objects.h @@ -295,26 +295,6 @@ #define SHADOW_SIZE_XL_BATTLE_ONLY SHADOW_SIZE_NONE // Battle-only definition for XL shadow size. -// If true, adds a small amount of overhead -// to OW code so that large (48x48, 64x64) OWs -// will display correctly under bridges, etc. -#define LARGE_OW_SUPPORT TRUE - -// See global.h for the toggle of OW_GFX_COMPRESS -// Compressed gfx are incompatible with non-power-of-two sprite sizes: -// (You should not use 48x48 sprites/tables for compressed gfx) -// 16x32, 32x32, 64x64 etc are fine - -// 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; diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 5c7243b0f8..e6c3c4ddf5 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -2092,9 +2092,8 @@ static void RefreshFollowerGraphics(struct ObjectEvent *objEvent) sprite->y += -(graphicsInfo->height >> 1) - sprite->centerToCornerVecY; } - #if OW_GFX_COMPRESS - LoadSheetGraphicsInfo(graphicsInfo, objEvent->graphicsId, sprite); - #endif + if (OW_GFX_COMPRESS) + LoadSheetGraphicsInfo(graphicsInfo, objEvent->graphicsId, sprite); sprite->oam.shape = graphicsInfo->oam->shape; sprite->oam.size = graphicsInfo->oam->size; @@ -5515,8 +5514,8 @@ bool8 FollowablePlayerMovement_Step(struct ObjectEvent *objectEvent, struct Spri // During a script, if player sidesteps or backsteps, // mirror player's direction instead if (ArePlayerFieldControlsLocked() - && gObjectEvents[gPlayerAvatar.objectEventId].facingDirection != gObjectEvents[gPlayerAvatar.objectEventId].movementDirection - ) { + && gObjectEvents[gPlayerAvatar.objectEventId].facingDirection != gObjectEvents[gPlayerAvatar.objectEventId].movementDirection) + { direction = gObjectEvents[gPlayerAvatar.objectEventId].movementDirection; objectEvent->facingDirectionLocked = TRUE; } @@ -6209,7 +6208,8 @@ u32 GetObjectObjectCollidesWith(struct ObjectEvent *objectEvent, s16 x, s16 y, b if (objectEvent->localId == OBJ_EVENT_ID_FOLLOWER) return OBJECT_EVENTS_COUNT; // follower cannot collide with other objects, but they can collide with it - if (addCoords) { + if (addCoords) + { x += objectEvent->currentCoords.x; y += objectEvent->currentCoords.y; } diff --git a/src/scrcmd.c b/src/scrcmd.c index 1dd23c36b3..b45a0d76a3 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -998,7 +998,8 @@ bool8 ScrCmd_fadeinbgm(struct ScriptContext *ctx) return FALSE; } -struct ObjectEvent * ScriptHideFollower(void) { +struct ObjectEvent * ScriptHideFollower(void) +{ struct ObjectEvent *obj = GetFollowerObject(); if (obj == NULL || obj->invisible) @@ -2562,11 +2563,13 @@ bool8 Scrcmd_getobjectfacingdirection(struct ScriptContext *ctx) return FALSE; } -bool8 ScrFunc_hidefollower(struct ScriptContext *ctx) { +bool8 ScrFunc_hidefollower(struct ScriptContext *ctx) +{ bool16 wait = VarGet(ScriptReadHalfword(ctx)); struct ObjectEvent *obj; - if ((obj = ScriptHideFollower()) != NULL && wait) { + if ((obj = ScriptHideFollower()) != NULL && wait) + { sMovingNpcId = obj->localId; sMovingNpcMapGroup = obj->mapGroup; sMovingNpcMapNum = obj->mapNum; diff --git a/src/script.c b/src/script.c index 91cf29a87a..acdaf3f241 100644 --- a/src/script.c +++ b/src/script.c @@ -256,7 +256,7 @@ void ScriptContext_SetupScript(const u8 *ptr) InitScriptContext(&sGlobalScriptContext, gScriptCmdTable, gScriptCmdTableEnd); SetupBytecodeScript(&sGlobalScriptContext, ptr); LockPlayerFieldControls(); - if (OW_MON_SCRIPT_MOVEMENT) + if (OW_FOLLOWERS_SCRIPT_MOVEMENT) FlagSet(FLAG_SAFE_FOLLOWER_MOVEMENT); sGlobalScriptContextStatus = CONTEXT_RUNNING; } diff --git a/src/script_movement.c b/src/script_movement.c index 021be6d621..28e9e02a47 100644 --- a/src/script_movement.c +++ b/src/script_movement.c @@ -222,7 +222,7 @@ static void ScriptMovement_TakeStep(u8 taskId, u8 moveScrId, u8 objEventId, cons // 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 && + if (OW_FOLLOWERS_SCRIPT_MOVEMENT && gSprites[obj->spriteId].sTimer == 1 && (objEventId = GetObjectObjectCollidesWith(obj, 0, 0, TRUE)) < OBJECT_EVENTS_COUNT && // switch `obj` to follower From f20aebf87856f8396071e0468a0376e328df1bea Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Wed, 12 Feb 2025 21:57:51 +0100 Subject: [PATCH 13/18] Fixed multi battle forced switches (#6243) Co-authored-by: Hedara Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- src/battle_script_commands.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index d962390fd8..a34a6dc4b0 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -12038,7 +12038,6 @@ static void Cmd_forcerandomswitch(void) { CMD_ARGS(const u8 *failInstr); - s32 i; s32 battler1PartyId = 0; s32 battler2PartyId = 0; @@ -12173,7 +12172,7 @@ static void Cmd_forcerandomswitch(void) battler1PartyId = gBattlerPartyIndexes[gBattlerTarget]; } - for (i = firstMonId; i < lastMonId; i++) + for (u32 i = firstMonId; i < lastMonId; i++) { if (GetMonData(&party[i], MON_DATA_SPECIES) != SPECIES_NONE && !GetMonData(&party[i], MON_DATA_IS_EGG) @@ -12204,12 +12203,12 @@ static void Cmd_forcerandomswitch(void) || (gBattleTypeFlags & BATTLE_TYPE_RECORDED_LINK && gBattleTypeFlags & BATTLE_TYPE_BATTLE_TOWER) || (gBattleTypeFlags & BATTLE_TYPE_RECORDED_LINK && gBattleTypeFlags & BATTLE_TYPE_MULTI)) { - SwitchPartyOrderLinkMulti(gBattlerTarget, i, 0); - SwitchPartyOrderLinkMulti(BATTLE_PARTNER(gBattlerTarget), i, 1); + SwitchPartyOrderLinkMulti(gBattlerTarget, gBattleStruct->monToSwitchIntoId[gBattlerTarget], 0); + SwitchPartyOrderLinkMulti(BATTLE_PARTNER(gBattlerTarget), gBattleStruct->monToSwitchIntoId[gBattlerTarget], 1); } if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER) - SwitchPartyOrderInGameMulti(gBattlerTarget, i); + SwitchPartyOrderInGameMulti(gBattlerTarget, gBattleStruct->monToSwitchIntoId[gBattlerTarget]); } } else From 9aea7c8e3ab1cf312a21b697e64c9a6452b4b938 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Wed, 12 Feb 2025 23:36:37 +0100 Subject: [PATCH 14/18] Changed bit order for follower graphicsId creation (#6249) Co-authored-by: Hedara --- include/constants/event_objects.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/constants/event_objects.h b/include/constants/event_objects.h index 219b990c5d..306aa055aa 100644 --- a/include/constants/event_objects.h +++ b/include/constants/event_objects.h @@ -270,10 +270,10 @@ #define OBJ_EVENT_GFX_VAR_E (OBJ_EVENT_GFX_VARS + 0xE) #define OBJ_EVENT_GFX_VAR_F (OBJ_EVENT_GFX_VARS + 0xF) -#define OBJ_EVENT_MON (1 << 13) -#define OBJ_EVENT_MON_SHINY (1 << 14) -#define OBJ_EVENT_MON_FEMALE (1 << 15) -#define OBJ_EVENT_MON_SPECIES_MASK (OBJ_EVENT_MON - 1) +#define OBJ_EVENT_MON (1u << 15) +#define OBJ_EVENT_MON_SHINY (1u << 14) +#define OBJ_EVENT_MON_FEMALE (1u << 13) +#define OBJ_EVENT_MON_SPECIES_MASK (~(7u << 13)) // Used to call a specific species' follower graphics. Useful for static encounters. #define OBJ_EVENT_GFX_SPECIES(name) (SPECIES_##name + OBJ_EVENT_MON) From 7d1bbf62e1415530f31a469d737f79955a7c2b22 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Thu, 13 Feb 2025 10:23:06 +0100 Subject: [PATCH 15/18] Apply suggestions from code review Co-authored-by: Bassoonian --- asm/macros/event.inc | 4 +--- src/event_object_movement.c | 18 +++++++++--------- src/scrcmd.c | 8 ++++---- src/script_movement.c | 12 +++++------- 4 files changed, 19 insertions(+), 23 deletions(-) diff --git a/asm/macros/event.inc b/asm/macros/event.inc index 3018a774b6..4c0bf54514 100644 --- a/asm/macros/event.inc +++ b/asm/macros/event.inc @@ -2440,9 +2440,7 @@ .2byte \dest .endm - @ hide any follower pokemon if present, - @ putting them into their pokeball; - @ by default waits for their movement to finish + @ 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 diff --git a/src/event_object_movement.c b/src/event_object_movement.c index e6c3c4ddf5..8b7733d4e4 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -1928,8 +1928,8 @@ struct Pokemon *GetFirstLiveMon(void) { 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))) + || (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; } @@ -5514,7 +5514,7 @@ bool8 FollowablePlayerMovement_Step(struct ObjectEvent *objectEvent, struct Spri // During a script, if player sidesteps or backsteps, // mirror player's direction instead if (ArePlayerFieldControlsLocked() - && gObjectEvents[gPlayerAvatar.objectEventId].facingDirection != gObjectEvents[gPlayerAvatar.objectEventId].movementDirection) + && gObjectEvents[gPlayerAvatar.objectEventId].facingDirection != gObjectEvents[gPlayerAvatar.objectEventId].movementDirection) { direction = gObjectEvents[gPlayerAvatar.objectEventId].movementDirection; objectEvent->facingDirectionLocked = TRUE; @@ -6412,9 +6412,9 @@ bool8 ObjectEventSetHeldMovement(struct ObjectEvent *objectEvent, u8 movementAct // 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)) + if (ArePlayerFieldControlsLocked() + && objectEvent->isPlayer + && FlagGet(FLAG_SAFE_FOLLOWER_MOVEMENT)) { objectEvent->playerCopyableMovement = sActionIdToCopyableMovement[objectEvent->movementActionId]; } @@ -6445,9 +6445,9 @@ void ObjectEventClearHeldMovement(struct ObjectEvent *objectEvent) // 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)) + if (ArePlayerFieldControlsLocked() + && objectEvent->isPlayer + && FlagGet(FLAG_SAFE_FOLLOWER_MOVEMENT)) { objectEvent->playerCopyableMovement = sActionIdToCopyableMovement[objectEvent->movementActionId]; } diff --git a/src/scrcmd.c b/src/scrcmd.c index b45a0d76a3..9b4d00143a 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -998,7 +998,7 @@ bool8 ScrCmd_fadeinbgm(struct ScriptContext *ctx) return FALSE; } -struct ObjectEvent * ScriptHideFollower(void) +struct ObjectEvent *ScriptHideFollower(void) { struct ObjectEvent *obj = GetFollowerObject(); @@ -1030,9 +1030,9 @@ bool8 ScrCmd_applymovement(struct ScriptContext *ctx) gObjectEvents[GetObjectEventIdByLocalId(localId)].directionOverwrite = DIR_NONE; ScriptMovement_StartObjectMovementScript(localId, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, movementScript); sMovingNpcId = localId; - if (localId != OBJ_EVENT_ID_FOLLOWER && - !FlagGet(FLAG_SAFE_FOLLOWER_MOVEMENT) - && (movementScript < Common_Movement_FollowerSafeStart || movementScript > Common_Movement_FollowerSafeEnd)) + if (localId != OBJ_EVENT_ID_FOLLOWER + && !FlagGet(FLAG_SAFE_FOLLOWER_MOVEMENT) + && (movementScript < Common_Movement_FollowerSafeStart || movementScript > Common_Movement_FollowerSafeEnd)) { ScriptHideFollower(); } diff --git a/src/script_movement.c b/src/script_movement.c index 28e9e02a47..80d9b323f6 100644 --- a/src/script_movement.c +++ b/src/script_movement.c @@ -215,19 +215,17 @@ static void ScriptMovement_TakeStep(u8 taskId, u8 moveScrId, u8 objEventId, cons u8 nextMoveActionId; struct ObjectEvent *obj = &gObjectEvents[objEventId]; - if (ObjectEventIsHeldMovementActive(obj) && - !ObjectEventClearHeldMovementIfFinished(obj)) + 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_FOLLOWERS_SCRIPT_MOVEMENT && - gSprites[obj->spriteId].sTimer == 1 && - (objEventId = GetObjectObjectCollidesWith(obj, 0, 0, TRUE)) < OBJECT_EVENTS_COUNT && + if (OW_FOLLOWERS_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) + && ((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); From 0da3e4988940cb34c654f063621ba1851b8a12d7 Mon Sep 17 00:00:00 2001 From: Hedara Date: Thu, 13 Feb 2025 10:36:52 +0100 Subject: [PATCH 16/18] Review comments --- include/config/overworld.h | 29 ++++++++++++++++++++++++----- include/constants/event_objects.h | 21 --------------------- include/constants/flags.h | 5 ++--- 3 files changed, 26 insertions(+), 29 deletions(-) diff --git a/include/config/overworld.h b/include/config/overworld.h index 7f5be368c2..e09e423a35 100644 --- a/include/config/overworld.h +++ b/include/config/overworld.h @@ -58,11 +58,30 @@ #define OW_FOLLOWERS_BOBBING TRUE // If true, follower pokemon will bob up and down during their idle & walking animations #define OW_FOLLOWERS_POKEBALLS TRUE // Followers will emerge from the pokeball they are stored in, instead of a normal pokeball -// 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_FOLLOWERS_SCRIPT_MOVEMENT TRUE + +#define OW_FOLLOWERS_SCRIPT_MOVEMENT TRUE // TRUE: Script collisions hide follower, FLAG_SAFE_FOLLOWER_MOVEMENT on by default + // FALSE: Script collisions unhandled, FLAG_SAFE_FOLLOWER_MOVEMENT off by default + +// 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) // Out-of-battle Ability effects #define OW_SYNCHRONIZE_NATURE GEN_LATEST // In Gen8+, if a Pokémon with Synchronize leads the party, wild Pokémon will always have their same Nature as opposed to the 50% chance in previous games. Gift Pokémon excluded. diff --git a/include/constants/event_objects.h b/include/constants/event_objects.h index b9f2622b54..219b990c5d 100644 --- a/include/constants/event_objects.h +++ b/include/constants/event_objects.h @@ -295,27 +295,6 @@ #define SHADOW_SIZE_XL_BATTLE_ONLY SHADOW_SIZE_NONE // Battle-only definition for XL shadow size. -// 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 F_INANIMATE (1 << 6) #define F_DISABLE_REFLECTION_PALETTE_LOAD (1 << 7) diff --git a/include/constants/flags.h b/include/constants/flags.h index 9450c6de1c..8685faca6f 100644 --- a/include/constants/flags.h +++ b/include/constants/flags.h @@ -1647,9 +1647,8 @@ #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) -// 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) +#define FLAG_SAFE_FOLLOWER_MOVEMENT (SPECIAL_FLAGS_START + 0x5) // When set, applymovement does not put the follower inside a pokeball + // Also, scripted movements on the player will move follower(s), too // 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) From 35324497a6438c27a06fdba0fe6e3eccd42908d7 Mon Sep 17 00:00:00 2001 From: Bassoonian Date: Thu, 13 Feb 2025 13:35:38 +0100 Subject: [PATCH 17/18] Update src/event_object_movement.c --- 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 8b7733d4e4..36b8ea69e7 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -6201,7 +6201,8 @@ static bool8 IsMetatileDirectionallyImpassable(struct ObjectEvent *objectEvent, return FALSE; } -u32 GetObjectObjectCollidesWith(struct ObjectEvent *objectEvent, s16 x, s16 y, bool32 addCoords) { +u32 GetObjectObjectCollidesWith(struct ObjectEvent *objectEvent, s16 x, s16 y, bool32 addCoords) +{ u8 i; struct ObjectEvent *curObject; From 845679471f22a364b1fba10e09d26ad3b2efa5e7 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Thu, 13 Feb 2025 13:32:08 -0300 Subject: [PATCH 18/18] Added missing Dream Eater and Reflect Type tests (#6245) --- src/data/moves_info.h | 1 + test/battle/move_effect/dream_eater.c | 62 ++++++++++++++++++++++++-- test/battle/move_effect/reflect_type.c | 42 ++++++++++++++--- 3 files changed, 97 insertions(+), 8 deletions(-) diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 31ac2afe4c..0589866293 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -3550,6 +3550,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .priority = 0, .category = DAMAGE_CATEGORY_SPECIAL, .healingMove = B_HEAL_BLOCKING >= GEN_6, + .ignoresSubstitute = B_UPDATED_MOVE_FLAGS >= GEN_5, .contestEffect = CONTEST_EFFECT_STARTLE_PREV_MONS, .contestCategory = CONTEST_CATEGORY_SMART, .contestComboStarterId = 0, diff --git a/test/battle/move_effect/dream_eater.c b/test/battle/move_effect/dream_eater.c index 85bdb42ac9..70319be9c5 100644 --- a/test/battle/move_effect/dream_eater.c +++ b/test/battle/move_effect/dream_eater.c @@ -55,6 +55,62 @@ SINGLE_BATTLE_TEST("Dream Eater fails if Heal Block applies") } } -TO_DO_BATTLE_TEST("Dream Eater works on targets with Comatose"); -TO_DO_BATTLE_TEST("Dream Eater fails if the target is behind a Substitute (Gen 1-4)"); -TO_DO_BATTLE_TEST("Dream Eater works if the target is behind a Substitute (Gen 5+)"); +SINGLE_BATTLE_TEST("Dream Eater works on targets with Comatose") +{ + s16 damage; + s16 healed; + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_KOMALA) { Ability(ABILITY_COMATOSE); } + } WHEN { + TURN { MOVE(player, MOVE_DREAM_EATER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DREAM_EATER, player); + HP_BAR(opponent, captureDamage: &damage); + HP_BAR(player, captureDamage: &healed); + } THEN { + EXPECT_MUL_EQ(damage, Q_4_12(-1.0/2.0), healed); + } +} + +#if B_UPDATED_MOVE_FLAGS < GEN_5 +SINGLE_BATTLE_TEST("Dream Eater fails if the target is behind a Substitute (Gen 1-4)") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_YAWN].effect == EFFECT_YAWN); + ASSUME(gMovesInfo[MOVE_SUBSTITUTE].effect == EFFECT_SUBSTITUTE); + ASSUME(!gMovesInfo[MOVE_DREAM_EATER].ignoresSubstitute); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_YAWN); MOVE(player, MOVE_SUBSTITUTE); } + TURN { } + TURN { MOVE(opponent, MOVE_DREAM_EATER); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Dream Eater!"); + MESSAGE("Wobbuffet wasn't affected!"); + } +} +#else +SINGLE_BATTLE_TEST("Dream Eater works if the target is behind a Substitute (Gen 5+)") +{ + s16 damage; + s16 healed; + GIVEN { + ASSUME(gMovesInfo[MOVE_YAWN].effect == EFFECT_YAWN); + ASSUME(gMovesInfo[MOVE_SUBSTITUTE].effect == EFFECT_SUBSTITUTE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + } WHEN { + TURN { MOVE(opponent, MOVE_YAWN); MOVE(player, MOVE_SUBSTITUTE); } + TURN { } + TURN { MOVE(opponent, MOVE_DREAM_EATER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DREAM_EATER, opponent); + HP_BAR(player, captureDamage: &damage); + HP_BAR(opponent, captureDamage: &healed); + } THEN { + EXPECT_MUL_EQ(damage, Q_4_12(-1.0/2.0), healed); + } +} +#endif diff --git a/test/battle/move_effect/reflect_type.c b/test/battle/move_effect/reflect_type.c index fc152fc27f..f3093ea4e3 100644 --- a/test/battle/move_effect/reflect_type.c +++ b/test/battle/move_effect/reflect_type.c @@ -1,9 +1,6 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Reflect Type fails if the user is Terastallized"); -TO_DO_BATTLE_TEST("Reflect Type succeeds against a Terastallized target and copies its Tera type"); - SINGLE_BATTLE_TEST("Reflect Type does not affect any of Arceus' forms") { u32 j; @@ -86,7 +83,7 @@ SINGLE_BATTLE_TEST("Reflect Type does not affect any of Silvally's forms") } } -SINGLE_BATTLE_TEST("Reflect Type does not affect Pokémon with no types") +SINGLE_BATTLE_TEST("Reflect Type fails if the target has no types") { GIVEN { ASSUME(gSpeciesInfo[SPECIES_ARCANINE].types[0] == TYPE_FIRE); @@ -151,7 +148,7 @@ SINGLE_BATTLE_TEST("Reflect Type copies a target's pure type") } } -SINGLE_BATTLE_TEST("Reflect Type defaults to Normal type for the user's types[0] and types[1] if the target only has a 3rd type") +SINGLE_BATTLE_TEST("Reflect Type defaults to Normal type for the user's 1st and 2nd types if the target only has a 3rd type") { GIVEN { ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[0] == TYPE_PSYCHIC); @@ -184,3 +181,38 @@ SINGLE_BATTLE_TEST("Reflect Type defaults to Normal type for the user's types[0] EXPECT_EQ(player->types[2], TYPE_GRASS); } } + +SINGLE_BATTLE_TEST("Reflect Type fails if the user is Terastallized") +{ + GIVEN { + PLAYER(SPECIES_ARCANINE) { TeraType(TYPE_NORMAL); } + OPPONENT(SPECIES_POLIWRATH); + } WHEN { + TURN { MOVE(player, MOVE_REFLECT_TYPE, gimmick: GIMMICK_TERA); } + } SCENE { + MESSAGE("Arcanine used Reflect Type!"); + MESSAGE("But it failed!"); + } THEN { + EXPECT_EQ(player->types[0], TYPE_FIRE); + EXPECT_EQ(player->types[1], TYPE_FIRE); + EXPECT_EQ(player->types[2], TYPE_MYSTERY); + } +} + +SINGLE_BATTLE_TEST("Reflect Type succeeds against a Terastallized target and copies its Tera type") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_POLIWRATH].types[0] != TYPE_NORMAL); + ASSUME(gSpeciesInfo[SPECIES_POLIWRATH].types[1] != TYPE_NORMAL); + PLAYER(SPECIES_ARCANINE) { TeraType(TYPE_NORMAL); } + OPPONENT(SPECIES_POLIWRATH); + } WHEN { + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_REFLECT_TYPE); } + } SCENE { + MESSAGE("The opposing Poliwrath used Reflect Type!"); + } THEN { + EXPECT_EQ(opponent->types[0], TYPE_NORMAL); + EXPECT_EQ(opponent->types[1], TYPE_NORMAL); + EXPECT_EQ(opponent->types[2], TYPE_NORMAL); + } +}