Follower NPCs (follow-me) (#6500)

Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com>
This commit is contained in:
Bivurnum 2025-04-27 07:56:18 -05:00 committed by GitHub
parent 87bd59b6f4
commit 47723c34e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 2800 additions and 112 deletions

View File

@ -2453,7 +2453,7 @@
callnative Script_SetDifficulty, requests_effects=1
.byte \difficulty
.endm
.macro forcesave
callnative Script_ForceSaveGame
waitstate
@ -2519,3 +2519,67 @@
compare \a, \b
cant_see_if 5
.endm
@ Follower NPCs
@ Sets an NPC up to follow the player.
@ Follower flags are defined in include/constants/follower_npc.h
@ If you want to specify a battle partner without specifying a custom script, you can set the script parameter to 0.
.macro setfollower localId:req, flags:req, script=0, battlePartner=0
checkfollower
goto_if_eq VAR_RESULT, TRUE, 1f
hidefollower
delay 16
callnative ScriptSetFollowerNPC
.if \script == 0
.set setScript, FALSE
.else
.set setScript, TRUE
.endif
.byte \localId
.2byte \flags
.byte setScript
.2byte \battlePartner
.4byte \script
updatefollowingmon
1:
.endm
@ Remove the follower NPC (assumes there will only ever be one).
.macro destroyfollower
callnative ScriptDestroyFollowerNPC
.endm
@ Makes the player and follower NPC face one another.
.macro facefollower
callnative ScriptFaceFollowerNPC
.endm
@ Makes the follower NPC walk into the player and get hidden.
@ Optionally, you can set the walk speed for the movement:
@ 0 = Slow
@ 1 = Normal (default)
@ 2 = Fast
@ 3 = Faster
.macro hidefollowernpc speed=1
callnative ScriptHideNPCFollower
.byte \speed
waitmovement OBJ_EVENT_ID_NPC_FOLLOWER
callnative HideNPCFollower
.endm
@ Checks if you have a follower NPC. Returns the result to VAR_RESULT.
.macro checkfollower
callnative ScriptCheckFollowerNPC
.endm
@ Updates Pokemon follower.
.macro updatefollowingmon
callnative ScriptUpdateFollowingMon
.endm
@ Changes the battle partner of the existing follower NPC.
.macro changefollowerbattler battlePartner:req
callnative ScriptChangeFollowerNPCBattlePartner
.2byte \battlePartner
.endm

View File

@ -30,6 +30,7 @@
#include "constants/field_tasks.h"
#include "constants/field_weather.h"
#include "constants/flags.h"
#include "constants/follower_npc.h"
#include "constants/frontier_util.h"
#include "constants/game_stat.h"
#include "constants/item.h"

View File

@ -293,7 +293,9 @@ EventScript_UseWaterfall::
msgbox Text_WantToWaterfall, MSGBOX_YESNO
goto_if_eq VAR_RESULT, NO, EventScript_EndWaterfall
msgbox Text_MonUsedWaterfall, MSGBOX_DEFAULT
hidefollowernpc
dofieldeffect FLDEFF_USE_WATERFALL
callnative FollowerNPC_WarpSetEnd
goto EventScript_EndWaterfall
EventScript_CannotUseWaterfall::
@ -321,12 +323,16 @@ EventScript_UseDive::
lockall
checkpartymove MOVE_DIVE
goto_if_eq VAR_RESULT, PARTY_SIZE, EventScript_CantDive
copyvar 0x8004 VAR_RESULT
bufferpartymonnick STR_VAR_1, VAR_RESULT
setfieldeffectargument 0, VAR_RESULT
setfieldeffectargument 1, 1
msgbox Text_WantToDive, MSGBOX_YESNO
goto_if_eq VAR_RESULT, NO, EventScript_EndDive
msgbox Text_MonUsedDive, MSGBOX_DEFAULT
hidefollowernpc
setfieldeffectargument 0, VAR_0x8004
setfieldeffectargument 1, 1
dofieldeffect FLDEFF_USE_DIVE
goto EventScript_EndDive
end
@ -350,6 +356,7 @@ EventScript_UseDiveUnderwater::
msgbox Text_WantToSurface, MSGBOX_YESNO
goto_if_eq VAR_RESULT, NO, EventScript_EndSurface
msgbox Text_MonUsedDive, MSGBOX_DEFAULT
hidefollowernpc
dofieldeffect FLDEFF_USE_DIVE
goto EventScript_EndSurface
end
@ -361,6 +368,7 @@ EventScript_CantSurface::
end
EventScript_EndSurface::
callnative SetFollowerNPCSurfSpriteAfterDive
releaseall
end

View File

@ -43,19 +43,19 @@ Common_Movement_WalkInPlaceFasterDown:
walk_in_place_faster_down
step_end
Common_Movement_FaceRight:
Common_Movement_FaceRight::
face_right
step_end
Common_Movement_FaceLeft:
Common_Movement_FaceLeft::
face_left
step_end
Common_Movement_FaceDown:
Common_Movement_FaceDown::
face_down
step_end
Common_Movement_FaceUp:
Common_Movement_FaceUp::
face_up
step_end
@ -96,7 +96,7 @@ Common_Movement_Delay32:
delay_16
step_end
Common_Movement_WalkUp:
Common_Movement_WalkUp::
walk_up
step_end
@ -105,3 +105,64 @@ Common_Movement_WalkUp2::
walk_up
walk_up
step_end
@ Follower NPC
Common_Movement_WalkUpSlow::
walk_slow_up
step_end
Common_Movement_WalkDownSlow::
walk_slow_down
step_end
Common_Movement_WalkRightSlow::
walk_slow_right
step_end
Common_Movement_WalkLeftSlow::
walk_slow_left
step_end
Common_Movement_WalkDown::
walk_down
step_end
Common_Movement_WalkRight::
walk_right
step_end
Common_Movement_WalkLeft::
walk_left
step_end
Common_Movement_WalkUpFast::
walk_fast_up
step_end
Common_Movement_WalkDownFast::
walk_fast_down
step_end
Common_Movement_WalkRightFast::
walk_fast_right
step_end
Common_Movement_WalkLeftFast::
walk_fast_left
step_end
Common_Movement_WalkUpFaster::
walk_faster_up
step_end
Common_Movement_WalkDownFaster::
walk_faster_down
step_end
Common_Movement_WalkRightFaster::
walk_faster_right
step_end
Common_Movement_WalkLeftFaster::
walk_faster_left
step_end

View File

@ -77,5 +77,6 @@ u16 FacilityClassToGraphicsId(u8 facilityClass);
bool32 ValidateBattleTowerRecord(u8 recordId); // unused
void TrySetLinkBattleTowerEnemyPartyLevel(void);
void CreateFacilityMon(const struct TrainerMon *fmon, u16 level, u8 fixedIV, u32 otID, u32 flags, struct Pokemon *dst);
void FillPartnerParty(u16 trainerId);
#endif //GUARD_BATTLE_TOWER_H

View File

@ -0,0 +1,13 @@
#ifndef GUARD_FOLLOWER_NPC_OVERWORLD_H
#define GUARD_FOLLOWER_NPC_OVERWORLD_H
// NPC Followers
#define FNPC_ENABLE_NPC_FOLLOWERS FALSE // Enables the use of script macros to designate NPCs to follow behind the player, DPP style. Slightly increases the size of the saveblock (SaveBlock3).
#define FNPC_FLAG_HEAL_AFTER_FOLLOWER_BATTLE 0 // Replace the 0 with a flag in order to use that flag to toggle whether the Player's party will be automatically healed after every follower partner battle. If you want this to always be active without using a flag, replace 0 with FNPC_ALWAYS.
#define FNPC_FLAG_PARTNER_WILD_BATTLES 0 // Replace the 0 with a flag in order to use that flag to toggle whether the follower partner will join you for wild battles. If you want this to always be active without using a flag, replace 0 with FNPC_ALWAYS.
#define FNPC_NPC_FOLLOWER_WILD_BATTLE_VS_2 TRUE // If set to TRUE, two wild Pokemon will show up to the partner battle instead of just one.
#define FNPC_NPC_FOLLOWER_PARTY_PREVIEW TRUE // If set to TRUE, a preview of the player's and partner's teams will be shown before every trainer battle.
#define FNPC_FACE_NPC_FOLLOWER_ON_DOOR_EXIT TRUE // If set to TRUE, the player will turn to face the follower when they exit a doorway.
#define FNPC_NPC_FOLLOWER_SHOW_AFTER_LEAVE_ROUTE TRUE // If set to TRUE, the follower will reappear and walk out of the player after using Fly, Escape Rope, or Teleport.
#endif // GUARD_FOLLOWER_NPC_OVERWORLD_H

View File

@ -255,6 +255,10 @@
#define MOVEMENT_ACTION_WALK_SLOW_STAIRS_UP 0xA7
#define MOVEMENT_ACTION_WALK_SLOW_STAIRS_LEFT 0xA8
#define MOVEMENT_ACTION_WALK_SLOW_STAIRS_RIGHT 0xA9
#define MOVEMENT_ACTION_SURF_STILL_DOWN 0xAA
#define MOVEMENT_ACTION_SURF_STILL_UP 0xAB
#define MOVEMENT_ACTION_SURF_STILL_LEFT 0xAC
#define MOVEMENT_ACTION_SURF_STILL_RIGHT 0xAD
#define MOVEMENT_ACTION_STEP_END 0xFE
#define MOVEMENT_ACTION_NONE 0xFF

View File

@ -323,6 +323,7 @@
#define OBJ_EVENT_ID_PLAYER 0xFF
#define OBJ_EVENT_ID_CAMERA 0x7F
#define OBJ_EVENT_ID_FOLLOWER 0xFE
#define OBJ_EVENT_ID_NPC_FOLLOWER 0xFD
// Object event local ids referenced in C files
#define LOCALID_ROUTE111_PLAYER_FALLING 45

View File

@ -0,0 +1,34 @@
#ifndef GUARD_CONSTANTS_FOLLOWER_NPC_H
#define GUARD_CONSTANTS_FOLLOWER_NPC_H
// NPC Follower Flags
#define FOLLOWER_NPC_FLAG_HAS_RUNNING_FRAMES 0x1 // Only use this if the NPC has running anim frames. Part of FOLLOWER_NPC_FLAG_ALL_LAND.
#define FOLLOWER_NPC_FLAG_CAN_BIKE 0x2 // Player is allowed to use the Bike. Part of FOLLOWER_NPC_FLAG_ALL_LAND.
#define FOLLOWER_NPC_FLAG_CAN_LEAVE_ROUTE 0x4 // Player is allowed to use Fly/Teleport/EscapeRope/etc. Part of FOLLOWER_NPC_FLAG_ALL_LAND.
#define FOLLOWER_NPC_FLAG_CAN_SURF 0x8 // Player is allowed to Surf. Part of FOLLOWER_NPC_FLAG_ALL_WATER.
#define FOLLOWER_NPC_FLAG_CAN_WATERFALL 0x10 // Player is allowed to use Waterfall. Part of FOLLOWER_NPC_FLAG_ALL_WATER.
#define FOLLOWER_NPC_FLAG_CAN_DIVE 0x20 // Player is allowed to use Dive. Part of FOLLOWER_NPC_FLAG_ALL_WATER.
#define FOLLOWER_NPC_FLAG_CLEAR_ON_WHITE_OUT 0x80 // The NPC follower will be destroyed if the player whites out.
#define FOLLOWER_NPC_FLAG_ALL_LAND FOLLOWER_NPC_FLAG_HAS_RUNNING_FRAMES | FOLLOWER_NPC_FLAG_CAN_BIKE | FOLLOWER_NPC_FLAG_CAN_LEAVE_ROUTE
#define FOLLOWER_NPC_FLAG_ALL_WATER FOLLOWER_NPC_FLAG_CAN_SURF | FOLLOWER_NPC_FLAG_CAN_WATERFALL | FOLLOWER_NPC_FLAG_CAN_DIVE
#define FOLLOWER_NPC_FLAG_ALL FOLLOWER_NPC_FLAG_ALL_LAND | FOLLOWER_NPC_FLAG_ALL_WATER | FOLLOWER_NPC_FLAG_CLEAR_ON_WHITE_OUT
// Shorter flag names for ease of use in setfollower script macro
#define FNPC_RUNNING FOLLOWER_NPC_FLAG_HAS_RUNNING_FRAMES
#define FNPC_BIKE FOLLOWER_NPC_FLAG_CAN_BIKE
#define FNPC_LEAVE_ROUTE FOLLOWER_NPC_FLAG_CAN_LEAVE_ROUTE
#define FNPC_SURF FOLLOWER_NPC_FLAG_CAN_SURF
#define FNPC_WATERFALL FOLLOWER_NPC_FLAG_CAN_WATERFALL
#define FNPC_DIVE FOLLOWER_NPC_FLAG_CAN_DIVE
#define FNPC_WHITE_OUT FOLLOWER_NPC_FLAG_CLEAR_ON_WHITE_OUT
#define FNPC_ALL_LAND FOLLOWER_NPC_FLAG_ALL_LAND
#define FNPC_ALL_WATER FOLLOWER_NPC_FLAG_ALL_WATER
#define FNPC_ALL FOLLOWER_NPC_FLAG_ALL
#define FNPC_NONE 0
#define FNPC_ALWAYS 2
#endif // GUARD_CONSTANTS_FOLLOWER_NPC_H

View File

@ -11,6 +11,7 @@
#include "config/dexnav.h"
#include "config/summary_screen.h"
#include "config/ai.h"
#include "config/follower_npc.h"
// Invalid Versions show as "----------" in Gen 4 and Gen 5's summary screen.
// In Gens 6 and 7, invalid versions instead show "a distant land" in the summary screen.

View File

@ -128,6 +128,7 @@ u8 GetObjectEventIdByXY(s16 x, s16 y);
void SetObjectEventDirection(struct ObjectEvent *objectEvent, u8 direction);
u8 GetFirstInactiveObjectEventId(void);
u8 GetObjectEventIdByLocalId(u8);
void RemoveObjectEvent(struct ObjectEvent *objectEvent);
void RemoveObjectEventByLocalIdAndMap(u8 localId, u8 mapNum, u8 mapGroup);
void LoadSpecialObjectReflectionPalette(u16 tag, u8 slot);
void TryMoveObjectEventToMapCoords(u8 localId, u8 mapNum, u8 mapGroup, s16 x, s16 y);
@ -253,6 +254,11 @@ void CameraObjectFreeze(void);
u8 GetObjectEventBerryTreeId(u8 objectEventId);
void SetBerryTreeJustPicked(u8 mapId, u8 mapNumber, u8 mapGroup);
bool8 IsBerryTreeSparkling(u8 localId, u8 mapNum, u8 mapGroup);
const struct ObjectEventTemplate *GetObjectEventTemplateByLocalIdAndMap(u8 localId, u8 mapNum, u8 mapGroup);
u8 TrySpawnObjectEventTemplate(const struct ObjectEventTemplate *objectEventTemplate, u8 mapNum, u8 mapGroup, s16 cameraX, s16 cameraY);
bool8 GetFollowerInfo(u32 *species, bool32 *shiny, bool32 *female);
const struct ObjectEventGraphicsInfo *SpeciesToGraphicsInfo(u32 species, bool32 shiny, bool32 female);
u16 GetObjectEventFlagIdByLocalIdAndMap(u8 localId, u8 mapNum, u8 mapGroup);
void MovementType_None(struct Sprite *);
void MovementType_LookAround(struct Sprite *);
@ -311,7 +317,6 @@ void MovementType_Invisible(struct Sprite *);
void MovementType_WalkSlowlyInPlace(struct Sprite *);
void MovementType_FollowPlayer(struct Sprite *);
u8 GetSlideMovementAction(u32);
u8 GetJumpMovementAction(u32);
u8 GetJump2MovementAction(u32);
u8 CopySprite(struct Sprite *sprite, s16 x, s16 y, u8 subpriority);
u8 CreateCopySpriteAt(struct Sprite *sprite, s16 x, s16 y, u8 subpriority);
@ -505,5 +510,6 @@ u8 GetSidewaysStairsCollision(struct ObjectEvent *objectEvent, u8 dir, u8 curren
bool8 MovementAction_EmoteX_Step0(struct ObjectEvent *, struct Sprite *);
bool8 MovementAction_EmoteDoubleExclamationMark_Step0(struct ObjectEvent *, struct Sprite *);
bool8 PlayerIsUnderWaterfall(struct ObjectEvent *objectEvent);
#endif //GUARD_EVENT_OBJECT_MOVEMENT_H

View File

@ -653,6 +653,27 @@ extern const u8 EventScript_VsSeekerChargingDone[];
extern const u8 Common_Movement_FollowerSafeStart[];
extern const u8 Common_Movement_FollowerSafeEnd[];
extern const u8 Common_Movement_WalkUpSlow[];
extern const u8 Common_Movement_WalkDownSlow[];
extern const u8 Common_Movement_WalkRightSlow[];
extern const u8 Common_Movement_WalkLeftSlow[];
extern const u8 Common_Movement_WalkUp[];
extern const u8 Common_Movement_WalkDown[];
extern const u8 Common_Movement_WalkRight[];
extern const u8 Common_Movement_WalkLeft[];
extern const u8 Common_Movement_WalkUpFast[];
extern const u8 Common_Movement_WalkDownFast[];
extern const u8 Common_Movement_WalkRightFast[];
extern const u8 Common_Movement_WalkLeftFast[];
extern const u8 Common_Movement_WalkUpFaster[];
extern const u8 Common_Movement_WalkDownFaster[];
extern const u8 Common_Movement_WalkRightFaster[];
extern const u8 Common_Movement_WalkLeftFaster[];
extern const u8 Common_Movement_FaceUp[];
extern const u8 Common_Movement_FaceDown[];
extern const u8 Common_Movement_FaceRight[];
extern const u8 Common_Movement_FaceLeft[];
extern const u8 EventScript_CancelMessageBox[];
extern const u8 Common_EventScript_ShowPokemonCenterSign[];
extern const u8 Common_EventScript_ShowPokemartSign[];

View File

@ -2,6 +2,7 @@
#define GUARD_FIELD_PLAYER_AVATAR_H
void PlayerStep(u8 direction, u16 newKeys, u16 heldKeys);
bool8 TryDoMetatileBehaviorForcedMovement();
void ClearPlayerAvatarInfo(void);
void SetPlayerAvatarExtraStateTransition(u16, u8);
u8 GetPlayerAvatarGenderByGraphicsId(u16);

View File

@ -44,5 +44,8 @@ void WriteFlashScanlineEffectBuffer(u8 flashLevel);
bool8 IsPlayerStandingStill(void);
void DoStairWarp(u16 metatileBehavior, u16 delay);
bool32 IsDirectionalStairWarpMetatileBehavior(u16 metatileBehavior, u8 playerDirection);
void SetPlayerVisibility(bool8 visible);
void Task_WarpAndLoadMap(u8 taskId);
void Task_DoDoorWarp(u8 taskId);
#endif // GUARD_FIELD_SCREEN_EFFECT_H

129
include/follower_npc.h Normal file
View File

@ -0,0 +1,129 @@
#ifndef GUARD_FOLLOWER_NPC_H
#define GUARD_FOLLOWER_NPC_H
#include "constants/follower_npc.h"
#define MOVEMENT_INVALID 0xFE
struct FollowerNPCSpriteGraphics
{
u16 normalId;
u16 machBikeId;
u16 acroBikeId;
u16 surfId;
u16 underwaterId;
};
enum FollowerNPCDataTypes
{
FNPC_DATA_IN_PROGRESS,
FNPC_DATA_WARP_END,
FNPC_DATA_SURF_BLOB,
FNPC_DATA_COME_OUT_DOOR,
FNPC_DATA_OBJ_ID,
FNPC_DATA_CURRENT_SPRITE,
FNPC_DATA_DELAYED_STATE,
FNPC_DATA_MAP_ID,
FNPC_DATA_MAP_NUM,
FNPC_DATA_MAP_GROUP,
FNPC_DATA_EVENT_FLAG,
FNPC_DATA_GFX_ID,
FNPC_DATA_FOLLOWER_FLAGS,
FNPC_DATA_BATTLE_PARTNER
};
enum FollowerNPCSpriteTypes
{
FOLLOWER_NPC_SPRITE_INDEX_NORMAL,
FOLLOWER_NPC_SPRITE_INDEX_MACH_BIKE,
FOLLOWER_NPC_SPRITE_INDEX_ACRO_BIKE,
FOLLOWER_NPC_SPRITE_INDEX_SURF,
FOLLOWER_NPC_SPRITE_INDEX_UNDERWATER,
};
enum FollowerNPCDoorStairsStates
{
FNPC_DOOR_NONE,
FNPC_DOOR_NEEDS_TO_EXIT
};
enum FollowerNPCWarpEndStates
{
FNPC_WARP_NONE,
FNPC_WARP_REAPPEAR
};
enum FollowerNPCSurfBlobStates
{
FNPC_SURF_BLOB_NONE,
FNPC_SURF_BLOB_NEW,
FNPC_SURF_BLOB_RECREATE,
FNPC_SURF_BLOB_DESTROY
};
enum FollowerNPCOutOfDoorTaskStates{
OPEN_DOOR,
NPC_WALK_OUT,
CLOSE_DOOR,
UNFREEZE_OBJECTS,
REALLOW_MOVEMENT
};
enum FollowerNPCHandleEscalatorFinishTaskStates{
MOVE_TO_PLAYER_POS,
WAIT_FOR_PLAYER_MOVE,
SHOW_FOLLOWER_DOWN,
MOVE_FOLLOWER_DOWN,
SHOW_FOLLOWER_UP,
MOVE_FOLLOWER_UP,
MOVEMENT_FINISH
};
void SetFollowerNPCData(enum FollowerNPCDataTypes type, u32 value);
const u8 *GetFollowerNPCScriptPointer(void);
u32 GetFollowerNPCData(enum FollowerNPCDataTypes type);
void ClearFollowerNPCData(void);
u32 DetermineFollowerNPCState(struct ObjectEvent *follower, u32 state, u32 direction);
void SetFollowerNPCSprite(u32 spriteIndex);
bool32 PlayerHasFollowerNPC(void);
void NPCFollow(struct ObjectEvent *npc, u32 state, bool32 ignoreScriptActive);
void CreateFollowerNPCAvatar(void);
void FollowerNPC_HandleSprite(void);
u32 DetermineFollowerNPCDirection(struct ObjectEvent *player, struct ObjectEvent *follower);
u32 GetFollowerNPCObjectId(void);
bool32 CheckFollowerNPCFlag(u32 flag);
bool32 FollowerNPC_IsCollisionExempt(struct ObjectEvent *obstacle, struct ObjectEvent *collider);
void HideNPCFollower(void);
void FollowerNPC_WarpSetEnd(void);
bool32 FollowerNPCCanBike(void);
void FollowerNPC_HandleBike(void);
void FollowerNPC_FollowerToWater(void);
void FollowerNPC_SetIndicatorToRecreateSurfBlob(void);
void FollowerNPC_BindToSurfBlobOnReloadScreen(void);
void PrepareFollowerNPCDismountSurf(void);
bool32 FollowerNPCComingThroughDoor(void);
void FollowerNPC_SetIndicatorToComeOutDoor(void);
void EscalatorMoveFollowerNPC(u32 movementType);
void EscalatorMoveFollowerNPCFinish(void);
void FollowerNPCWalkIntoPlayerForLeaveMap(void);
void FollowerNPCHideForLeaveMap(struct ObjectEvent *follower);
void FollowerNPCReappearAfterLeaveMap(struct ObjectEvent *follower, struct ObjectEvent *player);
void FollowerNPCFaceAfterLeaveMap(void);
bool32 FollowerNPCIsBattlePartner(void);
bool32 IsNPCFollowerWildBattle(void);
void PrepareForFollowerNPCBattle(void);
void RestorePartyAfterFollowerNPCBattle(void);
void FollowerNPC_TryRemoveFollowerOnWhiteOut(void);
void Task_MoveNPCFollowerAfterForcedMovement(u8 taskId);
void Task_HideNPCFollowerAfterMovementFinish(u8 taskId);
#endif // GUARD_FOLLOWER_NPC_H

View File

@ -0,0 +1,33 @@
#ifndef GUARD_FOLLOWER_NPC_ALTERNATE_SPRITES_H
#define GUARD_FOLLOWER_NPC_ALTERNATE_SPRITES_H
#include "constants/event_objects.h"
// This is where alternate sprites for NPC followers should be listed.
// If the appropriate alternate sprites are not added here, the NPC follower will not use the correct sprites for biking,
// surfing, etc.
// The normalId tells the game what GFX to tie the associated alternate sprites to. This is usually the sprite you would
// give to the object in Porymap.
// For example, Rival May's normalId is OBJ_EVENT_GFX_RIVAL_MAY_NORMAL. All the rest of the sprites in that same block
// will be used with an NPC follower that has that normalId.
static const struct FollowerNPCSpriteGraphics gFollowerNPCAlternateSprites[] =
{
{
.normalId = OBJ_EVENT_GFX_RIVAL_MAY_NORMAL,
.machBikeId = OBJ_EVENT_GFX_RIVAL_MAY_MACH_BIKE,
.acroBikeId = OBJ_EVENT_GFX_RIVAL_MAY_ACRO_BIKE,
.surfId = OBJ_EVENT_GFX_RIVAL_MAY_SURFING,
.underwaterId = OBJ_EVENT_GFX_MAY_UNDERWATER,
},
{
.normalId = OBJ_EVENT_GFX_RIVAL_BRENDAN_NORMAL,
.machBikeId = OBJ_EVENT_GFX_RIVAL_BRENDAN_MACH_BIKE,
.acroBikeId = OBJ_EVENT_GFX_RIVAL_BRENDAN_ACRO_BIKE,
.surfId = OBJ_EVENT_GFX_RIVAL_BRENDAN_SURFING,
.underwaterId = OBJ_EVENT_GFX_BRENDAN_UNDERWATER,
},
};
#endif // GUARD_FOLLOWER_NPC_ALTERNATE_SPRITES_H

View File

@ -3,8 +3,8 @@
#include <stddef.h>
#define TRUE 1
#define FALSE 0
#define TRUE 1
#define FALSE 0
#define IWRAM_DATA __attribute__((section(".bss")))
#define EWRAM_DATA __attribute__((section(".sbss")))

View File

@ -200,6 +200,31 @@ struct Time
/*0x04*/ s8 seconds;
};
struct NPCFollowerMapData
{
u8 id;
u8 number;
u8 group;
};
struct NPCFollower
{
u8 inProgress:1;
u8 warpEnd:1;
u8 createSurfBlob:3;
u8 comeOutDoorStairs:3;
u8 objId;
u8 currentSprite;
u8 delayedState;
struct NPCFollowerMapData map;
struct Coords16 log;
const u8 *script;
u16 flag;
u16 graphicsId;
u16 flags;
u8 battlePartner; // If you have more than 255 total battle partners defined, change this to a u16
};
#include "constants/items.h"
#define ITEM_FLAGS_COUNT ((ITEMS_COUNT / 8) + ((ITEMS_COUNT % 8) ? 1 : 0))
@ -208,6 +233,9 @@ struct SaveBlock3
#if OW_USE_FAKE_RTC
struct SiiRtcInfo fakeRTC;
#endif
#if FNPC_ENABLE_NPC_FOLLOWERS
struct NPCFollower NPCfollower;
#endif
#if OW_SHOW_ITEM_DESCRIPTIONS == OW_ITEM_DESCRIPTIONS_FIRST_TIME
u8 itemFlags[ITEM_FLAGS_COUNT];
#endif

View File

@ -26,6 +26,7 @@
#include "event_data.h"
#include "evolution_scene.h"
#include "field_weather.h"
#include "follower_npc.h"
#include "graphics.h"
#include "gpu_regs.h"
#include "international_string_util.h"
@ -430,7 +431,11 @@ void CB2_InitBattle(void)
gLoadFail = FALSE;
#endif // T_SHOULD_RUN_MOVE_ANIM
if (gBattleTypeFlags & BATTLE_TYPE_MULTI)
#if T_SHOULD_RUN_MOVE_ANIM
gLoadFail = FALSE;
#endif // T_SHOULD_RUN_MOVE_ANIM
if (gBattleTypeFlags & BATTLE_TYPE_MULTI && gBattleTypeFlags & BATTLE_TYPE_TRAINER)
{
if (gBattleTypeFlags & BATTLE_TYPE_RECORDED)
{
@ -1411,7 +1416,9 @@ static void CB2_PreInitIngamePlayerPartnerBattle(void)
*savedCallback = gMain.savedCallback;
*savedBattleTypeFlags = gBattleTypeFlags;
gMain.savedCallback = CB2_PreInitIngamePlayerPartnerBattle;
ShowPartyMenuToShowcaseMultiBattleParty();
if (!PlayerHasFollowerNPC() || !FollowerNPCIsBattlePartner() || (FNPC_NPC_FOLLOWER_PARTY_PREVIEW && FollowerNPCIsBattlePartner()))
ShowPartyMenuToShowcaseMultiBattleParty();
break;
case 1:
if (!gPaletteFade.active)

View File

@ -2,6 +2,7 @@
#include "battle.h"
#include "load_save.h"
#include "battle_setup.h"
#include "battle_tower.h"
#include "battle_transition.h"
#include "main.h"
#include "task.h"
@ -11,6 +12,7 @@
#include "metatile_behavior.h"
#include "field_player_avatar.h"
#include "fieldmap.h"
#include "follower_npc.h"
#include "random.h"
#include "starter_choose.h"
#include "script_pokemon_util.h"
@ -246,6 +248,7 @@ static void Task_BattleStart(u8 taskId)
case 1:
if (IsBattleTransitionDone() == TRUE)
{
PrepareForFollowerNPCBattle();
CleanupOverworldWindowsAndTilemaps();
SetMainCallback2(CB2_InitBattle);
RestartWildEncounterImmunitySteps();
@ -327,7 +330,11 @@ static void DoStandardWildBattle(bool32 isDouble)
StopPlayerAvatar();
gMain.savedCallback = CB2_EndWildBattle;
gBattleTypeFlags = 0;
if (isDouble)
if (IsNPCFollowerWildBattle())
{
gBattleTypeFlags |= BATTLE_TYPE_MULTI | BATTLE_TYPE_INGAME_PARTNER | BATTLE_TYPE_DOUBLE;
}
else if (isDouble)
gBattleTypeFlags |= BATTLE_TYPE_DOUBLE;
if (InBattlePyramid())
{
@ -573,6 +580,15 @@ static void CB2_EndWildBattle(void)
{
CpuFill16(0, (void *)(BG_PLTT), BG_PLTT_SIZE);
ResetOamRange(0, 128);
if (IsNPCFollowerWildBattle())
{
RestorePartyAfterFollowerNPCBattle();
if (FNPC_FLAG_HEAL_AFTER_FOLLOWER_BATTLE != 0
&& (FNPC_FLAG_HEAL_AFTER_FOLLOWER_BATTLE == FNPC_ALWAYS
|| FlagGet(FNPC_FLAG_HEAL_AFTER_FOLLOWER_BATTLE)))
HealPlayerParty();
}
if (IsPlayerDefeated(gBattleOutcome) == TRUE && !InBattlePyramid() && !InBattlePike())
{
@ -1160,9 +1176,24 @@ void ClearTrainerFlag(u16 trainerId)
void BattleSetup_StartTrainerBattle(void)
{
if (gNoOfApproachingTrainers == 2)
gBattleTypeFlags = (BATTLE_TYPE_DOUBLE | BATTLE_TYPE_TWO_OPPONENTS | BATTLE_TYPE_TRAINER);
{
if (FollowerNPCIsBattlePartner())
gBattleTypeFlags = (BATTLE_TYPE_MULTI | BATTLE_TYPE_DOUBLE | BATTLE_TYPE_INGAME_PARTNER | BATTLE_TYPE_TWO_OPPONENTS | BATTLE_TYPE_TRAINER);
else
gBattleTypeFlags = (BATTLE_TYPE_DOUBLE | BATTLE_TYPE_TWO_OPPONENTS | BATTLE_TYPE_TRAINER);
}
else
gBattleTypeFlags = (BATTLE_TYPE_TRAINER);
{
if (FollowerNPCIsBattlePartner())
{
gBattleTypeFlags = (BATTLE_TYPE_MULTI | BATTLE_TYPE_INGAME_PARTNER | BATTLE_TYPE_DOUBLE | BATTLE_TYPE_TRAINER);
TRAINER_BATTLE_PARAM.opponentB = 0xFFFF;
}
else
{
gBattleTypeFlags = (BATTLE_TYPE_TRAINER);
}
}
if (InBattlePyramid())
{
@ -1252,6 +1283,15 @@ static void CB2_EndTrainerBattle(void)
{
HandleBattleVariantEndParty();
if (FollowerNPCIsBattlePartner())
{
RestorePartyAfterFollowerNPCBattle();
if (FNPC_FLAG_HEAL_AFTER_FOLLOWER_BATTLE != 0
&& (FNPC_FLAG_HEAL_AFTER_FOLLOWER_BATTLE == FNPC_ALWAYS
|| FlagGet(FNPC_FLAG_HEAL_AFTER_FOLLOWER_BATTLE)))
HealPlayerParty();
}
if (TRAINER_BATTLE_PARAM.opponentA == TRAINER_SECRET_BASE)
{
DowngradeBadPoison();

View File

@ -75,7 +75,6 @@ static void FillTentTrainerParty_(u16 trainerId, u8 firstMonId, u8 monCount);
static void FillFactoryFrontierTrainerParty(u16 trainerId, u8 firstMonId);
static void FillFactoryTentTrainerParty(u16 trainerId, u8 firstMonId);
static u8 GetFrontierTrainerFixedIvs(u16 trainerId);
static void FillPartnerParty(u16 trainerId);
#if FREE_BATTLE_TOWER_E_READER == FALSE
static void SetEReaderTrainerChecksum(struct BattleTowerEReaderTrainer *ereaderTrainer);
#endif //FREE_BATTLE_TOWER_E_READER
@ -2958,7 +2957,7 @@ void TryHideBattleTowerReporter(void)
#define STEVEN_OTID 61226
static void FillPartnerParty(u16 trainerId)
void FillPartnerParty(u16 trainerId)
{
s32 i, j, k;
u32 firstIdPart = 0, secondIdPart = 0, thirdIdPart = 0;

View File

@ -2571,7 +2571,7 @@ bool32 HasNoMonsToSwitch(u32 battler, u8 partyIdBattlerOn1, u8 partyIdBattlerOn2
party = gEnemyParty;
// Edge case: If both opposing Pokemon were knocked out on the same turn,
// make sure opponent only sents out the final Pokemon once.
// make sure opponent only sends out the final Pokemon once.
if (battler == playerId
&& (gHitMarker & HITMARKER_FAINTED(flankId))
&& (gHitMarker & HITMARKER_FAINTED(playerId)))

View File

@ -279,6 +279,15 @@ u8 MovementAction_WalkSlowStairsLeft_Step0(struct ObjectEvent *objectEvent, stru
u8 MovementAction_WalkSlowStairsLeft_Step1(struct ObjectEvent *objectEvent, struct Sprite *sprite);
u8 MovementAction_WalkSlowStairsRight_Step0(struct ObjectEvent *objectEvent, struct Sprite *sprite);
u8 MovementAction_WalkSlowStairsRight_Step1(struct ObjectEvent *objectEvent, struct Sprite *sprite);
u8 MovementAction_SurfStillDown_Step0(struct ObjectEvent *objectEvent, struct Sprite *sprite);
u8 MovementAction_SurfStillDown_Step1(struct ObjectEvent *objectEvent, struct Sprite *sprite);
u8 MovementAction_SurfStillUp_Step0(struct ObjectEvent *objectEvent, struct Sprite *sprite);
u8 MovementAction_SurfStillUp_Step1(struct ObjectEvent *objectEvent, struct Sprite *sprite);
u8 MovementAction_SurfStillLeft_Step0(struct ObjectEvent *objectEvent, struct Sprite *sprite);
u8 MovementAction_SurfStillLeft_Step1(struct ObjectEvent *objectEvent, struct Sprite *sprite);
u8 MovementAction_SurfStillRight_Step0(struct ObjectEvent *objectEvent, struct Sprite *sprite);
u8 MovementAction_SurfStillRight_Step1(struct ObjectEvent *objectEvent, struct Sprite *sprite);
u8 (*const gMovementActionFuncs_FaceDown[])(struct ObjectEvent *, struct Sprite *);
u8 (*const gMovementActionFuncs_FaceUp[])(struct ObjectEvent *, struct Sprite *);
@ -450,6 +459,10 @@ u8 (*const gMovementActionFuncs_WalkSlowStairsDown[])(struct ObjectEvent *, stru
u8 (*const gMovementActionFuncs_WalkSlowStairsUp[])(struct ObjectEvent *, struct Sprite *);
u8 (*const gMovementActionFuncs_WalkSlowStairsLeft[])(struct ObjectEvent *, struct Sprite *);
u8 (*const gMovementActionFuncs_WalkSlowStairsRight[])(struct ObjectEvent *, struct Sprite *);
u8 (*const gMovementActionFuncs_SurfStillDown[])(struct ObjectEvent *, struct Sprite *);
u8 (*const gMovementActionFuncs_SurfStillUp[])(struct ObjectEvent *, struct Sprite *);
u8 (*const gMovementActionFuncs_SurfStillLeft[])(struct ObjectEvent *, struct Sprite *);
u8 (*const gMovementActionFuncs_SurfStillRight[])(struct ObjectEvent *, struct Sprite *);
u8 (*const *const gMovementActionFuncs[])(struct ObjectEvent *, struct Sprite *) = {
[MOVEMENT_ACTION_FACE_DOWN] = gMovementActionFuncs_FaceDown,
@ -622,6 +635,10 @@ u8 (*const *const gMovementActionFuncs[])(struct ObjectEvent *, struct Sprite *)
[MOVEMENT_ACTION_WALK_SLOW_STAIRS_UP] = gMovementActionFuncs_WalkSlowStairsUp,
[MOVEMENT_ACTION_WALK_SLOW_STAIRS_LEFT] = gMovementActionFuncs_WalkSlowStairsLeft,
[MOVEMENT_ACTION_WALK_SLOW_STAIRS_RIGHT] = gMovementActionFuncs_WalkSlowStairsRight,
[MOVEMENT_ACTION_SURF_STILL_DOWN] = gMovementActionFuncs_SurfStillDown,
[MOVEMENT_ACTION_SURF_STILL_UP] = gMovementActionFuncs_SurfStillUp,
[MOVEMENT_ACTION_SURF_STILL_LEFT] = gMovementActionFuncs_SurfStillLeft,
[MOVEMENT_ACTION_SURF_STILL_RIGHT] = gMovementActionFuncs_SurfStillRight,
};
u8 (*const gMovementActionFuncs_FaceDown[])(struct ObjectEvent *, struct Sprite *) = {
@ -1631,3 +1648,31 @@ bool8 (*const gMovementActionFuncs_WalkSlowStairsRight[])(struct ObjectEvent *,
MovementAction_WalkSlowStairsRight_Step1,
MovementAction_PauseSpriteAnim,
};
u8 (*const gMovementActionFuncs_SurfStillDown[])(struct ObjectEvent *, struct Sprite *) =
{
MovementAction_SurfStillDown_Step0,
MovementAction_SurfStillDown_Step1,
MovementAction_PauseSpriteAnim,
};
u8 (*const gMovementActionFuncs_SurfStillUp[])(struct ObjectEvent *, struct Sprite *) =
{
MovementAction_SurfStillUp_Step0,
MovementAction_SurfStillUp_Step1,
MovementAction_PauseSpriteAnim,
};
u8 (*const gMovementActionFuncs_SurfStillLeft[])(struct ObjectEvent *, struct Sprite *) =
{
MovementAction_SurfStillLeft_Step0,
MovementAction_SurfStillLeft_Step1,
MovementAction_PauseSpriteAnim,
};
u8 (*const gMovementActionFuncs_SurfStillRight[])(struct ObjectEvent *, struct Sprite *) =
{
MovementAction_SurfStillRight_Step0,
MovementAction_SurfStillRight_Step1,
MovementAction_PauseSpriteAnim,
};

View File

@ -18,6 +18,7 @@
#include "field_player_avatar.h"
#include "field_weather.h"
#include "fieldmap.h"
#include "follower_npc.h"
#include "follower_helper.h"
#include "gpu_regs.h"
#include "graphics.h"
@ -47,6 +48,7 @@
#include "constants/items.h"
#include "constants/map_types.h"
#include "constants/mauville_old_man.h"
#include "constants/metatile_behaviors.h"
#include "constants/rgb.h"
#include "constants/region_map_sections.h"
#include "constants/songs.h"
@ -169,7 +171,7 @@ static u16 GetObjectEventFlagIdByObjectEventId(u8);
static void UpdateObjectEventVisibility(struct ObjectEvent *, struct Sprite *);
static void MakeSpriteTemplateFromObjectEventTemplate(const struct ObjectEventTemplate *, struct SpriteTemplate *, const struct SubspriteTable **);
static void GetObjectEventMovingCameraOffset(s16 *, s16 *);
static const struct ObjectEventTemplate *GetObjectEventTemplateByLocalIdAndMap(u8, u8, u8);
const struct ObjectEventTemplate *GetObjectEventTemplateByLocalIdAndMap(u8 localId, u8 mapNum, u8 mapGroup);
static void RemoveObjectEventIfOutsideView(struct ObjectEvent *);
static void SpawnObjectEventOnReturnToField(u8, s16, s16);
static void SetPlayerAvatarObjectEventIdAndObjectId(u8, u8);
@ -199,9 +201,8 @@ static u8 DoJumpSpriteMovement(struct Sprite *);
static u8 DoJumpSpecialSpriteMovement(struct Sprite *);
static void CreateLevitateMovementTask(struct ObjectEvent *);
static void DestroyLevitateMovementTask(u8);
static bool8 GetFollowerInfo(u32 *species, bool32 *shiny, bool32 *female);
static u32 LoadDynamicFollowerPalette(u32 species, bool32 shiny, bool32 female);
static const struct ObjectEventGraphicsInfo *SpeciesToGraphicsInfo(u32 species, bool32 shiny, bool32 female);
const struct ObjectEventGraphicsInfo *SpeciesToGraphicsInfo(u32 species, bool32 shiny, bool32 female);
static bool8 NpcTakeStep(struct Sprite *);
static bool8 AreElevationsCompatible(u8, u8);
static void CopyObjectGraphicsInfoToSpriteTemplate_WithMovementType(u16 graphicsId, u16 movementType, struct SpriteTemplate *spriteTemplate, const struct SubspriteTable **subspriteTables);
@ -1353,7 +1354,12 @@ u8 GetFirstInactiveObjectEventId(void)
u8 GetObjectEventIdByLocalIdAndMap(u8 localId, u8 mapNum, u8 mapGroupId)
{
if (localId < OBJ_EVENT_ID_DYNAMIC_BASE)
return GetObjectEventIdByLocalIdAndMapInternal(localId, mapNum, mapGroupId);
{
if (PlayerHasFollowerNPC() && localId == OBJ_EVENT_ID_NPC_FOLLOWER)
return GetFollowerNPCObjectId();
else
return GetObjectEventIdByLocalIdAndMapInternal(localId, mapNum, mapGroupId);
}
return GetObjectEventIdByLocalId(localId);
}
@ -1506,7 +1512,7 @@ static bool8 GetAvailableObjectEventId(u16 localId, u8 mapNum, u8 mapGroup, u8 *
return FALSE;
}
static void RemoveObjectEvent(struct ObjectEvent *objectEvent)
void RemoveObjectEvent(struct ObjectEvent *objectEvent)
{
objectEvent->active = FALSE;
RemoveObjectEventInternal(objectEvent);
@ -1727,7 +1733,7 @@ static u8 TrySetupObjectEventSprite(const struct ObjectEventTemplate *objectEven
return objectEventId;
}
static u8 TrySpawnObjectEventTemplate(const struct ObjectEventTemplate *objectEventTemplate, u8 mapNum, u8 mapGroup, s16 cameraX, s16 cameraY)
u8 TrySpawnObjectEventTemplate(const struct ObjectEventTemplate *objectEventTemplate, u8 mapNum, u8 mapGroup, s16 cameraX, s16 cameraY)
{
u8 objectEventId;
u16 graphicsId = objectEventTemplate->graphicsId;
@ -1968,7 +1974,7 @@ struct ObjectEvent *GetFollowerObject(void)
}
// Return graphicsInfo for a pokemon species & form
static const struct ObjectEventGraphicsInfo *SpeciesToGraphicsInfo(u32 species, bool32 shiny, bool32 female)
const struct ObjectEventGraphicsInfo *SpeciesToGraphicsInfo(u32 species, bool32 shiny, bool32 female)
{
const struct ObjectEventGraphicsInfo *graphicsInfo = NULL;
#if OW_POKEMON_OBJECT_EVENTS
@ -2180,7 +2186,7 @@ static bool8 GetMonInfo(struct Pokemon *mon, u32 *species, bool32 *shiny, bool32
}
// Retrieve graphic information about the following pokemon, if any
static bool8 GetFollowerInfo(u32 *species, bool32 *shiny, bool32 *female)
bool8 GetFollowerInfo(u32 *species, bool32 *shiny, bool32 *female)
{
return GetMonInfo(GetFirstLiveMon(), species, shiny, female);
}
@ -2197,13 +2203,16 @@ void UpdateFollowingPokemon(void)
// 1. GetFollowerInfo returns FALSE
// 2. Map is indoors and gfx is larger than 32x32
// 3. flag is set
// 4. a follower NPC is present
if (OW_POKEMON_OBJECT_EVENTS == FALSE
|| OW_FOLLOWERS_ENABLED == FALSE
|| FlagGet(B_FLAG_FOLLOWERS_DISABLED)
|| !GetFollowerInfo(&species, &shiny, &female)
|| SpeciesToGraphicsInfo(species, shiny, female) == NULL
|| (gMapHeader.mapType == MAP_TYPE_INDOOR && SpeciesToGraphicsInfo(species, shiny, female)->oam->size > ST_OAM_SIZE_2)
|| FlagGet(FLAG_TEMP_HIDE_FOLLOWER))
|| FlagGet(FLAG_TEMP_HIDE_FOLLOWER)
|| PlayerHasFollowerNPC()
)
{
RemoveFollowingPokemon();
return;
@ -2776,7 +2785,9 @@ void RemoveObjectEventsOutsideView(void)
// Followers should not go OOB, or their sprites may be freed early during a cross-map scripting event,
// such as Wally's Ralts catch sequence
if (objectEvent->active && !objectEvent->isPlayer && objectEvent->localId != OBJ_EVENT_ID_FOLLOWER)
if (objectEvent->active && !objectEvent->isPlayer && objectEvent->localId != OBJ_EVENT_ID_FOLLOWER
&& (PlayerHasFollowerNPC() && i != GetFollowerNPCObjectId())
)
RemoveObjectEventIfOutsideView(objectEvent);
}
}
@ -3522,7 +3533,7 @@ const u8 *GetObjectEventScriptPointerByObjectEventId(u8 objectEventId)
return GetObjectEventScriptPointerByLocalIdAndMap(gObjectEvents[objectEventId].localId, gObjectEvents[objectEventId].mapNum, gObjectEvents[objectEventId].mapGroup);
}
static u16 GetObjectEventFlagIdByLocalIdAndMap(u8 localId, u8 mapNum, u8 mapGroup)
u16 GetObjectEventFlagIdByLocalIdAndMap(u8 localId, u8 mapNum, u8 mapGroup)
{
const struct ObjectEventTemplate *obj = GetObjectEventTemplateByLocalIdAndMap(localId, mapNum, mapGroup);
#ifdef UBFIX
@ -3569,7 +3580,7 @@ u8 GetObjectEventBerryTreeId(u8 objectEventId)
return gObjectEvents[objectEventId].trainerRange_berryTreeId;
}
static const struct ObjectEventTemplate *GetObjectEventTemplateByLocalIdAndMap(u8 localId, u8 mapNum, u8 mapGroup)
const struct ObjectEventTemplate *GetObjectEventTemplateByLocalIdAndMap(u8 localId, u8 mapNum, u8 mapGroup)
{
const struct ObjectEventTemplate *templates;
const struct MapHeader *mapHeader;
@ -6429,7 +6440,9 @@ u32 GetObjectObjectCollidesWith(struct ObjectEvent *objectEvent, s16 x, s16 y, b
for (i = 0; i < OBJECT_EVENTS_COUNT; i++)
{
curObject = &gObjectEvents[i];
if (curObject->active && (curObject->movementType != MOVEMENT_TYPE_FOLLOW_PLAYER || objectEvent != &gObjectEvents[gPlayerAvatar.objectEventId]) && curObject != objectEvent)
if (curObject->active && (curObject->movementType != MOVEMENT_TYPE_FOLLOW_PLAYER || objectEvent != &gObjectEvents[gPlayerAvatar.objectEventId]) && curObject != objectEvent
&& !FollowerNPC_IsCollisionExempt(curObject, objectEvent)
)
{
// check for collision if curObject is active, not the object in question, and not exempt from collisions
if ((curObject->currentCoords.x == x && curObject->currentCoords.y == y) || (curObject->previousCoords.x == x && curObject->previousCoords.y == y))
@ -6621,6 +6634,7 @@ bool8 ObjectEventSetHeldMovement(struct ObjectEvent *objectEvent, u8 movementAct
objectEvent->heldMovementActive = TRUE;
objectEvent->heldMovementFinished = FALSE;
gSprites[objectEvent->spriteId].sActionFuncId = 0;
NPCFollow(objectEvent, movementActionId, FALSE);
// When player is moved via script, set copyable movement
// for any followers via a lookup table
@ -11206,6 +11220,20 @@ bool8 MovementAction_EmoteDoubleExclamationMark_Step0(struct ObjectEvent *object
return TRUE;
}
bool8 PlayerIsUnderWaterfall(struct ObjectEvent *objectEvent)
{
s16 x;
s16 y;
x = objectEvent->currentCoords.x;
y = objectEvent->currentCoords.y;
MoveCoordsInDirection(DIR_NORTH, &x, &y, 0, 1);
if (MetatileBehavior_IsWaterfall(MapGridGetMetatileBehaviorAt(x, y)))
return TRUE;
return FALSE;
}
// Get gfx data from daycare pokemon and store it in vars
void GetDaycareGraphics(struct ScriptContext *ctx)
{
@ -11385,3 +11413,86 @@ static u16 GetUnownSpecies(struct Pokemon *mon)
return SPECIES_UNOWN;
return SPECIES_UNOWN_B + form - 1;
}
static void InitMovementSurfStill(struct ObjectEvent *objectEvent, struct Sprite *sprite, u8 direction, u8 speed)
{
u8 (*functions[ARRAY_COUNT(sDirectionAnimFuncsBySpeed)])(u8);
memcpy(functions, sDirectionAnimFuncsBySpeed, sizeof sDirectionAnimFuncsBySpeed);
InitNpcForMovement(objectEvent, sprite, direction, speed);
ObjectEventTurn(objectEvent, direction);
}
bool8 MovementAction_SurfStillDown_Step0(struct ObjectEvent *objectEvent, struct Sprite *sprite)
{
InitMovementSurfStill(objectEvent, sprite, DIR_SOUTH, MOVE_SPEED_FAST_1);
sprite->animPaused = TRUE;
return MovementAction_SurfStillDown_Step1(objectEvent, sprite);
}
bool8 MovementAction_SurfStillDown_Step1(struct ObjectEvent *objectEvent, struct Sprite *sprite)
{
if (UpdateMovementNormal(objectEvent, sprite))
{
sprite->sActionFuncId = 2;
return TRUE;
}
return FALSE;
}
bool8 MovementAction_SurfStillUp_Step0(struct ObjectEvent *objectEvent, struct Sprite *sprite)
{
InitMovementSurfStill(objectEvent, sprite, DIR_NORTH, MOVE_SPEED_FAST_1);
sprite->animPaused = TRUE;
return MovementAction_SurfStillUp_Step1(objectEvent, sprite);
}
bool8 MovementAction_SurfStillUp_Step1(struct ObjectEvent *objectEvent, struct Sprite *sprite)
{
if (UpdateMovementNormal(objectEvent, sprite))
{
sprite->sActionFuncId = 2;
return TRUE;
}
return FALSE;
}
bool8 MovementAction_SurfStillLeft_Step0(struct ObjectEvent *objectEvent, struct Sprite *sprite)
{
if (objectEvent->directionOverwrite)
InitMovementSurfStill(objectEvent, sprite, objectEvent->directionOverwrite, MOVE_SPEED_FAST_1);
else
InitMovementSurfStill(objectEvent, sprite, DIR_WEST, MOVE_SPEED_FAST_1);
sprite->animPaused = TRUE;
return MovementAction_SurfStillLeft_Step1(objectEvent, sprite);
}
bool8 MovementAction_SurfStillLeft_Step1(struct ObjectEvent *objectEvent, struct Sprite *sprite)
{
if (UpdateMovementNormal(objectEvent, sprite))
{
sprite->sActionFuncId = 2;
return TRUE;
}
return FALSE;
}
bool8 MovementAction_SurfStillRight_Step0(struct ObjectEvent *objectEvent, struct Sprite *sprite)
{
if (objectEvent->directionOverwrite)
InitMovementSurfStill(objectEvent, sprite, objectEvent->directionOverwrite, MOVE_SPEED_FAST_1);
else
InitMovementSurfStill(objectEvent, sprite, DIR_EAST, MOVE_SPEED_FAST_1);
sprite->animPaused = TRUE;
return MovementAction_SurfStillRight_Step1(objectEvent, sprite);
}
bool8 MovementAction_SurfStillRight_Step1(struct ObjectEvent *objectEvent, struct Sprite *sprite)
{
if (UpdateMovementNormal(objectEvent, sprite))
{
sprite->sActionFuncId = 2;
return TRUE;
}
return FALSE;
}

View File

@ -17,6 +17,7 @@
#include "field_screen_effect.h"
#include "field_specials.h"
#include "fldeff_misc.h"
#include "follower_npc.h"
#include "item_menu.h"
#include "link.h"
#include "match_call.h"
@ -399,6 +400,8 @@ static const u8 *GetInteractedObjectEventScript(struct MapPosition *position, u8
if (InTrainerHill() == TRUE)
script = GetTrainerHillTrainerScript();
else if (PlayerHasFollowerNPC() && objectEventId == GetFollowerNPCObjectId())
script = GetFollowerNPCScriptPointer();
else
script = GetObjectEventScriptPointerByObjectEventId(objectEventId);
@ -557,10 +560,14 @@ static const u8 *GetInteractedMetatileScript(struct MapPosition *position, u8 me
static const u8 *GetInteractedWaterScript(struct MapPosition *unused1, u8 metatileBehavior, u8 direction)
{
if (FlagGet(FLAG_BADGE05_GET) == TRUE && PartyHasMonWithSurf() == TRUE && IsPlayerFacingSurfableFishableWater() == TRUE)
if (FlagGet(FLAG_BADGE05_GET) == TRUE && PartyHasMonWithSurf() == TRUE && IsPlayerFacingSurfableFishableWater() == TRUE
&& CheckFollowerNPCFlag(FOLLOWER_NPC_FLAG_CAN_SURF)
)
return EventScript_UseSurf;
if (MetatileBehavior_IsWaterfall(metatileBehavior) == TRUE)
if (MetatileBehavior_IsWaterfall(metatileBehavior) == TRUE
&& CheckFollowerNPCFlag(FOLLOWER_NPC_FLAG_CAN_WATERFALL)
)
{
if (FlagGet(FLAG_BADGE08_GET) == TRUE && IsPlayerSurfingNorth() == TRUE)
return EventScript_UseWaterfall;
@ -572,6 +579,9 @@ static const u8 *GetInteractedWaterScript(struct MapPosition *unused1, u8 metati
static bool32 TrySetupDiveDownScript(void)
{
if (!CheckFollowerNPCFlag(FOLLOWER_NPC_FLAG_CAN_DIVE))
return FALSE;
if (FlagGet(FLAG_BADGE07_GET) && TrySetDiveWarp() == 2)
{
ScriptContext_SetupScript(EventScript_UseDive);
@ -582,6 +592,9 @@ static bool32 TrySetupDiveDownScript(void)
static bool32 TrySetupDiveEmergeScript(void)
{
if (!CheckFollowerNPCFlag(FOLLOWER_NPC_FLAG_CAN_DIVE))
return FALSE;
if (FlagGet(FLAG_BADGE07_GET) && gMapHeader.mapType == MAP_TYPE_UNDERWATER && TrySetDiveWarp() == 1)
{
ScriptContext_SetupScript(EventScript_UseDiveUnderwater);

View File

@ -12,6 +12,7 @@
#include "field_weather.h"
#include "fieldmap.h"
#include "fldeff.h"
#include "follower_npc.h"
#include "gpu_regs.h"
#include "main.h"
#include "malloc.h"
@ -145,6 +146,7 @@ static bool8 LavaridgeGym1FWarpEffect_Warp(struct Task *, struct ObjectEvent *,
static void Task_EscapeRopeWarpOut(u8);
static void EscapeRopeWarpOutEffect_Init(struct Task *);
static void EscapeRopeWarpOutEffect_HideFollowerNPC(struct Task *);
static void EscapeRopeWarpOutEffect_Spin(struct Task *);
static void FieldCallback_EscapeRopeWarpIn(void);
@ -696,6 +698,7 @@ static bool8 (*const sLavaridgeGym1FWarpEffectFuncs[])(struct Task *, struct Obj
static void (*const sEscapeRopeWarpOutEffectFuncs[])(struct Task *) =
{
EscapeRopeWarpOutEffect_Init,
EscapeRopeWarpOutEffect_HideFollowerNPC,
EscapeRopeWarpOutEffect_Spin,
};
@ -1377,32 +1380,61 @@ void FieldCallback_UseFly(void)
gFieldCallback = NULL;
}
#define taskState task->data[3]
#define fieldEffectStarted task->data[0]
static void Task_UseFly(u8 taskId)
{
struct ObjectEvent *follower = &gObjectEvents[GetFollowerNPCObjectId()];
struct Task *task;
task = &gTasks[taskId];
if (!task->data[0])
if (taskState == 0)
{
if (!IsWeatherNotFadingIn())
return;
gFieldEffectArguments[0] = GetCursorSelectionMonId();
if ((int)gFieldEffectArguments[0] > PARTY_SIZE - 1)
gFieldEffectArguments[0] = 0;
FieldEffectStart(FLDEFF_USE_FLY);
task->data[0]++;
if (!PlayerHasFollowerNPC())
{
taskState = 2;
}
else
{
FollowerNPCWalkIntoPlayerForLeaveMap();
taskState++;
}
}
if (!FieldEffectActiveListContains(FLDEFF_USE_FLY))
if (taskState == 1)
{
Overworld_ResetStateAfterFly();
WarpIntoMap();
SetMainCallback2(CB2_LoadMap);
gFieldCallback = FieldCallback_FlyIntoMap;
DestroyTask(taskId);
if (ObjectEventClearHeldMovementIfFinished(follower))
{
FollowerNPCHideForLeaveMap(follower);
taskState++;
}
}
if (taskState == 2)
{
if (!fieldEffectStarted)
{
if (!IsWeatherNotFadingIn())
return;
gFieldEffectArguments[0] = GetCursorSelectionMonId();
if ((int)gFieldEffectArguments[0] > PARTY_SIZE - 1)
gFieldEffectArguments[0] = 0;
FieldEffectStart(FLDEFF_USE_FLY);
fieldEffectStarted = TRUE;
}
if (!FieldEffectActiveListContains(FLDEFF_USE_FLY))
{
Overworld_ResetStateAfterFly();
WarpIntoMap();
SetMainCallback2(CB2_LoadMap);
gFieldCallback = FieldCallback_FlyIntoMap;
DestroyTask(taskId);
}
}
}
#undef taskState
static void FieldCallback_FlyIntoMap(void)
{
Overworld_PlaySpecialMapMusic();
@ -1418,20 +1450,51 @@ static void FieldCallback_FlyIntoMap(void)
gFieldCallback = NULL;
}
#define taskState task->data[0]
#define tWaitPaletteFadeIn 0
#define tWaitFieldEffectEnd 1
#define tNPCFollowerFacePlayer 2
#define tTaskEnd 3
static void Task_FlyIntoMap(u8 taskId)
{
struct ObjectEvent *player = &gObjectEvents[gPlayerAvatar.objectEventId];
struct ObjectEvent *follower = &gObjectEvents[GetFollowerNPCObjectId()];
struct Task *task;
task = &gTasks[taskId];
if (task->data[0] == 0)
if (taskState == tWaitPaletteFadeIn)
{
if (gPaletteFade.active)
{
return;
}
FieldEffectStart(FLDEFF_FLY_IN);
task->data[0]++;
taskState++;
}
if (!FieldEffectActiveListContains(FLDEFF_FLY_IN))
if (taskState == tWaitFieldEffectEnd)
{
if (!FieldEffectActiveListContains(FLDEFF_FLY_IN))
{
if (FNPC_NPC_FOLLOWER_SHOW_AFTER_LEAVE_ROUTE)
FollowerNPCReappearAfterLeaveMap(follower, player);
taskState++;
}
}
if (taskState == tNPCFollowerFacePlayer)
{
if (PlayerHasFollowerNPC() && ObjectEventClearHeldMovementIfFinished(follower))
{
if (FNPC_NPC_FOLLOWER_SHOW_AFTER_LEAVE_ROUTE)
FollowerNPCFaceAfterLeaveMap();
taskState++;
}
else if (!PlayerHasFollowerNPC())
{
taskState++;
}
}
if (taskState == tTaskEnd)
{
UnlockPlayerFieldControls();
UnfreezeObjectEvents();
@ -1571,6 +1634,8 @@ static bool8 FallWarpEffect_End(struct Task *task)
UnfreezeObjectEvents();
InstallCameraPanAheadCallback();
DestroyTask(FindTaskIdByFunc(Task_FallWarpFieldEffect));
FollowerNPC_WarpSetEnd();
return FALSE;
}
@ -1633,7 +1698,9 @@ static bool8 EscalatorWarpOut_WaitForPlayer(struct Task *task)
task->tState++;
task->data[2] = 0;
task->data[3] = 0;
if ((u8)task->tGoingUp == FALSE)
EscalatorMoveFollowerNPC(task->data[1]);
if ((u8)task->data[1] == FALSE)
{
task->tState = 4; // jump to EscalatorWarpOut_Down_Ride
}
@ -1755,6 +1822,7 @@ static bool8 EscalatorWarpIn_Init(struct Task *task)
ObjectEventSetHeldMovement(objectEvent, GetFaceDirectionMovementAction(DIR_EAST));
PlayerGetDestCoords(&x, &y);
behavior = MapGridGetMetatileBehaviorAt(x, y);
EscalatorMoveFollowerNPCFinish();
task->tState++;
task->data[1] = 16;
@ -2005,12 +2073,22 @@ static bool8 LavaridgeGymB1FWarpEffect_Init(struct Task *task, struct ObjectEven
task->data[1] = 1;
task->data[0]++;
if (objectEvent->localId == OBJ_EVENT_ID_PLAYER) // Hide follower before warping
{
HideFollowerForFieldEffect();
if (PlayerHasFollowerNPC() && gObjectEvents[GetFollowerNPCObjectId()].invisible == FALSE)
{
FollowerNPCWalkIntoPlayerForLeaveMap();
CreateTask(Task_HideNPCFollowerAfterMovementFinish, 2);
}
}
return TRUE;
}
static bool8 LavaridgeGymB1FWarpEffect_CameraShake(struct Task *task, struct ObjectEvent *objectEvent, struct Sprite *sprite)
{
if (FindTaskIdByFunc(Task_HideNPCFollowerAfterMovementFinish) != TASK_NONE)
return FALSE;
SetCameraPanning(0, task->data[1]);
task->data[1] = -task->data[1];
task->data[2]++;
@ -2201,12 +2279,22 @@ static bool8 LavaridgeGym1FWarpEffect_Init(struct Task *task, struct ObjectEvent
objectEvent->noShadow = TRUE;
task->data[0]++;
if (objectEvent->localId == OBJ_EVENT_ID_PLAYER) // Hide follower before warping
{
HideFollowerForFieldEffect();
if (PlayerHasFollowerNPC() && gObjectEvents[GetFollowerNPCObjectId()].invisible == FALSE)
{
FollowerNPCWalkIntoPlayerForLeaveMap();
CreateTask(Task_HideNPCFollowerAfterMovementFinish, 2);
}
}
return FALSE;
}
static bool8 LavaridgeGym1FWarpEffect_AshPuff(struct Task *task, struct ObjectEvent *objectEvent, struct Sprite *sprite)
{
if (FindTaskIdByFunc(Task_HideNPCFollowerAfterMovementFinish) != TASK_NONE)
return FALSE;
if (ObjectEventClearHeldMovementIfFinished(objectEvent))
{
if (task->data[1] > 3)
@ -2278,11 +2366,18 @@ void SpriteCB_AshPuff(struct Sprite *sprite)
FieldEffectStop(sprite, FLDEFF_ASH_PUFF);
}
#define tState data[0]
#define tSpinDelay data[1]
#define tNumTurns data[2]
#define tTimer data[14]
#define tStartDir data[15]
#define tState data[0]
#define tSpinDelay data[1]
#define tNumTurns data[2]
#define tHideFollower data[3]
#define tTimer data[14]
#define tStartDir data[15]
enum
{
START_MOVEMENT,
WAIT_MOVEMENT_END
};
void StartEscapeRopeFieldEffect(void)
{
@ -2299,11 +2394,40 @@ static void Task_EscapeRopeWarpOut(u8 taskId)
static void EscapeRopeWarpOutEffect_Init(struct Task *task)
{
task->tState++;
if (PlayerHasFollowerNPC())
task->tState++;
else
task->tState += 2;
task->tTimer = 64;
task->tStartDir = GetPlayerFacingDirection();
}
static void EscapeRopeWarpOutEffect_HideFollowerNPC(struct Task *task)
{
struct ObjectEvent *follower = &gObjectEvents[GetFollowerNPCObjectId()];
if (task->tHideFollower == START_MOVEMENT)
{
if (!PlayerHasFollowerNPC())
{
task->tState++;
}
else
{
FollowerNPCWalkIntoPlayerForLeaveMap();
task->tHideFollower = WAIT_MOVEMENT_END;
}
}
if (task->tHideFollower == WAIT_MOVEMENT_END)
{
if (ObjectEventClearHeldMovementIfFinished(follower))
{
FollowerNPCHideForLeaveMap(follower);
task->tState++;
}
}
}
static void EscapeRopeWarpOutEffect_Spin(struct Task *task)
{
struct ObjectEvent *objectEvent;
@ -2362,33 +2486,66 @@ static void EscapeRopeWarpInEffect_Init(struct Task *task)
{
task->tState++;
task->tStartDir = GetPlayerFacingDirection();
task->data[3] = 0;
}
}
static void EscapeRopeWarpInEffect_Spin(struct Task *task)
{
u8 spinDirections[5] = {DIR_SOUTH, DIR_WEST, DIR_EAST, DIR_NORTH, DIR_SOUTH};
struct ObjectEvent *objectEvent = &gObjectEvents[gPlayerAvatar.objectEventId];
if (task->tSpinDelay == 0 || (--task->tSpinDelay) == 0)
struct ObjectEvent *player = &gObjectEvents[gPlayerAvatar.objectEventId];
struct ObjectEvent *follower = &gObjectEvents[GetFollowerNPCObjectId()];
if ((task->tSpinDelay == 0 || (--task->tSpinDelay) == 0) && task->data[3] == 0)
{
if (ObjectEventIsMovementOverridden(objectEvent) && !ObjectEventClearHeldMovementIfFinished(objectEvent))
if (ObjectEventIsMovementOverridden(player) && !ObjectEventClearHeldMovementIfFinished(player))
{
return;
}
if (task->tNumTurns >= 32 && task->tStartDir == GetPlayerFacingDirection())
{
objectEvent->invisible = FALSE;
UnlockPlayerFieldControls();
UnfreezeObjectEvents();
DestroyTask(FindTaskIdByFunc(Task_EscapeRopeWarpIn));
return;
task->data[3]++;
}
ObjectEventSetHeldMovement(objectEvent, GetFaceDirectionMovementAction(spinDirections[objectEvent->facingDirection]));
if (task->data[3] == 0)
ObjectEventSetHeldMovement(player, GetFaceDirectionMovementAction(spinDirections[player->facingDirection]));
if (task->tNumTurns < 32)
task->tNumTurns++;
task->tSpinDelay = task->tNumTurns >> 2;
}
objectEvent->invisible ^= 1;
if (task->data[3] == 0)
player->invisible ^= 1;
if (task->data[3] == 1)
{
if (FNPC_NPC_FOLLOWER_SHOW_AFTER_LEAVE_ROUTE)
FollowerNPCReappearAfterLeaveMap(follower, player);
task->data[3]++;
}
if (task->data[3] == 2)
{
if (PlayerHasFollowerNPC() && ObjectEventClearHeldMovementIfFinished(follower))
{
if (FNPC_NPC_FOLLOWER_SHOW_AFTER_LEAVE_ROUTE)
FollowerNPCFaceAfterLeaveMap();
task->data[3]++;
}
else if (!PlayerHasFollowerNPC())
{
task->data[3]++;
}
}
if (task->data[3] == 3)
{
player->invisible = FALSE;
UnlockPlayerFieldControls();
UnfreezeObjectEvents();
DestroyTask(FindTaskIdByFunc(Task_EscapeRopeWarpIn));
return;
}
}
#undef tState
@ -2573,25 +2730,49 @@ static void TeleportWarpInFieldEffect_SpinEnter(struct Task *task)
task->data[0]++;
task->data[1] = 1;
task->data[2] = 0;
task->data[3] = 0;
}
}
static void TeleportWarpInFieldEffect_SpinGround(struct Task *task)
{
struct ObjectEvent *player = &gObjectEvents[gPlayerAvatar.objectEventId];
struct ObjectEvent *follower = &gObjectEvents[GetFollowerNPCObjectId()];
u8 spinDirections[5] = {DIR_SOUTH, DIR_WEST, DIR_EAST, DIR_NORTH, DIR_SOUTH};
struct ObjectEvent *objectEvent = &gObjectEvents[gPlayerAvatar.objectEventId];
if ((--task->data[1]) == 0)
if ((--task->data[1]) == 0 && task->data[3] == 0)
{
ObjectEventTurn(objectEvent, spinDirections[objectEvent->facingDirection]);
ObjectEventTurn(player, spinDirections[player->facingDirection]);
task->data[1] = 8;
if ((++task->data[2]) > 4 && task->data[14] == objectEvent->facingDirection)
if ((++task->data[2]) > 4 && task->data[14] == player->facingDirection)
{
UnlockPlayerFieldControls();
CameraObjectReset();
UnfreezeObjectEvents();
DestroyTask(FindTaskIdByFunc(Task_TeleportWarpIn));
if (FNPC_NPC_FOLLOWER_SHOW_AFTER_LEAVE_ROUTE)
FollowerNPCReappearAfterLeaveMap(follower, player);
task->data[3] = 1;
}
}
if (task->data[3] == 1)
{
if (PlayerHasFollowerNPC() && ObjectEventClearHeldMovementIfFinished(follower))
{
if (FNPC_NPC_FOLLOWER_SHOW_AFTER_LEAVE_ROUTE)
FollowerNPCFaceAfterLeaveMap();
task->data[3]++;
}
else if (!PlayerHasFollowerNPC())
{
task->data[3]++;
}
}
if (task->data[3] == 2)
{
UnlockPlayerFieldControls();
CameraObjectReset();
UnfreezeObjectEvents();
DestroyTask(FindTaskIdByFunc(Task_TeleportWarpIn));
}
}
// Task data for Task_FieldMoveShowMonOutDoors
@ -3096,6 +3277,8 @@ static void SurfFieldEffect_JumpOnSurfBlob(struct Task *task)
ObjectEventSetGraphicsId(objectEvent, GetPlayerAvatarGraphicsIdByStateId(PLAYER_AVATAR_STATE_SURFING));
ObjectEventClearHeldMovementIfFinished(objectEvent);
ObjectEventSetHeldMovement(objectEvent, GetJumpSpecialMovementAction(objectEvent->movementDirection));
FollowerNPC_FollowerToWater();
gFieldEffectArguments[0] = task->tDestX;
gFieldEffectArguments[1] = task->tDestY;
gFieldEffectArguments[2] = gPlayerAvatar.objectEventId;

View File

@ -9,6 +9,7 @@
#include "field_screen_effect.h"
#include "field_player_avatar.h"
#include "fieldmap.h"
#include "follower_npc.h"
#include "menu.h"
#include "metatile_behavior.h"
#include "overworld.h"
@ -28,6 +29,7 @@
#include "constants/event_object_movement.h"
#include "constants/field_effects.h"
#include "constants/items.h"
#include "constants/metatile_behaviors.h"
#include "constants/moves.h"
#include "constants/songs.h"
#include "constants/trainer_types.h"
@ -66,7 +68,6 @@ static bool8 TryInterruptObjectEventSpecialAnim(struct ObjectEvent *, u8);
static void npc_clear_strange_bits(struct ObjectEvent *);
static void MovePlayerAvatarUsingKeypadInput(u8, u16, u16);
static void PlayerAllowForcedMovementIfMovingSameDirection();
static bool8 TryDoMetatileBehaviorForcedMovement();
static u8 GetForcedMovementByMetatileBehavior();
static bool8 ForcedMovement_None(void);
@ -443,7 +444,7 @@ static void PlayerAllowForcedMovementIfMovingSameDirection(void)
gPlayerAvatar.flags &= ~PLAYER_AVATAR_FLAG_CONTROLLABLE;
}
static bool8 TryDoMetatileBehaviorForcedMovement(void)
bool8 TryDoMetatileBehaviorForcedMovement(void)
{
return sForcedMovementFuncs[GetForcedMovementByMetatileBehavior()]();
}
@ -520,6 +521,10 @@ static bool8 DoForcedMovement(u8 direction, void (*moveFunc)(u8))
{
playerAvatar->runningState = MOVING;
moveFunc(direction);
if (PlayerHasFollowerNPC()
&& gObjectEvents[GetFollowerNPCObjectId()].invisible == FALSE
&& FindTaskIdByFunc(Task_MoveNPCFollowerAfterForcedMovement) == TASK_NONE)
CreateTask(Task_MoveNPCFollowerAfterForcedMovement, 3);
return TRUE;
}
}
@ -826,7 +831,7 @@ static void PlayerNotOnBikeMoving(u8 direction, u16 heldKeys)
}
if (!(gPlayerAvatar.flags & PLAYER_AVATAR_FLAG_UNDERWATER) && (heldKeys & B_BUTTON) && FlagGet(FLAG_SYS_B_DASH)
&& IsRunningDisallowed(gObjectEvents[gPlayerAvatar.objectEventId].currentMetatileBehavior) == 0)
&& IsRunningDisallowed(gObjectEvents[gPlayerAvatar.objectEventId].currentMetatileBehavior) == 0 && !FollowerNPCComingThroughDoor())
{
if (ObjectMovingOnRockStairs(&gObjectEvents[gPlayerAvatar.objectEventId], direction))
PlayerRunSlow(direction);
@ -917,7 +922,9 @@ static bool8 CanStopSurfing(s16 x, s16 y, u8 direction)
{
if ((gPlayerAvatar.flags & PLAYER_AVATAR_FLAG_SURFING)
&& MapGridGetElevationAt(x, y) == 3
&& GetObjectEventIdByPosition(x, y, 3) == OBJECT_EVENTS_COUNT)
&& (GetObjectEventIdByPosition(x, y, 3) == OBJECT_EVENTS_COUNT
|| GetObjectEventIdByPosition(x, y, 3) == GetFollowerNPCObjectId()
))
{
CreateStopSurfingTask(direction);
return TRUE;
@ -1205,6 +1212,22 @@ void PlayerOnBikeCollide(u8 direction)
{
PlayCollisionSoundIfNotFacingWarp(direction);
PlayerSetAnimId(GetWalkInPlaceNormalMovementAction(direction), COPY_MOVE_WALK);
// Edge case: If the player stops at the top of a mud slide, but the NPC follower is still on a mud slide tile,
// move the follower into the player and hide them.
if (PlayerHasFollowerNPC())
{
struct ObjectEvent *npcFollower = &gObjectEvents[GetFollowerNPCObjectId()];
struct ObjectEvent *player = &gObjectEvents[gPlayerAvatar.objectEventId];
if (npcFollower->invisible == FALSE
&& player->currentMetatileBehavior != MB_MUDDY_SLOPE
&& npcFollower->currentMetatileBehavior == MB_MUDDY_SLOPE)
{
gPlayerAvatar.preventStep = TRUE;
ObjectEventSetHeldMovement(npcFollower, MOVEMENT_ACTION_WALK_FAST_UP);
CreateTask(Task_HideNPCFollowerAfterMovementFinish, 2);
}
}
}
void PlayerOnBikeCollideWithFarawayIslandMew(u8 direction)
@ -1596,6 +1619,7 @@ void InitPlayerAvatar(s16 x, s16 y, u8 direction, u8 gender)
gPlayerAvatar.spriteId = objectEvent->spriteId;
gPlayerAvatar.gender = gender;
SetPlayerAvatarStateMask(PLAYER_AVATAR_FLAG_CONTROLLABLE | PLAYER_AVATAR_FLAG_ON_FOOT);
CreateFollowerNPCAvatar();
}
void SetPlayerInvisibility(bool8 invisible)
@ -1845,6 +1869,7 @@ static void CreateStopSurfingTask(u8 direction)
taskId = CreateTask(Task_StopSurfingInit, 0xFF);
gTasks[taskId].data[0] = direction;
Task_StopSurfingInit(taskId);
PrepareFollowerNPCDismountSurf();
}
static void Task_StopSurfingInit(u8 taskId)

View File

@ -12,6 +12,7 @@
#include "field_screen_effect.h"
#include "field_special_scene.h"
#include "field_weather.h"
#include "follower_npc.h"
#include "gpu_regs.h"
#include "heal_location.h"
#include "io_reg.h"
@ -48,8 +49,6 @@ static void FillPalBufferWhite(void);
static void Task_ExitDoor(u8);
static bool32 WaitForWeatherFadeIn(void);
static void Task_SpinEnterWarp(u8 taskId);
static void Task_WarpAndLoadMap(u8 taskId);
static void Task_DoDoorWarp(u8 taskId);
static void Task_EnableScriptAfterMusicFade(u8 taskId);
static void ExitStairsMovement(s16*, s16*, s16*, s16*, s16*);
@ -125,7 +124,7 @@ void WarpFadeOutScreen(void)
}
}
static void SetPlayerVisibility(bool8 visible)
void SetPlayerVisibility(bool8 visible)
{
SetPlayerInvisibility(!visible);
}
@ -293,6 +292,7 @@ void FieldCB_DefaultWarpExit(void)
Overworld_PlaySpecialMapMusic();
WarpFadeInScreen();
SetUpWarpExitTask();
FollowerNPC_WarpSetEnd();
LockPlayerFieldControls();
}
@ -341,6 +341,7 @@ static void Task_ExitDoor(u8 taskId)
switch (task->tState)
{
case 0:
HideNPCFollower();
SetPlayerVisibility(FALSE);
FreezeObjectEvents();
PlayerGetDestCoords(x, y);
@ -370,6 +371,8 @@ static void Task_ExitDoor(u8 taskId)
case 3:
if (task->data[1] < 0 || gTasks[task->data[1]].isActive != TRUE)
{
FollowerNPC_SetIndicatorToComeOutDoor();
FollowerNPC_WarpSetEnd();
UnfreezeObjectEvents();
task->tState = 4;
}
@ -390,6 +393,7 @@ static void Task_ExitNonAnimDoor(u8 taskId)
switch (task->tState)
{
case 0:
HideNPCFollower();
SetPlayerVisibility(FALSE);
FreezeObjectEvents();
PlayerGetDestCoords(x, y);
@ -408,6 +412,14 @@ static void Task_ExitNonAnimDoor(u8 taskId)
case 2:
if (IsPlayerStandingStill())
{
s16 x, y;
PlayerGetDestCoords(&x, &y);
if (!MetatileBehavior_IsDeepSouthWarp(MapGridGetMetatileBehaviorAt(x, y + 1)))
FollowerNPC_SetIndicatorToComeOutDoor();
// TODO: Add specific follower door warp behavior for MB_DEEP_SOUTH_WARP.
FollowerNPC_WarpSetEnd();
UnfreezeObjectEvents();
task->tState = 3;
}
@ -661,7 +673,7 @@ void ReturnFromLinkRoom(void)
CreateTask(Task_ReturnToWorldFromLinkRoom, 10);
}
static void Task_WarpAndLoadMap(u8 taskId)
void Task_WarpAndLoadMap(u8 taskId)
{
struct Task *task = &gTasks[taskId];
@ -692,16 +704,35 @@ static void Task_WarpAndLoadMap(u8 taskId)
}
}
static void Task_DoDoorWarp(u8 taskId)
#define tDoorTask data[1]
enum
{
DOORWARP_OPEN_DOOR,
DOORWARP_START_WALK_UP,
DOORWARP_HIDE_PLAYER,
DOORWARP_WAIT_DOOR_ANIM_TASK,
DOORWARP_DO_WARP
};
void Task_DoDoorWarp(u8 taskId)
{
struct Task *task = &gTasks[taskId];
s16 *x = &task->data[2];
s16 *y = &task->data[3];
u8 playerObjId = gPlayerAvatar.objectEventId;
u8 followerObjId = GetFollowerNPCObjectId();
struct ObjectEvent *followerObject = GetFollowerObject();
switch (task->tState)
{
case 0:
case DOORWARP_OPEN_DOOR:
// Stop running.
if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_DASH))
SetPlayerAvatarTransitionFlags(PLAYER_AVATAR_FLAG_ON_FOOT);
// Just in case came out and went right back in, reset follower NPC door state.
SetFollowerNPCData(FNPC_DATA_COME_OUT_DOOR, FNPC_DOOR_NONE);
FreezeObjectEvents();
PlayerGetDestCoords(x, y);
PlaySE(GetDoorSoundEffect(*x, *y - 1));
@ -711,38 +742,49 @@ static void Task_DoDoorWarp(u8 taskId)
ClearObjectEventMovement(followerObject, &gSprites[followerObject->spriteId]);
ObjectEventSetHeldMovement(followerObject, MOVEMENT_ACTION_ENTER_POKEBALL);
}
task->data[1] = FieldAnimateDoorOpen(*x, *y - 1);
task->tState = 1;
task->tDoorTask = FieldAnimateDoorOpen(*x, *y - 1);
task->tState = DOORWARP_START_WALK_UP;
break;
case 1:
if (task->data[1] < 0 || gTasks[task->data[1]].isActive != TRUE)
case DOORWARP_START_WALK_UP:
if (task->tDoorTask < 0 || gTasks[task->tDoorTask].isActive != TRUE)
{
u8 objEventId;
objEventId = GetObjectEventIdByLocalIdAndMap(OBJ_EVENT_ID_PLAYER, 0, 0);
ObjectEventClearHeldMovementIfActive(&gObjectEvents[objEventId]);
objEventId = GetObjectEventIdByLocalIdAndMap(OBJ_EVENT_ID_PLAYER, 0, 0);
ObjectEventSetHeldMovement(&gObjectEvents[objEventId], MOVEMENT_ACTION_WALK_NORMAL_UP);
task->tState = 2;
ObjectEventClearHeldMovementIfActive(&gObjectEvents[playerObjId]);
ObjectEventSetHeldMovement(&gObjectEvents[playerObjId], MOVEMENT_ACTION_WALK_NORMAL_UP);
if (PlayerHasFollowerNPC() && !gObjectEvents[followerObjId].invisible)
{
u8 newState = DetermineFollowerNPCState(&gObjectEvents[followerObjId], MOVEMENT_ACTION_WALK_NORMAL_UP,
DetermineFollowerNPCDirection(&gObjectEvents[playerObjId], &gObjectEvents[followerObjId]));
ObjectEventClearHeldMovementIfActive(&gObjectEvents[followerObjId]);
ObjectEventSetHeldMovement(&gObjectEvents[followerObjId], newState);
}
task->tState = DOORWARP_HIDE_PLAYER;
}
break;
case 2:
case DOORWARP_HIDE_PLAYER:
if (IsPlayerStandingStill())
{
u8 objEventId;
task->data[1] = FieldAnimateDoorClose(*x, *y - 1);
objEventId = GetObjectEventIdByLocalIdAndMap(OBJ_EVENT_ID_PLAYER, 0, 0);
ObjectEventClearHeldMovementIfFinished(&gObjectEvents[objEventId]);
// Don't close door on NPC follower.
if (!PlayerHasFollowerNPC() || gObjectEvents[followerObjId].invisible)
task->tDoorTask = FieldAnimateDoorClose(*x, *y - 1);
ObjectEventClearHeldMovementIfFinished(&gObjectEvents[playerObjId]);
SetPlayerVisibility(FALSE);
task->tState = 3;
task->tState = DOORWARP_WAIT_DOOR_ANIM_TASK;
}
break;
case 3:
if (task->data[1] < 0 || gTasks[task->data[1]].isActive != TRUE)
case DOORWARP_WAIT_DOOR_ANIM_TASK:
if (task->tDoorTask < 0 || gTasks[task->tDoorTask].isActive != TRUE)
task->tState = DOORWARP_DO_WARP;
break;
case DOORWARP_DO_WARP:
if (PlayerHasFollowerNPC())
{
task->tState = 4;
ObjectEventClearHeldMovementIfActive(&gObjectEvents[followerObjId]);
ObjectEventSetHeldMovement(&gObjectEvents[followerObjId], MOVEMENT_ACTION_WALK_NORMAL_UP);
}
break;
case 4:
TryFadeOutOldMapMusic();
WarpFadeOutScreen();
PlayRainStoppingSoundEffect();
@ -1035,6 +1077,7 @@ static void Task_SpinEnterWarp(u8 taskId)
case 1:
if (WaitForWeatherFadeIn() && IsPlayerSpinEntranceActive() != TRUE)
{
FollowerNPC_WarpSetEnd();
UnfreezeObjectEvents();
UnlockPlayerFieldControls();
DestroyTask(taskId);

View File

@ -2,6 +2,7 @@
#include "field_effect.h"
#include "field_player_avatar.h"
#include "fldeff.h"
#include "follower_npc.h"
#include "party_menu.h"
#include "overworld.h"
#include "task.h"
@ -12,6 +13,9 @@ static void StartTeleportFieldEffect(void);
bool8 SetUpFieldMove_Teleport(void)
{
if (!CheckFollowerNPCFlag(FOLLOWER_NPC_FLAG_CAN_LEAVE_ROUTE))
return FALSE;
if (Overworld_MapTypeAllowsTeleportAndFly(gMapHeader.mapType) == TRUE)
{
gFieldCallback2 = FieldCallback_PrepareFadeInForTeleport;

1689
src/follower_npc.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,7 @@
#include "field_screen_effect.h"
#include "field_weather.h"
#include "fldeff.h"
#include "follower_npc.h"
#include "item.h"
#include "item_menu.h"
#include "item_use.h"
@ -263,7 +264,7 @@ void ItemUseOutOfBattle_Bike(u8 taskId)
}
else
{
if (Overworld_IsBikingAllowed() == TRUE && IsBikingDisallowedByPlayer() == 0)
if (Overworld_IsBikingAllowed() && !IsBikingDisallowedByPlayer() && FollowerNPCCanBike())
{
sItemUseOnFieldCB = ItemUseOnFieldCB_Bike;
SetUpItemUseOnFieldCallback(taskId);
@ -281,6 +282,8 @@ static void ItemUseOnFieldCB_Bike(u8 taskId)
GetOnOffBike(PLAYER_AVATAR_FLAG_MACH_BIKE);
else // ACRO_BIKE
GetOnOffBike(PLAYER_AVATAR_FLAG_ACRO_BIKE);
FollowerNPC_HandleBike();
ScriptUnfreezeObjectEvents();
UnlockPlayerFieldControls();
DestroyTask(taskId);
@ -1073,6 +1076,9 @@ static void ItemUseOnFieldCB_EscapeRope(u8 taskId)
bool8 CanUseDigOrEscapeRopeOnCurMap(void)
{
if (!CheckFollowerNPCFlag(FOLLOWER_NPC_FLAG_CAN_LEAVE_ROUTE))
return FALSE;
if (gMapHeader.allowEscaping)
return TRUE;
else

View File

@ -2,6 +2,7 @@
#include "malloc.h"
#include "berry_powder.h"
#include "fake_rtc.h"
#include "follower_npc.h"
#include "item.h"
#include "load_save.h"
#include "main.h"
@ -10,6 +11,7 @@
#include "pokemon_storage_system.h"
#include "random.h"
#include "save_location.h"
#include "script_pokemon_util.h"
#include "trainer_hill.h"
#include "gba/flash_internal.h"
#include "decoration_inventory.h"

View File

@ -47,6 +47,7 @@
#include "constants/map_groups.h"
#include "constants/items.h"
#include "difficulty.h"
#include "follower_npc.h"
extern const u8 EventScript_ResetAllMapFlags[];
@ -211,6 +212,7 @@ void NewGameInitData(void)
SetCurrentDifficultyLevel(DIFFICULTY_NORMAL);
ResetItemFlags();
ResetDexNav();
ClearFollowerNPCData();
}
static void ResetMiniGamesRecords(void)

View File

@ -24,6 +24,7 @@
#include "field_weather.h"
#include "fieldmap.h"
#include "fldeff.h"
#include "follower_npc.h"
#include "gpu_regs.h"
#include "heal_location.h"
#include "io_reg.h"
@ -68,6 +69,7 @@
#include "vs_seeker.h"
#include "frontier_util.h"
#include "constants/abilities.h"
#include "constants/event_object_movement.h"
#include "constants/event_objects.h"
#include "constants/layouts.h"
#include "constants/map_types.h"
@ -458,6 +460,7 @@ static void Overworld_ResetStateAfterWhiteOut(void)
VarSet(VAR_SHOULD_END_ABNORMAL_WEATHER, 0);
VarSet(VAR_ABNORMAL_WEATHER_LOCATION, ABNORMAL_WEATHER_NONE);
}
FollowerNPC_TryRemoveFollowerOnWhiteOut();
}
static void UpdateMiscOverworldStates(void)
@ -1535,6 +1538,9 @@ static void DoCB1_Overworld(u16 newKeys, u16 heldKeys)
PlayerStep(inputStruct.dpadDirection, newKeys, heldKeys);
}
}
// If stop running but keep holding B -> fix follower frame.
if (PlayerHasFollowerNPC() && (gPlayerAvatar.flags & PLAYER_AVATAR_FLAG_ON_FOOT) && IsPlayerStandingStill())
ObjectEventSetHeldMovement(&gObjectEvents[GetFollowerNPCObjectId()], GetFaceDirectionAnimNum(gObjectEvents[GetFollowerNPCObjectId()].facingDirection));
}
void CB1_Overworld(void)
@ -2228,6 +2234,7 @@ static bool32 ReturnToFieldLocal(u8 *state)
case 1:
InitViewGraphics();
TryLoadTrainerHillEReaderPalette();
FollowerNPC_BindToSurfBlobOnReloadScreen();
(*state)++;
break;
case 2:
@ -2428,6 +2435,7 @@ static void InitObjectEventsLocal(void)
TrySpawnObjectEvents(0, 0);
UpdateFollowingPokemon();
TryRunOnWarpIntoMapScript();
FollowerNPC_HandleSprite();
}
static void InitObjectEventsReturnToField(void)

View File

@ -25,6 +25,7 @@
#include "fieldmap.h"
#include "fldeff.h"
#include "fldeff_misc.h"
#include "follower_npc.h"
#include "frontier_util.h"
#include "gpu_regs.h"
#include "graphics.h"
@ -518,6 +519,7 @@ static bool8 SetUpFieldMove_Dive(void);
void TryItemHoldFormChange(struct Pokemon *mon);
static void ShowMoveSelectWindow(u8 slot);
static void Task_HandleWhichMoveInput(u8 taskId);
static void Task_HideFollowerNPCForTeleport(u8);
// static const data
#include "data/party_menu.h"
@ -4083,6 +4085,9 @@ bool8 FieldCallback_PrepareFadeInFromMenu(void)
{
FadeInFromBlack();
CreateTask(Task_FieldMoveWaitForFade, 8);
if (PlayerHasFollowerNPC())
CreateTask(Task_HideFollowerNPCForTeleport, 0);
return TRUE;
}
@ -4093,6 +4098,43 @@ bool8 FieldCallback_PrepareFadeInForTeleport(void)
return FieldCallback_PrepareFadeInFromMenu();
}
#define taskState task->data[0]
enum
{
FNPC_WALK_INTO_PLAYER,
FNPC_WAIT_FOR_ANIM_FINISH
};
static void Task_HideFollowerNPCForTeleport(u8 taskId)
{
struct ObjectEvent *follower = &gObjectEvents[GetFollowerNPCObjectId()];
struct Task *task;
task = &gTasks[taskId];
if (taskState == FNPC_WALK_INTO_PLAYER)
{
if (!PlayerHasFollowerNPC())
{
DestroyTask(taskId);
}
else
{
FollowerNPCWalkIntoPlayerForLeaveMap();
taskState = FNPC_WAIT_FOR_ANIM_FINISH;
}
}
if (taskState == FNPC_WAIT_FOR_ANIM_FINISH)
{
if (ObjectEventClearHeldMovementIfFinished(follower))
{
FollowerNPCHideForLeaveMap(follower);
DestroyTask(taskId);
}
}
}
#undef taskState
static void Task_FieldMoveWaitForFade(u8 taskId)
{
if (IsWeatherNotFadingIn() == TRUE)
@ -4130,6 +4172,9 @@ static void FieldCallback_Surf(void)
static bool8 SetUpFieldMove_Surf(void)
{
if (!CheckFollowerNPCFlag(FOLLOWER_NPC_FLAG_CAN_SURF))
return FALSE;
if (PartyHasMonWithSurf() == TRUE && IsPlayerFacingSurfableFishableWater() == TRUE)
{
gFieldCallback2 = FieldCallback_PrepareFadeInFromMenu;
@ -4149,6 +4194,9 @@ static void DisplayCantUseSurfMessage(void)
static bool8 SetUpFieldMove_Fly(void)
{
if (!CheckFollowerNPCFlag(FOLLOWER_NPC_FLAG_CAN_LEAVE_ROUTE))
return FALSE;
if (Overworld_MapTypeAllowsTeleportAndFly(gMapHeader.mapType) == TRUE)
return TRUE;
else
@ -4170,6 +4218,9 @@ static bool8 SetUpFieldMove_Waterfall(void)
{
s16 x, y;
if (!CheckFollowerNPCFlag(FOLLOWER_NPC_FLAG_CAN_WATERFALL))
return FALSE;
GetXYCoordsOneStepInFrontOfPlayer(&x, &y);
if (MetatileBehavior_IsWaterfall(MapGridGetMetatileBehaviorAt(x, y)) == TRUE && IsPlayerSurfingNorth() == TRUE)
{
@ -4188,6 +4239,9 @@ static void FieldCallback_Dive(void)
static bool8 SetUpFieldMove_Dive(void)
{
if (!CheckFollowerNPCFlag(FOLLOWER_NPC_FLAG_CAN_DIVE))
return FALSE;
gFieldEffectArguments[1] = TrySetDiveWarp();
if (gFieldEffectArguments[1] != 0)
{
@ -7594,7 +7648,11 @@ static void Task_WaitAfterMultiPartnerPartySlideIn(u8 taskId)
s16 *data = gTasks[taskId].data;
// data[0] used as a timer afterwards rather than the x pos
if (++data[0] == 256)
if (FollowerNPCIsBattlePartner()) {
if (++data[0] == 128)
Task_ClosePartyMenu(taskId);
}
else if (++data[0] == 256)
Task_ClosePartyMenu(taskId);
}

View File

@ -19,6 +19,7 @@
#include "field_player_avatar.h"
#include "field_specials.h"
#include "field_weather.h"
#include "follower_npc.h"
#include "graphics.h"
#include "item.h"
#include "caps.h"
@ -3462,7 +3463,8 @@ u8 GetMonsStateToDoubles_2(void)
s32 aliveCount = 0;
s32 i;
if (OW_DOUBLE_APPROACH_WITH_ONE_MON)
if (OW_DOUBLE_APPROACH_WITH_ONE_MON
|| FollowerNPCIsBattlePartner())
return PLAYER_HAS_TWO_USABLE_MONS;
for (i = 0; i < PARTY_SIZE; i++)

View File

@ -24,6 +24,7 @@
#include "field_tasks.h"
#include "field_weather.h"
#include "fieldmap.h"
#include "follower_npc.h"
#include "gpu_regs.h"
#include "item.h"
#include "lilycove_lady.h"
@ -56,6 +57,7 @@
#include "list_menu.h"
#include "malloc.h"
#include "constants/event_objects.h"
#include "constants/map_types.h"
typedef u16 (*SpecialFunc)(void);
typedef void (*NativeFunc)(struct ScriptContext *ctx);
@ -1494,7 +1496,29 @@ bool8 ScrCmd_resetobjectsubpriority(struct ScriptContext *ctx)
bool8 ScrCmd_faceplayer(struct ScriptContext *ctx)
{
Script_RequestEffects(SCREFF_V1 | SCREFF_HARDWARE);
if (PlayerHasFollowerNPC()
&& gObjectEvents[GetFollowerNPCObjectId()].invisible == FALSE
&& gSelectedObjectEvent == GetFollowerNPCObjectId())
{
struct ObjectEvent *npcFollower = &gObjectEvents[GetFollowerNPCObjectId()];
switch (DetermineFollowerNPCDirection(&gObjectEvents[gPlayerAvatar.objectEventId], npcFollower))
{
case DIR_NORTH:
ScriptMovement_StartObjectMovementScript(OBJ_EVENT_ID_NPC_FOLLOWER, npcFollower->mapGroup, npcFollower->mapNum, Common_Movement_FaceUp);
break;
case DIR_SOUTH:
ScriptMovement_StartObjectMovementScript(OBJ_EVENT_ID_NPC_FOLLOWER, npcFollower->mapGroup, npcFollower->mapNum, Common_Movement_FaceDown);
break;
case DIR_EAST:
ScriptMovement_StartObjectMovementScript(OBJ_EVENT_ID_NPC_FOLLOWER, npcFollower->mapGroup, npcFollower->mapNum, Common_Movement_FaceRight);
break;
case DIR_WEST:
ScriptMovement_StartObjectMovementScript(OBJ_EVENT_ID_NPC_FOLLOWER, npcFollower->mapGroup, npcFollower->mapNum, Common_Movement_FaceLeft);
break;
}
return FALSE;
}
if (gObjectEvents[gSelectedObjectEvent].active)
ObjectEventFaceOppositeDirection(&gObjectEvents[gSelectedObjectEvent], GetPlayerFacingDirection());
return FALSE;

View File

@ -5,6 +5,7 @@
#include "event_scripts.h"
#include "field_effect.h"
#include "field_player_avatar.h"
#include "follower_npc.h"
#include "pokemon.h"
#include "script.h"
#include "script_movement.h"
@ -630,6 +631,9 @@ static void StartTrainerApproach(TaskFunc followupFunc)
else
taskId = gApproachingTrainers[1].taskId;
if (PlayerHasFollowerNPC() && (gPlayerAvatar.flags & PLAYER_AVATAR_FLAG_ON_FOOT))
ObjectEventForceSetHeldMovement(&gObjectEvents[GetFollowerNPCObjectId()], GetFaceDirectionAnimNum(gObjectEvents[GetFollowerNPCObjectId()].facingDirection));
taskFunc = Task_RunTrainerSeeFuncList;
SetTaskFuncWithFollowupFunc(taskId, taskFunc, followupFunc);
gTasks[taskId].tFuncId = TRSEE_EXCLAMATION;

View File

@ -3,6 +3,7 @@
#include "pokemon.h"
#include "metatile_behavior.h"
#include "fieldmap.h"
#include "follower_npc.h"
#include "random.h"
#include "field_player_avatar.h"
#include "event_data.h"
@ -859,8 +860,18 @@ void RockSmashWildEncounter(void)
else if (WildEncounterCheck(wildPokemonInfo->encounterRate, TRUE) == TRUE
&& TryGenerateWildMon(wildPokemonInfo, WILD_AREA_ROCKS, WILD_CHECK_REPEL | WILD_CHECK_KEEN_EYE) == TRUE)
{
BattleSetup_StartWildBattle();
gSpecialVar_Result = TRUE;
if (TryDoDoubleWildBattle())
{
struct Pokemon mon1 = gEnemyParty[0];
TryGenerateWildMon(wildPokemonInfo, WILD_AREA_ROCKS, WILD_CHECK_REPEL | WILD_CHECK_KEEN_EYE);
gEnemyParty[1] = mon1;
BattleSetup_StartDoubleWildBattle();
gSpecialVar_Result = TRUE;
}
else {
BattleSetup_StartWildBattle();
gSpecialVar_Result = TRUE;
}
}
else
{
@ -1233,6 +1244,9 @@ bool8 TryDoDoubleWildBattle(void)
if (GetSafariZoneFlag()
|| (B_DOUBLE_WILD_REQUIRE_2_MONS == TRUE && GetMonsStateToDoubles() != PLAYER_HAS_TWO_USABLE_MONS))
return FALSE;
if (FollowerNPCIsBattlePartner() && FNPC_FLAG_PARTNER_WILD_BATTLES != 0
&& (FNPC_FLAG_PARTNER_WILD_BATTLES == FNPC_ALWAYS || FlagGet(FNPC_FLAG_PARTNER_WILD_BATTLES)) && FNPC_NPC_FOLLOWER_WILD_BATTLE_VS_2 == TRUE)
return TRUE;
else if (B_FLAG_FORCE_DOUBLE_WILD != 0 && FlagGet(B_FLAG_FORCE_DOUBLE_WILD))
return TRUE;
else if (B_DOUBLE_WILD_CHANCE != 0 && ((Random() % 100) + 1 <= B_DOUBLE_WILD_CHANCE))