summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDiegoisawesome <Diegoisawesome@users.noreply.github.com>2017-11-20 10:10:27 -0600
committerGitHub <noreply@github.com>2017-11-20 10:10:27 -0600
commit1e4f12e6fa9e3b22d4f1a60ca69313b5dec0ca38 (patch)
tree4b0e58280d62a4babd7e35de5f796e960e069d81 /src
parent5dc8beb034e70b4e1eba61cca4009d5e06356605 (diff)
parent4e25ef10f2883ef228b7d6c26162d2b00dc3c291 (diff)
Merge pull request #115 from DizzyEggg/decompile_wild_encounter
Decompile wild encounter
Diffstat (limited to 'src')
-rw-r--r--src/tv.c2
-rw-r--r--src/wild_encounter.c933
2 files changed, 934 insertions, 1 deletions
diff --git a/src/tv.c b/src/tv.c
index c1d648652..1d978b093 100644
--- a/src/tv.c
+++ b/src/tv.c
@@ -1883,7 +1883,7 @@ void PutFishingAdviceShowOnTheAir(void)
}
}
-void sub_80EDA3C(u16 species)
+void SetPokemonAnglerSpecies(u16 species)
{
sPokemonAnglerSpecies = species;
}
diff --git a/src/wild_encounter.c b/src/wild_encounter.c
new file mode 100644
index 000000000..ce1e02e5e
--- /dev/null
+++ b/src/wild_encounter.c
@@ -0,0 +1,933 @@
+#include "global.h"
+#include "wild_encounter.h"
+#include "pokemon.h"
+#include "species.h"
+#include "metatile_behavior.h"
+#include "fieldmap.h"
+#include "rng.h"
+#include "map_constants.h"
+#include "field_player_avatar.h"
+#include "abilities.h"
+#include "event_data.h"
+#include "safari_zone.h"
+#include "pokeblock.h"
+#include "battle_setup.h"
+#include "roamer.h"
+#include "game_stat.h"
+#include "tv.h"
+#include "link.h"
+#include "script.h"
+#include "items.h"
+
+extern const u8 EventScript_RepelWoreOff[];
+
+#define NUM_FEEBAS_SPOTS 6
+
+extern const u16 gRoute119WaterTileData[];
+extern const struct WildPokemonHeader gBattlePikeWildMonHeaders[];
+extern const struct WildPokemonHeader gBattlePyramidWildMonHeaders[];
+extern const struct WildPokemon gWildFeebasRoute119Data;
+
+extern u8 GetBattlePikeWildMonHeaderId(void);
+extern bool32 TryGenerateBattlePikeWildMon(bool8 checkKeenEyeIntimidate);
+extern void GenerateBattlePyramidWildMon(void);
+extern bool8 InBattlePike(void);
+extern bool8 InBattlePyramid(void);
+
+// this file's functions
+static u16 FeebasRandom(void);
+static void FeebasSeedRng(u16 seed);
+static bool8 IsWildLevelAllowedByRepel(u8 level);
+static void ApplyFluteEncounterRateMod(u32 *encRate);
+static void ApplyCleanseTagEncounterRateMod(u32 *encRate);
+static bool8 TryGetAbilityInfluencedWildMonIndex(const struct WildPokemon *wildMon, u8 type, u8 ability, u8 *monIndex);
+static bool8 IsAbilityAllowingEncounter(u8 level);
+
+EWRAM_DATA u8 sWildEncountersDisabled = 0;
+EWRAM_DATA u32 sFeebasRngValue = 0;
+
+void DisableWildEncounters(bool8 disabled)
+{
+ sWildEncountersDisabled = disabled;
+}
+
+static u16 GetRoute119WaterTileNum(s16 x, s16 y, u8 section)
+{
+ u16 xCur;
+ u16 yCur;
+ u16 yMin = gRoute119WaterTileData[section * 3 + 0];
+ u16 yMax = gRoute119WaterTileData[section * 3 + 1];
+ u16 tileNum = gRoute119WaterTileData[section * 3 + 2];
+
+ for (yCur = yMin; yCur <= yMax; yCur++)
+ {
+ for (xCur = 0; xCur < gMapHeader.mapData->width; xCur++)
+ {
+ u8 tileBehaviorId = MapGridGetMetatileBehaviorAt(xCur + 7, yCur + 7);
+ if (MetatileBehavior_IsSurfableAndNotWaterfall(tileBehaviorId) == TRUE)
+ {
+ tileNum++;
+ if (x == xCur && y == yCur)
+ return tileNum;
+ }
+ }
+ }
+ return tileNum + 1;
+}
+
+static bool8 CheckFeebas(void)
+{
+ u8 i;
+ u16 feebasSpots[NUM_FEEBAS_SPOTS];
+ s16 x;
+ s16 y;
+ u8 route119Section = 0;
+ u16 waterTileNum;
+
+ if (gSaveBlock1Ptr->location.mapGroup == MAP_GROUP_ROUTE119
+ && gSaveBlock1Ptr->location.mapNum == MAP_ID_ROUTE119)
+ {
+ GetXYCoordsOneStepInFrontOfPlayer(&x, &y);
+ x -= 7;
+ y -= 7;
+
+#ifdef NONMATCHING
+ if (y >= gRoute119WaterTileData[3 * 1 + 0] && y <= gRoute119WaterTileData[3 * 1 + 1])
+ route119Section = 1;
+ if (y >= gRoute119WaterTileData[3 * 2 + 0] && y <= gRoute119WaterTileData[3 * 2 + 1])
+ route119Section = 2;
+#else
+ {
+ register const u16 *arr asm("r0");
+ if (y >= (arr = gRoute119WaterTileData)[3 * 1 + 0] && y <= arr[3 * 1 + 1])
+ route119Section = 1;
+ if (y >= arr[3 * 2 + 0] && y <= arr[3 * 2 + 1])
+ route119Section = 2;
+ }
+#endif
+
+ if (Random() % 100 > 49) // 50% chance of encountering Feebas
+ return FALSE;
+
+ FeebasSeedRng(gSaveBlock1Ptr->easyChatPairs[0].unk2);
+ for (i = 0; i != NUM_FEEBAS_SPOTS;)
+ {
+ feebasSpots[i] = FeebasRandom() % 447;
+ if (feebasSpots[i] == 0)
+ feebasSpots[i] = 447;
+ if (feebasSpots[i] < 1 || feebasSpots[i] >= 4)
+ i++;
+ }
+ waterTileNum = GetRoute119WaterTileNum(x, y, route119Section);
+ for (i = 0; i < NUM_FEEBAS_SPOTS; i++)
+ {
+ if (waterTileNum == feebasSpots[i])
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+// The number 1103515245 comes from the example implementation of rand and srand
+// in the ISO C standard.
+
+static u16 FeebasRandom(void)
+{
+ sFeebasRngValue = (1103515245 * sFeebasRngValue) + 12345;
+ return sFeebasRngValue >> 16;
+}
+
+static void FeebasSeedRng(u16 seed)
+{
+ sFeebasRngValue = seed;
+}
+
+static u8 ChooseWildMonIndex_Land(void)
+{
+ u8 rand = Random() % 100;
+
+ if (rand < 20) // 20% chance
+ return 0;
+ else if (rand >= 20 && rand < 40) // 20% chance
+ return 1;
+ else if (rand >= 40 && rand < 50) // 10% chance
+ return 2;
+ else if (rand >= 50 && rand < 60) // 10% chance
+ return 3;
+ else if (rand >= 60 && rand < 70) // 10% chance
+ return 4;
+ else if (rand >= 70 && rand < 80) // 10% chance
+ return 5;
+ else if (rand >= 80 && rand < 85) // 5% chance
+ return 6;
+ else if (rand >= 85 && rand < 90) // 5% chance
+ return 7;
+ else if (rand >= 90 && rand < 94) // 4% chance
+ return 8;
+ else if (rand >= 94 && rand < 98) // 4% chance
+ return 9;
+ else if (rand == 98) // 1% chance
+ return 10;
+ else // 1% chance
+ return 11;
+}
+
+static u8 ChooseWildMonIndex_WaterRock(void)
+{
+ u8 rand = Random() % 100;
+
+ if (rand < 60) // 60% chance
+ return 0;
+ else if (rand >= 60 && rand < 90) // 30% chance
+ return 1;
+ else if (rand >= 90 && rand < 95) // 5% chance
+ return 2;
+ else if (rand >= 95 && rand < 99) // 4% chance
+ return 3;
+ else // 1% chance
+ return 4;
+}
+
+enum
+{
+ OLD_ROD,
+ GOOD_ROD,
+ SUPER_ROD
+};
+
+static u8 ChooseWildMonIndex_Fishing(u8 rod)
+{
+ u8 wildMonIndex = 0;
+ u8 rand = Random() % 100;
+
+ switch (rod)
+ {
+ case OLD_ROD:
+ if (rand < 70) // 70% chance
+ wildMonIndex = 0;
+ else // 30% chance
+ wildMonIndex = 1;
+ break;
+ case GOOD_ROD:
+ if (rand < 60) // 60% chance
+ wildMonIndex = 2;
+ if (rand >= 60 && rand < 80) // 20% chance
+ wildMonIndex = 3;
+ if (rand >= 80 && rand < 100) // 20% chance
+ wildMonIndex = 4;
+ break;
+ case SUPER_ROD:
+ if (rand < 40) // 40% chance
+ wildMonIndex = 5;
+ if (rand >= 40 && rand < 80) // 40% chance
+ wildMonIndex = 6;
+ if (rand >= 80 && rand < 95) // 15% chance
+ wildMonIndex = 7;
+ if (rand >= 95 && rand < 99) // 4% chance
+ wildMonIndex = 8;
+ if (rand == 99) // 1% chance
+ wildMonIndex = 9;
+ break;
+ }
+ return wildMonIndex;
+}
+
+static u8 ChooseWildMonLevel(const struct WildPokemon *wildPokemon)
+{
+ u8 min;
+ u8 max;
+ u8 range;
+ u8 rand;
+
+ // Make sure minimum level is less than maximum level
+ if (wildPokemon->maxLevel >= wildPokemon->minLevel)
+ {
+ min = wildPokemon->minLevel;
+ max = wildPokemon->maxLevel;
+ }
+ else
+ {
+ min = wildPokemon->maxLevel;
+ max = wildPokemon->minLevel;
+ }
+ range = max - min + 1;
+ rand = Random() % range;
+
+ // check ability for max level mon
+ if (!GetMonData(&gPlayerParty[0], MON_DATA_SANITY_BIT3))
+ {
+ u8 ability = GetMonAbility(&gPlayerParty[0]);
+ if (ability == ABILITY_HUSTLE || ability == ABILITY_VITAL_SPIRIT || ability == ABILITY_PRESSURE)
+ {
+ if (Random() % 2 == 0)
+ return max;
+
+ if (rand != 0)
+ rand--;
+ }
+ }
+
+ return min + rand;
+}
+
+static u16 GetCurrentMapWildMonHeaderId(void)
+{
+ u16 i;
+
+ for (i = 0; ; i++)
+ {
+ const struct WildPokemonHeader *wildHeader = &gWildMonHeaders[i];
+ if (wildHeader->mapGroup == 0xFF)
+ break;
+
+ if (gWildMonHeaders[i].mapGroup == gSaveBlock1Ptr->location.mapGroup &&
+ gWildMonHeaders[i].mapNum == gSaveBlock1Ptr->location.mapNum)
+ {
+ if (gSaveBlock1Ptr->location.mapGroup == MAP_GROUP_ALTERING_CAVE &&
+ gSaveBlock1Ptr->location.mapNum == MAP_ID_ALTERING_CAVE)
+ {
+ u16 alteringCaveId = VarGet(VAR_ALTERING_CAVE_WILD_SET);
+ if (alteringCaveId > 8)
+ alteringCaveId = 0;
+
+ i += alteringCaveId;
+ }
+
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+static u8 PickWildMonNature(void)
+{
+ u8 i;
+ u8 j;
+ struct Pokeblock *safariPokeblock;
+ u8 natures[25];
+
+ if (GetSafariZoneFlag() == TRUE && Random() % 100 < 80)
+ {
+ safariPokeblock = SafariZoneGetActivePokeblock();
+ if (safariPokeblock != NULL)
+ {
+ for (i = 0; i < 25; i++)
+ natures[i] = i;
+ for (i = 0; i < 24; i++)
+ {
+ for (j = i + 1; j < 25; j++)
+ {
+ if (Random() & 1)
+ {
+ u8 temp = natures[i];
+
+ natures[i] = natures[j];
+ natures[j] = temp;
+ }
+ }
+ }
+ for (i = 0; i < 25; i++)
+ {
+ if (PokeblockGetGain(natures[i], safariPokeblock) > 0)
+ return natures[i];
+ }
+ }
+ }
+ // check synchronize for a pokemon with the same ability
+ if (!GetMonData(&gPlayerParty[0], MON_DATA_SANITY_BIT3)
+ && GetMonAbility(&gPlayerParty[0]) == ABILITY_SYNCHRONIZE
+ && Random() % 2 == 0)
+ {
+ return GetMonData(&gPlayerParty[0], MON_DATA_PERSONALITY) % 25;
+ }
+
+ // random nature
+ return Random() % 25;
+}
+
+static void CreateWildMon(u16 species, u8 level)
+{
+ bool32 checkCuteCharm;
+
+ ZeroEnemyPartyMons();
+ checkCuteCharm = TRUE;
+
+ switch (gBaseStats[species].genderRatio)
+ {
+ case MON_MALE:
+ case MON_FEMALE:
+ case MON_GENDERLESS:
+ checkCuteCharm = FALSE;
+ break;
+ }
+
+ if (checkCuteCharm
+ && !GetMonData(&gPlayerParty[0], MON_DATA_SANITY_BIT3)
+ && GetMonAbility(&gPlayerParty[0]) == ABILITY_CUTE_CHARM
+ && Random() % 3 != 0)
+ {
+ u16 leadingMonSpecies = GetMonData(&gPlayerParty[0], MON_DATA_SPECIES);
+ u32 leadingMonPersonality = GetMonData(&gPlayerParty[0], MON_DATA_PERSONALITY);
+ u8 gender = GetGenderFromSpeciesAndPersonality(leadingMonSpecies, leadingMonPersonality);
+
+ // misses mon is genderless check, although no genderless mon can have cute charm as ability
+ if (gender == MON_FEMALE)
+ gender = MON_MALE;
+ else
+ gender = MON_FEMALE;
+
+ CreateMonWithGenderNatureLetter(&gEnemyParty[0], species, level, 32, gender, PickWildMonNature(), 0);
+ return;
+ }
+
+ CreateMonWithNature(&gEnemyParty[0], species, level, 32, PickWildMonNature());
+}
+
+enum
+{
+ WILD_AREA_LAND,
+ WILD_AREA_WATER,
+ WILD_AREA_ROCKS,
+ WILD_AREA_FISHING,
+};
+
+#define WILD_CHECK_REPEL 0x1
+#define WILD_CHECK_KEEN_EYE 0x2
+
+static bool8 TryGenerateWildMon(const struct WildPokemonInfo *wildMonInfo, u8 area, u8 flags)
+{
+ u8 wildMonIndex = 0;
+ u8 level;
+
+ switch (area)
+ {
+ case WILD_AREA_LAND:
+ if (TryGetAbilityInfluencedWildMonIndex(wildMonInfo->wildPokemon, TYPE_STEEL, ABILITY_MAGNET_PULL, &wildMonIndex))
+ break;
+ if (TryGetAbilityInfluencedWildMonIndex(wildMonInfo->wildPokemon, TYPE_ELECTRIC, ABILITY_STATIC, &wildMonIndex))
+ break;
+
+ wildMonIndex = ChooseWildMonIndex_Land();
+ break;
+ case WILD_AREA_WATER:
+ if (TryGetAbilityInfluencedWildMonIndex(wildMonInfo->wildPokemon, TYPE_ELECTRIC, ABILITY_STATIC, &wildMonIndex))
+ break;
+
+ wildMonIndex = ChooseWildMonIndex_WaterRock();
+ break;
+ case WILD_AREA_ROCKS:
+ wildMonIndex = ChooseWildMonIndex_WaterRock();
+ break;
+ }
+
+ level = ChooseWildMonLevel(&wildMonInfo->wildPokemon[wildMonIndex]);
+ if (flags & WILD_CHECK_REPEL && !IsWildLevelAllowedByRepel(level))
+ return FALSE;
+ if (gMapHeader.mapDataId != 0x166 && flags & WILD_CHECK_KEEN_EYE && !IsAbilityAllowingEncounter(level))
+ return FALSE;
+
+ CreateWildMon(wildMonInfo->wildPokemon[wildMonIndex].species, level);
+ return TRUE;
+}
+
+static u16 GenerateFishingWildMon(const struct WildPokemonInfo *wildMonInfo, u8 rod)
+{
+ u8 wildMonIndex = ChooseWildMonIndex_Fishing(rod);
+ u8 level = ChooseWildMonLevel(&wildMonInfo->wildPokemon[wildMonIndex]);
+
+ CreateWildMon(wildMonInfo->wildPokemon[wildMonIndex].species, level);
+ return wildMonInfo->wildPokemon[wildMonIndex].species;
+}
+
+static bool8 SetUpMassOutbreakEncounter(u8 flags)
+{
+ u16 i;
+
+ if (flags & WILD_CHECK_REPEL && !IsWildLevelAllowedByRepel(gSaveBlock1Ptr->outbreakPokemonLevel))
+ return FALSE;
+
+ CreateWildMon(gSaveBlock1Ptr->outbreakPokemonSpecies, gSaveBlock1Ptr->outbreakPokemonLevel);
+ for (i = 0; i < 4; i++)
+ SetMonMoveSlot(&gEnemyParty[0], gSaveBlock1Ptr->outbreakPokemonMoves[i], i);
+
+ return TRUE;
+}
+
+static bool8 DoMassOutbreakEncounterTest(void)
+{
+ if (gSaveBlock1Ptr->outbreakPokemonSpecies != 0
+ && gSaveBlock1Ptr->location.mapNum == gSaveBlock1Ptr->outbreakLocationMapNum
+ && gSaveBlock1Ptr->location.mapGroup == gSaveBlock1Ptr->outbreakLocationMapGroup)
+ {
+ if (Random() % 100 < gSaveBlock1Ptr->outbreakPokemonProbability)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static bool8 DoWildEncounterRateDiceRoll(u16 encounterRate)
+{
+ if (Random() % 2880 < encounterRate)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static bool8 DoWildEncounterRateTest(u32 encounterRate, bool8 ignoreAbility)
+{
+ encounterRate *= 16;
+ if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_MACH_BIKE | PLAYER_AVATAR_FLAG_ACRO_BIKE))
+ encounterRate = encounterRate * 80 / 100;
+ ApplyFluteEncounterRateMod(&encounterRate);
+ ApplyCleanseTagEncounterRateMod(&encounterRate);
+ if (!ignoreAbility && !GetMonData(&gPlayerParty[0], MON_DATA_SANITY_BIT3))
+ {
+ u32 ability = GetMonAbility(&gPlayerParty[0]);
+
+ if (ability == ABILITY_STENCH && gMapHeader.mapDataId == 0x169)
+ encounterRate = encounterRate * 3 / 4;
+ else if (ability == ABILITY_STENCH)
+ encounterRate /= 2;
+ else if (ability == ABILITY_ILLUMINATE)
+ encounterRate *= 2;
+ else if (ability == ABILITY_WHITE_SMOKE)
+ encounterRate /= 2;
+ else if (ability == ABILITY_ARENA_TRAP)
+ encounterRate *= 2;
+ else if (ability == ABILITY_SAND_VEIL && gSaveBlock1Ptr->weather == 8)
+ encounterRate /= 2;
+ }
+ if (encounterRate > 2880)
+ encounterRate = 2880;
+ return DoWildEncounterRateDiceRoll(encounterRate);
+}
+
+static bool8 DoGlobalWildEncounterDiceRoll(void)
+{
+ if (Random() % 100 >= 60)
+ return FALSE;
+ else
+ return TRUE;
+}
+
+static bool8 AreLegendariesInSootopolisPreventingEncounters(void)
+{
+ if (gSaveBlock1Ptr->location.mapGroup != MAP_GROUP_SOOTOPOLIS_CITY
+ || gSaveBlock1Ptr->location.mapNum != MAP_ID_SOOTOPOLIS_CITY)
+ {
+ return FALSE;
+ }
+
+ return FlagGet(FLAG_LEGENDARIES_IN_SOOTOPOLIS);
+}
+
+bool8 StandardWildEncounter(u16 currMetaTileBehavior, u16 previousMetaTileBehavior)
+{
+ u16 headerId;
+ struct Roamer *roamer;
+
+ if (sWildEncountersDisabled == TRUE)
+ return FALSE;
+
+ headerId = GetCurrentMapWildMonHeaderId();
+ if (headerId == 0xFFFF) // invalid
+ {
+ if (gMapHeader.mapDataId == 0x166)
+ {
+ headerId = GetBattlePikeWildMonHeaderId();
+ if (previousMetaTileBehavior != currMetaTileBehavior && !DoGlobalWildEncounterDiceRoll())
+ return FALSE;
+ else if (DoWildEncounterRateTest(gBattlePikeWildMonHeaders[headerId].landMonsInfo->encounterRate, FALSE) != TRUE)
+ return FALSE;
+ else if (TryGenerateWildMon(gBattlePikeWildMonHeaders[headerId].landMonsInfo, WILD_AREA_LAND, WILD_CHECK_KEEN_EYE) != TRUE)
+ return FALSE;
+ else if (!TryGenerateBattlePikeWildMon(TRUE))
+ return FALSE;
+
+ BattleSetup_StartBattlePikeWildBattle();
+ return TRUE;
+ }
+ if (gMapHeader.mapDataId == 0x169)
+ {
+ headerId = gSaveBlock2Ptr->battlePyramidWildHeaderId;
+ if (previousMetaTileBehavior != currMetaTileBehavior && !DoGlobalWildEncounterDiceRoll())
+ return FALSE;
+ else if (DoWildEncounterRateTest(gBattlePyramidWildMonHeaders[headerId].landMonsInfo->encounterRate, FALSE) != TRUE)
+ return FALSE;
+ else if (TryGenerateWildMon(gBattlePyramidWildMonHeaders[headerId].landMonsInfo, WILD_AREA_LAND, WILD_CHECK_KEEN_EYE) != TRUE)
+ return FALSE;
+
+ GenerateBattlePyramidWildMon();
+ BattleSetup_StartWildBattle();
+ return TRUE;
+ }
+ }
+ else
+ {
+ if (MetatileBehavior_IsLandWildEncounter(currMetaTileBehavior) == TRUE)
+ {
+ if (gWildMonHeaders[headerId].landMonsInfo == NULL)
+ return FALSE;
+ else if (previousMetaTileBehavior != currMetaTileBehavior && !DoGlobalWildEncounterDiceRoll())
+ return FALSE;
+ else if (DoWildEncounterRateTest(gWildMonHeaders[headerId].landMonsInfo->encounterRate, FALSE) != TRUE)
+ return FALSE;
+
+ if (TryStartRoamerEncounter() == TRUE)
+ {
+ roamer = &gSaveBlock1Ptr->roamer;
+ if (!IsWildLevelAllowedByRepel(roamer->level))
+ return FALSE;
+
+ BattleSetup_StartRoamerBattle();
+ return TRUE;
+ }
+ else
+ {
+ if (DoMassOutbreakEncounterTest() == TRUE && SetUpMassOutbreakEncounter(WILD_CHECK_REPEL | WILD_CHECK_KEEN_EYE) == TRUE)
+ {
+ BattleSetup_StartWildBattle();
+ return TRUE;
+ }
+
+ // try a regular wild land encounter
+ if (TryGenerateWildMon(gWildMonHeaders[headerId].landMonsInfo, WILD_AREA_LAND, WILD_CHECK_REPEL | WILD_CHECK_KEEN_EYE) == TRUE)
+ {
+ BattleSetup_StartWildBattle();
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+ }
+ else if (MetatileBehavior_IsWaterWildEncounter(currMetaTileBehavior) == TRUE
+ || (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_SURFING) && MetatileBehavior_IsBridge(currMetaTileBehavior) == TRUE))
+ {
+ if (AreLegendariesInSootopolisPreventingEncounters() == TRUE)
+ return FALSE;
+ else if (gWildMonHeaders[headerId].waterMonsInfo == NULL)
+ return FALSE;
+ else if (previousMetaTileBehavior != currMetaTileBehavior && !DoGlobalWildEncounterDiceRoll())
+ return FALSE;
+ else if (DoWildEncounterRateTest(gWildMonHeaders[headerId].waterMonsInfo->encounterRate, FALSE) != TRUE)
+ return FALSE;
+
+ if (TryStartRoamerEncounter() == TRUE)
+ {
+ roamer = &gSaveBlock1Ptr->roamer;
+ if (!IsWildLevelAllowedByRepel(roamer->level))
+ return FALSE;
+
+ BattleSetup_StartRoamerBattle();
+ return TRUE;
+ }
+ else // try a regular surfing encounter
+ {
+ if (TryGenerateWildMon(gWildMonHeaders[headerId].waterMonsInfo, WILD_AREA_WATER, WILD_CHECK_REPEL | WILD_CHECK_KEEN_EYE) == TRUE)
+ {
+ BattleSetup_StartWildBattle();
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+void RockSmashWildEncounter(void)
+{
+ u16 headerId = GetCurrentMapWildMonHeaderId();
+
+ if (headerId != 0xFFFF)
+ {
+ const struct WildPokemonInfo *wildPokemonInfo = gWildMonHeaders[headerId].rockSmashMonsInfo;
+
+ if (wildPokemonInfo == NULL)
+ {
+ gSpecialVar_Result = FALSE;
+ }
+ else if (DoWildEncounterRateTest(wildPokemonInfo->encounterRate, 1) == TRUE
+ && TryGenerateWildMon(wildPokemonInfo, 2, WILD_CHECK_REPEL | WILD_CHECK_KEEN_EYE) == TRUE)
+ {
+ BattleSetup_StartWildBattle();
+ gSpecialVar_Result = TRUE;
+ }
+ else
+ {
+ gSpecialVar_Result = FALSE;
+ }
+ }
+ else
+ {
+ gSpecialVar_Result = FALSE;
+ }
+}
+
+bool8 SweetScentWildEncounter(void)
+{
+ s16 x, y;
+ u16 headerId;
+
+ PlayerGetDestCoords(&x, &y);
+ headerId = GetCurrentMapWildMonHeaderId();
+ if (headerId == 0xFFFF) // invalid
+ {
+ if (gMapHeader.mapDataId == 0x166)
+ {
+ headerId = GetBattlePikeWildMonHeaderId();
+ if (TryGenerateWildMon(gBattlePikeWildMonHeaders[headerId].landMonsInfo, WILD_AREA_LAND, 0) != TRUE)
+ return FALSE;
+
+ TryGenerateBattlePikeWildMon(FALSE);
+ BattleSetup_StartBattlePikeWildBattle();
+ return TRUE;
+ }
+ if (gMapHeader.mapDataId == 0x169)
+ {
+ headerId = gSaveBlock2Ptr->battlePyramidWildHeaderId;
+ if (TryGenerateWildMon(gBattlePyramidWildMonHeaders[headerId].landMonsInfo, WILD_AREA_LAND, 0) != TRUE)
+ return FALSE;
+
+ GenerateBattlePyramidWildMon();
+ BattleSetup_StartWildBattle();
+ return TRUE;
+ }
+ }
+ else
+ {
+ if (MetatileBehavior_IsLandWildEncounter(MapGridGetMetatileBehaviorAt(x, y)) == TRUE)
+ {
+ if (gWildMonHeaders[headerId].landMonsInfo == NULL)
+ return FALSE;
+
+ if (TryStartRoamerEncounter() == TRUE)
+ {
+ BattleSetup_StartRoamerBattle();
+ return TRUE;
+ }
+
+ if (DoMassOutbreakEncounterTest() == TRUE)
+ SetUpMassOutbreakEncounter(0);
+ else
+ TryGenerateWildMon(gWildMonHeaders[headerId].landMonsInfo, WILD_AREA_LAND, 0);
+
+ BattleSetup_StartWildBattle();
+ return TRUE;
+ }
+ else if (MetatileBehavior_IsWaterWildEncounter(MapGridGetMetatileBehaviorAt(x, y)) == TRUE)
+ {
+ if (AreLegendariesInSootopolisPreventingEncounters() == TRUE)
+ return FALSE;
+ if (gWildMonHeaders[headerId].waterMonsInfo == NULL)
+ return FALSE;
+
+ if (TryStartRoamerEncounter() == TRUE)
+ {
+ BattleSetup_StartRoamerBattle();
+ return TRUE;
+ }
+
+ TryGenerateWildMon(gWildMonHeaders[headerId].waterMonsInfo, WILD_AREA_WATER, 0);
+ BattleSetup_StartWildBattle();
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+bool8 DoesCurrentMapHaveFishingMons(void)
+{
+ u16 headerId = GetCurrentMapWildMonHeaderId();
+
+ if (headerId != 0xFFFF && gWildMonHeaders[headerId].fishingMonsInfo != NULL)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+void FishingWildEncounter(u8 rod)
+{
+ u16 species;
+
+ if (CheckFeebas() == TRUE)
+ {
+ u8 level = ChooseWildMonLevel(&gWildFeebasRoute119Data);
+
+ species = gWildFeebasRoute119Data.species;
+ CreateWildMon(species, level);
+ }
+ else
+ {
+ species = GenerateFishingWildMon(gWildMonHeaders[GetCurrentMapWildMonHeaderId()].fishingMonsInfo, rod);
+ }
+ IncrementGameStat(GAME_STAT_FISHING_CAPTURES);
+ SetPokemonAnglerSpecies(species);
+ BattleSetup_StartWildBattle();
+}
+
+u16 GetLocalWildMon(bool8 *isWaterMon)
+{
+ u16 headerId;
+ const struct WildPokemonInfo *landMonsInfo;
+ const struct WildPokemonInfo *waterMonsInfo;
+
+ *isWaterMon = FALSE;
+ headerId = GetCurrentMapWildMonHeaderId();
+ if (headerId == 0xFFFF)
+ return SPECIES_NONE;
+ landMonsInfo = gWildMonHeaders[headerId].landMonsInfo;
+ waterMonsInfo = gWildMonHeaders[headerId].waterMonsInfo;
+ // Neither
+ if (landMonsInfo == NULL && waterMonsInfo == NULL)
+ return SPECIES_NONE;
+ // Land Pokemon
+ else if (landMonsInfo != NULL && waterMonsInfo == NULL)
+ return landMonsInfo->wildPokemon[ChooseWildMonIndex_Land()].species;
+ // Water Pokemon
+ else if (landMonsInfo == NULL && waterMonsInfo != NULL)
+ {
+ *isWaterMon = TRUE;
+ return waterMonsInfo->wildPokemon[ChooseWildMonIndex_WaterRock()].species;
+ }
+ // Either land or water Pokemon
+ if ((Random() % 100) < 80)
+ {
+ return landMonsInfo->wildPokemon[ChooseWildMonIndex_Land()].species;
+ }
+ else
+ {
+ *isWaterMon = TRUE;
+ return waterMonsInfo->wildPokemon[ChooseWildMonIndex_WaterRock()].species;
+ }
+}
+
+u16 GetLocalWaterMon(void)
+{
+ u16 headerId = GetCurrentMapWildMonHeaderId();
+
+ if (headerId != 0xFFFF)
+ {
+ const struct WildPokemonInfo *waterMonsInfo = gWildMonHeaders[headerId].waterMonsInfo;
+
+ if (waterMonsInfo)
+ return waterMonsInfo->wildPokemon[ChooseWildMonIndex_WaterRock()].species;
+ }
+ return SPECIES_NONE;
+}
+
+bool8 UpdateRepelCounter(void)
+{
+ u16 steps;
+
+ if (InBattlePike() || InBattlePyramid())
+ return FALSE;
+ if (InUnionRoom() == TRUE)
+ return FALSE;
+
+ steps = VarGet(VAR_REPEL_STEP_COUNT);
+
+ if (steps != 0)
+ {
+ steps--;
+ VarSet(VAR_REPEL_STEP_COUNT, steps);
+ if (steps == 0)
+ {
+ ScriptContext1_SetupScript(EventScript_RepelWoreOff);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static bool8 IsWildLevelAllowedByRepel(u8 wildLevel)
+{
+ u8 i;
+
+ if (!VarGet(VAR_REPEL_STEP_COUNT))
+ return TRUE;
+
+ for (i = 0; i < PARTY_SIZE; i++)
+ {
+ if (GetMonData(&gPlayerParty[i], MON_DATA_HP) && !GetMonData(&gPlayerParty[i], MON_DATA_IS_EGG))
+ {
+ u8 ourLevel = GetMonData(&gPlayerParty[i], MON_DATA_LEVEL);
+
+ if (wildLevel < ourLevel)
+ return FALSE;
+ else
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static bool8 IsAbilityAllowingEncounter(u8 level)
+{
+ u8 ability;
+
+ if (GetMonData(&gPlayerParty[0], MON_DATA_SANITY_BIT3))
+ return TRUE;
+
+ ability = GetMonAbility(&gPlayerParty[0]);
+ if (ability == ABILITY_KEEN_EYE || ability == ABILITY_INTIMIDATE)
+ {
+ u8 playerMonLevel = GetMonData(&gPlayerParty[0], MON_DATA_LEVEL);
+ if (playerMonLevel > 5 && level <= playerMonLevel - 5 && !(Random() % 2))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static bool8 TryGetRandomWildMonIndexByType(const struct WildPokemon *wildMon, u8 type, u8 numMon, u8 *monIndex)
+{
+ u8 validIndexes[numMon]; // variable length array, an interesting feature
+ u8 i, validMonCount;
+
+ for (i = 0; i < numMon; i++)
+ validIndexes[i] = 0;
+
+ for (validMonCount = 0, i = 0; i < numMon; i++)
+ {
+ if (gBaseStats[wildMon[i].species].type1 == type || gBaseStats[wildMon[i].species].type2 == type)
+ validIndexes[validMonCount++] = i;
+ }
+
+ if (validMonCount == 0 || validMonCount == numMon)
+ return FALSE;
+
+ *monIndex = validIndexes[Random() % validMonCount];
+ return TRUE;
+}
+
+static bool8 TryGetAbilityInfluencedWildMonIndex(const struct WildPokemon *wildMon, u8 type, u8 ability, u8 *monIndex)
+{
+ if (GetMonData(&gPlayerParty[0], MON_DATA_SANITY_BIT3))
+ return FALSE;
+ else if (GetMonAbility(&gPlayerParty[0]) != ability)
+ return FALSE;
+ else if (Random() % 2 != 0)
+ return FALSE;
+
+ return TryGetRandomWildMonIndexByType(wildMon, type, LAND_WILD_COUNT, monIndex);
+}
+
+static void ApplyFluteEncounterRateMod(u32 *encRate)
+{
+ if (FlagGet(FLAG_SYS_ENC_UP_ITEM) == TRUE)
+ *encRate += *encRate / 2;
+ else if (FlagGet(FLAG_SYS_ENC_DOWN_ITEM) == TRUE)
+ *encRate = *encRate / 2;
+}
+
+static void ApplyCleanseTagEncounterRateMod(u32 *encRate)
+{
+ if (GetMonData(&gPlayerParty[0], MON_DATA_HELD_ITEM) == ITEM_CLEANSE_TAG)
+ *encRate = *encRate * 2 / 3;
+}