diff --git a/include/battle.h b/include/battle.h index f009cbe7fb..b51a3d71b1 100644 --- a/include/battle.h +++ b/include/battle.h @@ -191,7 +191,8 @@ struct SideTimer u8 safeguardTimer; u8 safeguardBattlerId; u8 followmeTimer; - u8 followmeTarget; + u8 followmeTarget:3; + u8 followmePowder:1; // Rage powder, does not affect grass type pokemon. u8 spikesAmount; u8 toxicSpikesAmount; u8 stealthRockAmount; diff --git a/include/battle_util.h b/include/battle_util.h index 0b7b01d0c8..e10305e564 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -46,6 +46,7 @@ struct TypePower extern const struct TypePower gNaturalGiftTable[]; +bool32 IsAffectedByFollowMe(u32 battlerAtk, u32 defSide); void HandleAction_UseMove(void); void HandleAction_Switch(void); void HandleAction_UseItem(void); diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 600921d97c..7c74c659af 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -283,7 +283,7 @@ static const s8 sAiAbilityRatings[ABILITIES_COUNT] = [ABILITY_GORILLA_TACTICS] = 4, }; -static const u16 sEncouragedEncoreEffects[] = +static const u16 sEncouragedEncoreEffects[] = { EFFECT_DREAM_EATER, EFFECT_ATTACK_UP, @@ -365,7 +365,7 @@ static const u16 sDiscouragedPowerfulMoveEffects[] = 0xFFFF }; -static const u16 sIgnoreMoldBreakerMoves[] = +static const u16 sIgnoreMoldBreakerMoves[] = { MOVE_MOONGEIST_BEAM, MOVE_SUNSTEEL_STRIKE, @@ -381,7 +381,7 @@ static const u16 sIgnoreMoldBreakerMoves[] = #endif }; -static const u16 sInstructBannedMoves[] = +static const u16 sInstructBannedMoves[] = { MOVE_INSTRUCT, MOVE_BIDE, @@ -410,7 +410,7 @@ static const u16 sInstructBannedMoves[] = MOVE_SOLAR_BLADE, }; -static const u16 sRechargeMoves[] = +static const u16 sRechargeMoves[] = { MOVE_HYPER_BEAM, MOVE_BLAST_BURN, @@ -424,7 +424,7 @@ static const u16 sRechargeMoves[] = MOVE_ETERNABEAM, }; -static const u16 sOtherMoveCallingMoves[] = +static const u16 sOtherMoveCallingMoves[] = { MOVE_ASSIST, MOVE_COPYCAT, @@ -640,7 +640,7 @@ bool32 IsAffectedByPowder(u8 battler, u16 ability, u16 holdEffect) { if ((B_POWDER_GRASS >= GEN_6 && IS_BATTLER_OF_TYPE(battler, TYPE_GRASS)) || ability == ABILITY_OVERCOAT - || GetBattlerHoldEffect(battler, TRUE) == HOLD_EFFECT_SAFETY_GOOGLES) + || holdEffect == HOLD_EFFECT_SAFETY_GOOGLES) return FALSE; return TRUE; } @@ -929,7 +929,7 @@ u8 AI_GetMoveEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef) gMoveResultFlags = 0; gCurrentMove = move; effectivenessMultiplier = AI_GetTypeEffectiveness(gCurrentMove, battlerAtk, battlerDef); - + switch (effectivenessMultiplier) { case UQ_4_12(0.0): @@ -1047,7 +1047,7 @@ bool32 CanTargetFaintAiWithMod(u8 battlerDef, u8 battlerAtk, s32 hpMod, s32 dmgM u32 hpCheck = gBattleMons[battlerAtk].hp + hpMod; if (dmgMod) dmg *= dmgMod; - + if (moves[i] != MOVE_NONE && moves[i] != 0xFFFF && !(unusable & gBitTable[i]) && dmg >= hpCheck) { return TRUE; @@ -1091,22 +1091,22 @@ s32 AI_GetAbility(u32 battlerId) u16 AI_GetHoldEffect(u32 battlerId) { u32 holdEffect; - + if (!IsBattlerAIControlled(battlerId)) holdEffect = BATTLE_HISTORY->itemEffects[battlerId]; else holdEffect = GetBattlerHoldEffect(battlerId, FALSE); - + if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_NEGATE_UNAWARE) return holdEffect; - + if (gStatuses3[battlerId] & STATUS3_EMBARGO) return HOLD_EFFECT_NONE; if (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) return HOLD_EFFECT_NONE; if (AI_GetAbility(battlerId) == ABILITY_KLUTZ && !(gStatuses3[battlerId] & STATUS3_GASTRO_ACID)) return HOLD_EFFECT_NONE; - + return holdEffect; } @@ -1123,7 +1123,7 @@ bool32 AI_IsTerrainAffected(u8 battlerId, u32 flags) bool32 AI_IsBattlerGrounded(u8 battlerId) { u32 holdEffect = AI_GetHoldEffect(battlerId); - + if (holdEffect == HOLD_EFFECT_IRON_BALL) return TRUE; else if (gFieldStatuses & STATUS_FIELD_GRAVITY) @@ -1149,10 +1149,10 @@ bool32 AI_IsBattlerGrounded(u8 battlerId) bool32 DoesBattlerIgnoreAbilityChecks(u16 atkAbility, u16 move) { u32 i; - + if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_NEGATE_UNAWARE) return FALSE; // AI handicap flag: doesn't understand ability suppression concept - + for (i = 0; i < ARRAY_COUNT(sIgnoreMoldBreakerMoves); i++) { if (move == sIgnoreMoldBreakerMoves[i]) @@ -1172,7 +1172,7 @@ bool32 AI_WeatherHasEffect(void) u32 i; if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_NEGATE_UNAWARE) return TRUE; // AI doesn't understand weather supression (handicap) - + // need to manually check since we don't necessarily know opponent ability for (i = 0; i < gBattlersCount; i++) { @@ -1186,7 +1186,7 @@ bool32 AI_WeatherHasEffect(void) bool32 IsAromaVeilProtectedMove(u16 move) { u32 i; - + switch (move) { case MOVE_DISABLE: @@ -1273,7 +1273,7 @@ bool32 IsMoveRedirectionPrevented(u16 move, u16 atkAbility) { if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_NEGATE_UNAWARE) return FALSE; - + if (move == MOVE_SKY_DROP || move == MOVE_SNIPE_SHOT || atkAbility == ABILITY_PROPELLER_TAIL @@ -1287,7 +1287,7 @@ u32 AI_GetMoveAccuracy(u8 battlerAtk, u8 battlerDef, u16 atkAbility, u16 defAbil { u32 calc, moveAcc, atkParam, defParam; s8 buff, accStage, evasionStage; - + gPotentialItemEffectBattler = battlerDef; accStage = gBattleMons[battlerAtk].statStages[STAT_ACC]; evasionStage = gBattleMons[battlerDef].statStages[STAT_EVASION]; @@ -1366,25 +1366,25 @@ bool32 IsMoveEncouragedToHit(u8 battlerAtk, u8 battlerDef, u16 move) { if (IsSemiInvulnerable(battlerDef, move)) return FALSE; - + //TODO - anticipate protect move? - + // always hits if (gStatuses3[battlerDef] & STATUS3_ALWAYS_HITS || gDisableStructs[battlerDef].battlerWithSureHit == battlerAtk) return TRUE; - + if (AI_GetAbility(battlerDef) == ABILITY_NO_GUARD || AI_GetAbility(battlerAtk) == ABILITY_NO_GUARD) return TRUE; - + if (B_TOXIC_NEVER_MISS >= GEN_6 && gBattleMoves[move].effect == EFFECT_TOXIC && IS_BATTLER_OF_TYPE(battlerAtk, TYPE_POISON)) return TRUE; - + // discouraged from hitting if (AI_WeatherHasEffect() && (gBattleWeather & WEATHER_SUN_ANY) && (gBattleMoves[move].effect == EFFECT_THUNDER || gBattleMoves[move].effect == EFFECT_HURRICANE)) return FALSE; - - // increased accuracy but don't always hit + + // increased accuracy but don't always hit if ((AI_WeatherHasEffect() && (((gBattleWeather & WEATHER_RAIN_ANY) && (gBattleMoves[move].effect == EFFECT_THUNDER || gBattleMoves[move].effect == EFFECT_HURRICANE)) || (((gBattleWeather & WEATHER_HAIL_ANY) && move == MOVE_BLIZZARD)))) @@ -1394,23 +1394,23 @@ bool32 IsMoveEncouragedToHit(u8 battlerAtk, u8 battlerDef, u16 move) { return TRUE; } - + return FALSE; } bool32 ShouldTryOHKO(u8 battlerAtk, u8 battlerDef, u16 atkAbility, u16 defAbility, u32 accuracy, u16 move) { u32 holdEffect = AI_GetHoldEffect(battlerDef); - + gPotentialItemEffectBattler = battlerDef; if (holdEffect == HOLD_EFFECT_FOCUS_BAND && (Random() % 100) < GetBattlerHoldEffectParam(battlerDef)) return FALSE; //probabilistically speaking, focus band should activate so dont OHKO else if (holdEffect == HOLD_EFFECT_FOCUS_SASH && AtMaxHp(battlerDef)) return FALSE; - + if (!DoesBattlerIgnoreAbilityChecks(atkAbility, move) && defAbility == ABILITY_STURDY) return FALSE; - + if ((((gStatuses3[battlerDef] & STATUS3_ALWAYS_HITS) && gDisableStructs[battlerDef].battlerWithSureHit == battlerAtk) || atkAbility == ABILITY_NO_GUARD || defAbility == ABILITY_NO_GUARD) @@ -1433,7 +1433,7 @@ bool32 ShouldSetSandstorm(u8 battler, u16 ability, u16 holdEffect) return FALSE; else if (gBattleWeather & WEATHER_SANDSTORM_ANY) return FALSE; - + if (ability == ABILITY_SAND_VEIL || ability == ABILITY_SAND_RUSH || ability == ABILITY_SAND_FORCE @@ -1458,7 +1458,7 @@ bool32 ShouldSetHail(u8 battler, u16 ability, u16 holdEffect) return FALSE; else if (gBattleWeather & WEATHER_HAIL_ANY) return FALSE; - + if (ability == ABILITY_SNOW_CLOAK || ability == ABILITY_ICE_BODY || ability == ABILITY_FORECAST @@ -1472,7 +1472,7 @@ bool32 ShouldSetHail(u8 battler, u16 ability, u16 holdEffect) || HasMoveEffect(battler, EFFECT_WEATHER_BALL)) { return TRUE; - } + } return FALSE; } @@ -1482,7 +1482,7 @@ bool32 ShouldSetRain(u8 battlerAtk, u16 atkAbility, u16 holdEffect) return FALSE; else if (gBattleWeather & WEATHER_RAIN_ANY) return FALSE; - + if (holdEffect != HOLD_EFFECT_UTILITY_UMBRELLA && (atkAbility == ABILITY_SWIFT_SWIM || atkAbility == ABILITY_FORECAST @@ -1505,7 +1505,7 @@ bool32 ShouldSetSun(u8 battlerAtk, u16 atkAbility, u16 holdEffect) return FALSE; else if (gBattleWeather & WEATHER_SUN_ANY) return FALSE; - + if (holdEffect != HOLD_EFFECT_UTILITY_UMBRELLA && (atkAbility == ABILITY_CHLOROPHYLL || atkAbility == ABILITY_FLOWER_GIFT @@ -1533,13 +1533,13 @@ void ProtectChecks(u8 battlerAtk, u8 battlerDef, u16 move, u16 predictedMove, s1 u16 predictedEffect = gBattleMoves[predictedMove].effect; u8 defAbility = AI_GetAbility(battlerDef); u32 uses = gDisableStructs[battlerAtk].protectUses; - + /*if (GetMoveResultFlags(predictedMove) & (MOVE_RESULT_NO_EFFECT | MOVE_RESULT_MISSED)) { (*score) -= 5; return; }*/ - + if (uses == 0) { if (predictedMove != MOVE_NONE && predictedMove != 0xFFFF && !IS_MOVE_STATUS(predictedMove)) @@ -1561,7 +1561,7 @@ void ProtectChecks(u8 battlerAtk, u8 battlerDef, u16 move, u16 predictedMove, s1 { (*score)--; } - + if (gBattleMons[battlerDef].status1 & STATUS1_TOXIC_POISON || gBattleMons[battlerDef].status2 & (STATUS2_CURSED | STATUS2_INFATUATION) || gStatuses3[battlerDef] & (STATUS3_PERISH_SONG | STATUS3_LEECHSEED | STATUS3_YAWN)) @@ -1578,10 +1578,10 @@ bool32 ShouldLowerStat(u8 battler, u16 battlerAbility, u8 stat) || battlerAbility == ABILITY_WHITE_SMOKE || battlerAbility == ABILITY_FULL_METAL_BODY) return FALSE; - + return TRUE; } - + return FALSE; } @@ -1595,7 +1595,7 @@ bool32 BattlerStatCanRise(u8 battler, u16 battlerAbility, u8 stat) bool32 AreBattlersStatsMaxed(u8 battlerId) { - u32 i; + u32 i; for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++) { if (gBattleMons[battlerId].statStages[i] < MAX_STAT_STAGE) @@ -1607,7 +1607,7 @@ bool32 AreBattlersStatsMaxed(u8 battlerId) bool32 AnyStatIsRaised(u8 battlerId) { u32 i; - + for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++) { if (gBattleMons[battlerId].statStages[i] > DEFAULT_STAT_STAGE) @@ -1747,10 +1747,10 @@ bool32 ShouldLowerEvasion(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 CanAttackerFaintTarget(u8 battlerAtk, u8 battlerDef, u8 index, u8 numHits) { s32 dmg = AI_THINKING_STRUCT->simulatedDmg[battlerAtk][battlerDef][index]; - + if (numHits) dmg *= numHits; - + if (gBattleMons[battlerDef].hp <= dmg) return TRUE; return FALSE; @@ -1776,7 +1776,7 @@ bool32 HasOnlyMovesWithSplit(u32 battlerId, u32 split, bool32 onlyOffensive) if (moves[i] != MOVE_NONE && moves[i] != 0xFFFF && GetBattleMoveSplit(moves[i]) != split) return FALSE; } - + return TRUE; } @@ -1841,12 +1841,12 @@ bool32 HasMoveWithLowAccuracy(u8 battlerAtk, u8 battlerDef, u8 accCheck, bool32 s32 i; u16 *moves = GetMovesArray(battlerAtk); u8 moveLimitations = CheckMoveLimitations(battlerAtk, 0, 0xFF); - + for (i = 0; i < MAX_MON_MOVES; i++) { if (moves[i] == MOVE_NONE || moves[i] == 0xFFFF) continue; - + if (!(gBitTable[i] & moveLimitations)) { if (ignoreStatus && IS_MOVE_STATUS(moves[i])) @@ -1854,12 +1854,12 @@ bool32 HasMoveWithLowAccuracy(u8 battlerAtk, u8 battlerDef, u8 accCheck, bool32 else if ((!IS_MOVE_STATUS(moves[i]) && gBattleMoves[moves[i]].accuracy == 0) || gBattleMoves[moves[i]].target & (MOVE_TARGET_USER | MOVE_TARGET_OPPONENTS_FIELD)) continue; - + if (AI_GetMoveAccuracy(battlerAtk, battlerDef, atkAbility, defAbility, atkHoldEffect, defHoldEffect, moves[i]) <= accCheck) return TRUE; } } - + return FALSE; } @@ -1868,7 +1868,7 @@ bool32 HasSleepMoveWithLowAccuracy(u8 battlerAtk, u8 battlerDef) u8 moveLimitations = CheckMoveLimitations(battlerAtk, 0, 0xFF); u32 i; u16 *moves = GetMovesArray(battlerAtk); - + for (i = 0; i < MAX_MON_MOVES; i++) { if (moves[i] == MOVE_NONE) @@ -2067,7 +2067,7 @@ bool32 HasDamagingMove(u8 battlerId) { u32 i; u16 *moves = GetMovesArray(battlerId); - + for (i = 0; i < MAX_MON_MOVES; i++) { if (moves[i] != MOVE_NONE && moves[i] != 0xFFFF && gBattleMoves[moves[i]].power != 0) @@ -2106,7 +2106,7 @@ bool32 IsInstructBannedMove(u16 move) bool32 IsEncoreEncouragedEffect(u16 moveEffect) { u32 i; - + for (i = 0; i < ARRAY_COUNT(sEncouragedEncoreEffects); i++) { if (moveEffect == sEncouragedEncoreEffects[i]) @@ -2208,10 +2208,10 @@ static u32 GetTrapDamage(u8 battlerId) static u32 GetPoisonDamage(u8 battlerId) { u32 damage = 0; - + if (AI_GetAbility(battlerId) == ABILITY_POISON_HEAL) return damage; - + if (gBattleMons[battlerId].status1 & STATUS1_POISON) { damage = gBattleMons[battlerId].maxHP / 8; @@ -2260,7 +2260,7 @@ static u32 GetWeatherDamage(u8 battlerId) u32 damage = 0; if (!AI_WeatherHasEffect()) return 0; - + if (gBattleWeather & WEATHER_SANDSTORM_ANY) { if (BattlerAffectedBySandstorm(battlerId, ability) @@ -2289,17 +2289,17 @@ static u32 GetWeatherDamage(u8 battlerId) u32 GetBattlerSecondaryDamage(u8 battlerId) { u32 secondaryDamage; - + if (AI_GetAbility(battlerId) == ABILITY_MAGIC_GUARD) return FALSE; - + secondaryDamage = GetLeechSeedDamage(battlerId) + GetNightmareDamage(battlerId) + GetCurseDamage(battlerId) + GetTrapDamage(battlerId) + GetPoisonDamage(battlerId) + GetWeatherDamage(battlerId); - + return secondaryDamage; } @@ -2323,7 +2323,7 @@ bool32 BattlerWillFaintFromSecondaryDamage(u8 battler, u16 ability) static bool32 AnyUsefulStatIsRaised(u8 battler) { u8 statId; - + for (statId = STAT_ATK; statId < NUM_BATTLE_STATS; statId++) { if (gBattleMons[battler].statStages[statId] > DEFAULT_STAT_STAGE) @@ -2353,17 +2353,17 @@ static bool32 PartyBattlerShouldAvoidHazards(u8 currBattler, u8 switchBattler) u16 ability = GetMonAbility(mon); // we know our own party data u16 holdEffect = GetBattlerHoldEffect(GetMonData(mon, MON_DATA_HELD_ITEM), TRUE); u32 flags = gSideStatuses[GetBattlerSide(currBattler)] & (SIDE_STATUS_SPIKES | SIDE_STATUS_STEALTH_ROCK | SIDE_STATUS_STICKY_WEB | SIDE_STATUS_TOXIC_SPIKES); - + if (flags == 0) return FALSE; - + if (ability == ABILITY_MAGIC_GUARD || ability == ABILITY_LEVITATE || holdEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS) return FALSE; - + if (flags & (SIDE_STATUS_SPIKES | SIDE_STATUS_STEALTH_ROCK) && GetMonData(mon, MON_DATA_HP) < (GetMonData(mon, MON_DATA_MAX_HP) / 8)) return TRUE; - + return FALSE; } @@ -2378,9 +2378,9 @@ bool32 ShouldPivot(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u8 mo u8 backupBattler = gActiveBattler; bool32 shouldSwitch; u8 battlerToSwitch; - + gActiveBattler = battlerAtk; - shouldSwitch = ShouldSwitch(); + shouldSwitch = ShouldSwitch(); battlerToSwitch = *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler); gActiveBattler = backupBattler; @@ -2558,7 +2558,7 @@ bool32 CanKnockOffItem(u8 battler, u16 item) #endif )) && GetBattlerSide(battler) == B_SIDE_PLAYER) return FALSE; - + if (AI_GetAbility(battler) == ABILITY_STICKY_HOLD) return FALSE; @@ -2573,7 +2573,7 @@ bool32 IsBattlerIncapacitated(u8 battler, u16 ability) { if ((gBattleMons[battler].status1 & STATUS1_FREEZE) && !HasThawingMove(battler)) return TRUE; // if battler has thawing move we assume they will definitely use it, and thus being frozen should be neglected - + if (gBattleMons[battler].status1 & STATUS1_SLEEP) return TRUE; @@ -2686,7 +2686,7 @@ bool32 AI_CanConfuse(u8 battlerAtk, u8 battlerDef, u16 defAbility, u8 battlerAtk { return FALSE; } - + return TRUE; } @@ -2767,7 +2767,7 @@ bool32 ShouldTrap(u8 battlerAtk, u8 battlerDef, u16 move) { if (BattlerWillFaintFromSecondaryDamage(battlerDef, AI_DATA->defAbility)) return TRUE; // battler is taking secondary damage with low HP - + if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_STALL) { if (!CanTargetFaintAi(battlerDef, battlerAtk)) @@ -2781,12 +2781,12 @@ bool32 ShouldFakeOut(u8 battlerAtk, u8 battlerDef, u16 move) { if (AI_DATA->atkHoldEffect == HOLD_EFFECT_CHOICE_BAND && CountUsablePartyMons(battlerAtk) == 0) return FALSE; // don't lock attacker into fake out if can't switch out - + if (gDisableStructs[battlerAtk].isFirstTurn && ShouldTryToFlinch(battlerAtk, battlerDef, AI_DATA->atkAbility, AI_DATA->defAbility, move) && !DoesSubstituteBlockMove(battlerAtk, battlerDef, move)) return TRUE; - + return FALSE; } @@ -2814,12 +2814,12 @@ bool32 AnyPartyMemberStatused(u8 battlerId, bool32 checkSoundproof) { struct Pokemon *party; u32 i; - + if (GetBattlerSide(battlerId) == B_SIDE_PLAYER) party = gPlayerParty; else party = gEnemyParty; - + for (i = 0; i < PARTY_SIZE; i++) { if (checkSoundproof && GetMonAbility(&party[i]) == ABILITY_SOUNDPROOF) @@ -2863,21 +2863,21 @@ bool32 ShouldUseRecoilMove(u8 battlerAtk, u8 battlerDef, u32 recoilDmg, u8 moveI else return FALSE; //Not as good to use move if you'll faint and not win } - + return TRUE; } bool32 ShouldAbsorb(u8 battlerAtk, u8 battlerDef, u16 move, s32 damage) -{ +{ if (move == 0xFFFF || GetWhoStrikesFirst(battlerAtk, gBattlerTarget, TRUE) == 0) { // using item or user goes first u8 healPercent = (gBattleMoves[move].argument == 0) ? 50 : gBattleMoves[move].argument; s32 healDmg = (healPercent * damage) / 100; - + if (gStatuses3[battlerAtk] & STATUS3_HEAL_BLOCK) healDmg = 0; - + if (CanTargetFaintAi(battlerDef, battlerAtk) && !CanTargetFaintAiWithMod(battlerDef, battlerAtk, healDmg, 0)) return TRUE; // target can faint attacker unless they heal @@ -2890,7 +2890,7 @@ bool32 ShouldAbsorb(u8 battlerAtk, u8 battlerDef, u16 move, s32 damage) if (!CanTargetFaintAi(battlerDef, battlerAtk)) return TRUE; } - + return FALSE; } @@ -2953,7 +2953,7 @@ bool32 IsValidDoubleBattle(u8 battlerAtk) u16 GetAllyChosenMove(void) { u8 partnerBattler = BATTLE_PARTNER(sBattler_AI); - + if (!IsBattlerAlive(partnerBattler) || !IsBattlerAIControlled(partnerBattler)) return MOVE_NONE; // TODO: prediction? else if (partnerBattler > sBattler_AI) // Battler with the lower id chooses the move first. @@ -2966,7 +2966,7 @@ bool32 IsTargetingPartner(u8 battlerAtk, u8 battlerDef) { if ((battlerAtk & BIT_SIDE) == (battlerDef & BIT_SIDE)) return TRUE; - + return FALSE; } @@ -2975,7 +2975,7 @@ bool32 DoesPartnerHaveSameMoveEffect(u8 battlerAtkPartner, u8 battlerDef, u16 mo { if (!IsDoubleBattle()) return FALSE; - + if (gBattleMoves[move].effect == gBattleMoves[partnerMove].effect && gChosenMoveByBattler[battlerAtkPartner] != MOVE_NONE && gBattleStruct->moveTarget[battlerAtkPartner] == battlerDef) @@ -2990,7 +2990,7 @@ bool32 PartnerHasSameMoveEffectWithoutTarget(u8 battlerAtkPartner, u16 move, u16 { if (!IsDoubleBattle()) return FALSE; - + if (gBattleMoves[move].effect == gBattleMoves[partnerMove].effect && gChosenMoveByBattler[battlerAtkPartner] != MOVE_NONE) return TRUE; @@ -3002,7 +3002,7 @@ bool32 PartnerMoveEffectIsStatusSameTarget(u8 battlerAtkPartner, u8 battlerDef, { if (!IsDoubleBattle()) return FALSE; - + if (gChosenMoveByBattler[battlerAtkPartner] != MOVE_NONE && gBattleStruct->moveTarget[battlerAtkPartner] == battlerDef && (gBattleMoves[partnerMove].effect == EFFECT_SLEEP @@ -3020,14 +3020,14 @@ bool32 PartnerMoveEffectIsWeather(u8 battlerAtkPartner, u16 partnerMove) { if (!IsDoubleBattle()) return FALSE; - + if (gChosenMoveByBattler[battlerAtkPartner] != MOVE_NONE && (gBattleMoves[partnerMove].effect == EFFECT_SUNNY_DAY || gBattleMoves[partnerMove].effect == EFFECT_RAIN_DANCE || gBattleMoves[partnerMove].effect == EFFECT_SANDSTORM || gBattleMoves[partnerMove].effect == EFFECT_HAIL)) return TRUE; - + return FALSE; } @@ -3036,14 +3036,14 @@ bool32 PartnerMoveEffectIsTerrain(u8 battlerAtkPartner, u16 partnerMove) { if (!IsDoubleBattle()) return FALSE; - + if (gChosenMoveByBattler[battlerAtkPartner] != MOVE_NONE && (gBattleMoves[partnerMove].effect == EFFECT_GRASSY_TERRAIN || gBattleMoves[partnerMove].effect == EFFECT_MISTY_TERRAIN || gBattleMoves[partnerMove].effect == EFFECT_ELECTRIC_TERRAIN || gBattleMoves[partnerMove].effect == EFFECT_PSYCHIC_TERRAIN)) return TRUE; - + return FALSE; } @@ -3052,7 +3052,7 @@ bool32 PartnerMoveIs(u8 battlerAtkPartner, u16 partnerMove, u16 moveCheck) { if (!IsDoubleBattle()) return FALSE; - + if (gChosenMoveByBattler[battlerAtkPartner] != MOVE_NONE && partnerMove == moveCheck) return TRUE; return FALSE; @@ -3063,7 +3063,7 @@ bool32 PartnerMoveIsSameAsAttacker(u8 battlerAtkPartner, u8 battlerDef, u16 move { if (!IsDoubleBattle()) return FALSE; - + if (gChosenMoveByBattler[battlerAtkPartner] != MOVE_NONE && move == partnerMove && gBattleStruct->moveTarget[battlerAtkPartner] == battlerDef) return TRUE; return FALSE; @@ -3088,7 +3088,7 @@ bool32 ShouldUseWishAromatherapy(u8 battlerAtk, u8 battlerDef, u16 move) bool32 needHealing = FALSE; GetAIPartyIndexes(battlerAtk, &firstId, &lastId); - + if (GetBattlerSide(gActiveBattler) == B_SIDE_PLAYER) party = gPlayerParty; else @@ -3208,12 +3208,12 @@ bool32 IsPartyFullyHealedExceptBattler(u8 battlerId) { struct Pokemon *party; u32 i; - + if (GetBattlerSide(battlerId) == B_SIDE_PLAYER) party = gPlayerParty; else party = gEnemyParty; - + for (i = 0; i < PARTY_SIZE; i++) { if (i != gBattlerPartyIndexes[battlerId] @@ -3236,7 +3236,7 @@ bool32 PartyHasMoveSplit(u8 battlerId, u8 split) { if (GetMonData(&party[i], MON_DATA_HP, NULL) == 0) continue; - + for (j = 0; j < MAX_MON_MOVES; j++) { u16 move = GetMonData(&party[i], MON_DATA_MOVE1 + j, NULL); @@ -3281,7 +3281,7 @@ s8 GetAbilityRating(u16 ability) return sAiAbilityRatings[ability]; } -static const u16 sRecycleEncouragedItems[] = +static const u16 sRecycleEncouragedItems[] = { ITEM_CHESTO_BERRY, ITEM_LUM_BERRY, @@ -3309,15 +3309,15 @@ bool32 IsRecycleEncouragedItem(u16 item) // score increases #define STAT_UP_2_STAGE 8 -#define STAT_UP_STAGE 10 +#define STAT_UP_STAGE 10 void IncreaseStatUpScore(u8 battlerAtk, u8 battlerDef, u8 statId, s16 *score) { if (AI_DATA->atkAbility == ABILITY_CONTRARY) return; - + if (GetHealthPercentage(battlerAtk) < 80 && AI_RandLessThan(128)) return; - + switch (statId) { case STAT_ATK: @@ -3393,10 +3393,10 @@ void IncreasePoisonScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score) { if (!HasDamagingMove(battlerDef)) *score += 2; - + if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_STALL && HasMoveEffect(battlerAtk, EFFECT_PROTECT)) (*score)++; // stall tactic - + if (HasMoveEffect(battlerAtk, EFFECT_VENOSHOCK) || HasMoveEffect(battlerAtk, EFFECT_HEX) || HasMoveEffect(battlerAtk, EFFECT_VENOM_DRENCH) @@ -3417,7 +3417,7 @@ void IncreaseBurnScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score) if (CanTargetFaintAi(battlerDef, battlerAtk)) *score += 2; // burning the target to stay alive is cool } - + if (HasMoveEffect(battlerAtk, EFFECT_HEX) || HasMoveEffect(AI_DATA->battlerAtkPartner, EFFECT_HEX)) (*score)++; } @@ -3429,7 +3429,7 @@ void IncreaseParalyzeScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score) { u8 atkSpeed = GetBattlerTotalSpeedStat(battlerAtk); u8 defSpeed = GetBattlerTotalSpeedStat(battlerDef); - + if ((defSpeed >= atkSpeed && defSpeed / 2 < atkSpeed) // You'll go first after paralyzing foe || HasMoveEffect(battlerAtk, EFFECT_HEX) || HasMoveEffect(battlerAtk, EFFECT_FLINCH_HIT) @@ -3447,11 +3447,11 @@ void IncreaseSleepScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score) *score += 2; else return; - + if ((HasMoveEffect(battlerAtk, EFFECT_DREAM_EATER) || HasMoveEffect(battlerAtk, EFFECT_NIGHTMARE)) && !(HasMoveEffect(battlerDef, EFFECT_SNORE) || HasMoveEffect(battlerDef, EFFECT_SLEEP_TALK))) (*score)++; - + if (HasMoveEffect(battlerAtk, EFFECT_HEX) || HasMoveEffect(AI_DATA->battlerAtkPartner, EFFECT_HEX)) (*score)++; } diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 57b6a0064b..de7105a270 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -7408,7 +7408,7 @@ static void Cmd_various(void) case VARIOUS_SET_MAGIC_COAT_TARGET: gBattlerAttacker = gBattlerTarget; side = GetBattlerSide(gBattlerAttacker) ^ BIT_SIDE; - if (gSideTimers[side].followmeTimer != 0 && gBattleMons[gSideTimers[side].followmeTarget].hp != 0) + if (IsAffectedByFollowMe(gBattlerAttacker, side)) gBattlerTarget = gSideTimers[side].followmeTarget; else gBattlerTarget = gActiveBattler; @@ -9980,7 +9980,7 @@ static void Cmd_counterdamagecalculator(void) { gBattleMoveDamage = gProtectStructs[gBattlerAttacker].physicalDmg * 2; - if (gSideTimers[sideTarget].followmeTimer && gBattleMons[gSideTimers[sideTarget].followmeTarget].hp) + if (IsAffectedByFollowMe(gBattlerAttacker, sideTarget)) gBattlerTarget = gSideTimers[sideTarget].followmeTarget; else gBattlerTarget = gProtectStructs[gBattlerAttacker].physicalBattlerId; @@ -9999,11 +9999,13 @@ static void Cmd_mirrorcoatdamagecalculator(void) // a copy of atkA1 with the phy u8 sideAttacker = GetBattlerSide(gBattlerAttacker); u8 sideTarget = GetBattlerSide(gProtectStructs[gBattlerAttacker].specialBattlerId); - if (gProtectStructs[gBattlerAttacker].specialDmg && sideAttacker != sideTarget && gBattleMons[gProtectStructs[gBattlerAttacker].specialBattlerId].hp) + if (gProtectStructs[gBattlerAttacker].specialDmg + && sideAttacker != sideTarget + && gBattleMons[gProtectStructs[gBattlerAttacker].specialBattlerId].hp) { gBattleMoveDamage = gProtectStructs[gBattlerAttacker].specialDmg * 2; - if (gSideTimers[sideTarget].followmeTimer && gBattleMons[gSideTimers[sideTarget].followmeTarget].hp) + if (IsAffectedByFollowMe(gBattlerAttacker, sideTarget)) gBattlerTarget = gSideTimers[sideTarget].followmeTarget; else gBattlerTarget = gProtectStructs[gBattlerAttacker].specialBattlerId; @@ -11084,6 +11086,7 @@ static void Cmd_setforcedtarget(void) // follow me { gSideTimers[GetBattlerSide(gBattlerAttacker)].followmeTimer = 1; gSideTimers[GetBattlerSide(gBattlerAttacker)].followmeTarget = gBattlerAttacker; + gSideTimers[GetBattlerSide(gBattlerAttacker)].followmePowder = TestMoveFlags(gCurrentMove, FLAG_POWDER); gBattlescriptCurrInstr++; } @@ -12556,7 +12559,7 @@ static void Cmd_metalburstdamagecalculator(void) { gBattleMoveDamage = gProtectStructs[gBattlerAttacker].physicalDmg * 150 / 100; - if (gSideTimers[sideTarget].followmeTimer && gBattleMons[gSideTimers[sideTarget].followmeTarget].hp) + if (IsAffectedByFollowMe(gBattlerAttacker, sideTarget)) gBattlerTarget = gSideTimers[sideTarget].followmeTarget; else gBattlerTarget = gProtectStructs[gBattlerAttacker].physicalBattlerId; @@ -12569,7 +12572,7 @@ static void Cmd_metalburstdamagecalculator(void) { gBattleMoveDamage = gProtectStructs[gBattlerAttacker].specialDmg * 150 / 100; - if (gSideTimers[sideTarget].followmeTimer && gBattleMons[gSideTimers[sideTarget].followmeTarget].hp) + if (IsAffectedByFollowMe(gBattlerAttacker, sideTarget)) gBattlerTarget = gSideTimers[sideTarget].followmeTarget; else gBattlerTarget = gProtectStructs[gBattlerAttacker].specialBattlerId; diff --git a/src/battle_util.c b/src/battle_util.c index 3466a88f9d..7deebd25e3 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -90,7 +90,7 @@ static const u8 sPkblToEscapeFactor[][3] = { static const u8 sGoNearCounterToCatchFactor[] = {4, 3, 2, 1}; static const u8 sGoNearCounterToEscapeFactor[] = {4, 4, 4, 4}; -static const u16 sSkillSwapBannedAbilities[] = +static const u16 sSkillSwapBannedAbilities[] = { ABILITY_WONDER_GUARD, ABILITY_MULTITYPE, @@ -109,9 +109,9 @@ static const u16 sSkillSwapBannedAbilities[] = ABILITY_GULP_MISSILE, }; -static const u16 sRolePlayBannedAbilities[] = +static const u16 sRolePlayBannedAbilities[] = { - ABILITY_TRACE, + ABILITY_TRACE, ABILITY_WONDER_GUARD, ABILITY_FORECAST, ABILITY_FLOWER_GIFT, @@ -134,7 +134,7 @@ static const u16 sRolePlayBannedAbilities[] = ABILITY_GULP_MISSILE, }; -static const u16 sRolePlayBannedAttackerAbilities[] = +static const u16 sRolePlayBannedAttackerAbilities[] = { ABILITY_MULTITYPE, ABILITY_ZEN_MODE, @@ -150,7 +150,7 @@ static const u16 sRolePlayBannedAttackerAbilities[] = ABILITY_GULP_MISSILE, }; -static const u16 sWorrySeedBannedAbilities[] = +static const u16 sWorrySeedBannedAbilities[] = { ABILITY_MULTITYPE, ABILITY_STANCE_CHANGE, @@ -166,7 +166,7 @@ static const u16 sWorrySeedBannedAbilities[] = ABILITY_GULP_MISSILE, }; -static const u16 sGastroAcidBannedAbilities[] = +static const u16 sGastroAcidBannedAbilities[] = { ABILITY_AS_ONE_ICE_RIDER, ABILITY_AS_ONE_SHADOW_RIDER, @@ -184,7 +184,7 @@ static const u16 sGastroAcidBannedAbilities[] = ABILITY_ZEN_MODE, }; -static const u16 sEntrainmentBannedAttackerAbilities[] = +static const u16 sEntrainmentBannedAttackerAbilities[] = { ABILITY_TRACE, ABILITY_FORECAST, @@ -202,7 +202,7 @@ static const u16 sEntrainmentBannedAttackerAbilities[] = ABILITY_GULP_MISSILE, }; -static const u16 sEntrainmentTargetSimpleBeamBannedAbilities[] = +static const u16 sEntrainmentTargetSimpleBeamBannedAbilities[] = { ABILITY_TRUANT, ABILITY_MULTITYPE, @@ -217,6 +217,21 @@ static const u16 sEntrainmentTargetSimpleBeamBannedAbilities[] = ABILITY_GULP_MISSILE, }; +bool32 IsAffectedByFollowMe(u32 battlerAtk, u32 defSide) +{ + u32 ability = GetBattlerAbility(battlerAtk); + + if (gSideTimers[defSide].followmeTimer == 0 + || gBattleMons[gSideTimers[defSide].followmeTarget].hp == 0 + || ability == ABILITY_PROPELLER_TAIL || ability == ABILITY_STALWART) + return FALSE; + + if (gSideTimers[defSide].followmePowder && !IsAffectedByPowder(battlerAtk, ability, GetBattlerHoldEffect(battlerAtk, TRUE))) + return FALSE; + + return TRUE; +} + // Functions void HandleAction_UseMove(void) { @@ -292,12 +307,9 @@ void HandleAction_UseMove(void) // choose target side = GetBattlerSide(gBattlerAttacker) ^ BIT_SIDE; - if (gSideTimers[side].followmeTimer != 0 + if (IsAffectedByFollowMe(gBattlerAttacker, side) && gBattleMoves[gCurrentMove].target == MOVE_TARGET_SELECTED - && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gSideTimers[side].followmeTarget) - && gBattleMons[gSideTimers[side].followmeTarget].hp != 0 - && (GetBattlerAbility(gBattlerAttacker) != ABILITY_PROPELLER_TAIL - || GetBattlerAbility(gBattlerAttacker) != ABILITY_STALWART)) + && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gSideTimers[side].followmeTarget)) { gBattlerTarget = gSideTimers[side].followmeTarget; } @@ -1542,7 +1554,7 @@ bool32 IsHealBlockPreventingMove(u32 battler, u32 move) { if (!(gStatuses3[battler] & STATUS3_HEAL_BLOCK)) return FALSE; - + switch (gBattleMoves[move].effect) { case EFFECT_ABSORB: @@ -3831,7 +3843,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move gBattleCommunication[MULTISTRING_CHOOSER] = 3; break; } - + BattleScriptPushCursorAndCallback(BattleScript_OverworldTerrain); effect++; } @@ -5399,13 +5411,13 @@ static u8 HealConfuseBerry(u32 battlerId, u32 itemId, u8 flavorId, bool32 end2) if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; gBattleMoveDamage *= -1; - + if (GetBattlerAbility(battlerId) == ABILITY_RIPEN) { gBattleMoveDamage *= 2; gBattlerAbility = battlerId; } - + if (end2) { if (GetFlavorRelationByPersonality(gBattleMons[battlerId].personality, flavorId) < 0) @@ -5439,10 +5451,10 @@ static u8 StatRaiseBerry(u32 battlerId, u32 itemId, u32 statId, bool32 end2) SET_STATCHANGER(statId, 2, FALSE); else SET_STATCHANGER(statId, 1, FALSE); - + gBattleScripting.animArg1 = 14 + statId; gBattleScripting.animArg2 = 0; - + if (end2) { BattleScriptExecute(BattleScript_BerryStatRaiseEnd2); @@ -5485,15 +5497,15 @@ static u8 RandomStatRaiseBerry(u32 battlerId, u32 itemId, bool32 end2) gBattleTextBuff2[7] = EOS; gEffectBattler = battlerId; - + if (GetBattlerAbility(battlerId) == ABILITY_RIPEN) SET_STATCHANGER(i + 1, 4, FALSE); else SET_STATCHANGER(i + 1, 2, FALSE); - + gBattleScripting.animArg1 = 0x21 + i + 6; gBattleScripting.animArg2 = 0; - + if (end2) { BattleScriptExecute(BattleScript_BerryStatRaiseEnd2); @@ -5513,7 +5525,7 @@ static u8 TrySetMicleBerry(u32 battlerId, u32 itemId, bool32 end2) if (HasEnoughHpToEatBerry(battlerId, 4, itemId)) { gProtectStructs[battlerId].micle = TRUE; // battler's next attack has increased accuracy - + if (end2) { BattleScriptExecute(BattleScript_MicleBerryActivateEnd2); @@ -5536,7 +5548,7 @@ static u8 DamagedStatBoostBerryEffect(u8 battlerId, u8 statId, u8 split) && !DoesSubstituteBlockMove(gBattlerAttacker, battlerId, gCurrentMove) && GetBattleMoveSplit(gCurrentMove) == split) { - + PREPARE_STAT_BUFFER(gBattleTextBuff1, statId); PREPARE_STRING_BUFFER(gBattleTextBuff2, STRINGID_STATROSE); @@ -5545,7 +5557,7 @@ static u8 DamagedStatBoostBerryEffect(u8 battlerId, u8 statId, u8 split) SET_STATCHANGER(statId, 2, FALSE); else SET_STATCHANGER(statId, 1, FALSE); - + gBattleScripting.animArg1 = 14 + statId; gBattleScripting.animArg2 = 0; BattleScriptPushCursor(); @@ -5563,11 +5575,11 @@ static u8 ItemHealHp(u32 battlerId, u32 itemId, bool32 end2, bool32 percentHeal) gBattleMoveDamage = (gBattleMons[battlerId].maxHP * GetBattlerHoldEffectParam(battlerId) / 100) * -1; else gBattleMoveDamage = GetBattlerHoldEffectParam(battlerId) * -1; - + // check ripen if (ItemId_GetPocket(itemId) == POCKET_BERRIES && GetBattlerAbility(battlerId) == ABILITY_RIPEN) gBattleMoveDamage *= 2; - + gBattlerAbility = battlerId; // in SWSH, berry juice shows ability pop up but has no effect. This is mimicked here if (end2) { @@ -5839,7 +5851,7 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) { u8 maxPP = CalculatePPWithBonus(move, ppBonuses, i); u8 ppRestored = GetBattlerHoldEffectParam(battlerId); - + if (GetBattlerAbility(battlerId) == ABILITY_RIPEN) { ppRestored *= 2; @@ -6417,7 +6429,7 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) gBattleMoveDamage = 1; if (GetBattlerAbility(battlerId) == ABILITY_RIPEN) gBattleMoveDamage *= 2; - + effect = ITEM_HP_CHANGE; BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_JabocaRowapBerryActivates; @@ -6437,7 +6449,7 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) gBattleMoveDamage = 1; if (GetBattlerAbility(battlerId) == ABILITY_RIPEN) gBattleMoveDamage *= 2; - + effect = ITEM_HP_CHANGE; BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_JabocaRowapBerryActivates; @@ -6552,7 +6564,7 @@ u8 GetMoveTarget(u16 move, u8 setTarget) { case MOVE_TARGET_SELECTED: side = GetBattlerSide(gBattlerAttacker) ^ BIT_SIDE; - if (gSideTimers[side].followmeTimer && gBattleMons[gSideTimers[side].followmeTarget].hp) + if (IsAffectedByFollowMe(gBattlerAttacker, side)) { targetBattler = gSideTimers[side].followmeTarget; } @@ -6587,7 +6599,7 @@ u8 GetMoveTarget(u16 move, u8 setTarget) break; case MOVE_TARGET_RANDOM: side = GetBattlerSide(gBattlerAttacker) ^ BIT_SIDE; - if (gSideTimers[side].followmeTimer && gBattleMons[gSideTimers[side].followmeTarget].hp) + if (IsAffectedByFollowMe(gBattlerAttacker, side)) targetBattler = gSideTimers[side].followmeTarget; else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && moveTarget & MOVE_TARGET_RANDOM) targetBattler = SetRandomTarget(gBattlerAttacker); @@ -7617,7 +7629,7 @@ static u32 CalcAttackStat(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, b break; case ABILITY_ICE_SCALES: if (IS_MOVE_SPECIAL(move)) - MulModifier(&modifier, UQ_4_12(0.5)); + MulModifier(&modifier, UQ_4_12(0.5)); break; } @@ -8496,7 +8508,7 @@ static bool32 TryRemoveScreens(u8 battler) bool32 removed = FALSE; u8 battlerSide = GetBattlerSide(battler); u8 enemySide = GetBattlerSide(BATTLE_OPPOSITE(battler)); - + // try to remove from battler's side if (gSideStatuses[battlerSide] & (SIDE_STATUS_REFLECT | SIDE_STATUS_LIGHTSCREEN | SIDE_STATUS_AURORA_VEIL)) { @@ -8506,7 +8518,7 @@ static bool32 TryRemoveScreens(u8 battler) gSideTimers[battlerSide].auroraVeilTimer = 0; removed = TRUE; } - + // try to remove from battler opponent's side if (gSideStatuses[enemySide] & (SIDE_STATUS_REFLECT | SIDE_STATUS_LIGHTSCREEN | SIDE_STATUS_AURORA_VEIL)) { @@ -8516,7 +8528,7 @@ static bool32 TryRemoveScreens(u8 battler) gSideTimers[enemySide].auroraVeilTimer = 0; removed = TRUE; } - + return removed; } @@ -8543,7 +8555,7 @@ struct Pokemon *GetBattlerPartyData(u8 battlerId) mon = &gPlayerParty[gBattlerPartyIndexes[battlerId]]; else mon = &gEnemyParty[gBattlerPartyIndexes[battlerId]]; - + return mon; }