Added createfollowernpc macro to make a new follower from a gfx id (#7196)

This commit is contained in:
Bivurnum 2025-06-25 03:59:25 -05:00 committed by GitHub
parent b5956d8eba
commit bfb0c2fa73
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 128 additions and 96 deletions

View File

@ -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

View File

@ -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,

View File

@ -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;

View File

@ -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();
}