From 7a393a974a5ae91a72399e2e612d31c5182287cc Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Tue, 28 May 2024 11:34:56 +0200 Subject: [PATCH] Adds Dragon Darts effect (CFRU port) (#4612) * Adds Dragon Darts effect (CFRU port) * fix test compile * review --- include/battle_util.h | 47 +++--- include/constants/battle_move_effects.h | 1 + src/battle_script_commands.c | 46 ++++-- src/battle_util.c | 123 ++++++++++----- src/data/battle_move_effects.h | 6 + src/data/moves_info.h | 2 +- test/battle/move_effect/dragon_darts.c | 189 ++++++++++++++++++++++++ 7 files changed, 342 insertions(+), 72 deletions(-) create mode 100644 test/battle/move_effect/dragon_darts.c diff --git a/include/battle_util.h b/include/battle_util.h index df32314039..2bfc9cba9e 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -20,26 +20,31 @@ #define MOVE_LIMITATION_PLACEHOLDER (1 << 15) #define MOVE_LIMITATIONS_ALL 0xFFFF -#define ABILITYEFFECT_ON_SWITCHIN 0 -#define ABILITYEFFECT_ENDTURN 1 -#define ABILITYEFFECT_MOVES_BLOCK 2 -#define ABILITYEFFECT_ABSORBING 3 -#define ABILITYEFFECT_MOVE_END_ATTACKER 4 -#define ABILITYEFFECT_MOVE_END 5 -#define ABILITYEFFECT_IMMUNITY 6 -#define ABILITYEFFECT_SYNCHRONIZE 7 -#define ABILITYEFFECT_ATK_SYNCHRONIZE 8 -#define ABILITYEFFECT_TRACE1 9 -#define ABILITYEFFECT_TRACE2 10 -#define ABILITYEFFECT_MOVE_END_OTHER 11 -#define ABILITYEFFECT_NEUTRALIZINGGAS 12 -#define ABILITYEFFECT_FIELD_SPORT 13 // Only used if B_SPORT_TURNS >= GEN_6 -#define ABILITYEFFECT_ON_WEATHER 14 -#define ABILITYEFFECT_ON_TERRAIN 15 -#define ABILITYEFFECT_SWITCH_IN_TERRAIN 16 -#define ABILITYEFFECT_SWITCH_IN_WEATHER 17 -#define ABILITYEFFECT_OPPORTUNIST 18 -#define ABILITYEFFECT_SWITCH_IN_STATUSES 19 +enum { + ABILITYEFFECT_ON_SWITCHIN, + ABILITYEFFECT_ENDTURN, + ABILITYEFFECT_MOVES_BLOCK, + ABILITYEFFECT_WOULD_BLOCK, // Checks immunity without triggering a script + ABILITYEFFECT_ABSORBING, + ABILITYEFFECT_WOULD_ABSORB, // Checks immunity without triggering a script + ABILITYEFFECT_MOVE_END_ATTACKER, + ABILITYEFFECT_MOVE_END, + ABILITYEFFECT_IMMUNITY, + ABILITYEFFECT_SYNCHRONIZE, + ABILITYEFFECT_ATK_SYNCHRONIZE, + ABILITYEFFECT_TRACE1, + ABILITYEFFECT_TRACE2, + ABILITYEFFECT_MOVE_END_OTHER, + ABILITYEFFECT_NEUTRALIZINGGAS, + ABILITYEFFECT_FIELD_SPORT, // Only used if B_SPORT_TURNS >= GEN_6 + ABILITYEFFECT_ON_WEATHER, + ABILITYEFFECT_ON_TERRAIN, + ABILITYEFFECT_SWITCH_IN_TERRAIN, + ABILITYEFFECT_SWITCH_IN_WEATHER, + ABILITYEFFECT_OPPORTUNIST, + ABILITYEFFECT_SWITCH_IN_STATUSES, +}; + // Special cases #define ABILITYEFFECT_MUD_SPORT 252 // Only used if B_SPORT_TURNS >= GEN_6 #define ABILITYEFFECT_WATER_SPORT 253 // Only used if B_SPORT_TURNS >= GEN_6 @@ -233,6 +238,8 @@ bool32 MoveHasAdditionalEffectWithChance(u32 move, u32 moveEffect, u32 chance); bool32 MoveHasAdditionalEffectSelf(u32 move, u32 moveEffect); bool32 MoveHasAdditionalEffectSelfArg(u32 move, u32 moveEffect, u32 argument); bool32 MoveHasChargeTurnAdditionalEffect(u32 move); +bool32 CanTargetPartner(u32 battlerAtk, u32 battlerDef); +bool32 TargetFullyImmuneToCurrMove(u32 BattlerAtk, u32 battlerDef); bool32 CanSleep(u32 battler); bool32 CanBePoisoned(u32 battlerAttacker, u32 battlerTarget); diff --git a/include/constants/battle_move_effects.h b/include/constants/battle_move_effects.h index 6061373d0e..34a6d9bb4d 100644 --- a/include/constants/battle_move_effects.h +++ b/include/constants/battle_move_effects.h @@ -352,6 +352,7 @@ enum { EFFECT_SPICY_EXTRACT, EFFECT_TERA_BLAST, EFFECT_TERA_STARSTORM, + EFFECT_DRAGON_DARTS, NUM_BATTLE_MOVE_EFFECTS, }; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 3d975176e1..31601e5d92 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -336,6 +336,7 @@ static bool8 CanAbilityPreventStatLoss(u16 abilityDef); 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 Cmd_attackcanceler(void); static void Cmd_accuracycheck(void); @@ -1683,11 +1684,9 @@ u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u return calc; } -static void Cmd_accuracycheck(void) +static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u8 *failInstr, u16 move) { - CMD_ARGS(const u8 *failInstr, u16 move); - - u32 type, move = cmd->move; + u32 type; u32 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, move); u32 abilityAtk = GetBattlerAbility(gBattlerAttacker); u32 abilityDef = GetBattlerAbility(gBattlerTarget); @@ -1699,11 +1698,11 @@ static void Cmd_accuracycheck(void) if (move == NO_ACC_CALC_CHECK_LOCK_ON) { if (gStatuses3[gBattlerTarget] & STATUS3_ALWAYS_HITS && gDisableStructs[gBattlerTarget].battlerWithSureHit == gBattlerAttacker) - gBattlescriptCurrInstr = cmd->nextInstr; + gBattlescriptCurrInstr = nextInstr; else if (gStatuses3[gBattlerTarget] & (STATUS3_SEMI_INVULNERABLE)) - gBattlescriptCurrInstr = cmd->failInstr; + gBattlescriptCurrInstr = failInstr; else if (!JumpIfMoveAffectedByProtect(gCurrentMove)) - gBattlescriptCurrInstr = cmd->nextInstr; + gBattlescriptCurrInstr = nextInstr; } else if (gSpecialStatuses[gBattlerAttacker].parentalBondState == PARENTAL_BOND_2ND_HIT || (gSpecialStatuses[gBattlerAttacker].multiHitOn @@ -1711,7 +1710,7 @@ static void Cmd_accuracycheck(void) || !(gMovesInfo[move].effect == EFFECT_TRIPLE_KICK || gMovesInfo[move].effect == EFFECT_POPULATION_BOMB)))) { // No acc checks for second hit of Parental Bond or multi hit moves, except Triple Kick/Triple Axel/Population Bomb - gBattlescriptCurrInstr = cmd->nextInstr; + gBattlescriptCurrInstr = nextInstr; } else { @@ -1739,6 +1738,18 @@ static void Cmd_accuracycheck(void) if (holdEffectAtk == HOLD_EFFECT_BLUNDER_POLICY) gBattleStruct->blunderPolicy = TRUE; // Only activates from missing through acc/evasion checks + if (gMovesInfo[gCurrentMove].effect == EFFECT_DRAGON_DARTS + && !recalcDragonDarts // So we don't jump back and forth between targets + && CanTargetPartner(gBattlerAttacker, gBattlerTarget) + && !TargetFullyImmuneToCurrMove(gBattlerAttacker, BATTLE_PARTNER(gBattlerTarget))) + { + // Smart target to partner if miss + gBattlerTarget = BATTLE_PARTNER(gBattlerTarget); + gMoveResultFlags &= ~MOVE_RESULT_MISSED; + AccuracyCheck(TRUE, nextInstr, failInstr, move); + return; + } + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && (moveTarget == MOVE_TARGET_BOTH || moveTarget == MOVE_TARGET_FOES_AND_ALLY)) gBattleCommunication[MISS_TYPE] = B_MSG_AVOIDED_ATK; @@ -1752,6 +1763,16 @@ static void Cmd_accuracycheck(void) } } +static void Cmd_accuracycheck(void) +{ + CMD_ARGS(const u8 *failInstr, u16 move); + + // The main body of this function has been moved to AccuracyCheck() to accomodate + // Dragon Darts' multiple accuracy checks on a single attack; + // each dart can try to re-target once after missing. + AccuracyCheck(FALSE, cmd->nextInstr, cmd->failInstr, cmd->move); +} + static void Cmd_attackstring(void) { CMD_ARGS(); @@ -5960,10 +5981,11 @@ static void Cmd_moveend(void) } else { - if (gCurrentMove == MOVE_DRAGON_DARTS) - { - // TODO - } + if (gMovesInfo[gCurrentMove].effect == EFFECT_DRAGON_DARTS + && gBattleStruct->moveTarget[gBattlerAttacker] == gBattlerTarget // Haven't already changed targets + && CanTargetPartner(gBattlerAttacker, gBattlerTarget) + && !TargetFullyImmuneToCurrMove(gBattlerAttacker, BATTLE_PARTNER(gBattlerTarget))) + gBattlerTarget = BATTLE_PARTNER(gBattlerTarget); // Target the partner in doubles for second hit. if (gBattleMons[gBattlerAttacker].hp && gBattleMons[gBattlerTarget].hp diff --git a/src/battle_util.c b/src/battle_util.c index c76a17ed2e..fa5379d8bf 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -3629,6 +3629,11 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType) { gMultiHitCounter = gMovesInfo[gCurrentMove].strikeCount; PREPARE_BYTE_NUMBER_BUFFER(gBattleScripting.multihitString, 3, 0) + + if (gMovesInfo[gCurrentMove].effect == EFFECT_DRAGON_DARTS + && CanTargetPartner(gBattlerAttacker, gBattlerTarget) + && TargetFullyImmuneToCurrMove(gBattlerAttacker, gBattlerTarget)) + gBattlerTarget = BATTLE_PARTNER(gBattlerTarget); } } else if (B_BEAT_UP >= GEN_5 && gMovesInfo[gCurrentMove].effect == EFFECT_BEAT_UP) @@ -4275,7 +4280,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 BattleScriptPushCursorAndCallback(BattleScript_OverworldWeatherStarts); } break; - case ABILITYEFFECT_ON_SWITCHIN: // 0 + case ABILITYEFFECT_ON_SWITCHIN: gBattleScripting.battler = battler; switch (gLastUsedAbility) { @@ -4845,7 +4850,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 break; } break; - case ABILITYEFFECT_ENDTURN: // 1 + case ABILITYEFFECT_ENDTURN: if (IsBattlerAlive(battler)) { gBattlerAttacker = battler; @@ -5036,18 +5041,20 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } } break; - case ABILITYEFFECT_MOVES_BLOCK: // 2 + case ABILITYEFFECT_MOVES_BLOCK: + case ABILITYEFFECT_WOULD_BLOCK: { u16 moveTarget = GetBattlerMoveTargetType(battler, move); u16 battlerAbility = GetBattlerAbility(battler); u16 targetAbility = GetBattlerAbility(gBattlerTarget); + const u8 * battleScriptBlocksMove = NULL; if ((gLastUsedAbility == ABILITY_SOUNDPROOF && gMovesInfo[move].soundMove && !(moveTarget & MOVE_TARGET_USER)) || (gLastUsedAbility == ABILITY_BULLETPROOF && gMovesInfo[move].ballisticMove)) { if (gBattleMons[gBattlerAttacker].status2 & STATUS2_MULTIPLETURNS) gHitMarker |= HITMARKER_NO_PPDEDUCT; - gBattlescriptCurrInstr = BattleScript_SoundproofProtected; + battleScriptBlocksMove = BattleScript_SoundproofProtected; effect = 1; } else if ((gLastUsedAbility == ABILITY_DAZZLING || gLastUsedAbility == ABILITY_QUEENLY_MAJESTY || gLastUsedAbility == ABILITY_ARMOR_TAIL || IsBattlerAlive(battler ^= BIT_FLANK)) @@ -5057,7 +5064,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 { if (gBattleMons[gBattlerAttacker].status2 & STATUS2_MULTIPLETURNS) gHitMarker |= HITMARKER_NO_PPDEDUCT; - gBattlescriptCurrInstr = BattleScript_DazzlingProtected; + battleScriptBlocksMove = BattleScript_DazzlingProtected; effect = 1; } else if (GetChosenMovePriority(gBattlerAttacker) > 0 @@ -5067,7 +5074,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE) || !(moveTarget & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY))) CancelMultiTurnMoves(gBattlerAttacker); // Don't cancel moves that can hit two targets bc one target might not be protected gBattleScripting.battler = gBattlerAbility = gBattlerTarget; - gBattlescriptCurrInstr = BattleScript_DarkTypePreventsPrankster; + battleScriptBlocksMove = BattleScript_DarkTypePreventsPrankster; effect = 1; } else if (GetBattlerAbility(gBattlerTarget) == ABILITY_GOOD_AS_GOLD @@ -5076,12 +5083,24 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && !(moveTarget & MOVE_TARGET_OPPONENTS_FIELD) && !(moveTarget & MOVE_TARGET_ALL_BATTLERS)) { - gBattlescriptCurrInstr = BattleScript_GoodAsGoldActivates; + battleScriptBlocksMove = BattleScript_GoodAsGoldActivates; effect = 1; } + if (caseID == ABILITYEFFECT_WOULD_BLOCK) + { + if (effect && gLastUsedAbility != 0xFFFF) + RecordAbilityBattle(battler, gLastUsedAbility); + + return effect; + } + else if (effect) + { + gBattlescriptCurrInstr = battleScriptBlocksMove; + } break; } - case ABILITYEFFECT_ABSORBING: // 3 + case ABILITYEFFECT_ABSORBING: + case ABILITYEFFECT_WOULD_ABSORB: if (move != MOVE_NONE) { u8 statId = 0; @@ -5114,31 +5133,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 effect = 2, statId = STAT_ATK; break; case ABILITY_FLASH_FIRE: - if (moveType == TYPE_FIRE - && (B_FLASH_FIRE_FROZEN >= GEN_5 || !(gBattleMons[battler].status1 & STATUS1_FREEZE))) - { - if (!(gBattleResources->flags->flags[battler] & RESOURCE_FLAG_FLASH_FIRE)) - { - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FLASH_FIRE_BOOST; - if (gProtectStructs[gBattlerAttacker].notFirstStrike) - gBattlescriptCurrInstr = BattleScript_FlashFireBoost; - else - gBattlescriptCurrInstr = BattleScript_FlashFireBoost_PPLoss; - - gBattleResources->flags->flags[battler] |= RESOURCE_FLAG_FLASH_FIRE; - effect = 3; - } - else - { - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FLASH_FIRE_NO_BOOST; - if (gProtectStructs[gBattlerAttacker].notFirstStrike) - gBattlescriptCurrInstr = BattleScript_FlashFireBoost; - else - gBattlescriptCurrInstr = BattleScript_FlashFireBoost_PPLoss; - - effect = 3; - } - } + if (moveType == TYPE_FIRE && (B_FLASH_FIRE_FROZEN >= GEN_5 || !(gBattleMons[battler].status1 & STATUS1_FREEZE))) + effect = 3; break; case ABILITY_WELL_BAKED_BODY: if (moveType == TYPE_FIRE) @@ -5153,8 +5149,14 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 effect = 1; break; } + if (caseID == ABILITYEFFECT_WOULD_ABSORB) + { + if (effect && gLastUsedAbility != 0xFFFF) + RecordAbilityBattle(battler, gLastUsedAbility); - if (effect == 1) // Drain Hp ability. + return effect; + } + else if (effect == 1) // Drain Hp ability. { if (BATTLER_MAX_HP(battler) || (B_HEAL_BLOCKING >= GEN_5 && gStatuses3[battler] & STATUS3_HEAL_BLOCK)) { @@ -5197,6 +5199,26 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 PREPARE_STAT_BUFFER(gBattleTextBuff1, statId); } } + else if (effect == 3) + { + if (!(gBattleResources->flags->flags[battler] & RESOURCE_FLAG_FLASH_FIRE)) + { + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FLASH_FIRE_BOOST; + if (gProtectStructs[gBattlerAttacker].notFirstStrike) + gBattlescriptCurrInstr = BattleScript_FlashFireBoost; + else + gBattlescriptCurrInstr = BattleScript_FlashFireBoost_PPLoss; + gBattleResources->flags->flags[battler] |= RESOURCE_FLAG_FLASH_FIRE; + } + else + { + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FLASH_FIRE_NO_BOOST; + if (gProtectStructs[gBattlerAttacker].notFirstStrike) + gBattlescriptCurrInstr = BattleScript_FlashFireBoost; + else + gBattlescriptCurrInstr = BattleScript_FlashFireBoost_PPLoss; + } + } if (effect) gMultiHitCounter = 0; // Prevent multi-hit moves from hitting more than once after move has been absorbed. @@ -5876,7 +5898,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } } break; - case ABILITYEFFECT_IMMUNITY: // 5 + case ABILITYEFFECT_IMMUNITY: for (battler = 0; battler < gBattlersCount; battler++) { switch (GetBattlerAbility(battler)) @@ -5989,7 +6011,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } } break; - case ABILITYEFFECT_ATK_SYNCHRONIZE: // 8 + case ABILITYEFFECT_ATK_SYNCHRONIZE: if (gLastUsedAbility == ABILITY_SYNCHRONIZE && (gHitMarker & HITMARKER_SYNCHRONISE_EFFECT)) { gHitMarker &= ~HITMARKER_SYNCHRONISE_EFFECT; @@ -11000,13 +11022,12 @@ void TryRestoreHeldItems(void) u16 lostItem = gBattleStruct->itemLost[i].originalItem; // Check if the lost item is a berry and the mon is not holding it - if (ItemId_GetPocket(lostItem) == POCKET_BERRIES && GetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM) != lostItem) + if (ItemId_GetPocket(lostItem) == POCKET_BERRIES && GetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM) != lostItem) lostItem = ITEM_NONE; // Check if the lost item should be restored - if ((lostItem != ITEM_NONE || returnNPCItems) && ItemId_GetPocket(lostItem) != POCKET_BERRIES) + if ((lostItem != ITEM_NONE || returnNPCItems) && ItemId_GetPocket(lostItem) != POCKET_BERRIES) SetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM, &lostItem); - } } } @@ -11512,3 +11533,27 @@ void RemoveBattlerType(u32 battler, u8 type) *(u8 *)(&gBattleMons[battler].type1 + i) = TYPE_MYSTERY; } } + +bool32 CanTargetPartner(u32 battlerAtk, u32 battlerDef) +{ + return (gBattleTypeFlags & BATTLE_TYPE_DOUBLE + && IsBattlerAlive(BATTLE_PARTNER(battlerDef)) + && battlerDef != BATTLE_PARTNER(battlerAtk)); +} + +static inline bool32 DoesCurrentTargetHaveAbilityImmunity(void) +{ + return (AbilityBattleEffects(ABILITYEFFECT_WOULD_BLOCK, gBattlerTarget, 0, 0, 0) + || AbilityBattleEffects(ABILITYEFFECT_WOULD_ABSORB, gBattlerTarget, 0, 0, 0)); +} + +bool32 TargetFullyImmuneToCurrMove(u32 BattlerAtk, u32 battlerDef) +{ + u32 moveType = 0; + GET_MOVE_TYPE(gCurrentMove, moveType); + + return ((CalcTypeEffectivenessMultiplier(gCurrentMove, moveType, BattlerAtk, battlerDef, GetBattlerAbility(battlerDef), FALSE) == UQ_4_12(0.0)) + || IsBattlerProtected(battlerDef, gCurrentMove) + || IsSemiInvulnerable(battlerDef, gCurrentMove) + || DoesCurrentTargetHaveAbilityImmunity()); +} diff --git a/src/data/battle_move_effects.h b/src/data/battle_move_effects.h index 7354a8cea5..6f503e4f42 100644 --- a/src/data/battle_move_effects.h +++ b/src/data/battle_move_effects.h @@ -2243,4 +2243,10 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] = .battleScript = BattleScript_EffectPhotonGeyser, .battleTvScore = 0, // TODO: Assign points }, + + [EFFECT_DRAGON_DARTS] = + { + .battleScript = BattleScript_EffectHit, + .battleTvScore = 0, // TODO: Assign points + }, }; diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 3d2f009659..b58bc18444 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -16586,7 +16586,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .description = COMPOUND_STRING( "The user attacks twice. Two\n" "targets are hit once each."), - .effect = EFFECT_HIT, // TODO: EFFECT_DRAGON_DARTS + .effect = EFFECT_DRAGON_DARTS, .power = 50, .type = TYPE_DRAGON, .accuracy = 100, diff --git a/test/battle/move_effect/dragon_darts.c b/test/battle/move_effect/dragon_darts.c new file mode 100644 index 0000000000..8190f0f4bd --- /dev/null +++ b/test/battle/move_effect/dragon_darts.c @@ -0,0 +1,189 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_DRAGON_DARTS].effect == EFFECT_DRAGON_DARTS); + ASSUME(gSpeciesInfo[SPECIES_CLEFAIRY].types[0] == TYPE_FAIRY || gSpeciesInfo[SPECIES_CLEFAIRY].types[1] == TYPE_FAIRY); +} + +SINGLE_BATTLE_TEST("Dragon Darts strikes twice") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_DARTS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, player); + MESSAGE("Hit 2 time(s)!"); + } +} + +DOUBLE_BATTLE_TEST("Dragon Darts strikes each opponent once in a double battle") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_DRAGON_DARTS, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentRight); + MESSAGE("Hit 2 time(s)!"); + } +} + +DOUBLE_BATTLE_TEST("Dragon Darts strikes the ally twice if the target protects") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_PROTECT); MOVE(playerLeft, MOVE_DRAGON_DARTS, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentRight); + MESSAGE("Hit 2 time(s)!"); + } +} + +DOUBLE_BATTLE_TEST("Dragon Darts strikes the right ally twice if the target is a fairy type") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CLEFAIRY); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_DRAGON_DARTS, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentRight); + MESSAGE("Hit 2 time(s)!"); + } +} + +DOUBLE_BATTLE_TEST("Dragon Darts strikes the left ally twice if the target is a fairy type") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CLEFAIRY); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_DRAGON_DARTS, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentRight); + MESSAGE("Hit 2 time(s)!"); + } +} + +DOUBLE_BATTLE_TEST("Dragon Darts strikes the ally twice if the target is in a semi-invulnerable turn") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_FLY].effect == EFFECT_SEMI_INVULNERABLE); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_FLY, target: playerLeft); MOVE(playerLeft, MOVE_DRAGON_DARTS, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLY, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentRight); + MESSAGE("Hit 2 time(s)!"); + } +} + +DOUBLE_BATTLE_TEST("Dragon Darts is not effected by Wide Guard") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_WIDE_GUARD); MOVE(playerLeft, MOVE_DRAGON_DARTS, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WIDE_GUARD, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentRight); + MESSAGE("Hit 2 time(s)!"); + } +} + +DOUBLE_BATTLE_TEST("Dragon Darts strikes hit the ally if the target fainted") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, MOVE_SONIC_BOOM, target: opponentLeft); MOVE(playerLeft, MOVE_DRAGON_DARTS, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SONIC_BOOM, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentRight); + MESSAGE("Hit 2 time(s)!"); + } +} + +DOUBLE_BATTLE_TEST("Dragon Darts strikes left ally twice if one strike misses") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_BRIGHT_POWDER); }; + } WHEN { + TURN { MOVE(playerLeft, MOVE_DRAGON_DARTS, target: opponentRight, hit: FALSE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentLeft); + MESSAGE("Hit 2 time(s)!"); + } +} + +DOUBLE_BATTLE_TEST("Dragon Darts strikes right ally twice if one strike misses") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_BRIGHT_POWDER); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_DRAGON_DARTS, target: opponentLeft, hit: FALSE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentRight); + MESSAGE("Hit 2 time(s)!"); + } +}