From a5956960867a1a4e099143375df2ece9117b03f6 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Sat, 4 Oct 2025 18:05:28 +0200 Subject: [PATCH] Update fishing odds to match official games (#7574) --- include/config/fishing.h | 13 + include/config/item.h | 9 - include/constants/wild_encounter.h | 3 - include/field_player_avatar.h | 2 +- include/fishing.h | 9 + include/random.h | 2 + src/battle_setup.c | 4 +- src/field_player_avatar.c | 674 +---------------------------- src/fishing.c | 650 ++++++++++++++++++++++++++++ src/item_use.c | 1 + src/pokemon.c | 5 +- src/wild_encounter.c | 18 +- 12 files changed, 682 insertions(+), 708 deletions(-) create mode 100644 include/config/fishing.h create mode 100644 include/fishing.h create mode 100644 src/fishing.c diff --git a/include/config/fishing.h b/include/config/fishing.h new file mode 100644 index 0000000000..0cbf666a54 --- /dev/null +++ b/include/config/fishing.h @@ -0,0 +1,13 @@ +#ifndef GUARD_CONFIG_FISHING_H +#define GUARD_CONFIG_FISHING_H + +#define I_FISHING_BITE_ODDS GEN_LATEST // In Gen 1 and Gen 2, the Old Rod has a 100% chance for a bite, Good Rod has a 66% chance for a bite, and Super Rod has a 50% chance for a bite. In Gen 3, all rods have a base 50% chance for a bite. In Gen 4 onwards, the Old Rod has a base 25% chance for a bite, Good Rod has a 50% chance for a bite, and Super Rod has a 75% chance for a bite. +#define I_FISHING_MINIGAME GEN_3 // Each generation uses a variation of reeling in Pokémon once they have been hooked. NOTE: Only the Gen 1/2 and Gen 3 minigames are implemented right now! +#define I_FISHING_ENVIRONMENT GEN_LATEST // In Gen 3, the battle environment when fighting a hooked Pokémon is based on the tile the player is standing on. In Gen 4 onwards, the environment is based on tile that is being fished in, resulting in it usually being a water environment. +#define I_FISHING_STICKY_BOOST GEN_LATEST // In Gen 3, a Pokemon with Suction Cups or Sticky Hold in the first slot of the party causes the chance for a bite to increase by about 35%. In Gen 4 onwards, it doubles the base bite chance. +#define I_FISHING_FOLLOWER_BOOST FALSE // In HGSS, fishing bite odds are increased depending on the friendship of the current following Pokémon. +#define I_FISHING_CHAIN FALSE // Introduced in XY, hooking the same Pokémon repeatedly will increase the odds of that mon being shiny. NOTE: This implementation is an approximation of the actual feature, as XY have not been throughoutly documented or datamined. +#define I_FISHING_PROXIMITY FALSE // In XY, bite chance is boosted by the number of adjacent non-surfable tiles next to your fishing line +#define I_FISHING_TIME_OF_DAY_BOOST FALSE // In XY, bite chance is boosted during morning and evening + +#endif // GUARD_CONFIG_FISHING_H diff --git a/include/config/item.h b/include/config/item.h index 96156b0db8..6792f8979f 100644 --- a/include/config/item.h +++ b/include/config/item.h @@ -38,13 +38,4 @@ // Vs. Seeker #define I_VS_SEEKER_CHARGING 0 // If this flag is assigned, the Vs Seeker functionality will be enabled. When the player has the Vs. Seeker, Match Call rematch functions will stop working. -// Fishing -#define I_FISHING_BITE_ODDS GEN_LATEST // In Gen 1 and Gen 2, the Old Rod has a 100% chance for a bite, Good Rod has a 66% chance for a bite, and Super Rod has a 50% chance for a bite. In Gen 3, all rods have a base 50% chance for a bite. In Gen 4 onwards, the Old Rod has a base 25% chance for a bite, Good Rod has a 50% chance for a bite, and Super Rod has a 75% chance for a bite. -#define I_FISHING_MINIGAME GEN_3 // Each generation uses a variation of reeling in Pokémon once they have been hooked. NOTE: Only the Gen 1/2 and Gen 3 minigames are implemented right now! -#define I_FISHING_ENVIRONMENT GEN_LATEST // In Gen 3, the battle environment when fighting a hooked Pokémon is based on the tile the player is standing on. In Gen 4 onwards, the environment is based on tile that is being fished in, resulting in it usually being a water environment. -#define I_FISHING_STICKY_BOOST GEN_LATEST // In Gen 3, a Pokemon with Suction Cups or Sticky Hold in the first slot of the party causes the chance for a bite to increase by about 35%. In Gen 4 onwards, it doubles the base bite chance. -#define I_FISHING_FOLLOWER_BOOST FALSE // In HGSS, fishing bite odds are increased depending on the friendship of the current following Pokémon. -#define I_FISHING_CHAIN FALSE // Introduced in XY, hooking the same Pokémon repeatedly will increase the odds of that mon being shiny. NOTE: This implementation is an approximation of the actual feature, as XY have not been throughoutly documented or datamined. -#define I_FISHING_PROXIMITY FALSE // Introduced in XY, fishing away from other people in enclosed areas will increase the chances of a Pokémon being hooked. NOTE: This implementation is an approximation of the actual feature, as XY have not been throughoutly documented or datamined. - #endif // GUARD_CONFIG_ITEM_H diff --git a/include/constants/wild_encounter.h b/include/constants/wild_encounter.h index 364d18350f..364dc7b35a 100644 --- a/include/constants/wild_encounter.h +++ b/include/constants/wild_encounter.h @@ -9,7 +9,4 @@ #define NUM_ALTERING_CAVE_TABLES 9 -#define FISHING_CHAIN_LENGTH_MAX 200 -#define FISHING_CHAIN_SHINY_STREAK_MAX 20 - #endif // GUARD_CONSTANTS_WILD_ENCOUNTER_H diff --git a/include/field_player_avatar.h b/include/field_player_avatar.h index cb7128f203..a6d56f62f7 100644 --- a/include/field_player_avatar.h +++ b/include/field_player_avatar.h @@ -64,7 +64,7 @@ bool32 IsPlayerSpinEntranceActive(void); bool32 IsPlayerSpinExitActive(void); void SetPlayerInvisibility(bool8 invisible); u8 player_get_pos_including_state_based_drift(s16 *x, s16 *y); -void StartFishing(u8 rod); +void SetPlayerAvatarFishing(u8 direction); bool8 ObjectMovingOnRockStairs(struct ObjectEvent *objectEvent, u8 direction); //sideways stairs u8 GetRightSideStairsDirection(u8 direction); diff --git a/include/fishing.h b/include/fishing.h new file mode 100644 index 0000000000..05ee236604 --- /dev/null +++ b/include/fishing.h @@ -0,0 +1,9 @@ +#ifndef GUARD_FISHING_H +#define GUARD_FISHING_H + +void StartFishing(u8 rod); +void UpdateChainFishingStreak(); +u32 CalculateChainFishingShinyRolls(void); +bool32 ShouldUseFishingEnvironmentInBattle(); + +#endif // GUARD_FISHING_H \ No newline at end of file diff --git a/include/random.h b/include/random.h index d7f707f320..3da116a3f2 100644 --- a/include/random.h +++ b/include/random.h @@ -219,6 +219,8 @@ enum RandomTag RNG_BALLTHROW_SHAKE, RNG_PRESENT, RNG_MAGNITUDE, + RNG_FISHING_BITE, + RNG_FISHING_GEN3_STICKY, }; #define RandomWeighted(tag, ...) \ diff --git a/src/battle_setup.c b/src/battle_setup.c index d755fc1b95..f5925d86d7 100644 --- a/src/battle_setup.c +++ b/src/battle_setup.c @@ -52,7 +52,7 @@ #include "constants/trainers.h" #include "constants/trainer_hill.h" #include "constants/weather.h" -#include "wild_encounter.h" +#include "fishing.h" enum { TRANSITION_TYPE_NORMAL, @@ -627,7 +627,7 @@ enum BattleEnvironments BattleSetup_GetEnvironmentId(void) u16 tileBehavior; s16 x, y; - if (I_FISHING_ENVIRONMENT >= GEN_4 && gIsFishingEncounter) + if (ShouldUseFishingEnvironmentInBattle()) GetXYCoordsOneStepInFrontOfPlayer(&x, &y); else PlayerGetDestCoords(&x, &y); diff --git a/src/field_player_avatar.c b/src/field_player_avatar.c index 8bee9c7644..4633e38d82 100644 --- a/src/field_player_avatar.c +++ b/src/field_player_avatar.c @@ -146,40 +146,6 @@ static void CreateStopSurfingTask(u8); static void Task_StopSurfingInit(u8); static void Task_WaitStopSurfing(u8); -static void Task_Fishing(u8); -static bool32 Fishing_Init(struct Task *); -static bool32 Fishing_GetRodOut(struct Task *); -static bool32 Fishing_WaitBeforeDots(struct Task *); -static bool32 Fishing_InitDots(struct Task *); -static bool32 Fishing_ShowDots(struct Task *); -static bool32 Fishing_CheckForBite(struct Task *); -static bool32 Fishing_GotBite(struct Task *); -static bool32 Fishing_ChangeMinigame(struct Task *); -static bool32 Fishing_WaitForA(struct Task *); -static bool32 Fishing_APressNoMinigame(struct Task *); -static bool32 Fishing_CheckMoreDots(struct Task *); -static bool32 Fishing_MonOnHook(struct Task *); -static bool32 Fishing_StartEncounter(struct Task *); -static bool32 Fishing_NotEvenNibble(struct Task *); -static bool32 Fishing_GotAway(struct Task *); -static bool32 Fishing_NoMon(struct Task *); -static bool32 Fishing_PutRodAway(struct Task *); -static bool32 Fishing_EndNoMon(struct Task *); -static void AlignFishingAnimationFrames(void); -static bool32 DoesFishingMinigameAllowCancel(void); -static bool32 Fishing_DoesFirstMonInPartyHaveSuctionCupsOrStickyHold(void); -static bool32 Fishing_RollForBite(u32, bool32); -static u32 CalculateFishingBiteOdds(u32, bool32); -static u32 CalculateFishingFollowerBoost(void); -static u32 CalculateFishingProximityBoost(u32 odds); -static void GetCoordinatesAroundBobber(s16[], s16[][AXIS_COUNT], u32); -static u32 CountQualifyingTiles(s16[][AXIS_COUNT], s16 player[], u8 facingDirection, struct ObjectEvent *objectEvent, bool32 isTileLand[]); -static bool32 CheckTileQualification(s16 tile[], s16 player[], u32 facingDirection, struct ObjectEvent* objectEvent, bool32 isTileLand[], u32 direction); -static u32 CountLandTiles(bool32 isTileLand[]); -static bool32 IsPlayerHere(s16, s16, s16, s16); -static bool32 IsMetatileBlocking(s16, s16, u32); -static bool32 IsMetatileLand(s16, s16, u32); - static u8 TrySpinPlayerForWarp(struct ObjectEvent *, s16 *); static bool8 (*const sForcedMovementTestFuncs[NUM_FORCED_MOVEMENTS])(u8) = @@ -1663,7 +1629,7 @@ void SetPlayerAvatarFieldMove(void) StartSpriteAnim(&gSprites[gPlayerAvatar.spriteId], ANIM_FIELD_MOVE); } -static void SetPlayerAvatarFishing(u8 direction) +void SetPlayerAvatarFishing(u8 direction) { ObjectEventSetGraphicsId(&gObjectEvents[gPlayerAvatar.objectEventId], GetPlayerAvatarGraphicsIdByStateId(PLAYER_AVATAR_STATE_FISHING)); StartSpriteAnim(&gSprites[gPlayerAvatar.spriteId], GetFishingDirectionAnimNum(direction)); @@ -1933,644 +1899,6 @@ static void Task_WaitStopSurfing(u8 taskId) } } -#define tStep data[0] -#define tFrameCounter data[1] -#define tNumDots data[2] -#define tDotsRequired data[3] -#define tRoundsPlayed data[12] -#define tMinRoundsRequired data[13] -#define tPlayerGfxId data[14] -#define tFishingRod data[15] - -#define FISHING_PROXIMITY_BOOST 4 -#define FISHING_STICKY_BOOST 36 - -#if I_FISHING_BITE_ODDS >= GEN_4 - #define FISHING_OLD_ROD_ODDS 75 - #define FISHING_GOOD_ROD_ODDS 50 - #define FISHING_SUPER_ROD_ODDS 25 -#elif I_FISHING_BITE_ODDS >= GEN_3 - #define FISHING_OLD_ROD_ODDS 50 - #define FISHING_GOOD_ROD_ODDS 50 - #define FISHING_SUPER_ROD_ODDS 50 -#else - #define FISHING_OLD_ROD_ODDS 0 - #define FISHING_GOOD_ROD_ODDS 33 - #define FISHING_SUPER_ROD_ODDS 50 -#endif - -enum -{ - FISHING_INIT, - FISHING_GET_ROD_OUT, - FISHING_WAIT_BEFORE_DOTS, - FISHING_INIT_DOTS, - FISHING_SHOW_DOTS, - FISHING_CHECK_FOR_BITE, - FISHING_GOT_BITE, - FISHING_CHANGE_MINIGAME, - FISHING_WAIT_FOR_A, - FISHING_A_PRESS_NO_MINIGAME, - FISHING_CHECK_MORE_DOTS, - FISHING_MON_ON_HOOK, - FISHING_START_ENCOUNTER, - FISHING_NOT_EVEN_NIBBLE, - FISHING_GOT_AWAY, - FISHING_NO_MON, - FISHING_PUT_ROD_AWAY, - FISHING_END_NO_MON, -}; - -static bool32 (*const sFishingStateFuncs[])(struct Task *) = -{ - [FISHING_INIT] = Fishing_Init, - [FISHING_GET_ROD_OUT] = Fishing_GetRodOut, - [FISHING_WAIT_BEFORE_DOTS] = Fishing_WaitBeforeDots, - [FISHING_INIT_DOTS] = Fishing_InitDots, - [FISHING_SHOW_DOTS] = Fishing_ShowDots, - [FISHING_CHECK_FOR_BITE] = Fishing_CheckForBite, - [FISHING_GOT_BITE] = Fishing_GotBite, - [FISHING_CHANGE_MINIGAME] = Fishing_ChangeMinigame, - [FISHING_WAIT_FOR_A] = Fishing_WaitForA, - [FISHING_A_PRESS_NO_MINIGAME] = Fishing_APressNoMinigame, - [FISHING_CHECK_MORE_DOTS] = Fishing_CheckMoreDots, - [FISHING_MON_ON_HOOK] = Fishing_MonOnHook, - [FISHING_START_ENCOUNTER] = Fishing_StartEncounter, - [FISHING_NOT_EVEN_NIBBLE] = Fishing_NotEvenNibble, - [FISHING_GOT_AWAY] = Fishing_GotAway, - [FISHING_NO_MON] = Fishing_NoMon, - [FISHING_PUT_ROD_AWAY] = Fishing_PutRodAway, - [FISHING_END_NO_MON] = Fishing_EndNoMon, -}; - -void StartFishing(u8 rod) -{ - u8 taskId = CreateTask(Task_Fishing, 0xFF); - - gTasks[taskId].tFishingRod = rod; - Task_Fishing(taskId); -} - -static void Task_Fishing(u8 taskId) -{ - while (sFishingStateFuncs[gTasks[taskId].tStep](&gTasks[taskId])) - ; -} - -static bool32 Fishing_Init(struct Task *task) -{ - LockPlayerFieldControls(); - gPlayerAvatar.preventStep = TRUE; - task->tStep = FISHING_GET_ROD_OUT; - return FALSE; -} - -static bool32 Fishing_GetRodOut(struct Task *task) -{ - struct ObjectEvent *playerObjEvent; - const s16 minRounds1[] = { - [OLD_ROD] = 1, - [GOOD_ROD] = 1, - [SUPER_ROD] = 1 - }; - const s16 minRounds2[] = { - [OLD_ROD] = 1, - [GOOD_ROD] = 3, - [SUPER_ROD] = 6 - }; - - task->tRoundsPlayed = 0; - task->tMinRoundsRequired = minRounds1[task->tFishingRod] + (Random() % minRounds2[task->tFishingRod]); - task->tPlayerGfxId = gObjectEvents[gPlayerAvatar.objectEventId].graphicsId; - playerObjEvent = &gObjectEvents[gPlayerAvatar.objectEventId]; - ObjectEventClearHeldMovementIfActive(playerObjEvent); - playerObjEvent->enableAnim = TRUE; - SetPlayerAvatarFishing(playerObjEvent->facingDirection); - task->tStep = FISHING_WAIT_BEFORE_DOTS; - return FALSE; -} - -static bool32 Fishing_WaitBeforeDots(struct Task *task) -{ - AlignFishingAnimationFrames(); - - // Wait one second - task->tFrameCounter++; - if (task->tFrameCounter >= 60) - task->tStep = FISHING_INIT_DOTS; - return FALSE; -} - -static bool32 Fishing_InitDots(struct Task *task) -{ - u32 randVal; - - LoadMessageBoxAndFrameGfx(0, TRUE); - task->tStep = FISHING_SHOW_DOTS; - task->tFrameCounter = 0; - task->tNumDots = 0; - randVal = Random(); - randVal %= 10; - task->tDotsRequired = randVal + 1; - if (task->tRoundsPlayed == 0) - task->tDotsRequired = randVal + 4; - if (task->tDotsRequired >= 10) - task->tDotsRequired = 10; - return TRUE; -} - -static bool32 Fishing_ShowDots(struct Task *task) -{ - const u8 dot[] = _("·"); - - AlignFishingAnimationFrames(); - task->tFrameCounter++; - if (JOY_NEW(A_BUTTON)) - { - if (!DoesFishingMinigameAllowCancel()) - return FALSE; - - task->tStep = FISHING_NOT_EVEN_NIBBLE; - if (task->tRoundsPlayed != 0) - task->tStep = FISHING_GOT_AWAY; - return TRUE; - } - else - { - if (task->tFrameCounter >= 20) - { - task->tFrameCounter = 0; - if (task->tNumDots >= task->tDotsRequired) - { - task->tStep = FISHING_CHECK_FOR_BITE; - if (task->tRoundsPlayed != 0) - task->tStep = FISHING_GOT_BITE; - task->tRoundsPlayed++; - } - else - { - AddTextPrinterParameterized(0, FONT_NORMAL, dot, task->tNumDots * 8, 1, 0, NULL); - task->tNumDots++; - } - } - return FALSE; - } -} - -static bool32 Fishing_CheckForBite(struct Task *task) -{ - bool32 bite, firstMonHasSuctionOrSticky; - - AlignFishingAnimationFrames(); - task->tStep = FISHING_GOT_BITE; - bite = FALSE; - - if (!DoesCurrentMapHaveFishingMons()) - { - task->tStep = FISHING_NOT_EVEN_NIBBLE; - return TRUE; - } - - firstMonHasSuctionOrSticky = Fishing_DoesFirstMonInPartyHaveSuctionCupsOrStickyHold(); - - if(firstMonHasSuctionOrSticky) - bite = Fishing_RollForBite(task->tFishingRod, firstMonHasSuctionOrSticky); - - if (!bite) - bite = Fishing_RollForBite(task->tFishingRod, FALSE); - - if (!bite) - task->tStep = FISHING_NOT_EVEN_NIBBLE; - - if (bite) - StartSpriteAnim(&gSprites[gPlayerAvatar.spriteId], GetFishingBiteDirectionAnimNum(GetPlayerFacingDirection())); - - return TRUE; -} - -static bool32 Fishing_GotBite(struct Task *task) -{ - AlignFishingAnimationFrames(); - AddTextPrinterParameterized(0, FONT_NORMAL, gText_OhABite, 0, 17, 0, NULL); - task->tStep = FISHING_CHANGE_MINIGAME; - task->tFrameCounter = 0; - return FALSE; -} - -static bool32 Fishing_ChangeMinigame(struct Task *task) -{ - switch (I_FISHING_MINIGAME) - { - case GEN_1: - case GEN_2: - task->tStep = FISHING_A_PRESS_NO_MINIGAME; - break; - case GEN_3: - default: - task->tStep = FISHING_WAIT_FOR_A; - break; - } - return TRUE; -} - -// We have a bite. Now, wait for the player to press A, or the timer to expire. -static bool32 Fishing_WaitForA(struct Task *task) -{ - const s16 reelTimeouts[3] = { - [OLD_ROD] = 36, - [GOOD_ROD] = 33, - [SUPER_ROD] = 30 - }; - - AlignFishingAnimationFrames(); - task->tFrameCounter++; - if (task->tFrameCounter >= reelTimeouts[task->tFishingRod]) - task->tStep = FISHING_GOT_AWAY; - else if (JOY_NEW(A_BUTTON)) - task->tStep = FISHING_CHECK_MORE_DOTS; - return FALSE; -} - -static bool32 Fishing_APressNoMinigame(struct Task *task) -{ - AlignFishingAnimationFrames(); - if (JOY_NEW(A_BUTTON)) - task->tStep = FISHING_MON_ON_HOOK; - return FALSE; -} - -// Determine if we're going to play the dot game again -static bool32 Fishing_CheckMoreDots(struct Task *task) -{ - const s16 moreDotsChance[][2] = - { - [OLD_ROD] = {0, 0}, - [GOOD_ROD] = {40, 10}, - [SUPER_ROD] = {70, 30} - }; - - AlignFishingAnimationFrames(); - task->tStep = FISHING_MON_ON_HOOK; - if (task->tRoundsPlayed < task->tMinRoundsRequired) - { - task->tStep = FISHING_INIT_DOTS; - } - else if (task->tRoundsPlayed < 2) - { - // probability of having to play another round - s16 probability = Random() % 100; - - if (moreDotsChance[task->tFishingRod][task->tRoundsPlayed] > probability) - task->tStep = FISHING_INIT_DOTS; - } - return FALSE; -} - -static bool32 Fishing_MonOnHook(struct Task *task) -{ - AlignFishingAnimationFrames(); - FillWindowPixelBuffer(0, PIXEL_FILL(1)); - AddTextPrinterParameterized2(0, FONT_NORMAL, gText_PokemonOnHook, 1, 0, TEXT_COLOR_DARK_GRAY, TEXT_COLOR_WHITE, TEXT_COLOR_LIGHT_GRAY); - task->tStep = FISHING_START_ENCOUNTER; - task->tFrameCounter = 0; - return FALSE; -} - -static bool32 Fishing_StartEncounter(struct Task *task) -{ - if (task->tFrameCounter == 0) - AlignFishingAnimationFrames(); - - RunTextPrinters(); - - if (task->tFrameCounter == 0) - { - if (!IsTextPrinterActive(0)) - { - struct ObjectEvent *playerObjEvent = &gObjectEvents[gPlayerAvatar.objectEventId]; - - ObjectEventSetGraphicsId(playerObjEvent, task->tPlayerGfxId); - ObjectEventTurn(playerObjEvent, playerObjEvent->movementDirection); - if (gPlayerAvatar.flags & PLAYER_AVATAR_FLAG_SURFING) - SetSurfBlob_PlayerOffset(gObjectEvents[gPlayerAvatar.objectEventId].fieldEffectSpriteId, FALSE, 0); - gSprites[gPlayerAvatar.spriteId].x2 = 0; - gSprites[gPlayerAvatar.spriteId].y2 = 0; - ClearDialogWindowAndFrame(0, TRUE); - task->tFrameCounter++; - return FALSE; - } - } - - if (task->tFrameCounter != 0) - { - gPlayerAvatar.preventStep = FALSE; - UnlockPlayerFieldControls(); - FishingWildEncounter(task->tFishingRod); - RecordFishingAttemptForTV(TRUE); - DestroyTask(FindTaskIdByFunc(Task_Fishing)); - } - return FALSE; -} - -static bool32 Fishing_NotEvenNibble(struct Task *task) -{ - gChainFishingDexNavStreak = 0; - AlignFishingAnimationFrames(); - StartSpriteAnim(&gSprites[gPlayerAvatar.spriteId], GetFishingNoCatchDirectionAnimNum(GetPlayerFacingDirection())); - FillWindowPixelBuffer(0, PIXEL_FILL(1)); - AddTextPrinterParameterized2(0, FONT_NORMAL, gText_NotEvenANibble, 1, 0, TEXT_COLOR_DARK_GRAY, TEXT_COLOR_WHITE, TEXT_COLOR_LIGHT_GRAY); - task->tStep = FISHING_NO_MON; - return TRUE; -} - -static bool32 Fishing_GotAway(struct Task *task) -{ - gChainFishingDexNavStreak = 0; - AlignFishingAnimationFrames(); - StartSpriteAnim(&gSprites[gPlayerAvatar.spriteId], GetFishingNoCatchDirectionAnimNum(GetPlayerFacingDirection())); - FillWindowPixelBuffer(0, PIXEL_FILL(1)); - AddTextPrinterParameterized2(0, FONT_NORMAL, gText_ItGotAway, 1, 0, TEXT_COLOR_DARK_GRAY, TEXT_COLOR_WHITE, TEXT_COLOR_LIGHT_GRAY); - task->tStep = FISHING_NO_MON; - return TRUE; -} - -static bool32 Fishing_NoMon(struct Task *task) -{ - AlignFishingAnimationFrames(); - task->tStep = FISHING_PUT_ROD_AWAY; - return FALSE; -} - -static bool32 Fishing_PutRodAway(struct Task *task) -{ - AlignFishingAnimationFrames(); - if (gSprites[gPlayerAvatar.spriteId].animEnded) - { - struct ObjectEvent *playerObjEvent = &gObjectEvents[gPlayerAvatar.objectEventId]; - - ObjectEventSetGraphicsId(playerObjEvent, task->tPlayerGfxId); - ObjectEventTurn(playerObjEvent, playerObjEvent->movementDirection); - if (gPlayerAvatar.flags & PLAYER_AVATAR_FLAG_SURFING) - SetSurfBlob_PlayerOffset(gObjectEvents[gPlayerAvatar.objectEventId].fieldEffectSpriteId, FALSE, 0); - gSprites[gPlayerAvatar.spriteId].x2 = 0; - gSprites[gPlayerAvatar.spriteId].y2 = 0; - task->tStep = FISHING_END_NO_MON; - } - return FALSE; -} - -static bool32 Fishing_EndNoMon(struct Task *task) -{ - RunTextPrinters(); - if (!IsTextPrinterActive(0)) - { - gPlayerAvatar.preventStep = FALSE; - UnlockPlayerFieldControls(); - UnfreezeObjectEvents(); - ClearDialogWindowAndFrame(0, TRUE); - RecordFishingAttemptForTV(FALSE); - DestroyTask(FindTaskIdByFunc(Task_Fishing)); - } - return FALSE; -} - -static bool32 DoesFishingMinigameAllowCancel(void) -{ - switch(I_FISHING_MINIGAME) - { - case GEN_1: - case GEN_2: - return FALSE; - case GEN_3: - default: - return TRUE; - } -} - -static bool32 Fishing_DoesFirstMonInPartyHaveSuctionCupsOrStickyHold(void) -{ - enum Ability ability; - - if (GetMonData(&gPlayerParty[0], MON_DATA_SANITY_IS_EGG)) - return FALSE; - - ability = GetMonAbility(&gPlayerParty[0]); - - return (ability == ABILITY_SUCTION_CUPS || ability == ABILITY_STICKY_HOLD); -} - -static bool32 Fishing_RollForBite(u32 rod, bool32 isStickyHold) -{ - return ((Random() % 100) > CalculateFishingBiteOdds(rod, isStickyHold)); -} - -static u32 CalculateFishingBiteOdds(u32 rod, bool32 isStickyHold) -{ - u32 odds; - - if (rod == OLD_ROD) - odds = FISHING_OLD_ROD_ODDS; - if (rod == GOOD_ROD) - odds = FISHING_GOOD_ROD_ODDS; - if (rod == SUPER_ROD) - odds = FISHING_SUPER_ROD_ODDS; - - odds -= CalculateFishingFollowerBoost(); - - if (isStickyHold) - { - if (I_FISHING_STICKY_BOOST >= GEN_4) - odds -= (100 - odds); - else - odds -= FISHING_STICKY_BOOST; - } - - odds -= CalculateFishingProximityBoost(odds); - - return odds; -} - -static u32 CalculateFishingFollowerBoost() -{ - u32 friendship; - struct Pokemon *mon = GetFirstLiveMon(); - - if (!I_FISHING_FOLLOWER_BOOST || !mon) - return 0; - - friendship = GetMonData(mon, MON_DATA_FRIENDSHIP); - if (friendship >= 250) - return 50; - else if (friendship >= 200) - return 40; - else if (friendship >= 150) - return 30; - else if (friendship >= 100) - return 20; - else - return 0; -} - -static u32 CalculateFishingProximityBoost(u32 odds) -{ - s16 player[AXIS_COUNT], bobber[AXIS_COUNT]; - s16 surroundingTile[CARDINAL_DIRECTION_COUNT][AXIS_COUNT] = {{0, 0}}; - bool32 isTileLand[CARDINAL_DIRECTION_COUNT] = {FALSE}; - u32 facingDirection, numQualifyingTile = 0; - struct ObjectEvent *objectEvent; - - if (!I_FISHING_PROXIMITY) - return 0; - - objectEvent = &gObjectEvents[gPlayerAvatar.objectEventId]; - - player[AXIS_X] = objectEvent->currentCoords.x; - player[AXIS_Y] = objectEvent->currentCoords.y; - bobber[AXIS_X] = objectEvent->currentCoords.x; - bobber[AXIS_Y] = objectEvent->currentCoords.y; - - facingDirection = GetPlayerFacingDirection(); - MoveCoords(facingDirection, &bobber[AXIS_X], &bobber[AXIS_Y]); - - GetCoordinatesAroundBobber(bobber, surroundingTile, facingDirection); - numQualifyingTile = CountQualifyingTiles(surroundingTile, player, facingDirection, objectEvent, isTileLand); - - numQualifyingTile += CountLandTiles(isTileLand); - - return (numQualifyingTile == 3) ? odds : (numQualifyingTile * FISHING_PROXIMITY_BOOST); -} - -static void GetCoordinatesAroundBobber(s16 bobber[], s16 surroundingTile[][AXIS_COUNT], u32 facingDirection) -{ - u32 direction; - - for (direction = DIR_SOUTH; direction < CARDINAL_DIRECTION_COUNT; direction++) - { - surroundingTile[direction][AXIS_X] = bobber[AXIS_X]; - surroundingTile[direction][AXIS_Y] = bobber[AXIS_Y]; - MoveCoords(direction, &surroundingTile[direction][AXIS_X], &surroundingTile[direction][AXIS_Y]); - } -} - -static u32 CountQualifyingTiles(s16 surroundingTile[][AXIS_COUNT], s16 player[], u8 facingDirection, struct ObjectEvent *objectEvent, bool32 isTileLand[]) -{ - u32 numQualifyingTile = 0; - s16 tile[AXIS_COUNT]; - u8 direction = DIR_SOUTH; - - for (direction = DIR_SOUTH; direction < CARDINAL_DIRECTION_COUNT; direction++) - { - tile[AXIS_X] = surroundingTile[direction][AXIS_X]; - tile[AXIS_Y] = surroundingTile[direction][AXIS_Y]; - - if (!CheckTileQualification(tile, player, facingDirection, objectEvent, isTileLand, direction)) - continue; - - numQualifyingTile++; - } - return numQualifyingTile; -} - -static bool32 CheckTileQualification(s16 tile[], s16 player[], u32 facingDirection, struct ObjectEvent* objectEvent, bool32 isTileLand[], u32 direction) -{ - u32 collison = GetCollisionAtCoords(objectEvent, tile[AXIS_X], tile[AXIS_Y], facingDirection); - - if (IsPlayerHere(tile[AXIS_X], tile[AXIS_Y], player[AXIS_X], player[AXIS_Y])) - return FALSE; - else if (IsMetatileBlocking(tile[AXIS_X], tile[AXIS_Y], collison)) - return TRUE; - else if (MetatileBehavior_IsSurfableFishableWater(MapGridGetMetatileBehaviorAt(tile[AXIS_X], tile[AXIS_Y]))) - return FALSE; - else if (IsMetatileLand(tile[AXIS_X], tile[AXIS_Y], collison)) - isTileLand[direction] = TRUE; - - return FALSE; -} - -static u32 CountLandTiles(bool32 isTileLand[]) -{ - u32 direction, numQualifyingTile = 0; - - for (direction = DIR_SOUTH; direction < CARDINAL_DIRECTION_COUNT; direction++) - if (isTileLand[direction]) - numQualifyingTile++; - - return (numQualifyingTile < 2) ? 0 : numQualifyingTile; -} - -static bool32 IsPlayerHere(s16 x, s16 y, s16 playerX, s16 playerY) -{ - return ((x == playerX) && (y == playerY)); -} - -static bool32 IsMetatileBlocking(s16 x, s16 y, u32 collison) -{ - switch(collison) - { - case COLLISION_NONE: - case COLLISION_STOP_SURFING: - case COLLISION_ELEVATION_MISMATCH: - return FALSE; - default: - return TRUE; - case COLLISION_OBJECT_EVENT: - return (gObjectEvents[GetObjectEventIdByXY(x,y)].inanimate); - } - return TRUE; -} - -static bool32 IsMetatileLand(s16 x, s16 y, u32 collison) -{ - switch(collison) - { - case COLLISION_NONE: - case COLLISION_STOP_SURFING: - case COLLISION_ELEVATION_MISMATCH: - return TRUE; - default: - return FALSE; - } -} - -#undef tStep -#undef tFrameCounter -#undef tFishingRod - -static void AlignFishingAnimationFrames(void) -{ - struct Sprite *playerSprite = &gSprites[gPlayerAvatar.spriteId]; - u8 animCmdIndex; - u8 animType; - - AnimateSprite(playerSprite); - playerSprite->x2 = 0; - playerSprite->y2 = 0; - animCmdIndex = playerSprite->animCmdIndex; - if (playerSprite->anims[playerSprite->animNum][animCmdIndex].type == -1) - { - animCmdIndex--; - } - else - { - playerSprite->animDelayCounter++; - if (playerSprite->anims[playerSprite->animNum][animCmdIndex].type == -1) - animCmdIndex--; - } - animType = playerSprite->anims[playerSprite->animNum][animCmdIndex].type; - if (animType == 1 || animType == 2 || animType == 3) - { - playerSprite->x2 = 8; - if (GetPlayerFacingDirection() == 3) - playerSprite->x2 = -8; - } - if (animType == 5) - playerSprite->y2 = -8; - if (animType == 10 || animType == 11) - playerSprite->y2 = 8; - if (gPlayerAvatar.flags & PLAYER_AVATAR_FLAG_SURFING) - SetSurfBlob_PlayerOffset(gObjectEvents[gPlayerAvatar.objectEventId].fieldEffectSpriteId, TRUE, playerSprite->y2); -} - void SetSpinStartFacingDir(u8 direction) { sSpinStartFacingDir = direction; diff --git a/src/fishing.c b/src/fishing.c new file mode 100644 index 0000000000..7730351abb --- /dev/null +++ b/src/fishing.c @@ -0,0 +1,650 @@ +#include "global.h" +#include "main.h" +#include "event_object_movement.h" +#include "fieldmap.h" +#include "field_effect_helpers.h" +#include "field_player_avatar.h" +#include "menu.h" +#include "metatile_behavior.h" +#include "random.h" +#include "script.h" +#include "strings.h" +#include "task.h" +#include "text.h" +#include "tv.h" +#include "wild_encounter.h" +#include "config/fishing.h" + +static void Task_Fishing(u8); +static bool32 Fishing_Init(struct Task *); +static bool32 Fishing_GetRodOut(struct Task *); +static bool32 Fishing_WaitBeforeDots(struct Task *); +static bool32 Fishing_InitDots(struct Task *); +static bool32 Fishing_ShowDots(struct Task *); +static bool32 Fishing_CheckForBite(struct Task *); +static bool32 Fishing_GotBite(struct Task *); +static bool32 Fishing_ChangeMinigame(struct Task *); +static bool32 Fishing_WaitForA(struct Task *); +static bool32 Fishing_APressNoMinigame(struct Task *); +static bool32 Fishing_CheckMoreDots(struct Task *); +static bool32 Fishing_MonOnHook(struct Task *); +static bool32 Fishing_StartEncounter(struct Task *); +static bool32 Fishing_NotEvenNibble(struct Task *); +static bool32 Fishing_GotAway(struct Task *); +static bool32 Fishing_NoMon(struct Task *); +static bool32 Fishing_PutRodAway(struct Task *); +static bool32 Fishing_EndNoMon(struct Task *); +static void AlignFishingAnimationFrames(void); +static bool32 DoesFishingMinigameAllowCancel(void); +static bool32 Fishing_DoesFirstMonInPartyHaveSuctionCupsOrStickyHold(void); +static bool32 Fishing_RollForBite(u32, bool32); +static u32 CalculateFishingBiteOdds(u32, bool32); +static u32 CalculateFishingFollowerBoost(void); +static u32 CalculateFishingProximityBoost(void); +static u32 CalculateFishingTimeOfDayBoost(void); + +#define FISHING_PROXIMITY_BOOST 20 //Active if config I_FISHING_PROXIMITY is TRUE +#define FISHING_TIME_OF_DAY_BOOST 20 //Active if config I_FISHING_TIME_OF_DAY_BOOST is TRUE +#define FISHING_GEN3_STICKY_CHANCE 85 //Active if config I_FISHING_STICKY_BOOST is set to GEN_3 or lower + +#if I_FISHING_BITE_ODDS >= GEN_4 + #define FISHING_OLD_ROD_ODDS 25 + #define FISHING_GOOD_ROD_ODDS 50 + #define FISHING_SUPER_ROD_ODDS 75 +#elif I_FISHING_BITE_ODDS >= GEN_3 + #define FISHING_OLD_ROD_ODDS 50 + #define FISHING_GOOD_ROD_ODDS 50 + #define FISHING_SUPER_ROD_ODDS 50 +#else + #define FISHING_OLD_ROD_ODDS 100 + #define FISHING_GOOD_ROD_ODDS 33 + #define FISHING_SUPER_ROD_ODDS 50 +#endif + +struct FriendshipHookChanceBoost +{ + u8 threshold; + u8 bonus; +}; + +//Needs to be defined in descending order and end with the 0 friendship boost +//Active if config I_FISHING_FOLLOWER_BOOST is TRUE +static const struct FriendshipHookChanceBoost sFriendshipHookChanceBoostArray[] = +{ + {.threshold = 250, .bonus = 50}, + {.threshold = 200, .bonus = 40}, + {.threshold = 150, .bonus = 30}, + {.threshold = 100, .bonus = 20}, + {.threshold = 0, .bonus = 0}, +}; + +#define FISHING_CHAIN_SHINY_STREAK_MAX 20 + +enum +{ + FISHING_INIT, + FISHING_GET_ROD_OUT, + FISHING_WAIT_BEFORE_DOTS, + FISHING_INIT_DOTS, + FISHING_SHOW_DOTS, + FISHING_CHECK_FOR_BITE, + FISHING_GOT_BITE, + FISHING_CHANGE_MINIGAME, + FISHING_WAIT_FOR_A, + FISHING_A_PRESS_NO_MINIGAME, + FISHING_CHECK_MORE_DOTS, + FISHING_MON_ON_HOOK, + FISHING_START_ENCOUNTER, + FISHING_NOT_EVEN_NIBBLE, + FISHING_GOT_AWAY, + FISHING_NO_MON, + FISHING_PUT_ROD_AWAY, + FISHING_END_NO_MON, +}; + +static bool32 (*const sFishingStateFuncs[])(struct Task *) = +{ + [FISHING_INIT] = Fishing_Init, + [FISHING_GET_ROD_OUT] = Fishing_GetRodOut, + [FISHING_WAIT_BEFORE_DOTS] = Fishing_WaitBeforeDots, + [FISHING_INIT_DOTS] = Fishing_InitDots, + [FISHING_SHOW_DOTS] = Fishing_ShowDots, + [FISHING_CHECK_FOR_BITE] = Fishing_CheckForBite, + [FISHING_GOT_BITE] = Fishing_GotBite, + [FISHING_CHANGE_MINIGAME] = Fishing_ChangeMinigame, + [FISHING_WAIT_FOR_A] = Fishing_WaitForA, + [FISHING_A_PRESS_NO_MINIGAME] = Fishing_APressNoMinigame, + [FISHING_CHECK_MORE_DOTS] = Fishing_CheckMoreDots, + [FISHING_MON_ON_HOOK] = Fishing_MonOnHook, + [FISHING_START_ENCOUNTER] = Fishing_StartEncounter, + [FISHING_NOT_EVEN_NIBBLE] = Fishing_NotEvenNibble, + [FISHING_GOT_AWAY] = Fishing_GotAway, + [FISHING_NO_MON] = Fishing_NoMon, + [FISHING_PUT_ROD_AWAY] = Fishing_PutRodAway, + [FISHING_END_NO_MON] = Fishing_EndNoMon, +}; + +#define tStep data[0] +#define tFrameCounter data[1] +#define tNumDots data[2] +#define tDotsRequired data[3] +#define tRoundsPlayed data[12] +#define tMinRoundsRequired data[13] +#define tPlayerGfxId data[14] +#define tFishingRod data[15] + +void StartFishing(u8 rod) +{ + u8 taskId = CreateTask(Task_Fishing, 0xFF); + + gTasks[taskId].tFishingRod = rod; + Task_Fishing(taskId); +} + +static void Task_Fishing(u8 taskId) +{ + while (sFishingStateFuncs[gTasks[taskId].tStep](&gTasks[taskId])) + ; +} + +static bool32 Fishing_Init(struct Task *task) +{ + LockPlayerFieldControls(); + gPlayerAvatar.preventStep = TRUE; + task->tStep = FISHING_GET_ROD_OUT; + return FALSE; +} + +static bool32 Fishing_GetRodOut(struct Task *task) +{ + struct ObjectEvent *playerObjEvent; + const s16 minRounds1[] = { + [OLD_ROD] = 1, + [GOOD_ROD] = 1, + [SUPER_ROD] = 1 + }; + const s16 minRounds2[] = { + [OLD_ROD] = 1, + [GOOD_ROD] = 3, + [SUPER_ROD] = 6 + }; + + task->tRoundsPlayed = 0; + task->tMinRoundsRequired = minRounds1[task->tFishingRod] + (Random() % minRounds2[task->tFishingRod]); + task->tPlayerGfxId = gObjectEvents[gPlayerAvatar.objectEventId].graphicsId; + playerObjEvent = &gObjectEvents[gPlayerAvatar.objectEventId]; + ObjectEventClearHeldMovementIfActive(playerObjEvent); + playerObjEvent->enableAnim = TRUE; + SetPlayerAvatarFishing(playerObjEvent->facingDirection); + task->tStep = FISHING_WAIT_BEFORE_DOTS; + return FALSE; +} + +static bool32 Fishing_WaitBeforeDots(struct Task *task) +{ + AlignFishingAnimationFrames(); + + // Wait one second + task->tFrameCounter++; + if (task->tFrameCounter >= 60) + task->tStep = FISHING_INIT_DOTS; + return FALSE; +} + +static bool32 Fishing_InitDots(struct Task *task) +{ + u32 randVal; + + LoadMessageBoxAndFrameGfx(0, TRUE); + task->tStep = FISHING_SHOW_DOTS; + task->tFrameCounter = 0; + task->tNumDots = 0; + randVal = Random(); + randVal %= 10; + task->tDotsRequired = randVal + 1; + if (task->tRoundsPlayed == 0) + task->tDotsRequired = randVal + 4; + if (task->tDotsRequired >= 10) + task->tDotsRequired = 10; + return TRUE; +} + +static bool32 Fishing_ShowDots(struct Task *task) +{ + const u8 dot[] = _("·"); + + AlignFishingAnimationFrames(); + task->tFrameCounter++; + if (JOY_NEW(A_BUTTON)) + { + if (!DoesFishingMinigameAllowCancel()) + return FALSE; + + task->tStep = FISHING_NOT_EVEN_NIBBLE; + if (task->tRoundsPlayed != 0) + task->tStep = FISHING_GOT_AWAY; + return TRUE; + } + else + { + if (task->tFrameCounter >= 20) + { + task->tFrameCounter = 0; + if (task->tNumDots >= task->tDotsRequired) + { + task->tStep = FISHING_CHECK_FOR_BITE; + if (task->tRoundsPlayed != 0) + task->tStep = FISHING_GOT_BITE; + task->tRoundsPlayed++; + } + else + { + AddTextPrinterParameterized(0, FONT_NORMAL, dot, task->tNumDots * 8, 1, 0, NULL); + task->tNumDots++; + } + } + return FALSE; + } +} + +static bool32 Fishing_CheckForBite(struct Task *task) +{ + bool32 bite, firstMonHasSuctionOrSticky; + + AlignFishingAnimationFrames(); + task->tStep = FISHING_GOT_BITE; + bite = FALSE; + + if (!DoesCurrentMapHaveFishingMons()) + { + task->tStep = FISHING_NOT_EVEN_NIBBLE; + return TRUE; + } + + firstMonHasSuctionOrSticky = Fishing_DoesFirstMonInPartyHaveSuctionCupsOrStickyHold(); + + if(firstMonHasSuctionOrSticky && I_FISHING_STICKY_BOOST < GEN_4) + bite = RandomPercentage(RNG_FISHING_GEN3_STICKY, FISHING_GEN3_STICKY_CHANCE); + + if (!bite) + bite = Fishing_RollForBite(task->tFishingRod, firstMonHasSuctionOrSticky); + + if (!bite) + task->tStep = FISHING_NOT_EVEN_NIBBLE; + + if (bite) + StartSpriteAnim(&gSprites[gPlayerAvatar.spriteId], GetFishingBiteDirectionAnimNum(GetPlayerFacingDirection())); + + return TRUE; +} + +static bool32 Fishing_GotBite(struct Task *task) +{ + AlignFishingAnimationFrames(); + AddTextPrinterParameterized(0, FONT_NORMAL, gText_OhABite, 0, 17, 0, NULL); + task->tStep = FISHING_CHANGE_MINIGAME; + task->tFrameCounter = 0; + return FALSE; +} + +static bool32 Fishing_ChangeMinigame(struct Task *task) +{ + switch (I_FISHING_MINIGAME) + { + case GEN_1: + case GEN_2: + task->tStep = FISHING_A_PRESS_NO_MINIGAME; + break; + case GEN_3: + default: + task->tStep = FISHING_WAIT_FOR_A; + break; + } + return TRUE; +} + +// We have a bite. Now, wait for the player to press A, or the timer to expire. +static bool32 Fishing_WaitForA(struct Task *task) +{ + const s16 reelTimeouts[3] = { + [OLD_ROD] = 36, + [GOOD_ROD] = 33, + [SUPER_ROD] = 30 + }; + + AlignFishingAnimationFrames(); + task->tFrameCounter++; + if (task->tFrameCounter >= reelTimeouts[task->tFishingRod]) + task->tStep = FISHING_GOT_AWAY; + else if (JOY_NEW(A_BUTTON)) + task->tStep = FISHING_CHECK_MORE_DOTS; + return FALSE; +} + +static bool32 Fishing_APressNoMinigame(struct Task *task) +{ + AlignFishingAnimationFrames(); + if (JOY_NEW(A_BUTTON)) + task->tStep = FISHING_MON_ON_HOOK; + return FALSE; +} + +// Determine if we're going to play the dot game again +static bool32 Fishing_CheckMoreDots(struct Task *task) +{ + const s16 moreDotsChance[][2] = + { + [OLD_ROD] = {0, 0}, + [GOOD_ROD] = {40, 10}, + [SUPER_ROD] = {70, 30} + }; + + AlignFishingAnimationFrames(); + task->tStep = FISHING_MON_ON_HOOK; + if (task->tRoundsPlayed < task->tMinRoundsRequired) + { + task->tStep = FISHING_INIT_DOTS; + } + else if (task->tRoundsPlayed < 2) + { + // probability of having to play another round + s16 probability = Random() % 100; + + if (moreDotsChance[task->tFishingRod][task->tRoundsPlayed] > probability) + task->tStep = FISHING_INIT_DOTS; + } + return FALSE; +} + +static bool32 Fishing_MonOnHook(struct Task *task) +{ + AlignFishingAnimationFrames(); + FillWindowPixelBuffer(0, PIXEL_FILL(1)); + AddTextPrinterParameterized2(0, FONT_NORMAL, gText_PokemonOnHook, 1, 0, TEXT_COLOR_DARK_GRAY, TEXT_COLOR_WHITE, TEXT_COLOR_LIGHT_GRAY); + task->tStep = FISHING_START_ENCOUNTER; + task->tFrameCounter = 0; + return FALSE; +} + +static bool32 Fishing_StartEncounter(struct Task *task) +{ + if (task->tFrameCounter == 0) + AlignFishingAnimationFrames(); + + RunTextPrinters(); + + if (task->tFrameCounter == 0) + { + if (!IsTextPrinterActive(0)) + { + struct ObjectEvent *playerObjEvent = &gObjectEvents[gPlayerAvatar.objectEventId]; + + ObjectEventSetGraphicsId(playerObjEvent, task->tPlayerGfxId); + ObjectEventTurn(playerObjEvent, playerObjEvent->movementDirection); + if (gPlayerAvatar.flags & PLAYER_AVATAR_FLAG_SURFING) + SetSurfBlob_PlayerOffset(gObjectEvents[gPlayerAvatar.objectEventId].fieldEffectSpriteId, FALSE, 0); + gSprites[gPlayerAvatar.spriteId].x2 = 0; + gSprites[gPlayerAvatar.spriteId].y2 = 0; + ClearDialogWindowAndFrame(0, TRUE); + task->tFrameCounter++; + return FALSE; + } + } + + if (task->tFrameCounter != 0) + { + gPlayerAvatar.preventStep = FALSE; + UnlockPlayerFieldControls(); + FishingWildEncounter(task->tFishingRod); + RecordFishingAttemptForTV(TRUE); + DestroyTask(FindTaskIdByFunc(Task_Fishing)); + } + return FALSE; +} + +static bool32 Fishing_NotEvenNibble(struct Task *task) +{ + gChainFishingDexNavStreak = 0; + AlignFishingAnimationFrames(); + StartSpriteAnim(&gSprites[gPlayerAvatar.spriteId], GetFishingNoCatchDirectionAnimNum(GetPlayerFacingDirection())); + FillWindowPixelBuffer(0, PIXEL_FILL(1)); + AddTextPrinterParameterized2(0, FONT_NORMAL, gText_NotEvenANibble, 1, 0, TEXT_COLOR_DARK_GRAY, TEXT_COLOR_WHITE, TEXT_COLOR_LIGHT_GRAY); + task->tStep = FISHING_NO_MON; + return TRUE; +} + +static bool32 Fishing_GotAway(struct Task *task) +{ + gChainFishingDexNavStreak = 0; + AlignFishingAnimationFrames(); + StartSpriteAnim(&gSprites[gPlayerAvatar.spriteId], GetFishingNoCatchDirectionAnimNum(GetPlayerFacingDirection())); + FillWindowPixelBuffer(0, PIXEL_FILL(1)); + AddTextPrinterParameterized2(0, FONT_NORMAL, gText_ItGotAway, 1, 0, TEXT_COLOR_DARK_GRAY, TEXT_COLOR_WHITE, TEXT_COLOR_LIGHT_GRAY); + task->tStep = FISHING_NO_MON; + return TRUE; +} + +static bool32 Fishing_NoMon(struct Task *task) +{ + AlignFishingAnimationFrames(); + task->tStep = FISHING_PUT_ROD_AWAY; + return FALSE; +} + +static bool32 Fishing_PutRodAway(struct Task *task) +{ + AlignFishingAnimationFrames(); + if (gSprites[gPlayerAvatar.spriteId].animEnded) + { + struct ObjectEvent *playerObjEvent = &gObjectEvents[gPlayerAvatar.objectEventId]; + + ObjectEventSetGraphicsId(playerObjEvent, task->tPlayerGfxId); + ObjectEventTurn(playerObjEvent, playerObjEvent->movementDirection); + if (gPlayerAvatar.flags & PLAYER_AVATAR_FLAG_SURFING) + SetSurfBlob_PlayerOffset(gObjectEvents[gPlayerAvatar.objectEventId].fieldEffectSpriteId, FALSE, 0); + gSprites[gPlayerAvatar.spriteId].x2 = 0; + gSprites[gPlayerAvatar.spriteId].y2 = 0; + task->tStep = FISHING_END_NO_MON; + } + return FALSE; +} + +static bool32 Fishing_EndNoMon(struct Task *task) +{ + RunTextPrinters(); + if (!IsTextPrinterActive(0)) + { + gPlayerAvatar.preventStep = FALSE; + UnlockPlayerFieldControls(); + UnfreezeObjectEvents(); + ClearDialogWindowAndFrame(0, TRUE); + RecordFishingAttemptForTV(FALSE); + DestroyTask(FindTaskIdByFunc(Task_Fishing)); + } + return FALSE; +} + +static bool32 DoesFishingMinigameAllowCancel(void) +{ + switch(I_FISHING_MINIGAME) + { + case GEN_1: + case GEN_2: + return FALSE; + case GEN_3: + default: + return TRUE; + } +} + +static bool32 Fishing_DoesFirstMonInPartyHaveSuctionCupsOrStickyHold(void) +{ + enum Ability ability; + + if (GetMonData(&gPlayerParty[0], MON_DATA_SANITY_IS_EGG)) + return FALSE; + + ability = GetMonAbility(&gPlayerParty[0]); + + return (ability == ABILITY_SUCTION_CUPS || ability == ABILITY_STICKY_HOLD); +} + +static bool32 Fishing_RollForBite(u32 rod, bool32 isStickyHold) +{ + return ((RandomUniform(RNG_FISHING_BITE, 1, 100)) <= CalculateFishingBiteOdds(rod, isStickyHold)); +} + +static u32 CalculateFishingBiteOdds(u32 rod, bool32 isStickyHold) +{ + u32 odds; + + if (rod == OLD_ROD) + odds = FISHING_OLD_ROD_ODDS; + if (rod == GOOD_ROD) + odds = FISHING_GOOD_ROD_ODDS; + if (rod == SUPER_ROD) + odds = FISHING_SUPER_ROD_ODDS; + + odds += CalculateFishingFollowerBoost(); + odds += CalculateFishingProximityBoost(); + odds += CalculateFishingTimeOfDayBoost(); + + if (isStickyHold && I_FISHING_STICKY_BOOST >= GEN_4) + odds *= 2; + + odds = min(100, odds); + DebugPrintf("Fishing odds: %d", odds); + return odds; +} + +static u32 CalculateFishingFollowerBoost() +{ + u32 friendship; + struct Pokemon *mon = GetFirstLiveMon(); + + if (!I_FISHING_FOLLOWER_BOOST || !mon) + return 0; + + friendship = GetMonData(mon, MON_DATA_FRIENDSHIP); + for (u32 i = 0;; i++) + { + if (friendship >= sFriendshipHookChanceBoostArray[i].threshold) + return sFriendshipHookChanceBoostArray[i].bonus; + } +} + +static u32 CalculateFishingProximityBoost() +{ + s16 bobber_x, bobber_y, tile_x, tile_y; + u32 direction, facingDirection, numQualifyingTile = 0; + struct ObjectEvent *objectEvent; + + if (!I_FISHING_PROXIMITY) + return 0; + + objectEvent = &gObjectEvents[gPlayerAvatar.objectEventId]; + + bobber_x = objectEvent->currentCoords.x; + bobber_y = objectEvent->currentCoords.y; + + facingDirection = GetPlayerFacingDirection(); + MoveCoords(facingDirection, &bobber_x, &bobber_y); + + numQualifyingTile = 0; + for (direction = DIR_SOUTH; direction < CARDINAL_DIRECTION_COUNT; direction++) + { + tile_x = bobber_x; + tile_y = bobber_y; + MoveCoords(direction, &tile_x, &tile_y); + if (tile_x == objectEvent->currentCoords.x && tile_y == objectEvent->currentCoords.y) + continue; + if (!MetatileBehavior_IsSurfableFishableWater(MapGridGetMetatileBehaviorAt(tile_x, tile_y))) + numQualifyingTile++; + else if (MapGridGetCollisionAt(tile_x, tile_y)) + numQualifyingTile++; + else if (GetMapBorderIdAt(tile_x, tile_y) == -1) + numQualifyingTile++; + } + + return (numQualifyingTile * FISHING_PROXIMITY_BOOST); +} + +static u32 CalculateFishingTimeOfDayBoost() +{ + if (!I_FISHING_TIME_OF_DAY_BOOST) + return 0; + + enum TimeOfDay timeOfDay = GetTimeOfDay(); + if (timeOfDay == TIME_MORNING || timeOfDay == TIME_EVENING) + return FISHING_TIME_OF_DAY_BOOST; + return 0; +} + +#undef tStep +#undef tFrameCounter +#undef tNumDots +#undef tDotsRequired +#undef tRoundsPlayed +#undef tMinRoundsRequired +#undef tPlayerGfxId +#undef tFishingRod + +static void AlignFishingAnimationFrames(void) +{ + struct Sprite *playerSprite = &gSprites[gPlayerAvatar.spriteId]; + u8 animCmdIndex; + u8 animType; + + AnimateSprite(playerSprite); + playerSprite->x2 = 0; + playerSprite->y2 = 0; + animCmdIndex = playerSprite->animCmdIndex; + if (playerSprite->anims[playerSprite->animNum][animCmdIndex].type == -1) + { + animCmdIndex--; + } + else + { + playerSprite->animDelayCounter++; + if (playerSprite->anims[playerSprite->animNum][animCmdIndex].type == -1) + animCmdIndex--; + } + animType = playerSprite->anims[playerSprite->animNum][animCmdIndex].type; + if (animType == 1 || animType == 2 || animType == 3) + { + playerSprite->x2 = 8; + if (GetPlayerFacingDirection() == 3) + playerSprite->x2 = -8; + } + if (animType == 5) + playerSprite->y2 = -8; + if (animType == 10 || animType == 11) + playerSprite->y2 = 8; + if (gPlayerAvatar.flags & PLAYER_AVATAR_FLAG_SURFING) + SetSurfBlob_PlayerOffset(gObjectEvents[gPlayerAvatar.objectEventId].fieldEffectSpriteId, TRUE, playerSprite->y2); +} + +void UpdateChainFishingStreak() +{ + if (!I_FISHING_CHAIN) + return; + + if (gChainFishingDexNavStreak == MAX_u8) + return; + + gChainFishingDexNavStreak++; +} + +u32 CalculateChainFishingShinyRolls(void) +{ + if (!I_FISHING_CHAIN || !gIsFishingEncounter) + return 0; + u32 a = 2 * min(gChainFishingDexNavStreak, FISHING_CHAIN_SHINY_STREAK_MAX); + DebugPrintf("Total Shiny Rolls %d", a); + return a; +} + +bool32 ShouldUseFishingEnvironmentInBattle() +{ + return (I_FISHING_ENVIRONMENT >= GEN_4 && gIsFishingEncounter); +} \ No newline at end of file diff --git a/src/item_use.c b/src/item_use.c index 3e0e0dbeb6..1b91dd76da 100644 --- a/src/item_use.c +++ b/src/item_use.c @@ -18,6 +18,7 @@ #include "field_player_avatar.h" #include "field_screen_effect.h" #include "field_weather.h" +#include "fishing.h" #include "fldeff.h" #include "follower_npc.h" #include "item.h" diff --git a/src/pokemon.c b/src/pokemon.c index 25f7cd5ed8..32fe01812b 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -19,6 +19,7 @@ #include "field_player_avatar.h" #include "field_specials.h" #include "field_weather.h" +#include "fishing.h" #include "follower_npc.h" #include "graphics.h" #include "item.h" @@ -66,7 +67,6 @@ #include "constants/trainers.h" #include "constants/union_room.h" #include "constants/weather.h" -#include "wild_encounter.h" #define FRIENDSHIP_EVO_THRESHOLD ((P_FRIENDSHIP_EVO_THRESHOLD >= GEN_8) ? 160 : 220) @@ -1090,8 +1090,7 @@ void CreateBoxMon(struct BoxPokemon *boxMon, u16 species, u8 level, u8 fixedIV, totalRerolls += I_SHINY_CHARM_ADDITIONAL_ROLLS; if (LURE_STEP_COUNT != 0) totalRerolls += 1; - if (I_FISHING_CHAIN && gIsFishingEncounter) - totalRerolls += CalculateChainFishingShinyRolls(); + totalRerolls += CalculateChainFishingShinyRolls(); if (gDexNavSpecies) totalRerolls += CalculateDexNavShinyRolls(); diff --git a/src/wild_encounter.c b/src/wild_encounter.c index 9161797d25..f6bf7a7598 100644 --- a/src/wild_encounter.c +++ b/src/wild_encounter.c @@ -3,6 +3,7 @@ #include "pokemon.h" #include "metatile_behavior.h" #include "fieldmap.h" +#include "fishing.h" #include "follower_npc.h" #include "random.h" #include "field_player_avatar.h" @@ -43,7 +44,6 @@ extern const u8 EventScript_SprayWoreOff[]; static u16 FeebasRandom(void); static void FeebasSeedRng(u16 seed); -static void UpdateChainFishingStreak(); static bool8 IsWildLevelAllowedByRepel(u8 level); static void ApplyFluteEncounterRateMod(u32 *encRate); static void ApplyCleanseTagEncounterRateMod(u32 *encRate); @@ -964,22 +964,6 @@ bool8 DoesCurrentMapHaveFishingMons(void) return FALSE; } -u32 CalculateChainFishingShinyRolls(void) -{ - return (2 * min(gChainFishingDexNavStreak, FISHING_CHAIN_SHINY_STREAK_MAX)); -} - -static void UpdateChainFishingStreak() -{ - if (!I_FISHING_CHAIN) - return; - - if (gChainFishingDexNavStreak >= FISHING_CHAIN_LENGTH_MAX) - return; - - gChainFishingDexNavStreak++; -} - void FishingWildEncounter(u8 rod) { u16 species;