From 264e72c1a1d839b1016751c60cf43885b2929922 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Fri, 14 Feb 2025 00:22:10 +0100 Subject: [PATCH 01/14] Use more accurate count in MovesInfo (#6260) --- src/data/moves_info.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 0589866293..f826a05fb4 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -110,7 +110,7 @@ static const u8 sFeintDescription[] = _( "An attack that hits foes\n" "using moves like Protect."); -const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = +const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = { [MOVE_NONE] = { From 6d6b0f6c3be259a1462106b0ddb97a394e54ca6c Mon Sep 17 00:00:00 2001 From: Deokishisu <6993375+Deokishisu@users.noreply.github.com> Date: Fri, 14 Feb 2025 04:11:37 -0500 Subject: [PATCH 02/14] Fix Extra `task->tState++` and `break` in `Task_DrawFieldMessageBox` (#6261) --- src/field_message_box.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/field_message_box.c b/src/field_message_box.c index 47c728f74d..bf9ce1aa93 100755 --- a/src/field_message_box.c +++ b/src/field_message_box.c @@ -38,8 +38,6 @@ static void Task_DrawFieldMessage(u8 taskId) LoadMessageBoxAndBorderGfx(); task->tState++; break; - task->tState++; - break; case 1: DrawDialogueFrame(0, TRUE); task->tState++; From 53727aa23d006b6f05aaa6b4adf78ad491eaad29 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Fri, 14 Feb 2025 12:26:49 +0100 Subject: [PATCH 03/14] Fixes ability Embody Aspect triggering multiple times (#6259) Co-authored-by: Bassoonian --- include/battle.h | 1 + src/battle_util.c | 4 +++- test/battle/ability/embody_aspect.c | 23 +++++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/include/battle.h b/include/battle.h index 0ca26cc8e6..2659c08844 100644 --- a/include/battle.h +++ b/include/battle.h @@ -833,6 +833,7 @@ struct BattleStruct u8 usedEjectItem; u8 usedMicleBerry; u8 trainerSlideSpriteIds[MAX_BATTLERS_COUNT]; + u8 embodyAspectBoost[NUM_BATTLE_SIDES]; }; // The palaceFlags member of struct BattleStruct contains 1 flag per move to indicate which moves the AI should consider, diff --git a/src/battle_util.c b/src/battle_util.c index 6419b5017d..20e4ba635e 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -5107,7 +5107,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITY_EMBODY_ASPECT_HEARTHFLAME_MASK: case ABILITY_EMBODY_ASPECT_WELLSPRING_MASK: case ABILITY_EMBODY_ASPECT_CORNERSTONE_MASK: - if (!gSpecialStatuses[battler].switchInAbilityDone) + if (!gSpecialStatuses[battler].switchInAbilityDone + && !(gBattleStruct->embodyAspectBoost[GetBattlerSide(battler)] & (1u << gBattlerPartyIndexes[battler]))) { u32 stat; @@ -5126,6 +5127,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 gBattleScripting.savedBattler = gBattlerAttacker; gBattlerAttacker = battler; gSpecialStatuses[battler].switchInAbilityDone = TRUE; + gBattleStruct->embodyAspectBoost[GetBattlerSide(battler)] |= 1u << gBattlerPartyIndexes[battler]; SET_STATCHANGER(stat, 1, FALSE); BattleScriptPushCursorAndCallback(BattleScript_BattlerAbilityStatRaiseOnSwitchIn); effect++; diff --git a/test/battle/ability/embody_aspect.c b/test/battle/ability/embody_aspect.c index fffc8cf423..22b28dea22 100644 --- a/test/battle/ability/embody_aspect.c +++ b/test/battle/ability/embody_aspect.c @@ -57,3 +57,26 @@ SINGLE_BATTLE_TEST("Embody Aspect activates when it's no longer effected by Neut MESSAGE("The opposing Ogerpon's Embody Aspect raised its Speed!"); } } + +SINGLE_BATTLE_TEST("Embody Aspect raises Speed only once per battle") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_OGERPON_TEAL_TERA) { Ability(ABILITY_EMBODY_ASPECT_TEAL_MASK); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { SWITCH(opponent, 1); } + TURN { SWITCH(opponent, 0); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_EMBODY_ASPECT_TEAL_MASK); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Ogerpon's Embody Aspect raised its Speed!"); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_EMBODY_ASPECT_TEAL_MASK); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Ogerpon's Embody Aspect raised its Speed!"); + } + } THEN { + EXPECT_EQ(opponent->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} From b1c597495b17181bc33d6bcca3fd414a57768c0a Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Fri, 14 Feb 2025 12:47:02 +0000 Subject: [PATCH 04/14] Fixes Called moves ignoring redirection (#6267) --- data/battle_scripts_1.s | 14 -- include/battle_util.h | 1 + src/battle_script_commands.c | 29 ++-- src/battle_util.c | 226 ++++++++++++++------------- test/battle/move_effect/follow_me.c | 71 +++++++++ test/battle/move_effect/instruct.c | 69 +++++++- test/battle/move_effect/sleep_talk.c | 57 ++++++- test/battle/move_effect/snore.c | 59 +++++++ 8 files changed, 391 insertions(+), 135 deletions(-) create mode 100644 test/battle/move_effect/follow_me.c create mode 100644 test/battle/move_effect/snore.c diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 2b0ca93a9d..c69129125a 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -1744,8 +1744,6 @@ BattleScript_EffectCopycat:: trycopycat BattleScript_CopycatFail attackanimation waitanimation - setbyte sB_ANIM_TURN, 0 - setbyte sB_ANIM_TARGETS_HIT, 0 jumptocalledmove TRUE BattleScript_CopycatFail: ppreduce @@ -1763,8 +1761,6 @@ BattleScript_EffectInstruct:: copybyte gBattlerTarget, gEffectBattler printstring STRINGID_USEDINSTRUCTEDMOVE waitmessage B_WAIT_TIME_LONG - setbyte sB_ANIM_TURN, 0 - setbyte sB_ANIM_TARGETS_HIT, 0 jumptocalledmove TRUE BattleScript_EffectAutotomize:: @@ -2188,8 +2184,6 @@ BattleScript_EffectMeFirst:: trymefirst BattleScript_FailedFromPpReduce attackanimation waitanimation - setbyte sB_ANIM_TURN, 0 - setbyte sB_ANIM_TARGETS_HIT, 0 jumptocalledmove TRUE BattleScript_EffectAttackSpAttackUp:: @@ -3842,8 +3836,6 @@ BattleScript_EffectMetronome:: pause B_WAIT_TIME_SHORT attackanimation waitanimation - setbyte sB_ANIM_TURN, 0 - setbyte sB_ANIM_TARGETS_HIT, 0 metronome BattleScript_EffectLeechSeed:: @@ -4038,8 +4030,6 @@ BattleScript_SleepTalkIsAsleep:: BattleScript_SleepTalkUsingMove:: attackanimation waitanimation - setbyte sB_ANIM_TURN, 0 - setbyte sB_ANIM_TARGETS_HIT, 0 jumptocalledmove TRUE BattleScript_EffectDestinyBond:: @@ -5055,8 +5045,6 @@ BattleScript_EffectAssist:: assistattackselect BattleScript_FailedFromPpReduce attackanimation waitanimation - setbyte sB_ANIM_TURN, 0 - setbyte sB_ANIM_TARGETS_HIT, 0 jumptocalledmove TRUE BattleScript_EffectIngrain:: @@ -8709,8 +8697,6 @@ BattleScript_BattleBondActivatesOnMoveEndAttacker:: BattleScript_DancerActivates:: call BattleScript_AbilityPopUp waitmessage B_WAIT_TIME_SHORT - setbyte sB_ANIM_TURN, 0 - setbyte sB_ANIM_TARGETS_HIT, 0 orword gHitMarker, HITMARKER_ALLOW_NO_PP jumptocalledmove TRUE diff --git a/include/battle_util.h b/include/battle_util.h index 463d6609db..636428854b 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -166,6 +166,7 @@ STATIC_ASSERT(sizeof(struct DamageCalculationData) <= 4, StructExceedsFourBytes) void HandleAction_ThrowBall(void); bool32 IsAffectedByFollowMe(u32 battlerAtk, u32 defSide, u32 move); +bool32 HandleMoveTargetRedirection(void); void HandleAction_UseMove(void); void HandleAction_Switch(void); void HandleAction_UseItem(void); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index a34a6dc4b0..c3dc016314 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -336,6 +336,7 @@ static bool8 CanBurnHitThaw(u16 move); static u32 GetNextTarget(u32 moveTarget, bool32 excludeCurrent); static void TryUpdateEvolutionTracker(u32 evolutionMethod, u32 upAmount, u16 usedMove); static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u8 *failInstr, u16 move); +static void ResetValuesForCalledMove(void); static void Cmd_attackcanceler(void); static void Cmd_accuracycheck(void); @@ -8133,6 +8134,18 @@ static void Cmd_hidepartystatussummary(void) gBattlescriptCurrInstr = cmd->nextInstr; } +static void ResetValuesForCalledMove(void) +{ + if (gBattlerByTurnOrder[gCurrentTurnActionNumber] != gBattlerAttacker) + gBattleStruct->atkCancellerTracker = 0; + else + SetAtkCancellerForCalledMove(); + gBattleScripting.animTurn = 0; + gBattleScripting.animTargetsHit = 0; + SetTypeBeforeUsingMove(gCurrentMove, gBattlerAttacker); + HandleMoveTargetRedirection(); +} + static void Cmd_jumptocalledmove(void) { CMD_ARGS(bool8 notChosenMove); @@ -8142,6 +8155,8 @@ static void Cmd_jumptocalledmove(void) else gChosenMove = gCurrentMove = gCalledMove; + ResetValuesForCalledMove(); + gBattlescriptCurrInstr = GET_MOVE_BATTLESCRIPT(gCurrentMove); } @@ -10193,10 +10208,8 @@ static void Cmd_various(void) gBattlescriptCurrInstr = cmd->failInstr; else { - SetTypeBeforeUsingMove(gCalledMove, gBattlerTarget); gEffectBattler = gBattleStruct->lastMoveTarget[gBattlerTarget]; gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED; - gBattleStruct->atkCancellerTracker = 0; PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, battler, gBattlerPartyIndexes[battler]); gBattlescriptCurrInstr = cmd->nextInstr; } @@ -11212,8 +11225,8 @@ static void SetMoveForMirrorMove(u32 move) gCurrentMove = move; } - SetAtkCancellerForCalledMove(); gBattlerTarget = GetMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE); + ResetValuesForCalledMove(); gBattlescriptCurrInstr = GET_MOVE_BATTLESCRIPT(gCurrentMove); } @@ -12765,10 +12778,10 @@ static void Cmd_metronome(void) #endif gCurrentMove = RandomUniformExcept(RNG_METRONOME, 1, moveCount - 1, InvalidMetronomeMove); - SetAtkCancellerForCalledMove(); PrepareStringBattle(STRINGID_WAGGLINGAFINGER, gBattlerAttacker); gBattlescriptCurrInstr = GET_MOVE_BATTLESCRIPT(gCurrentMove); gBattlerTarget = GetMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE); + ResetValuesForCalledMove(); } static void Cmd_dmgtolevel(void) @@ -17342,14 +17355,10 @@ void BS_JumpIfBlockedBySoundproof(void) void BS_SetMagicCoatTarget(void) { NATIVE_ARGS(); - u32 side; gBattleStruct->attackerBeforeBounce = gBattleScripting.battler = gBattlerAttacker; gBattlerAttacker = gBattlerTarget; - side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker)); - if (IsAffectedByFollowMe(gBattlerAttacker, side, gCurrentMove)) - gBattlerTarget = gSideTimers[side].followmeTarget; - else - gBattlerTarget = gBattleStruct->attackerBeforeBounce; + gBattlerTarget = gBattleStruct->attackerBeforeBounce; + HandleMoveTargetRedirection(); gBattlescriptCurrInstr = cmd->nextInstr; } diff --git a/src/battle_util.c b/src/battle_util.c index 20e4ba635e..14469b072a 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -124,11 +124,94 @@ bool32 IsAffectedByFollowMe(u32 battlerAtk, u32 defSide, u32 move) return TRUE; } +bool32 HandleMoveTargetRedirection(void) +{ + u32 redirectorOrderNum = MAX_BATTLERS_COUNT; + u16 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove); + u32 moveType = GetMoveType(gCurrentMove); + u32 side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker)); + u32 ability = GetBattlerAbility(gBattleStruct->moveTarget[gBattlerAttacker]); + + if (IsAffectedByFollowMe(gBattlerAttacker, side, gCurrentMove) + && moveTarget == MOVE_TARGET_SELECTED + && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gSideTimers[side].followmeTarget)) + { + gBattleStruct->moveTarget[gBattlerAttacker] = gBattlerTarget = gSideTimers[side].followmeTarget; // follow me moxie fix + return FALSE; + } + else if (IsDoubleBattle() + && gSideTimers[side].followmeTimer == 0 + && (!IS_MOVE_STATUS(gCurrentMove) || (moveTarget != MOVE_TARGET_USER && moveTarget != MOVE_TARGET_ALL_BATTLERS)) + && ((ability != ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC) + || (ability != ABILITY_STORM_DRAIN && moveType == TYPE_WATER))) + { + // Find first battler that redirects the move (in turn order) + u32 battler; + for (battler = 0; battler < gBattlersCount; battler++) + { + ability = GetBattlerAbility(battler); + if ((B_REDIRECT_ABILITY_ALLIES >= GEN_4 || !IsAlly(gBattlerAttacker, battler)) + && battler != gBattlerAttacker + && gBattleStruct->moveTarget[gBattlerAttacker] != battler + && ((ability == ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC) + || (ability == ABILITY_STORM_DRAIN && moveType == TYPE_WATER)) + && GetBattlerTurnOrderNum(battler) < redirectorOrderNum + && gMovesInfo[gCurrentMove].effect != EFFECT_SNIPE_SHOT + && gMovesInfo[gCurrentMove].effect != EFFECT_PLEDGE + && GetBattlerAbility(gBattlerAttacker) != ABILITY_PROPELLER_TAIL + && GetBattlerAbility(gBattlerAttacker) != ABILITY_STALWART) + { + redirectorOrderNum = GetBattlerTurnOrderNum(battler); + } + } + if (redirectorOrderNum == MAX_BATTLERS_COUNT) + { + if (moveTarget & MOVE_TARGET_RANDOM) + { + gBattlerTarget = SetRandomTarget(gBattlerAttacker); + } + else if (moveTarget & MOVE_TARGET_FOES_AND_ALLY) + { + for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount; gBattlerTarget++) + { + if (gBattlerTarget == gBattlerAttacker) + continue; + if (IsBattlerAlive(gBattlerTarget)) + break; + } + } + else + { + gBattlerTarget = *(gBattleStruct->moveTarget + gBattlerAttacker); + } + + if (!IsBattlerAlive(gBattlerTarget) && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget)) + { + gBattlerTarget = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerTarget))); + } + } + else + { + u16 battlerAbility; + battler = gBattlerByTurnOrder[redirectorOrderNum]; + battlerAbility = GetBattlerAbility(battler); + + RecordAbilityBattle(battler, gBattleMons[battler].ability); + if (battlerAbility == ABILITY_LIGHTNING_ROD && gCurrentMove != MOVE_TEATIME) + gSpecialStatuses[battler].lightningRodRedirected = TRUE; + else if (battlerAbility == ABILITY_STORM_DRAIN) + gSpecialStatuses[battler].stormDrainRedirected = TRUE; + gBattlerTarget = battler; + } + return TRUE; + } + return FALSE; +} + // Functions void HandleAction_UseMove(void) { - u32 battler, i, side, moveType, ability, var = MAX_BATTLERS_COUNT; - u16 moveTarget; + u32 i; gBattlerAttacker = gBattlerByTurnOrder[gCurrentTurnActionNumber]; if (gBattleStruct->absentBattlerFlags & (1u << gBattlerAttacker) @@ -199,7 +282,6 @@ void HandleAction_UseMove(void) // Set dynamic move type. SetTypeBeforeUsingMove(gChosenMove, gBattlerAttacker); - moveType = GetMoveType(gCurrentMove); // check Z-Move used if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE && !IS_MOVE_STATUS(gCurrentMove) && !IsZMove(gCurrentMove)) @@ -214,115 +296,44 @@ void HandleAction_UseMove(void) gCurrentMove = gChosenMove = GetMaxMove(gBattlerAttacker, gCurrentMove); } - moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove); - - // choose target - side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker)); - ability = GetBattlerAbility(gBattleStruct->moveTarget[gBattlerAttacker]); - if (IsAffectedByFollowMe(gBattlerAttacker, side, gCurrentMove) - && moveTarget == MOVE_TARGET_SELECTED - && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gSideTimers[side].followmeTarget)) + if (!HandleMoveTargetRedirection()) { - gBattleStruct->moveTarget[gBattlerAttacker] = gBattlerTarget = gSideTimers[side].followmeTarget; // follow me moxie fix - } - else if (IsDoubleBattle() - && gSideTimers[side].followmeTimer == 0 - && (!IS_MOVE_STATUS(gCurrentMove) || (moveTarget != MOVE_TARGET_USER && moveTarget != MOVE_TARGET_ALL_BATTLERS)) - && ((ability != ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC) - || (ability != ABILITY_STORM_DRAIN && moveType == TYPE_WATER))) - { - // Find first battler that redirects the move (in turn order) - for (battler = 0; battler < gBattlersCount; battler++) + u32 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove); + if (IsDoubleBattle() && moveTarget & MOVE_TARGET_RANDOM) { - ability = GetBattlerAbility(battler); - if ((B_REDIRECT_ABILITY_ALLIES >= GEN_4 || !IsAlly(gBattlerAttacker, battler)) - && battler != gBattlerAttacker - && gBattleStruct->moveTarget[gBattlerAttacker] != battler - && ((ability == ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC) - || (ability == ABILITY_STORM_DRAIN && moveType == TYPE_WATER)) - && GetBattlerTurnOrderNum(battler) < var - && gMovesInfo[gCurrentMove].effect != EFFECT_SNIPE_SHOT - && gMovesInfo[gCurrentMove].effect != EFFECT_PLEDGE - && GetBattlerAbility(gBattlerAttacker) != ABILITY_PROPELLER_TAIL - && GetBattlerAbility(gBattlerAttacker) != ABILITY_STALWART) - { - var = GetBattlerTurnOrderNum(battler); - } - } - if (var == MAX_BATTLERS_COUNT) - { - if (moveTarget & MOVE_TARGET_RANDOM) - { - gBattlerTarget = SetRandomTarget(gBattlerAttacker); - } - else if (moveTarget & MOVE_TARGET_FOES_AND_ALLY) - { - for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount; gBattlerTarget++) - { - if (gBattlerTarget == gBattlerAttacker) - continue; - if (IsBattlerAlive(gBattlerTarget)) - break; - } - } - else - { - gBattlerTarget = *(gBattleStruct->moveTarget + gBattlerAttacker); - } - - if (!IsBattlerAlive(gBattlerTarget) && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget)) + gBattlerTarget = SetRandomTarget(gBattlerAttacker); + if (gAbsentBattlerFlags & (1u << gBattlerTarget) + && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget)) { gBattlerTarget = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerTarget))); } } + else if (moveTarget == MOVE_TARGET_ALLY) + { + if (IsBattlerAlive(BATTLE_PARTNER(gBattlerAttacker))) + gBattlerTarget = BATTLE_PARTNER(gBattlerAttacker); + else + gBattlerTarget = gBattlerAttacker; + } + else if (IsDoubleBattle() && moveTarget == MOVE_TARGET_FOES_AND_ALLY) + { + for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount; gBattlerTarget++) + { + if (gBattlerTarget == gBattlerAttacker) + continue; + if (IsBattlerAlive(gBattlerTarget)) + break; + } + } else { - u16 battlerAbility; - battler = gBattlerByTurnOrder[var]; - battlerAbility = GetBattlerAbility(battler); - - RecordAbilityBattle(battler, gBattleMons[battler].ability); - if (battlerAbility == ABILITY_LIGHTNING_ROD && gCurrentMove != MOVE_TEATIME) - gSpecialStatuses[battler].lightningRodRedirected = TRUE; - else if (battlerAbility == ABILITY_STORM_DRAIN) - gSpecialStatuses[battler].stormDrainRedirected = TRUE; - gBattlerTarget = battler; - } - } - else if (IsDoubleBattle() && moveTarget & MOVE_TARGET_RANDOM) - { - gBattlerTarget = SetRandomTarget(gBattlerAttacker); - if (gAbsentBattlerFlags & (1u << gBattlerTarget) - && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget)) - { - gBattlerTarget = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerTarget))); - } - } - else if (moveTarget == MOVE_TARGET_ALLY) - { - if (IsBattlerAlive(BATTLE_PARTNER(gBattlerAttacker))) - gBattlerTarget = BATTLE_PARTNER(gBattlerAttacker); - else - gBattlerTarget = gBattlerAttacker; - } - else if (IsDoubleBattle() && moveTarget == MOVE_TARGET_FOES_AND_ALLY) - { - for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount; gBattlerTarget++) - { - if (gBattlerTarget == gBattlerAttacker) - continue; - if (IsBattlerAlive(gBattlerTarget)) - break; - } - } - else - { - gBattlerTarget = *(gBattleStruct->moveTarget + gBattlerAttacker); - if (!IsBattlerAlive(gBattlerTarget) - && moveTarget != MOVE_TARGET_OPPONENTS_FIELD - && (GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget))) - { - gBattlerTarget = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerTarget))); + gBattlerTarget = *(gBattleStruct->moveTarget + gBattlerAttacker); + if (!IsBattlerAlive(gBattlerTarget) + && moveTarget != MOVE_TARGET_OPPONENTS_FIELD + && (GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget))) + { + gBattlerTarget = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerTarget))); + } } } @@ -3275,7 +3286,8 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType) gBattleMons[gBattlerAttacker].status1 -= toSub; if (gBattleMons[gBattlerAttacker].status1 & STATUS1_SLEEP) { - if (gChosenMove != MOVE_SNORE && gChosenMove != MOVE_SLEEP_TALK) + u32 moveEffect = gMovesInfo[gChosenMove].effect; + if (moveEffect != EFFECT_SNORE && moveEffect != EFFECT_SLEEP_TALK) { gBattlescriptCurrInstr = BattleScript_MoveUsedIsAsleep; gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; @@ -3591,7 +3603,7 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType) gBattleStruct->atkCancellerTracker++; break; case CANCELLER_STANCE_CHANGE_2: - if (B_STANCE_CHANGE_FAIL >= GEN_7 && TryFormChangeBeforeMove()) + if (B_STANCE_CHANGE_FAIL >= GEN_7 && !gBattleStruct->isAtkCancelerForCalledMove && TryFormChangeBeforeMove()) effect = 1; gBattleStruct->atkCancellerTracker++; break; @@ -6222,7 +6234,6 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 // Set bit and save Dancer mon's original target gSpecialStatuses[battler].dancerUsedMove = TRUE; gSpecialStatuses[battler].dancerOriginalTarget = *(gBattleStruct->moveTarget + battler) | 0x4; - gBattleStruct->atkCancellerTracker = 0; gBattlerAttacker = gBattlerAbility = battler; gCalledMove = gCurrentMove; @@ -6231,7 +6242,6 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 // Edge case for dance moves that hit multiply targets gHitMarker &= ~HITMARKER_NO_ATTACKSTRING; - SetTypeBeforeUsingMove(gCalledMove, battler); // Make sure that the target isn't an ally - if it is, target the original user if (GetBattlerSide(gBattlerTarget) == GetBattlerSide(gBattlerAttacker)) diff --git a/test/battle/move_effect/follow_me.c b/test/battle/move_effect/follow_me.c new file mode 100644 index 0000000000..fe7b96207d --- /dev/null +++ b/test/battle/move_effect/follow_me.c @@ -0,0 +1,71 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_FOLLOW_ME].effect == EFFECT_FOLLOW_ME); + ASSUME(gMovesInfo[MOVE_SPOTLIGHT].effect == EFFECT_FOLLOW_ME); +} + +DOUBLE_BATTLE_TEST("Follow Me redirects single target moves used by opponents to user") +{ + struct BattlePokemon *moveUser = NULL; + struct BattlePokemon *partner = NULL; + PARAMETRIZE { moveUser = opponentLeft; partner = opponentRight; } + PARAMETRIZE { moveUser = opponentRight; partner = opponentLeft; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_TACKLE, target: moveUser); + MOVE(playerRight, MOVE_TACKLE, target: partner); + MOVE(moveUser, MOVE_FOLLOW_ME); + MOVE(partner, MOVE_TACKLE, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FOLLOW_ME, moveUser); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft); + HP_BAR(moveUser); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight); + HP_BAR(moveUser); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, partner); + HP_BAR(playerLeft); + } +} + +DOUBLE_BATTLE_TEST("Spotlight redirects single target moves used by the opposing side to Spotlight's target") +{ + struct BattlePokemon *moveTarget = NULL; + PARAMETRIZE { moveTarget = playerRight; } + PARAMETRIZE { moveTarget = opponentLeft; } + PARAMETRIZE { moveTarget = opponentRight; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SPOTLIGHT, target: moveTarget); + MOVE(playerRight, MOVE_TACKLE, target: opponentRight); + MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft); + MOVE(opponentRight, MOVE_TACKLE, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPOTLIGHT, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight); + if (moveTarget != playerRight) + HP_BAR(moveTarget); + else + HP_BAR(opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft); + if (moveTarget == playerRight) + HP_BAR(moveTarget); + else + HP_BAR(playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentRight); + if (moveTarget == playerRight) + HP_BAR(moveTarget); + else + HP_BAR(playerLeft); + } +} diff --git a/test/battle/move_effect/instruct.c b/test/battle/move_effect/instruct.c index b31076f3fe..313fb05cef 100644 --- a/test/battle/move_effect/instruct.c +++ b/test/battle/move_effect/instruct.c @@ -217,8 +217,11 @@ DOUBLE_BATTLE_TEST("Instruct-called moves keep their priority") } } -DOUBLE_BATTLE_TEST("Instructed move will be absorbed by Lightning Rod if it turns into an Electric Type move") +DOUBLE_BATTLE_TEST("Instructed move will be redirected and absorbed by Lightning Rod if it turns into an Electric Type move") { + struct BattlePokemon *moveTarget = NULL; + PARAMETRIZE { moveTarget = opponentLeft; } + PARAMETRIZE { moveTarget = opponentRight; } GIVEN { PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WYNAUT); @@ -226,7 +229,7 @@ DOUBLE_BATTLE_TEST("Instructed move will be absorbed by Lightning Rod if it turn OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { - MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft); + MOVE(playerLeft, MOVE_TACKLE, target: moveTarget); MOVE(opponentLeft, MOVE_PLASMA_FISTS, target: playerLeft); MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft); MOVE(opponentRight, MOVE_CELEBRATE); @@ -239,3 +242,65 @@ DOUBLE_BATTLE_TEST("Instructed move will be absorbed by Lightning Rod if it turn NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft); } } + +DOUBLE_BATTLE_TEST("Instructed move will be redirected by Follow Me after instructed target loses Stalwart") +{ + struct BattlePokemon *moveTarget = NULL; + PARAMETRIZE { moveTarget = opponentLeft; } + PARAMETRIZE { moveTarget = opponentRight; } + GIVEN { + ASSUME(gMovesInfo[MOVE_FOLLOW_ME].effect == EFFECT_FOLLOW_ME); + ASSUME(gMovesInfo[MOVE_SKILL_SWAP].effect == EFFECT_SKILL_SWAP); + PLAYER(SPECIES_DURALUDON) { Ability(ABILITY_STALWART); } + PLAYER(SPECIES_DURALUDON) { Ability(ABILITY_STALWART); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { + MOVE(playerLeft, MOVE_TACKLE, target: moveTarget); + MOVE(opponentLeft, MOVE_FOLLOW_ME); + MOVE(opponentRight, MOVE_SKILL_SWAP, target: playerLeft); + MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FOLLOW_ME, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft); + HP_BAR(moveTarget); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft); + HP_BAR(opponentLeft); + } +} + +DOUBLE_BATTLE_TEST("Instructed move will be redirected by Rage Powder after instructed target loses Grass typing") +{ + struct BattlePokemon *moveTarget = NULL; + PARAMETRIZE { moveTarget = opponentLeft; } + PARAMETRIZE { moveTarget = opponentRight; } + GIVEN { + ASSUME(gMovesInfo[MOVE_RAGE_POWDER].effect == EFFECT_FOLLOW_ME); + ASSUME(gMovesInfo[MOVE_RAGE_POWDER].powderMove == TRUE); + ASSUME(gMovesInfo[MOVE_SOAK].effect == EFFECT_SOAK); + PLAYER(SPECIES_TREECKO); + PLAYER(SPECIES_SCEPTILE); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { + MOVE(playerLeft, MOVE_TACKLE, target: moveTarget); + MOVE(opponentLeft, MOVE_RAGE_POWDER); + MOVE(opponentRight, MOVE_SOAK, target: playerLeft); + MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAGE_POWDER, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft); + HP_BAR(moveTarget); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SOAK, opponentRight); + MESSAGE("Treecko transformed into the Water type!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft); + HP_BAR(opponentLeft); + } +} diff --git a/test/battle/move_effect/sleep_talk.c b/test/battle/move_effect/sleep_talk.c index 8ecd600f36..aaff5dc123 100644 --- a/test/battle/move_effect/sleep_talk.c +++ b/test/battle/move_effect/sleep_talk.c @@ -32,7 +32,6 @@ SINGLE_BATTLE_TEST("Sleep Talk fails if not asleep") } } - SINGLE_BATTLE_TEST("Sleep Talk works if user has Comatose") { @@ -91,3 +90,59 @@ SINGLE_BATTLE_TEST("Sleep Talk can use moves while choiced into Sleep Talk") ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE); } } + +SINGLE_BATTLE_TEST("Sleep Talk fails if user is taunted") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_TAUNT].effect == EFFECT_TAUNT); + ASSUME(gMovesInfo[MOVE_SLEEP_TALK].category == DAMAGE_CATEGORY_STATUS); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); Moves(MOVE_SLEEP_TALK, MOVE_TACKLE, MOVE_FLY, MOVE_DIG); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TAUNT); MOVE(player, MOVE_SLEEP_TALK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAUNT, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SLEEP_TALK, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + } + } +} + +DOUBLE_BATTLE_TEST("Sleep Talk calls move and that move may be redirected by Lightning Rod") +{ + PASSES_RANDOMLY(1, 2, RNG_RANDOM_TARGET); + GIVEN { + ASSUME(gMovesInfo[MOVE_SPARK].type == TYPE_ELECTRIC); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); Moves(MOVE_SLEEP_TALK, MOVE_SPARK, MOVE_FLY, MOVE_DIG); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_RAICHU) { Ability(ABILITY_LIGHTNING_ROD); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SLEEP_TALK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SLEEP_TALK, playerLeft); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SPARK, playerLeft); + MESSAGE("The opposing Raichu's Lightning Rod took the attack!"); + ABILITY_POPUP(opponentRight, ABILITY_LIGHTNING_ROD); + } +} + +DOUBLE_BATTLE_TEST("Sleep Talk calls move and that move may be redirected by Storm Drain") +{ + PASSES_RANDOMLY(1, 2, RNG_RANDOM_TARGET); + GIVEN { + ASSUME(gMovesInfo[MOVE_WATER_GUN].type == TYPE_WATER); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); Moves(MOVE_SLEEP_TALK, MOVE_WATER_GUN, MOVE_FLY, MOVE_DIG); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GASTRODON) { Ability(ABILITY_STORM_DRAIN); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SLEEP_TALK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SLEEP_TALK, playerLeft); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, playerLeft); + MESSAGE("The opposing Gastrodon's Storm Drain took the attack!"); + ABILITY_POPUP(opponentRight, ABILITY_STORM_DRAIN); + } +} diff --git a/test/battle/move_effect/snore.c b/test/battle/move_effect/snore.c new file mode 100644 index 0000000000..d83e35b16c --- /dev/null +++ b/test/battle/move_effect/snore.c @@ -0,0 +1,59 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_SNORE].effect == EFFECT_SNORE); +} + +SINGLE_BATTLE_TEST("Snore fails if not asleep") +{ + u32 status; + PARAMETRIZE { status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_NONE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Status1(status); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SNORE); } + } SCENE { + if (status == STATUS1_SLEEP) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SNORE, player); + NOT MESSAGE("But it failed!"); + } + else { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SNORE, player); + MESSAGE("But it failed!"); + } + } +} + +SINGLE_BATTLE_TEST("Snore works if user has Comatose") +{ + + GIVEN { + PLAYER(SPECIES_KOMALA); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SNORE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SNORE, player); + NOT MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Snore fails if user is throat chopped") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_THROAT_CHOP, MOVE_EFFECT_THROAT_CHOP)); + ASSUME(gMovesInfo[MOVE_SNORE].soundMove == TRUE); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_THROAT_CHOP); MOVE(player, MOVE_SNORE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THROAT_CHOP, opponent); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SNORE, player); + } +} From 7cd614a0cd2019d913f78bd83467ce8bd7d0d268 Mon Sep 17 00:00:00 2001 From: moostoet <70690976+moostoet@users.noreply.github.com> Date: Sat, 15 Feb 2025 22:48:26 +0100 Subject: [PATCH 05/14] Fix AI wrongly thinking it strikes first with priority even if player is using priority themselves (#6274) --- src/battle_ai_util.c | 9 ++++++++- test/battle/ai/ai.c | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index e440091183..b765616802 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -1094,8 +1094,15 @@ s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler, u32 moveConsidered) u32 abilityAI = AI_DATA->abilities[battlerAI]; u32 abilityPlayer = AI_DATA->abilities[battler]; - if (GetMovePriority(battlerAI, moveConsidered) > 0) + u32 predictedMove = AI_DATA->lastUsedMove[battler]; // TODO update for move prediction + + s8 aiPriority = GetMovePriority(battlerAI, moveConsidered); + s8 playerPriority = GetMovePriority(battler, predictedMove); + + if (aiPriority > playerPriority) return AI_IS_FASTER; + else if (aiPriority < playerPriority) + return AI_IS_SLOWER; speedBattlerAI = GetBattlerTotalSpeedStatArgs(battlerAI, abilityAI, holdEffectAI); speedBattler = GetBattlerTotalSpeedStatArgs(battler, abilityPlayer, holdEffectPlayer); diff --git a/test/battle/ai/ai.c b/test/battle/ai/ai.c index 0883e3cc59..5f01c761a6 100644 --- a/test/battle/ai/ai.c +++ b/test/battle/ai/ai.c @@ -831,3 +831,17 @@ AI_SINGLE_BATTLE_TEST("AI stays choice locked into moves in spite of the player' TURN { EXPECT_MOVE(opponent, aiMove); } } } + +AI_SINGLE_BATTLE_TEST("AI won't use Sucker Punch if it expects a move of the same priority bracket and the opponent is faster") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_QUICK_ATTACK].priority == 1); + ASSUME(gMovesInfo[MOVE_SUCKER_PUNCH].priority == 1); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { Speed(300); Moves(MOVE_QUICK_ATTACK); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(100); Moves(MOVE_SUCKER_PUNCH, MOVE_TACKLE); } + } WHEN { + TURN { MOVE(player, MOVE_QUICK_ATTACK); EXPECT_MOVE(opponent, MOVE_SUCKER_PUNCH); } + TURN { MOVE(player, MOVE_QUICK_ATTACK); EXPECT_MOVE(opponent, MOVE_TACKLE); } + } +} From 444437274c5cfa44ab4972b5fc685c946c9bca40 Mon Sep 17 00:00:00 2001 From: Salem <65783283+u8-Salem@users.noreply.github.com> Date: Mon, 17 Feb 2025 10:02:28 +0100 Subject: [PATCH 06/14] remove obsolete check for steven when retrieving partner name (#6283) --- src/pokemon.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/pokemon.c b/src/pokemon.c index 437ed55e4b..6066a5c149 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -6056,15 +6056,8 @@ const u8 *GetTrainerPartnerName(void) { if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER) { - if (gPartnerTrainerId == TRAINER_PARTNER(PARTNER_STEVEN)) - { - return GetTrainerNameFromId(TRAINER_STEVEN); - } - else - { - GetFrontierTrainerName(gStringVar1, gPartnerTrainerId); - return gStringVar1; - } + GetFrontierTrainerName(gStringVar1, gPartnerTrainerId); + return gStringVar1; } else { From 4d9659987d9ba5720121a2b9c4615f366934a662 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Mon, 17 Feb 2025 12:34:21 +0100 Subject: [PATCH 07/14] Fixes Protean not restoring types after ai damage calcs (#6280) --- src/battle_ai_util.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index b765616802..dadf39c61e 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -542,6 +542,20 @@ static inline s32 SetFixedMoveBasePower(u32 battlerAtk, u32 move) return fixedBasePower; } +static inline void AI_StoreBattlerTypes(u32 battlerAtk, u32 *types) +{ + types[0] = gBattleMons[battlerAtk].types[0]; + types[1] = gBattleMons[battlerAtk].types[1]; + types[2] = gBattleMons[battlerAtk].types[2]; +} + +static inline void AI_RestoreBattlerTypes(u32 battlerAtk, u32 *types) +{ + gBattleMons[battlerAtk].types[0] = types[0]; + gBattleMons[battlerAtk].types[1] = types[1]; + gBattleMons[battlerAtk].types[2] = types[2]; +} + static inline void CalcDynamicMoveDamage(struct DamageCalculationData *damageCalcData, s32 *expectedDamage, s32 *minimumDamage, u32 holdEffectAtk, u32 abilityAtk) { u32 move = damageCalcData->move; @@ -664,6 +678,9 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u { s32 critChanceIndex, fixedBasePower; + u32 types[3]; + AI_StoreBattlerTypes(battlerAtk, types); + ProteanTryChangeType(battlerAtk, aiData->abilities[battlerAtk], move, moveType); fixedBasePower = SetFixedMoveBasePower(battlerAtk, move); @@ -741,6 +758,8 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u aiData->holdEffects[battlerAtk], aiData->abilities[battlerAtk]); } + + AI_RestoreBattlerTypes(battlerAtk, types); } else { From b2f45fcf1f09f50c11addb10f5dbc5c0efd17113 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Tue, 18 Feb 2025 20:43:07 +0100 Subject: [PATCH 08/14] Restoretarget in Rototiller script + Tests (#6296) --- data/battle_scripts_1.s | 1 + test/battle/move_effect/rototiller.c | 98 ++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 test/battle/move_effect/rototiller.c diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index c69129125a..c5a8bdd6b9 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -1564,6 +1564,7 @@ BattleScript_RototillerMoveTargetEnd: moveendto MOVEEND_NEXT_TARGET addbyte gBattlerTarget, 1 jumpifbytenotequal gBattlerTarget, gBattlersCount, BattleScript_RototillerLoop + restoretarget end BattleScript_RototillerCantRaiseMultipleStats: diff --git a/test/battle/move_effect/rototiller.c b/test/battle/move_effect/rototiller.c new file mode 100644 index 0000000000..32ae7da75f --- /dev/null +++ b/test/battle/move_effect/rototiller.c @@ -0,0 +1,98 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_ROTOTILLER].effect == EFFECT_ROTOTILLER); +} + +DOUBLE_BATTLE_TEST("Rototiller boosts Attack and Special Attack of all Grass types on the field") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_TANGELA].types[0] == TYPE_GRASS); + ASSUME(gSpeciesInfo[SPECIES_SNIVY].types[0] == TYPE_GRASS); + PLAYER(SPECIES_TANGELA); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SNIVY); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, MOVE_ROTOTILLER); MOVE(playerLeft, MOVE_CELEBRATE); MOVE(opponentLeft, MOVE_CELEBRATE); MOVE(opponentRight, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROTOTILLER, playerRight); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(playerLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponentLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(playerRight->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentRight->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Rototiller fails if there are no valid targets") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[0] != TYPE_GRASS); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[1] != TYPE_GRASS); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROTOTILLER); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_ROTOTILLER, player); + MESSAGE("Wobbuffet used Rototiller!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Rototiller doesn't affect pokemon that are semi-invulnerable") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_TANGELA].types[0] == TYPE_GRASS); + ASSUME(gMovesInfo[MOVE_DIG].effect == EFFECT_SEMI_INVULNERABLE); + PLAYER(SPECIES_TANGELA); + OPPONENT(SPECIES_TANGELA); + } WHEN { + TURN { MOVE(opponent, MOVE_DIG); MOVE(player, MOVE_ROTOTILLER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DIG, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROTOTILLER, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("It won't have any effect on the opposing Tangela!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Rototiller fails if the only valid target is semi-invulnerable") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_TANGELA].types[0] == TYPE_GRASS); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[0] != TYPE_GRASS); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[1] != TYPE_GRASS); + ASSUME(gMovesInfo[MOVE_DIG].effect == EFFECT_SEMI_INVULNERABLE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TANGELA); + } WHEN { + TURN { MOVE(opponent, MOVE_DIG); MOVE(player, MOVE_ROTOTILLER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DIG, opponent); + MESSAGE("Wobbuffet used Rototiller!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_ROTOTILLER, player); + MESSAGE("But it failed!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + } +} From 5c3381b2f64cd2b22f1abb9f8f6db8b42665d88a Mon Sep 17 00:00:00 2001 From: cawtds <38510667+cawtds@users.noreply.github.com> Date: Wed, 19 Feb 2025 12:51:11 +0100 Subject: [PATCH 09/14] fix spit up getting skipped (#6295) --- src/debug.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debug.c b/src/debug.c index 3615ef1997..9064dbb36c 100644 --- a/src/debug.c +++ b/src/debug.c @@ -4009,7 +4009,7 @@ static void DebugAction_Give_Pokemon_ComplexCreateMon(u8 taskId) //https://githu //Moves for (i = 0; i < MAX_MON_MOVES; i++) { - if (moves[i] == 0 || moves[i] == 0xFF || moves[i] >= MOVES_COUNT) + if (moves[i] == MOVE_NONE || moves[i] >= MOVES_COUNT) continue; SetMonMoveSlot(&mon, moves[i], i); From 290ddfb9ad9052984beaea2bd76f2b654e4aabbf Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Wed, 19 Feb 2025 18:07:40 +0100 Subject: [PATCH 10/14] Fixed Kecleon Shiny palette (#6298) Co-authored-by: Hedara --- graphics/pokemon/kecleon/overworld_shiny.pal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphics/pokemon/kecleon/overworld_shiny.pal b/graphics/pokemon/kecleon/overworld_shiny.pal index 77cc0112e0..d12afcf8bb 100644 --- a/graphics/pokemon/kecleon/overworld_shiny.pal +++ b/graphics/pokemon/kecleon/overworld_shiny.pal @@ -14,6 +14,6 @@ JASC-PAL 96 168 120 64 136 96 232 232 248 -152 40 64 +39 44 149 0 0 0 0 0 0 From e09ac1d96902a19092c62e3ec156edc6ca1de99c Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Wed, 19 Feb 2025 18:07:54 +0100 Subject: [PATCH 11/14] Update how_to_testing_system.md (#6281) Co-authored-by: Bassoonian --- docs/tutorials/how_to_testing_system.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/tutorials/how_to_testing_system.md b/docs/tutorials/how_to_testing_system.md index c573dfbbf7..8a97800f87 100644 --- a/docs/tutorials/how_to_testing_system.md +++ b/docs/tutorials/how_to_testing_system.md @@ -554,6 +554,10 @@ Causes the test to fail if a and b compare incorrectly, e.g. EXPECT_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); ``` +### `FORCE_MOVE_ANIM` +`FORCE_MOVE_ANIM(TRUE)` +Forces the moves in the current test to do their animations in headless mode. Useful for debugging animations. + ## Overworld Command Reference ### `OVERWORLD_SCRIPT` From 5340dc868784dbdc50ad3e3372fe4d4234a336cf Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Wed, 19 Feb 2025 17:09:06 +0000 Subject: [PATCH 12/14] Fixes non-Ghost type Curse animation (#6299) --- data/battle_scripts_1.s | 4 ++++ src/battle_util.c | 36 +++++---------------------------- test/battle/move_effect/curse.c | 2 ++ 3 files changed, 11 insertions(+), 31 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index c5a8bdd6b9..0fc03658c1 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -4142,11 +4142,15 @@ BattleScript_CurseTrySpeed:: setbyte sB_ANIM_TURN, 1 attackanimation waitanimation + setbyte sSTAT_ANIM_PLAYED, FALSE + playstatchangeanimation BS_ATTACKER, BIT_SPEED, STAT_CHANGE_NEGATIVE setstatchanger STAT_SPEED, 1, TRUE statbuffchange MOVE_EFFECT_AFFECTS_USER | STAT_CHANGE_ALLOW_PTR, BattleScript_CurseTryAttack printfromtable gStatDownStringIds waitmessage B_WAIT_TIME_LONG BattleScript_CurseTryAttack:: + setbyte sSTAT_ANIM_PLAYED, FALSE + playstatchangeanimation BS_ATTACKER, BIT_ATK | BIT_DEF, 0 setstatchanger STAT_ATK, 1, FALSE statbuffchange MOVE_EFFECT_AFFECTS_USER | STAT_CHANGE_ALLOW_PTR, BattleScript_CurseTryDefense printfromtable gStatUpStringIds diff --git a/src/battle_util.c b/src/battle_util.c index 14469b072a..09679f656a 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -133,11 +133,11 @@ bool32 HandleMoveTargetRedirection(void) u32 ability = GetBattlerAbility(gBattleStruct->moveTarget[gBattlerAttacker]); if (IsAffectedByFollowMe(gBattlerAttacker, side, gCurrentMove) - && moveTarget == MOVE_TARGET_SELECTED - && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gSideTimers[side].followmeTarget)) + && moveTarget == MOVE_TARGET_SELECTED + && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gSideTimers[side].followmeTarget)) { gBattleStruct->moveTarget[gBattlerAttacker] = gBattlerTarget = gSideTimers[side].followmeTarget; // follow me moxie fix - return FALSE; + return TRUE; } else if (IsDoubleBattle() && gSideTimers[side].followmeTimer == 0 @@ -164,33 +164,7 @@ bool32 HandleMoveTargetRedirection(void) redirectorOrderNum = GetBattlerTurnOrderNum(battler); } } - if (redirectorOrderNum == MAX_BATTLERS_COUNT) - { - if (moveTarget & MOVE_TARGET_RANDOM) - { - gBattlerTarget = SetRandomTarget(gBattlerAttacker); - } - else if (moveTarget & MOVE_TARGET_FOES_AND_ALLY) - { - for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount; gBattlerTarget++) - { - if (gBattlerTarget == gBattlerAttacker) - continue; - if (IsBattlerAlive(gBattlerTarget)) - break; - } - } - else - { - gBattlerTarget = *(gBattleStruct->moveTarget + gBattlerAttacker); - } - - if (!IsBattlerAlive(gBattlerTarget) && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget)) - { - gBattlerTarget = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerTarget))); - } - } - else + if (redirectorOrderNum != MAX_BATTLERS_COUNT) { u16 battlerAbility; battler = gBattlerByTurnOrder[redirectorOrderNum]; @@ -202,8 +176,8 @@ bool32 HandleMoveTargetRedirection(void) else if (battlerAbility == ABILITY_STORM_DRAIN) gSpecialStatuses[battler].stormDrainRedirected = TRUE; gBattlerTarget = battler; + return TRUE; } - return TRUE; } return FALSE; } diff --git a/test/battle/move_effect/curse.c b/test/battle/move_effect/curse.c index 0269659b37..9d456ef8db 100644 --- a/test/battle/move_effect/curse.c +++ b/test/battle/move_effect/curse.c @@ -15,7 +15,9 @@ SINGLE_BATTLE_TEST("Curse lowers Speed, raises Attack, and raises Defense when u TURN { MOVE(player, MOVE_CURSE); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_CURSE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); MESSAGE("Wobbuffet's Speed fell!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); MESSAGE("Wobbuffet's Attack rose!"); MESSAGE("Wobbuffet's Defense rose!"); } From f6ff64cdbe71040f77ca3bab9773ac67d23004b8 Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Thu, 20 Feb 2025 15:55:10 +0000 Subject: [PATCH 13/14] Fixes Brick Break/Psychic Fangs/Raging Bull breaking screens if target is immune (#6308) --- data/battle_scripts_1.s | 3 +- test/battle/move_effect/brick_break.c | 94 ++++++++++++++++----------- test/battle/move_effect/raging_bull.c | 1 - 3 files changed, 59 insertions(+), 39 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 0fc03658c1..3016540a80 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -632,7 +632,7 @@ BattleScript_AffectionBasedStatus_HealFreezeString: printstring STRINGID_ATTACKERMELTEDTHEICE goto BattleScript_AffectionBasedStatusHeal_Continue BattleScript_AffectionBasedStatus_HealFrostbiteString: - printstring STRINGID_ATTACKERMELTEDTHEICE + printstring STRINGID_ATTACKERHEALEDITSFROSTBITE BattleScript_AffectionBasedStatusHeal_Continue: waitmessage B_WAIT_TIME_LONG clearstatus BS_ATTACKER @@ -5090,6 +5090,7 @@ BattleScript_EffectBrickBreak:: accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE attackstring ppreduce + typecalc removelightscreenreflect critcalc damagecalc diff --git a/test/battle/move_effect/brick_break.c b/test/battle/move_effect/brick_break.c index 513369b5a1..30436c6d56 100644 --- a/test/battle/move_effect/brick_break.c +++ b/test/battle/move_effect/brick_break.c @@ -4,119 +4,139 @@ ASSUMPTIONS { ASSUME(gMovesInfo[MOVE_BRICK_BREAK].effect == EFFECT_BRICK_BREAK); + ASSUME(gMovesInfo[MOVE_PSYCHIC_FANGS].effect == EFFECT_BRICK_BREAK); ASSUME(gMovesInfo[MOVE_SNOWSCAPE].effect == EFFECT_SNOWSCAPE); ASSUME(gMovesInfo[MOVE_LIGHT_SCREEN].effect == EFFECT_LIGHT_SCREEN); ASSUME(gMovesInfo[MOVE_REFLECT].effect == EFFECT_REFLECT); ASSUME(gMovesInfo[MOVE_AURORA_VEIL].effect == EFFECT_AURORA_VEIL); } -SINGLE_BATTLE_TEST("Brick Break removes Light Screen, Reflect and Aurora Veil from the target's side of the field") +SINGLE_BATTLE_TEST("Brick Break and Psychic Fangs remove Light Screen, Reflect and Aurora Veil from the target's side of the field") { - u16 move; + u32 move; + u32 breakingMove; - PARAMETRIZE { move = MOVE_LIGHT_SCREEN; } - PARAMETRIZE { move = MOVE_REFLECT; } - PARAMETRIZE { move = MOVE_AURORA_VEIL; } + PARAMETRIZE { move = MOVE_LIGHT_SCREEN; breakingMove = MOVE_BRICK_BREAK; } + PARAMETRIZE { move = MOVE_REFLECT; breakingMove = MOVE_BRICK_BREAK; } + PARAMETRIZE { move = MOVE_AURORA_VEIL; breakingMove = MOVE_BRICK_BREAK; } + PARAMETRIZE { move = MOVE_LIGHT_SCREEN; breakingMove = MOVE_PSYCHIC_FANGS; } + PARAMETRIZE { move = MOVE_REFLECT; breakingMove = MOVE_PSYCHIC_FANGS; } + PARAMETRIZE { move = MOVE_AURORA_VEIL; breakingMove = MOVE_PSYCHIC_FANGS; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(player, MOVE_SNOWSCAPE); } - TURN { MOVE(opponent, move); MOVE(player, MOVE_BRICK_BREAK); } + TURN { MOVE(opponent, move); MOVE(player, breakingMove); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_SNOWSCAPE, player); ANIMATION(ANIM_TYPE_MOVE, move, opponent); - ANIMATION(ANIM_TYPE_MOVE, MOVE_BRICK_BREAK, player); + ANIMATION(ANIM_TYPE_MOVE, breakingMove, player); MESSAGE("The wall shattered!"); HP_BAR(opponent); } } -SINGLE_BATTLE_TEST("Brick Break doesn't remove Light Screen, Reflect and Aurora Veil if the target is immune") +SINGLE_BATTLE_TEST("Brick Break and Psychic Fangs don't remove Light Screen, Reflect and Aurora Veil if the target is immune") { - u16 move; + u32 move; + u32 breakingMove; - PARAMETRIZE { move = MOVE_LIGHT_SCREEN; } - PARAMETRIZE { move = MOVE_REFLECT; } - PARAMETRIZE { move = MOVE_AURORA_VEIL; } + PARAMETRIZE { move = MOVE_LIGHT_SCREEN; breakingMove = MOVE_BRICK_BREAK; } + PARAMETRIZE { move = MOVE_REFLECT; breakingMove = MOVE_BRICK_BREAK; } + PARAMETRIZE { move = MOVE_AURORA_VEIL; breakingMove = MOVE_BRICK_BREAK; } + PARAMETRIZE { move = MOVE_LIGHT_SCREEN; breakingMove = MOVE_PSYCHIC_FANGS; } + PARAMETRIZE { move = MOVE_REFLECT; breakingMove = MOVE_PSYCHIC_FANGS; } + PARAMETRIZE { move = MOVE_AURORA_VEIL; breakingMove = MOVE_PSYCHIC_FANGS; } - KNOWN_FAILING; GIVEN { PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_GASTLY); + OPPONENT(SPECIES_SABLEYE); } WHEN { TURN { MOVE(player, MOVE_SNOWSCAPE); } - TURN { MOVE(opponent, move); MOVE(player, MOVE_BRICK_BREAK); } + TURN { MOVE(opponent, move); MOVE(player, breakingMove); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_SNOWSCAPE, player); ANIMATION(ANIM_TYPE_MOVE, move, opponent); NONE_OF { - ANIMATION(ANIM_TYPE_MOVE, MOVE_BRICK_BREAK, player); + ANIMATION(ANIM_TYPE_MOVE, breakingMove, player); MESSAGE("The wall shattered!"); HP_BAR(opponent); } } } -SINGLE_BATTLE_TEST("Brick Break doesn't remove Light Screen, Reflect and Aurora Veil if the target Protected") +SINGLE_BATTLE_TEST("Brick Break and Psychic Fangs don't remove Light Screen, Reflect and Aurora Veil if the target Protected") { - u16 move; + u32 move; + u32 breakingMove; - PARAMETRIZE { move = MOVE_LIGHT_SCREEN; } - PARAMETRIZE { move = MOVE_REFLECT; } - PARAMETRIZE { move = MOVE_AURORA_VEIL; } + PARAMETRIZE { move = MOVE_LIGHT_SCREEN; breakingMove = MOVE_BRICK_BREAK; } + PARAMETRIZE { move = MOVE_REFLECT; breakingMove = MOVE_BRICK_BREAK; } + PARAMETRIZE { move = MOVE_AURORA_VEIL; breakingMove = MOVE_BRICK_BREAK; } + PARAMETRIZE { move = MOVE_LIGHT_SCREEN; breakingMove = MOVE_PSYCHIC_FANGS; } + PARAMETRIZE { move = MOVE_REFLECT; breakingMove = MOVE_PSYCHIC_FANGS; } + PARAMETRIZE { move = MOVE_AURORA_VEIL; breakingMove = MOVE_PSYCHIC_FANGS; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(player, MOVE_SNOWSCAPE); MOVE(opponent, move); } - TURN { MOVE(player, MOVE_BRICK_BREAK); MOVE(opponent, MOVE_PROTECT); } + TURN { MOVE(player, breakingMove); MOVE(opponent, MOVE_PROTECT); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_SNOWSCAPE, player); ANIMATION(ANIM_TYPE_MOVE, move, opponent); ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponent); NONE_OF { - ANIMATION(ANIM_TYPE_MOVE, MOVE_BRICK_BREAK, player); + ANIMATION(ANIM_TYPE_MOVE, breakingMove, player); MESSAGE("The wall shattered!"); HP_BAR(opponent); } } } -SINGLE_BATTLE_TEST("Brick Break doesn't remove Light Screen, Reflect and Aurora Veil if it misses") +SINGLE_BATTLE_TEST("Brick Break and Psychic Fangs don't remove Light Screen, Reflect and Aurora Veil if it misses") { - u16 move; + u32 move; + u32 breakingMove; - PARAMETRIZE { move = MOVE_LIGHT_SCREEN; } - PARAMETRIZE { move = MOVE_REFLECT; } - PARAMETRIZE { move = MOVE_AURORA_VEIL; } + PARAMETRIZE { move = MOVE_LIGHT_SCREEN; breakingMove = MOVE_BRICK_BREAK; } + PARAMETRIZE { move = MOVE_REFLECT; breakingMove = MOVE_BRICK_BREAK; } + PARAMETRIZE { move = MOVE_AURORA_VEIL; breakingMove = MOVE_BRICK_BREAK; } + PARAMETRIZE { move = MOVE_LIGHT_SCREEN; breakingMove = MOVE_PSYCHIC_FANGS; } + PARAMETRIZE { move = MOVE_REFLECT; breakingMove = MOVE_PSYCHIC_FANGS; } + PARAMETRIZE { move = MOVE_AURORA_VEIL; breakingMove = MOVE_PSYCHIC_FANGS; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_BRIGHT_POWDER); } } WHEN { TURN { MOVE(player, MOVE_SNOWSCAPE); MOVE(opponent, move); } - TURN { MOVE(player, MOVE_BRICK_BREAK, hit: FALSE); } + TURN { MOVE(player, breakingMove, hit: FALSE); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_SNOWSCAPE, player); ANIMATION(ANIM_TYPE_MOVE, move, opponent); NONE_OF { - ANIMATION(ANIM_TYPE_MOVE, MOVE_BRICK_BREAK, player); + ANIMATION(ANIM_TYPE_MOVE, breakingMove, player); MESSAGE("The wall shattered!"); HP_BAR(opponent); } } } -DOUBLE_BATTLE_TEST("Brick Break can remove Light Screen, Reflect and Aurora Veil on users side") +DOUBLE_BATTLE_TEST("Brick Break and Psychic Fangs can remove Light Screen, Reflect and Aurora Veil on users side") { - u16 move; + u32 move; + u32 breakingMove; - PARAMETRIZE { move = MOVE_LIGHT_SCREEN; } - PARAMETRIZE { move = MOVE_REFLECT; } - PARAMETRIZE { move = MOVE_AURORA_VEIL; } + PARAMETRIZE { move = MOVE_LIGHT_SCREEN; breakingMove = MOVE_BRICK_BREAK; } + PARAMETRIZE { move = MOVE_REFLECT; breakingMove = MOVE_BRICK_BREAK; } + PARAMETRIZE { move = MOVE_AURORA_VEIL; breakingMove = MOVE_BRICK_BREAK; } + PARAMETRIZE { move = MOVE_LIGHT_SCREEN; breakingMove = MOVE_PSYCHIC_FANGS; } + PARAMETRIZE { move = MOVE_REFLECT; breakingMove = MOVE_PSYCHIC_FANGS; } + PARAMETRIZE { move = MOVE_AURORA_VEIL; breakingMove = MOVE_PSYCHIC_FANGS; } GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -127,12 +147,12 @@ DOUBLE_BATTLE_TEST("Brick Break can remove Light Screen, Reflect and Aurora Veil TURN { MOVE(opponentLeft, MOVE_SNOWSCAPE); MOVE(playerLeft, move); - MOVE(playerRight, MOVE_BRICK_BREAK, target: playerLeft); + MOVE(playerRight, breakingMove, target: playerLeft); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_SNOWSCAPE, opponentLeft); ANIMATION(ANIM_TYPE_MOVE, move, playerLeft); - ANIMATION(ANIM_TYPE_MOVE, MOVE_BRICK_BREAK, playerRight); + ANIMATION(ANIM_TYPE_MOVE, breakingMove, playerRight); MESSAGE("The wall shattered!"); HP_BAR(playerLeft); } diff --git a/test/battle/move_effect/raging_bull.c b/test/battle/move_effect/raging_bull.c index 7e72ca8273..17b9c23985 100644 --- a/test/battle/move_effect/raging_bull.c +++ b/test/battle/move_effect/raging_bull.c @@ -41,7 +41,6 @@ SINGLE_BATTLE_TEST("Raging Bull doesn't remove Light Screen, Reflect and Aurora PARAMETRIZE { move = MOVE_REFLECT; } PARAMETRIZE { move = MOVE_AURORA_VEIL; } - KNOWN_FAILING; GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_GASTLY); From 767ea38dbf7ee875ad60fa8082d9b15378e1e554 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Thu, 20 Feb 2025 23:18:27 +0100 Subject: [PATCH 14/14] Fixes doesnt effect message for Thunder Wave (#6304) Co-authored-by: Bassoonian --- data/battle_scripts_1.s | 2 +- test/battle/status1/paralysis.c | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index c8fa0103d6..c921c9d07a 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -3657,12 +3657,12 @@ BattleScript_EffectParalyze:: jumpifmovehadnoeffect BattleScript_ButItFailed jumpifstatus BS_TARGET, STATUS1_PARALYSIS, BattleScript_AlreadyParalyzed jumpifelectricabilityaffected BS_TARGET, ABILITY_VOLT_ABSORB, BattleScript_VoltAbsorbHeal + clearmoveresultflags MOVE_RESULT_SUPER_EFFECTIVE | MOVE_RESULT_NOT_VERY_EFFECTIVE tryparalyzetype BS_ATTACKER, BS_TARGET, BattleScript_NotAffected jumpifstatus BS_TARGET, STATUS1_ANY, BattleScript_ButItFailed jumpifterrainaffected BS_TARGET, STATUS_FIELD_MISTY_TERRAIN, BattleScript_MistyTerrainPrevents accuracycheck BattleScript_ButItFailed, ACC_CURR_MOVE jumpifsafeguard BattleScript_SafeguardProtected - clearmoveresultflags MOVE_RESULT_SUPER_EFFECTIVE | MOVE_RESULT_NOT_VERY_EFFECTIVE attackanimation waitanimation seteffectprimary MOVE_EFFECT_PARALYSIS diff --git a/test/battle/status1/paralysis.c b/test/battle/status1/paralysis.c index 4caeb3c509..64ed27f361 100644 --- a/test/battle/status1/paralysis.c +++ b/test/battle/status1/paralysis.c @@ -62,3 +62,18 @@ AI_SINGLE_BATTLE_TEST("AI avoids Thunder Wave when it can not paralyse target") TURN { SCORE_EQ(opponent, MOVE_CELEBRATE, MOVE_THUNDER_WAVE); } // Both get -10 } } + +SINGLE_BATTLE_TEST("Thunder Wave doesn't affect Electric types in Gen6+") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_PIKACHU].types[0] == TYPE_ELECTRIC); + ASSUME(B_PARALYZE_ELECTRIC >= GEN_6); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIKACHU); + } WHEN { + TURN { MOVE(player, MOVE_THUNDER_WAVE); } + } SCENE { + MESSAGE("Wobbuffet used Thunder Wave!"); + MESSAGE("It doesn't affect the opposing Pikachu…"); + } +}