From 6c05a08750ae2872bd51294273fa23fb6afb5aa7 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Wed, 21 Jan 2026 11:55:30 +0100 Subject: [PATCH] Refactor OHKO Moves (#8916) --- data/battle_scripts_1.s | 12 -- include/battle_scripts.h | 1 - include/battle_util.h | 2 + include/constants/battle.h | 12 +- include/constants/battle_move_effects.h | 1 - include/move.h | 35 +++- include/test/battle.h | 12 +- src/battle_ai_main.c | 8 +- src/battle_ai_util.c | 2 +- src/battle_controller_player_partner.c | 5 +- src/battle_dome.c | 1 - src/battle_dynamax.c | 1 - src/battle_gimmick.c | 20 +-- src/battle_main.c | 18 +- src/battle_script_commands.c | 162 +++--------------- src/battle_util.c | 105 ++++++++++-- src/battle_z_move.c | 2 +- src/data/battle_move_effects.h | 8 +- src/data/moves_info.h | 5 +- test/battle/ai/can_use_all_moves.c | 2 +- test/battle/gimmick/dynamax.c | 2 +- test/battle/move_animations/all_anims.c | 4 +- test/battle/move_effect/ohko.c | 45 +++++ test/battle/move_effect/sheer_cold.c | 86 ---------- .../move_has_no_effect_on_same_type.c | 39 +++++ 25 files changed, 282 insertions(+), 308 deletions(-) delete mode 100644 test/battle/move_effect/sheer_cold.c create mode 100644 test/battle/move_flags/move_has_no_effect_on_same_type.c diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index a57530f402..e4a934a053 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -2675,18 +2675,6 @@ BattleScript_AbilityPreventsRest:: waitmessage B_WAIT_TIME_LONG goto BattleScript_MoveEnd -BattleScript_EffectOHKO:: - attackcanceler - typecalc - jumpifmovehadnoeffect BattleScript_HitFromAtkAnimation - tryKO BattleScript_KOFail - goto BattleScript_HitFromAtkAnimation -BattleScript_KOFail:: - pause B_WAIT_TIME_LONG - printfromtable gKOFailedStringIds - waitmessage B_WAIT_TIME_LONG - goto BattleScript_MoveEnd - BattleScript_RecoilIfMiss:: printstring STRINGID_PKMNCRASHED waitmessage B_WAIT_TIME_LONG diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 230991de13..c70f80b8bc 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -587,7 +587,6 @@ extern const u8 BattleScript_EffectLightScreen[]; extern const u8 BattleScript_EffectRest[]; extern const u8 BattleScript_RestIsAlreadyAsleep[]; extern const u8 BattleScript_InsomniaProtects[]; -extern const u8 BattleScript_EffectOHKO[]; extern const u8 BattleScript_EffectHealBlock[]; extern const u8 BattleScript_RecoilIfMiss[]; extern const u8 BattleScript_EffectMist[]; diff --git a/include/battle_util.h b/include/battle_util.h index 0b060f4e2c..0c5212f783 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -463,6 +463,8 @@ bool32 IsHazardOnSideAndClear(enum BattleSide side, enum Hazards hazardType); void RemoveHazardFromField(enum BattleSide side, enum Hazards hazardType); bool32 CanMoveSkipAccuracyCalc(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef, enum Move move, enum ResultOption option); u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, enum Move move, enum Ability atkAbility, enum Ability defAbility, enum HoldEffect atkHoldEffect, enum HoldEffect defHoldEffect); +bool32 DoesOHKOMoveMissTarget(struct BattleCalcValues *cv); +bool32 DoesMoveMissTarget(struct BattleCalcValues *cv); bool32 IsSemiInvulnerable(u32 battler, enum SemiInvulnerableExclusion excludeCommander); bool32 BreaksThroughSemiInvulnerablity(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef, enum Move move); bool32 HasPartnerTrainer(u32 battler); diff --git a/include/constants/battle.h b/include/constants/battle.h index d2be57e222..0a2f0ecb55 100644 --- a/include/constants/battle.h +++ b/include/constants/battle.h @@ -406,11 +406,13 @@ enum TypeSideHazard #define MOVE_RESULT_NOT_VERY_EFFECTIVE (1 << 2) #define MOVE_RESULT_DOESNT_AFFECT_FOE (1 << 3) #define MOVE_RESULT_ONE_HIT_KO (1 << 4) -#define MOVE_RESULT_FAILED (1 << 5) -#define MOVE_RESULT_FOE_ENDURED (1 << 6) -#define MOVE_RESULT_FOE_HUNG_ON (1 << 7) -#define MOVE_RESULT_STURDIED (1 << 8) -#define MOVE_RESULT_FOE_ENDURED_AFFECTION (1 << 9) +#define MOVE_RESULT_ONE_HIT_KO_NO_AFFECT (1 << 5) +#define MOVE_RESULT_ONE_HIT_KO_STURDY (1 << 6) +#define MOVE_RESULT_FAILED (1 << 7) +#define MOVE_RESULT_FOE_ENDURED (1 << 8) +#define MOVE_RESULT_FOE_HUNG_ON (1 << 9) +#define MOVE_RESULT_STURDIED (1 << 10) +#define MOVE_RESULT_FOE_ENDURED_AFFECTION (1 << 11) #define MOVE_RESULT_AVOIDED_ATTACK (MOVE_RESULT_MISSED | MOVE_RESULT_FAILED) #define MOVE_RESULT_NO_EFFECT (MOVE_RESULT_MISSED | MOVE_RESULT_FAILED | MOVE_RESULT_DOESNT_AFFECT_FOE) diff --git a/include/constants/battle_move_effects.h b/include/constants/battle_move_effects.h index f7f345af2a..3ebf4ab8ef 100644 --- a/include/constants/battle_move_effects.h +++ b/include/constants/battle_move_effects.h @@ -33,7 +33,6 @@ enum __attribute__((packed)) 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 1d63abbc14..8a3c6122da 100644 --- a/include/move.h +++ b/include/move.h @@ -1,6 +1,7 @@ #ifndef GUARD_MOVES_H #define GUARD_MOVES_H +#include "generational_changes.h" #include "contest_effect.h" #include "constants/battle.h" #include "constants/battle_factory.h" @@ -94,6 +95,7 @@ struct MoveInfo u32 criticalHitStage:2; bool32 alwaysCriticalHit:1; u32 numAdditionalEffects:3; // limited to 7 + // Flags bool32 makesContact:1; bool32 ignoresProtect:1; @@ -126,6 +128,12 @@ struct MoveInfo bool32 alwaysHitsInRain:1; bool32 accuracy50InSun:1; bool32 alwaysHitsInHailSnow:1; + bool32 alwaysHitsOnSameType:1; // Always hits if user is of same type as move + bool32 noAffectOnSameTypeTarget:1; // Fails if target is of same type as move + bool32 accIncreaseByTenOnSameType:1; // Accuracy is increased by 10% if user is of same type as move + bool32 padding1:15; + // end of word + // Ban flags bool32 gravityBanned:1; bool32 mirrorMoveBanned:1; @@ -143,7 +151,7 @@ struct MoveInfo bool32 dampBanned:1; //Other bool32 validApprenticeMove:1; - u32 padding:3; + u32 padding2:17; // end of word union { @@ -165,7 +173,7 @@ struct MoveInfo } reflectDamage; struct { u16 terrain; - u16 percent:14; + u16 percent:13; enum TerrainGroundCheck groundCheck:2; u16 hitsBothFoes:1; } terrainBoost; @@ -458,6 +466,29 @@ static inline bool32 MoveAlwaysHitsInHailSnow(enum Move moveId) return gMovesInfo[SanitizeMoveId(moveId)].alwaysHitsInHailSnow; } +static inline bool32 MoveAlwaysHitsOnSameType(enum Move moveId) +{ + #if TESTING + if (moveId == MOVE_TOXIC && GetConfig(CONFIG_TOXIC_NEVER_MISS) < GEN_6) + return FALSE; + #endif + return gMovesInfo[SanitizeMoveId(moveId)].alwaysHitsOnSameType; +} + +static inline bool32 MoveHasNoEffectOnSameType(enum Move moveId) +{ + #if TESTING + if (moveId == MOVE_SHEER_COLD && GetConfig(CONFIG_SHEER_COLD_IMMUNITY) < GEN_7) + return FALSE; + #endif + return gMovesInfo[SanitizeMoveId(moveId)].noAffectOnSameTypeTarget; +} + +static inline bool32 MoveHasIncreasedAccByTenOnSameType(enum Move moveId) +{ + return gMovesInfo[SanitizeMoveId(moveId)].accIncreaseByTenOnSameType; +} + static inline bool32 IsMoveGravityBanned(enum Move moveId) { return gMovesInfo[SanitizeMoveId(moveId)].gravityBanned; diff --git a/include/test/battle.h b/include/test/battle.h index d925185b1d..449b83b287 100644 --- a/include/test/battle.h +++ b/include/test/battle.h @@ -1098,18 +1098,14 @@ void Environment_(u32 sourceLine, u32 environment); static inline bool8 IsMultibattleTest(void) { - if (TESTING) + #if TESTING { if (((gBattleTypeFlags & BATTLE_MULTI_TEST) == BATTLE_MULTI_TEST) - || ((gBattleTypeFlags & BATTLE_TWO_VS_ONE_TEST) == BATTLE_TWO_VS_ONE_TEST)) + || ((gBattleTypeFlags & BATTLE_TWO_VS_ONE_TEST) == BATTLE_TWO_VS_ONE_TEST)) return TRUE; - else - return FALSE; - } - else - { - return FALSE; } + #endif + return FALSE; } // Created for easy use of EXPECT_MOVES, so the user can provide 1, 2, 3 or 4 moves for AI which can pass the test. diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index f3958db5e5..8fe899d0e5 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -1805,10 +1805,6 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, enum Move move, s32 s || !(weather & (B_WEATHER_ICY_ANY))) ADJUST_SCORE(-10); break; - case EFFECT_SHEER_COLD: - if (GetConfig(CONFIG_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); @@ -4638,7 +4634,6 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, enum Move move } break; case EFFECT_OHKO: - case EFFECT_SHEER_COLD: if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) break; else if (gBattleMons[battlerAtk].volatiles.lockOn) @@ -4758,7 +4753,7 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, enum Move move ADJUST_SCORE(BEST_EFFECT); break; case EFFECT_LOCK_ON: - if (HasMoveWithEffect(battlerAtk, EFFECT_OHKO) || HasMoveWithEffect(battlerAtk, EFFECT_SHEER_COLD)) + if (HasMoveWithEffect(battlerAtk, EFFECT_OHKO)) ADJUST_SCORE(GOOD_EFFECT); else if (HasMoveWithLowAccuracy(battlerAtk, battlerDef, 85, TRUE)) ADJUST_SCORE(GOOD_EFFECT); @@ -6344,7 +6339,6 @@ static s32 AI_Risky(u32 battlerAtk, u32 battlerDef, enum Move 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: diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 9200ab1dae..5da9ab18c4 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -2171,7 +2171,7 @@ bool32 ShouldTryOHKO(u32 battlerAtk, u32 battlerDef, enum Ability atkAbility, en else // test the odds { u32 odds = accuracy + (gBattleMons[battlerAtk].level - gBattleMons[battlerDef].level); - if (B_SHEER_COLD_ACC >= GEN_7 && GetMoveEffect(move) == EFFECT_SHEER_COLD && !IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_ICE)) + if (MoveHasIncreasedAccByTenOnSameType(move) && !IS_BATTLER_OF_TYPE(battlerAtk, GetMoveType(move))) odds -= 10; if (Random() % 100 + 1 < odds && gBattleMons[battlerAtk].level >= gBattleMons[battlerDef].level) return TRUE; diff --git a/src/battle_controller_player_partner.c b/src/battle_controller_player_partner.c index aa43e4f504..c4e3a35275 100644 --- a/src/battle_controller_player_partner.c +++ b/src/battle_controller_player_partner.c @@ -344,8 +344,9 @@ static void PlayerPartnerHandleChoosePokemon(u32 battler) gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE; gBattleStruct->monToSwitchIntoId[battler] = chosenMonId; } - if (TESTING) - TestRunner_Battle_CheckSwitch(battler, chosenMonId); + #if TESTING + TestRunner_Battle_CheckSwitch(battler, chosenMonId); + #endif BtlController_EmitChosenMonReturnValue(battler, B_COMM_TO_ENGINE, chosenMonId, NULL); BtlController_Complete(battler); } diff --git a/src/battle_dome.c b/src/battle_dome.c index 84a2a2ed2d..98a3e91ae6 100644 --- a/src/battle_dome.c +++ b/src/battle_dome.c @@ -3945,7 +3945,6 @@ static bool32 IsDomeLuckyMove(enum Move move) return FALSE; // fallthrough case EFFECT_OHKO: - case EFFECT_SHEER_COLD: case EFFECT_METRONOME: case EFFECT_MIRROR_MOVE: case EFFECT_SKETCH: diff --git a/src/battle_dynamax.c b/src/battle_dynamax.c index 2481cce637..17c25a0f5f 100644 --- a/src/battle_dynamax.c +++ b/src/battle_dynamax.c @@ -402,7 +402,6 @@ static enum MaxPowerTier GetMaxPowerTier(enum Move 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_gimmick.c b/src/battle_gimmick.c index 2381a6de01..dec65c2bb1 100644 --- a/src/battle_gimmick.c +++ b/src/battle_gimmick.c @@ -44,10 +44,11 @@ bool32 CanActivateGimmick(u32 battler, enum Gimmick gimmick) bool32 IsGimmickSelected(u32 battler, enum Gimmick gimmick) { // There's no player select in tests, but some gimmicks need to test choice before they are fully activated. - if (TESTING) - return (gBattleStruct->gimmick.toActivate & (1u << battler)) && gBattleStruct->gimmick.usableGimmick[battler] == gimmick; - else - return gBattleStruct->gimmick.usableGimmick[battler] == gimmick && gBattleStruct->gimmick.playerSelect; + #if TESTING + return (gBattleStruct->gimmick.toActivate & (1u << battler)) && gBattleStruct->gimmick.usableGimmick[battler] == gimmick; + #else + return gBattleStruct->gimmick.usableGimmick[battler] == gimmick && gBattleStruct->gimmick.playerSelect; + #endif } // Sets a battler as having a gimmick active using their party index. @@ -66,13 +67,11 @@ enum Gimmick GetActiveGimmick(u32 battler) bool32 ShouldTrainerBattlerUseGimmick(u32 battler, enum Gimmick gimmick) { // There are no trainer party settings in battles, but the AI needs to know which gimmick to use. - if (TESTING) - { - return gimmick == TestRunner_Battle_GetChosenGimmick(GetBattlerTrainer(battler), gBattlerPartyIndexes[battler]); - } + #if TESTING + return gimmick == TestRunner_Battle_GetChosenGimmick(GetBattlerTrainer(battler), gBattlerPartyIndexes[battler]); + #else // The player can bypass these checks because they can choose through the controller. - else if (IsOnPlayerSide(battler) - && !((gBattleTypeFlags & BATTLE_TYPE_MULTI) && battler == B_POSITION_PLAYER_RIGHT)) + if (IsOnPlayerSide(battler) && !((gBattleTypeFlags & BATTLE_TYPE_MULTI) && battler == B_POSITION_PLAYER_RIGHT)) { return TRUE; } @@ -84,6 +83,7 @@ bool32 ShouldTrainerBattlerUseGimmick(u32 battler, enum Gimmick gimmick) if (gimmick == GIMMICK_DYNAMAX && gBattleStruct->opponentMonCanDynamax & 1 << gBattlerPartyIndexes[battler]) return TRUE; } + #endif return FALSE; } diff --git a/src/battle_main.c b/src/battle_main.c index ed56573b6f..9e55d97fcf 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -567,11 +567,10 @@ static void CB2_InitBattleInternal(void) TryFormChange(i, B_SIDE_OPPONENT, FORM_CHANGE_BEGIN_BATTLE); } - if (TESTING) - { - gPlayerPartyCount = CalculatePartyCount(gPlayerParty); - gEnemyPartyCount = CalculatePartyCount(gEnemyParty); - } + #if TESTING + gPlayerPartyCount = CalculatePartyCount(gPlayerParty); + gEnemyPartyCount = CalculatePartyCount(gEnemyParty); + #endif gBattleCommunication[MULTIUSE_STATE] = 0; } @@ -2989,10 +2988,10 @@ static void ClearSetBScriptingStruct(void) memset(&gBattleScripting, 0, sizeof(gBattleScripting)); gBattleScripting.windowsType = temp; - if (TESTING) + gBattleScripting.battleStyle = gSaveBlock2Ptr->optionsBattleStyle; + #if TESTING gBattleScripting.battleStyle = OPTIONS_BATTLE_STYLE_SET; - else - gBattleScripting.battleStyle = gSaveBlock2Ptr->optionsBattleStyle; + #endif gBattleScripting.expOnCatch = (GetConfig(CONFIG_EXP_CATCH) >= GEN_6); gBattleScripting.specialTrainerBattleType = specialBattleType; } @@ -6030,8 +6029,7 @@ void SetTypeBeforeUsingMove(enum Move move, u32 battler) if (holdEffect == HOLD_EFFECT_GEMS && GetBattleMoveType(move) == GetItemSecondaryId(heldItem) && effect != EFFECT_PLEDGE - && effect != EFFECT_OHKO - && effect != EFFECT_SHEER_COLD) + && effect != EFFECT_OHKO) { gSpecialStatuses[battler].gemParam = GetBattlerHoldEffectParam(battler); gSpecialStatuses[battler].gemBoost = TRUE; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 5f2c88f3da..65ebdeb51f 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1117,9 +1117,8 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u { enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker); enum HoldEffect holdEffectAtk = GetBattlerHoldEffect(gBattlerAttacker); - enum BattleMoveEffects effect = GetMoveEffect(gCurrentMove); - if (ShouldSkipAccuracyCalcPastFirstHit(gBattlerAttacker, abilityAtk, holdEffectAtk, effect) + if (ShouldSkipAccuracyCalcPastFirstHit(gBattlerAttacker, abilityAtk, holdEffectAtk, GetMoveEffect(gCurrentMove)) || IsMaxMove(gCurrentMove) || IsZMove(gCurrentMove)) { @@ -1143,23 +1142,19 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u continue; numTargets++; - enum Ability abilityDef = GetBattlerAbility(battlerDef); - if (CanMoveSkipAccuracyCalc(gBattlerAttacker, battlerDef, abilityAtk, abilityDef, gCurrentMove, RUN_SCRIPT)) - continue; + struct BattleCalcValues cv = {0}; + cv.move = gCurrentMove; + cv.battlerAtk = gBattlerAttacker; + cv.battlerDef = battlerDef; + cv.abilities[gBattlerAttacker] = abilityAtk; + cv.abilities[battlerDef] = GetBattlerAbility(battlerDef); + cv.holdEffects[gBattlerAttacker] = holdEffectAtk; + cv.holdEffects[battlerDef] = GetBattlerHoldEffect(battlerDef); - u32 holdEffectDef = GetBattlerHoldEffect(battlerDef); - u32 accuracy = GetTotalAccuracy(gBattlerAttacker, - battlerDef, - gCurrentMove, - abilityAtk, - abilityDef, - holdEffectAtk, - holdEffectDef); - - if (!RandomPercentage(RNG_ACCURACY, accuracy)) + if (DoesMoveMissTarget(&cv)) { - gBattleStruct->moveResultFlags[battlerDef] = MOVE_RESULT_MISSED; + gBattleStruct->moveResultFlags[battlerDef] |= MOVE_RESULT_MISSED; gBattleCommunication[MISS_TYPE] = B_MSG_MISSED; numMisses++; @@ -1192,10 +1187,24 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_MISSED) { - gBattleStruct->moveResultFlags[gBattlerTarget] = MOVE_RESULT_MISSED; gLastLandedMoves[gBattlerTarget] = 0; gLastHitByType[gBattlerTarget] = 0; - gBattlescriptCurrInstr = failInstr; + + if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_ONE_HIT_KO_STURDY) + { + gLastUsedAbility = ABILITY_STURDY; + gBattlescriptCurrInstr = BattleScript_SturdyPreventsOHKO; + gBattlerAbility = gBattlerTarget; + } + else + { + gBattlescriptCurrInstr = failInstr; + } + + if (gBattleStruct->moveResultFlags[gBattlerTarget] & (MOVE_RESULT_ONE_HIT_KO_NO_AFFECT | MOVE_RESULT_ONE_HIT_KO_STURDY)) + gBattleStruct->moveResultFlags[gBattlerTarget] = MOVE_RESULT_DOESNT_AFFECT_FOE; + else + gBattleStruct->moveResultFlags[gBattlerTarget] = MOVE_RESULT_MISSED; } else { @@ -8338,126 +8347,9 @@ static void Cmd_setlightscreen(void) gBattlescriptCurrInstr = cmd->nextInstr; } -// for var lands -#define NO_HIT 0 -#define CALC_ACC 1 -#define SURE_HIT 2 -// for var endured -#define NOT_ENDURED 0 -#define FOCUS_SASHED 1 -#define FOCUS_BANDED 2 -#define AFFECTION_ENDURED 3 static void Cmd_tryKO(void) { - CMD_ARGS(const u8 *failInstr); - - enum BattleMoveEffects effect = GetMoveEffect(gCurrentMove); - enum HoldEffect holdEffect = GetBattlerHoldEffect(gBattlerTarget); - enum Ability targetAbility = GetBattlerAbility(gBattlerTarget); - u32 rand = Random() % 100; - u32 affectionScore = GetBattlerAffectionHearts(gBattlerTarget); - u32 endured = NOT_ENDURED; - - // Dynamaxed Pokemon cannot be hit by OHKO moves. - if ((GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX)) - { - gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED; - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_KO_UNAFFECTED; - gBattlescriptCurrInstr = cmd->failInstr; - return; - } - - gPotentialItemEffectBattler = gBattlerTarget; - if (holdEffect == HOLD_EFFECT_FOCUS_BAND - && (Random() % 100) < GetBattlerHoldEffectParam(gBattlerTarget)) - { - endured = FOCUS_BANDED; - RecordItemEffectBattle(gBattlerTarget, holdEffect); - } - else if (holdEffect == HOLD_EFFECT_FOCUS_SASH && IsBattlerAtMaxHp(gBattlerTarget)) - { - endured = FOCUS_SASHED; - RecordItemEffectBattle(gBattlerTarget, holdEffect); - } - else if (B_AFFECTION_MECHANICS == TRUE && IsOnPlayerSide(gBattlerTarget) && affectionScore >= AFFECTION_THREE_HEARTS) - { - if ((affectionScore == AFFECTION_FIVE_HEARTS && rand < 20) - || (affectionScore == AFFECTION_FOUR_HEARTS && rand < 15) - || (affectionScore == AFFECTION_THREE_HEARTS && rand < 10)) - endured = AFFECTION_ENDURED; - } - - if (targetAbility == ABILITY_STURDY) - { - gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED; - gLastUsedAbility = ABILITY_STURDY; - gBattlescriptCurrInstr = BattleScript_SturdyPreventsOHKO; - gBattlerAbility = gBattlerTarget; - } - else - { - u32 lands = NO_HIT; - if (gBattleMons[gBattlerTarget].level > gBattleMons[gBattlerAttacker].level) - lands = NO_HIT; - else if ((gBattleMons[gBattlerTarget].volatiles.lockOn && gBattleMons[gBattlerTarget].volatiles.battlerWithSureHit == gBattlerAttacker) - || IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_NO_GUARD) - || IsAbilityAndRecord(gBattlerTarget, targetAbility, ABILITY_NO_GUARD)) - lands = SURE_HIT; - else - lands = CALC_ACC; - - if (lands == CALC_ACC) - { - u16 odds = GetMoveAccuracy(gCurrentMove) + (gBattleMons[gBattlerAttacker].level - gBattleMons[gBattlerTarget].level); - 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 = SURE_HIT; - } - - if (lands == SURE_HIT) - { - if (gBattleMons[gBattlerTarget].volatiles.endured) - { - gBattleStruct->moveDamage[gBattlerTarget] = gBattleMons[gBattlerTarget].hp - 1; - gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_FOE_ENDURED; - } - else if (endured == FOCUS_BANDED || endured == FOCUS_SASHED) - { - gBattleStruct->moveDamage[gBattlerTarget] = gBattleMons[gBattlerTarget].hp - 1; - gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_FOE_HUNG_ON; - gLastUsedItem = gBattleMons[gBattlerTarget].item; - } - else if (endured == AFFECTION_ENDURED) - { - gBattleStruct->moveDamage[gBattlerTarget] = gBattleMons[gBattlerTarget].hp - 1; - gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_FOE_ENDURED_AFFECTION; - } - else - { - gBattleStruct->moveDamage[gBattlerTarget] = gBattleMons[gBattlerTarget].hp; - gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_ONE_HIT_KO; - } - gBattlescriptCurrInstr = cmd->nextInstr; - } - else - { - gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED; - if (gBattleMons[gBattlerAttacker].level >= gBattleMons[gBattlerTarget].level) - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_KO_MISS; - else - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_KO_UNAFFECTED; - gBattlescriptCurrInstr = cmd->failInstr; - } - } } -#undef NO_HIT -#undef CALC_ACC -#undef SURE_HIT -#undef NOT_ENDURED -#undef FOCUS_SASHED -#undef FOCUS_BANDED -#undef AFFECTION_ENDURED static void Cmd_checknonvolatiletrigger(void) { diff --git a/src/battle_util.c b/src/battle_util.c index ffa720779c..ad8f0f598d 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -3853,7 +3853,6 @@ static void ForewarnChooseMove(u32 battler) switch (GetMoveEffect(data[count].moveId)) { case EFFECT_OHKO: - case EFFECT_SHEER_COLD: data[count].power = 150; break; case EFFECT_REFLECT_DAMAGE: @@ -4880,9 +4879,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab ctx.isAnticipation = TRUE; modifier = CalcTypeEffectivenessMultiplier(&ctx); - if (modifier >= UQ_4_12(2.0) - || moveEffect == EFFECT_OHKO - || moveEffect == EFFECT_SHEER_COLD) + if (modifier >= UQ_4_12(2.0) || moveEffect == EFFECT_OHKO) { effect++; break; @@ -9508,6 +9505,9 @@ s32 DoFixedDamageMoveCalc(struct BattleContext *ctx) dmg = GetNonDynamaxHP(ctx->battlerDef) - gBattleMons[ctx->battlerAtk].hp; } break; + case EFFECT_OHKO: + dmg = gBattleMons[ctx->battlerDef].hp; + break; default: break; } @@ -10028,7 +10028,7 @@ static inline uq4_12_t CalcTypeEffectivenessMultiplierInternal(struct BattleCont ctx->airBalloonBlocked = TRUE; } } - else if (GetConfig(CONFIG_SHEER_COLD_IMMUNITY) >= GEN_7 && GetMoveEffect(ctx->move) == EFFECT_SHEER_COLD && IS_BATTLER_OF_TYPE(ctx->battlerDef, TYPE_ICE)) + else if (MoveHasNoEffectOnSameType(ctx->move) && IS_BATTLER_OF_TYPE(ctx->battlerDef, GetMoveType(ctx->move))) { modifier = UQ_4_12(0.0); } @@ -12058,11 +12058,9 @@ void RemoveHazardFromField(enum BattleSide side, enum Hazards hazardType) } } -static bool32 CanToxicSkipAccuracyCheck(u32 battlerAtk, u32 move) +static bool32 CanMoveSkipAccuracyCheck(u32 battlerAtk, u32 move) { - if (GetConfig(CONFIG_TOXIC_NEVER_MISS) < GEN_6) - return FALSE; - return move == MOVE_TOXIC && IS_BATTLER_OF_TYPE(battlerAtk, TYPE_POISON); + return MoveAlwaysHitsOnSameType(move) && IS_BATTLER_OF_TYPE(battlerAtk, GetMoveType(move)); } bool32 CanMoveSkipAccuracyCalc(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef, enum Move move, enum ResultOption option) @@ -12072,7 +12070,7 @@ bool32 CanMoveSkipAccuracyCalc(u32 battlerAtk, u32 battlerDef, enum Ability abil enum BattleMoveEffects moveEffect = GetMoveEffect(move); if ((gBattleMons[battlerDef].volatiles.lockOn && gBattleMons[battlerDef].volatiles.battlerWithSureHit == battlerAtk) - || CanToxicSkipAccuracyCheck(battlerAtk, move) + || CanMoveSkipAccuracyCheck(battlerAtk, move) || gBattleMons[battlerDef].volatiles.glaiveRush) { effect = TRUE; @@ -12095,8 +12093,7 @@ bool32 CanMoveSkipAccuracyCalc(u32 battlerAtk, u32 battlerDef, enum Ability abil // If the target is under the effects of Telekinesis, and the move isn't a OH-KO move, move hits. else if (gBattleMons[battlerDef].volatiles.telekinesis && !IsSemiInvulnerable(battlerDef, CHECK_ALL) - && moveEffect != EFFECT_OHKO - && moveEffect != EFFECT_SHEER_COLD) + && moveEffect != EFFECT_OHKO) { effect = TRUE; } @@ -12223,6 +12220,9 @@ u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, enum Move move, enum Abilit break; } + if (MoveHasIncreasedAccByTenOnSameType(move) && !IS_BATTLER_OF_TYPE(battlerAtk, GetMoveType(move))) + calc = (calc * 110) / 100; + // Attacker's hold effect switch (atkHoldEffect) { @@ -12268,6 +12268,85 @@ u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, enum Move move, enum Abilit return calc; } +bool32 DoesOHKOMoveMissTarget(struct BattleCalcValues *cv) +{ + enum OHKOResult { + NO_HIT, + CALC_ACC, + SURE_HIT, + }; + + // Dynamaxed Pokemon cannot be hit by OHKO moves. + if (GetActiveGimmick(cv->battlerDef) == GIMMICK_DYNAMAX) + { + gBattleStruct->moveResultFlags[cv->battlerDef] |= MOVE_RESULT_ONE_HIT_KO_NO_AFFECT; + return TRUE; + } + + if (cv->abilities[cv->battlerDef] == ABILITY_STURDY) + { + gBattleStruct->moveResultFlags[cv->battlerDef] |= MOVE_RESULT_ONE_HIT_KO_STURDY; + return TRUE; + } + + enum OHKOResult lands = NO_HIT; + + if (gBattleMons[cv->battlerDef].level > gBattleMons[cv->battlerAtk].level) + { + lands = NO_HIT; + } + else if ((gBattleMons[cv->battlerDef].volatiles.lockOn && gBattleMons[cv->battlerDef].volatiles.battlerWithSureHit == cv->battlerAtk) + || IsAbilityAndRecord(cv->battlerAtk, cv->abilities[cv->battlerAtk], ABILITY_NO_GUARD) + || IsAbilityAndRecord(cv->battlerDef, cv->abilities[cv->battlerDef], ABILITY_NO_GUARD)) + { + lands = SURE_HIT; + } + else + { + lands = CALC_ACC; + } + + if (lands == CALC_ACC) + { + u32 odds = GetMoveAccuracy(cv->move) + (gBattleMons[cv->battlerAtk].level - gBattleMons[cv->battlerDef].level); + if (MoveHasIncreasedAccByTenOnSameType(cv->move) && !IS_BATTLER_OF_TYPE(cv->battlerAtk, GetMoveType(cv->move))) + odds -= 10; + if (RandomPercentage(RNG_ACCURACY, odds) && gBattleMons[cv->battlerAtk].level >= gBattleMons[cv->battlerDef].level) + lands = SURE_HIT; + } + + if (lands == SURE_HIT) + { + gBattleStruct->moveResultFlags[cv->battlerDef] |= MOVE_RESULT_ONE_HIT_KO_NO_AFFECT; + return FALSE; + } + + if (gBattleMons[cv->battlerAtk].level < gBattleMons[cv->battlerDef].level) + gBattleStruct->moveResultFlags[cv->battlerDef] |= MOVE_RESULT_ONE_HIT_KO_NO_AFFECT; + + return TRUE; +} + +bool32 DoesMoveMissTarget(struct BattleCalcValues *cv) +{ + if (GetMoveEffect(cv->move) == EFFECT_OHKO) + return DoesOHKOMoveMissTarget(cv); + + if (CanMoveSkipAccuracyCalc(cv->battlerAtk, cv->battlerDef, cv->abilities[cv->battlerAtk], cv->abilities[cv->battlerDef], cv->move, RUN_SCRIPT)) + return FALSE; + + u32 accuracy = GetTotalAccuracy( + cv->battlerAtk, + cv->battlerDef, + cv->move, + cv->abilities[cv->battlerAtk], + cv->abilities[cv->battlerDef], + cv->holdEffects[cv->battlerAtk], + cv->holdEffects[cv->battlerDef] + ); + return !RandomPercentage(RNG_ACCURACY, accuracy); +} + bool32 IsSemiInvulnerable(u32 battler, enum SemiInvulnerableExclusion excludeCommander) { if (gBattleMons[battler].volatiles.semiInvulnerable == STATE_COMMANDER) @@ -12281,7 +12360,7 @@ bool32 BreaksThroughSemiInvulnerablity(u32 battlerAtk, u32 battlerDef, enum Abil if (state != STATE_COMMANDER) { - if (CanToxicSkipAccuracyCheck(battlerAtk, move)) + if (CanMoveSkipAccuracyCheck(battlerAtk, move)) return TRUE; if (abilityAtk == ABILITY_NO_GUARD || abilityDef == ABILITY_NO_GUARD) return TRUE; diff --git a/src/battle_z_move.c b/src/battle_z_move.c index 1ae542f4ba..9e11a553fd 100644 --- a/src/battle_z_move.c +++ b/src/battle_z_move.c @@ -548,7 +548,7 @@ u32 GetZMovePower(enum Move move) if (GetMoveCategory(move) == DAMAGE_CATEGORY_STATUS) return 0; enum BattleMoveEffects moveEffect = GetMoveEffect(move); - if (moveEffect == EFFECT_OHKO || moveEffect == EFFECT_SHEER_COLD) + if (moveEffect == EFFECT_OHKO) return 180; u32 power = GetMoveZPowerOverride(move); diff --git a/src/data/battle_move_effects.h b/src/data/battle_move_effects.h index 58fb318f74..8329851be3 100644 --- a/src/data/battle_move_effects.h +++ b/src/data/battle_move_effects.h @@ -216,17 +216,11 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] = [EFFECT_OHKO] = { - .battleScript = BattleScript_EffectOHKO, + .battleScript = BattleScript_EffectHit, .battleTvScore = 7, .battleFactoryStyle = FACTORY_STYLE_HIGH_RISK, }, - [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 70754c2adb..003910f9e3 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -2544,6 +2544,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .argument = { .nonVolatileStatus = MOVE_EFFECT_TOXIC }, .zMove = { .effect = Z_EFFECT_DEF_UP_1 }, .magicCoatAffected = TRUE, + .alwaysHitsOnSameType = B_TOXIC_NEVER_MISS >= GEN_6, .contestEffect = CONTEST_EFFECT_WORSEN_CONDITION_OF_PREV_MONS, .contestCategory = CONTEST_CATEGORY_SMART, .contestComboStarterId = COMBO_STARTER_TOXIC, @@ -8839,13 +8840,15 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .description = COMPOUND_STRING( "A chilling attack that\n" "causes fainting if it hits."), - .effect = EFFECT_SHEER_COLD, + .effect = EFFECT_OHKO, .power = 1, .type = TYPE_ICE, .accuracy = 30, .pp = 5, .target = TARGET_SELECTED, .priority = 0, + .noAffectOnSameTypeTarget = B_SHEER_COLD_IMMUNITY >= GEN_7, + .accIncreaseByTenOnSameType = B_SHEER_COLD_ACC >= GEN_7, .category = DAMAGE_CATEGORY_SPECIAL, .contestEffect = CONTEST_EFFECT_BADLY_STARTLE_MONS_WITH_GOOD_APPEALS, .contestCategory = CONTEST_CATEGORY_BEAUTY, diff --git a/test/battle/ai/can_use_all_moves.c b/test/battle/ai/can_use_all_moves.c index 78e1154054..208809d9b5 100644 --- a/test/battle/ai/can_use_all_moves.c +++ b/test/battle/ai/can_use_all_moves.c @@ -299,7 +299,7 @@ AI_DOUBLE_BATTLE_TEST("AI can use all moves, 301-400") switch (effect) { //TODO: AI HANDLING - case EFFECT_SHEER_COLD: // Guillotine is crashing the test entirely. + case EFFECT_OHKO: // Guillotine is crashing the test entirely. case EFFECT_WATER_SPORT: case EFFECT_LUCKY_CHANT: case EFFECT_ME_FIRST: diff --git a/test/battle/gimmick/dynamax.c b/test/battle/gimmick/dynamax.c index 387de6ab16..60a4abeef9 100644 --- a/test/battle/gimmick/dynamax.c +++ b/test/battle/gimmick/dynamax.c @@ -224,7 +224,7 @@ SINGLE_BATTLE_TEST("Dynamax: Dynamaxed Pokemon cannot be hit by OHKO moves") } SCENE { MESSAGE("Wobbuffet used Max Strike!"); MESSAGE("The opposing Machamp used Fissure!"); - MESSAGE("Wobbuffet is unaffected!"); + MESSAGE("It doesn't affect Wobbuffet…"); NONE_OF { HP_BAR(player); } } } diff --git a/test/battle/move_animations/all_anims.c b/test/battle/move_animations/all_anims.c index 78f870d02c..435be8f9e8 100644 --- a/test/battle/move_animations/all_anims.c +++ b/test/battle/move_animations/all_anims.c @@ -310,7 +310,7 @@ static void WhenSingles(enum Move move, struct BattlePokemon *attacker, struct B MOVE(attacker, move); MOVE(defender, MOVE_SWORDS_DANCE); } - else if (effect == EFFECT_OHKO || effect == EFFECT_SHEER_COLD) + else if (effect == EFFECT_OHKO) { // defender needs to send out a different team member MOVE(attacker, move); SEND_OUT(defender, 1); @@ -526,7 +526,7 @@ static void DoublesWhen(enum Move move, struct BattlePokemon *attacker, struct B MOVE(attacker, move, target: target); MOVE(target, MOVE_SWORDS_DANCE); } - else if (effect == EFFECT_OHKO || effect == EFFECT_SHEER_COLD) + else if (effect == EFFECT_OHKO) { // Opponent needs to send out a different team member MOVE(attacker, move, target: target); SEND_OUT(target, 2); diff --git a/test/battle/move_effect/ohko.c b/test/battle/move_effect/ohko.c index 28c23d716d..7a5f9c8d91 100644 --- a/test/battle/move_effect/ohko.c +++ b/test/battle/move_effect/ohko.c @@ -83,6 +83,51 @@ SINGLE_BATTLE_TEST("OHKO moves fail if target protects") NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_FISSURE, player); } } + +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("OHKO moves faints the target, skipping regular damage calculations") 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 +TO_DO_BATTLE_TEST("OHKO moves ignore non-stage accuracy modifiers") // Gravity, Wide Lens, Compound Eyes +TO_DO_BATTLE_TEST("OHKO: Sheer Cold's accuracy decreasaes by 10% if the user is not Ice type") diff --git a/test/battle/move_effect/sheer_cold.c b/test/battle/move_effect/sheer_cold.c deleted file mode 100644 index c0b076f448..0000000000 --- a/test/battle/move_effect/sheer_cold.c +++ /dev/null @@ -1,86 +0,0 @@ -#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 (Gen3-6)") -{ - GIVEN { - WITH_CONFIG(CONFIG_SHEER_COLD_IMMUNITY, GEN_6); - ASSUME(GetSpeciesType(SPECIES_GLALIE, 0) == TYPE_ICE); - PLAYER(SPECIES_WYNAUT); - OPPONENT(SPECIES_GLALIE); - } WHEN { - TURN { MOVE(player, MOVE_SHEER_COLD); } - } SCENE { - NOT MESSAGE("It doesn't affect the opposing Glalie…"); - ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player); - HP_BAR(opponent, hp: 0); - } -} - -SINGLE_BATTLE_TEST("Sheer Cold doesn't affect Ice-type Pokémon (Gen7+)") -{ - GIVEN { - WITH_CONFIG(CONFIG_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 diff --git a/test/battle/move_flags/move_has_no_effect_on_same_type.c b/test/battle/move_flags/move_has_no_effect_on_same_type.c new file mode 100644 index 0000000000..41e5774e1c --- /dev/null +++ b/test/battle/move_flags/move_has_no_effect_on_same_type.c @@ -0,0 +1,39 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(MoveHasNoEffectOnSameType(MOVE_SHEER_COLD)); +} + +SINGLE_BATTLE_TEST("Sheer Cold does affect Ice-type Pokémon (Gen3-6)") +{ + GIVEN { + WITH_CONFIG(CONFIG_SHEER_COLD_IMMUNITY, GEN_6); + ASSUME(GetSpeciesType(SPECIES_GLALIE, 0) == TYPE_ICE); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_GLALIE); + } WHEN { + TURN { MOVE(player, MOVE_SHEER_COLD); } + } SCENE { + NOT MESSAGE("It doesn't affect the opposing Glalie…"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player); + HP_BAR(opponent, hp: 0); + } +} + +SINGLE_BATTLE_TEST("Sheer Cold doesn't affect Ice-type Pokémon (Gen7+)") +{ + GIVEN { + WITH_CONFIG(CONFIG_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…"); + } +} +