Merge pull request #158 from MiyazakiTheFalse/codex/enhance-endchase-feedback-and-sounds

Chase end feedback: concise messages, relief SFX, and resolved HUD phase
This commit is contained in:
MiyazakiTheFalse 2026-03-07 17:01:40 +00:00 committed by GitHub
commit 971ae0089f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 85 additions and 12 deletions

View File

@ -27,5 +27,6 @@ void ChaseStamina_OnMapTransition(const struct WarpData *from, const struct Warp
void ChaseStamina_OnBattleStart(void);
bool8 ChaseStamina_ShouldPrioritizeWildOpponent(u8 battler1, u8 battler2);
const u8 *ChaseStamina_TryConsumeEndFeedback(void);
#endif // GUARD_CHASE_STAMINA_H

View File

@ -5,5 +5,6 @@ void OverworldHud_Show(void);
void OverworldHud_Hide(void);
void OverworldHud_ShowToast(const u8 *text, u8 durationFrames);
void OverworldHud_Update(void);
void OverworldHud_BeginChaseResolvedState(void);
#endif // GUARD_OVERWORLD_HUD_H

View File

@ -1508,6 +1508,8 @@ extern const u8 gText_LevelUp_Plus[];
extern const u8 gText_PokemonOnHook[];
extern const u8 gText_NotEvenANibble[];
extern const u8 gText_ItGotAway[];
extern const u8 gText_YouGotAway[];
extern const u8 gText_TheChaseEnded[];
// pokemon_summary_screen
extern const u8 gText_PokeSum_PageName_KnownMoves[];

View File

@ -46,6 +46,12 @@
#define CHASE_STEPS_MIN 30
#define CHASE_STEPS_MAX 140
typedef enum
{
CHASE_END_FEEDBACK_NONE,
CHASE_END_FEEDBACK_ESCAPED,
CHASE_END_FEEDBACK_ENDED,
} ChaseEndFeedback;
#define CHASE_START_TOAST_DURATION_FRAMES 90
static const u8 sChaseStartToastText[] = _("Something is chasing you!");
@ -58,6 +64,7 @@ static EWRAM_DATA u16 sChaseStepsRemaining = 0;
static EWRAM_DATA u8 sChaseReengageStepCountdown = 0;
static EWRAM_DATA bool8 sPendingWildFirstMovePriority = FALSE;
static EWRAM_DATA bool8 sBattleUsesWildFirstMovePriority = FALSE;
static EWRAM_DATA u8 sPendingChaseEndFeedback = CHASE_END_FEEDBACK_NONE;
static bool8 IsMapTypeChaseCompatible(u8 mapType);
@ -148,6 +155,7 @@ static bool8 IsCurrentAreaValidForActiveChase(void)
return TRUE;
}
static void EndChase(bool8 shouldQueueFeedback, u8 feedbackType)
static bool8 ShouldSuppressChaseStartFeedback(void)
{
if (ScriptContext_IsEnabled())
@ -168,10 +176,15 @@ static void TryShowChaseStartFeedback(void)
static void EndChase(void)
{
bool8 wasActive = ChaseStamina_IsChaseActive();
sActiveChasers = 0;
sChaseStepsRemaining = 0;
sChaseReengageStepCountdown = 0;
ChaseOverworld_OnChaseEnded();
if (wasActive && shouldQueueFeedback && feedbackType != CHASE_END_FEEDBACK_NONE)
sPendingChaseEndFeedback = feedbackType;
}
static void StartChase(u8 initialChasers, u16 initialSteps)
@ -180,7 +193,7 @@ static void StartChase(u8 initialChasers, u16 initialSteps)
if (initialChasers == 0 || initialSteps == 0)
{
EndChase();
EndChase(FALSE, CHASE_END_FEEDBACK_NONE);
return;
}
@ -355,7 +368,7 @@ void ChaseStamina_UpdateOverworldFrame(bool8 tookStep)
ClampCurrentStamina();
if (ChaseStamina_IsChaseActive() && !IsCurrentAreaValidForActiveChase())
EndChase();
EndChase(TRUE, CHASE_END_FEEDBACK_ENDED);
if (sStaminaExhaustedCooldownFrames != 0)
sStaminaExhaustedCooldownFrames--;
@ -379,7 +392,7 @@ void ChaseStamina_UpdateOverworldFrame(bool8 tookStep)
sChaseStepsRemaining--;
if (sChaseStepsRemaining == 0)
{
EndChase();
EndChase(TRUE, CHASE_END_FEEDBACK_ESCAPED);
}
else if (sChaseReengageStepCountdown != 0)
{
@ -426,7 +439,7 @@ bool8 ChaseStamina_ShouldSuppressRandomEncounters(void)
{
if (ChaseStamina_IsChaseActive() && !IsCurrentAreaValidForActiveChase())
{
EndChase();
EndChase(TRUE, CHASE_END_FEEDBACK_ENDED);
return FALSE;
}
@ -498,7 +511,7 @@ void ChaseStamina_OnWildBattleEnded(u8 battleOutcome, u32 battleTypeFlags)
if (sActiveChasers != 0)
sActiveChasers--;
if (sActiveChasers == 0)
EndChase();
EndChase(TRUE, CHASE_END_FEEDBACK_ENDED);
break;
}
@ -510,12 +523,12 @@ void ChaseStamina_OnWildBattleEnded(u8 battleOutcome, u32 battleTypeFlags)
case B_OUTCOME_MON_TELEPORTED:
case B_OUTCOME_LINK_BATTLE_RAN:
// Forced-end and escape outcomes clear the chase to keep its state deterministic.
EndChase();
EndChase(FALSE, CHASE_END_FEEDBACK_NONE);
break;
default:
// Any unknown outcome is treated as a forced end to avoid stale chase state.
EndChase();
EndChase(FALSE, CHASE_END_FEEDBACK_NONE);
break;
}
}
@ -549,7 +562,7 @@ void ChaseStamina_OnMapTransition(const struct WarpData *from, const struct Warp
if (from == NULL || to == NULL)
{
EndChase();
EndChase(TRUE, CHASE_END_FEEDBACK_ENDED);
return;
}
@ -557,7 +570,7 @@ void ChaseStamina_OnMapTransition(const struct WarpData *from, const struct Warp
toMap = Overworld_GetMapHeaderByGroupAndId(to->mapGroup, to->mapNum);
if (fromMap == NULL || toMap == NULL)
{
EndChase();
EndChase(TRUE, CHASE_END_FEEDBACK_ENDED);
return;
}
@ -565,20 +578,38 @@ void ChaseStamina_OnMapTransition(const struct WarpData *from, const struct Warp
{
if (!IsMapTypeChaseCompatible(toMap->mapType))
{
EndChase();
EndChase(TRUE, CHASE_END_FEEDBACK_ENDED);
return;
}
}
if (IsMapTypeOutdoors(fromMap->mapType) != IsMapTypeOutdoors(toMap->mapType))
{
EndChase();
EndChase(TRUE, CHASE_END_FEEDBACK_ENDED);
return;
}
if (fromMap->regionMapSectionId != toMap->regionMapSectionId)
{
EndChase();
EndChase(TRUE, CHASE_END_FEEDBACK_ENDED);
return;
}
}
const u8 *ChaseStamina_TryConsumeEndFeedback(void)
{
const u8 *message = NULL;
switch (sPendingChaseEndFeedback)
{
case CHASE_END_FEEDBACK_ESCAPED:
message = gText_YouGotAway;
break;
case CHASE_END_FEEDBACK_ENDED:
message = gText_TheChaseEnded;
break;
}
sPendingChaseEndFeedback = CHASE_END_FEEDBACK_NONE;
return message;
}

View File

@ -134,6 +134,7 @@ static u16 GetCenterScreenMetatileBehavior(void);
static void SetDefaultFlashLevel(void);
static void Overworld_TryMapConnectionMusicTransition(void);
static void ChooseAmbientCrySpecies(void);
static void TryShowChaseEndFeedback(void);
static void CB2_Overworld(void);
static void CB2_LoadMap2(void);
@ -1510,6 +1511,26 @@ void CB1_Overworld(void)
}
}
static void TryShowChaseEndFeedback(void)
{
const u8 *message;
if (ArePlayerFieldControlsLocked())
return;
if (ScriptContext_IsEnabled())
return;
if (!IsFieldMessageBoxHidden())
return;
message = ChaseStamina_TryConsumeEndFeedback();
if (message == NULL)
return;
OverworldHud_BeginChaseResolvedState();
PlaySE(SE_DING_DONG);
ShowFieldMessage(message);
}
static void OverworldBasic(void)
{
if (IsCorpseRunFeatureEnabled())
@ -1519,6 +1540,7 @@ static void OverworldBasic(void)
}
OverworldHud_Update();
TryShowChaseEndFeedback();
ScriptContext_RunScript();
RunTasks();

