diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index c8fa0103d6..71df1f7dde 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -640,7 +640,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 @@ -1563,6 +1563,7 @@ BattleScript_RototillerMoveTargetEnd: moveendto MOVEEND_NEXT_TARGET addbyte gBattlerTarget, 1 jumpifbytenotequal gBattlerTarget, gBattlersCount, BattleScript_RototillerLoop + restoretarget end BattleScript_RototillerCantRaiseMultipleStats: @@ -1743,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 @@ -1762,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:: @@ -2187,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:: @@ -3657,12 +3652,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 @@ -3843,8 +3838,6 @@ BattleScript_EffectMetronome:: pause B_WAIT_TIME_SHORT attackanimation waitanimation - setbyte sB_ANIM_TURN, 0 - setbyte sB_ANIM_TARGETS_HIT, 0 metronome BattleScript_EffectLeechSeed:: @@ -4051,8 +4044,6 @@ BattleScript_SleepTalkIsAsleep:: BattleScript_SleepTalkUsingMove:: attackanimation waitanimation - setbyte sB_ANIM_TURN, 0 - setbyte sB_ANIM_TARGETS_HIT, 0 jumptocalledmove TRUE BattleScript_EffectDestinyBond:: @@ -4167,11 +4158,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 @@ -5070,8 +5065,6 @@ BattleScript_EffectAssist:: assistattackselect BattleScript_FailedFromPpReduce attackanimation waitanimation - setbyte sB_ANIM_TURN, 0 - setbyte sB_ANIM_TARGETS_HIT, 0 jumptocalledmove TRUE BattleScript_EffectIngrain:: @@ -5112,6 +5105,7 @@ BattleScript_EffectBrickBreak:: accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE attackstring ppreduce + typecalc removelightscreenreflect critcalc damagecalc @@ -8651,8 +8645,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/docs/tutorials/how_to_testing_system.md b/docs/tutorials/how_to_testing_system.md index da11944e25..2e5f83ff76 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` 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 diff --git a/include/battle.h b/include/battle.h index f43659020f..7c62e66db1 100644 --- a/include/battle.h +++ b/include/battle.h @@ -835,6 +835,7 @@ struct BattleStruct u8 padding3:2; struct MessageStatus slideMessageStatus; 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/include/battle_util.h b/include/battle_util.h index eeb3f89135..6b7fdef74a 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -172,6 +172,7 @@ enum SleepClauseBlock 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_ai_util.c b/src/battle_ai_util.c index a122807293..7807872b59 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -533,6 +533,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; @@ -658,6 +672,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); @@ -735,6 +752,8 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u aiData->holdEffects[battlerAtk], aiData->abilities[battlerAtk]); } + + AI_RestoreBattlerTypes(battlerAtk, types); } else { @@ -1068,8 +1087,15 @@ s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler, u32 moveConsidered) u32 abilityAI = AI_DATA->abilities[battlerAI]; u32 abilityPlayer = AI_DATA->abilities[battler]; - if (GetBattleMovePriority(battlerAI, moveConsidered) > 0) + u32 predictedMove = AI_DATA->lastUsedMove[battler]; // TODO update for move prediction + + s8 aiPriority = GetBattleMovePriority(battlerAI, moveConsidered); + s8 playerPriority = GetBattleMovePriority(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/src/battle_script_commands.c b/src/battle_script_commands.c index 6824243300..efbac6019e 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -344,6 +344,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); @@ -8746,6 +8747,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); @@ -8755,6 +8768,8 @@ static void Cmd_jumptocalledmove(void) else gChosenMove = gCurrentMove = gCalledMove; + ResetValuesForCalledMove(); + gBattlescriptCurrInstr = GetMoveBattleScript(gCurrentMove); } @@ -10811,10 +10826,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; } @@ -11804,8 +11817,8 @@ static void SetMoveForMirrorMove(u32 move) gCurrentMove = move; } - SetAtkCancellerForCalledMove(); gBattlerTarget = GetBattleMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE); + ResetValuesForCalledMove(); gBattlescriptCurrInstr = GetMoveBattleScript(gCurrentMove); } @@ -13333,10 +13346,10 @@ static void Cmd_metronome(void) #endif gCurrentMove = RandomUniformExcept(RNG_METRONOME, 1, moveCount - 1, InvalidMetronomeMove); - SetAtkCancellerForCalledMove(); PrepareStringBattle(STRINGID_WAGGLINGAFINGER, gBattlerAttacker); gBattlescriptCurrInstr = GetMoveBattleScript(gCurrentMove); gBattlerTarget = GetBattleMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE); + ResetValuesForCalledMove(); } static void Cmd_dmgtolevel(void) @@ -18046,14 +18059,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 3fadae9c00..ad725db7e6 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -234,11 +234,69 @@ 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 = GetBattleMoveType(gCurrentMove); + u32 moveEffect = GetMoveEffect(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 TRUE; + } + else if (IsDoubleBattle() + && gSideTimers[side].followmeTimer == 0 + && (!IsBattleMoveStatus(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 + && moveEffect != EFFECT_SNIPE_SHOT + && moveEffect != EFFECT_PLEDGE + && GetBattlerAbility(gBattlerAttacker) != ABILITY_PROPELLER_TAIL + && GetBattlerAbility(gBattlerAttacker) != ABILITY_STALWART) + { + redirectorOrderNum = GetBattlerTurnOrderNum(battler); + } + } + if (redirectorOrderNum != MAX_BATTLERS_COUNT) + { + 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->battlerState[gBattlerAttacker].absentBattlerFlags @@ -308,7 +366,6 @@ void HandleAction_UseMove(void) // Set dynamic move type. SetTypeBeforeUsingMove(gChosenMove, gBattlerAttacker); - moveType = GetBattleMoveType(gCurrentMove); // check Z-Move used if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE && !IsBattleMoveStatus(gCurrentMove) && !IsZMove(gCurrentMove)) @@ -323,117 +380,44 @@ void HandleAction_UseMove(void) gCurrentMove = gChosenMove = GetMaxMove(gBattlerAttacker, gCurrentMove); } - moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove); - u32 moveEffect = GetMoveEffect(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 - && !gBattleStruct->battlerState[gBattleStruct->moveTarget[gBattlerAttacker]].pursuitTarget - && (!IsBattleMoveStatus(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 || !IsBattlerAlly(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 - && moveEffect != EFFECT_SNIPE_SHOT - && moveEffect != 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 = GetPartnerBattler(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 = GetPartnerBattler(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 = GetPartnerBattler(gBattlerTarget); + gBattlerTarget = *(gBattleStruct->moveTarget + gBattlerAttacker); + if (!IsBattlerAlive(gBattlerTarget) + && moveTarget != MOVE_TARGET_OPPONENTS_FIELD + && (GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget))) + { + gBattlerTarget = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerTarget))); + } } } @@ -3215,6 +3199,8 @@ static void CancellerAsleep(u32 *effect) gBattleMons[gBattlerAttacker].status1 -= toSub; if (gBattleMons[gBattlerAttacker].status1 & STATUS1_SLEEP) { + u32 moveEffect = GetMoveEffect(gChosenMove); + if (moveEffect != EFFECT_SNORE && moveEffect != EFFECT_SLEEP_TALK) if (gChosenMove != MOVE_SNORE && gChosenMove != MOVE_SLEEP_TALK) { gBattlescriptCurrInstr = BattleScript_MoveUsedIsAsleep; @@ -3551,7 +3537,7 @@ static void CancellerThaw(u32 *effect) static void CancellerStanceChangeTwo(u32 *effect) { - if (B_STANCE_CHANGE_FAIL >= GEN_7 && TryFormChangeBeforeMove()) + if (B_STANCE_CHANGE_FAIL >= GEN_7 && !gBattleStruct->isAtkCancelerForCalledMove && TryFormChangeBeforeMove()) *effect = 1; } @@ -5306,7 +5292,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; @@ -5325,6 +5312,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++; @@ -6297,7 +6285,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; @@ -6306,7 +6293,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 (IsBattlerAlly(gBattlerTarget, gBattlerAttacker)) diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 389fc8d5eb..0adc112f31 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -105,7 +105,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] = { diff --git a/src/debug.c b/src/debug.c index 99f8975197..8a20e6b2b1 100644 --- a/src/debug.c +++ b/src/debug.c @@ -3439,7 +3439,7 @@ static void DebugAction_Give_Pokemon_ComplexCreateMon(u8 taskId) //https://githu //Moves for (i = 0; i < MAX_MON_MOVES; i++) { - if (moves[i] == MOVE_NONE || moves[i] == 0xFF || moves[i] >= MOVES_COUNT) + if (moves[i] == MOVE_NONE || moves[i] >= MOVES_COUNT) continue; SetMonMoveSlot(&mon, moves[i], i); 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++; diff --git a/src/pokemon.c b/src/pokemon.c index 2483d636a3..6bec95c307 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -6074,15 +6074,8 @@ const u8 *GetTrainerPartnerName(void) { if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER) { - if (gPartnerTrainerId == TRAINER_PARTNER(PARTNER_STEVEN)) - { - return GetTrainerNameFromId(PARTNER_STEVEN); - } - else - { - GetFrontierTrainerName(gStringVar1, gPartnerTrainerId); - return gStringVar1; - } + GetFrontierTrainerName(gStringVar1, gPartnerTrainerId); + return gStringVar1; } else { 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); + } +} diff --git a/test/battle/ai/ai.c b/test/battle/ai/ai.c index e9b5a2e51c..da77f828e4 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); } + } +} diff --git a/test/battle/move_effect/brick_break.c b/test/battle/move_effect/brick_break.c index a4aae1e683..05e22f42d9 100644 --- a/test/battle/move_effect/brick_break.c +++ b/test/battle/move_effect/brick_break.c @@ -4,119 +4,139 @@ ASSUMPTIONS { ASSUME(GetMoveEffect(MOVE_BRICK_BREAK) == EFFECT_BRICK_BREAK); + ASSUME(GetMoveEffect(MOVE_PSYCHIC_FANGS) == EFFECT_BRICK_BREAK); ASSUME(GetMoveEffect(MOVE_SNOWSCAPE) == EFFECT_SNOWSCAPE); ASSUME(GetMoveEffect(MOVE_LIGHT_SCREEN) == EFFECT_LIGHT_SCREEN); ASSUME(GetMoveEffect(MOVE_REFLECT) == EFFECT_REFLECT); ASSUME(GetMoveEffect(MOVE_AURORA_VEIL) == 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/curse.c b/test/battle/move_effect/curse.c index 868608caa8..bb1de42fba 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!"); } 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 28294dc3a5..85f56d4c4b 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/raging_bull.c b/test/battle/move_effect/raging_bull.c index c75c977495..056a5093a8 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); 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); + } +} diff --git a/test/battle/move_effect/sleep_talk.c b/test/battle/move_effect/sleep_talk.c index 4ac89e69d8..b00f89e582 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); + } +} 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…"); + } +}