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:
.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::
call MossdeepCity_SpaceCenter_2F_EventScript_ChoosePartyForMultiBattle
release

View File

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

View File

@ -23,6 +23,7 @@
#include "field_message_box.h"
#include "field_screen_effect.h"
#include "field_weather.h"
#include "follower_npc.h"
#include "international_string_util.h"
#include "item.h"
#include "item_icon.h"
@ -58,6 +59,7 @@
#include "constants/battle_ai.h"
#include "constants/battle_frontier.h"
#include "constants/coins.h"
#include "constants/event_objects.h"
#include "constants/expansion.h"
#include "constants/flags.h"
#include "constants/items.h"
@ -71,6 +73,17 @@
#include "fake_rtc.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
{
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_OpenSubMenuFlagsVars(u8 taskId);
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 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_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_Slow(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_ShowExpansionVersion[];
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_PrintTimeOfDay[];
extern const u8 Debug_EventScript_TellTheTime[];
@ -393,6 +412,17 @@ static const u8 *const gTimeOfDayStringsTable[TIMES_OF_DAY_COUNT] = {
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
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}");
@ -454,6 +484,17 @@ static const struct DebugMenuOption sDebugMenu_Actions_TimeMenu_Weekdays[] =
{ 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[] =
{
{ COMPOUND_STRING("Get time…"), DebugAction_ExecuteScript, Debug_EventScript_TellTheTime },
@ -475,6 +516,13 @@ static const struct DebugMenuOption sDebugMenu_Actions_BerryFunctions[] =
{ 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[] =
{
{ 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("Berry Functions…"), DebugAction_OpenSubMenu, sDebugMenu_Actions_BerryFunctions },
{ 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 },
{ NULL }
};
@ -766,7 +815,8 @@ static bool32 IsSubMenuAction(const void *action)
{
return action == DebugAction_OpenSubMenu
|| action == DebugAction_OpenSubMenuFlagsVars
|| action == DebugAction_OpenSubMenuFakeRTC;
|| action == DebugAction_OpenSubMenuFakeRTC
|| action == DebugAction_OpenSubMenuCreateFollowerNPC;
}
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);
}
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
@ -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 tMenuTaskId

View File

@ -49,7 +49,6 @@
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);
@ -227,45 +226,6 @@ static void TurnNPCIntoFollower(u32 localId, u32 followerFlags, u32 setScript, c
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)
{
u32 i;
@ -712,6 +672,58 @@ static void CalculateFollowerNPCEscalatorTrajectoryDown(struct Task *task)
#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);
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);
u32 battlePartner = ScriptReadHalfword(ctx);
if (PlayerHasFollowerNPC())
if (!FNPC_ENABLE_NPC_FOLLOWERS || PlayerHasFollowerNPC())
return;
SetFollowerNPCData(FNPC_DATA_BATTLE_PARTNER, battlePartner);
@ -1651,12 +1663,7 @@ void ScriptCreateFollowerNPC(struct ScriptContext *ctx)
void ScriptDestroyFollowerNPC(struct ScriptContext *ctx)
{
if (!PlayerHasFollowerNPC())
return;
RemoveObjectEvent(&gObjectEvents[GetFollowerNPCData(FNPC_DATA_OBJ_ID)]);
ClearFollowerNPCData();
UpdateFollowingPokemon();
DestroyFollowerNPC();
}
void ScriptFaceFollowerNPC(struct ScriptContext *ctx)