View File

@ -30,6 +30,7 @@
#define CHASE_ICON_X_SPACING 18
#define CHASE_ICON_Y 11
#define CHASE_HUD_ICON_SPECIES SPECIES_HOUNDOUR
#define CHASE_RESOLVED_DISPLAY_FRAMES 30
#define CHASE_PRESSURE_HIGH_COUNT 2
#define CHASE_PULSE_SLOW_FRAMES 14
#define CHASE_PULSE_FAST_FRAMES 6
@ -109,6 +110,7 @@ static EWRAM_DATA u8 sLastChaserCount = 0xFF;
static EWRAM_DATA u8 sLastPulseDelay = 0xFF;
static EWRAM_DATA u8 sPulseFrameCounter = 0;
static EWRAM_DATA u8 sChaseIconSpriteIds[CHASE_ICON_COUNT_MAX] = {MAX_SPRITES, MAX_SPRITES};
static EWRAM_DATA u8 sChaseResolvedDisplayFrames = 0;
static const u8 sOverworldChaseCountTextColor[] = {TEXT_COLOR_TRANSPARENT, TEXT_COLOR_WHITE, TEXT_COLOR_DARK_GRAY};
@ -170,6 +172,9 @@ static void OverworldHud_UpdateChaseIcons(void)
// These are HUD-only indicators for chase pressure (screen-space icons),
// not the overworld pursuing object events handled by chase_overworld.c.
if (ChaseStamina_IsChaseActive())
iconCount = min(ChaseStamina_GetActiveChasers(), CHASE_ICON_COUNT_MAX);
else if (sChaseResolvedDisplayFrames != 0)
iconCount = 1;
chaserCount = ChaseStamina_GetActiveChasers();
else
chaserCount = 0;
@ -307,6 +312,7 @@ void OverworldHud_Show(void)
sLastStaminaCurrent = 0xFF;
sLastStaminaMax = 0xFF;
sChaseResolvedDisplayFrames = 0;
sLastChaserCount = 0xFF;
sPulseFrameCounter = 0;
sLastPulseDelay = 0xFF;
@ -348,6 +354,9 @@ void OverworldHud_Update(void)
if (sOverworldHudWindowId == WINDOW_NONE)
OverworldHud_Show();
if (sChaseResolvedDisplayFrames != 0 && !ChaseStamina_IsChaseActive())
sChaseResolvedDisplayFrames--;
current = ChaseStamina_GetCurrent();
max = ChaseStamina_GetMax();
@ -364,3 +373,8 @@ void OverworldHud_Update(void)
CopyWindowToVram(sOverworldHudWindowId, COPYWIN_GFX);
OverworldHud_UpdateChaseIcons();
}
void OverworldHud_BeginChaseResolvedState(void)
{
sChaseResolvedDisplayFrames = CHASE_RESOLVED_DISPLAY_FRAMES;
}

View File

@ -1072,6 +1072,8 @@ ALIGNED(4) const u8 gText_Ghost[] = _("GHOST");
const u8 gText_PokemonOnHook[] = _("A POKéMON's on the hook!{PAUSE_UNTIL_PRESS}");
const u8 gText_NotEvenANibble[] = _("Not even a nibble‥{PAUSE_UNTIL_PRESS}");
const u8 gText_ItGotAway[] = _("It got away‥{PAUSE_UNTIL_PRESS}");
const u8 gText_YouGotAway[] = _("You got away.");
const u8 gText_TheChaseEnded[] = _("The chase ended.");
const u8 gText_Rooftop2[] = _("ROOFTOP");
ALIGNED(4) const u8 gString_PokemonFireRed_Staff[] = _("Pokémon FireRed Version\nStaff");
ALIGNED(4) const u8 gString_PokemonLeafGreen_Staff[] = _("Pokémon LeafGreen Version\nStaff");