diff --git a/include/battle.h b/include/battle.h index f5df5a46207a..9f8d50690803 100644 --- a/include/battle.h +++ b/include/battle.h @@ -113,6 +113,7 @@ struct DisableStruct u8 steelSurgeDone:1; u8 weatherAbilityDone:1; u8 terrainAbilityDone:1; + u8 usedProteanLibero:1; }; struct ProtectStruct @@ -699,10 +700,10 @@ struct BattleStruct u16 changedSpecies[NUM_BATTLE_SIDES][PARTY_SIZE]; // For forms when multiple mons can change into the same pokemon. u8 quickClawBattlerId; struct LostItem itemLost[PARTY_SIZE]; // Player's team that had items consumed or stolen (two bytes per party member) - u8 blunderPolicy:1; // should blunder policy activate - u8 swapDamageCategory:1; // Photon Geyser, Shell Side Arm, Light That Burns the Sky u8 forcedSwitch:4; // For each battler u8 switchInAbilityPostponed:4; // To not activate against an empty field, each bit for battler + u8 blunderPolicy:1; // should blunder policy activate + u8 swapDamageCategory:1; // Photon Geyser, Shell Side Arm, Light That Burns the Sky u8 ballSpriteIds[2]; // item gfx, window gfx u8 appearedInBattle; // Bitfield to track which Pokemon appeared in battle. Used for Burmy's form change u8 skyDropTargets[MAX_BATTLERS_COUNT]; // For Sky Drop, to account for if multiple Pokemon use Sky Drop in a double battle. @@ -721,22 +722,24 @@ struct BattleStruct uq4_12_t supremeOverlordModifier[MAX_BATTLERS_COUNT]; u8 itemPartyIndex[MAX_BATTLERS_COUNT]; u8 itemMoveIndex[MAX_BATTLERS_COUNT]; - bool8 trainerSlideHalfHpMsgDone; u8 trainerSlideFirstCriticalHitMsgState:2; u8 trainerSlideFirstSuperEffectiveHitMsgState:2; u8 trainerSlideFirstSTABMoveMsgState:2; u8 trainerSlidePlayerMonUnaffectedMsgState:2; - bool8 trainerSlideMegaEvolutionMsgDone; - bool8 trainerSlideZMoveMsgDone; - bool8 trainerSlideBeforeFirstTurnMsgDone; - bool8 trainerSlideDynamaxMsgDone; + u8 trainerSlideHalfHpMsgDone:1; + u8 trainerSlideMegaEvolutionMsgDone:1; + u8 trainerSlideZMoveMsgDone:1; + u8 trainerSlideBeforeFirstTurnMsgDone:1; + u8 trainerSlideDynamaxMsgDone:1; + u8 pledgeMove:1; + u8 isSkyBattle:1; u32 aiDelayTimer; // Counts number of frames AI takes to choose an action. u32 aiDelayFrames; // Number of frames it took to choose an action. - bool8 transformZeroToHero[PARTY_SIZE][NUM_BATTLE_SIDES]; - u8 pledgeMove:1; - bool8 isSkyBattle:1; u8 timesGotHit[NUM_BATTLE_SIDES][PARTY_SIZE]; u8 enduredDamage; + u8 transformZeroToHero[NUM_BATTLE_SIDES]; + u8 intrepidSwordBoost[NUM_BATTLE_SIDES]; + u8 dauntlessShieldBoost[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/config/battle.h b/include/config/battle.h index 26cda4e6e206..6f6bc92fe65d 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -109,6 +109,9 @@ #define B_TRANSFORM_SHINY GEN_LATEST // In Gen4+, Transform will copy the shiny state of the opponent instead of maintaining its own shiny state. #define B_TRANSFORM_FORM_CHANGES GEN_LATEST // In Gen5+, Transformed Pokemon cannot change forms. +#define B_WIDE_GUARD GEN_LATEST // In Gen5 only, Quick Guard has a chance to fail if used consecutively. +#define B_QUICK_GUARD GEN_LATEST // In Gen5 only, Wide Guard has a chance to fail if used consecutively. + // Ability settings #define B_EXPANDED_ABILITY_NAMES TRUE // If TRUE, ability names are increased from 12 characters to 16 characters. #define B_ABILITY_WEATHER GEN_LATEST // In Gen6+, ability-induced weather lasts 5 turns. Before, it lasted until the battle ended or until it was changed by a move or a different weather-affecting ability. @@ -130,6 +133,9 @@ #define B_TRANSISTOR_BOOST GEN_LATEST // In Gen9+, Transistor will only boost Electric-type moves by 1.3x as opposed to 1.5x. #define B_ILLUMINATE_EFFECT GEN_LATEST // In Gen9+, Illuminate prevents accuracy reductions and ignores the target's evasion. #define B_WEAK_ARMOR_SPEED GEN_LATEST // In Gen7+, Weak Armor raises Speed by 2 stages instead of 1 when hit by a physical move. +#define B_PROTEAN_LIBERO GEN_LATEST // In Gen7+, Weak Armor raises Speed by 2 stages instead of 1 when hit by a physical move. +#define B_INTREPID_SWORD GEN_LATEST // In Gen9+, Intrepid Sword raises Attack by one stage only once per Battle. +#define B_DAUNTLESS_SHIELD GEN_LATEST // In Gen9+, Dauntless Shield raises Defense by one stage only once per Battle. // Item settings #define B_HP_BERRIES GEN_LATEST // In Gen4+, berries which restore HP activate immediately after HP drops to half. In Gen3, the effect occurs at the end of the turn. diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index de6da180b038..330a33e96a04 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1244,11 +1244,12 @@ static bool32 TryAegiFormChange(void) bool32 ProteanTryChangeType(u32 battler, u32 ability, u32 move, u32 moveType) { if ((ability == ABILITY_PROTEAN || ability == ABILITY_LIBERO) + && !gDisableStructs[gBattlerAttacker].usedProteanLibero && (gBattleMons[battler].type1 != moveType || gBattleMons[battler].type2 != moveType || (gBattleMons[battler].type3 != moveType && gBattleMons[battler].type3 != TYPE_MYSTERY)) && move != MOVE_STRUGGLE) { - SET_BATTLER_TYPE(gBattlerAttacker, moveType); + SET_BATTLER_TYPE(battler, moveType); return TRUE; } return FALSE; @@ -1324,6 +1325,8 @@ static void Cmd_attackcanceler(void) // Check Protean activation. if (ProteanTryChangeType(gBattlerAttacker, attackerAbility, gCurrentMove, moveType)) { + if (B_PROTEAN_LIBERO == GEN_9) + gDisableStructs[gBattlerAttacker].usedProteanLibero = TRUE; PREPARE_TYPE_BUFFER(gBattleTextBuff1, moveType); gBattlerAbility = gBattlerAttacker; BattleScriptPushCursor(); @@ -10639,7 +10642,9 @@ static void Cmd_setprotectlike(void) if (gCurrentTurnActionNumber == (gBattlersCount - 1)) notLastTurn = FALSE; - if (sProtectSuccessRates[gDisableStructs[gBattlerAttacker].protectUses] >= Random() && notLastTurn) + if ((sProtectSuccessRates[gDisableStructs[gBattlerAttacker].protectUses] >= Random() && notLastTurn) + || (gCurrentMove == MOVE_WIDE_GUARD && B_WIDE_GUARD != GEN_5) + || (gCurrentMove == MOVE_QUICK_GUARD && B_QUICK_GUARD != GEN_5)) { if (!gBattleMoves[gCurrentMove].argument) // Protects one mon only. { diff --git a/src/battle_util.c b/src/battle_util.c index 237b43e34e43..0d30acbdc0cb 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4704,9 +4704,12 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITY_INTREPID_SWORD: - if (!gSpecialStatuses[battler].switchInAbilityDone && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + if (!gSpecialStatuses[battler].switchInAbilityDone && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN) + && !(gBattleStruct->intrepidSwordBoost[GetBattlerSide(battler)] & gBitTable[gBattlerPartyIndexes[battler]])) { gBattlerAttacker = battler; + if (B_INTREPID_SWORD == GEN_9) + gBattleStruct->intrepidSwordBoost[GetBattlerSide(battler)] |= gBitTable[gBattlerPartyIndexes[battler]]; gSpecialStatuses[battler].switchInAbilityDone = TRUE; SET_STATCHANGER(STAT_ATK, 1, FALSE); BattleScriptPushCursorAndCallback(BattleScript_BattlerAbilityStatRaiseOnSwitchIn); @@ -4714,9 +4717,12 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITY_DAUNTLESS_SHIELD: - if (!gSpecialStatuses[battler].switchInAbilityDone && CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN)) + if (!gSpecialStatuses[battler].switchInAbilityDone && CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN) + && !(gBattleStruct->dauntlessShieldBoost[GetBattlerSide(battler)] & gBitTable[gBattlerPartyIndexes[battler]])) { gBattlerAttacker = battler; + if (B_DAUNTLESS_SHIELD == GEN_9) + gBattleStruct->dauntlessShieldBoost[GetBattlerSide(battler)] |= gBitTable[gBattlerPartyIndexes[battler]]; gSpecialStatuses[battler].switchInAbilityDone = TRUE; SET_STATCHANGER(STAT_DEF, 1, FALSE); BattleScriptPushCursorAndCallback(BattleScript_BattlerAbilityStatRaiseOnSwitchIn); @@ -4816,10 +4822,10 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (!gSpecialStatuses[battler].switchInAbilityDone && GetMonData(mon, MON_DATA_SPECIES) == SPECIES_PALAFIN_HERO - && !gBattleStruct->transformZeroToHero[gBattlerPartyIndexes[battler]][side]) + && !(gBattleStruct->transformZeroToHero[side] & gBitTable[gBattlerPartyIndexes[battler]])) { gSpecialStatuses[battler].switchInAbilityDone = TRUE; - gBattleStruct->transformZeroToHero[gBattlerPartyIndexes[battler]][side] = TRUE; + gBattleStruct->transformZeroToHero[side] |= gBitTable[gBattlerPartyIndexes[battler]]; BattleScriptPushCursorAndCallback(BattleScript_ZeroToHeroActivates); effect++; } diff --git a/test/battle/ability/dauntless_shield.c b/test/battle/ability/dauntless_shield.c new file mode 100644 index 000000000000..eb7b5c15cb18 --- /dev/null +++ b/test/battle/ability/dauntless_shield.c @@ -0,0 +1,47 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(P_GEN_8_POKEMON == TRUE); + ASSUME(B_PROTEAN_LIBERO == GEN_9); +} + +SINGLE_BATTLE_TEST("Dauntless Shield raises Attack by one stage") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZAMAZENTA) { Ability(ABILITY_DAUNTLESS_SHIELD); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DAUNTLESS_SHIELD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Zamazenta's Dauntless Shield raised its Defense!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Dauntless Shield raises Attack by one stage only once per battle") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZAMAZENTA) { Ability(ABILITY_DAUNTLESS_SHIELD); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { SWITCH(opponent, 1); } + TURN { SWITCH(opponent, 0); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DAUNTLESS_SHIELD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Zamazenta's Dauntless Shield raised its Defense!"); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_DAUNTLESS_SHIELD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Zamazenta's Dauntless Shield raised its Defense!"); + } + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} diff --git a/test/battle/ability/intrepid_sword.c b/test/battle/ability/intrepid_sword.c new file mode 100644 index 000000000000..88228bbe784a --- /dev/null +++ b/test/battle/ability/intrepid_sword.c @@ -0,0 +1,47 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(P_GEN_8_POKEMON == TRUE); + ASSUME(B_INTREPID_SWORD == GEN_9); +} + +SINGLE_BATTLE_TEST("Intrepid Sword raises Attack by one stage") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_INTREPID_SWORD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Zacian's Intrepid Sword raised its Attack!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Intrepid Sword raises Attack by one stage only once per battle") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { SWITCH(opponent, 1); } + TURN { SWITCH(opponent, 0); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_INTREPID_SWORD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Zacian's Intrepid Sword raised its Attack!"); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_INTREPID_SWORD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Zacian's Intrepid Sword raised its Attack!"); + } + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} diff --git a/test/battle/ability/protean.c b/test/battle/ability/protean.c new file mode 100644 index 000000000000..936c341f8992 --- /dev/null +++ b/test/battle/ability/protean.c @@ -0,0 +1,34 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(B_PROTEAN_LIBERO == GEN_9); +} + +SINGLE_BATTLE_TEST("Protean changes the type of the user only once per switch in") +{ + GIVEN { + PLAYER(SPECIES_REGIROCK); + OPPONENT(SPECIES_KECLEON) { Ability(ABILITY_PROTEAN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_WATER_GUN); } + TURN { MOVE(opponent, MOVE_TACKLE); } + TURN { SWITCH(opponent, 1); } + TURN { SWITCH(opponent, 0); } + TURN { MOVE(opponent, MOVE_WATER_GUN); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_PROTEAN); + MESSAGE("Foe Kecleon transformed into the Water type!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_PROTEAN); + MESSAGE("Foe Kecleon transformed into the Normal type!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + ABILITY_POPUP(opponent, ABILITY_PROTEAN); + MESSAGE("Foe Kecleon transformed into the Water type!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + } +} diff --git a/test/battle/move_effect/protect.c b/test/battle/move_effect/protect.c index dd9f57b3562c..c187d6d5739f 100644 --- a/test/battle/move_effect/protect.c +++ b/test/battle/move_effect/protect.c @@ -124,13 +124,13 @@ SINGLE_BATTLE_TEST("Spiky Shield does 1/8 dmg of max hp of attackers making cont u16 usedMove = MOVE_NONE; u16 hp = 400, maxHp = 400; - PARAMETRIZE { usedMove = MOVE_TACKLE; hp = 1;} - PARAMETRIZE { usedMove = MOVE_TACKLE;} - PARAMETRIZE { usedMove = MOVE_LEER;} - PARAMETRIZE { usedMove = MOVE_WATER_GUN;} + PARAMETRIZE { usedMove = MOVE_TACKLE; hp = 1; } + PARAMETRIZE { usedMove = MOVE_TACKLE; } + PARAMETRIZE { usedMove = MOVE_LEER; } + PARAMETRIZE { usedMove = MOVE_WATER_GUN; } GIVEN { - PLAYER(SPECIES_WOBBUFFET) {HP(hp); MaxHP(maxHp); } + PLAYER(SPECIES_WOBBUFFET) { HP(hp); MaxHP(maxHp); } PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -295,7 +295,7 @@ DOUBLE_BATTLE_TEST("Wide Guard protects self and ally from multi-target moves") OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(opponentLeft, MOVE_WIDE_GUARD); MOVE(playerLeft, move, target:opponentLeft); } + TURN { MOVE(opponentLeft, MOVE_WIDE_GUARD); MOVE(playerLeft, move, target: opponentLeft); } TURN {} } SCENE { MESSAGE("Foe Wobbuffet used Wide Guard!"); @@ -320,6 +320,34 @@ DOUBLE_BATTLE_TEST("Wide Guard protects self and ally from multi-target moves") } } +DOUBLE_BATTLE_TEST("Wide Guard can not fail on consecutive turns") +{ + u8 turns; + + PASSES_RANDOMLY(2, 2); + GIVEN { + ASSUME(gBattleMoves[MOVE_HYPER_VOICE].target == MOVE_TARGET_BOTH); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_WIDE_GUARD); MOVE(playerLeft, MOVE_HYPER_VOICE, target: opponentLeft); } + TURN { MOVE(opponentLeft, MOVE_WIDE_GUARD); MOVE(playerLeft, MOVE_HYPER_VOICE, target: opponentLeft); } + TURN {} + } SCENE { + for (turns = 0; turns < 2; turns++) { + MESSAGE("Foe Wobbuffet used Wide Guard!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WIDE_GUARD, opponentLeft); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerLeft); + MESSAGE("Foe Wobbuffet protected itself!"); + NOT HP_BAR(opponentLeft); + MESSAGE("Foe Wobbuffet protected itself!"); + NOT HP_BAR(opponentRight); + } + } +} + DOUBLE_BATTLE_TEST("Quick Guard protects self and ally from priority moves") { u16 move = MOVE_NONE; @@ -355,6 +383,31 @@ DOUBLE_BATTLE_TEST("Quick Guard protects self and ally from priority moves") } } +DOUBLE_BATTLE_TEST("Quick Guard can not fail on consecutive turns") +{ + u8 turns; + + PASSES_RANDOMLY(2, 2); + GIVEN { + ASSUME(gBattleMoves[MOVE_QUICK_ATTACK].priority == 1); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_QUICK_GUARD); MOVE(playerLeft, MOVE_QUICK_ATTACK, target: opponentRight); } + TURN { MOVE(opponentLeft, MOVE_QUICK_GUARD); MOVE(playerLeft, MOVE_QUICK_ATTACK, target: opponentRight); } + } SCENE { + for (turns = 0; turns < 2; turns++) { + MESSAGE("Foe Wobbuffet used Quick Guard!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_GUARD, opponentLeft); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, playerLeft); + MESSAGE("Foe Wobbuffet protected itself!"); + NOT HP_BAR(opponentRight); + } + } +} + DOUBLE_BATTLE_TEST("Crafty Shield protects self and ally from status moves") { u16 move = MOVE_NONE;