diff --git a/asm/macros/event.inc b/asm/macros/event.inc index 7ffdcccf0a..d58f0197c2 100644 --- a/asm/macros/event.inc +++ b/asm/macros/event.inc @@ -2570,7 +2570,7 @@ @ Follower NPCs - @ Sets an NPC up to follow the player. + @ Sets an existing 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 setfollowernpc localId:req, flags:req, script=0, battlePartner=0 @@ -2598,6 +2598,19 @@ .endif .endm + @ Creates a new follower NPC with the specified graphics id. + .macro createfollowernpc gfx:req, flags:req, script=0, battlePartner=0 + .if FNPC_ENABLE_NPC_FOLLOWERS + callnative ScriptCreateFollowerNPC + .2byte \gfx + .2byte \flags + .4byte \script + .2byte \battlePartner + .else + .error "createfollowernpc unavailable with FNPC_ENABLE_NPC_FOLLOWERS defined as FALSE" + .endif + .endm + @ Remove the follower NPC (assumes there will only ever be one). .macro destroyfollowernpc .if FNPC_ENABLE_NPC_FOLLOWERS diff --git a/include/follower_npc.h b/include/follower_npc.h index 52355a78b7..ec41fa1364 100644 --- a/include/follower_npc.h +++ b/include/follower_npc.h @@ -23,9 +23,6 @@ enum FollowerNPCDataTypes 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, diff --git a/include/global.h b/include/global.h index 8aaca8e495..fa185f3332 100644 --- a/include/global.h +++ b/include/global.h @@ -204,11 +204,11 @@ struct Time /*0x04*/ s8 seconds; }; -struct NPCFollowerMapData +struct NPCFollowerPadding { - u8 id; - u8 number; - u8 group; + u8 padding1; + u8 padding2; + u8 padding3; }; struct NPCFollower @@ -220,7 +220,7 @@ struct NPCFollower u8 objId; u8 currentSprite; u8 delayedState; - struct NPCFollowerMapData map; + struct NPCFollowerPadding padding; struct Coords16 log; const u8 *script; u16 flag; diff --git a/src/follower_npc.c b/src/follower_npc.c index 0a8cde49c8..67129548ac 100644 --- a/src/follower_npc.c +++ b/src/follower_npc.c @@ -49,6 +49,7 @@ static void SetFollowerNPCScriptPointer(const u8 *script); static void PlayerLogCoordinates(struct ObjectEvent *player); static void TurnNPCIntoFollower(u32 localId, u32 followerFlags, u32 setScript, const u8 *script); +static void CreateFollowerNPC(u32 gfx, u32 followerFlags, const u8 *scriptPtr); static u32 GetFollowerNPCSprite(void); static bool32 IsStateMovement(u32 state); static u32 GetPlayerFaceToDoorDirection(struct ObjectEvent *player, struct ObjectEvent *follower); @@ -92,15 +93,6 @@ void SetFollowerNPCData(enum FollowerNPCDataTypes type, u32 value) case FNPC_DATA_DELAYED_STATE: gSaveBlock3Ptr->NPCfollower.delayedState = value; break; - case FNPC_DATA_MAP_ID: - gSaveBlock3Ptr->NPCfollower.map.id = value; - break; - case FNPC_DATA_MAP_NUM: - gSaveBlock3Ptr->NPCfollower.map.number = value; - break; - case FNPC_DATA_MAP_GROUP: - gSaveBlock3Ptr->NPCfollower.map.group = value; - break; case FNPC_DATA_EVENT_FLAG: gSaveBlock3Ptr->NPCfollower.flag = value; break; @@ -161,12 +153,6 @@ u32 GetFollowerNPCData(enum FollowerNPCDataTypes type) return gSaveBlock3Ptr->NPCfollower.currentSprite; case FNPC_DATA_DELAYED_STATE: return gSaveBlock3Ptr->NPCfollower.delayedState; - case FNPC_DATA_MAP_ID: - return gSaveBlock3Ptr->NPCfollower.map.id; - case FNPC_DATA_MAP_NUM: - return gSaveBlock3Ptr->NPCfollower.map.number; - case FNPC_DATA_MAP_GROUP: - return gSaveBlock3Ptr->NPCfollower.map.group; case FNPC_DATA_EVENT_FLAG: return gSaveBlock3Ptr->NPCfollower.flag; case FNPC_DATA_GFX_ID: @@ -187,77 +173,97 @@ void ClearFollowerNPCData(void) #endif } -static void TurnNPCIntoFollower(u32 localId, u32 followerFlags, u32 setScript, const u8 *ptr) +static void TurnNPCIntoFollower(u32 localId, u32 followerFlags, u32 setScript, const u8 *scriptPtr) { struct ObjectEventTemplate npc; struct ObjectEvent *follower; - u32 npcX, npcY; - u32 faceDirection; - u32 eventObjId; + u32 eventObjId = GetObjectEventIdByLocalId(localId); + u32 npcX = gObjectEvents[eventObjId].currentCoords.x; + u32 npcY = gObjectEvents[eventObjId].currentCoords.y; const u8 *script; u32 flag; - // Only allow 1 follower NPC at a time. - if (PlayerHasFollowerNPC()) + flag = GetObjectEventFlagIdByLocalIdAndMap(localId, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup); + // If the object does not have an event flag, don't create follower. + if (flag == 0) return; - for (eventObjId = 0; eventObjId < OBJECT_EVENTS_COUNT; eventObjId++) + if (setScript == TRUE) + // Set the custom script. + script = scriptPtr; + else + // Use the object's original script. + script = GetObjectEventScriptPointerByObjectEventId(eventObjId); + + RemoveObjectEvent(&gObjectEvents[eventObjId]); + FlagSet(flag); + + npc = *GetObjectEventTemplateByLocalIdAndMap(localId, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup); + npc.movementType = 0; + npc.script = script; + npc.localId = OBJ_EVENT_ID_NPC_FOLLOWER; + SetFollowerNPCData(FNPC_DATA_OBJ_ID, TrySpawnObjectEventTemplate(&npc, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, npcX, npcY)); + follower = &gObjectEvents[GetFollowerNPCData(FNPC_DATA_OBJ_ID)]; + MoveObjectEventToMapCoords(follower, npcX, npcY); + ObjectEventTurn(follower, gObjectEvents[eventObjId].facingDirection); + follower->movementType = MOVEMENT_TYPE_NONE; + gSprites[follower->spriteId].callback = MovementType_None; + + SetFollowerNPCData(FNPC_DATA_IN_PROGRESS, TRUE); + SetFollowerNPCData(FNPC_DATA_GFX_ID, follower->graphicsId); + SetFollowerNPCScriptPointer(script); + SetFollowerNPCData(FNPC_DATA_EVENT_FLAG, flag); + SetFollowerNPCData(FNPC_DATA_FOLLOWER_FLAGS, followerFlags); + SetFollowerNPCData(FNPC_DATA_SURF_BLOB, FNPC_SURF_BLOB_NONE); + SetFollowerNPCData(FNPC_DATA_COME_OUT_DOOR, FNPC_DOOR_NONE); + + // If the player is biking and the follower flags prohibit biking, force the player to dismount the bike. + if (!CheckFollowerNPCFlag(FOLLOWER_NPC_FLAG_CAN_BIKE) + && TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_BIKE)) + SetPlayerAvatarTransitionFlags(PLAYER_AVATAR_FLAG_ON_FOOT); + + // Set the follower sprite to match the player state. + if (!TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_ON_FOOT)) + FollowerNPC_HandleSprite(); +} + +static void CreateFollowerNPC(u32 gfx, u32 followerFlags, const u8 *scriptPtr) +{ + struct ObjectEvent *player = &gObjectEvents[gPlayerAvatar.objectEventId]; + struct ObjectEvent *follower; + struct ObjectEventTemplate npc = { - if (!gObjectEvents[eventObjId].active || gObjectEvents[eventObjId].isPlayer) - continue; + .localId = OBJ_EVENT_ID_NPC_FOLLOWER, + .graphicsId = gfx, + .x = player->currentCoords.x, + .y = player->currentCoords.y, + .elevation = PlayerGetElevation(), + .script = scriptPtr + }; - if (gObjectEvents[eventObjId].localId == localId) - { - flag = GetObjectEventFlagIdByLocalIdAndMap(localId, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup); - // If the object does not have an event flag, don't create follower. - if (flag == 0) - return; + SetFollowerNPCData(FNPC_DATA_OBJ_ID, TrySpawnObjectEventTemplate(&npc, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, player->currentCoords.x, player->currentCoords.y)); + follower = &gObjectEvents[GetFollowerNPCData(FNPC_DATA_OBJ_ID)]; + follower->movementType = MOVEMENT_TYPE_NONE; + gSprites[follower->spriteId].callback = MovementType_None; + + SetFollowerNPCData(FNPC_DATA_IN_PROGRESS, TRUE); + SetFollowerNPCData(FNPC_DATA_GFX_ID, follower->graphicsId); + SetFollowerNPCData(FNPC_DATA_FOLLOWER_FLAGS, followerFlags); + SetFollowerNPCData(FNPC_DATA_SURF_BLOB, FNPC_SURF_BLOB_NONE); + SetFollowerNPCData(FNPC_DATA_COME_OUT_DOOR, FNPC_DOOR_NONE); + SetFollowerNPCScriptPointer(scriptPtr); - if (setScript == TRUE) - // Set the custom script. - script = ptr; - else - // Use the object's original script. - script = GetObjectEventScriptPointerByObjectEventId(eventObjId); + // If the player is biking and the follower flags prohibit biking, force the player to dismount the bike. + if (!CheckFollowerNPCFlag(FOLLOWER_NPC_FLAG_CAN_BIKE) + && TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_BIKE)) + SetPlayerAvatarTransitionFlags(PLAYER_AVATAR_FLAG_ON_FOOT); - npcX = gObjectEvents[eventObjId].currentCoords.x; - npcY = gObjectEvents[eventObjId].currentCoords.y; - faceDirection = gObjectEvents[eventObjId].facingDirection; - SetFollowerNPCData(FNPC_DATA_MAP_ID, gObjectEvents[eventObjId].localId); - RemoveObjectEvent(&gObjectEvents[eventObjId]); - FlagSet(flag); + // Set the follower sprite to match the player state. + if (!TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_ON_FOOT)) + FollowerNPC_HandleSprite(); - npc = *GetObjectEventTemplateByLocalIdAndMap(GetFollowerNPCData(FNPC_DATA_MAP_ID), gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup); - npc.movementType = 0; - npc.script = script; - npc.localId = OBJ_EVENT_ID_NPC_FOLLOWER; - SetFollowerNPCData(FNPC_DATA_OBJ_ID, TrySpawnObjectEventTemplate(&npc, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, npcX, npcY)); - follower = &gObjectEvents[GetFollowerNPCData(FNPC_DATA_OBJ_ID)]; - MoveObjectEventToMapCoords(follower, npcX, npcY); - ObjectEventTurn(follower, faceDirection); - follower->movementType = MOVEMENT_TYPE_NONE; - gSprites[follower->spriteId].callback = MovementType_None; - - SetFollowerNPCData(FNPC_DATA_IN_PROGRESS, TRUE); - SetFollowerNPCData(FNPC_DATA_GFX_ID, follower->graphicsId); - SetFollowerNPCData(FNPC_DATA_MAP_NUM, gSaveBlock1Ptr->location.mapNum); - SetFollowerNPCData(FNPC_DATA_MAP_GROUP, gSaveBlock1Ptr->location.mapGroup); - SetFollowerNPCScriptPointer(script); - SetFollowerNPCData(FNPC_DATA_EVENT_FLAG, flag); - SetFollowerNPCData(FNPC_DATA_FOLLOWER_FLAGS, followerFlags); - SetFollowerNPCData(FNPC_DATA_SURF_BLOB, FNPC_SURF_BLOB_NONE); - SetFollowerNPCData(FNPC_DATA_COME_OUT_DOOR, FNPC_DOOR_NONE); - - // If the player is biking and the follower flags prohibit biking, force the player to dismount the bike. - if (!CheckFollowerNPCFlag(FOLLOWER_NPC_FLAG_CAN_BIKE) - && TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_BIKE)) - SetPlayerAvatarTransitionFlags(PLAYER_AVATAR_FLAG_ON_FOOT); - - // Set the follower sprite to match the player state. - if (!TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_ON_FOOT)) - FollowerNPC_HandleSprite(); - } - } + HideNPCFollower(); + SetFollowerNPCData(FNPC_DATA_WARP_END, FNPC_WARP_REAPPEAR); } static u32 GetFollowerNPCSprite(void) @@ -951,13 +957,13 @@ void SetFollowerNPCSprite(u32 spriteIndex) SetFollowerNPCData(FNPC_DATA_CURRENT_SPRITE, spriteIndex); oldSpriteId = follower->spriteId; newGraphicsId = GetFollowerNPCSprite(); + clone = *GetObjectEventTemplateByLocalIdAndMap(OBJ_EVENT_ID_NPC_FOLLOWER, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup); backupFollower = *follower; backupFollower.graphicsId = newGraphicsId; DestroySprite(&gSprites[oldSpriteId]); RemoveObjectEvent(&gObjectEvents[GetFollowerNPCObjectId()]); - clone = *GetObjectEventTemplateByLocalIdAndMap(GetFollowerNPCData(FNPC_DATA_MAP_ID), GetFollowerNPCData(FNPC_DATA_MAP_NUM), GetFollowerNPCData(FNPC_DATA_MAP_GROUP)); clone.graphicsId = newGraphicsId; clone.movementType = 0; clone.localId = OBJ_EVENT_ID_NPC_FOLLOWER; @@ -1128,20 +1134,20 @@ void NPCFollow(struct ObjectEvent *npc, u32 state, bool32 ignoreScriptActive) void CreateFollowerNPCAvatar(void) { - struct ObjectEvent *player; - struct ObjectEventTemplate clone; - if (!PlayerHasFollowerNPC()) return; - player = &gObjectEvents[gPlayerAvatar.objectEventId]; - clone = *GetObjectEventTemplateByLocalIdAndMap(GetFollowerNPCData(FNPC_DATA_MAP_ID), GetFollowerNPCData(FNPC_DATA_MAP_NUM), GetFollowerNPCData(FNPC_DATA_MAP_GROUP)); - - clone.graphicsId = GetFollowerNPCSprite(); - clone.x = player->currentCoords.x - 7; - clone.y = player->currentCoords.y - 7; - clone.movementType = 0; - clone.localId = OBJ_EVENT_ID_NPC_FOLLOWER; + struct ObjectEvent *player = &gObjectEvents[gPlayerAvatar.objectEventId]; + struct ObjectEventTemplate clone = + { + .localId = OBJ_EVENT_ID_NPC_FOLLOWER, + .graphicsId = GetFollowerNPCSprite(), + .x = player->currentCoords.x - 7, + .y = player->currentCoords.y - 7, + .elevation = player->currentElevation, + .script = GetFollowerNPCScriptPointer(), + .movementType = MOVEMENT_TYPE_FACE_DOWN + }; switch (GetPlayerFacingDirection()) { @@ -1157,7 +1163,7 @@ void CreateFollowerNPCAvatar(void) } // Create NPC and store ID. - SetFollowerNPCData(FNPC_DATA_OBJ_ID, TrySpawnObjectEventTemplate(&clone, GetFollowerNPCData(FNPC_DATA_MAP_NUM), GetFollowerNPCData(FNPC_DATA_MAP_GROUP), clone.x, clone.y)); + SetFollowerNPCData(FNPC_DATA_OBJ_ID, TrySpawnObjectEventTemplate(&clone, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, clone.x, clone.y)); if (GetFollowerNPCData(FNPC_DATA_OBJ_ID) == OBJECT_EVENTS_COUNT) { ClearFollowerNPCData(); @@ -1622,17 +1628,33 @@ void ScriptSetFollowerNPC(struct ScriptContext *ctx) u32 battlePartner = ScriptReadHalfword(ctx); const u8 *script = (const u8 *)ScriptReadWord(ctx); + if (PlayerHasFollowerNPC()) + return; + SetFollowerNPCData(FNPC_DATA_BATTLE_PARTNER, battlePartner); TurnNPCIntoFollower(localId, flags, setScript, script); } +void ScriptCreateFollowerNPC(struct ScriptContext *ctx) +{ + u32 gfx = ScriptReadHalfword(ctx); + u32 flags = ScriptReadHalfword(ctx); + const u8 *script = (const u8 *)ScriptReadWord(ctx); + u32 battlePartner = ScriptReadHalfword(ctx); + + if (PlayerHasFollowerNPC()) + return; + + SetFollowerNPCData(FNPC_DATA_BATTLE_PARTNER, battlePartner); + CreateFollowerNPC(gfx, flags, script); +} + void ScriptDestroyFollowerNPC(struct ScriptContext *ctx) { if (!PlayerHasFollowerNPC()) return; RemoveObjectEvent(&gObjectEvents[GetFollowerNPCData(FNPC_DATA_OBJ_ID)]); - FlagSet(GetFollowerNPCData(FNPC_DATA_EVENT_FLAG)); ClearFollowerNPCData(); UpdateFollowingPokemon(); }