diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/wild_encounter.c | 483 |
1 files changed, 483 insertions, 0 deletions
diff --git a/src/wild_encounter.c b/src/wild_encounter.c new file mode 100644 index 000000000..7d4cc7794 --- /dev/null +++ b/src/wild_encounter.c @@ -0,0 +1,483 @@ +#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" + +EWRAM_DATA u8 sWildEncountersDisabled = 0; +EWRAM_DATA u32 sFeebasRngValue = 0; + +#define NUM_FEEBAS_SPOTS 6 + +extern const u16 gRoute119WaterTileData[]; + +// this file's functions +u16 FeebasRandom(void); +void FeebasSeedRng(u16 seed); +bool8 IsWildLevelAllowedByRepel(u8 level); +void ApplyFluteEncounterRateMod(u32 *encRate); +void ApplyCleanseTagEncounterRateMod(u32 *encRate); +bool8 TryGetAbilityInfluencedWildMonIndex(const struct WildPokemon *wildMon, u8 type, u8 ability, u8 *monIndex); +bool8 IsAbilityAllowingEncounter(u8 level); + +void DisableWildEncounters(bool8 disabled) +{ + sWildEncountersDisabled = disabled; +} + +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; +} + +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; +} + +u16 FeebasRandom(void) +{ + sFeebasRngValue = 12345 + 0x41C64E6D * sFeebasRngValue; + return sFeebasRngValue >> 16; +} + +void FeebasSeedRng(u16 seed) +{ + sFeebasRngValue = seed; +} + +u8 ChooseWildMonIndex_Land(void) +{ + u8 rand = Random() % 100; + + if (rand < 20) // 20% chance + return 0; + if (rand >= 20 && rand < 40) // 20% chance + return 1; + if (rand >= 40 && rand < 50) // 10% chance + return 2; + if (rand >= 50 && rand < 60) // 10% chance + return 3; + if (rand >= 60 && rand < 70) // 10% chance + return 4; + if (rand >= 70 && rand < 80) // 10% chance + return 5; + if (rand >= 80 && rand < 85) // 5% chance + return 6; + if (rand >= 85 && rand < 90) // 5% chance + return 7; + if (rand >= 90 && rand < 94) // 4% chance + return 8; + if (rand >= 94 && rand < 98) // 4% chance + return 9; + if (rand == 98) // 1% chance + return 10; + else // 1% chance + return 11; +} + +u8 ChooseWildMonIndex_WaterRock(void) +{ + u8 rand = Random() % 100; + + if (rand < 60) // 60% chance + return 0; + if (rand >= 60 && rand < 90) // 30% chance + return 1; + if (rand >= 90 && rand < 95) // 5% chance + return 2; + if (rand >= 95 && rand < 99) // 4% chance + return 3; + else // 1% chance + return 4; +} + +enum +{ + OLD_ROD, + GOOD_ROD, + SUPER_ROD +}; + +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; +} + +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; +} + +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; +} + +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; +} + +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 + +bool8 TryGenerateWildMon(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; +} + +u16 GenerateFishingWildMon(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; +} + +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; +} + +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; +} + +bool8 DoWildEncounterRateDiceRoll(u16 encounterRate) +{ + if (Random() % 2880 < encounterRate) + return TRUE; + else + return FALSE; +} + +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); +} |