Add debug options for follower NPCs (#7215)

This commit is contained in:
Bivurnum 2025-06-25 14:13:43 -05:00 committed by GitHub
parent 4f731835b4
commit c6221fa50e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 181 additions and 48 deletions

View File

@ -270,6 +270,24 @@ Debug_ShowExpansionVersion::
Debug_ExpansionVersion: Debug_ExpansionVersion:
.string "pokeemerald-expansion {STR_VAR_1}$" .string "pokeemerald-expansion {STR_VAR_1}$"
Debug_Follower_NPC_Not_Enabled::
msgbox Debug_Enable_To_Use_Follower_NPCs, MSGBOX_DEFAULT
release
end
Debug_Enable_To_Use_Follower_NPCs:
.string "Feature unavailable.\nSet FNPC_ENABLE_NPC_FOLLOWERS to\lTRUE in 'include/config/follower_npc.h'.$"
Debug_Follower_NPC_Event_Script::
lock
facefollowernpc
msgbox Debug_Follower_NPC_Test_Message, MSGBOX_DEFAULT
releaseall
end
Debug_Follower_NPC_Test_Message:
.string "This is a Follower NPC test.$"
Debug_EventScript_Steven_Multi:: Debug_EventScript_Steven_Multi::
call MossdeepCity_SpaceCenter_2F_EventScript_ChoosePartyForMultiBattle call MossdeepCity_SpaceCenter_2F_EventScript_ChoosePartyForMultiBattle
release release

View File

@ -82,6 +82,8 @@ const u8 *GetFollowerNPCScriptPointer(void);
u32 GetFollowerNPCData(enum FollowerNPCDataTypes type); u32 GetFollowerNPCData(enum FollowerNPCDataTypes type);
void ClearFollowerNPCData(void); void ClearFollowerNPCData(void);
void CreateFollowerNPC(u32 gfx, u32 followerFlags, const u8 *scriptPtr);
void DestroyFollowerNPC(void);
u32 DetermineFollowerNPCState(struct ObjectEvent *follower, u32 state, u32 direction); u32 DetermineFollowerNPCState(struct ObjectEvent *follower, u32 state, u32 direction);
void SetFollowerNPCSprite(u32 spriteIndex); void SetFollowerNPCSprite(u32 spriteIndex);

View File

@ -23,6 +23,7 @@
#include "field_message_box.h" #include "field_message_box.h"
#include "field_screen_effect.h" #include "field_screen_effect.h"
#include "field_weather.h" #include "field_weather.h"
#include "follower_npc.h"
#include "international_string_util.h" #include "international_string_util.h"
#include "item.h" #include "item.h"
#include "item_icon.h" #include "item_icon.h"
@ -58,6 +59,7 @@
#include "constants/battle_ai.h" #include "constants/battle_ai.h"
#include "constants/battle_frontier.h" #include "constants/battle_frontier.h"
#include "constants/coins.h" #include "constants/coins.h"
#include "constants/event_objects.h"
#include "constants/expansion.h" #include "constants/expansion.h"
#include "constants/flags.h" #include "constants/flags.h"
#include "constants/items.h" #include "constants/items.h"
@ -71,6 +73,17 @@
#include "fake_rtc.h" #include "fake_rtc.h"
#include "save.h" #include "save.h"
enum FollowerNPCCreateDebugMenu
{
DEBUG_FNPC_BRENDAN,
DEBUG_FNPC_MAY,
DEBUG_FNPC_STEVEN,
DEBUG_FNPC_WALLY,
DEBUG_FNPC_RED,
DEBUG_FNPC_LEAF,
DEBUG_FNPC_COUNT,
};
enum FlagsVarsDebugMenu enum FlagsVarsDebugMenu
{ {
DEBUG_FLAGVAR_MENU_ITEM_FLAGS, DEBUG_FLAGVAR_MENU_ITEM_FLAGS,
@ -223,6 +236,7 @@ static void Debug_RefreshListMenu(u8 taskId);
static void DebugAction_OpenSubMenu(u8 taskId, const struct DebugMenuOption *items); static void DebugAction_OpenSubMenu(u8 taskId, const struct DebugMenuOption *items);
static void DebugAction_OpenSubMenuFlagsVars(u8 taskId); static void DebugAction_OpenSubMenuFlagsVars(u8 taskId);
static void DebugAction_OpenSubMenuFakeRTC(u8 taskId, const struct DebugMenuOption *items); static void DebugAction_OpenSubMenuFakeRTC(u8 taskId, const struct DebugMenuOption *items);
static void DebugAction_OpenSubMenuCreateFollowerNPC(u8 taskId, const struct DebugMenuOption *items);
static void DebugAction_ExecuteScript(u8 taskId, const u8 *script); static void DebugAction_ExecuteScript(u8 taskId, const u8 *script);
static void DebugTask_HandleMenuInput_General(u8 taskId); static void DebugTask_HandleMenuInput_General(u8 taskId);
@ -241,6 +255,9 @@ static void DebugAction_Util_CheatStart(u8 taskId);
static void DebugAction_TimeMenu_ChangeTimeOfDay(u8 taskId); static void DebugAction_TimeMenu_ChangeTimeOfDay(u8 taskId);
static void DebugAction_TimeMenu_ChangeWeekdays(u8 taskId); static void DebugAction_TimeMenu_ChangeWeekdays(u8 taskId);
static void DebugAction_CreateFollowerNPC(u8 taskId);
static void DebugAction_DestroyFollowerNPC(u8 taskId);
static void DebugAction_PCBag_Fill_PCBoxes_Fast(u8 taskId); static void DebugAction_PCBag_Fill_PCBoxes_Fast(u8 taskId);
static void DebugAction_PCBag_Fill_PCBoxes_Slow(u8 taskId); static void DebugAction_PCBag_Fill_PCBoxes_Slow(u8 taskId);
static void DebugAction_PCBag_Fill_PCItemStorage(u8 taskId); static void DebugAction_PCBag_Fill_PCItemStorage(u8 taskId);
@ -345,6 +362,8 @@ extern const u8 Debug_CheckROMSpace[];
extern const u8 Debug_BoxFilledMessage[]; extern const u8 Debug_BoxFilledMessage[];
extern const u8 Debug_ShowExpansionVersion[]; extern const u8 Debug_ShowExpansionVersion[];
extern const u8 Debug_EventScript_EWRAMCounters[]; extern const u8 Debug_EventScript_EWRAMCounters[];
extern const u8 Debug_Follower_NPC_Event_Script[];
extern const u8 Debug_Follower_NPC_Not_Enabled[];
extern const u8 Debug_EventScript_Steven_Multi[]; extern const u8 Debug_EventScript_Steven_Multi[];
extern const u8 Debug_EventScript_PrintTimeOfDay[]; extern const u8 Debug_EventScript_PrintTimeOfDay[];
extern const u8 Debug_EventScript_TellTheTime[]; extern const u8 Debug_EventScript_TellTheTime[];
@ -393,6 +412,17 @@ static const u8 *const gTimeOfDayStringsTable[TIMES_OF_DAY_COUNT] = {
COMPOUND_STRING("Night"), COMPOUND_STRING("Night"),
}; };
// Follower NPC
static const u8 *const gFollowerNPCStringsTable[DEBUG_FNPC_COUNT] = {
COMPOUND_STRING("Brendan"),
COMPOUND_STRING("May"),
COMPOUND_STRING("Steven"),
COMPOUND_STRING("Wally"),
COMPOUND_STRING("Red"),
COMPOUND_STRING("Leaf"),
};
// Flags/Vars Menu // Flags/Vars Menu
static const u8 sDebugText_FlagsVars_VariableHex[] = _("{STR_VAR_1}{CLEAR_TO 90}\n0x{STR_VAR_2}{CLEAR_TO 90}"); static const u8 sDebugText_FlagsVars_VariableHex[] = _("{STR_VAR_1}{CLEAR_TO 90}\n0x{STR_VAR_2}{CLEAR_TO 90}");
static const u8 sDebugText_FlagsVars_Variable[] = _("Var: {STR_VAR_1}{CLEAR_TO 90}\nVal: {STR_VAR_3}{CLEAR_TO 90}\n{STR_VAR_2}"); static const u8 sDebugText_FlagsVars_Variable[] = _("Var: {STR_VAR_1}{CLEAR_TO 90}\nVal: {STR_VAR_3}{CLEAR_TO 90}\n{STR_VAR_2}");
@ -454,6 +484,17 @@ static const struct DebugMenuOption sDebugMenu_Actions_TimeMenu_Weekdays[] =
{ NULL } { NULL }
}; };
static const struct DebugMenuOption sDebugMenu_Actions_FollowerNPCMenu_Create[] =
{
[DEBUG_FNPC_BRENDAN] = { gFollowerNPCStringsTable[DEBUG_FNPC_BRENDAN], DebugAction_CreateFollowerNPC },
[DEBUG_FNPC_MAY] = { gFollowerNPCStringsTable[DEBUG_FNPC_MAY], DebugAction_CreateFollowerNPC },
[DEBUG_FNPC_STEVEN] = { gFollowerNPCStringsTable[DEBUG_FNPC_STEVEN], DebugAction_CreateFollowerNPC },
[DEBUG_FNPC_WALLY] = { gFollowerNPCStringsTable[DEBUG_FNPC_WALLY], DebugAction_CreateFollowerNPC },
[DEBUG_FNPC_RED] = { gFollowerNPCStringsTable[DEBUG_FNPC_RED], DebugAction_CreateFollowerNPC },
[DEBUG_FNPC_LEAF] = { gFollowerNPCStringsTable[DEBUG_FNPC_LEAF], DebugAction_CreateFollowerNPC },
{ NULL }
};
static const struct DebugMenuOption sDebugMenu_Actions_TimeMenu[] = static const struct DebugMenuOption sDebugMenu_Actions_TimeMenu[] =
{ {
{ COMPOUND_STRING("Get time…"), DebugAction_ExecuteScript, Debug_EventScript_TellTheTime }, { COMPOUND_STRING("Get time…"), DebugAction_ExecuteScript, Debug_EventScript_TellTheTime },
@ -475,6 +516,13 @@ static const struct DebugMenuOption sDebugMenu_Actions_BerryFunctions[] =
{ NULL } { NULL }
}; };
static const struct DebugMenuOption sDebugMenu_Actions_FollowerNPCMenu[] =
{
{ COMPOUND_STRING("Create Follower"), DebugAction_OpenSubMenuCreateFollowerNPC, sDebugMenu_Actions_FollowerNPCMenu_Create },
{ COMPOUND_STRING("Destroy Follower"), DebugAction_DestroyFollowerNPC },
{ NULL }
};
static const struct DebugMenuOption sDebugMenu_Actions_Utilities[] = static const struct DebugMenuOption sDebugMenu_Actions_Utilities[] =
{ {
{ COMPOUND_STRING("Fly to map…"), DebugAction_Util_Fly }, { COMPOUND_STRING("Fly to map…"), DebugAction_Util_Fly },
@ -486,6 +534,7 @@ static const struct DebugMenuOption sDebugMenu_Actions_Utilities[] =
{ COMPOUND_STRING("Cheat start"), DebugAction_Util_CheatStart }, { COMPOUND_STRING("Cheat start"), DebugAction_Util_CheatStart },
{ COMPOUND_STRING("Berry Functions…"), DebugAction_OpenSubMenu, sDebugMenu_Actions_BerryFunctions }, { COMPOUND_STRING("Berry Functions…"), DebugAction_OpenSubMenu, sDebugMenu_Actions_BerryFunctions },
{ COMPOUND_STRING("EWRAM Counters…"), DebugAction_ExecuteScript, Debug_EventScript_EWRAMCounters }, { COMPOUND_STRING("EWRAM Counters…"), DebugAction_ExecuteScript, Debug_EventScript_EWRAMCounters },
{ COMPOUND_STRING("Follower NPC…"), DebugAction_OpenSubMenu, sDebugMenu_Actions_FollowerNPCMenu },
{ COMPOUND_STRING("Steven Multi"), DebugAction_ExecuteScript, Debug_EventScript_Steven_Multi }, { COMPOUND_STRING("Steven Multi"), DebugAction_ExecuteScript, Debug_EventScript_Steven_Multi },
{ NULL } { NULL }
}; };
@ -766,7 +815,8 @@ static bool32 IsSubMenuAction(const void *action)
{ {
return action == DebugAction_OpenSubMenu return action == DebugAction_OpenSubMenu
|| action == DebugAction_OpenSubMenuFlagsVars || action == DebugAction_OpenSubMenuFlagsVars
|| action == DebugAction_OpenSubMenuFakeRTC; || action == DebugAction_OpenSubMenuFakeRTC
|| action == DebugAction_OpenSubMenuCreateFollowerNPC;
} }
static void Debug_ShowMenu(DebugFunc HandleInput, const struct DebugMenuOption *items) static void Debug_ShowMenu(DebugFunc HandleInput, const struct DebugMenuOption *items)
@ -1206,6 +1256,19 @@ static void DebugAction_ExecuteScript(u8 taskId, const u8 *script)
Debug_DestroyMenu_Full_Script(taskId, script); Debug_DestroyMenu_Full_Script(taskId, script);
} }
static void DebugAction_OpenSubMenuCreateFollowerNPC(u8 taskId, const struct DebugMenuOption *items)
{
if (FNPC_ENABLE_NPC_FOLLOWERS)
{
Debug_DestroyMenu(taskId);
Debug_ShowMenu(DebugTask_HandleMenuInput_General, items);
}
else
{
Debug_DestroyMenu_Full_Script(taskId, Debug_Follower_NPC_Not_Enabled);
}
}
// ******************************* // *******************************
// Actions Utilities // Actions Utilities
@ -3272,6 +3335,49 @@ static void DebugAction_Sound_MUS_SelectId(u8 taskId)
} }
} }
static const u32 gDebugFollowerNPCGraphics[] =
{
OBJ_EVENT_GFX_RIVAL_BRENDAN_NORMAL,
OBJ_EVENT_GFX_RIVAL_MAY_NORMAL,
OBJ_EVENT_GFX_STEVEN,
OBJ_EVENT_GFX_WALLY,
OBJ_EVENT_GFX_RED,
OBJ_EVENT_GFX_LEAF,
};
static void DebugAction_CreateFollowerNPC(u8 taskId)
{
u32 input = ListMenu_ProcessInput(gTasks[taskId].tMenuTaskId);
u32 gfx = gDebugFollowerNPCGraphics[input];
Debug_DestroyMenu_Full(taskId);
LockPlayerFieldControls();
DestroyFollowerNPC();
SetFollowerNPCData(FNPC_DATA_BATTLE_PARTNER, PARTNER_STEVEN);
CreateFollowerNPC(gfx, FNPC_ALL, Debug_Follower_NPC_Event_Script);
if (gfx != OBJ_EVENT_GFX_RIVAL_BRENDAN_NORMAL && gfx != OBJ_EVENT_GFX_RIVAL_MAY_NORMAL)
{
u32 flags = GetFollowerNPCData(FNPC_DATA_FOLLOWER_FLAGS);
SetFollowerNPCData(FNPC_DATA_FOLLOWER_FLAGS, (flags &= ~FNPC_RUNNING));
}
UnlockPlayerFieldControls();
}
static void DebugAction_DestroyFollowerNPC(u8 taskId)
{
if (FNPC_ENABLE_NPC_FOLLOWERS)
{
Debug_DestroyMenu_Full(taskId);
LockPlayerFieldControls();
DestroyFollowerNPC();
UnlockPlayerFieldControls();
}
else
{
Debug_DestroyMenu_Full_Script(taskId, Debug_Follower_NPC_Not_Enabled);
}
}
#undef tCurrentSong #undef tCurrentSong
#undef tMenuTaskId #undef tMenuTaskId

