Update fishing odds to match official games (#7574)

This commit is contained in:
FosterProgramming 2025-10-04 18:05:28 +02:00 committed by GitHub
parent 730352683c
commit a595696086
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 682 additions and 708 deletions

13
include/config/fishing.h Normal file
View File

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

View File

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

View File

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

View File

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

9
include/fishing.h Normal file
View File

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

View File

@ -219,6 +219,8 @@ enum RandomTag
RNG_BALLTHROW_SHAKE,
RNG_PRESENT,
RNG_MAGNITUDE,
RNG_FISHING_BITE,
RNG_FISHING_GEN3_STICKY,
};
#define RandomWeighted(tag, ...) \

View File

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

View File

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

650
src/fishing.c Normal file
View File

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

View File

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

View File

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

View File

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