diff --git a/asm/macros/event.inc b/asm/macros/event.inc index d18bcc9420..3018a774b6 100644 --- a/asm/macros/event.inc +++ b/asm/macros/event.inc @@ -2440,3 +2440,10 @@ .2byte \dest .endm + @ hide any follower pokemon if present, + @ putting them into their pokeball; + @ by default waits for their movement to finish + .macro hidefollower wait=1 + callnative ScrFunc_hidefollower + .2byte \wait + .endm diff --git a/data/scripts/field_move_scripts.inc b/data/scripts/field_move_scripts.inc index f1bd3618e1..30393b771d 100644 --- a/data/scripts/field_move_scripts.inc +++ b/data/scripts/field_move_scripts.inc @@ -118,7 +118,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 @@ -126,7 +125,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 @@ -134,7 +132,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 @@ -142,7 +139,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 diff --git a/data/scripts/pkmn_center_nurse.inc b/data/scripts/pkmn_center_nurse.inc index 4aca9d768b..dd27ed80a6 100644 --- a/data/scripts/pkmn_center_nurse.inc +++ b/data/scripts/pkmn_center_nurse.inc @@ -33,7 +33,8 @@ EventScript_PkmnCenterNurse_IllTakeYourPkmn2:: return EventScript_PkmnCenterNurse_TakeAndHealPkmn:: - applymovement VAR_0x800B, Movement_PkmnCenterNurse_Turn @ Changed from Common_Movement_WalkInPlaceFasterLeft to force the follower to enter their Poké Ball + hidefollower 0 + applymovement VAR_0x800B, Movement_PkmnCenterNurse_Turn waitmovement 0 dofieldeffect FLDEFF_POKECENTER_HEAL .if OW_UNION_DISABLE_CHECK == FALSE && OW_FLAG_MOVE_UNION_ROOM_CHECK != 0 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/include/config/overworld.h b/include/config/overworld.h index be56ccb26f..ff5937743c 100644 --- a/include/config/overworld.h +++ b/include/config/overworld.h @@ -54,7 +54,7 @@ // 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 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_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_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 diff --git a/include/constants/event_objects.h b/include/constants/event_objects.h index 219b990c5d..63849ec676 100644 --- a/include/constants/event_objects.h +++ b/include/constants/event_objects.h @@ -295,6 +295,47 @@ #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; +// 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 b1fcae5eb7..9450c6de1c 100644 --- a/include/constants/flags.h +++ b/include/constants/flags.h @@ -1647,7 +1647,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 1dc604138b..40a0e30c21 100644 --- a/include/event_object_movement.h +++ b/include/event_object_movement.h @@ -177,6 +177,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 40ec9a411a..5c7243b0f8 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -381,7 +381,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, @@ -463,6 +463,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, }; #include "data/object_events/object_event_graphics_info_pointers.h" @@ -1925,6 +1926,14 @@ struct Pokemon *GetFirstLiveMon(void) 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]; } @@ -2083,8 +2092,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; @@ -5409,6 +5419,7 @@ bool8 MovementType_FollowPlayer_Moving(struct ObjectEvent *objectEvent, struct S objectEvent->movementActionId = MOVEMENT_ACTION_NONE; sprite->sActionFuncId = 0; objectEvent->singleMovementActive = FALSE; + objectEvent->facingDirectionLocked = FALSE; if (sprite->sTypeFuncId) // restore nonzero state sprite->sTypeFuncId = 1; } @@ -5479,7 +5490,8 @@ bool8 FollowablePlayerMovement_Step(struct ObjectEvent *objectEvent, struct Spri { // 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; @@ -5500,6 +5512,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); GetCollisionAtCoords(objectEvent, x, y, direction); // Sets directionOverwrite for stairs if (GetLedgeJumpDirection(x, y, direction) != DIR_NONE) @@ -5507,53 +5528,28 @@ bool8 FollowablePlayerMovement_Step(struct ObjectEvent *objectEvent, struct Spri // InitJumpRegular will set the proper speed ObjectEventSetSingleMovement(objectEvent, sprite, GetJump2MovementAction(direction)); } - else if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_DASH)) + else if (playerAction >= MOVEMENT_ACTION_WALK_SLOW_DOWN && playerAction <= MOVEMENT_ACTION_WALK_SLOW_RIGHT) { - // Set follow speed according to player's speed - if (playerAction >= MOVEMENT_ACTION_RUN_DOWN_SLOW && playerAction <= MOVEMENT_ACTION_RUN_RIGHT_SLOW) + 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_FOLLOWERS_BOBBING == TRUE) - sprite->y2 = -1; - } - } - sprite->sActionFuncId = 0; - 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_FOLLOWERS_BOBBING == TRUE) sprite->y2 = -1; } + sprite->sActionFuncId = 0; objectEvent->singleMovementActive = TRUE; sprite->sTypeFuncId = 2; return TRUE; @@ -6206,13 +6202,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++) { @@ -6223,11 +6223,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) @@ -6379,6 +6384,19 @@ static u8 TryUpdateMovementActionOnStairs(struct ObjectEvent *objectEvent, u8 mo } } +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, + // 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, +}; + bool8 ObjectEventSetHeldMovement(struct ObjectEvent *objectEvent, u8 movementActionId) { if (ObjectEventIsMovementOverridden(objectEvent)) @@ -6391,6 +6409,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->playerCopyableMovement = sActionIdToCopyableMovement[objectEvent->movementActionId]; + } + return FALSE; } @@ -6414,6 +6442,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->playerCopyableMovement = sActionIdToCopyableMovement[objectEvent->movementActionId]; + } } u8 ObjectEventCheckHeldMovementStatus(struct ObjectEvent *objectEvent) diff --git a/src/scrcmd.c b/src/scrcmd.c index 58a7c510a6..1dd23c36b3 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -998,6 +998,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)); @@ -1015,17 +1029,11 @@ bool8 ScrCmd_applymovement(struct ScriptContext *ctx) gObjectEvents[GetObjectEventIdByLocalId(localId)].directionOverwrite = DIR_NONE; ScriptMovement_StartObjectMovementScript(localId, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, movementScript); sMovingNpcId = localId; - objEvent = GetFollowerObject(); - // Force follower into pokeball - if (localId != OBJ_EVENT_ID_FOLLOWER - && !FlagGet(FLAG_SAFE_FOLLOWER_MOVEMENT) - && (movementScript < Common_Movement_FollowerSafeStart || movementScript > Common_Movement_FollowerSafeEnd) - && (objEvent = GetFollowerObject()) - && !objEvent->invisible) + 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; } @@ -1247,8 +1255,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; } } @@ -2550,3 +2561,23 @@ bool8 Scrcmd_getobjectfacingdirection(struct ScriptContext *ctx) return FALSE; } + +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 e6e2aa264d..91cf29a87a 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" #include "field_message_box.h" @@ -255,6 +256,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 d67afd9972..021be6d621 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