From 8e3183a7a9befdb4b1e6af8bc80e18c9cbfcaf5c Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:11:05 +0000 Subject: [PATCH] Simplify defrosting/thawing and expand target thawing config (#9271) --- data/battle_scripts_1.s | 18 +--- include/battle.h | 3 +- include/battle_script_commands.h | 4 +- include/battle_scripts.h | 6 +- include/config/battle.h | 2 +- include/constants/battle_string_ids.h | 2 - include/constants/generational_changes.h | 2 +- src/battle_ai_main.c | 5 +- src/battle_message.c | 10 +- src/battle_move_resolution.c | 120 +++++++++++------------ src/battle_script_commands.c | 49 +++++---- src/battle_util2.c | 3 +- test/battle/ai/ai.c | 2 +- test/battle/status1/freeze.c | 82 +++++++++++++--- 14 files changed, 183 insertions(+), 125 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index ad363619ab..632f4bc50c 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -5644,26 +5644,14 @@ BattleScript_MoveUsedIsFrozen:: statusanimation BS_ATTACKER goto BattleScript_MoveEnd -BattleScript_MoveUsedUnfroze:: +BattleScript_BattlerDefrosted:: printfromtable gGotDefrostedStringIds waitmessage B_WAIT_TIME_LONG - updatestatusicon BS_ATTACKER - return - -BattleScript_MoveUsedUnfrostbite:: - printfromtable gFrostbiteHealedStringIds - waitmessage B_WAIT_TIME_LONG - updatestatusicon BS_ATTACKER - return - -BattleScript_DefrostedViaFireMove:: - printstring STRINGID_PKMNWASDEFROSTED - waitmessage B_WAIT_TIME_LONG updatestatusicon BS_SCRIPTING return -BattleScript_FrostbiteHealedViaFireMove:: - printstring STRINGID_PKMNFROSTBITEHEALED +BattleScript_BattlerFrostbiteHealed:: + printfromtable gFrostbiteHealedStringIds waitmessage B_WAIT_TIME_LONG updatestatusicon BS_SCRIPTING return diff --git a/include/battle.h b/include/battle.h index e2c8ba1415..bcfbeb6c91 100644 --- a/include/battle.h +++ b/include/battle.h @@ -601,7 +601,8 @@ struct BattleStruct u8 sleepClauseNotBlocked:1; u8 isSkyBattle:1; u8 unableToUseMove:1; // for the current action only, to check if the battler failed to act at end turn use the DisableStruct member - u8 unused:4; + u8 triAttackBurn:1; + u8 unused:3; void (*savedCallback)(void); u16 chosenItem[MAX_BATTLERS_COUNT]; u16 choicedMove[MAX_BATTLERS_COUNT]; diff --git a/include/battle_script_commands.h b/include/battle_script_commands.h index 19c3ca4d98..7130655435 100644 --- a/include/battle_script_commands.h +++ b/include/battle_script_commands.h @@ -68,7 +68,9 @@ bool32 ProteanTryChangeType(enum BattlerId battler, enum Ability ability, enum M u8 GetFirstFaintedPartyIndex(enum BattlerId battler); void SaveBattlerTarget(enum BattlerId battler); void SaveBattlerAttacker(enum BattlerId battler); -bool32 CanBurnHitThaw(enum Ability abilityAtk, enum Move move); +bool32 CanBurnHitThaw(enum Move move); +bool32 CanMoveThawTarget(enum Ability abilityAtk, enum Move move); +bool32 CanFireMoveThawTarget(enum Move move); extern void (*const gBattleScriptingCommandsTable[])(void); extern const struct StatFractions gAccuracyStageRatios[]; diff --git a/include/battle_scripts.h b/include/battle_scripts.h index b0cd2d1416..7ff471bc60 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -126,10 +126,8 @@ extern const u8 BattleScript_PoisonTurnDmg[]; extern const u8 BattleScript_BurnTurnDmg[]; extern const u8 BattleScript_FrostbiteTurnDmg[]; extern const u8 BattleScript_MoveUsedIsFrozen[]; -extern const u8 BattleScript_MoveUsedUnfroze[]; -extern const u8 BattleScript_MoveUsedUnfrostbite[]; -extern const u8 BattleScript_DefrostedViaFireMove[]; -extern const u8 BattleScript_FrostbiteHealedViaFireMove[]; +extern const u8 BattleScript_BattlerDefrosted[]; +extern const u8 BattleScript_BattlerFrostbiteHealed[]; extern const u8 BattleScript_MoveUsedIsParalyzed[]; extern const u8 BattleScript_MoveUsedFlinched[]; extern const u8 BattleScript_PrintUproarOverTurns[]; diff --git a/include/config/battle.h b/include/config/battle.h index 4836c81f9e..f94ba6a0b2 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -113,7 +113,7 @@ #define B_TELEPORT_BEHAVIOR GEN_LATEST // In LGPE onwards (Gen8+ here), Teleport allows the user to swap out with another party member. #define B_BEAT_UP GEN_LATEST // In Gen5+, Beat Up uses a different formula to calculate its damage, and deals Dark-type damage. Prior to Gen 5, each hit also announces the party member's name. #define B_DARK_VOID_FAIL GEN_LATEST // In Gen7+, only Darkrai can use Dark Void. -#define B_BURN_HIT_THAW GEN_LATEST // In Gen6+, damaging moves with a chance of burn will thaw the target, regardless if they're fire-type moves or not. +#define B_HIT_THAW GEN_LATEST // In Gen6+, damaging moves that thaw the user will thaw the target. In Gen 3+, Fire-type moves thaw the target. In Gen 1-2, damaging moves that can burn will thaw the target, regardless if they can be burned or not. #define B_HEALING_WISH_SWITCH GEN_LATEST // In Gen5+, the mon receiving Healing Wish is sent out at the end of the turn. // Additionally, in gen8+ the Healing Wish's effect will be stored until the user switches into a statused or hurt mon. #define B_DEFOG_EFFECT_CLEARING GEN_LATEST // In Gen5+, Defog does not lower Evasion of target behind Subsitute. In Gen6+, Defog clears Spikes, Toxic Spikes, Stealth Rock and Sticky Web from both sides. In Gen8+, Defog also clears active Terrain. diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index 19dc192669..204b707173 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -53,7 +53,6 @@ enum StringID STRINGID_PKMNFROZENBY, STRINGID_PKMNISFROZEN, STRINGID_PKMNWASDEFROSTED, - STRINGID_PKMNWASDEFROSTED2, STRINGID_PKMNWASDEFROSTEDBY, STRINGID_PKMNWASPARALYZED, STRINGID_PKMNWASPARALYZEDBY, @@ -629,7 +628,6 @@ enum StringID STRINGID_PKMNSITEMHEALEDFROSTBITE, STRINGID_ATTACKERHEALEDITSFROSTBITE, STRINGID_PKMNFROSTBITEHEALED, - STRINGID_PKMNFROSTBITEHEALED2, STRINGID_PKMNFROSTBITEHEALEDBY, STRINGID_MIRRORHERBCOPIED, STRINGID_STARTEDSNOW, diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index 3461e358fc..46cac1a496 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -104,7 +104,7 @@ F(B_TELEPORT_BEHAVIOR, teleportBehavior, (u32, GEN_COUNT - 1)) \ F(B_BEAT_UP, beatUp, (u32, GEN_COUNT - 1)) \ F(B_DARK_VOID_FAIL, darkVoidFail, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ - F(B_BURN_HIT_THAW, burnHitThaw, (u32, GEN_COUNT - 1)) \ + F(B_HIT_THAW, hitThaw, (u32, GEN_COUNT - 1)) \ F(B_HEALING_WISH_SWITCH, healingWishSwitch, (u32, GEN_COUNT - 1)) \ F(B_DEFOG_EFFECT_CLEARING, defogEffectClearing, (u32, GEN_COUNT - 1)) \ F(B_STOCKPILE_RAISES_DEFS, stockpileRaisesDefs, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 4e5a00b9dc..bf500b5fa7 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -1203,13 +1203,14 @@ static s32 AI_CheckBadMove(enum BattlerId battlerAtk, enum BattlerId battlerDef, } // Don't use anything but super effective thawing moves if target is frozen if any other attack available - if (((GetMoveType(move) == TYPE_FIRE && GetMovePower(move) != 0) || CanBurnHitThaw(abilityAtk, move)) && effectiveness < UQ_4_12(2.0) && (gBattleMons[battlerDef].status1 & STATUS1_ICY_ANY)) + if ((CanFireMoveThawTarget(move) || CanBurnHitThaw(move) || CanMoveThawTarget(abilityAtk, move)) + && effectiveness < UQ_4_12(2.0) && (gBattleMons[battlerDef].status1 & STATUS1_ICY_ANY)) { enum Move aiMove; for (u32 moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++) { aiMove = gBattleMons[battlerAtk].moves[moveIndex]; - if (GetMoveType(aiMove) != TYPE_FIRE && !CanBurnHitThaw(abilityAtk, aiMove) && GetMovePower(gBattleMons[battlerAtk].moves[moveIndex]) != 0) + if (!CanFireMoveThawTarget(aiMove) && !CanBurnHitThaw(aiMove) && !CanMoveThawTarget(abilityAtk, aiMove)) { ADJUST_SCORE(-1); break; diff --git a/src/battle_message.c b/src/battle_message.c index a4d95f2cb4..2f3d3854cf 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -230,8 +230,7 @@ const u8 *const gBattleStringsTable[STRINGID_COUNT] = [STRINGID_PKMNFROZENBY] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_BUFF1} froze {B_EFF_NAME_WITH_PREFIX2} solid!"), //not in gen 5+, ability popup [STRINGID_PKMNISFROZEN] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} is frozen solid!"), [STRINGID_PKMNWASDEFROSTED] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX} thawed out!"), - [STRINGID_PKMNWASDEFROSTED2] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} thawed out!"), - [STRINGID_PKMNWASDEFROSTEDBY] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX}'s {B_CURRENT_MOVE} melted the ice!"), + [STRINGID_PKMNWASDEFROSTEDBY] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_CURRENT_MOVE} melted the ice!"), [STRINGID_PKMNWASPARALYZED] = COMPOUND_STRING("{B_EFF_NAME_WITH_PREFIX} is paralyzed, so it may be unable to move!"), [STRINGID_PKMNWASPARALYZEDBY] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_BUFF1} paralyzed {B_EFF_NAME_WITH_PREFIX2}, so it may be unable to move!"), //not in gen 5+, ability popup [STRINGID_PKMNISPARALYZED] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} couldn't move because it's paralyzed!"), @@ -806,8 +805,7 @@ const u8 *const gBattleStringsTable[STRINGID_COUNT] = [STRINGID_PKMNSITEMHEALEDFROSTBITE] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_LAST_ITEM} cured its frostbite!"), [STRINGID_ATTACKERHEALEDITSFROSTBITE] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} cured its frostbite through sheer determination so you wouldn't worry!"), [STRINGID_PKMNFROSTBITEHEALED] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s frostbite was cured!"), - [STRINGID_PKMNFROSTBITEHEALED2] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX}'s frostbite was cured!"), - [STRINGID_PKMNFROSTBITEHEALEDBY] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX}'s {B_CURRENT_MOVE} cured its frostbite!"), + [STRINGID_PKMNFROSTBITEHEALEDBY] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_CURRENT_MOVE} cured its frostbite!"), [STRINGID_MIRRORHERBCOPIED] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX} used its Mirror Herb to mirror its opponent's stat changes!"), [STRINGID_STARTEDSNOW] = COMPOUND_STRING("It started to snow!"), [STRINGID_SNOWCONTINUES] = COMPOUND_STRING("Snow continues to fall."), //not in gen 5+ (lol) @@ -1212,7 +1210,7 @@ const u16 gGotFrostbiteStringIds[] = const u16 gFrostbiteHealedStringIds[] = { - [B_MSG_FROSTBITE_HEALED] = STRINGID_PKMNFROSTBITEHEALED2, + [B_MSG_FROSTBITE_HEALED] = STRINGID_PKMNFROSTBITEHEALED, [B_MSG_FROSTBITE_HEALED_BY_MOVE] = STRINGID_PKMNFROSTBITEHEALEDBY }; @@ -1224,7 +1222,7 @@ const u16 gGotFrozenStringIds[] = const u16 gGotDefrostedStringIds[] = { - [B_MSG_DEFROSTED] = STRINGID_PKMNWASDEFROSTED2, + [B_MSG_DEFROSTED] = STRINGID_PKMNWASDEFROSTED, [B_MSG_DEFROSTED_BY_MOVE] = STRINGID_PKMNWASDEFROSTEDBY }; diff --git a/src/battle_move_resolution.c b/src/battle_move_resolution.c index 28471aa172..10c10f192b 100644 --- a/src/battle_move_resolution.c +++ b/src/battle_move_resolution.c @@ -17,7 +17,7 @@ static enum Move GetOriginallyUsedMove(enum Move chosenMove); static void SetSameMoveTurnValues(enum BattleMoveEffects moveEffect); static void TryClearChargeVolatile(enum Type moveType); static inline bool32 IsBattlerUsingBeakBlast(enum BattlerId battler); -static void RequestNonVolatileChangee(enum BattlerId battlerAtk); +static void RequestNonVolatileChange(enum BattlerId battlerAtk); static bool32 CanBattlerBounceBackMove(struct BattleContext *ctx); static bool32 TryMagicBounce(struct BattleContext *ctx); static bool32 TryMagicCoat(struct BattleContext *ctx); @@ -97,6 +97,13 @@ static enum CancelerResult CancelerChillyReception(struct BattleContext *ctx) return CANCELER_RESULT_SUCCESS; } +static void DefrostBattler(enum BattlerId battler, u32 status) +{ + gBattleScripting.battler = battler; + gBattleMons[battler].status1 &= ~status; + RequestNonVolatileChange(battler); +} + static enum CancelerResult CancelerAsleepOrFrozen(struct BattleContext *ctx) { enum CancelerResult result = CANCELER_RESULT_BREAK; @@ -153,7 +160,7 @@ static enum CancelerResult CancelerAsleepOrFrozen(struct BattleContext *ctx) BattleScriptCall(BattleScript_MoveUsedWokeUp); } } - RequestNonVolatileChangee(ctx->battlerAtk); + RequestNonVolatileChange(ctx->battlerAtk); } else if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_FREEZE && !MoveThawsUser(ctx->move)) { @@ -164,12 +171,11 @@ static enum CancelerResult CancelerAsleepOrFrozen(struct BattleContext *ctx) } else // unfreeze { - gBattleMons[ctx->battlerAtk].status1 &= ~STATUS1_FREEZE; + DefrostBattler(ctx->battlerAtk, STATUS1_FREEZE); result = CANCELER_RESULT_BREAK; - BattleScriptCall(BattleScript_MoveUsedUnfroze); + BattleScriptCall(BattleScript_BattlerDefrosted); gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_DEFROSTED; } - RequestNonVolatileChangee(ctx->battlerAtk); } return result; @@ -564,35 +570,36 @@ static enum CancelerResult CancelerThaw(struct BattleContext *ctx) { enum CancelerResult result = CANCELER_RESULT_BREAK; - if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_FREEZE) + if (MoveThawsUser(ctx->move)) { - if (!(IsMoveEffectRemoveSpeciesType(ctx->move, MOVE_EFFECT_REMOVE_ARG_TYPE, TYPE_FIRE) && !IS_BATTLER_OF_TYPE(ctx->battlerAtk, TYPE_FIRE))) + if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_FREEZE) { - gBattleMons[ctx->battlerAtk].status1 &= ~STATUS1_FREEZE; - result = CANCELER_RESULT_BREAK; - BattleScriptCall(BattleScript_MoveUsedUnfroze); - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_DEFROSTED_BY_MOVE; + if (!IsMoveEffectRemoveSpeciesType(ctx->move, MOVE_EFFECT_REMOVE_ARG_TYPE, TYPE_FIRE) || IS_BATTLER_OF_TYPE(ctx->battlerAtk, TYPE_FIRE)) + { + DefrostBattler(ctx->battlerAtk, STATUS1_FREEZE); + result = CANCELER_RESULT_BREAK; + BattleScriptCall(BattleScript_BattlerDefrosted); + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_DEFROSTED_BY_MOVE; + } + else + { + result = CANCELER_RESULT_FAILURE; + } } - else + else if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_FROSTBITE) { - result = CANCELER_RESULT_FAILURE; + if (!IsMoveEffectRemoveSpeciesType(ctx->move, MOVE_EFFECT_REMOVE_ARG_TYPE, TYPE_FIRE) || IS_BATTLER_OF_TYPE(ctx->battlerAtk, TYPE_FIRE)) + { + DefrostBattler(ctx->battlerAtk, STATUS1_FROSTBITE); + result = CANCELER_RESULT_BREAK; + BattleScriptCall(BattleScript_BattlerFrostbiteHealed); + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FROSTBITE_HEALED_BY_MOVE; + } + else + { + result = CANCELER_RESULT_FAILURE; + } } - RequestNonVolatileChangee(ctx->battlerAtk); - } - else if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_FROSTBITE && MoveThawsUser(ctx->move)) - { - if (!(IsMoveEffectRemoveSpeciesType(ctx->move, MOVE_EFFECT_REMOVE_ARG_TYPE, TYPE_FIRE) && !IS_BATTLER_OF_TYPE(ctx->battlerAtk, TYPE_FIRE))) - { - gBattleMons[ctx->battlerAtk].status1 &= ~STATUS1_FROSTBITE; - result = CANCELER_RESULT_BREAK; - BattleScriptCall(BattleScript_MoveUsedUnfrostbite); - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FROSTBITE_HEALED_BY_MOVE; - } - else - { - result = CANCELER_RESULT_FAILURE; - } - RequestNonVolatileChangee(ctx->battlerAtk); } return result; } @@ -2827,17 +2834,10 @@ static enum MoveEndResult MoveEndMultihitMove(void) return result; } -static void DefrostBattler(enum BattlerId battler, u32 status) -{ - gBattleScripting.battler = battler; - gBattleMons[battler].status1 &= ~status; - BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[battler].status1), &gBattleMons[battler].status1); - MarkBattlerForControllerExec(battler); -} - static enum MoveEndResult MoveEndDefrost(void) { enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker); + const u8 *battleScript = NULL; while (gBattleStruct->eventState.moveEndBattler < gBattlersCount) { @@ -2846,32 +2846,29 @@ static enum MoveEndResult MoveEndDefrost(void) if (battler == gBattlerAttacker) continue; - if (gBattleMons[battler].status1 & STATUS1_FREEZE - && IsBattlerTurnDamaged(battler) - && IsBattlerAlive(battler) - && GetBattleMoveType(gCurrentMove) == TYPE_FIRE) + if (!(gBattleMons[battler].status1 & STATUS1_ICY_ANY) + || !IsBattlerTurnDamaged(battler) + || !IsBattlerAlive(battler)) + continue; + + if (gBattleMons[battler].status1 & STATUS1_FREEZE) + battleScript = BattleScript_BattlerDefrosted; + else + battleScript = BattleScript_BattlerFrostbiteHealed; + + if ((CanFireMoveThawTarget(gCurrentMove) || CanBurnHitThaw(gCurrentMove)) && gBattleMons[battler].status1 & STATUS1_FREEZE) { - DefrostBattler(battler, STATUS1_FREEZE); - BattleScriptCall(BattleScript_DefrostedViaFireMove); + DefrostBattler(battler, gBattleMons[battler].status1); + BattleScriptCall(battleScript); + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_DEFROSTED; return MOVEEND_RESULT_RUN_SCRIPT; } - else if (gBattleMons[battler].status1 & STATUS1_FREEZE - && IsBattlerTurnDamaged(battler) - && IsBattlerAlive(battler) - && IsBattlerAlive(gBattlerAttacker) - && CanBurnHitThaw(abilityAtk, gCurrentMove)) + else if (IsBattlerAlive(gBattlerAttacker) + && CanMoveThawTarget(abilityAtk, gCurrentMove)) { - DefrostBattler(battler, STATUS1_FREEZE); - BattleScriptCall(BattleScript_DefrostedViaFireMove); - return MOVEEND_RESULT_RUN_SCRIPT; - } - else if (gBattleMons[battler].status1 & STATUS1_FROSTBITE - && IsBattlerTurnDamaged(battler) - && IsBattlerAlive(battler) - && MoveThawsUser(GetOriginallyUsedMove(gChosenMove))) - { - DefrostBattler(battler, STATUS1_FROSTBITE); - BattleScriptCall(BattleScript_FrostbiteHealedViaFireMove); + DefrostBattler(battler, gBattleMons[battler].status1); + BattleScriptCall(battleScript); + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_DEFROSTED; return MOVEEND_RESULT_RUN_SCRIPT; } } @@ -3692,6 +3689,7 @@ static enum MoveEndResult MoveEndClearBits(void) gBattleStruct->swapDamageCategory = FALSE; gBattleStruct->categoryOverride = FALSE; gBattleStruct->additionalEffectsCounter = 0; + gBattleStruct->triAttackBurn = FALSE; gBattleStruct->poisonPuppeteerConfusion = FALSE; gBattleStruct->fickleBeamBoosted = FALSE; gBattleStruct->battlerState[gBattlerAttacker].usedMicleBerry = FALSE; @@ -3964,14 +3962,14 @@ void MoveValuesCleanUp(void) gBattleCommunication[MISS_TYPE] = 0; } -static void RequestNonVolatileChangee(enum BattlerId battlerAtk) +static void RequestNonVolatileChange(enum BattlerId battlerAtk) { BtlController_EmitSetMonData( battlerAtk, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, - 4, + sizeof(gBattleMons[battlerAtk].status1), &gBattleMons[battlerAtk].status1); MarkBattlerForControllerExec(battlerAtk); } diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index bb578b35f9..5710d17447 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2554,21 +2554,21 @@ void SetMoveEffect(enum BattlerId battlerAtk, enum BattlerId effectBattler, enum gBattlescriptCurrInstr = battleScript; break; case MOVE_EFFECT_TRI_ATTACK: - if (gBattleMons[gEffectBattler].status1) + { + static const u8 sTriAttackEffects[] = { - gBattlescriptCurrInstr = battleScript; - } - else - { - static const u8 sTriAttackEffects[] = - { - MOVE_EFFECT_BURN, - MOVE_EFFECT_FREEZE_OR_FROSTBITE, - MOVE_EFFECT_PARALYSIS - }; - SetMoveEffect(battlerAtk, effectBattler, RandomElement(RNG_TRI_ATTACK, sTriAttackEffects), battleScript, effectFlags); - } + MOVE_EFFECT_BURN, + MOVE_EFFECT_FREEZE_OR_FROSTBITE, + MOVE_EFFECT_PARALYSIS + }; + u32 chosenMoveEffect = RandomUniform(RNG_TRI_ATTACK, 0, ARRAY_COUNT(sTriAttackEffects) - 1); + if (sTriAttackEffects[chosenMoveEffect] == MOVE_EFFECT_BURN) + gBattleStruct->triAttackBurn = TRUE; + + if (!gBattleMons[effectBattler].status1) + SetMoveEffect(battlerAtk, effectBattler, sTriAttackEffects[chosenMoveEffect], battleScript, effectFlags); break; + } case MOVE_EFFECT_WRAP: if (gBattleMons[gEffectBattler].volatiles.wrapped) { @@ -3438,6 +3438,7 @@ void SetMoveEffect(enum BattlerId battlerAtk, enum BattlerId effectBattler, enum if ((gBattleMons[gEffectBattler].status1 & argStatus) && (NumAffectedSpreadMoveTargets() > 1 || !IsMoveEffectBlockedByTarget(abilities[gEffectBattler]))) { + gBattleScripting.battler = gEffectBattler; gBattleMons[gEffectBattler].status1 &= ~(argStatus); BtlController_EmitSetMonData(gEffectBattler, 0, REQUEST_STATUS_BATTLE, 0, 4, &gBattleMons[gEffectBattler].status1); MarkBattlerForControllerExec(gEffectBattler); @@ -3456,10 +3457,10 @@ void SetMoveEffect(enum BattlerId battlerAtk, enum BattlerId effectBattler, enum gBattlescriptCurrInstr = BattleScript_TargetBurnHeal; break; case STATUS1_FREEZE: - gBattlescriptCurrInstr = BattleScript_DefrostedViaFireMove; + gBattlescriptCurrInstr = BattleScript_BattlerDefrosted; break; case STATUS1_FROSTBITE: - gBattlescriptCurrInstr = BattleScript_FrostbiteHealedViaFireMove; + gBattlescriptCurrInstr = BattleScript_BattlerFrostbiteHealed; break; case STATUS1_POISON: case STATUS1_TOXIC_POISON: @@ -11652,11 +11653,11 @@ static bool32 CanAbilityPreventStatLoss(enum Ability abilityDef) return FALSE; } -bool32 CanBurnHitThaw(enum Ability abilityAtk, enum Move move) +bool32 CanBurnHitThaw(enum Move move) { u8 i; - if (GetConfig(B_BURN_HIT_THAW) >= GEN_6 && abilityAtk != ABILITY_SHEER_FORCE) + if (GetConfig(B_HIT_THAW) <= GEN_2) { u32 numAdditionalEffects = GetMoveAdditionalEffectCount(move); for (i = 0; i < numAdditionalEffects; i++) @@ -11664,11 +11665,25 @@ bool32 CanBurnHitThaw(enum Ability abilityAtk, enum Move move) const struct AdditionalEffect *additionalEffect = GetMoveAdditionalEffectById(move, i); if (additionalEffect->moveEffect == MOVE_EFFECT_BURN) return TRUE; + + if (additionalEffect->moveEffect == MOVE_EFFECT_TRI_ATTACK + && gBattleStruct->triAttackBurn) + return TRUE; } } return FALSE; } +bool32 CanMoveThawTarget(enum Ability abilityAtk, enum Move move) +{ + return GetConfig(B_HIT_THAW) >= GEN_6 && !IsSheerForceAffected(move, abilityAtk) && MoveThawsUser(move); +} + +bool32 CanFireMoveThawTarget(enum Move move) +{ + return GetConfig(B_HIT_THAW) >= GEN_3 && GetMoveType(move) == TYPE_FIRE && GetMovePower(move) != 0; +} + void BS_CheckParentalBondCounter(void) { NATIVE_ARGS(u8 counter, const u8 *jumpInstr); diff --git a/src/battle_util2.c b/src/battle_util2.c index 419cebf981..15b8e9fb98 100644 --- a/src/battle_util2.c +++ b/src/battle_util2.c @@ -189,7 +189,8 @@ u32 BattlePalace_TryEscapeStatus(enum BattlerId battler) { // Unfreeze gBattleMons[battler].status1 &= ~(STATUS1_FREEZE); - BattleScriptCall(BattleScript_MoveUsedUnfroze); + gBattleScripting.battler = battler; + BattleScriptCall(BattleScript_BattlerDefrosted); gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_DEFROSTED; } effect = 2; diff --git a/test/battle/ai/ai.c b/test/battle/ai/ai.c index a1d0529c07..dcd6328cfa 100644 --- a/test/battle/ai/ai.c +++ b/test/battle/ai/ai.c @@ -687,7 +687,7 @@ AI_SINGLE_BATTLE_TEST("AI won't use thawing moves if target is frozen unless it PARAMETRIZE { status = STATUS1_FROSTBITE; aiMove = MOVE_EMBER; aiFlags = AI_FLAG_CHECK_BAD_MOVE; } GIVEN { - WITH_CONFIG(B_BURN_HIT_THAW, GEN_6); // In Gen 5, non-Fire burning moves didn't cause thawing + WITH_CONFIG(B_HIT_THAW, GEN_6); // In Gen 5, moves that thawed the user didn't thaw the target ASSUME(GetMoveType(MOVE_EMBER) == TYPE_FIRE); ASSUME(GetMoveCategory(MOVE_TACKLE) == DAMAGE_CATEGORY_PHYSICAL); ASSUME(GetMoveCategory(MOVE_WATER_GUN) == DAMAGE_CATEGORY_SPECIAL); diff --git a/test/battle/status1/freeze.c b/test/battle/status1/freeze.c index 7144dc1c88..970ca954f4 100644 --- a/test/battle/status1/freeze.c +++ b/test/battle/status1/freeze.c @@ -14,25 +14,27 @@ SINGLE_BATTLE_TEST("Freeze has a 20% chance of being thawed") } } -SINGLE_BATTLE_TEST("Freeze is thawed by opponent's Fire-type attacks") +SINGLE_BATTLE_TEST("Freeze is thawed by opponent's Fire-type attacks (Gen 3+)") { GIVEN { - ASSUME(GetMoveType(MOVE_EMBER) == TYPE_FIRE); + WITH_CONFIG(B_HIT_THAW, GEN_3); + ASSUME(GetMoveType(MOVE_FIRE_SPIN) == TYPE_FIRE); PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(opponent, MOVE_EMBER); MOVE(player, MOVE_CELEBRATE); } + TURN { MOVE(opponent, MOVE_FIRE_SPIN); MOVE(player, MOVE_CELEBRATE); } } SCENE { - ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_SPIN, opponent); MESSAGE("Wobbuffet thawed out!"); STATUS_ICON(player, none: TRUE); ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); } } -SINGLE_BATTLE_TEST("Freeze is thawed by opponent's Fire-type attacks even if Sheer Force affected") +SINGLE_BATTLE_TEST("Freeze is thawed by opponent's Fire-type attacks even if Sheer Force affected (Gen 3+)") { GIVEN { + WITH_CONFIG(B_HIT_THAW, GEN_3); ASSUME(GetMoveType(MOVE_EMBER) == TYPE_FIRE); PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); } OPPONENT(SPECIES_TAUROS) { Ability(ABILITY_SHEER_FORCE); } @@ -46,11 +48,46 @@ SINGLE_BATTLE_TEST("Freeze is thawed by opponent's Fire-type attacks even if She } } -SINGLE_BATTLE_TEST("Freeze is thawed by opponent's attack that can burn (Gen 6+)") +SINGLE_BATTLE_TEST("Freeze is thawed by opponent's attack that can burn (Gen 1-2)") { GIVEN { - WITH_CONFIG(B_BURN_HIT_THAW, GEN_6); - ASSUME(MoveHasAdditionalEffect(MOVE_SCALD, MOVE_EFFECT_BURN)); + WITH_CONFIG(B_HIT_THAW, GEN_2); + ASSUME(MoveHasAdditionalEffect(MOVE_EMBER, MOVE_EFFECT_BURN)); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_EMBER); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); + MESSAGE("Wobbuffet thawed out!"); + STATUS_ICON(player, none: TRUE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } +} + +SINGLE_BATTLE_TEST("Freeze is thawed by opponent's Tri Attack 1/3 of the time (Gen 1-2)") +{ + PASSES_RANDOMLY(1, 3, RNG_TRI_ATTACK); + GIVEN { + WITH_CONFIG(B_HIT_THAW, GEN_2); + ASSUME(MoveHasAdditionalEffect(MOVE_TRI_ATTACK, MOVE_EFFECT_TRI_ATTACK)); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TRI_ATTACK); MOVE(player, MOVE_CELEBRATE, WITH_RNG(RNG_FROZEN, FALSE)); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRI_ATTACK, opponent); + MESSAGE("Wobbuffet thawed out!"); + STATUS_ICON(player, none: TRUE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } +} + +SINGLE_BATTLE_TEST("Freeze is thawed by opponent's attack that can thaw the user (Gen 6+)") +{ + GIVEN { + WITH_CONFIG(B_HIT_THAW, GEN_6); + ASSUME(MoveThawsUser(MOVE_SCALD)); PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -62,24 +99,45 @@ SINGLE_BATTLE_TEST("Freeze is thawed by opponent's attack that can burn (Gen 6+) ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); } } -SINGLE_BATTLE_TEST("Freeze isn't thawed by opponent's attack that can burn if Sheer Force affected (Gen 6+)") + +SINGLE_BATTLE_TEST("Freeze isn't thawed by opponent's attack that can thaw the user if Sheer Force affected (Gen 6+)") { GIVEN { - WITH_CONFIG(B_BURN_HIT_THAW, GEN_6); - ASSUME(MoveHasAdditionalEffect(MOVE_SCALD, MOVE_EFFECT_BURN)); + WITH_CONFIG(B_HIT_THAW, GEN_6); + ASSUME(MoveThawsUser(MOVE_SCALD)); + ASSUME(MoveIsAffectedBySheerForce(MOVE_SCALD)); PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); } OPPONENT(SPECIES_TAUROS) { Ability(ABILITY_SHEER_FORCE); } } WHEN { - TURN { MOVE(opponent, MOVE_SCALD); } + TURN { MOVE(opponent, MOVE_SCALD); MOVE(player, MOVE_CELEBRATE, WITH_RNG(RNG_FROZEN, FALSE)); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_SCALD, opponent); NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); MESSAGE("Wobbuffet thawed out!"); STATUS_ICON(player, none: TRUE); } } } +SINGLE_BATTLE_TEST("Freeze is thawed by opponent's attack that can thaw the user if not Sheer Force affected (Gen 6+)") +{ + GIVEN { + WITH_CONFIG(B_HIT_THAW, GEN_6); + ASSUME(MoveThawsUser(MOVE_HYDRO_STEAM)); + ASSUME(!MoveIsAffectedBySheerForce(MOVE_HYDRO_STEAM)); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); } + OPPONENT(SPECIES_TAUROS) { Ability(ABILITY_SHEER_FORCE); } + } WHEN { + TURN { MOVE(opponent, MOVE_HYDRO_STEAM); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYDRO_STEAM, opponent); + MESSAGE("Wobbuffet thawed out!"); + STATUS_ICON(player, none: TRUE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } +} + SINGLE_BATTLE_TEST("Freeze is thawed by user's Flame Wheel") { GIVEN {