View File

@ -49,7 +49,6 @@
static void SetFollowerNPCScriptPointer(const u8 *script); static void SetFollowerNPCScriptPointer(const u8 *script);
static void PlayerLogCoordinates(struct ObjectEvent *player); static void PlayerLogCoordinates(struct ObjectEvent *player);
static void TurnNPCIntoFollower(u32 localId, u32 followerFlags, u32 setScript, const u8 *script); 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 u32 GetFollowerNPCSprite(void);
static bool32 IsStateMovement(u32 state); static bool32 IsStateMovement(u32 state);
static u32 GetPlayerFaceToDoorDirection(struct ObjectEvent *player, struct ObjectEvent *follower); static u32 GetPlayerFaceToDoorDirection(struct ObjectEvent *player, struct ObjectEvent *follower);
@ -227,45 +226,6 @@ static void TurnNPCIntoFollower(u32 localId, u32 followerFlags, u32 setScript, c
FollowerNPC_HandleSprite(); FollowerNPC_HandleSprite();
} }
static void CreateFollowerNPC(u32 gfx, u32 followerFlags, const u8 *scriptPtr)
{
struct ObjectEvent *player = &gObjectEvents[gPlayerAvatar.objectEventId];
struct ObjectEvent *follower;
struct ObjectEventTemplate npc =
{
.localId = OBJ_EVENT_ID_NPC_FOLLOWER,
.graphicsId = gfx,
.x = player->currentCoords.x,
.y = player->currentCoords.y,
.elevation = PlayerGetElevation(),
.script = scriptPtr
};
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 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) static u32 GetFollowerNPCSprite(void)
{ {
u32 i; u32 i;
@ -712,6 +672,58 @@ static void CalculateFollowerNPCEscalatorTrajectoryDown(struct Task *task)
#undef tCounter #undef tCounter
void CreateFollowerNPC(u32 gfx, u32 followerFlags, const u8 *scriptPtr)
{
if (PlayerHasFollowerNPC())
return;
struct ObjectEvent *player = &gObjectEvents[gPlayerAvatar.objectEventId];
struct ObjectEvent *follower;
struct ObjectEventTemplate npc =
{
.localId = OBJ_EVENT_ID_NPC_FOLLOWER,
.graphicsId = gfx,
.x = player->currentCoords.x,
.y = player->currentCoords.y,
.elevation = PlayerGetElevation(),
.script = scriptPtr
};
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 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);
}
void DestroyFollowerNPC(void)
{
if (!PlayerHasFollowerNPC())
return;
RemoveObjectEvent(&gObjectEvents[GetFollowerNPCData(FNPC_DATA_OBJ_ID)]);
ClearFollowerNPCData();
UpdateFollowingPokemon();
}
#define RETURN_STATE(state, dir) return newState == MOVEMENT_INVALID ? state + (dir - 1) : ReturnFollowerNPCDelayedState(dir - 1); #define RETURN_STATE(state, dir) return newState == MOVEMENT_INVALID ? state + (dir - 1) : ReturnFollowerNPCDelayedState(dir - 1);
u32 DetermineFollowerNPCState(struct ObjectEvent *follower, u32 state, u32 direction) u32 DetermineFollowerNPCState(struct ObjectEvent *follower, u32 state, u32 direction)
{ {
@ -1642,7 +1654,7 @@ void ScriptCreateFollowerNPC(struct ScriptContext *ctx)
const u8 *script = (const u8 *)ScriptReadWord(ctx); const u8 *script = (const u8 *)ScriptReadWord(ctx);
u32 battlePartner = ScriptReadHalfword(ctx); u32 battlePartner = ScriptReadHalfword(ctx);
if (PlayerHasFollowerNPC()) if (!FNPC_ENABLE_NPC_FOLLOWERS || PlayerHasFollowerNPC())
return; return;
SetFollowerNPCData(FNPC_DATA_BATTLE_PARTNER, battlePartner); SetFollowerNPCData(FNPC_DATA_BATTLE_PARTNER, battlePartner);
@ -1651,12 +1663,7 @@ void ScriptCreateFollowerNPC(struct ScriptContext *ctx)
void ScriptDestroyFollowerNPC(struct ScriptContext *ctx) void ScriptDestroyFollowerNPC(struct ScriptContext *ctx)
{ {
if (!PlayerHasFollowerNPC()) DestroyFollowerNPC();
return;
RemoveObjectEvent(&gObjectEvents[GetFollowerNPCData(FNPC_DATA_OBJ_ID)]);
ClearFollowerNPCData();
UpdateFollowingPokemon();
} }
void ScriptFaceFollowerNPC(struct ScriptContext *ctx) void ScriptFaceFollowerNPC(struct ScriptContext *ctx)