#include "global.h" #include "battle.h" #include "battle_controllers.h" #include "abilities.h" #include "moves.h" #include "pokemon.h" #include "species.h" #include "rng.h" #include "util.h" extern u8 gActiveBank; extern u8 gAbsentBankFlags; extern u32 gBattleTypeFlags; extern u32 gStatuses3[BATTLE_BANKS_COUNT]; extern struct BattlePokemon gBattleMons[BATTLE_BANKS_COUNT]; extern u16 gBattlePartyID[BATTLE_BANKS_COUNT]; extern u16 gUnknown_02024250[BATTLE_BANKS_COUNT]; extern u8 gUnknown_02024270[BATTLE_BANKS_COUNT]; extern u16 gDynamicBasePower; extern u8 gBattleMoveFlags; extern u8 gCritMultiplier; extern s32 gBattleMoveDamage; extern const struct BattleMove gBattleMoves[]; extern const struct BaseStats gBaseStats[]; extern const u8 gTypeEffectiveness[]; // this file's functions bool8 HasSuperEffectiveMoveAgainstOpponents(bool8 noRng); bool8 FindMonWithFlagsAndSuperEffective(u8 flags, u8 moduloPercent); bool8 ShouldUseItem(void); bool8 ShouldSwitchIfPerishSong(void) { if (gStatuses3[gActiveBank] & STATUS3_PERISH_SONG && gDisableStructs[gActiveBank].perishSong1 == 0) { *(gBattleStruct->field_294 + gActiveBank) = 6; EmitCmd_x21(1, 2, 0); return TRUE; } return FALSE; } bool8 ShouldSwitchIfWonderGuard(void) { u8 opposingIdentity; u8 opposingBank; u8 moveFlags; s32 i, j; s32 firstId; s32 lastId; // + 1 struct Pokemon *party = NULL; u16 move; if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) return FALSE; opposingIdentity = GetBankIdentity(gActiveBank) ^ BIT_SIDE; if (gBattleMons[GetBankByIdentity(opposingIdentity)].ability != ABILITY_WONDER_GUARD) return FALSE; // check if pokemon has a super effective move for (opposingBank = GetBankByIdentity(opposingIdentity), i = 0; i < 4; i++) { move = gBattleMons[gActiveBank].moves[i]; if (move == MOVE_NONE) continue; moveFlags = AI_TypeCalc(move, gBattleMons[opposingBank].species, gBattleMons[opposingBank].ability); if (moveFlags & MOVESTATUS_SUPEREFFECTIVE) return FALSE; } // get party information if (gBattleTypeFlags & (BATTLE_TYPE_TWO_OPPONENTS | BATTLE_TYPE_x800000)) { if ((gActiveBank & BIT_MON) == 0) firstId = 0, lastId = 3; else firstId = 3, lastId = 6; } else { firstId = 0, lastId = 6; } if (GetBankSide(gActiveBank) == SIDE_PLAYER) party = gPlayerParty; else party = gEnemyParty; // find a pokemon in the party that has a super effective move for (i = firstId; i < lastId; i++) { if (GetMonData(&party[i], MON_DATA_HP) == 0) continue; if (GetMonData(&party[i], MON_DATA_SPECIES2) == SPECIES_NONE) continue; if (GetMonData(&party[i], MON_DATA_SPECIES2) == SPECIES_EGG) continue; if (i == gBattlePartyID[gActiveBank]) continue; GetMonData(&party[i], MON_DATA_SPECIES); // unused return value GetMonData(&party[i], MON_DATA_ALT_ABILITY); // unused return value for (opposingBank = GetBankByIdentity(opposingIdentity), j = 0; j < 4; j++) { move = GetMonData(&party[i], MON_DATA_MOVE1 + j); if (move == MOVE_NONE) continue; moveFlags = AI_TypeCalc(move, gBattleMons[opposingBank].species, gBattleMons[opposingBank].ability); if (moveFlags & MOVESTATUS_SUPEREFFECTIVE && Random() % 3 < 2) { // we found a mon *(gBattleStruct->field_294 + gActiveBank) = i; EmitCmd_x21(1, 2, 0); return TRUE; } } } return FALSE; // at this point there is not a single pokemon in the party that has a super effective move against a pokemon with wonder guard } bool8 FindMonThatAbsorbsOpponentsMove(void) { u8 bankIn1, bankIn2; u8 absorbingTypeAbility; s32 firstId; s32 lastId; // + 1 struct Pokemon *party; s32 i; if (HasSuperEffectiveMoveAgainstOpponents(TRUE) && Random() % 3 != 0) return FALSE; if (gUnknown_02024250[gActiveBank] == 0) return FALSE; if (gUnknown_02024250[gActiveBank] == 0xFFFF) return FALSE; if (gBattleMoves[gUnknown_02024250[gActiveBank]].power == 0) return FALSE; if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) { bankIn1 = gActiveBank; if (gAbsentBankFlags & gBitTable[GetBankByIdentity(GetBankIdentity(gActiveBank) ^ BIT_MON)]) bankIn2 = gActiveBank; else bankIn2 = GetBankByIdentity(GetBankIdentity(gActiveBank) ^ BIT_MON); } else { bankIn1 = gActiveBank; bankIn2 = gActiveBank; } if (gBattleMoves[gUnknown_02024250[gActiveBank]].type == TYPE_FIRE) absorbingTypeAbility = ABILITY_FLASH_FIRE; else if (gBattleMoves[gUnknown_02024250[gActiveBank]].type == TYPE_WATER) absorbingTypeAbility = ABILITY_WATER_ABSORB; else if (gBattleMoves[gUnknown_02024250[gActiveBank]].type == TYPE_ELECTRIC) absorbingTypeAbility = ABILITY_VOLT_ABSORB; else return FALSE; if (gBattleMons[gActiveBank].ability == absorbingTypeAbility) return FALSE; if (gBattleTypeFlags & (BATTLE_TYPE_TWO_OPPONENTS | BATTLE_TYPE_x800000)) { if ((gActiveBank & BIT_MON) == 0) firstId = 0, lastId = 3; else firstId = 3, lastId = 6; } else { firstId = 0, lastId = 6; } if (GetBankSide(gActiveBank) == SIDE_PLAYER) party = gPlayerParty; else party = gEnemyParty; for (i = firstId; i < lastId; i++) { u16 species; u8 monAbility; if (GetMonData(&party[i], MON_DATA_HP) == 0) continue; if (GetMonData(&party[i], MON_DATA_SPECIES2) == SPECIES_NONE) continue; if (GetMonData(&party[i], MON_DATA_SPECIES2) == SPECIES_EGG) continue; if (i == gBattlePartyID[bankIn1]) continue; if (i == gBattlePartyID[bankIn2]) continue; if (i == *(gBattleStruct->field_5C + bankIn1)) continue; if (i == *(gBattleStruct->field_5C + bankIn2)) continue; species = GetMonData(&party[i], MON_DATA_SPECIES); if (GetMonData(&party[i], MON_DATA_ALT_ABILITY) != 0) monAbility = gBaseStats[species].ability2; else monAbility = gBaseStats[species].ability1; if (absorbingTypeAbility == monAbility && Random() & 1) { // we found a mon *(gBattleStruct->field_294 + gActiveBank) = i; EmitCmd_x21(1, 2, 0); return TRUE; } } return FALSE; } bool8 ShouldSwitchIfNaturalCure(void) { if (!(gBattleMons[gActiveBank].status1 & STATUS_SLEEP)) return FALSE; if (gBattleMons[gActiveBank].ability != ABILITY_NATURAL_CURE) return FALSE; if (gBattleMons[gActiveBank].hp < gBattleMons[gActiveBank].maxHP / 2) return FALSE; if ((gUnknown_02024250[gActiveBank] == 0 || gUnknown_02024250[gActiveBank] == 0xFFFF) && Random() & 1) { *(gBattleStruct->field_294 + gActiveBank) = 6; EmitCmd_x21(1, 2, 0); return TRUE; } else if (gBattleMoves[gUnknown_02024250[gActiveBank]].power == 0 && Random() & 1) { *(gBattleStruct->field_294 + gActiveBank) = 6; EmitCmd_x21(1, 2, 0); return TRUE; } if (FindMonWithFlagsAndSuperEffective(MOVESTATUS_NOTAFFECTED, 1)) return TRUE; if (FindMonWithFlagsAndSuperEffective(MOVESTATUS_NOTVERYEFFECTIVE, 1)) return TRUE; if (Random() & 1) { *(gBattleStruct->field_294 + gActiveBank) = 6; EmitCmd_x21(1, 2, 0); return TRUE; } return FALSE; } bool8 HasSuperEffectiveMoveAgainstOpponents(bool8 noRng) { u8 opposingIdentity; u8 opposingBank; s32 i; u8 moveFlags; u16 move; opposingIdentity = GetBankIdentity(gActiveBank) ^ BIT_SIDE; opposingBank = GetBankByIdentity(opposingIdentity); if (!(gAbsentBankFlags & gBitTable[opposingBank])) { for (i = 0; i < 4; i++) { move = gBattleMons[gActiveBank].moves[i]; if (move == MOVE_NONE) continue; moveFlags = AI_TypeCalc(move, gBattleMons[opposingBank].species, gBattleMons[opposingBank].ability); if (moveFlags & MOVESTATUS_SUPEREFFECTIVE) { if (noRng) return TRUE; if (Random() % 10 != 0) return TRUE; } } } if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) return FALSE; opposingBank = GetBankByIdentity(opposingIdentity ^ BIT_MON); if (!(gAbsentBankFlags & gBitTable[opposingBank])) { for (i = 0; i < 4; i++) { move = gBattleMons[gActiveBank].moves[i]; if (move == MOVE_NONE) continue; moveFlags = AI_TypeCalc(move, gBattleMons[opposingBank].species, gBattleMons[opposingBank].ability); if (moveFlags & MOVESTATUS_SUPEREFFECTIVE) { if (noRng) return TRUE; if (Random() % 10 != 0) return TRUE; } } } return FALSE; } bool8 AreStatsRaised(void) { u8 buffedStatsValue = 0; s32 i; for (i = 0; i < BATTLE_STATS_NO; i++) { if (gBattleMons[gActiveBank].statStages[i] > 6) buffedStatsValue += gBattleMons[gActiveBank].statStages[i] - 6; } return (buffedStatsValue > 3); } bool8 FindMonWithFlagsAndSuperEffective(u8 flags, u8 moduloPercent) { u8 bankIn1, bankIn2; s32 firstId; s32 lastId; // + 1 struct Pokemon *party; s32 i, j; u16 move; u8 moveFlags; if (gUnknown_02024250[gActiveBank] == 0) return FALSE; if (gUnknown_02024250[gActiveBank] == 0xFFFF) return FALSE; if (gUnknown_02024270[gActiveBank] == 0xFF) return FALSE; if (gBattleMoves[gUnknown_02024250[gActiveBank]].power == 0) return FALSE; if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) { bankIn1 = gActiveBank; if (gAbsentBankFlags & gBitTable[GetBankByIdentity(GetBankIdentity(gActiveBank) ^ BIT_MON)]) bankIn2 = gActiveBank; else bankIn2 = GetBankByIdentity(GetBankIdentity(gActiveBank) ^ BIT_MON); } else { bankIn1 = gActiveBank; bankIn2 = gActiveBank; } if (gBattleTypeFlags & (BATTLE_TYPE_TWO_OPPONENTS | BATTLE_TYPE_x800000)) { if ((gActiveBank & BIT_MON) == 0) firstId = 0, lastId = 3; else firstId = 3, lastId = 6; } else { firstId = 0, lastId = 6; } if (GetBankSide(gActiveBank) == SIDE_PLAYER) party = gPlayerParty; else party = gEnemyParty; for (i = firstId; i < lastId; i++) { u16 species; u8 monAbility; if (GetMonData(&party[i], MON_DATA_HP) == 0) continue; if (GetMonData(&party[i], MON_DATA_SPECIES2) == SPECIES_NONE) continue; if (GetMonData(&party[i], MON_DATA_SPECIES2) == SPECIES_EGG) continue; if (i == gBattlePartyID[bankIn1]) continue; if (i == gBattlePartyID[bankIn2]) continue; if (i == *(gBattleStruct->field_5C + bankIn1)) continue; if (i == *(gBattleStruct->field_5C + bankIn2)) continue; species = GetMonData(&party[i], MON_DATA_SPECIES); if (GetMonData(&party[i], MON_DATA_ALT_ABILITY) != 0) monAbility = gBaseStats[species].ability2; else monAbility = gBaseStats[species].ability1; moveFlags = AI_TypeCalc(gUnknown_02024250[gActiveBank], species, monAbility); if (moveFlags & flags) { bankIn1 = gUnknown_02024270[gActiveBank]; for (j = 0; j < 4; j++) { move = GetMonData(&party[i], MON_DATA_MOVE1 + j); if (move == 0) continue; moveFlags = AI_TypeCalc(move, gBattleMons[bankIn1].species, gBattleMons[bankIn1].ability); if (moveFlags & MOVESTATUS_SUPEREFFECTIVE && Random() % moduloPercent == 0) { *(gBattleStruct->field_294 + gActiveBank) = i; EmitCmd_x21(1, 2, 0); return TRUE; } } } } return FALSE; } bool8 ShouldSwitch(void) { u8 bankIn1, bankIn2; u8 *activeBankPtr; // needed to match s32 firstId; s32 lastId; // + 1 struct Pokemon *party; s32 i; s32 availableToSwitch; if (gBattleMons[*(activeBankPtr = &gActiveBank)].status2 & (STATUS2_WRAPPED | STATUS2_ESCAPE_PREVENTION)) return FALSE; if (gStatuses3[gActiveBank] & STATUS3_ROOTED) return FALSE; if (AbilityBattleEffects(ABILITYEFFECT_CHECK_OTHER_SIDE, gActiveBank, ABILITY_SHADOW_TAG, 0, 0)) return FALSE; if (AbilityBattleEffects(ABILITYEFFECT_CHECK_OTHER_SIDE, gActiveBank, ABILITY_ARENA_TRAP, 0, 0)) return FALSE; // misses the flying or levitate check if (AbilityBattleEffects(ABILITYEFFECT_FIELD_SPORT, 0, ABILITY_MAGNET_PULL, 0, 0)) { if (gBattleMons[gActiveBank].type1 == TYPE_STEEL) return FALSE; if (gBattleMons[gActiveBank].type2 == TYPE_STEEL) return FALSE; } if (gBattleTypeFlags & BATTLE_TYPE_ARENA) return FALSE; availableToSwitch = 0; if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) { bankIn1 = *activeBankPtr; if (gAbsentBankFlags & gBitTable[GetBankByIdentity(GetBankIdentity(*activeBankPtr) ^ BIT_MON)]) bankIn2 = *activeBankPtr; else bankIn2 = GetBankByIdentity(GetBankIdentity(*activeBankPtr) ^ BIT_MON); } else { bankIn1 = *activeBankPtr; bankIn2 = *activeBankPtr; } if (gBattleTypeFlags & (BATTLE_TYPE_TWO_OPPONENTS | BATTLE_TYPE_x800000)) { if ((gActiveBank & BIT_MON) == 0) firstId = 0, lastId = 3; else firstId = 3, lastId = 6; } else { firstId = 0, lastId = 6; } if (GetBankSide(gActiveBank) == SIDE_PLAYER) party = gPlayerParty; else party = gEnemyParty; for (i = firstId; i < lastId; i++) { if (GetMonData(&party[i], MON_DATA_HP) == 0) continue; if (GetMonData(&party[i], MON_DATA_SPECIES2) == SPECIES_NONE) continue; if (GetMonData(&party[i], MON_DATA_SPECIES2) == SPECIES_EGG) continue; if (i == gBattlePartyID[bankIn1]) continue; if (i == gBattlePartyID[bankIn2]) continue; if (i == *(gBattleStruct->field_5C + bankIn1)) continue; if (i == *(gBattleStruct->field_5C + bankIn2)) continue; availableToSwitch++; } if (availableToSwitch == 0) return FALSE; if (ShouldSwitchIfPerishSong()) return TRUE; if (ShouldSwitchIfWonderGuard()) return TRUE; if (FindMonThatAbsorbsOpponentsMove()) return TRUE; if (ShouldSwitchIfNaturalCure()) return TRUE; if (HasSuperEffectiveMoveAgainstOpponents(FALSE)) return FALSE; if (AreStatsRaised()) return FALSE; if (FindMonWithFlagsAndSuperEffective(MOVESTATUS_NOTAFFECTED, 2) || FindMonWithFlagsAndSuperEffective(MOVESTATUS_NOTVERYEFFECTIVE, 3)) return TRUE; return FALSE; } void AI_TrySwitchOrUseItem(void) { struct Pokemon *party; u8 bankIn1, bankIn2; s32 firstId; s32 lastId; // + 1 u8 bankIdentity = GetBankIdentity(gActiveBank); if (GetBankSide(gActiveBank) == SIDE_PLAYER) party = gPlayerParty; else party = gEnemyParty; if (gBattleTypeFlags & BATTLE_TYPE_TRAINER) { if (ShouldSwitch()) { if (*(gBattleStruct->field_294 + gActiveBank) == 6) { s32 monToSwitchId = GetMostSuitableMonToSwitchInto(); if (monToSwitchId == 6) { if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) { bankIn1 = GetBankByIdentity(bankIdentity); bankIn2 = bankIn1; } else { bankIn1 = GetBankByIdentity(bankIdentity); bankIn2 = GetBankByIdentity(bankIdentity ^ BIT_MON); } if (gBattleTypeFlags & (BATTLE_TYPE_TWO_OPPONENTS | BATTLE_TYPE_x800000)) { if ((gActiveBank & BIT_MON) == 0) firstId = 0, lastId = 3; else firstId = 3, lastId = 6; } else { firstId = 0, lastId = 6; } for (monToSwitchId = firstId; monToSwitchId < lastId; monToSwitchId++) { if (GetMonData(&party[monToSwitchId], MON_DATA_HP) == 0) continue; if (monToSwitchId == gBattlePartyID[bankIn1]) continue; if (monToSwitchId == gBattlePartyID[bankIn2]) continue; if (monToSwitchId == *(gBattleStruct->field_5C + bankIn1)) continue; if (monToSwitchId == *(gBattleStruct->field_5C + bankIn2)) continue; break; } } *(gBattleStruct->field_294 + gActiveBank) = monToSwitchId; } *(gBattleStruct->field_5C + gActiveBank) = *(gBattleStruct->field_294 + gActiveBank); return; } else if (ShouldUseItem()) { return; } } EmitCmd_x21(1, 0, (gActiveBank ^ BIT_SIDE) << 8); } #define TYPE_FORESIGHT 0xFE #define TYPE_ENDTABLE 0xFF static void ModulateByTypeEffectiveness(u8 atkType, u8 defType1, u8 defType2, u8 *var) { s32 i = 0; while (gTypeEffectiveness[i] != TYPE_ENDTABLE) { if (gTypeEffectiveness[i] == TYPE_FORESIGHT) { i += 3; continue; } else if (gTypeEffectiveness[i] == atkType) { // check type1 if (gTypeEffectiveness[i + 1] == defType1) *var = (*var * gTypeEffectiveness[i + 2]) / 10; // check type2 if (gTypeEffectiveness[i + 1] == defType2 && defType1 != defType2) *var = (*var * gTypeEffectiveness[i + 2]) / 10; } i += 3; } } u8 GetMostSuitableMonToSwitchInto(void) { u8 opposingBank; u8 bestDmg; // note : should be changed to u32 for obvious reasons u8 bestMonId; u8 bankIn1, bankIn2; s32 firstId; s32 lastId; // + 1 struct Pokemon *party; s32 i, j; u8 invalidMons; u16 move; if (*(gBattleStruct->field_5C + gActiveBank) != 6) return *(gBattleStruct->field_5C + gActiveBank); if (gBattleTypeFlags & BATTLE_TYPE_ARENA) return gBattlePartyID[gActiveBank] + 1; if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) { bankIn1 = gActiveBank; if (gAbsentBankFlags & gBitTable[GetBankByIdentity(GetBankIdentity(gActiveBank) ^ BIT_MON)]) bankIn2 = gActiveBank; else bankIn2 = GetBankByIdentity(GetBankIdentity(gActiveBank) ^ BIT_MON); // UB: It considers the opponent only player's side even though it can battle alongside player; opposingBank = Random() & BIT_MON; if (gAbsentBankFlags & gBitTable[opposingBank]) opposingBank ^= BIT_MON; } else { opposingBank = GetBankByIdentity(GetBankIdentity(gActiveBank) ^ BIT_SIDE); bankIn1 = gActiveBank; bankIn2 = gActiveBank; } if (gBattleTypeFlags & (BATTLE_TYPE_TWO_OPPONENTS | BATTLE_TYPE_x800000)) { if ((gActiveBank & BIT_MON) == 0) firstId = 0, lastId = 3; else firstId = 3, lastId = 6; } else { firstId = 0, lastId = 6; } if (GetBankSide(gActiveBank) == SIDE_PLAYER) party = gPlayerParty; else party = gEnemyParty; invalidMons = 0; while (invalidMons != 0x3F) // all mons are invalid { bestDmg = 0; bestMonId = 6; // find the mon which type is the most suitable offensively for (i = firstId; i < lastId; i++) { u16 species = GetMonData(&party[i], MON_DATA_SPECIES); if (species != SPECIES_NONE && GetMonData(&party[i], MON_DATA_HP) != 0 && !(gBitTable[i] & invalidMons) && gBattlePartyID[bankIn1] != i && gBattlePartyID[bankIn2] != i && i != *(gBattleStruct->field_5C + bankIn1) && i != *(gBattleStruct->field_5C + bankIn2)) { u8 type1 = gBaseStats[species].type1; u8 type2 = gBaseStats[species].type2; u8 typeDmg = 10; ModulateByTypeEffectiveness(gBattleMons[opposingBank].type1, type1, type2, &typeDmg); ModulateByTypeEffectiveness(gBattleMons[opposingBank].type2, type1, type2, &typeDmg); if (bestDmg < typeDmg) { bestDmg = typeDmg; bestMonId = i; } } else { invalidMons |= gBitTable[i]; } } // ok, we know the mon has the right typing but does it have at least one super effective move? if (bestMonId != 6) { for (i = 0; i < 4; i++) { move = GetMonData(&party[bestMonId], MON_DATA_MOVE1 + i); if (move != MOVE_NONE && TypeCalc(move, gActiveBank, opposingBank) & MOVESTATUS_SUPEREFFECTIVE) break; } if (i != 4) return bestMonId; // has both the typing and at least one super effective move invalidMons |= gBitTable[bestMonId]; // sorry buddy, we want something better } else { invalidMons = 0x3F; // no viable mon to switch } } gDynamicBasePower = 0; gBattleStruct->dynamicMoveType = 0; gBattleScripting.dmgMultiplier = 1; gBattleMoveFlags = 0; gCritMultiplier = 1; bestDmg = 0; bestMonId = 6; // if we couldn't find the best mon in terms of typing, find the one that deals most damage for (i = firstId; i < lastId; i++) { if ((u16)(GetMonData(&party[i], MON_DATA_SPECIES)) == SPECIES_NONE) continue; if (GetMonData(&party[i], MON_DATA_HP) == 0) continue; if (gBattlePartyID[bankIn1] == i) continue; if (gBattlePartyID[bankIn2] == i) continue; if (i == *(gBattleStruct->field_5C + bankIn1)) continue; if (i == *(gBattleStruct->field_5C + bankIn2)) continue; for (j = 0; j < 4; j++) { move = GetMonData(&party[i], MON_DATA_MOVE1 + j); gBattleMoveDamage = 0; if (move != MOVE_NONE && gBattleMoves[move].power != 1) { AI_CalcDmg(gActiveBank, opposingBank); TypeCalc(move, gActiveBank, opposingBank); } if (bestDmg < gBattleMoveDamage) { bestDmg = gBattleMoveDamage; bestMonId = i; } } } return bestMonId; }