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? diff --git a/asm/macros/event.inc b/asm/macros/event.inc index 3c1ae5abc1..9d0ef6178f 100644 --- a/asm/macros/event.inc +++ b/asm/macros/event.inc @@ -1946,3 +1946,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/field_move_scripts.inc b/data/scripts/field_move_scripts.inc index e15115f826..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 @@ -376,6 +372,24 @@ EventScript_EndSurface:: releaseall end +EventScript_DigCommon: + callfunc ScrFunc_IsFollowerFieldMoveUser + .2byte VAR_0x8004 + setfieldeffectargument 3, VAR_0x8004 @ skip pose if true + dofieldeffect FLDEFF_USE_DIG + waitstate +EventScript_DigSealedChamber:: @ fallthrough + setflag FLAG_SAFE_FOLLOWER_MOVEMENT + call_if_eq VAR_0x8004, TRUE, EventScript_FollowerFieldMove + callnative DoBrailleDigEffect + releaseall + end + +@ Use Dig from party menu +EventScript_UseDig:: + lockall + goto EventScript_DigCommon + Text_CantDive: .string "The sea is deep here. A POKéMON\n" .string "may be able to go underwater.$" 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/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/constants/event_objects.h b/include/constants/event_objects.h index 67aced84fd..2be687a1b0 100644 --- a/include/constants/event_objects.h +++ b/include/constants/event_objects.h @@ -288,9 +288,15 @@ #define OW_SPECIES(x) (((x)->graphicsId & OBJ_EVENT_GFX_SPECIES_MASK) - OBJ_EVENT_GFX_MON_BASE) #define OW_FORM(x) ((x)->graphicsId >> OBJ_EVENT_GFX_SPECIES_BITS) +// Whether Object Event is an OW pokemon +#define IS_OW_MON_OBJ(obj) ((obj)->graphicsId >= OBJ_EVENT_GFX_MON_BASE) + // If true, follower pokemon will bob up and down // during their idle & walking animations #define OW_MON_BOBBING TRUE +// If true, OW pokemon with `MOVEMENT_TYPE_WANDER*` +// will walk-in-place in between steps +#define OW_MON_WANDER_WALK TRUE // If true, adds a small amount of overhead // to OW code so that large (48x48, 64x64) OWs @@ -306,6 +312,33 @@ // 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 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 f9456570b2..2016f67fc5 100644 --- a/include/event_object_movement.h +++ b/include/event_object_movement.h @@ -164,6 +164,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 *); @@ -298,7 +299,7 @@ u8 CreateCopySpriteAt(struct Sprite *sprite, s16 x, s16 y, u8 subpriority); u8 MovementType_WanderAround_Step0(struct ObjectEvent *, struct Sprite *); u8 MovementType_WanderAround_Step1(struct ObjectEvent *, struct Sprite *); u8 MovementType_WanderAround_Step2(struct ObjectEvent *, struct Sprite *); -u8 MovementType_WanderAround_Step3(struct ObjectEvent *, struct Sprite *); +u8 MovementType_Wander_Step3(struct ObjectEvent *, struct Sprite *); u8 MovementType_WanderAround_Step4(struct ObjectEvent *, struct Sprite *); u8 MovementType_WanderAround_Step5(struct ObjectEvent *, struct Sprite *); u8 MovementType_WanderAround_Step6(struct ObjectEvent *, struct Sprite *); @@ -321,14 +322,12 @@ u8 MovementType_LookAround_Step4(struct ObjectEvent *, struct Sprite *); u8 MovementType_WanderUpAndDown_Step0(struct ObjectEvent *, struct Sprite *); u8 MovementType_WanderUpAndDown_Step1(struct ObjectEvent *, struct Sprite *); u8 MovementType_WanderUpAndDown_Step2(struct ObjectEvent *, struct Sprite *); -u8 MovementType_WanderUpAndDown_Step3(struct ObjectEvent *, struct Sprite *); u8 MovementType_WanderUpAndDown_Step4(struct ObjectEvent *, struct Sprite *); u8 MovementType_WanderUpAndDown_Step5(struct ObjectEvent *, struct Sprite *); u8 MovementType_WanderUpAndDown_Step6(struct ObjectEvent *, struct Sprite *); u8 MovementType_WanderLeftAndRight_Step0(struct ObjectEvent *, struct Sprite *); u8 MovementType_WanderLeftAndRight_Step1(struct ObjectEvent *, struct Sprite *); u8 MovementType_WanderLeftAndRight_Step2(struct ObjectEvent *, struct Sprite *); -u8 MovementType_WanderLeftAndRight_Step3(struct ObjectEvent *, struct Sprite *); u8 MovementType_WanderLeftAndRight_Step4(struct ObjectEvent *, struct Sprite *); u8 MovementType_WanderLeftAndRight_Step5(struct ObjectEvent *, struct Sprite *); u8 MovementType_WanderLeftAndRight_Step6(struct ObjectEvent *, struct Sprite *); diff --git a/include/event_scripts.h b/include/event_scripts.h index 2fa4468c74..ff920d62ae 100644 --- a/include/event_scripts.h +++ b/include/event_scripts.h @@ -404,6 +404,7 @@ extern const u8 EventScript_FailSweetScent[]; extern const u8 EventScript_UseFlash[]; extern const u8 EventScript_UseCut[]; extern const u8 EventScript_UseRockSmash[]; +extern const u8 EventScript_UseDig[]; //player pc extern const u8 LittlerootTown_BrendansHouse_2F_EventScript_TurnOffPlayerPC[]; diff --git a/remote_build.sh b/remote_build.sh index 835520290d..300ac51ba5 100644 --- a/remote_build.sh +++ b/remote_build.sh @@ -25,7 +25,8 @@ git push build --force-with-lease if [[ $retVal -eq 0 ]]; then commit_msg=$(git log -1 --pretty=%B) if [[ "$commit_msg" == "$temp_commit_msg" ]]; then - git reset "$old_head" &>/dev/null + # Keep i(N)tent to add + git reset --mixed -N "$old_head" &>/dev/null fi fi set -x 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; } diff --git a/src/braille_puzzles.c b/src/braille_puzzles.c index ab0f610fd7..757cac43b8 100644 --- a/src/braille_puzzles.c +++ b/src/braille_puzzles.c @@ -87,7 +87,6 @@ void DoBrailleDigEffect(void) DrawWholeMapView(); PlaySE(SE_BANG); FlagSet(FLAG_SYS_BRAILLE_DIG); - UnlockPlayerFieldControls(); } bool8 CheckRelicanthWailord(void) diff --git a/src/data/object_events/movement_type_func_tables.h b/src/data/object_events/movement_type_func_tables.h index 749c14969c..35180f5b3c 100755 --- a/src/data/object_events/movement_type_func_tables.h +++ b/src/data/object_events/movement_type_func_tables.h @@ -2,7 +2,7 @@ u8 (*const gMovementTypeFuncs_WanderAround[])(struct ObjectEvent *, struct Sprit MovementType_WanderAround_Step0, MovementType_WanderAround_Step1, MovementType_WanderAround_Step2, - MovementType_WanderAround_Step3, + MovementType_Wander_Step3, MovementType_WanderAround_Step4, MovementType_WanderAround_Step5, MovementType_WanderAround_Step6, @@ -36,7 +36,7 @@ u8 (*const gMovementTypeFuncs_WanderUpAndDown[])(struct ObjectEvent *, struct Sp MovementType_WanderUpAndDown_Step0, MovementType_WanderUpAndDown_Step1, MovementType_WanderUpAndDown_Step2, - MovementType_WanderUpAndDown_Step3, + MovementType_Wander_Step3, MovementType_WanderUpAndDown_Step4, MovementType_WanderUpAndDown_Step5, MovementType_WanderUpAndDown_Step6, @@ -48,7 +48,7 @@ u8 (*const gMovementTypeFuncs_WanderLeftAndRight[])(struct ObjectEvent *, struct MovementType_WanderLeftAndRight_Step0, MovementType_WanderLeftAndRight_Step1, MovementType_WanderLeftAndRight_Step2, - MovementType_WanderLeftAndRight_Step3, + MovementType_Wander_Step3, MovementType_WanderLeftAndRight_Step4, MovementType_WanderLeftAndRight_Step5, MovementType_WanderLeftAndRight_Step6, diff --git a/src/event_object_movement.c b/src/event_object_movement.c index e883df7112..ee88393308 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -103,6 +103,7 @@ static EWRAM_DATA struct LockedAnimObjectEvents *sLockedAnimObjectEvents = {0}; static void MoveCoordsInDirection(u32, s16 *, s16 *, s16, s16); static bool8 ObjectEventExecSingleMovementAction(struct ObjectEvent *, struct Sprite *); +static bool32 UpdateMonMoveInPlace(struct ObjectEvent *, struct Sprite *); static void SetMovementDelay(struct Sprite *, s16); static bool8 WaitForMovementDelay(struct Sprite *); static u8 GetCollisionInDirection(struct ObjectEvent *, u8); @@ -364,7 +365,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, @@ -446,6 +447,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 @@ -1422,7 +1424,7 @@ static u8 InitObjectEventStateFromTemplate(const struct ObjectEventTemplate *tem objectEvent->triggerGroundEffectsOnMove = TRUE; objectEvent->graphicsId = PackGraphicsId(template); SetObjectEventDynamicGraphicsId(objectEvent); - if (objectEvent->graphicsId >= OBJ_EVENT_GFX_MON_BASE) { + if (IS_OW_MON_OBJ(objectEvent)) { if (template->script && template->script[0] == 0x7d) objectEvent->shiny = T1_READ_16(&template->script[2]) >> 15; else if (template->trainerRange_berryTreeId) @@ -1721,7 +1723,7 @@ static u16 PackGraphicsId(const struct ObjectEventTemplate *template) { u32 form = 0; // set form based on template's script, // if first command is bufferspeciesname - if (graphicsId >= OBJ_EVENT_GFX_MON_BASE) { + if (IS_OW_MON_OBJ(template)) { if (template->script && template->script[0] == 0x7d) { form = T1_READ_16(&template->script[2]); form = (form >> 10) & 0x1F; @@ -1952,8 +1954,16 @@ u8 CreateVirtualObject(u16 graphicsId, u8 virtualObjId, s16 x, s16 y, u8 elevati 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; } @@ -3595,12 +3605,20 @@ bool8 MovementType_WanderAround_Step2(struct ObjectEvent *objectEvent, struct Sp return TRUE; } -bool8 MovementType_WanderAround_Step3(struct ObjectEvent *objectEvent, struct Sprite *sprite) +// common; used by all MovementType_Wander*_Step3 +bool8 MovementType_Wander_Step3(struct ObjectEvent *objectEvent, struct Sprite *sprite) { if (WaitForMovementDelay(sprite)) { + // resets a mid-movement sprite + ClearObjectEventMovement(objectEvent, sprite); sprite->sTypeFuncId = 4; return TRUE; + } else if ( + OW_MON_WANDER_WALK == TRUE + && IS_OW_MON_OBJ(objectEvent)) + { + UpdateMonMoveInPlace(objectEvent, sprite); } return FALSE; } @@ -3927,16 +3945,6 @@ bool8 MovementType_WanderUpAndDown_Step2(struct ObjectEvent *objectEvent, struct return TRUE; } -bool8 MovementType_WanderUpAndDown_Step3(struct ObjectEvent *objectEvent, struct Sprite *sprite) -{ - if (WaitForMovementDelay(sprite)) - { - sprite->sTypeFuncId = 4; - return TRUE; - } - return FALSE; -} - bool8 MovementType_WanderUpAndDown_Step4(struct ObjectEvent *objectEvent, struct Sprite *sprite) { u8 direction; @@ -3995,16 +4003,6 @@ bool8 MovementType_WanderLeftAndRight_Step2(struct ObjectEvent *objectEvent, str return TRUE; } -bool8 MovementType_WanderLeftAndRight_Step3(struct ObjectEvent *objectEvent, struct Sprite *sprite) -{ - if (WaitForMovementDelay(sprite)) - { - sprite->sTypeFuncId = 4; - return TRUE; - } - return FALSE; -} - bool8 MovementType_WanderLeftAndRight_Step4(struct ObjectEvent *objectEvent, struct Sprite *sprite) { u8 direction; @@ -5459,7 +5457,7 @@ bool8 MovementType_FollowPlayer_Active(struct ObjectEvent *objectEvent, struct S // Animate entering pokeball ClearObjectEventMovement(objectEvent, sprite); ObjectEventSetSingleMovement(objectEvent, sprite, MOVEMENT_ACTION_ENTER_POKEBALL); - objectEvent->singleMovementActive = 1; + objectEvent->singleMovementActive = TRUE; sprite->sTypeFuncId = 2; // movement action sets state to 0 return TRUE; } @@ -5476,7 +5474,8 @@ bool8 MovementType_FollowPlayer_Moving(struct ObjectEvent *objectEvent, struct S #else if (ObjectEventExecSingleMovementAction(objectEvent, sprite)) { #endif - objectEvent->singleMovementActive = 0; + objectEvent->singleMovementActive = FALSE; + objectEvent->facingDirectionLocked = FALSE; if (sprite->sTypeFuncId) // restore nonzero state sprite->sTypeFuncId = 1; } else if (objectEvent->movementActionId < MOVEMENT_ACTION_EXIT_POKEBALL) { @@ -5487,19 +5486,27 @@ bool8 MovementType_FollowPlayer_Moving(struct ObjectEvent *objectEvent, struct S return FALSE; } -bool8 FollowablePlayerMovement_Idle(struct ObjectEvent *objectEvent, struct Sprite *sprite, u8 playerDirection, bool8 tileCallback(u8)) -{ +// single function for updating an OW mon's walk-in-place movements +static bool32 UpdateMonMoveInPlace(struct ObjectEvent *objectEvent, struct Sprite *sprite) { if (!objectEvent->singleMovementActive) { // walk in place - ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkInPlaceNormalMovementAction(objectEvent->facingDirection)); - sprite->sTypeFuncId = 1; - objectEvent->singleMovementActive = 1; - return TRUE; + ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkInPlaceNormalMovementAction(objectEvent->facingDirection)); + objectEvent->singleMovementActive = TRUE; + return TRUE; } else if (ObjectEventExecSingleMovementAction(objectEvent, sprite)) { // finish movement action - objectEvent->singleMovementActive = 0; + objectEvent->singleMovementActive = FALSE; } else if (OW_MON_BOBBING == TRUE && (sprite->data[3] & 7) == 2) sprite->y2 ^= -1; + return FALSE; +} + +bool8 FollowablePlayerMovement_Idle(struct ObjectEvent *objectEvent, struct Sprite *sprite, u8 playerDirection, bool8 tileCallback(u8)) +{ + if (UpdateMonMoveInPlace(objectEvent, sprite)) { + sprite->sTypeFuncId = 1; + return TRUE; + } UpdateFollowerTransformEffect(objectEvent, sprite); return FALSE; } @@ -5511,9 +5518,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; @@ -5529,14 +5534,14 @@ 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; } MoveObjectEventToMapCoords(objectEvent, targetX, targetY); ObjectEventSetSingleMovement(objectEvent, sprite, MOVEMENT_ACTION_EXIT_POKEBALL); - objectEvent->singleMovementActive = 1; + objectEvent->singleMovementActive = TRUE; sprite->sTypeFuncId = 2; if (OW_MON_BOBBING == TRUE) sprite->y2 = 0; @@ -5548,47 +5553,38 @@ 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 - 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 - objectEvent->singleMovementActive = 1; + sprite->sActionFuncId = 0; + objectEvent->singleMovementActive = TRUE; sprite->sTypeFuncId = 2; return TRUE; } @@ -5770,6 +5766,14 @@ bool8 MovementType_MoveInPlace_Step1(struct ObjectEvent *objectEvent, struct Spr { if (ObjectEventExecSingleMovementAction(objectEvent, sprite)) sprite->sTypeFuncId = 0; + // similar to UpdateMonMoveInPlace + else if ( + OW_MON_BOBBING == TRUE + && IS_OW_MON_OBJ(objectEvent) + && (sprite->data[3] & 7) == 2) + { + sprite->y2 ^= 1; + } return FALSE; } @@ -6112,13 +6116,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++) { @@ -6128,11 +6136,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) @@ -6261,6 +6274,19 @@ 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, + // 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)) @@ -6271,6 +6297,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; } @@ -6293,6 +6329,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) @@ -9583,10 +9628,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) @@ -10210,14 +10255,16 @@ static u8 DoJumpSpecialSpriteMovement(struct Sprite *sprite) static void SetMovementDelay(struct Sprite *sprite, s16 timer) { - sprite->data[3] = timer; + sprite->data[3] = timer; // kept for legacy reasons + sprite->data[7] = timer; // actual timer } static bool8 WaitForMovementDelay(struct Sprite *sprite) { - if (--sprite->data[3] == 0) + if (--sprite->data[7] == 0) { + sprite->data[3] = 0; // reset animation timer return TRUE; - else + } else return FALSE; } diff --git a/src/fldeff_dig.c b/src/fldeff_dig.c index c3ab989cd8..63eb848bf2 100644 --- a/src/fldeff_dig.c +++ b/src/fldeff_dig.c @@ -1,11 +1,13 @@ #include "global.h" #include "braille_puzzles.h" +#include "event_scripts.h" #include "field_effect.h" #include "field_player_avatar.h" #include "fldeff.h" #include "item_use.h" #include "overworld.h" #include "party_menu.h" +#include "script.h" #include "sprite.h" #include "constants/field_effects.h" @@ -31,8 +33,8 @@ bool8 SetUpFieldMove_Dig(void) static void FieldCallback_Dig(void) { Overworld_ResetStateAfterDigEscRope(); - FieldEffectStart(FLDEFF_USE_DIG); gFieldEffectArguments[0] = GetCursorSelectionMonId(); + ScriptContext_SetupScript(EventScript_UseDig); } bool8 FldEff_UseDig(void) @@ -53,7 +55,8 @@ static void StartDigFieldEffect(void) FieldEffectActiveListRemove(FLDEFF_USE_DIG); if (ShouldDoBrailleDigEffect()) { - DoBrailleDigEffect(); + // EventScript_DigSealedChamber handles DoBrailleDigEffect call + ScriptContext_Enable(); } else { diff --git a/src/scrcmd.c b/src/scrcmd.c index b3b373336a..80468d31ba 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -1006,6 +1006,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)); @@ -1019,17 +1033,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; } @@ -1249,8 +1257,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; } } @@ -2378,3 +2389,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