mirror of
https://github.com/pret/pokefirered.git
synced 2026-05-20 20:08:07 -05:00
1128 lines
36 KiB
C
1128 lines
36 KiB
C
#include "global.h"
|
|
#include "random.h"
|
|
#include "battle_debug.h"
|
|
#include "wild_encounter.h"
|
|
#include "event_data.h"
|
|
#include "fieldmap.h"
|
|
#include "random.h"
|
|
#include "roamer.h"
|
|
#include "field_player_avatar.h"
|
|
#include "battle_setup.h"
|
|
#include "overworld.h"
|
|
#include "metatile_behavior.h"
|
|
#include "event_scripts.h"
|
|
#include "script.h"
|
|
#include "link.h"
|
|
#include "quest_log.h"
|
|
#include "safari_zone.h"
|
|
#include "rtc.h"
|
|
#include "constants/maps.h"
|
|
#include "constants/abilities.h"
|
|
#include "constants/item.h"
|
|
#include "constants/items.h"
|
|
#include "constants/weather.h"
|
|
|
|
#define MAX_ENCOUNTER_RATE 1600
|
|
|
|
enum
|
|
{
|
|
WILD_AREA_LAND,
|
|
WILD_AREA_WATER,
|
|
WILD_AREA_ROCKS,
|
|
WILD_AREA_FISHING,
|
|
};
|
|
|
|
#define WILD_CHECK_REPEL 0x1
|
|
#define WILD_CHECK_KEEN_EYE 0x2
|
|
|
|
#define HEADER_NONE 0xFFFF
|
|
|
|
struct WildEncounterData
|
|
{
|
|
u32 rngState;
|
|
u16 prevMetatileBehavior;
|
|
u16 encounterRateBuff;
|
|
u8 stepsSinceLastEncounter;
|
|
u8 abilityEffect;
|
|
u16 leadMonHeldItem;
|
|
};
|
|
|
|
static EWRAM_DATA struct WildEncounterData sWildEncounterData = {};
|
|
static EWRAM_DATA bool8 sWildEncountersDisabled = FALSE;
|
|
EWRAM_DATA bool8 gIsFishingEncounter = 0;
|
|
EWRAM_DATA bool8 gIsSurfingEncounter = 0;
|
|
EWRAM_DATA u16 gChainFishingDexNavStreak = 0;
|
|
|
|
static bool8 UnlockedTanobyOrAreNotInTanoby(void);
|
|
static u32 GenerateUnownPersonalityByLetter(u8 letter);
|
|
static void UpdateChainFishingStreak();
|
|
static bool8 IsWildLevelAllowedByRepel(u8 level);
|
|
static void ApplyFluteEncounterRateMod(u32 *rate);
|
|
static u8 GetMaxLevelOfSpeciesInWildTable(const struct WildPokemon *wildMon, u16 species, u8 area);
|
|
static u8 GetFluteEncounterRateModType(void);
|
|
static void ApplyCleanseTagEncounterRateMod(u32 *rate);
|
|
static bool8 IsLeadMonHoldingCleanseTag(void);
|
|
static u16 WildEncounterRandom(void);
|
|
static void AddToWildEncounterRateBuff(u8 encouterRate);
|
|
static bool8 TryGetAbilityInfluencedWildMonIndex(const struct WildPokemon *wildMon, u8 type, u16 ability, u8 *monIndex, u32 size);
|
|
static bool8 IsAbilityAllowingEncounter(u8 level);
|
|
|
|
#include "data/wild_encounters.h"
|
|
|
|
static const u8 sUnownLetterSlots[][LAND_WILD_COUNT] = {
|
|
// A A A A A A A A A A A ?
|
|
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27},
|
|
// C C C D D D H H H U U O
|
|
{ 2, 2, 2, 3, 3, 3, 7, 7, 7, 20, 20, 14},
|
|
// N N N N S S S S I I E E
|
|
{13, 13, 13, 13, 18, 18, 18, 18, 8, 8, 4, 4},
|
|
// P P L L J J R R R Q Q Q
|
|
{15, 15, 11, 11, 9, 9, 17, 17, 17, 16, 16, 16},
|
|
// Y Y T T G G G F F F K K
|
|
{24, 24, 19, 19, 6, 6, 6, 5, 5, 5, 10, 10},
|
|
// V V V W W W X X M M B B
|
|
{21, 21, 21, 22, 22, 22, 23, 23, 12, 12, 1, 1},
|
|
// Z Z Z Z Z Z Z Z Z Z Z !
|
|
{25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26},
|
|
};
|
|
|
|
void DisableWildEncounters(bool8 state)
|
|
{
|
|
sWildEncountersDisabled = state;
|
|
}
|
|
|
|
u8 ChooseWildMonIndex_Land(void)
|
|
{
|
|
u8 wildMonIndex = 0;
|
|
bool8 swap = FALSE;
|
|
u8 rand = Random() % ENCOUNTER_CHANCE_LAND_MONS_TOTAL;
|
|
|
|
if (rand < ENCOUNTER_CHANCE_LAND_MONS_SLOT_0)
|
|
wildMonIndex = 0;
|
|
else if (rand >= ENCOUNTER_CHANCE_LAND_MONS_SLOT_0 && rand < ENCOUNTER_CHANCE_LAND_MONS_SLOT_1)
|
|
wildMonIndex = 1;
|
|
else if (rand >= ENCOUNTER_CHANCE_LAND_MONS_SLOT_1 && rand < ENCOUNTER_CHANCE_LAND_MONS_SLOT_2)
|
|
wildMonIndex = 2;
|
|
else if (rand >= ENCOUNTER_CHANCE_LAND_MONS_SLOT_2 && rand < ENCOUNTER_CHANCE_LAND_MONS_SLOT_3)
|
|
wildMonIndex = 3;
|
|
else if (rand >= ENCOUNTER_CHANCE_LAND_MONS_SLOT_3 && rand < ENCOUNTER_CHANCE_LAND_MONS_SLOT_4)
|
|
wildMonIndex = 4;
|
|
else if (rand >= ENCOUNTER_CHANCE_LAND_MONS_SLOT_4 && rand < ENCOUNTER_CHANCE_LAND_MONS_SLOT_5)
|
|
wildMonIndex = 5;
|
|
else if (rand >= ENCOUNTER_CHANCE_LAND_MONS_SLOT_5 && rand < ENCOUNTER_CHANCE_LAND_MONS_SLOT_6)
|
|
wildMonIndex = 6;
|
|
else if (rand >= ENCOUNTER_CHANCE_LAND_MONS_SLOT_6 && rand < ENCOUNTER_CHANCE_LAND_MONS_SLOT_7)
|
|
wildMonIndex = 7;
|
|
else if (rand >= ENCOUNTER_CHANCE_LAND_MONS_SLOT_7 && rand < ENCOUNTER_CHANCE_LAND_MONS_SLOT_8)
|
|
wildMonIndex = 8;
|
|
else if (rand >= ENCOUNTER_CHANCE_LAND_MONS_SLOT_8 && rand < ENCOUNTER_CHANCE_LAND_MONS_SLOT_9)
|
|
wildMonIndex = 9;
|
|
else if (rand >= ENCOUNTER_CHANCE_LAND_MONS_SLOT_9 && rand < ENCOUNTER_CHANCE_LAND_MONS_SLOT_10)
|
|
wildMonIndex = 10;
|
|
else
|
|
wildMonIndex = 11;
|
|
|
|
if (LURE_STEP_COUNT != 0 && (Random() % 10 < 2))
|
|
swap = TRUE;
|
|
|
|
if (swap)
|
|
wildMonIndex = 11 - wildMonIndex;
|
|
|
|
return wildMonIndex;
|
|
}
|
|
|
|
u8 ChooseWildMonIndex_WaterRock(void)
|
|
{
|
|
u8 wildMonIndex = 0;
|
|
bool8 swap = FALSE;
|
|
u8 rand = Random() % ENCOUNTER_CHANCE_WATER_MONS_TOTAL;
|
|
|
|
if (rand < ENCOUNTER_CHANCE_WATER_MONS_SLOT_0)
|
|
wildMonIndex = 0;
|
|
else if (rand >= ENCOUNTER_CHANCE_WATER_MONS_SLOT_0 && rand < ENCOUNTER_CHANCE_WATER_MONS_SLOT_1)
|
|
wildMonIndex = 1;
|
|
else if (rand >= ENCOUNTER_CHANCE_WATER_MONS_SLOT_1 && rand < ENCOUNTER_CHANCE_WATER_MONS_SLOT_2)
|
|
wildMonIndex = 2;
|
|
else if (rand >= ENCOUNTER_CHANCE_WATER_MONS_SLOT_2 && rand < ENCOUNTER_CHANCE_WATER_MONS_SLOT_3)
|
|
wildMonIndex = 3;
|
|
else
|
|
wildMonIndex = 4;
|
|
|
|
if (LURE_STEP_COUNT != 0 && (Random() % 10 < 2))
|
|
swap = TRUE;
|
|
|
|
if (swap)
|
|
wildMonIndex = 4 - wildMonIndex;
|
|
|
|
return wildMonIndex;
|
|
}
|
|
|
|
static u8 ChooseWildMonIndex_Fishing(u8 rod)
|
|
{
|
|
u8 wildMonIndex = 0;
|
|
bool8 swap = FALSE;
|
|
u8 rand = Random() % max(max(ENCOUNTER_CHANCE_FISHING_MONS_OLD_ROD_TOTAL, ENCOUNTER_CHANCE_FISHING_MONS_GOOD_ROD_TOTAL),
|
|
ENCOUNTER_CHANCE_FISHING_MONS_SUPER_ROD_TOTAL);
|
|
|
|
if (LURE_STEP_COUNT != 0 && (Random() % 10 < 2))
|
|
swap = TRUE;
|
|
|
|
switch (rod)
|
|
{
|
|
case OLD_ROD:
|
|
if (rand < ENCOUNTER_CHANCE_FISHING_MONS_OLD_ROD_SLOT_0)
|
|
wildMonIndex = 0;
|
|
else
|
|
wildMonIndex = 1;
|
|
|
|
if (swap)
|
|
wildMonIndex = 1 - wildMonIndex;
|
|
break;
|
|
case GOOD_ROD:
|
|
if (rand < ENCOUNTER_CHANCE_FISHING_MONS_GOOD_ROD_SLOT_2)
|
|
wildMonIndex = 2;
|
|
if (rand >= ENCOUNTER_CHANCE_FISHING_MONS_GOOD_ROD_SLOT_2 && rand < ENCOUNTER_CHANCE_FISHING_MONS_GOOD_ROD_SLOT_3)
|
|
wildMonIndex = 3;
|
|
if (rand >= ENCOUNTER_CHANCE_FISHING_MONS_GOOD_ROD_SLOT_3 && rand < ENCOUNTER_CHANCE_FISHING_MONS_GOOD_ROD_SLOT_4)
|
|
wildMonIndex = 4;
|
|
|
|
if (swap)
|
|
wildMonIndex = 6 - wildMonIndex;
|
|
break;
|
|
case SUPER_ROD:
|
|
if (rand < ENCOUNTER_CHANCE_FISHING_MONS_SUPER_ROD_SLOT_5)
|
|
wildMonIndex = 5;
|
|
if (rand >= ENCOUNTER_CHANCE_FISHING_MONS_SUPER_ROD_SLOT_5 && rand < ENCOUNTER_CHANCE_FISHING_MONS_SUPER_ROD_SLOT_6)
|
|
wildMonIndex = 6;
|
|
if (rand >= ENCOUNTER_CHANCE_FISHING_MONS_SUPER_ROD_SLOT_6 && rand < ENCOUNTER_CHANCE_FISHING_MONS_SUPER_ROD_SLOT_7)
|
|
wildMonIndex = 7;
|
|
if (rand >= ENCOUNTER_CHANCE_FISHING_MONS_SUPER_ROD_SLOT_7 && rand < ENCOUNTER_CHANCE_FISHING_MONS_SUPER_ROD_SLOT_8)
|
|
wildMonIndex = 8;
|
|
if (rand >= ENCOUNTER_CHANCE_FISHING_MONS_SUPER_ROD_SLOT_8 && rand < ENCOUNTER_CHANCE_FISHING_MONS_SUPER_ROD_SLOT_9)
|
|
wildMonIndex = 9;
|
|
|
|
if (swap)
|
|
wildMonIndex = 14 - wildMonIndex;
|
|
break;
|
|
}
|
|
return wildMonIndex;
|
|
}
|
|
|
|
static u8 ChooseWildMonLevel(const struct WildPokemon *wildPokemon, u8 wildMonIndex, u8 area)
|
|
{
|
|
u8 min;
|
|
u8 max;
|
|
u8 range;
|
|
u8 rand;
|
|
|
|
if (LURE_STEP_COUNT == 0)
|
|
{
|
|
// Make sure minimum level is less than maximum level
|
|
if (wildPokemon[wildMonIndex].maxLevel >= wildPokemon[wildMonIndex].minLevel)
|
|
{
|
|
min = wildPokemon[wildMonIndex].minLevel;
|
|
max = wildPokemon[wildMonIndex].maxLevel;
|
|
}
|
|
else
|
|
{
|
|
min = wildPokemon[wildMonIndex].maxLevel;
|
|
max = wildPokemon[wildMonIndex].minLevel;
|
|
}
|
|
range = max - min + 1;
|
|
rand = Random() % range;
|
|
|
|
// check ability for max level mon
|
|
if (!GetMonData(&gPlayerParty[0], MON_DATA_SANITY_IS_EGG))
|
|
{
|
|
u16 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;
|
|
}
|
|
else
|
|
{
|
|
// Looks for the max level of all slots that share the same species as the selected slot.
|
|
max = GetMaxLevelOfSpeciesInWildTable(wildPokemon, wildPokemon[wildMonIndex].species, area);
|
|
if (max > 0)
|
|
return max + 1;
|
|
else // Failsafe
|
|
return wildPokemon[wildMonIndex].maxLevel + 1;
|
|
}
|
|
}
|
|
|
|
u16 GetCurrentMapWildMonHeaderId(void)
|
|
{
|
|
u16 i;
|
|
|
|
for (i = 0; ; i++)
|
|
{
|
|
const struct WildPokemonHeader * wildHeader = &gWildMonHeaders[i];
|
|
if (wildHeader->mapGroup == MAP_GROUP(UNDEFINED))
|
|
break;
|
|
|
|
if (gWildMonHeaders[i].mapGroup == gSaveBlock1Ptr->location.mapGroup &&
|
|
gWildMonHeaders[i].mapNum == gSaveBlock1Ptr->location.mapNum)
|
|
{
|
|
if (gSaveBlock1Ptr->location.mapGroup == MAP_GROUP(SIX_ISLAND_ALTERING_CAVE) &&
|
|
gSaveBlock1Ptr->location.mapNum == MAP_NUM(SIX_ISLAND_ALTERING_CAVE))
|
|
{
|
|
u16 alteringCaveId = VarGet(VAR_ALTERING_CAVE_WILD_SET);
|
|
if (alteringCaveId >= NUM_ALTERING_CAVE_TABLES)
|
|
alteringCaveId = 0;
|
|
|
|
i += alteringCaveId;
|
|
}
|
|
|
|
if (!UnlockedTanobyOrAreNotInTanoby())
|
|
break;
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return HEADER_NONE;
|
|
}
|
|
|
|
static bool8 UnlockedTanobyOrAreNotInTanoby(void)
|
|
{
|
|
if (FlagGet(FLAG_SYS_UNLOCKED_TANOBY_RUINS))
|
|
return TRUE;
|
|
if (gSaveBlock1Ptr->location.mapGroup != MAP_GROUP(SEVEN_ISLAND_TANOBY_RUINS_DILFORD_CHAMBER))
|
|
return TRUE;
|
|
if (!(gSaveBlock1Ptr->location.mapNum == MAP_NUM(SEVEN_ISLAND_TANOBY_RUINS_MONEAN_CHAMBER)
|
|
|| gSaveBlock1Ptr->location.mapNum == MAP_NUM(SEVEN_ISLAND_TANOBY_RUINS_LIPTOO_CHAMBER)
|
|
|| gSaveBlock1Ptr->location.mapNum == MAP_NUM(SEVEN_ISLAND_TANOBY_RUINS_WEEPTH_CHAMBER)
|
|
|| gSaveBlock1Ptr->location.mapNum == MAP_NUM(SEVEN_ISLAND_TANOBY_RUINS_DILFORD_CHAMBER)
|
|
|| gSaveBlock1Ptr->location.mapNum == MAP_NUM(SEVEN_ISLAND_TANOBY_RUINS_SCUFIB_CHAMBER)
|
|
|| gSaveBlock1Ptr->location.mapNum == MAP_NUM(SEVEN_ISLAND_TANOBY_RUINS_RIXY_CHAMBER)
|
|
|| gSaveBlock1Ptr->location.mapNum == MAP_NUM(SEVEN_ISLAND_TANOBY_RUINS_VIAPOIS_CHAMBER)
|
|
))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
u8 PickWildMonNature(void)
|
|
{
|
|
// check synchronize for a Pokémon with the same ability
|
|
if (OW_SYNCHRONIZE_NATURE < GEN_9
|
|
&& !GetMonData(&gPlayerParty[0], MON_DATA_SANITY_IS_EGG)
|
|
&& GetMonAbility(&gPlayerParty[0]) == ABILITY_SYNCHRONIZE
|
|
&& (OW_SYNCHRONIZE_NATURE == GEN_8 || Random() % 2 == 0))
|
|
{
|
|
return GetMonData(&gPlayerParty[0], MON_DATA_PERSONALITY) % NUM_NATURES;
|
|
}
|
|
|
|
// random nature
|
|
return Random() % NUM_NATURES;
|
|
}
|
|
|
|
void CreateWildMon(u16 species, u8 level, u8 unownSlot)
|
|
{
|
|
u32 personality;
|
|
s8 chamber;
|
|
bool32 checkCuteCharm;
|
|
u8 unownLetter = NUM_UNOWN_FORMS;
|
|
|
|
ZeroEnemyPartyMons();
|
|
|
|
switch (gSpeciesInfo[species].genderRatio)
|
|
{
|
|
case MON_MALE:
|
|
case MON_FEMALE:
|
|
case MON_GENDERLESS:
|
|
checkCuteCharm = FALSE;
|
|
break;
|
|
}
|
|
|
|
if (species == SPECIES_UNOWN) {
|
|
chamber = gSaveBlock1Ptr->location.mapNum - MAP_NUM(SEVEN_ISLAND_TANOBY_RUINS_MONEAN_CHAMBER);
|
|
unownLetter = sUnownLetterSlots[chamber][unownSlot];
|
|
}
|
|
|
|
if (checkCuteCharm
|
|
&& !GetMonData(&gPlayerParty[0], MON_DATA_SANITY_IS_EGG)
|
|
&& 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, USE_RANDOM_IVS, gender, PickWildMonNature(), unownLetter);
|
|
return;
|
|
}
|
|
|
|
if (species != SPECIES_UNOWN)
|
|
{
|
|
CreateMonWithNature(&gEnemyParty[0], species, level, USE_RANDOM_IVS, PickWildMonNature());
|
|
}
|
|
else
|
|
{
|
|
personality = GenerateUnownPersonalityByLetter(unownLetter);
|
|
CreateMon(&gEnemyParty[0], species, level, USE_RANDOM_IVS, TRUE, personality, FALSE, 0);
|
|
}
|
|
}
|
|
|
|
static u32 GenerateUnownPersonalityByLetter(u8 letter)
|
|
{
|
|
u32 personality;
|
|
do
|
|
{
|
|
personality = (Random() << 16) | Random();
|
|
} while (GetUnownLetterByPersonalityLoByte(personality) != letter);
|
|
return personality;
|
|
}
|
|
|
|
u8 GetUnownLetterByPersonalityLoByte(u32 personality)
|
|
{
|
|
return GET_UNOWN_LETTER(personality);
|
|
}
|
|
|
|
#define TRY_GET_ABILITY_INFLUENCED_WILD_MON_INDEX(wildPokemon, type, ability, ptr, count) TryGetAbilityInfluencedWildMonIndex(wildPokemon, type, ability, ptr, count)
|
|
|
|
static bool8 TryGenerateWildMon(const struct WildPokemonInfo * wildMonInfo, u8 area, u8 flags)
|
|
{
|
|
u8 wildMonIndex = 0;
|
|
u8 level;
|
|
switch (area)
|
|
{
|
|
case WILD_AREA_LAND:
|
|
if (TRY_GET_ABILITY_INFLUENCED_WILD_MON_INDEX(wildMonInfo->wildPokemon, TYPE_STEEL, ABILITY_MAGNET_PULL, &wildMonIndex, LAND_WILD_COUNT))
|
|
break;
|
|
if (TRY_GET_ABILITY_INFLUENCED_WILD_MON_INDEX(wildMonInfo->wildPokemon, TYPE_ELECTRIC, ABILITY_STATIC, &wildMonIndex, LAND_WILD_COUNT))
|
|
break;
|
|
if (OW_LIGHTNING_ROD == GEN_8 && TRY_GET_ABILITY_INFLUENCED_WILD_MON_INDEX(wildMonInfo->wildPokemon, TYPE_ELECTRIC, ABILITY_LIGHTNING_ROD, &wildMonIndex, LAND_WILD_COUNT))
|
|
break;
|
|
if (OW_FLASH_FIRE == GEN_8 && TRY_GET_ABILITY_INFLUENCED_WILD_MON_INDEX(wildMonInfo->wildPokemon, TYPE_FIRE, ABILITY_FLASH_FIRE, &wildMonIndex, LAND_WILD_COUNT))
|
|
break;
|
|
if (OW_HARVEST == GEN_8 && TRY_GET_ABILITY_INFLUENCED_WILD_MON_INDEX(wildMonInfo->wildPokemon, TYPE_GRASS, ABILITY_HARVEST, &wildMonIndex, LAND_WILD_COUNT))
|
|
break;
|
|
if (OW_STORM_DRAIN == GEN_8 && TRY_GET_ABILITY_INFLUENCED_WILD_MON_INDEX(wildMonInfo->wildPokemon, TYPE_WATER, ABILITY_STORM_DRAIN, &wildMonIndex, LAND_WILD_COUNT))
|
|
break;
|
|
|
|
wildMonIndex = ChooseWildMonIndex_Land();
|
|
break;
|
|
case WILD_AREA_WATER:
|
|
if (TRY_GET_ABILITY_INFLUENCED_WILD_MON_INDEX(wildMonInfo->wildPokemon, TYPE_STEEL, ABILITY_MAGNET_PULL, &wildMonIndex, WATER_WILD_COUNT))
|
|
break;
|
|
if (TRY_GET_ABILITY_INFLUENCED_WILD_MON_INDEX(wildMonInfo->wildPokemon, TYPE_ELECTRIC, ABILITY_STATIC, &wildMonIndex, WATER_WILD_COUNT))
|
|
break;
|
|
if (OW_LIGHTNING_ROD == GEN_8 && TRY_GET_ABILITY_INFLUENCED_WILD_MON_INDEX(wildMonInfo->wildPokemon, TYPE_ELECTRIC, ABILITY_LIGHTNING_ROD, &wildMonIndex, WATER_WILD_COUNT))
|
|
break;
|
|
if (OW_FLASH_FIRE == GEN_8 && TRY_GET_ABILITY_INFLUENCED_WILD_MON_INDEX(wildMonInfo->wildPokemon, TYPE_FIRE, ABILITY_FLASH_FIRE, &wildMonIndex, WATER_WILD_COUNT))
|
|
break;
|
|
if (OW_HARVEST == GEN_8 && TRY_GET_ABILITY_INFLUENCED_WILD_MON_INDEX(wildMonInfo->wildPokemon, TYPE_GRASS, ABILITY_HARVEST, &wildMonIndex, WATER_WILD_COUNT))
|
|
break;
|
|
if (OW_STORM_DRAIN == GEN_8 && TRY_GET_ABILITY_INFLUENCED_WILD_MON_INDEX(wildMonInfo->wildPokemon, TYPE_WATER, ABILITY_STORM_DRAIN, &wildMonIndex, WATER_WILD_COUNT))
|
|
break;
|
|
|
|
wildMonIndex = ChooseWildMonIndex_WaterRock();
|
|
break;
|
|
case WILD_AREA_ROCKS:
|
|
wildMonIndex = ChooseWildMonIndex_WaterRock();
|
|
break;
|
|
}
|
|
|
|
level = ChooseWildMonLevel(wildMonInfo->wildPokemon, wildMonIndex, area);
|
|
if (flags & WILD_CHECK_REPEL && !IsWildLevelAllowedByRepel(level))
|
|
return FALSE;
|
|
if (flags & WILD_CHECK_KEEN_EYE && !IsAbilityAllowingEncounter(level))
|
|
return FALSE;
|
|
|
|
CreateWildMon(wildMonInfo->wildPokemon[wildMonIndex].species, level, wildMonIndex);
|
|
return TRUE;
|
|
}
|
|
|
|
static u16 GenerateFishingEncounter(const struct WildPokemonInfo * wildMonInfo, u8 rod)
|
|
{
|
|
u8 wildMonIndex = ChooseWildMonIndex_Fishing(rod);
|
|
u16 wildMonSpecies = wildMonInfo->wildPokemon[wildMonIndex].species;
|
|
u8 level = ChooseWildMonLevel(wildMonInfo->wildPokemon, wildMonIndex, WILD_AREA_FISHING);
|
|
|
|
UpdateChainFishingStreak();
|
|
CreateWildMon(wildMonSpecies, level, wildMonIndex);
|
|
return wildMonSpecies;
|
|
}
|
|
|
|
static bool8 DoWildEncounterRateDiceRoll(u16 encounterRate)
|
|
{
|
|
if (WildEncounterRandom() % MAX_ENCOUNTER_RATE < encounterRate)
|
|
return TRUE;
|
|
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;
|
|
encounterRate += sWildEncounterData.encounterRateBuff * 16 / 200;
|
|
ApplyFluteEncounterRateMod(&encounterRate);
|
|
ApplyCleanseTagEncounterRateMod(&encounterRate);
|
|
if (LURE_STEP_COUNT != 0)
|
|
encounterRate *= 2;
|
|
if (!ignoreAbility && !GetMonData(&gPlayerParty[0], MON_DATA_SANITY_IS_EGG))
|
|
{
|
|
u32 ability = GetMonAbility(&gPlayerParty[0]);
|
|
|
|
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 == WEATHER_SANDSTORM)
|
|
encounterRate /= 2;
|
|
else if (ability == ABILITY_SNOW_CLOAK && gSaveBlock1Ptr->weather == WEATHER_SNOW)
|
|
encounterRate /= 2;
|
|
else if (ability == ABILITY_QUICK_FEET)
|
|
encounterRate /= 2;
|
|
else if (ability == ABILITY_INFILTRATOR && OW_INFILTRATOR == GEN_8)
|
|
encounterRate /= 2;
|
|
else if (ability == ABILITY_NO_GUARD)
|
|
encounterRate *= 2;
|
|
}
|
|
if (encounterRate > MAX_ENCOUNTER_RATE)
|
|
encounterRate = MAX_ENCOUNTER_RATE;
|
|
return DoWildEncounterRateDiceRoll(encounterRate);
|
|
}
|
|
|
|
static u8 GetAbilityEncounterRateModType(void)
|
|
{
|
|
sWildEncounterData.abilityEffect = 0;
|
|
if (!GetMonData(&gPlayerParty[0], MON_DATA_SANITY_IS_EGG))
|
|
{
|
|
u16 ability = GetMonAbility(&gPlayerParty[0]);
|
|
if (ability == ABILITY_STENCH)
|
|
sWildEncounterData.abilityEffect = 1;
|
|
else if (ability == ABILITY_ILLUMINATE)
|
|
sWildEncounterData.abilityEffect = 2;
|
|
else if (ability == ABILITY_WHITE_SMOKE)
|
|
sWildEncounterData.abilityEffect = 1;
|
|
else if (ability == ABILITY_ARENA_TRAP)
|
|
sWildEncounterData.abilityEffect = 2;
|
|
else if (ability == ABILITY_SAND_VEIL && gSaveBlock1Ptr->weather == WEATHER_SANDSTORM)
|
|
sWildEncounterData.abilityEffect = 1;
|
|
else if (ability == ABILITY_SNOW_CLOAK && gSaveBlock1Ptr->weather == WEATHER_SNOW)
|
|
sWildEncounterData.abilityEffect = 1;
|
|
else if (ability == ABILITY_QUICK_FEET)
|
|
sWildEncounterData.abilityEffect = 1;
|
|
else if (ability == ABILITY_INFILTRATOR && OW_INFILTRATOR == GEN_8)
|
|
sWildEncounterData.abilityEffect = 1;
|
|
else if (ability == ABILITY_NO_GUARD)
|
|
sWildEncounterData.abilityEffect = 2;
|
|
}
|
|
return sWildEncounterData.abilityEffect;
|
|
}
|
|
|
|
static bool8 DoGlobalWildEncounterDiceRoll(void)
|
|
{
|
|
if ((Random() % 100) >= 60)
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
bool8 TryStandardWildLandEncounter(u16 headerId, u32 currMetatileAttrs, u16 previousMetatileBehavior)
|
|
{
|
|
struct Roamer * roamer;
|
|
if (gWildMonHeaders[headerId].landMonsInfo == NULL)
|
|
return FALSE;
|
|
if (previousMetatileBehavior != ExtractMetatileAttribute(currMetatileAttrs, METATILE_ATTRIBUTE_BEHAVIOR) && !DoGlobalWildEncounterDiceRoll())
|
|
return FALSE;
|
|
if (DoWildEncounterRateTest(gWildMonHeaders[headerId].landMonsInfo->encounterRate, FALSE) != TRUE)
|
|
{
|
|
AddToWildEncounterRateBuff(gWildMonHeaders[headerId].landMonsInfo->encounterRate);
|
|
return FALSE;
|
|
}
|
|
if (TryStartRoamerEncounter() == TRUE)
|
|
{
|
|
roamer = &gSaveBlock1Ptr->roamer;
|
|
if (!IsWildLevelAllowedByRepel(roamer->level))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
StartRoamerBattle();
|
|
return TRUE;
|
|
}
|
|
|
|
// try a regular wild land encounter
|
|
if (TryGenerateWildMon(gWildMonHeaders[headerId].landMonsInfo, WILD_AREA_LAND, WILD_CHECK_REPEL) == TRUE)
|
|
{
|
|
if (TryDoDoubleWildBattle())
|
|
{
|
|
struct Pokemon mon1 = gEnemyParty[0];
|
|
TryGenerateWildMon(gWildMonHeaders[headerId].landMonsInfo, WILD_AREA_LAND, WILD_CHECK_KEEN_EYE);
|
|
gEnemyParty[1] = mon1;
|
|
StartDoubleWildBattle();
|
|
}
|
|
else
|
|
{
|
|
StartWildBattle();
|
|
}
|
|
return TRUE;
|
|
}
|
|
AddToWildEncounterRateBuff(gWildMonHeaders[headerId].landMonsInfo->encounterRate);
|
|
return FALSE;
|
|
}
|
|
|
|
bool8 TryStandardWildSurfEncounter(u16 headerId, u32 currMetatileAttrs, u16 previousMetatileBehavior)
|
|
{
|
|
struct Roamer * roamer;
|
|
if (gWildMonHeaders[headerId].waterMonsInfo == NULL)
|
|
return FALSE;
|
|
if (previousMetatileBehavior != ExtractMetatileAttribute(currMetatileAttrs, METATILE_ATTRIBUTE_BEHAVIOR) && !DoGlobalWildEncounterDiceRoll())
|
|
return FALSE;
|
|
if (DoWildEncounterRateTest(gWildMonHeaders[headerId].waterMonsInfo->encounterRate, FALSE) != TRUE)
|
|
{
|
|
AddToWildEncounterRateBuff(gWildMonHeaders[headerId].waterMonsInfo->encounterRate);
|
|
return FALSE;
|
|
}
|
|
|
|
if (TryStartRoamerEncounter() == TRUE)
|
|
{
|
|
roamer = &gSaveBlock1Ptr->roamer;
|
|
if (!IsWildLevelAllowedByRepel(roamer->level))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
StartRoamerBattle();
|
|
return TRUE;
|
|
}
|
|
// try a regular surfing encounter
|
|
if (TryGenerateWildMon(gWildMonHeaders[headerId].waterMonsInfo, WILD_AREA_WATER, WILD_CHECK_REPEL) == TRUE)
|
|
{
|
|
gIsSurfingEncounter = TRUE;
|
|
if (TryDoDoubleWildBattle())
|
|
{
|
|
struct Pokemon mon1 = gEnemyParty[0];
|
|
TryGenerateWildMon(gWildMonHeaders[headerId].waterMonsInfo, WILD_AREA_WATER, WILD_CHECK_KEEN_EYE);
|
|
gEnemyParty[1] = mon1;
|
|
StartDoubleWildBattle();
|
|
}
|
|
else
|
|
{
|
|
StartWildBattle();
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
AddToWildEncounterRateBuff(gWildMonHeaders[headerId].waterMonsInfo->encounterRate);
|
|
return FALSE;
|
|
}
|
|
|
|
bool8 StandardWildEncounter(u32 currMetatileAttrs, u16 previousMetatileBehavior)
|
|
{
|
|
u16 headerId;
|
|
|
|
if (sWildEncountersDisabled == TRUE)
|
|
return FALSE;
|
|
|
|
headerId = GetCurrentMapWildMonHeaderId();
|
|
|
|
if (headerId == HEADER_NONE)
|
|
return FALSE;
|
|
|
|
if (ExtractMetatileAttribute(currMetatileAttrs, METATILE_ATTRIBUTE_ENCOUNTER_TYPE) == TILE_ENCOUNTER_LAND)
|
|
return TryStandardWildLandEncounter(headerId, currMetatileAttrs, previousMetatileBehavior);
|
|
else if (ExtractMetatileAttribute(currMetatileAttrs, METATILE_ATTRIBUTE_ENCOUNTER_TYPE) == TILE_ENCOUNTER_WATER
|
|
|| (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_SURFING) && MetatileBehavior_IsBridge(ExtractMetatileAttribute(currMetatileAttrs, METATILE_ATTRIBUTE_BEHAVIOR)) == TRUE))
|
|
return TryStandardWildSurfEncounter(headerId, currMetatileAttrs, previousMetatileBehavior);
|
|
return FALSE;
|
|
}
|
|
|
|
void RockSmashWildEncounter(void)
|
|
{
|
|
u16 headerIdx = GetCurrentMapWildMonHeaderId();
|
|
if (headerIdx == HEADER_NONE)
|
|
gSpecialVar_Result = FALSE;
|
|
else if (gWildMonHeaders[headerIdx].rockSmashMonsInfo == NULL)
|
|
gSpecialVar_Result = FALSE;
|
|
else if (DoWildEncounterRateTest(gWildMonHeaders[headerIdx].rockSmashMonsInfo->encounterRate, TRUE) != TRUE)
|
|
gSpecialVar_Result = FALSE;
|
|
else if (TryGenerateWildMon(gWildMonHeaders[headerIdx].rockSmashMonsInfo, WILD_AREA_ROCKS, WILD_CHECK_REPEL) == TRUE)
|
|
{
|
|
StartWildBattle();
|
|
gSpecialVar_Result = TRUE;
|
|
}
|
|
else
|
|
gSpecialVar_Result = FALSE;
|
|
}
|
|
|
|
bool8 SweetScentWildEncounter(void)
|
|
{
|
|
s16 x, y;
|
|
u16 headerId;
|
|
|
|
PlayerGetDestCoords(&x, &y);
|
|
headerId = GetCurrentMapWildMonHeaderId();
|
|
|
|
if (headerId == HEADER_NONE)
|
|
return FALSE;
|
|
|
|
if (MapGridGetMetatileAttributeAt(x, y, METATILE_ATTRIBUTE_ENCOUNTER_TYPE) == TILE_ENCOUNTER_LAND)
|
|
{
|
|
if (TryStartRoamerEncounter() == TRUE)
|
|
{
|
|
StartRoamerBattle();
|
|
return TRUE;
|
|
}
|
|
|
|
if (gWildMonHeaders[headerId].landMonsInfo == NULL)
|
|
return FALSE;
|
|
|
|
TryGenerateWildMon(gWildMonHeaders[headerId].landMonsInfo, WILD_AREA_LAND, 0);
|
|
|
|
StartWildBattle();
|
|
return TRUE;
|
|
}
|
|
else if (MapGridGetMetatileAttributeAt(x, y, METATILE_ATTRIBUTE_ENCOUNTER_TYPE) == TILE_ENCOUNTER_WATER)
|
|
{
|
|
if (TryStartRoamerEncounter() == TRUE)
|
|
{
|
|
StartRoamerBattle();
|
|
return TRUE;
|
|
}
|
|
|
|
if (gWildMonHeaders[headerId].waterMonsInfo == NULL)
|
|
return FALSE;
|
|
|
|
TryGenerateWildMon(gWildMonHeaders[headerId].waterMonsInfo, WILD_AREA_WATER, 0);
|
|
StartWildBattle();
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
bool8 DoesCurrentMapHaveFishingMons(void)
|
|
{
|
|
u16 headerIdx = GetCurrentMapWildMonHeaderId();
|
|
if (headerIdx == HEADER_NONE)
|
|
return FALSE;
|
|
if (gWildMonHeaders[headerIdx].fishingMonsInfo == NULL)
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
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)
|
|
{
|
|
gIsFishingEncounter = TRUE;
|
|
GenerateFishingEncounter(gWildMonHeaders[GetCurrentMapWildMonHeaderId()].fishingMonsInfo, rod);
|
|
IncrementGameStat(GAME_STAT_FISHING_CAPTURES);
|
|
StartWildBattle();
|
|
}
|
|
|
|
u16 GetLocalWildMon(bool8 *isWaterMon)
|
|
{
|
|
u16 headerId;
|
|
const struct WildPokemonInfo *landMonsInfo;
|
|
const struct WildPokemonInfo *waterMonsInfo;
|
|
|
|
*isWaterMon = FALSE;
|
|
headerId = GetCurrentMapWildMonHeaderId();
|
|
if (headerId == HEADER_NONE)
|
|
return SPECIES_NONE;
|
|
landMonsInfo = gWildMonHeaders[headerId].landMonsInfo;
|
|
waterMonsInfo = gWildMonHeaders[headerId].waterMonsInfo;
|
|
// Neither
|
|
if (landMonsInfo == NULL && waterMonsInfo == NULL)
|
|
return SPECIES_NONE;
|
|
// Land Pokémon
|
|
else if (landMonsInfo != NULL && waterMonsInfo == NULL)
|
|
return landMonsInfo->wildPokemon[ChooseWildMonIndex_Land()].species;
|
|
// Water Pokémon
|
|
else if (landMonsInfo == NULL && waterMonsInfo != NULL)
|
|
{
|
|
*isWaterMon = TRUE;
|
|
return waterMonsInfo->wildPokemon[ChooseWildMonIndex_WaterRock()].species;
|
|
}
|
|
// Either land or water Pokémon
|
|
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 != HEADER_NONE)
|
|
{
|
|
const struct WildPokemonInfo * waterMonsInfo = gWildMonHeaders[headerId].waterMonsInfo;
|
|
|
|
if (waterMonsInfo)
|
|
return waterMonsInfo->wildPokemon[ChooseWildMonIndex_WaterRock()].species;
|
|
}
|
|
return SPECIES_NONE;
|
|
}
|
|
|
|
bool8 UpdateRepelCounter(void)
|
|
{
|
|
u16 repelLureVar = VarGet(VAR_REPEL_STEP_COUNT);
|
|
u16 steps = REPEL_LURE_STEPS(repelLureVar);
|
|
bool32 isLure = IS_LAST_USED_LURE(repelLureVar);
|
|
|
|
if (InUnionRoom() == TRUE)
|
|
return FALSE;
|
|
|
|
if (gQuestLogState == QL_STATE_PLAYBACK)
|
|
return FALSE;
|
|
|
|
if (steps != 0)
|
|
{
|
|
steps--;
|
|
if (!isLure)
|
|
{
|
|
VarSet(VAR_REPEL_STEP_COUNT, steps);
|
|
if (steps == 0)
|
|
{
|
|
ScriptContext_SetupScript(EventScript_SprayWoreOff);
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VarSet(VAR_REPEL_STEP_COUNT, steps | REPEL_LURE_MASK);
|
|
if (steps == 0)
|
|
{
|
|
ScriptContext_SetupScript(EventScript_SprayWoreOff);
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static bool8 IsWildLevelAllowedByRepel(u8 wildLevel)
|
|
{
|
|
u8 i;
|
|
|
|
if (!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)
|
|
{
|
|
u16 ability;
|
|
|
|
if (GetMonData(&gPlayerParty[0], MON_DATA_SANITY_IS_EGG))
|
|
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 (gSpeciesInfo[wildMon[i].species].types[0] == type || gSpeciesInfo[wildMon[i].species].types[1] == type)
|
|
validIndexes[validMonCount++] = i;
|
|
}
|
|
|
|
if (validMonCount == 0 || validMonCount == numMon)
|
|
return FALSE;
|
|
|
|
*monIndex = validIndexes[Random() % validMonCount];
|
|
return TRUE;
|
|
}
|
|
|
|
static u8 GetMaxLevelOfSpeciesInWildTable(const struct WildPokemon *wildMon, u16 species, u8 area)
|
|
{
|
|
u8 i, maxLevel = 0, numMon = 0;
|
|
|
|
switch (area)
|
|
{
|
|
case WILD_AREA_LAND:
|
|
numMon = LAND_WILD_COUNT;
|
|
break;
|
|
case WILD_AREA_WATER:
|
|
numMon = WATER_WILD_COUNT;
|
|
break;
|
|
case WILD_AREA_ROCKS:
|
|
numMon = ROCK_WILD_COUNT;
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < numMon; i++)
|
|
{
|
|
if (wildMon[i].species == species && wildMon[i].maxLevel > maxLevel)
|
|
maxLevel = wildMon[i].maxLevel;
|
|
}
|
|
|
|
return maxLevel;
|
|
}
|
|
|
|
static bool8 TryGetAbilityInfluencedWildMonIndex(const struct WildPokemon *wildMon, u8 type, u16 ability, u8 *monIndex, u32 size)
|
|
{
|
|
if (GetMonData(&gPlayerParty[0], MON_DATA_SANITY_IS_EGG))
|
|
return FALSE;
|
|
else if (GetMonAbility(&gPlayerParty[0]) != ability)
|
|
return FALSE;
|
|
else if (Random() % 2 != 0)
|
|
return FALSE;
|
|
|
|
return TryGetRandomWildMonIndexByType(wildMon, type, size, monIndex);
|
|
}
|
|
|
|
static void ApplyFluteEncounterRateMod(u32 *encounterRate)
|
|
{
|
|
switch (GetFluteEncounterRateModType())
|
|
{
|
|
case 1:
|
|
*encounterRate += *encounterRate / 2;
|
|
break;
|
|
case 2:
|
|
*encounterRate = *encounterRate / 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static u8 GetFluteEncounterRateModType(void)
|
|
{
|
|
if (FlagGet(FLAG_SYS_WHITE_FLUTE_ACTIVE) == TRUE)
|
|
return 1;
|
|
else if (FlagGet(FLAG_SYS_BLACK_FLUTE_ACTIVE) == TRUE)
|
|
return 2;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static void ApplyCleanseTagEncounterRateMod(u32 *encounterRate)
|
|
{
|
|
if (IsLeadMonHoldingCleanseTag())
|
|
*encounterRate = *encounterRate * 2 / 3;
|
|
}
|
|
|
|
static bool8 IsLeadMonHoldingCleanseTag(void)
|
|
{
|
|
if (sWildEncounterData.leadMonHeldItem == ITEM_CLEANSE_TAG)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
void SeedWildEncounterRng(u16 seed)
|
|
{
|
|
sWildEncounterData.rngState = seed;
|
|
ResetEncounterRateModifiers();
|
|
}
|
|
|
|
static u16 WildEncounterRandom(void)
|
|
{
|
|
sWildEncounterData.rngState = ISO_RANDOMIZE2(sWildEncounterData.rngState);
|
|
return sWildEncounterData.rngState >> 16;
|
|
}
|
|
|
|
static u8 GetMapBaseEncounterCooldown(u8 encounterType)
|
|
{
|
|
u16 headerIdx = GetCurrentMapWildMonHeaderId();
|
|
if (headerIdx == HEADER_NONE)
|
|
return 0xFF;
|
|
if (encounterType == TILE_ENCOUNTER_LAND)
|
|
{
|
|
if (gWildMonHeaders[headerIdx].landMonsInfo == NULL)
|
|
return 0xFF;
|
|
if (gWildMonHeaders[headerIdx].landMonsInfo->encounterRate >= 80)
|
|
return 0;
|
|
if (gWildMonHeaders[headerIdx].landMonsInfo->encounterRate < 10)
|
|
return 8;
|
|
return 8 - (gWildMonHeaders[headerIdx].landMonsInfo->encounterRate / 10);
|
|
}
|
|
if (encounterType == TILE_ENCOUNTER_WATER)
|
|
{
|
|
if (gWildMonHeaders[headerIdx].waterMonsInfo == NULL)
|
|
return 0xFF;
|
|
if (gWildMonHeaders[headerIdx].waterMonsInfo->encounterRate >= 80)
|
|
return 0;
|
|
if (gWildMonHeaders[headerIdx].waterMonsInfo->encounterRate < 10)
|
|
return 8;
|
|
return 8 - (gWildMonHeaders[headerIdx].waterMonsInfo->encounterRate / 10);
|
|
}
|
|
return 0xFF;
|
|
}
|
|
|
|
void ResetEncounterRateModifiers(void)
|
|
{
|
|
sWildEncounterData.encounterRateBuff = 0;
|
|
sWildEncounterData.stepsSinceLastEncounter = 0;
|
|
}
|
|
|
|
static bool8 HandleWildEncounterCooldown(u32 currMetatileAttrs)
|
|
{
|
|
u8 encounterType = ExtractMetatileAttribute(currMetatileAttrs, METATILE_ATTRIBUTE_ENCOUNTER_TYPE);
|
|
u32 minSteps;
|
|
u32 encRate;
|
|
if (encounterType == TILE_ENCOUNTER_NONE)
|
|
return FALSE;
|
|
minSteps = GetMapBaseEncounterCooldown(encounterType);
|
|
if (minSteps == 0xFF)
|
|
return FALSE;
|
|
minSteps *= 256;
|
|
encRate = 5 * 256;
|
|
switch (GetFluteEncounterRateModType())
|
|
{
|
|
case 1:
|
|
minSteps -= minSteps / 2;
|
|
encRate += encRate / 2;
|
|
break;
|
|
case 2:
|
|
minSteps *= 2;
|
|
encRate /= 2;
|
|
break;
|
|
}
|
|
sWildEncounterData.leadMonHeldItem = GetMonData(&gPlayerParty[0], MON_DATA_HELD_ITEM);
|
|
if (IsLeadMonHoldingCleanseTag() == TRUE)
|
|
{
|
|
minSteps += minSteps / 3;
|
|
encRate -= encRate / 3;
|
|
}
|
|
switch (GetAbilityEncounterRateModType())
|
|
{
|
|
case 1:
|
|
minSteps *= 2;
|
|
encRate /= 2;
|
|
break;
|
|
case 2:
|
|
minSteps /= 2;
|
|
encRate *= 2;
|
|
break;
|
|
}
|
|
minSteps /= 256;
|
|
encRate /= 256;
|
|
if (sWildEncounterData.stepsSinceLastEncounter >= minSteps)
|
|
return TRUE;
|
|
sWildEncounterData.stepsSinceLastEncounter++;
|
|
if ((Random() % 100) < encRate)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
bool8 TryStandardWildEncounter(u32 currMetatileAttrs)
|
|
{
|
|
if (!HandleWildEncounterCooldown(currMetatileAttrs))
|
|
{
|
|
sWildEncounterData.prevMetatileBehavior = ExtractMetatileAttribute(currMetatileAttrs, METATILE_ATTRIBUTE_BEHAVIOR);
|
|
return FALSE;
|
|
}
|
|
else if (StandardWildEncounter(currMetatileAttrs, sWildEncounterData.prevMetatileBehavior) == TRUE)
|
|
{
|
|
sWildEncounterData.encounterRateBuff = 0;
|
|
sWildEncounterData.stepsSinceLastEncounter = 0;
|
|
sWildEncounterData.prevMetatileBehavior = ExtractMetatileAttribute(currMetatileAttrs, METATILE_ATTRIBUTE_BEHAVIOR);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
sWildEncounterData.prevMetatileBehavior = ExtractMetatileAttribute(currMetatileAttrs, METATILE_ATTRIBUTE_BEHAVIOR);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static void AddToWildEncounterRateBuff(u8 encounterRate)
|
|
{
|
|
if (REPEL_STEP_COUNT == 0)
|
|
sWildEncounterData.encounterRateBuff += encounterRate;
|
|
else
|
|
sWildEncounterData.encounterRateBuff = 0;
|
|
}
|
|
|
|
bool8 TryDoDoubleWildBattle(void)
|
|
{
|
|
if (GetSafariZoneFlag()
|
|
|| (B_DOUBLE_WILD_REQUIRE_2_MONS == TRUE && GetMonsStateToDoubles() != PLAYER_HAS_TWO_USABLE_MONS))
|
|
return FALSE;
|
|
else if (B_FLAG_FORCE_DOUBLE_WILD != 0 && FlagGet(B_FLAG_FORCE_DOUBLE_WILD))
|
|
return TRUE;
|
|
else if (B_DOUBLE_WILD_CHANCE != 0 && ((Random() % 100) + 1 <= B_DOUBLE_WILD_CHANCE))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
u8 ChooseHiddenMonIndex(void)
|
|
{
|
|
#ifdef ENCOUNTER_CHANCE_HIDDEN_MONS_TOTAL
|
|
u8 rand = Random() % ENCOUNTER_CHANCE_HIDDEN_MONS_TOTAL;
|
|
|
|
if (rand < ENCOUNTER_CHANCE_HIDDEN_MONS_SLOT_0)
|
|
return 0;
|
|
else if (rand >= ENCOUNTER_CHANCE_HIDDEN_MONS_SLOT_0 && rand < ENCOUNTER_CHANCE_HIDDEN_MONS_SLOT_1)
|
|
return 1;
|
|
else
|
|
return 2;
|
|
#else
|
|
return 0xFF;
|
|
#endif
|
|
}
|
|
|
|
bool32 MapHasNoEncounterData(void)
|
|
{
|
|
return (GetCurrentMapWildMonHeaderId() == HEADER_NONE);
|
|
}
|