From bf8c9609ae735d7188ad5418bad78de2727e41d9 Mon Sep 17 00:00:00 2001 From: ghoulslash <41651341+ghoulslash@users.noreply.github.com> Date: Fri, 17 Nov 2023 10:04:41 -0500 Subject: [PATCH] add battle pyramid wild reqs (#2581) * add battle pyramid wild reqs * fix comment * fix ice type round to match vanilla. move config to overworld * some fixes * BATTLE_PYRAMID_RANDOM_ENCOUNTERS define to FALSE defualt * fix GetSpeciesName * fix GetTotalBaseStat define, gEvolutionTable targetSpecies call * fix wildMons call * fix call to CheckBattlePyramidEvoRequirement * fix arg 2 in CheckBattlePyramidEvoRequirement * free->Free and fix evoItems ptr * move sBurningMoves * fix comment fix ice type round to match vanilla. move config to overworld some fixes BATTLE_PYRAMID_RANDOM_ENCOUNTERS define to FALSE defualt fix GetSpeciesName fix GetTotalBaseStat define, gEvolutionTable targetSpecies call fix wildMons call fix call to CheckBattlePyramidEvoRequirement fix arg 2 in CheckBattlePyramidEvoRequirement free->Free and fix evoItems ptr move sBurningMoves BATTLE_PYRAMID_RANDOM_ENCOUNTERS defaults to false * fix errors * initial movesCount, abilitiesCount --------- Co-authored-by: ghoulslash Co-authored-by: Bassoonian --- include/config/overworld.h | 2 + src/battle_pyramid.c | 210 +++++++++++++++- .../battle_pyramid_wild_requirements.h | 237 ++++++++++++++++++ 3 files changed, 447 insertions(+), 2 deletions(-) create mode 100644 src/data/battle_frontier/battle_pyramid_wild_requirements.h diff --git a/include/config/overworld.h b/include/config/overworld.h index 5002be3d28..e2ced7ccbf 100644 --- a/include/config/overworld.h +++ b/include/config/overworld.h @@ -31,4 +31,6 @@ #define OW_FLAG_NO_TRAINER_SEE 0 // If this flag is set, trainers will not battle the player unless they're talked to. #define OW_FLAG_NO_COLLISION 0 // If this flag is set, the player will be able to walk over tiles with collision. Mainly intended for debugging purposes. +#define BATTLE_PYRAMID_RANDOM_ENCOUNTERS FALSE // If set to TRUE, battle pyramid Pokemon will be generated randomly based on the round's challenge instead of hardcoded in src/data/battle_frontier/battle_pyramid_level_50_wild_mons.h (or open_level_wild_mons.h) + #endif // GUARD_CONFIG_OVERWORLD_H diff --git a/src/battle_pyramid.c b/src/battle_pyramid.c index ba3d620111..1f73ac5a9d 100644 --- a/src/battle_pyramid.c +++ b/src/battle_pyramid.c @@ -27,6 +27,7 @@ #include "overworld.h" #include "event_scripts.h" #include "graphics.h" +#include "wild_encounter.h" #include "constants/battle_frontier.h" #include "constants/battle_pyramid.h" #include "constants/event_objects.h" @@ -101,8 +102,13 @@ static bool8 TrySetPyramidObjectEventPositionAtCoords(bool8, u8, u8, u8 *, u8, u // Const rom data. #define ABILITY_RANDOM 2 // For wild mons data. -#include "data/battle_frontier/battle_pyramid_level_50_wild_mons.h" -#include "data/battle_frontier/battle_pyramid_open_level_wild_mons.h" +#if BATTLE_PYRAMID_RANDOM_ENCOUNTERS == TRUE + #include "data/battle_frontier/battle_pyramid_wild_requirements.h" +#else + #include "data/battle_frontier/battle_pyramid_level_50_wild_mons.h" + #include "data/battle_frontier/battle_pyramid_open_level_wild_mons.h" +#endif + static const struct PyramidFloorTemplate sPyramidFloorTemplates[] = { @@ -1340,6 +1346,205 @@ static void MarkPyramidTrainerAsBattled(u16 trainerId) gObjectEvents[gSelectedObjectEvent].initialCoords.y = gObjectEvents[gSelectedObjectEvent].currentCoords.y; } +#if BATTLE_PYRAMID_RANDOM_ENCOUNTERS == TRUE +// check if given species evolved from a specific evolutionary stone +// if nItems is passed as 0, it will check for any EVO_ITEM case +extern struct Evolution gEvolutionTable[][EVOS_PER_MON]; +static bool32 CheckBattlePyramidEvoRequirement(u16 species, const u16 *evoItems, u8 nItems) +{ + u32 i, j, k; + for (i = 0; i < NUM_SPECIES; i++) + { + for (j = 0; j < EVOS_PER_MON; j++) + { + if (gEvolutionTable[i][j].targetSpecies == species + && (gEvolutionTable[i][j].method == EVO_ITEM || gEvolutionTable[i][j].method == EVO_ITEM_MALE || gEvolutionTable[i][j].method == EVO_ITEM_FEMALE)) + { + if (nItems == 0) + { + // Any EVO_ITEM case will do + return TRUE; + } + else + { + // Otherwise, need to match specific set provided + for (k = 0; k < nItems; k++) { + if (gEvolutionTable[i][j].param == evoItems[k]) { + return TRUE; + } + } + } + } + } + } + return FALSE; +} + +extern u32 GetTotalBaseStat(u32 species); +void GenerateBattlePyramidWildMon(void) +{ + u8 name[POKEMON_NAME_LENGTH + 1]; + int i, j; + u32 id; + u32 lvl = gSaveBlock2Ptr->frontier.lvlMode; + u16 round = (gSaveBlock2Ptr->frontier.pyramidWinStreaks[lvl] / 7) % TOTAL_PYRAMID_ROUNDS; + const struct BattlePyramidRequirement *reqs = &sBattlePyramidRequirementsByRound[round]; + u16 species; + u32 bstLim; + u16 *moves = NULL; + u16 *abilities = NULL; + int moveCount = 0, abilityCount = 0; + + if (reqs->nMoves != 0) + moves = AllocZeroed(sizeof(u16) * reqs->nMoves); + + if (reqs->nAbilities != 0) + abilities = AllocZeroed(sizeof(u16) * reqs->nAbilities); + + if (round >= TOTAL_PYRAMID_ROUNDS) + round = TOTAL_PYRAMID_ROUNDS - 1; + + id = GetMonData(&gEnemyParty[0], MON_DATA_SPECIES, NULL) - 1; // index in table (0-11) -> higher index is lower probability + bstLim = 450 + (25*round) + (5*id); // higher BST limit for 'rarer' wild mon rolls + + while (1) + { + species = Random() % FORMS_START; + // check type + if (reqs->type != TYPE_MYSTERY && gSpeciesInfo[species].types[0] != reqs->type && gSpeciesInfo[species].types[1] != reqs->type) + continue; + + // check base stat total + if (GetTotalBaseStat(species) > bstLim) + continue; + + // check moves + if (reqs->nMoves != 0) + { + moveCount = 0; + // get list of moves that can be learned + for (i = 0; i < reqs->nMoves; i++) + { + if (CanLearnTeachableMove(species, reqs->moves[i])) + { + moves[moveCount] = reqs->moves[i]; + moveCount++; + } + } + if (moveCount == 0) + continue; + } + + // check abilities + if (reqs->nAbilities != 0) + { + abilityCount = 0; + // get list of moves that can be learned + for (i = 0; i < reqs->nAbilities; i++) + { + for (j = 0; j < NUM_ABILITY_SLOTS; j++) + { + if (gSpeciesInfo[species].abilities[j] == reqs->abilities[i]) + { + abilities[abilityCount] = reqs->abilities[i]; + abilityCount++; + break; + } + } + } + if (abilityCount == 0) + continue; + } + // check evos + if (reqs->evoItems[0] != 0 && !CheckBattlePyramidEvoRequirement(species, reqs->evoItems, reqs->nEvoItems)) + continue; + + // we found a species we can use! + break; + } + + // Set species, name + SetMonData(&gEnemyParty[0], MON_DATA_SPECIES, &species); + StringCopy(name, gSpeciesNames[species]); + SetMonData(&gEnemyParty[0], MON_DATA_NICKNAME, &name); + + // set level + if (lvl != FRONTIER_LVL_50) + { + lvl = SetFacilityPtrsGetLevel(); + lvl -= (5 + (Random() % (TOTAL_PYRAMID_ROUNDS - round)/2)); + } + else + { + lvl = 50 - (5 + (Random() % (TOTAL_PYRAMID_ROUNDS - round)/4)); + } + SetMonData(&gEnemyParty[0], + MON_DATA_EXP, + &gExperienceTables[gSpeciesInfo[species].growthRate][lvl]); + + // Give initial moves and replace one with desired move + GiveBoxMonInitialMoveset(&gEnemyParty[0].box); + if (moves != NULL) + { + // get a random move to give + i = 0; + while (1) + { + id = moves[Random() % moveCount]; + if (!MonKnowsMove(&gEnemyParty[0], id)) + { + // replace random move + SetMonData(&gEnemyParty[0], MON_DATA_MOVE1 + Random() % MAX_MON_MOVES, &id); + break; + } + i++; + if (i == 20) + break; + } + Free(moves); + } + + // Initialize a random ability num + if (gSpeciesInfo[species].abilities[1]) + { + i = GetMonData(&gEnemyParty[0], MON_DATA_PERSONALITY, NULL) % 2; + SetMonData(&gEnemyParty[0], MON_DATA_ABILITY_NUM, &i); + } + else + { + i = 0; + SetMonData(&gEnemyParty[0], MON_DATA_ABILITY_NUM, &i); + } + + // Try to replace with desired ability + if (abilities != NULL) + { + i = 0; + while (1) + { + id = abilities[Random() % abilityCount]; + for (j = 0; j < NUM_ABILITY_SLOTS; j++) + { + if (id == gSpeciesInfo[species].abilities[j]) + { + // Set this ability num + SetMonData(&gEnemyParty[0], MON_DATA_ABILITY_NUM, &id); + } + } + } + Free(abilities); + } + + if (gSaveBlock2Ptr->frontier.pyramidWinStreaks[gSaveBlock2Ptr->frontier.lvlMode] >= 140) + { + id = (Random() % 17) + 15; + for (i = 0; i < NUM_STATS; i++) + SetMonData(&gEnemyParty[0], MON_DATA_HP_IV + i, &id); + } + + CalculateMonStats(&gEnemyParty[0]); +} +#else void GenerateBattlePyramidWildMon(void) { u8 name[POKEMON_NAME_LENGTH + 1]; @@ -1412,6 +1617,7 @@ void GenerateBattlePyramidWildMon(void) } CalculateMonStats(&gEnemyParty[0]); } +#endif u8 GetPyramidRunMultiplier(void) { diff --git a/src/data/battle_frontier/battle_pyramid_wild_requirements.h b/src/data/battle_frontier/battle_pyramid_wild_requirements.h new file mode 100644 index 0000000000..1b9e359f1f --- /dev/null +++ b/src/data/battle_frontier/battle_pyramid_wild_requirements.h @@ -0,0 +1,237 @@ +#if BATTLE_PYRAMID_RANDOM_ENCOUNTERS == TRUE + +#include "constants/abilities.h" +#include "constants/battle_move_effects.h" + +struct BattlePyramidRequirement { + const u16 *moves; /* use moves instead of effects so we don't need to find moves with said effect in our loop */ + u16 abilities[10]; + u8 nAbilities; + u8 type; + u8 nMoves; + const u16 *evoItems; + u8 nEvoItems; +}; + +// EFFECT_PARALYZE, EFFECT_PARALYZE_HIT (30% or more) +static const u16 sParalyzingMoves[] = { + //MOVE_THUNDER_PUNCH, + MOVE_BODY_SLAM, + MOVE_STUN_SPORE, + //MOVE_THUNDER_SHOCK, + //MOVE_THUNDERBOLT, + MOVE_THUNDER_WAVE, + MOVE_LICK, + MOVE_GLARE, + MOVE_ZAP_CANNON, + MOVE_SPARK, + MOVE_DRAGON_BREATH, + MOVE_FORCE_PALM, + MOVE_DISCHARGE, + //MOVE_BOLT_STRIKE, + MOVE_NUZZLE, + MOVE_SPLISHY_SPLASH, + MOVE_BUZZY_BUZZ, + MOVE_COMBAT_TORQUE, +}; + +// EFFECT_POISON_HIT (30% or more), EFFECT_POISON, EFFECT_POISON_FANG, EFFECT_TOXIC, EFFECT_TOXIC_THREAD +static const u16 sPoisoningMoves[] = { + MOVE_POISON_STING, + //MOVE_TWINEEDLE, + MOVE_SMOG, + MOVE_SLUDGE, + MOVE_SLUDGE_BOMB, + //MOVE_POISON_TAIL, + MOVE_POISON_JAB, + //MOVE_CROSS_POISON, + MOVE_GUNK_SHOT, + //MOVE_SLUDGE_WAVE, + MOVE_NOXIOUS_TORQUE, + //MOVE_ACID, + MOVE_POISON_POWDER, + MOVE_TOXIC, + MOVE_POISON_GAS, + MOVE_POISON_FANG, + MOVE_BANEFUL_BUNKER, + MOVE_TOXIC_THREAD, +}; + +// EFFECT_BURN_HIT, EFFECT_WILL_O_WISP +static const u16 sBurningMoves[] = { + MOVE_WILL_O_WISP, + //MOVE_EMBER, + //MOVE_FLAMETHROWER, + //MOVE_FIRE_BLAST, + //MOVE_HEAT_WAVE, + //MOVE_BLAZE_KICK, + MOVE_LAVA_PLUME, + MOVE_SCALD, + MOVE_INFERNO, + MOVE_SEARING_SHOT, + MOVE_BLUE_FLARE, + MOVE_STEAM_ERUPTION, + MOVE_SIZZLY_SLIDE, + //MOVE_PYRO_BALL, + MOVE_BURNING_JEALOUSY, + MOVE_SCORCHING_SANDS, + MOVE_SANDSEAR_STORM, + MOVE_BLAZING_TORQUE, +}; + +// EFFECT_FREEZE, EFFECT_FREEZE_HIT +static const u16 sFrostbiteMoves[] = { + MOVE_ICE_PUNCH, + MOVE_ICE_BEAM, + MOVE_BLIZZARD, + MOVE_POWDER_SNOW, + MOVE_FREEZING_GLARE, +}; + +// EFFECT_GRUDGE, EFFECT_SPITE, EFFECT_EERIE_SPELL +static const u16 sPPReducingMoves[] = { + MOVE_GRUDGE, + MOVE_SPITE, + MOVE_EERIE_SPELL, +}; + +// EFFECT_EXPLOSION +static const u16 sExplosionMoves[] = { + MOVE_SELF_DESTRUCT, + MOVE_EXPLOSION, + MOVE_MISTY_EXPLOSION, +}; + +// EFFECT_RAIN_DANCE, EFFECT_SANDSTORM, EFFECT_HAIL, EFFECT_SUNNY_DAY, +static const u16 sWeatherChangingMoves[] = { + MOVE_RAIN_DANCE, + MOVE_SANDSTORM, + MOVE_HAIL, + MOVE_SUNNY_DAY, +}; + +// EFFECT_RECHARGE, EFFECT_RECOIL_33 +static const u16 sPowerfulNormalMoves[] = { + MOVE_HYPER_BEAM, + MOVE_GIGA_IMPACT, + MOVE_THRASH, + MOVE_BODY_SLAM, + MOVE_DOUBLE_EDGE, +}; + +static const u16 sEvoItems[] = {ITEM_FIRE_STONE, ITEM_WATER_STONE, ITEM_THUNDER_STONE}; + +static const struct BattlePyramidRequirement sBattlePyramidRequirementsByRound[] = { + [0] = /* pokemon with moves that paraylze */ + { + .type = TYPE_MYSTERY, // no type limitation + .moves = sParalyzingMoves, + .nMoves = NELEMS(sParalyzingMoves), + .abilities = { ABILITY_STATIC }, + .nAbilities = 1, + }, + [1] = /* pokemon with moves that poison */ + { + .type = TYPE_MYSTERY, + .moves = sPoisoningMoves, + .nMoves = NELEMS(sPoisoningMoves), + .abilities = { ABILITY_POISON_POINT }, + }, + [2] = /* Pokemon with moves that burn */ + { + .type = TYPE_MYSTERY, + .moves = sBurningMoves, + .nMoves = NELEMS(sBurningMoves), + .abilities = { ABILITY_FLAME_BODY }, + .nAbilities = 1, + }, + [3] = /* pokemon with moves that waste PP */ + { + .type = TYPE_MYSTERY, + .moves = sPPReducingMoves, + .nMoves = NELEMS(sPPReducingMoves), + .abilities = { ABILITY_PRESSURE }, + .nAbilities = 1, + }, + [4] = /* pokemon with Levitate */ + { + .type = TYPE_MYSTERY, + .abilities = { ABILITY_LEVITATE }, + .nAbilities = 1, + }, + [5] = /* pokemon with trapping abilities */ + { + .type = TYPE_MYSTERY, + .abilities = { ABILITY_SHADOW_TAG, ABILITY_ARENA_TRAP }, // TODO magnet pull? + .nAbilities = 2, + }, + [6] = /* ice types */ + { + .type = TYPE_ICE, + }, + + [7] = /* pokemon with explosion effects */ + { + .type = TYPE_MYSTERY, + .moves = sExplosionMoves, + .nMoves = NELEMS(sExplosionMoves), + }, + [8] = /* psychic types */ + { + .type = TYPE_PSYCHIC, + }, + [9] = /* rock types */ + { + .type = TYPE_ROCK, + }, + [10] = /* fighting types */ + { + .type = TYPE_FIGHTING, + }, + [11] = /* pokemon with weather-altering effects */ + { + .type = TYPE_MYSTERY, + .moves = sWeatherChangingMoves, + .nMoves = NELEMS(sWeatherChangingMoves), + .abilities = { ABILITY_SAND_SPIT, ABILITY_DRIZZLE, ABILITY_SNOW_WARNING, ABILITY_DROUGHT, ABILITY_SAND_STREAM }, + .nAbilities = 5, + }, + [12] = /* bug types */ + { + .type = TYPE_BUG, + }, + [13] = /* dark types */ + { + .type = TYPE_DARK, + }, + [14] = /* water types */ + { + .type = TYPE_WATER, + }, + [15] = /* ghost types */ + { + .type = TYPE_GHOST, + }, + [16] = /* steel types */ + { + .type = TYPE_STEEL, + }, + [17] = /* flying/dragon types */ + { + .type = TYPE_DRAGON, + }, + [18] = /* evolve via water/thunder/fire stone */ + { + .type = TYPE_MYSTERY, + .evoItems = sEvoItems, + .nEvoItems = 3, + }, + [19] = /* normal with powerful moves */ + { + .type = TYPE_NORMAL, + .moves = sPowerfulNormalMoves, + .nMoves = NELEMS(sPowerfulNormalMoves), + }, +}; + +#endif