From d795de9e9d5bfb4672520f8f0a86b6529c2d7ccb Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Tue, 3 Jun 2025 17:26:48 -0400 Subject: [PATCH] Remove more instances of hard-coded Move IDs (#7056) --- include/constants/battle_move_effects.h | 2 + include/move.h | 2 +- src/battle_ai_main.c | 66 +++++++----- src/battle_ai_switch_items.c | 6 +- src/battle_ai_util.c | 128 ++++++++++++------------ src/battle_dome.c | 8 +- src/battle_dynamax.c | 1 + src/battle_main.c | 1 + src/battle_script_commands.c | 23 +++-- src/battle_tv.c | 3 +- src/battle_util.c | 20 ++-- src/battle_z_move.c | 4 +- src/data/battle_move_effects.h | 12 +++ src/data/moves_info.h | 4 +- test/battle/move_animations/all_anims.c | 1 + test/battle/move_effect/ohko.c | 38 ++----- test/battle/move_effect/sheer_cold.c | 70 +++++++++++++ 17 files changed, 251 insertions(+), 138 deletions(-) create mode 100644 test/battle/move_effect/sheer_cold.c diff --git a/include/constants/battle_move_effects.h b/include/constants/battle_move_effects.h index 3d899469f4..4637d583e1 100644 --- a/include/constants/battle_move_effects.h +++ b/include/constants/battle_move_effects.h @@ -8,6 +8,7 @@ enum BattleMoveEffects EFFECT_NON_VOLATILE_STATUS, EFFECT_ABSORB, EFFECT_EXPLOSION, + EFFECT_MISTY_EXPLOSION, // Same as EFFECT_EXPLOSION but it's boosted on Misty Terrain EFFECT_DREAM_EATER, EFFECT_MIRROR_MOVE, EFFECT_ATTACK_UP, @@ -34,6 +35,7 @@ enum BattleMoveEffects EFFECT_LIGHT_SCREEN, EFFECT_REST, EFFECT_OHKO, + EFFECT_SHEER_COLD, // Same as EFFECT_OHKO but Ice-types are immune to it and has decreased accuracy for non Ice-type users. EFFECT_FUSION_COMBO, EFFECT_FIXED_PERCENT_DAMAGE, EFFECT_FIXED_HP_DAMAGE, diff --git a/include/move.h b/include/move.h index ba51165cb6..6fb342df63 100644 --- a/include/move.h +++ b/include/move.h @@ -494,7 +494,7 @@ static inline u32 GetMoveTwoTurnAttackWeather(u32 moveId) return gMovesInfo[SanitizeMoveId(moveId)].argument.twoTurnAttack.status; } -static inline u32 GetMoveProtectMethod(u32 moveId) +static inline enum ProtectMethod GetMoveProtectMethod(u32 moveId) { return gMovesInfo[SanitizeMoveId(moveId)].argument.protectMethod; } diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index cc6ca2a8f0..3a7cdb410d 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -1085,6 +1085,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_LEVEL_DAMAGE: case EFFECT_PSYWAVE: case EFFECT_OHKO: + case EFFECT_SHEER_COLD: case EFFECT_BIDE: case EFFECT_FIXED_PERCENT_DAMAGE: case EFFECT_ENDEAVOR: @@ -1293,6 +1294,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) default: break; // check move damage case EFFECT_EXPLOSION: + case EFFECT_MISTY_EXPLOSION: if (!(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_WILL_SUICIDE)) ADJUST_SCORE(-2); @@ -1675,9 +1677,11 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) || !(weather & (B_WEATHER_HAIL | B_WEATHER_SNOW))) ADJUST_SCORE(-10); break; - case EFFECT_OHKO: - if (B_SHEER_COLD_IMMUNITY >= GEN_7 && move == MOVE_SHEER_COLD && IS_BATTLER_OF_TYPE(battlerDef, TYPE_ICE)) + case EFFECT_SHEER_COLD: + if (B_SHEER_COLD_IMMUNITY >= GEN_7 && IS_BATTLER_OF_TYPE(battlerDef, TYPE_ICE)) RETURN_SCORE_MINUS(20); + // fallthrough + case EFFECT_OHKO: if (!ShouldTryOHKO(battlerAtk, battlerDef, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], move)) ADJUST_SCORE(-10); else if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) @@ -2194,24 +2198,27 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_PROTECT: { bool32 decreased = FALSE; - switch (move) + enum ProtectMethod protectMethod = GetMoveProtectMethod(move); + switch (protectMethod) { - case MOVE_QUICK_GUARD: - case MOVE_WIDE_GUARD: - case MOVE_CRAFTY_SHIELD: + case PROTECT_QUICK_GUARD: + case PROTECT_WIDE_GUARD: + case PROTECT_CRAFTY_SHIELD: if (!isDoubleBattle) { ADJUST_SCORE(-10); decreased = TRUE; } break; - case MOVE_MAT_BLOCK: + case PROTECT_MAT_BLOCK: if (!gDisableStructs[battlerAtk].isFirstTurn) { ADJUST_SCORE(-10); decreased = TRUE; } break; + default: + break; } // move check if (decreased) @@ -2222,9 +2229,9 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; } - if (move != MOVE_QUICK_GUARD - && move != MOVE_WIDE_GUARD - && move != MOVE_CRAFTY_SHIELD) //These moves have infinite usage + if (protectMethod != PROTECT_QUICK_GUARD + && protectMethod != PROTECT_WIDE_GUARD + && protectMethod != PROTECT_CRAFTY_SHIELD) //These moves have infinite usage { if (GetBattlerSecondaryDamage(battlerAtk) >= gBattleMons[battlerAtk].hp && !(IsMoxieTypeAbility(aiData->abilities[battlerDef]))) @@ -2704,7 +2711,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case EFFECT_INSTRUCT: { - u16 instructedMove; + u32 instructedMove; if (AI_IsSlower(battlerAtk, battlerDef, move)) instructedMove = predictedMove; else @@ -2729,15 +2736,16 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } else { + enum BattleMoveEffects instructedEffect = GetMoveEffect(instructedMove); if (GetBattlerMoveTargetType(battlerDef, instructedMove) & (MOVE_TARGET_SELECTED | MOVE_TARGET_DEPENDS | MOVE_TARGET_RANDOM | MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY | MOVE_TARGET_OPPONENTS_FIELD) - && instructedMove != MOVE_MIND_BLOWN && instructedMove != MOVE_STEEL_BEAM) + && instructedEffect != EFFECT_MIND_BLOWN && instructedEffect != EFFECT_MAX_HP_50_RECOIL) ADJUST_SCORE(-10); //Don't force the enemy to attack you again unless it can kill itself with Mind Blown - else if (instructedMove != MOVE_MIND_BLOWN) + else if (instructedEffect != EFFECT_MIND_BLOWN) ADJUST_SCORE(-5); //Do something better } } @@ -2932,7 +2940,9 @@ static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if (IsBattleMoveStatus(move)) return score; // status moves aren't accounted here - if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, AI_ATTACKING) && GetMoveEffect(move) != EFFECT_EXPLOSION) + enum BattleMoveEffects effect = GetMoveEffect(move); + if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, AI_ATTACKING) + && effect != EFFECT_EXPLOSION && effect != EFFECT_MISTY_EXPLOSION) { if (CanIndexMoveGuaranteeFaintTarget(battlerAtk, battlerDef, movesetIndex)) ADJUST_SCORE(1); // Bonus point if the KO is guaranteed @@ -3108,6 +3118,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) switch (effect) { case EFFECT_EXPLOSION: + case EFFECT_MISTY_EXPLOSION: if (gAiThinkingStruct->aiFlags[battlerAtk] & (AI_FLAG_RISKY | AI_FLAG_WILL_SUICIDE)) { RETURN_SCORE_PLUS(10); @@ -3860,6 +3871,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(DECENT_EFFECT); break; case EFFECT_EXPLOSION: + case EFFECT_MISTY_EXPLOSION: case EFFECT_MEMENTO: if (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_WILL_SUICIDE && gBattleMons[battlerDef].statStages[STAT_EVASION] < 7) { @@ -4109,6 +4121,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) } break; case EFFECT_OHKO: + case EFFECT_SHEER_COLD: if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) break; else if (gStatuses3[battlerAtk] & STATUS3_ALWAYS_HITS) @@ -4234,7 +4247,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(BEST_EFFECT); break; case EFFECT_LOCK_ON: - if (HasMoveWithEffect(battlerAtk, EFFECT_OHKO)) + if (HasMoveWithEffect(battlerAtk, EFFECT_OHKO) || HasMoveWithEffect(battlerAtk, EFFECT_SHEER_COLD)) ADJUST_SCORE(GOOD_EFFECT); else if (HasMoveWithLowAccuracy(battlerAtk, battlerDef, 85, TRUE, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef])) ADJUST_SCORE(GOOD_EFFECT); @@ -4270,13 +4283,14 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) case EFFECT_PROTECT: if (predictedMove == 0xFFFF) predictedMove = MOVE_NONE; - switch (move) + enum ProtectMethod protectMethod = GetMoveProtectMethod(move); + switch (protectMethod) { - case MOVE_QUICK_GUARD: + case PROTECT_QUICK_GUARD: if (predictedMove != MOVE_NONE && GetMovePriority(predictedMove) > 0) ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score); break; - case MOVE_WIDE_GUARD: + case PROTECT_WIDE_GUARD: if (predictedMove != MOVE_NONE && GetBattlerMoveTargetType(battlerDef, predictedMove) & (MOVE_TARGET_FOES_AND_ALLY | MOVE_TARGET_BOTH)) { ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score); @@ -4287,17 +4301,17 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score); } break; - case MOVE_CRAFTY_SHIELD: + case PROTECT_CRAFTY_SHIELD: if (predictedMove != MOVE_NONE && IsBattleMoveStatus(predictedMove) && !(GetBattlerMoveTargetType(battlerDef, predictedMove) & MOVE_TARGET_USER)) ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score); break; - case MOVE_MAT_BLOCK: + case PROTECT_MAT_BLOCK: if (gDisableStructs[battlerAtk].isFirstTurn && predictedMove != MOVE_NONE && !IsBattleMoveStatus(predictedMove) && !(GetBattlerMoveTargetType(battlerDef, predictedMove) & MOVE_TARGET_USER)) ProtectChecks(battlerAtk, battlerDef, move, predictedMove, &score); break; - case MOVE_KINGS_SHIELD: + case PROTECT_KINGS_SHIELD: if (aiData->abilities[battlerAtk] == ABILITY_STANCE_CHANGE //Special logic for Aegislash && gBattleMons[battlerAtk].species == SPECIES_AEGISLASH_BLADE && !IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef])) @@ -4444,7 +4458,9 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) { enum BattleMoveEffects predictedEffect = GetMoveEffect(predictedMove); if ((AI_IsFaster(battlerAtk, battlerDef, move)) - && (predictedEffect == EFFECT_EXPLOSION || predictedEffect == EFFECT_PROTECT)) + && (predictedEffect == EFFECT_EXPLOSION + || predictedEffect == EFFECT_MISTY_EXPLOSION + || predictedEffect == EFFECT_PROTECT)) ADJUST_SCORE(GOOD_EFFECT); else if (predictedEffect == EFFECT_SEMI_INVULNERABLE && !(gStatuses3[battlerDef] & STATUS3_SEMI_INVULNERABLE)) ADJUST_SCORE(GOOD_EFFECT); @@ -5512,6 +5528,7 @@ static s32 AI_Risky(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(STRONG_RISKY_EFFECT); break; case EFFECT_EXPLOSION: + case EFFECT_MISTY_EXPLOSION: ADJUST_SCORE(STRONG_RISKY_EFFECT); break; @@ -5534,6 +5551,7 @@ static s32 AI_Risky(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_FLATTER: case EFFECT_ATTRACT: case EFFECT_OHKO: + case EFFECT_SHEER_COLD: ADJUST_SCORE(AVERAGE_RISKY_EFFECT); break; case EFFECT_HIT: @@ -5610,7 +5628,7 @@ static s32 AI_PreferBatonPass(u32 battlerAtk, u32 battlerDef, u32 move, s32 scor ADJUST_SCORE(DECENT_EFFECT); break; case EFFECT_PROTECT: - if (gLastMoves[battlerAtk] == MOVE_PROTECT || gLastMoves[battlerAtk] == MOVE_DETECT) + if (GetProtectType(GetMoveProtectMethod(gLastMoves[battlerAtk])) == PROTECT_TYPE_SINGLE) ADJUST_SCORE(-2); else ADJUST_SCORE(DECENT_EFFECT); @@ -5664,6 +5682,7 @@ static s32 AI_HPAware(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) switch (effect) { case EFFECT_EXPLOSION: + case EFFECT_MISTY_EXPLOSION: case EFFECT_RESTORE_HP: case EFFECT_REST: case EFFECT_DESTINY_BOND: @@ -5692,6 +5711,7 @@ static s32 AI_HPAware(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) switch (effect) { case EFFECT_EXPLOSION: + case EFFECT_MISTY_EXPLOSION: case EFFECT_BIDE: case EFFECT_CONVERSION: case EFFECT_LIGHT_SCREEN: diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 2080a88a3b..fdfefd7238 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -209,7 +209,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) // Check if mon has an "important" status move if (aiMoveEffect == EFFECT_REFLECT || aiMoveEffect == EFFECT_LIGHT_SCREEN || aiMoveEffect == EFFECT_SPIKES || aiMoveEffect == EFFECT_TOXIC_SPIKES || aiMoveEffect == EFFECT_STEALTH_ROCK || aiMoveEffect == EFFECT_STICKY_WEB || aiMoveEffect == EFFECT_LEECH_SEED - || aiMoveEffect == EFFECT_EXPLOSION + || aiMoveEffect == EFFECT_EXPLOSION || aiMoveEffect == EFFECT_MISTY_EXPLOSION || nonVolatileStatus == MOVE_EFFECT_SLEEP || nonVolatileStatus == MOVE_EFFECT_TOXIC || nonVolatileStatus == MOVE_EFFECT_PARALYSIS @@ -2118,7 +2118,9 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, } // If a self destruction move doesn't OHKO, don't factor it into revenge killing - if (GetMoveEffect(aiMove) == EFFECT_EXPLOSION && damageDealt < playerMonHP) + enum BattleMoveEffects aiEffect = GetMoveEffect(aiMove); + if ((aiEffect == EFFECT_EXPLOSION || aiEffect == EFFECT_MISTY_EXPLOSION) + && damageDealt < playerMonHP) continue; // Check that mon isn't one shot and set best damage mon diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 34fda88378..5d263e9b56 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -561,6 +561,7 @@ bool32 IsDamageMoveUnusable(u32 battlerAtk, u32 battlerDef, u32 move, u32 moveTy return TRUE; break; case EFFECT_EXPLOSION: + case EFFECT_MISTY_EXPLOSION: case EFFECT_MIND_BLOWN: if (battlerDefAbility == ABILITY_DAMP || partnerDefAbility == ABILITY_DAMP) return TRUE; @@ -1022,6 +1023,7 @@ static bool32 AI_IsMoveEffectInMinus(u32 battlerAtk, u32 battlerDef, u32 move, s case EFFECT_MAX_HP_50_RECOIL: case EFFECT_MIND_BLOWN: case EFFECT_EXPLOSION: + case EFFECT_MISTY_EXPLOSION: case EFFECT_FINAL_GAMBIT: return TRUE; case EFFECT_RECOIL: @@ -1777,7 +1779,7 @@ bool32 ShouldTryOHKO(u32 battlerAtk, u32 battlerDef, u32 atkAbility, u32 defAbil else // test the odds { u32 odds = accuracy + (gBattleMons[battlerAtk].level - gBattleMons[battlerDef].level); - if (B_SHEER_COLD_ACC >= GEN_7 && move == MOVE_SHEER_COLD && !IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_ICE)) + if (B_SHEER_COLD_ACC >= GEN_7 && GetMoveEffect(move) == EFFECT_SHEER_COLD && !IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_ICE)) odds -= 10; if (Random() % 100 + 1 < odds && gBattleMons[battlerAtk].level >= gBattleMons[battlerDef].level) return TRUE; @@ -4478,7 +4480,7 @@ bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove) return FALSE; } -void SetAIUsingGimmick(u32 battler, enum AIConsiderGimmick use) +void SetAIUsingGimmick(u32 battler, enum AIConsiderGimmick use) { if (use == USE_GIMMICK) gAiBattleData->aiUsingGimmick |= (1<aiUsingGimmick &= ~(1<aiUsingGimmick & (1<gimmick.usableGimmick[battler] != GIMMICK_TERA) + if (gBattleStruct->gimmick.usableGimmick[battler] != GIMMICK_TERA) return; - - if (!(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_TERA)) + + if (!(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_TERA)) return; - + // TODO: Currently only single battles are considered. - if (IsDoubleBattle()) - return; - - // TODO: A lot of these checks are most effective for an omnicient ai. + if (IsDoubleBattle()) + return; + + // TODO: A lot of these checks are most effective for an omnicient ai. // If we don't have enough information about the opponent's moves, consider simpler checks based on type effectivness. u32 opposingBattler = GetOppositeBattler(battler); @@ -4528,20 +4530,20 @@ void DecideTerastal(u32 battler) uq4_12_t effectiveness; - for (int i = 0; i < MAX_MON_MOVES; i++) + for (int i = 0; i < MAX_MON_MOVES; i++) { - if (!IsMoveUnusable(i, aiMoves[i], gAiLogicData->moveLimitations[battler]) && !IsBattleMoveStatus(aiMoves[i])) + if (!IsMoveUnusable(i, aiMoves[i], gAiLogicData->moveLimitations[battler]) && !IsBattleMoveStatus(aiMoves[i])) altCalcs.dealtWithoutTera[i] = AI_CalcDamage(aiMoves[i], battler, opposingBattler, &effectiveness, NO_GIMMICK, NO_GIMMICK, AI_GetWeather()); - else + else altCalcs.dealtWithoutTera[i] = noDmg; - - if (!IsMoveUnusable(i, oppMoves[i], gAiLogicData->moveLimitations[opposingBattler]) && !IsBattleMoveStatus(oppMoves[i])) + + if (!IsMoveUnusable(i, oppMoves[i], gAiLogicData->moveLimitations[opposingBattler]) && !IsBattleMoveStatus(oppMoves[i])) { altCalcs.takenWithTera[i] = AI_CalcDamage(oppMoves[i], opposingBattler, battler, &effectiveness, USE_GIMMICK, USE_GIMMICK, AI_GetWeather()); effectivenessTakenWithTera[i] = effectiveness; } - else + else { altCalcs.takenWithTera[i] = noDmg; effectivenessTakenWithTera[i] = Q_4_12(0.0); @@ -4552,19 +4554,19 @@ void DecideTerastal(u32 battler) enum AIConsiderGimmick res = ShouldTeraFromCalcs(battler, opposingBattler, &altCalcs); - if (res == USE_GIMMICK) + if (res == USE_GIMMICK) { // Damage calcs for damage received assumed we wouldn't tera. Adjust that so that further AI decisions are more accurate. - for (int i = 0; i < MAX_MON_MOVES; i++) + for (int i = 0; i < MAX_MON_MOVES; i++) { gAiLogicData->simulatedDmg[opposingBattler][battler][i] = altCalcs.takenWithTera[i]; gAiLogicData->effectiveness[opposingBattler][battler][i] = effectivenessTakenWithTera[i]; } } - else + else { - // Damage calcs for damage dealt assumed we would tera. Adjust that so that further AI decisions are more accurate. - for (int i = 0; i < MAX_MON_MOVES; i++) + // Damage calcs for damage dealt assumed we would tera. Adjust that so that further AI decisions are more accurate. + for (int i = 0; i < MAX_MON_MOVES; i++) gAiLogicData->simulatedDmg[battler][opposingBattler][i] = altCalcs.dealtWithoutTera[i]; } @@ -4582,13 +4584,13 @@ enum AIConsiderGimmick ShouldTeraFromCalcs(u32 battler, u32 opposingBattler, str struct Pokemon* party = GetBattlerParty(battler); // Check how many pokemon we have that could tera - int numPossibleTera = 0; - for (int i = 0; i < PARTY_SIZE; i++) + int numPossibleTera = 0; + for (int i = 0; i < PARTY_SIZE; i++) { if (GetMonData(&party[i], MON_DATA_HP) != 0 && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE && GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG - && GetMonData(&party[i], MON_DATA_TERA_TYPE) > 0) + && GetMonData(&party[i], MON_DATA_TERA_TYPE) > 0) numPossibleTera++; } @@ -4602,44 +4604,44 @@ enum AIConsiderGimmick ShouldTeraFromCalcs(u32 battler, u32 opposingBattler, str bool32 hasKoWithout = FALSE; u16 killingMove = MOVE_NONE; - for (int i = 0; i < MAX_MON_MOVES; i++) + for (int i = 0; i < MAX_MON_MOVES; i++) { - if (dealtWithTera[i].median >= oppHp) + if (dealtWithTera[i].median >= oppHp) { u16 move = aiMoves[i]; if (killingMove == MOVE_NONE || GetBattleMovePriority(battler, gAiLogicData->abilities[battler], move) > GetBattleMovePriority(battler, gAiLogicData->abilities[battler], killingMove)) killingMove = move; - } - if (dealtWithoutTera[i].median >= oppHp) + } + if (dealtWithoutTera[i].median >= oppHp) hasKoWithout = TRUE; } bool32 enablesKo = (killingMove != MOVE_NONE) && !hasKoWithout; // Check whether tera saves us from a KO - bool32 savedFromKo = FALSE; + bool32 savedFromKo = FALSE; bool32 getsKodRegardlessBySingleMove = FALSE; - for (int i = 0; i < MAX_MON_MOVES; i++) + for (int i = 0; i < MAX_MON_MOVES; i++) { - if (takenWithoutTera[i].maximum >= aiHp && takenWithTera[i].maximum >= aiHp) + if (takenWithoutTera[i].maximum >= aiHp && takenWithTera[i].maximum >= aiHp) getsKodRegardlessBySingleMove = TRUE; - if (takenWithoutTera[i].maximum >= aiHp && takenWithTera[i].maximum < aiHp) + if (takenWithoutTera[i].maximum >= aiHp && takenWithTera[i].maximum < aiHp) savedFromKo = TRUE; } if (getsKodRegardlessBySingleMove) savedFromKo = FALSE; - // Check whether opponent can punish tera by ko'ing + // Check whether opponent can punish tera by ko'ing u16 hardPunishingMove = MOVE_NONE; - for (int i = 0; i < MAX_MON_MOVES; i++) + for (int i = 0; i < MAX_MON_MOVES; i++) { - if (takenWithTera[i].maximum >= aiHp) + if (takenWithTera[i].maximum >= aiHp) { u16 move = oppMoves[i]; - if (hardPunishingMove == MOVE_NONE || GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], move) > GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], hardPunishingMove)) + if (hardPunishingMove == MOVE_NONE || GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], move) > GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], hardPunishingMove)) hardPunishingMove = move; } } @@ -4648,32 +4650,32 @@ enum AIConsiderGimmick ShouldTeraFromCalcs(u32 battler, u32 opposingBattler, str // (e.g. a weakness becomes a resistance, a 4x weakness becomes neutral, etc) bool32 takesBigHit = FALSE; bool32 savedFromAllBigHits = TRUE; - for (int i = 0; i < MAX_MON_MOVES; i++) + for (int i = 0; i < MAX_MON_MOVES; i++) { - if (takenWithoutTera[i].median > aiHp/2) + if (takenWithoutTera[i].median > aiHp/2) { - takesBigHit = TRUE; - if (takenWithTera[i].median > aiHp/4) + takesBigHit = TRUE; + if (takenWithTera[i].median > aiHp/4) savedFromAllBigHits = FALSE; } } // Check for any benefit whatsoever. Only used for the last possible mon that could tera. bool32 anyOffensiveBenefit = FALSE; - for (int i = 0; i < MAX_MON_MOVES; i++) + for (int i = 0; i < MAX_MON_MOVES; i++) { - if (dealtWithTera[i].median > dealtWithoutTera[i].median) + if (dealtWithTera[i].median > dealtWithoutTera[i].median) anyOffensiveBenefit = TRUE; - } + } bool32 anyDefensiveBenefit = FALSE; - bool32 anyDefensiveDrawback = FALSE; - for (int i = 0; i < MAX_MON_MOVES; i++) + bool32 anyDefensiveDrawback = FALSE; + for (int i = 0; i < MAX_MON_MOVES; i++) { - if (takenWithTera[i].median < takenWithoutTera[i].median) + if (takenWithTera[i].median < takenWithoutTera[i].median) anyDefensiveBenefit = TRUE; - if (takenWithTera[i].median > takenWithoutTera[i].median) + if (takenWithTera[i].median > takenWithoutTera[i].median) anyDefensiveDrawback = TRUE; } @@ -4681,55 +4683,55 @@ enum AIConsiderGimmick ShouldTeraFromCalcs(u32 battler, u32 opposingBattler, str // This is done after all loops to minimize the possibility of a timing attack in which the player could // determine whether the AI will tera based on the time taken to select a move. - if (enablesKo) + if (enablesKo) { - if (hardPunishingMove == MOVE_NONE) + if (hardPunishingMove == MOVE_NONE) { return USE_GIMMICK; } - else + else { // will we go first? - if (AI_WhoStrikesFirst(battler, opposingBattler, killingMove) == AI_IS_FASTER && GetBattleMovePriority(battler, gAiLogicData->abilities[battler], killingMove) >= GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], hardPunishingMove)) + if (AI_WhoStrikesFirst(battler, opposingBattler, killingMove) == AI_IS_FASTER && GetBattleMovePriority(battler, gAiLogicData->abilities[battler], killingMove) >= GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], hardPunishingMove)) return USE_GIMMICK; } } // Decide to conserve tera based on number of possible later oppotunities u16 conserveTeraChance = AI_CONSERVE_TERA_CHANCE_PER_MON * (numPossibleTera-1); - if (RandomPercentage(RNG_AI_CONSERVE_TERA, conserveTeraChance)) + if (RandomPercentage(RNG_AI_CONSERVE_TERA, conserveTeraChance)) return NO_GIMMICK; - if (savedFromKo) + if (savedFromKo) { - if (hardPunishingMove == MOVE_NONE) + if (hardPunishingMove == MOVE_NONE) { return USE_GIMMICK; } - else + else { // If tera saves us from a ko from one move, but enables a ko otherwise, randomly predict // savesFromKo being true ensures opponent doesn't have a ko if we don't tera - if (Random() % 100 < AI_TERA_PREDICT_CHANCE) + if (Random() % 100 < AI_TERA_PREDICT_CHANCE) return USE_GIMMICK; } } - if (hardPunishingMove != MOVE_NONE) + if (hardPunishingMove != MOVE_NONE) return NO_GIMMICK; - if (takesBigHit && savedFromAllBigHits) + if (takesBigHit && savedFromAllBigHits) return USE_GIMMICK; - // No strongly compelling reason to tera. Conserve it if possible. - if (numPossibleTera > 1) + // No strongly compelling reason to tera. Conserve it if possible. + if (numPossibleTera > 1) return NO_GIMMICK; - if (anyOffensiveBenefit || (anyDefensiveBenefit && !anyDefensiveDrawback)) + if (anyOffensiveBenefit || (anyDefensiveBenefit && !anyDefensiveDrawback)) return USE_GIMMICK; // TODO: Effects other than direct damage are not yet considered. For example, may want to tera poison to avoid a Toxic. - + return NO_GIMMICK; } diff --git a/src/battle_dome.c b/src/battle_dome.c index 0ded9ada1e..b53d5fdcc8 100644 --- a/src/battle_dome.c +++ b/src/battle_dome.c @@ -3938,6 +3938,7 @@ static bool32 IsDomeRiskyMoveEffect(enum BattleMoveEffects effect) switch(effect) { case EFFECT_EXPLOSION: + case EFFECT_MISTY_EXPLOSION: case EFFECT_SPITE: case EFFECT_DESTINY_BOND: case EFFECT_PERISH_SONG: @@ -3954,7 +3955,8 @@ static bool32 IsDomeLuckyMove(u32 move) switch(GetMoveEffect(move)) { case EFFECT_COUNTER: - case EFFECT_OHKO: // Technically redundant because of the above accuracy check + case EFFECT_OHKO: + case EFFECT_SHEER_COLD: case EFFECT_METRONOME: case EFFECT_MIRROR_MOVE: case EFFECT_SKETCH: @@ -5125,11 +5127,13 @@ static u16 GetWinningMove(int winnerTournamentId, int loserTournamentId, u8 roun moves[i * MAX_MON_MOVES + j] = gFacilityTrainerMons[DOME_MONS[winnerTournamentId][i]].moves[j]; movePower = GetMovePower(moves[i * MAX_MON_MOVES + j]); + enum BattleMoveEffects effect = GetMoveEffect(moves[i * MAX_MON_MOVES + j]); if (IsBattleMoveStatus(moves[i * MAX_MON_MOVES + j])) movePower = 40; else if (movePower == 1) movePower = 60; - else if (GetMoveEffect(moves[i * MAX_MON_MOVES + j]) == EFFECT_EXPLOSION) + else if (B_EXPLOSION_DEFENSE < GEN_5 + && (effect == EFFECT_EXPLOSION || EFFECT_MISTY_EXPLOSION)) movePower /= 2; for (k = 0; k < FRONTIER_PARTY_SIZE; k++) diff --git a/src/battle_dynamax.c b/src/battle_dynamax.c index c9f9947785..a34ecc21b3 100644 --- a/src/battle_dynamax.c +++ b/src/battle_dynamax.c @@ -407,6 +407,7 @@ static u32 GetMaxPowerTier(u32 move) case EFFECT_FINAL_GAMBIT: return MAX_POWER_TIER_2; case EFFECT_OHKO: + case EFFECT_SHEER_COLD: case EFFECT_RETURN: case EFFECT_FRUSTRATION: case EFFECT_HEAT_CRASH: diff --git a/src/battle_main.c b/src/battle_main.c index 9cf45280c1..2bf62fa1ed 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -6058,6 +6058,7 @@ void SetTypeBeforeUsingMove(u32 move, u32 battler) && GetBattleMoveType(move) == GetItemSecondaryId(heldItem) && effect != EFFECT_PLEDGE && effect != EFFECT_OHKO + && effect != EFFECT_SHEER_COLD && effect != EFFECT_STRUGGLE) { gSpecialStatuses[battler].gemParam = GetBattlerHoldEffectParam(battler); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 33414378ae..08531ef5f2 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1248,7 +1248,10 @@ static void Cmd_attackcanceler(void) enum BattleMoveEffects effect = GetMoveEffect(gCurrentMove); - if (!IsBattlerAlive(gBattlerAttacker) && effect != EFFECT_EXPLOSION && !(gHitMarker & HITMARKER_NO_ATTACKSTRING)) + if (!IsBattlerAlive(gBattlerAttacker) + && effect != EFFECT_EXPLOSION + && effect != EFFECT_MISTY_EXPLOSION + && !(gHitMarker & HITMARKER_NO_ATTACKSTRING)) { gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; gBattlescriptCurrInstr = BattleScript_MoveEnd; @@ -1515,7 +1518,8 @@ static bool32 AccuracyCalcHelper(u32 move, u32 battler) // If the target is under the effects of Telekinesis, and the move isn't a OH-KO move, move hits. else if (gStatuses3[battler] & STATUS3_TELEKINESIS && !(gStatuses3[battler] & STATUS3_SEMI_INVULNERABLE) - && moveEffect != EFFECT_OHKO) + && moveEffect != EFFECT_OHKO + && moveEffect != EFFECT_SHEER_COLD) { effect = TRUE; } @@ -2459,14 +2463,15 @@ static void Cmd_attackanimation(void) u32 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove); u32 moveResultFlags = gBattleStruct->moveResultFlags[gBattlerTarget]; + enum BattleMoveEffects effect = GetMoveEffect(gCurrentMove); if (IsDoubleSpreadMove()) moveResultFlags = UpdateEffectivenessResultFlagsForDoubleSpreadMoves(gBattleStruct->moveResultFlags[gBattlerTarget]); if ((gHitMarker & (HITMARKER_NO_ANIMATIONS | HITMARKER_DISABLE_ANIMATION)) - && gCurrentMove != MOVE_TRANSFORM - && gCurrentMove != MOVE_SUBSTITUTE - && gCurrentMove != MOVE_ALLY_SWITCH + && effect != EFFECT_TRANSFORM + && effect != EFFECT_SUBSTITUTE + && effect != EFFECT_ALLY_SWITCH // In a wild double battle gotta use the teleport animation if two wild pokemon are alive. && !(GetMoveEffect(gCurrentMove) == EFFECT_TELEPORT && WILD_DOUBLE_BATTLE && !IsOnPlayerSide(gBattlerAttacker) && IsBattlerAlive(BATTLE_PARTNER(gBattlerAttacker)))) { @@ -6820,7 +6825,7 @@ static void Cmd_moveend(void) MoveValuesCleanUp(); gBattleScripting.moveEffect = gBattleScripting.savedMoveEffect; - if (moveEffect == EFFECT_EXPLOSION) + if (moveEffect == EFFECT_EXPLOSION || moveEffect == EFFECT_MISTY_EXPLOSION) BattleScriptPush(gBattleMoveEffects[EFFECT_HIT].battleScript); // Edge case for Explosion not changing targets else BattleScriptPush(GetMoveBattleScript(gCurrentMove)); @@ -6984,6 +6989,7 @@ static void Cmd_moveend(void) } break; case EFFECT_EXPLOSION: + case EFFECT_MISTY_EXPLOSION: if (!IsAbilityOnField(ABILITY_DAMP)) { gBattleStruct->moveDamage[gBattlerAttacker] = 0; @@ -9111,7 +9117,7 @@ static bool32 TrySymbiosis(u32 battler, u32 itemId) && GetBattlerHoldEffect(battler, TRUE) != HOLD_EFFECT_EJECT_BUTTON && GetBattlerHoldEffect(battler, TRUE) != HOLD_EFFECT_EJECT_PACK && (B_SYMBIOSIS_GEMS < GEN_7 || !(gSpecialStatuses[battler].gemBoost)) - && gCurrentMove != MOVE_FLING //Fling and damage-reducing berries are handled separately. + && GetMoveEffect(gCurrentMove) != EFFECT_FLING //Fling and damage-reducing berries are handled separately. && !gSpecialStatuses[battler].berryReduced && TryTriggerSymbiosis(battler, BATTLE_PARTNER(battler))) { @@ -12860,6 +12866,7 @@ static void Cmd_tryKO(void) CMD_ARGS(const u8 *failInstr); bool32 lands = FALSE; + enum BattleMoveEffects effect = GetMoveEffect(gCurrentMove); enum ItemHoldEffect holdEffect = GetBattlerHoldEffect(gBattlerTarget, TRUE); u16 targetAbility = GetBattlerAbility(gBattlerTarget); u32 rand = Random() % 100; @@ -12915,7 +12922,7 @@ static void Cmd_tryKO(void) else { u16 odds = GetMoveAccuracy(gCurrentMove) + (gBattleMons[gBattlerAttacker].level - gBattleMons[gBattlerTarget].level); - if (B_SHEER_COLD_ACC >= GEN_7 && gCurrentMove == MOVE_SHEER_COLD && !IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_ICE)) + if (B_SHEER_COLD_ACC >= GEN_7 && effect == EFFECT_SHEER_COLD && !IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_ICE)) odds -= 10; if (RandomPercentage(RNG_ACCURACY, odds) && gBattleMons[gBattlerAttacker].level >= gBattleMons[gBattlerTarget].level) lands = TRUE; diff --git a/src/battle_tv.c b/src/battle_tv.c index 732216d7ec..e7c182e537 100644 --- a/src/battle_tv.c +++ b/src/battle_tv.c @@ -599,7 +599,8 @@ void BattleTv_SetDataBasedOnMove(u16 move, u16 weatherFlags, struct DisableStruc tvPtr->side[atkSide].wishMonId = gBattlerPartyIndexes[gBattlerAttacker] + 1; tvPtr->side[atkSide].wishMoveSlot = moveSlot; } - if (GetMoveEffect(move) == EFFECT_EXPLOSION) + enum BattleMoveEffects effect = GetMoveEffect(move); + if (effect == EFFECT_EXPLOSION || effect == EFFECT_MISTY_EXPLOSION) { tvPtr->side[atkSide ^ BIT_SIDE].explosionMonId = gBattlerPartyIndexes[gBattlerAttacker] + 1; tvPtr->side[atkSide ^ BIT_SIDE].explosionMoveSlot = moveSlot; diff --git a/src/battle_util.c b/src/battle_util.c index 7493b38363..d6034a1478 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -2333,9 +2333,11 @@ static void CancellerPsychicTerrain(u32 *effect) static void CancellerExplodingDamp(u32 *effect) { + enum BattleMoveEffects moveEffect = GetMoveEffect(gCurrentMove); u32 dampBattler = IsAbilityOnField(ABILITY_DAMP); - if (dampBattler && (GetMoveEffect(gCurrentMove) == EFFECT_EXPLOSION - || GetMoveEffect(gCurrentMove) == EFFECT_MIND_BLOWN)) + if (dampBattler && (moveEffect == EFFECT_EXPLOSION + || moveEffect == EFFECT_MISTY_EXPLOSION + || moveEffect == EFFECT_MIND_BLOWN)) { gBattleScripting.battler = dampBattler - 1; gBattlescriptCurrInstr = BattleScript_DampStopsExplosion; @@ -2776,6 +2778,7 @@ static void ForewarnChooseMove(u32 battler) switch (GetMoveEffect(data[count].moveId)) { case EFFECT_OHKO: + case EFFECT_SHEER_COLD: data[count].power = 150; break; case EFFECT_COUNTER: @@ -3594,7 +3597,9 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 { move = gBattleMons[i].moves[j]; moveType = GetBattleMoveType(move); - if (CalcTypeEffectivenessMultiplier(move, moveType, i, battler, ABILITY_ANTICIPATION, FALSE) >= UQ_4_12(2.0) || GetMoveEffect(move) == EFFECT_OHKO) + enum BattleMoveEffects moveEffect = GetMoveEffect(move); + if (CalcTypeEffectivenessMultiplier(move, moveType, i, battler, ABILITY_ANTICIPATION, FALSE) >= UQ_4_12(2.0) + || moveEffect == EFFECT_OHKO || moveEffect == EFFECT_SHEER_COLD) { effect++; break; @@ -8087,8 +8092,8 @@ static inline u32 CalcMoveBasePower(struct DamageCalculationData *damageCalcData if (gProtectStructs[battlerAtk].lashOutAffected) basePower *= 2; break; - case EFFECT_EXPLOSION: - if (move == MOVE_MISTY_EXPLOSION && IsBattlerTerrainAffected(battlerAtk, STATUS_FIELD_MISTY_TERRAIN)) + case EFFECT_MISTY_EXPLOSION: + if (IsBattlerTerrainAffected(battlerAtk, STATUS_FIELD_MISTY_TERRAIN)) basePower = uq4_12_multiply(basePower, UQ_4_12(1.5)); break; case EFFECT_DYNAMAX_DOUBLE_DMG: @@ -8800,7 +8805,8 @@ static inline u32 CalcDefenseStat(struct DamageCalculationData *damageCalcData, } // Self-destruct / Explosion cut defense in half - if (B_EXPLOSION_DEFENSE < GEN_5 && moveEffect == EFFECT_EXPLOSION) + if (B_EXPLOSION_DEFENSE < GEN_5 && (moveEffect == EFFECT_EXPLOSION + || moveEffect == EFFECT_MISTY_EXPLOSION)) defStat /= 2; // critical hits ignore positive stat changes @@ -9595,7 +9601,7 @@ static inline uq4_12_t CalcTypeEffectivenessMultiplierInternal(u32 move, u32 mov RecordAbilityBattle(battlerDef, ABILITY_LEVITATE); } } - else if (B_SHEER_COLD_IMMUNITY >= GEN_7 && move == MOVE_SHEER_COLD && IS_BATTLER_OF_TYPE(battlerDef, TYPE_ICE)) + else if (B_SHEER_COLD_IMMUNITY >= GEN_7 && GetMoveEffect(move) == EFFECT_SHEER_COLD && IS_BATTLER_OF_TYPE(battlerDef, TYPE_ICE)) { modifier = UQ_4_12(0.0); } diff --git a/src/battle_z_move.c b/src/battle_z_move.c index 53df466af8..4f33ff5a17 100644 --- a/src/battle_z_move.c +++ b/src/battle_z_move.c @@ -549,7 +549,8 @@ u32 GetZMovePower(u32 move) { if (GetMoveCategory(move) == DAMAGE_CATEGORY_STATUS) return 0; - if (GetMoveEffect(move) == EFFECT_OHKO) + enum BattleMoveEffects moveEffect = GetMoveEffect(move); + if (moveEffect == EFFECT_OHKO || moveEffect == EFFECT_SHEER_COLD) return 180; u32 power = GetMoveZPowerOverride(move); @@ -568,4 +569,3 @@ u32 GetZMovePower(u32 move) else if (power >= 60) return 120; else return 100; } - diff --git a/src/data/battle_move_effects.h b/src/data/battle_move_effects.h index b116b61999..27fb0a55ea 100644 --- a/src/data/battle_move_effects.h +++ b/src/data/battle_move_effects.h @@ -34,6 +34,12 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] = .battleTvScore = 0, // TODO: Assign points }, + [EFFECT_MISTY_EXPLOSION] = + { + .battleScript = BattleScript_EffectExplosion, + .battleTvScore = 0, // TODO: Assign points + }, + [EFFECT_DREAM_EATER] = { .battleScript = BattleScript_EffectDreamEater, @@ -203,6 +209,12 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] = .battleTvScore = 7, }, + [EFFECT_SHEER_COLD] = + { + .battleScript = BattleScript_EffectOHKO, + .battleTvScore = 7, + }, + [EFFECT_FUSION_COMBO] = { .battleScript = BattleScript_EffectHit, diff --git a/src/data/moves_info.h b/src/data/moves_info.h index db76c62ab8..217ef20689 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -8686,7 +8686,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .description = COMPOUND_STRING( "A chilling attack that\n" "causes fainting if it hits."), - .effect = EFFECT_OHKO, + .effect = EFFECT_SHEER_COLD, .power = 1, .type = TYPE_ICE, .accuracy = 30, @@ -18544,7 +18544,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .description = COMPOUND_STRING( "Hit everything and faint.\n" "Powers up on Misty Terrain."), - .effect = EFFECT_EXPLOSION, + .effect = EFFECT_MISTY_EXPLOSION, .power = 100, .type = TYPE_FAIRY, .accuracy = 100, diff --git a/test/battle/move_animations/all_anims.c b/test/battle/move_animations/all_anims.c index e4144ebb9f..cb21a1df05 100644 --- a/test/battle/move_animations/all_anims.c +++ b/test/battle/move_animations/all_anims.c @@ -76,6 +76,7 @@ static bool32 AttackerHasToSwitch(u32 move) // User needs to send out a differen { if (gMovesInfo[move].effect == EFFECT_TELEPORT || gMovesInfo[move].effect == EFFECT_EXPLOSION + || gMovesInfo[move].effect == EFFECT_MISTY_EXPLOSION || gMovesInfo[move].effect == EFFECT_BATON_PASS || gMovesInfo[move].effect == EFFECT_MEMENTO || gMovesInfo[move].effect == EFFECT_HEALING_WISH diff --git a/test/battle/move_effect/ohko.c b/test/battle/move_effect/ohko.c index 562045afb3..580219e3db 100644 --- a/test/battle/move_effect/ohko.c +++ b/test/battle/move_effect/ohko.c @@ -3,22 +3,7 @@ ASSUMPTIONS { - ASSUME(GetMoveEffect(MOVE_SHEER_COLD) == EFFECT_OHKO); -} - -SINGLE_BATTLE_TEST("Sheer Cold doesn't affect Ice-type Pokémon") -{ - GIVEN { - ASSUME(B_SHEER_COLD_IMMUNITY >= GEN_7); - ASSUME(GetSpeciesType(SPECIES_GLALIE, 0) == TYPE_ICE); - PLAYER(SPECIES_WYNAUT); - OPPONENT(SPECIES_GLALIE); - } WHEN { - TURN { MOVE(player, MOVE_SHEER_COLD); } - } SCENE { - NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player); - MESSAGE("It doesn't affect the opposing Glalie…"); - } + ASSUME(GetMoveEffect(MOVE_FISSURE) == EFFECT_OHKO); } SINGLE_BATTLE_TEST("OHKO moves can hit semi-invulnerable mons when the user has No-Guard") @@ -29,9 +14,9 @@ SINGLE_BATTLE_TEST("OHKO moves can hit semi-invulnerable mons when the user has OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(opponent, MOVE_FLY); } - TURN { MOVE(player, MOVE_SHEER_COLD); } + TURN { MOVE(player, MOVE_FISSURE); } } SCENE { - ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FISSURE, player); HP_BAR(opponent, hp: 0); } } @@ -42,9 +27,9 @@ SINGLE_BATTLE_TEST("OHKO moves can can be endured by Focus Sash") PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_FOCUS_SASH); } } WHEN { - TURN { MOVE(player, MOVE_SHEER_COLD); } + TURN { MOVE(player, MOVE_FISSURE); } } SCENE { - ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FISSURE, player); HP_BAR(opponent, hp: 1); MESSAGE("The opposing Wobbuffet hung on using its Focus Sash!"); } @@ -56,15 +41,14 @@ SINGLE_BATTLE_TEST("OHKO moves can can be endured by Sturdy") PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_GEODUDE) { Ability(ABILITY_STURDY); } } WHEN { - TURN { MOVE(player, MOVE_SHEER_COLD); } + TURN { MOVE(player, MOVE_FISSURE); } } SCENE { - NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_FISSURE, player); ABILITY_POPUP(opponent, ABILITY_STURDY); - MESSAGE("The opposing Geodude was protected by Sturdy!"); } } -TO_DO_BATTLE_TEST("Fissure faints the target, skipping regular damage calculations") -TO_DO_BATTLE_TEST("Fissure always fails if the target has a higher level than the user") -TO_DO_BATTLE_TEST("Fissure's accuracy increases by 1% for every level the user has over the target") -TO_DO_BATTLE_TEST("Fissure's ignores non-stage accuracy modifiers") // Gravity, Wide Lens, Compound Eyes +TO_DO_BATTLE_TEST("OHKO moves faints the target, skipping regular damage calculations") +TO_DO_BATTLE_TEST("OHKO moves always fails if the target has a higher level than the user") +TO_DO_BATTLE_TEST("OHKO moves's accuracy increases by 1% for every level the user has over the target") +TO_DO_BATTLE_TEST("OHKO moves's ignores non-stage accuracy modifiers") // Gravity, Wide Lens, Compound Eyes diff --git a/test/battle/move_effect/sheer_cold.c b/test/battle/move_effect/sheer_cold.c new file mode 100644 index 0000000000..2cb469e92d --- /dev/null +++ b/test/battle/move_effect/sheer_cold.c @@ -0,0 +1,70 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_SHEER_COLD) == EFFECT_SHEER_COLD); +} + +SINGLE_BATTLE_TEST("Sheer Cold doesn't affect Ice-type Pokémon") +{ + GIVEN { + ASSUME(B_SHEER_COLD_IMMUNITY >= GEN_7); + ASSUME(GetSpeciesType(SPECIES_GLALIE, 0) == TYPE_ICE); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_GLALIE); + } WHEN { + TURN { MOVE(player, MOVE_SHEER_COLD); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player); + MESSAGE("It doesn't affect the opposing Glalie…"); + } +} + +SINGLE_BATTLE_TEST("Sheer Cold can hit semi-invulnerable mons when the user has No-Guard") +{ + GIVEN { + ASSUME(GetItemHoldEffect(ITEM_FOCUS_SASH) == HOLD_EFFECT_FOCUS_SASH); + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_NO_GUARD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_FLY); } + TURN { MOVE(player, MOVE_SHEER_COLD); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player); + HP_BAR(opponent, hp: 0); + } +} + +SINGLE_BATTLE_TEST("Sheer Cold can be endured by Focus Sash") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_FOCUS_SASH); } + } WHEN { + TURN { MOVE(player, MOVE_SHEER_COLD); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player); + HP_BAR(opponent, hp: 1); + MESSAGE("The opposing Wobbuffet hung on using its Focus Sash!"); + } +} + +SINGLE_BATTLE_TEST("Sheer Cold can be endured by Sturdy") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GEODUDE) { Ability(ABILITY_STURDY); } + } WHEN { + TURN { MOVE(player, MOVE_SHEER_COLD); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player); + ABILITY_POPUP(opponent, ABILITY_STURDY); + } +} + +TO_DO_BATTLE_TEST("Sheer Cold faints the target, skipping regular damage calculations") +TO_DO_BATTLE_TEST("Sheer Cold always fails if the target has a higher level than the user") +TO_DO_BATTLE_TEST("Sheer Cold's accuracy increases by 1% for every level the user has over the target") +TO_DO_BATTLE_TEST("Sheer Cold's accuracy decreasaes by 10% if the user is not Ice type") +TO_DO_BATTLE_TEST("Sheer Cold's ignores non-stage accuracy modifiers") // Gravity, Wide Lens, Compound Eyes