diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index d9338ad599..d1cf55ffee 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1366,6 +1366,10 @@ callnative BS_RestoreAttacker .endm + .macro jumpifintimidateabilityprevented + callnative BS_JumpIfIntimidateAbilityPrevented + .endm + .macro metalburstdamagecalculator failInstr:req callnative BS_CalcMetalBurstDmg .4byte \failInstr @@ -1382,6 +1386,10 @@ .byte \battler .endm + .macro tryintimidatejectpack + callnative BS_TryIntimidatEjectpack + .endm + .macro allyswitchswapbattlers callnative BS_AllySwitchSwapBattler .endm diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index ae9ff11e01..3bf5b1f3d7 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -3197,7 +3197,7 @@ BattleScript_StatDownEnd:: BattleScript_MirrorArmorReflect:: pause B_WAIT_TIME_SHORT call BattleScript_AbilityPopUp - jumpifsubstituteblocks BattleScript_AbilityNoSpecificStatLoss + jumpifstatus2 BS_ATTACKER, STATUS2_SUBSTITUTE, BattleScript_MirrorArmorDoesntAffect BattleScript_MirrorArmorReflectStatLoss: statbuffchange MOVE_EFFECT_AFFECTS_USER | STAT_CHANGE_MIRROR_ARMOR | STAT_CHANGE_NOT_PROTECT_AFFECTED | STAT_CHANGE_ALLOW_PTR, BattleScript_MirrorArmorReflectEnd jumpifbyte CMP_LESS_THAN, cMULTISTRING_CHOOSER, B_MSG_STAT_WONT_DECREASE, BattleScript_MirrorArmorReflectAnim @@ -3211,6 +3211,13 @@ BattleScript_MirrorArmorReflectPrintString: BattleScript_MirrorArmorReflectEnd: return +BattleScript_MirrorArmorDoesntAffect: + swapattackerwithtarget + printstring STRINGID_ITDOESNTAFFECT + waitmessage B_WAIT_TIME_LONG + swapattackerwithtarget + return + BattleScript_MirrorArmorReflectWontFall: copybyte gBattlerTarget, gBattlerAttacker @ STRINGID_STATSWONTDECREASE uses target goto BattleScript_MirrorArmorReflectPrintString @@ -7719,13 +7726,7 @@ BattleScript_IntimidateLoop: jumpiftargetally BattleScript_IntimidateLoopIncrement jumpifabsent BS_TARGET, BattleScript_IntimidateLoopIncrement jumpifstatus2 BS_TARGET, STATUS2_SUBSTITUTE, BattleScript_IntimidateLoopIncrement -.if B_UPDATED_INTIMIDATE >= GEN_8 @These abilties specifically prevent just intimidate, without blocking stat decreases - jumpifability BS_TARGET, ABILITY_INNER_FOCUS, BattleScript_IntimidatePrevented - jumpifability BS_TARGET, ABILITY_SCRAPPY, BattleScript_IntimidatePrevented - jumpifability BS_TARGET, ABILITY_OWN_TEMPO, BattleScript_IntimidatePrevented - jumpifability BS_TARGET, ABILITY_OBLIVIOUS, BattleScript_IntimidatePrevented -.endif - jumpifability BS_TARGET, ABILITY_GUARD_DOG, BattleScript_IntimidateInReverse + jumpifintimidateabilityprevented BattleScript_IntimidateEffect: copybyte sBATTLER, gBattlerAttacker setstatchanger STAT_ATK, 1, TRUE @@ -7750,9 +7751,10 @@ BattleScript_IntimidateLoopIncrement: destroyabilitypopup restoretarget pause B_WAIT_TIME_MED + tryintimidatejectpack end3 -BattleScript_IntimidatePrevented: +BattleScript_IntimidatePrevented:: copybyte sBATTLER, gBattlerTarget call BattleScript_AbilityPopUp printstring STRINGID_PKMNPREVENTSSTATLOSSWITH @@ -7772,7 +7774,7 @@ BattleScript_IntimidateContrary_WontIncrease: printstring STRINGID_TARGETSTATWONTGOHIGHER goto BattleScript_IntimidateEffect_WaitString -BattleScript_IntimidateInReverse: +BattleScript_IntimidateInReverse:: copybyte sBATTLER, gBattlerTarget call BattleScript_AbilityPopUpTarget pause B_WAIT_TIME_SHORT @@ -9527,7 +9529,7 @@ BattleScript_EjectButtonActivates:: printstring STRINGID_EJECTBUTTONACTIVATE waitmessage B_WAIT_TIME_LONG removeitem BS_SCRIPTING - undodynamax BS_SCRIPTING + undodynamax BS_SCRIPTING makeinvisible BS_SCRIPTING openpartyscreen BS_SCRIPTING, BattleScript_EjectButtonEnd copybyte sSAVED_BATTLER, sBATTLER diff --git a/include/battle.h b/include/battle.h index 457cbbc6b0..a8cc291455 100644 --- a/include/battle.h +++ b/include/battle.h @@ -174,7 +174,8 @@ struct ProtectStruct u16 eatMirrorHerb:1; u16 activateOpportunist:2; // 2 - to copy stats. 1 - stats copied (do not repeat). 0 - no stats to copy u16 usedAllySwitch:1; - u16 padding:5; + u16 lashOutAffected:1; + u16 padding:4; // End of 16-bit bitfield u16 physicalDmg; u16 specialDmg; @@ -1272,4 +1273,3 @@ static inline bool32 IsBattlerInvalidForSpreadMove(u32 battlerAtk, u32 battlerDe } #endif // GUARD_BATTLE_H - diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 770f21b7a9..10ebf7e020 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -171,6 +171,8 @@ extern const u8 BattleScript_RainDishActivates[]; extern const u8 BattleScript_SandstreamActivates[]; extern const u8 BattleScript_ShedSkinActivates[]; extern const u8 BattleScript_IntimidateActivates[]; +extern const u8 BattleScript_IntimidatePrevented[]; +extern const u8 BattleScript_IntimidateInReverse[]; extern const u8 BattleScript_DroughtActivates[]; extern const u8 BattleScript_TookAttack[]; extern const u8 BattleScript_SturdyPreventsOHKO[]; diff --git a/include/battle_util.h b/include/battle_util.h index 43d55ef2e8..c18ac5cbaf 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -368,6 +368,7 @@ void ClearPursuitValuesIfSet(u32 battler); void ClearPursuitValues(void); bool32 HasWeatherEffect(void); u32 RestoreWhiteHerbStats(u32 battler); +bool32 IsFutureSightAttackerInParty(u32 battlerAtk, u32 battlerDef); bool32 HadMoreThanHalfHpNowDoesnt(u32 battler); #endif // GUARD_BATTLE_UTIL_H diff --git a/src/battle_end_turn.c b/src/battle_end_turn.c index ab6de8449e..d6905e9065 100644 --- a/src/battle_end_turn.c +++ b/src/battle_end_turn.c @@ -335,8 +335,6 @@ static bool32 HandleEndTurnFutureSight(u32 battler) if (gWishFutureKnock.futureSightCounter[battler] == gBattleTurnCounter) { - struct Pokemon *party; - if (gWishFutureKnock.futureSightCounter[battler] == gBattleTurnCounter && gWishFutureKnock.futureSightCounter[BATTLE_PARTNER(battler)] <= gBattleTurnCounter) { @@ -357,8 +355,7 @@ static bool32 HandleEndTurnFutureSight(u32 battler) gBattlerAttacker = gWishFutureKnock.futureSightBattlerIndex[battler]; gCurrentMove = gWishFutureKnock.futureSightMove[battler]; - party = GetSideParty(GetBattlerSide(gBattlerAttacker)); - if (&party[gWishFutureKnock.futureSightPartyIndex[gBattlerTarget]] == &party[gBattlerPartyIndexes[gBattlerAttacker]]) + if (!IsFutureSightAttackerInParty(gBattlerAttacker, gBattlerTarget)) SetTypeBeforeUsingMove(gCurrentMove, gBattlerAttacker); BattleScriptExecute(BattleScript_MonTookFutureAttack); diff --git a/src/battle_main.c b/src/battle_main.c index 8b960191cb..d35919cb8d 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3746,6 +3746,7 @@ static void TryDoEventsBeforeFirstTurn(void) { for (i = 0; i < gBattlersCount; i++) { + gBattleStruct->monToSwitchIntoId[i] = PARTY_SIZE; // Included here because switches can happen before during set ups (eg. eject pack) struct Pokemon *party = GetBattlerParty(i); struct Pokemon *mon = &party[gBattlerPartyIndexes[i]]; if (!IsBattlerAlive(i) || gBattleMons[i].species == SPECIES_NONE || GetMonData(mon, MON_DATA_IS_EGG)) @@ -4758,6 +4759,8 @@ u32 GetBattlerTotalSpeedStatArgs(u32 battler, u32 ability, enum ItemHoldEffect h speed = (GetHighestStatId(battler) == STAT_SPEED) ? (speed * 150) / 100 : speed; else if (ability == ABILITY_QUARK_DRIVE && !(gBattleMons[battler].status2 & STATUS2_TRANSFORMED) && (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gDisableStructs[battler].boosterEnergyActivates)) speed = (GetHighestStatId(battler) == STAT_SPEED) ? (speed * 150) / 100 : speed; + else if (ability == ABILITY_UNBURDEN && gDisableStructs[battler].unburdenActive) + speed *= 2; // stat stages speed *= gStatStageRatios[gBattleMons[battler].statStages[STAT_SPEED]][0]; @@ -4784,8 +4787,6 @@ u32 GetBattlerTotalSpeedStatArgs(u32 battler, u32 ability, enum ItemHoldEffect h // various effects if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_TAILWIND) speed *= 2; - if (gDisableStructs[battler].unburdenActive) - speed *= 2; // paralysis drop if (gBattleMons[battler].status1 & STATUS1_PARALYSIS && ability != ABILITY_QUICK_FEET) @@ -4854,7 +4855,6 @@ s32 GetBattleMovePriority(u32 battler, u32 ability, u32 move) return priority; } -// Function for AI with variables provided as arguments to speed the computation time s32 GetWhichBattlerFasterArgs(u32 battler1, u32 battler2, bool32 ignoreChosenMoves, u32 ability1, u32 ability2, enum ItemHoldEffect holdEffectBattler1, enum ItemHoldEffect holdEffectBattler2, u32 speedBattler1, u32 speedBattler2, s32 priority1, s32 priority2) { @@ -5140,6 +5140,7 @@ static void TurnValuesCleanUp(bool8 var0) gSpecialStatuses[i].parentalBondState = PARENTAL_BOND_OFF; gBattleStruct->battlerState[i].usedEjectItem = FALSE; + gProtectStructs[i].lashOutAffected = FALSE; } gSideTimers[B_SIDE_PLAYER].followmeTimer = 0; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 9f3332f576..265c5f1020 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -4665,7 +4665,6 @@ void SetMoveEffect(bool32 primary, bool32 certain) static bool32 CanApplyAdditionalEffect(const struct AdditionalEffect *additionalEffect) { - // Self-targeting move effects only apply after the last mon has been hit if (additionalEffect->self && NumAffectedSpreadMoveTargets() > 1 && GetNextTarget(GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove), TRUE) != MAX_BATTLERS_COUNT) @@ -6374,7 +6373,6 @@ static bool32 HandleMoveEndAbilityBlock(u32 battlerAtk, u32 battlerDef, u32 move gEffectBattler = battlerDef; BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_MagicianActivates; - gSpecialStatuses[battlerAtk].preventLifeOrbDamage = TRUE; effect = TRUE; } break; @@ -7356,6 +7354,9 @@ static void Cmd_moveend(void) if (numEjectPackBattlers > 1) SortBattlersBySpeed(battlers, FALSE); + for (i = 0; i < gBattlersCount; i++) + gProtectStructs[i].statFell = FALSE; // restore for every possible eject pack battler + for (i = 0; i < gBattlersCount; i++) { u32 battler = battlers[i]; @@ -7370,7 +7371,6 @@ static void Cmd_moveend(void) BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_EjectPackActivates; AI_DATA->ejectPackSwitch = TRUE; - gProtectStructs[battler].statFell = FALSE; break; // Only the fastest Eject item activates } } @@ -7617,7 +7617,6 @@ static void Cmd_moveend(void) gProtectStructs[gBattlerAttacker].shellTrap = FALSE; gBattleStruct->ateBoost[gBattlerAttacker] = FALSE; gSpecialStatuses[gBattlerAttacker].gemBoost = FALSE; - gSpecialStatuses[gBattlerAttacker].preventLifeOrbDamage = 0; gSpecialStatuses[gBattlerTarget].berryReduced = FALSE; gSpecialStatuses[gBattlerTarget].distortedTypeMatchups = FALSE; gBattleScripting.moveEffect = 0; @@ -8392,6 +8391,7 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler) { gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_NEUTRALIZING_GAS; gSpecialStatuses[battler].announceNeutralizingGas = TRUE; + gDisableStructs[battler].neutralizingGas = TRUE; gBattlerAbility = battler; BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_SwitchInAbilityMsgRet; @@ -8456,13 +8456,7 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler) else if (IsBattlerAffectedByHazards(battler, TRUE)) { i = GetBattlerAbility(battler); - if (!(gBattleMons[battler].status1 & STATUS1_ANY) - && !IS_BATTLER_OF_TYPE(battler, TYPE_STEEL) - && i != ABILITY_IMMUNITY - && i != ABILITY_PURIFYING_SALT - && !IsAbilityOnSide(battler, ABILITY_PASTEL_VEIL) - && !(gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SAFEGUARD) - && !(gFieldStatuses & STATUS_FIELD_MISTY_TERRAIN)) + if (CanBePoisoned(gBattlerAttacker, battler, i)) { if (gSideTimers[GetBattlerSide(battler)].toxicSpikesAmount >= 2) gBattleMons[battler].status1 |= STATUS1_TOXIC_POISON; @@ -12664,7 +12658,8 @@ static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, const u8 *BS_ptr } else { - gProtectStructs[battler].statFell = TRUE; // Eject pack, lash out + gProtectStructs[battler].statFell = TRUE; + gProtectStructs[battler].lashOutAffected = TRUE; gBattleCommunication[MULTISTRING_CHOOSER] = (gBattlerTarget == battler); // B_MSG_ATTACKER_STAT_FELL or B_MSG_DEFENDER_STAT_FELL } } @@ -18853,6 +18848,90 @@ void BS_SetSteelsurge(void) } } +void BS_JumpIfIntimidateAbilityPrevented(void) +{ + NATIVE_ARGS(); + + u32 hasAbility = FALSE; + u32 ability = GetBattlerAbility(gBattlerTarget); + + switch (ability) + { + case ABILITY_INNER_FOCUS: + case ABILITY_SCRAPPY: + case ABILITY_OWN_TEMPO: + case ABILITY_OBLIVIOUS: + if (B_UPDATED_INTIMIDATE >= GEN_8) + { + hasAbility = TRUE; + gBattlescriptCurrInstr = BattleScript_IntimidatePrevented; + } + else + { + gBattlescriptCurrInstr = cmd->nextInstr; + } + break; + case ABILITY_GUARD_DOG: + hasAbility = TRUE; + gBattlescriptCurrInstr = BattleScript_IntimidateInReverse; + break; + default: + gBattlescriptCurrInstr = cmd->nextInstr; + break; + } + + if (hasAbility) + { + gLastUsedAbility = ability; + gBattlerAbility = gBattlerTarget; + RecordAbilityBattle(gBattlerTarget, gLastUsedAbility); + } +} + +void BS_TryIntimidatEjectpack(void) +{ + NATIVE_ARGS(); + + u32 affectedBattler = 0xFF; + u32 battler = BATTLE_OPPOSITE(gBattlerAttacker); + u32 partnerBattler = BATTLE_PARTNER(battler); + + bool32 ejectPackBattler = CanEjectPackTrigger(gBattlerAttacker, battler, MOVE_NONE); + bool32 ejectPackPartnerBattler = CanEjectPackTrigger(gBattlerAttacker, partnerBattler, MOVE_NONE); + + if (ejectPackBattler && ejectPackPartnerBattler) + { + u32 battlerSpeed = GetBattlerTotalSpeedStat(battler); + u32 partnerbattlerSpeed = GetBattlerTotalSpeedStat(partnerBattler); + + if (battlerSpeed >= partnerbattlerSpeed) + affectedBattler = battler; + else + affectedBattler = partnerBattler; + } + else if (ejectPackBattler) + { + affectedBattler = battler; + } + else if (ejectPackPartnerBattler) + { + affectedBattler = partnerBattler; + } + + gBattlescriptCurrInstr = cmd->nextInstr; + if (affectedBattler != 0xFF) + { + gProtectStructs[battler].statFell = FALSE; + gProtectStructs[partnerBattler].statFell = FALSE; + AI_DATA->ejectPackSwitch = TRUE; + gBattleScripting.battler = affectedBattler; + gLastUsedItem = gBattleMons[affectedBattler].item; + RecordItemEffectBattle(affectedBattler, HOLD_EFFECT_EJECT_PACK); + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_EjectPackActivate_Ret; + } +} + void BS_JumpIfCanGigantamax(void) { NATIVE_ARGS(u8 battler, const u8 *jumpInstr); diff --git a/src/battle_util.c b/src/battle_util.c index 5af83fb3ad..d017249bc3 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4599,12 +4599,9 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && IsBattlerAlive(gBattlerAttacker)) { // Prevent Innards Out effect if Future Sight user is currently not on field - if (GetMoveEffect(gCurrentMove) == EFFECT_FUTURE_SIGHT) - { - if (gWishFutureKnock.futureSightPartyIndex[gBattlerTarget] != gBattlerPartyIndexes[gBattlerAttacker] - && gWishFutureKnock.futureSightPartyIndex[gBattlerTarget] != BATTLE_PARTNER(gBattlerPartyIndexes[gBattlerAttacker])) - break; - } + if (GetMoveEffect(gCurrentMove) == EFFECT_FUTURE_SIGHT + && IsFutureSightAttackerInParty(gBattlerAttacker, gBattlerTarget)) + break; gBattleScripting.battler = gBattlerTarget; gBattleStruct->moveDamage[gBattlerAttacker] = gBattleStruct->moveDamage[gBattlerTarget]; @@ -5961,6 +5958,9 @@ static u32 ItemHealHp(u32 battler, u32 itemId, enum ItemCaseId caseID, bool32 pe static bool32 UnnerveOn(u32 battler, u32 itemId) { + if (gBattleScripting.overrideBerryRequirements > 0) // Berries that aren't eaten naturally ignore unnerve + return FALSE; + if (ItemId_GetPocket(itemId) == POCKET_BERRIES && IsUnnerveAbilityOnOpposingSide(battler)) return TRUE; return FALSE; @@ -6934,7 +6934,7 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler, bool32 moveTurn) && (IsBattlerTurnDamaged(gBattlerTarget) || gBattleStruct->moveDamage[gBattlerTarget]) // Needs the second check in case of Substitute && GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && !gSpecialStatuses[gBattlerAttacker].preventLifeOrbDamage) + && !IsFutureSightAttackerInParty(gBattlerAttacker, gBattlerTarget)) { gBattleStruct->moveDamage[gBattlerAttacker] = GetNonDynamaxMaxHP(gBattlerAttacker) / 10; if (gBattleStruct->moveDamage[gBattlerAttacker] == 0) @@ -7190,9 +7190,6 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler, bool32 moveTurn) gBattlescriptCurrInstr = BattleScript_WhiteHerbRet; } break; - case HOLD_EFFECT_EJECT_PACK: - effect = TryEjectPack(battler, ITEMEFFECT_ON_SWITCH_IN); - break; default: break; } @@ -8007,7 +8004,7 @@ static inline u32 CalcMoveBasePower(struct DamageCalculationData *damageCalcData basePower *= 2; break; case EFFECT_LASH_OUT: - if (gProtectStructs[battlerAtk].statFell) + if (gProtectStructs[battlerAtk].lashOutAffected) basePower *= 2; break; case EFFECT_EXPLOSION: @@ -9361,8 +9358,6 @@ static inline s32 DoFutureSightAttackDamageCalcVars(struct DamageCalculationData if (dmg == 0) dmg = 1; - gSpecialStatuses[battlerAtk].preventLifeOrbDamage = TRUE; - return dmg; } @@ -9390,14 +9385,11 @@ static u32 GetWeather(void) return gBattleWeather; } -static inline bool32 IsFutureSightAttackerInParty(struct DamageCalculationData *damageCalcData) +bool32 IsFutureSightAttackerInParty(u32 battlerAtk, u32 battlerDef) { - if (GetMoveEffect(damageCalcData->move) != EFFECT_FUTURE_SIGHT) - return FALSE; - - struct Pokemon *party = GetSideParty(GetBattlerSide(damageCalcData->battlerAtk)); - return &party[gWishFutureKnock.futureSightPartyIndex[damageCalcData->battlerDef]] - != &party[gBattlerPartyIndexes[damageCalcData->battlerAtk]]; + struct Pokemon *party = GetSideParty(GetBattlerSide(battlerAtk)); + return &party[gWishFutureKnock.futureSightPartyIndex[battlerDef]] != &party[gBattlerPartyIndexes[battlerAtk]] + && &party[gWishFutureKnock.futureSightPartyIndex[battlerDef]] != &party[BATTLE_PARTNER(gBattlerPartyIndexes[battlerAtk])]; } s32 CalculateMoveDamage(struct DamageCalculationData *damageCalcData, u32 fixedBasePower) @@ -9409,7 +9401,8 @@ s32 CalculateMoveDamage(struct DamageCalculationData *damageCalcData, u32 fixedB GetBattlerAbility(damageCalcData->battlerDef), damageCalcData->updateFlags); - if (IsFutureSightAttackerInParty(damageCalcData)) + if (GetMoveEffect(damageCalcData->move) == EFFECT_FUTURE_SIGHT + && IsFutureSightAttackerInParty(damageCalcData->battlerAtk, damageCalcData->battlerDef)) return DoFutureSightAttackDamageCalc(damageCalcData, typeEffectivenessMultiplier, GetWeather()); return DoMoveDamageCalc(damageCalcData, fixedBasePower, typeEffectivenessMultiplier, GetWeather()); diff --git a/src/event_object_movement.c b/src/event_object_movement.c index aefa8e8ceb..aba39dd9d9 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -1850,13 +1850,10 @@ u8 CreateObjectGraphicsSpriteWithTag(u16 graphicsId, void (*callback)(struct Spr const struct ObjectEventGraphicsInfo *graphicsInfo = GetObjectEventGraphicsInfo(graphicsId); struct Sprite *sprite; u8 spriteId; - bool32 isShiny = graphicsId & OBJ_EVENT_MON_SHINY; spriteTemplate = Alloc(sizeof(struct SpriteTemplate)); CopyObjectGraphicsInfoToSpriteTemplate(graphicsId, callback, spriteTemplate, &subspriteTables); - if (isShiny) - graphicsId -= SPECIES_SHINY_TAG; if (OW_GFX_COMPRESS) { diff --git a/test/battle/ability/analytic.c b/test/battle/ability/analytic.c index 44c42ae2d5..526e0983f4 100644 --- a/test/battle/ability/analytic.c +++ b/test/battle/ability/analytic.c @@ -1,7 +1,25 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Analytic increases the power of moves by 30% if it's the last one that uses its move"); +SINGLE_BATTLE_TEST("Analytic increases the power of moves by 30% if it's the last one that uses its move", s16 damage) +{ + u32 speed; + + PARAMETRIZE { speed = 3; } + PARAMETRIZE { speed = 1; } + + GIVEN { + PLAYER(SPECIES_MAGNEMITE) { Ability(ABILITY_ANALYTIC); Speed(speed); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(2); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.3), results[1].damage); + } +} + TO_DO_BATTLE_TEST("Analytic takes into account modifications to speeed an priority (Gen 5-8)"); //Eg. Paralysis, Power Weight, Stall TO_DO_BATTLE_TEST("Analytic does not take into account modifications to speeed an priority (Gen 8)"); //Eg. Paralysis, Power Weight, Stall TO_DO_BATTLE_TEST("Analytic takes into account the turn order of what fainted Pokémon would've moved"); diff --git a/test/battle/ability/comatose.c b/test/battle/ability/comatose.c index b23a6ca85f..d714ee5e3d 100644 --- a/test/battle/ability/comatose.c +++ b/test/battle/ability/comatose.c @@ -55,3 +55,35 @@ SINGLE_BATTLE_TEST("Comatose may be suppressed if pokemon transformed into a pok else if (move == MOVE_SLEEP_POWDER) { STATUS_ICON(opponent, sleep: TRUE); } } } + +SINGLE_BATTLE_TEST("Comatose pokemon doesn't get poisoned by Toxic Spikes on switch-in") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_KOMALA) { Ability(ABILITY_COMATOSE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TOXIC_SPIKES); } + TURN { SWITCH(player, 1); } + } SCENE { + NOT STATUS_ICON(player, STATUS1_POISON); + ABILITY_POPUP(player, ABILITY_COMATOSE); + NOT HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Comatose pokemon don't get poisoned by Toxic Spikes on switch-in if forced in by phazing with Mold Breaker") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_KOMALA) { Ability(ABILITY_COMATOSE); } + OPPONENT(SPECIES_PINSIR) { Ability(ABILITY_MOLD_BREAKER); } + } WHEN { + TURN { MOVE(opponent, MOVE_TOXIC_SPIKES); } + TURN { MOVE(opponent, MOVE_DRAGON_TAIL); } + } SCENE { + NOT STATUS_ICON(player, STATUS1_POISON); + ABILITY_POPUP(player, ABILITY_COMATOSE); + NOT HP_BAR(player); + } +} diff --git a/test/battle/ability/immunity.c b/test/battle/ability/immunity.c index 92e32d31f3..365b5d0d50 100644 --- a/test/battle/ability/immunity.c +++ b/test/battle/ability/immunity.c @@ -45,3 +45,20 @@ SINGLE_BATTLE_TEST("Immunity prevents Toxic Spikes poison") NOT STATUS_ICON(opponent, poison: TRUE); } } + +SINGLE_BATTLE_TEST("Immunity doesn't prevent pokemon from being poisoned by Toxic Spikes on switch-in if forced in by phazing with Mold Breaker, but it cures it immediately") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_DRAGON_TAIL) == EFFECT_HIT_SWITCH_TARGET); + ASSUME(GetMoveEffect(MOVE_TOXIC_SPIKES) == EFFECT_TOXIC_SPIKES); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_SNORLAX) { Ability(ABILITY_IMMUNITY); } + OPPONENT(SPECIES_PINSIR) { Ability(ABILITY_MOLD_BREAKER); } + } WHEN { + TURN { MOVE(opponent, MOVE_TOXIC_SPIKES); } + TURN { MOVE(opponent, MOVE_DRAGON_TAIL); } + } SCENE { + STATUS_ICON(player, STATUS1_POISON); + NOT HP_BAR(player); + } +} diff --git a/test/battle/ability/intimidate.c b/test/battle/ability/intimidate.c index dd1f761dee..e5571b23fc 100644 --- a/test/battle/ability/intimidate.c +++ b/test/battle/ability/intimidate.c @@ -104,7 +104,7 @@ DOUBLE_BATTLE_TEST("Intimidate doesn't activate on an empty field in a double ba } } -SINGLE_BATTLE_TEST("Intimidate and Eject Button force the opponent to Attack") +SINGLE_BATTLE_TEST("Intimidate and Eject Button don't force the opponent to Attack") { GIVEN { ASSUME(gItemsInfo[ITEM_EJECT_BUTTON].holdEffect == HOLD_EFFECT_EJECT_BUTTON); @@ -365,6 +365,7 @@ DOUBLE_BATTLE_TEST("Intimidate will correctly decrease the attack of the second } SCENE { ABILITY_POPUP(opponentLeft, ABILITY_INTIMIDATE); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); ABILITY_POPUP(playerLeft, ABILITY_PROTOSYNTHESIS); @@ -372,7 +373,6 @@ DOUBLE_BATTLE_TEST("Intimidate will correctly decrease the attack of the second ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); } - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); } } @@ -390,3 +390,48 @@ SINGLE_BATTLE_TEST("Intimdate does not lose timing after mega evolution and swit ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); } } + +DOUBLE_BATTLE_TEST("Intimidate drop down both opposing atk before eject pack has the chance to activate") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); } + } WHEN { + TURN { SWITCH(opponentLeft, 2); SEND_OUT(playerLeft, 2); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Intimidate will not miss timing for competitive") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_MILOTIC) { Ability(ABILITY_COMPETITIVE); } + PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); } + } WHEN { + TURN { SWITCH(opponentLeft, 2); SEND_OUT(playerLeft, 2); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + ABILITY_POPUP(playerRight, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } +} diff --git a/test/battle/ability/magician.c b/test/battle/ability/magician.c index f622ac07df..d12fe920b8 100644 --- a/test/battle/ability/magician.c +++ b/test/battle/ability/magician.c @@ -1,7 +1,7 @@ #include "global.h" #include "test/battle.h" -SINGLE_BATTLE_TEST("Magician does not get self-damage recoil after stealing Life Orb") +SINGLE_BATTLE_TEST("Magician gets self-damage recoil after stealing Life Orb") { GIVEN { ASSUME(gItemsInfo[ITEM_LIFE_ORB].holdEffect == HOLD_EFFECT_LIFE_ORB); @@ -16,10 +16,8 @@ SINGLE_BATTLE_TEST("Magician does not get self-damage recoil after stealing Life MESSAGE("Delphox used Tackle!"); ABILITY_POPUP(player, ABILITY_MAGICIAN); MESSAGE("Delphox stole the opposing Wobbuffet's Life Orb!"); - NONE_OF { - HP_BAR(player); - MESSAGE("Delphox was hurt by the Life Orb!"); - } + HP_BAR(player); + MESSAGE("Delphox was hurt by the Life Orb!"); // 2nd turn - Life Orb recoil happens now MESSAGE("Delphox used Tackle!"); HP_BAR(player); diff --git a/test/battle/ability/mirror_armor.c b/test/battle/ability/mirror_armor.c index 0918682d76..97e7616c94 100644 --- a/test/battle/ability/mirror_armor.c +++ b/test/battle/ability/mirror_armor.c @@ -102,10 +102,8 @@ SINGLE_BATTLE_TEST("Mirror Armor lowers the Attack of Pokemon with Intimidate") } } -// Unsure whether this should or should not fail, as Showdown has conflicting information. Needs testing in gen8 games. SINGLE_BATTLE_TEST("Mirror Armor doesn't lower the stats of an attacking Pokemon behind Substitute") { - KNOWN_FAILING; GIVEN { PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); } OPPONENT(SPECIES_WYNAUT); diff --git a/test/battle/ability/purifying_salt.c b/test/battle/ability/purifying_salt.c index cb8fc6ca56..85d2e9c009 100644 --- a/test/battle/ability/purifying_salt.c +++ b/test/battle/ability/purifying_salt.c @@ -100,3 +100,20 @@ SINGLE_BATTLE_TEST("Purifying Salt user can't be poisoned by Toxic Spikes") EXPECT_EQ(player->status1, STATUS1_NONE); } } + +SINGLE_BATTLE_TEST("Purifying Salt doesn't prevent pokemon from being poisoned by Toxic Spikes on switch-in if forced in by phazing with Mold Breaker") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_DRAGON_TAIL) == EFFECT_HIT_SWITCH_TARGET); + ASSUME(GetMoveEffect(MOVE_TOXIC_SPIKES) == EFFECT_TOXIC_SPIKES); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_GARGANACL) { Ability(ABILITY_PURIFYING_SALT); } + OPPONENT(SPECIES_PINSIR) { Ability(ABILITY_MOLD_BREAKER); } + } WHEN { + TURN { MOVE(opponent, MOVE_TOXIC_SPIKES); } + TURN { MOVE(opponent, MOVE_DRAGON_TAIL); } + } SCENE { + STATUS_ICON(player, STATUS1_POISON); + HP_BAR(player); + } +} diff --git a/test/battle/ability/teraform_zero.c b/test/battle/ability/teraform_zero.c index 09ce921931..96186f2047 100644 --- a/test/battle/ability/teraform_zero.c +++ b/test/battle/ability/teraform_zero.c @@ -84,7 +84,6 @@ SINGLE_BATTLE_TEST("Teraform Zero cannot be copied") DOUBLE_BATTLE_TEST("Teraform Zero shouldn't cause Neutralizing Gas to show it's popup when trying to activate") { - KNOWN_FAILING; // #5010 GIVEN { PLAYER(SPECIES_TERAPAGOS_TERASTAL); PLAYER(SPECIES_ABSOL) {Ability(ABILITY_PRESSURE); } @@ -95,8 +94,8 @@ DOUBLE_BATTLE_TEST("Teraform Zero shouldn't cause Neutralizing Gas to show it's TURN { SWITCH(playerRight, 2); MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } } SCENE { MESSAGE("Terapagos is storing energy!"); - MESSAGE("Terapagos terastalized into the Stellar type!"); + MESSAGE("Terapagos terastallized into the Stellar type!"); NOT ABILITY_POPUP(playerRight, ABILITY_NEUTRALIZING_GAS); - MESSAGE("Terapagos used Celebreate!"); + MESSAGE("Terapagos used Celebrate!"); } } diff --git a/test/battle/ability/unburden.c b/test/battle/ability/unburden.c new file mode 100644 index 0000000000..63a72e2ace --- /dev/null +++ b/test/battle/ability/unburden.c @@ -0,0 +1,101 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Unburden doubles speed once user uses item") +{ + GIVEN { + ASSUME(ItemId_GetHoldEffect(ITEM_GRASSY_SEED) == HOLD_EFFECT_SEEDS); + ASSUME(GetMoveEffect(MOVE_U_TURN) == EFFECT_HIT_ESCAPE); + PLAYER(SPECIES_DRIFBLIM) { Ability(ABILITY_UNBURDEN); Item(ITEM_GRASSY_SEED); Speed(5); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(7); } + OPPONENT(SPECIES_RILLABOOM) { Speed(7); Ability(ABILITY_GRASSY_SURGE); } + } WHEN { + TURN { MOVE(opponent, MOVE_U_TURN); SEND_OUT(opponent, 1); } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, opponent); + ABILITY_POPUP(opponent, ABILITY_GRASSY_SURGE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + // Turn 2, doubled speed + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +SINGLE_BATTLE_TEST("Unburden doubles speed once user gets their item knocked off") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_KNOCK_OFF, MOVE_EFFECT_KNOCK_OFF)); + PLAYER(SPECIES_DRIFBLIM) { Ability(ABILITY_UNBURDEN); Item(ITEM_POTION); Speed(5); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(7); } + } WHEN { + TURN { MOVE(opponent, MOVE_KNOCK_OFF); } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + // Turn 2, doubled speed + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +SINGLE_BATTLE_TEST("Unburden doesn't activate when item is consumed in Neutralizing Gas") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_U_TURN) == EFFECT_HIT_ESCAPE); + ASSUME(MoveHasAdditionalEffect(MOVE_KNOCK_OFF, MOVE_EFFECT_KNOCK_OFF)); + PLAYER(SPECIES_DRIFBLIM) { Ability(ABILITY_UNBURDEN); Item(ITEM_POTION); Speed(5); } + OPPONENT(SPECIES_WEEZING) { Speed(7); Ability(ABILITY_NEUTRALIZING_GAS); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(7); } + } WHEN { + TURN { MOVE(opponent, MOVE_KNOCK_OFF); } + TURN { MOVE(opponent, MOVE_U_TURN); SEND_OUT(opponent, 1); } + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_NEUTRALIZING_GAS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + // Turn 2, no speed increase + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, opponent); + MESSAGE("The effects of the neutralizing gas wore off!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + // Turn 3, no speed increase + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } +} + +SINGLE_BATTLE_TEST("Unburden doubling speed effect is ignored by Neutralizing Gas") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_U_TURN) == EFFECT_HIT_ESCAPE); + ASSUME(MoveHasAdditionalEffect(MOVE_KNOCK_OFF, MOVE_EFFECT_KNOCK_OFF)); + PLAYER(SPECIES_DRIFBLIM) { Ability(ABILITY_UNBURDEN); Item(ITEM_POTION); Speed(5); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(7); } + OPPONENT(SPECIES_WEEZING) { Speed(7); Ability(ABILITY_NEUTRALIZING_GAS); } + } WHEN { + TURN { MOVE(opponent, MOVE_KNOCK_OFF); } + TURN { MOVE(opponent, MOVE_U_TURN); SEND_OUT(opponent, 1); } + TURN { MOVE(opponent, MOVE_U_TURN); SEND_OUT(opponent, 0); } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + // Turn 2, doubled speed + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, opponent); + ABILITY_POPUP(opponent, ABILITY_NEUTRALIZING_GAS); + // Turn 3, no speed increase + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, opponent); + MESSAGE("The effects of the neutralizing gas wore off!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + // Turn 4, doubled speed + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} diff --git a/test/battle/hold_effect/eject_pack.c b/test/battle/hold_effect/eject_pack.c index da053ec5e6..0a21362a99 100644 --- a/test/battle/hold_effect/eject_pack.c +++ b/test/battle/hold_effect/eject_pack.c @@ -160,3 +160,74 @@ SINGLE_BATTLE_TEST("Eject Pack will miss timing to switch out user if Eject Butt EXPECT(opponent->species == SPECIES_WYNAUT); } } + +DOUBLE_BATTLE_TEST("Eject Pack: Only the fastest Eject Pack will activate after intimidate") +{ + u32 speed; + + PARAMETRIZE { speed = 1; } + PARAMETRIZE { speed = 11; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(10); Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT) { Speed(speed); Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_WOBBUFFET) { Speed(3); } + OPPONENT(SPECIES_WYNAUT) { Speed(4); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_EKANS) { Speed(6); Ability(ABILITY_INTIMIDATE); } + } WHEN { + TURN { + SWITCH(opponentLeft, 2); + if (speed == 11) + SEND_OUT(playerRight, 2); + else + SEND_OUT(playerLeft, 2); + } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + if (speed == 11) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + } else { + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + } + } +} + +DOUBLE_BATTLE_TEST("Eject Pack: Only the fastest Eject Pack will activate after a move stat drop") +{ + u32 speed; + + PARAMETRIZE { speed = 1; } + PARAMETRIZE { speed = 11; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(10); Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT) { Speed(speed); Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_WOBBUFFET) { Speed(3); } + OPPONENT(SPECIES_WYNAUT) { Speed(4); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_BUBBLE); + if (speed == 11) + SEND_OUT(playerRight, 2); + else + SEND_OUT(playerLeft, 2); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BUBBLE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + if (speed == 11) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + } else { + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + } + } +} diff --git a/test/battle/move_effect/aqua_ring.c b/test/battle/move_effect/aqua_ring.c index 158c839d79..d137a35276 100644 --- a/test/battle/move_effect/aqua_ring.c +++ b/test/battle/move_effect/aqua_ring.c @@ -1,6 +1,32 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Aqua Ring recovers 1/16th HP at end of turn"); -TO_DO_BATTLE_TEST("Aqua Ring can be used under Heal Block but will not heal the user"); +SINGLE_BATTLE_TEST("Aqua Ring recovers 1/16th HP at end of turn") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(50); MaxHP(128); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_AQUA_RING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_RING, player); + } THEN { + EXPECT(player->hp == 58); + } +} + +SINGLE_BATTLE_TEST("Aqua Ring can be used under Heal Block but will not heal the user") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(50); MaxHP(128); Speed(50); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(100); } + } WHEN { + TURN { MOVE(opponent, MOVE_HEAL_BLOCK); MOVE(player, MOVE_AQUA_RING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_RING, player); + } THEN { + EXPECT(player->hp == 50); + } +} + TO_DO_BATTLE_TEST("Baton Pass passes Aqua Ring's effect"); diff --git a/test/battle/move_effect/attract.c b/test/battle/move_effect/attract.c index 748a88a950..be30483358 100644 --- a/test/battle/move_effect/attract.c +++ b/test/battle/move_effect/attract.c @@ -1,7 +1,105 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Attract causes the target to become infatuated with the user if they have opposite genders"); -TO_DO_BATTLE_TEST("Attract ignores type immunity"); -TO_DO_BATTLE_TEST("Attract bypasses Substitute"); -TO_DO_BATTLE_TEST("Attract fails if the target is already infatuated"); +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_ATTRACT) == EFFECT_ATTRACT); + ASSUME(gSpeciesInfo[SPECIES_NIDOKING].genderRatio == MON_MALE); + ASSUME(gSpeciesInfo[SPECIES_NIDOQUEEN].genderRatio == MON_FEMALE); +} + +SINGLE_BATTLE_TEST("Attract causes the target to become infatuated with the user if they have opposite genders") +{ + GIVEN { + PLAYER(SPECIES_NIDOQUEEN); + OPPONENT(SPECIES_NIDOKING); + } WHEN { + TURN { MOVE(player, MOVE_ATTRACT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ATTRACT, player); + MESSAGE("The opposing Nidoking fell in love!"); + } THEN { + EXPECT(opponent->status2 & STATUS2_INFATUATION); + } +} + +SINGLE_BATTLE_TEST("Attract ignores type immunity") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_ATTRACT) == TYPE_NORMAL); + PLAYER(SPECIES_NIDOQUEEN); + OPPONENT(SPECIES_MISDREAVUS) { Gender(MON_MALE); } + } WHEN { + TURN { MOVE(player, MOVE_ATTRACT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ATTRACT, player); + MESSAGE("The opposing Misdreavus fell in love!"); + } THEN { + EXPECT(opponent->status2 & STATUS2_INFATUATION); + } +} + +SINGLE_BATTLE_TEST("Attract bypasses Substitute") +{ + GIVEN { + PLAYER(SPECIES_NIDOQUEEN) { Speed(90); } + OPPONENT(SPECIES_NIDOKING) { Speed(100); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); } + TURN { MOVE(player, MOVE_ATTRACT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ATTRACT, player); + MESSAGE("The opposing Nidoking fell in love!"); + } THEN { + EXPECT(opponent->status2 & STATUS2_INFATUATION); + } +} + +SINGLE_BATTLE_TEST("Attract fails if the target is already infatuated") +{ + GIVEN { + PLAYER(SPECIES_NIDOQUEEN); + OPPONENT(SPECIES_NIDOKING); + } WHEN { + TURN { MOVE(player, MOVE_ATTRACT); } + TURN { MOVE(player, MOVE_ATTRACT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ATTRACT, player); + MESSAGE("The opposing Nidoking fell in love!"); + MESSAGE("Nidoqueen used Attract!"); + MESSAGE("But it failed!"); + } THEN { + EXPECT(opponent->status2 & STATUS2_INFATUATION); + } +} + +SINGLE_BATTLE_TEST("Attract fails when used on a Pokémon of the same gender") +{ + GIVEN { + PLAYER(SPECIES_NIDOQUEEN); + OPPONENT(SPECIES_NIDOQUEEN); + } WHEN { + TURN { MOVE(player, MOVE_ATTRACT); } + } SCENE { + MESSAGE("Nidoqueen used Attract!"); + MESSAGE("But it failed!"); + } THEN { + EXPECT(!(opponent->status2 & STATUS2_INFATUATION)); + } +} + +SINGLE_BATTLE_TEST("Attract fails when used on a genderless Pokémon") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_STARMIE].genderRatio == MON_GENDERLESS); + PLAYER(SPECIES_NIDOQUEEN); + OPPONENT(SPECIES_STARMIE); + } WHEN { + TURN { MOVE(player, MOVE_ATTRACT); } + } SCENE { + MESSAGE("Nidoqueen used Attract!"); + MESSAGE("But it failed!"); + } THEN { + EXPECT(!(opponent->status2 & STATUS2_INFATUATION)); + } +} diff --git a/test/battle/move_effect/bestow.c b/test/battle/move_effect/bestow.c index 1161cdf8ae..24abdb3bea 100644 --- a/test/battle/move_effect/bestow.c +++ b/test/battle/move_effect/bestow.c @@ -1,10 +1,130 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Bestow transfers its held item to the target"); -TO_DO_BATTLE_TEST("Bestow fails if the user has no held item"); -TO_DO_BATTLE_TEST("Bestow fails if the target already has a held item"); -TO_DO_BATTLE_TEST("Bestow fails if the target is behind a Substitute"); -TO_DO_BATTLE_TEST("Bestow fails if the user is holding Mail"); -TO_DO_BATTLE_TEST("Bestow fails if the user's held item changes its form"); -TO_DO_BATTLE_TEST("Bestow fails if the user's held item is a Z-Crystal"); +SINGLE_BATTLE_TEST("Bestow transfers its held item to the target") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BESTOW); } + } THEN { + EXPECT(player->item == ITEM_NONE); + EXPECT(opponent->item == ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Bestow fails if the user has no held item") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BESTOW); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(player->item == ITEM_NONE); + EXPECT(opponent->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Bestow fails if the target already has a held item") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LUM_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_BESTOW); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(player->item == ITEM_SITRUS_BERRY); + EXPECT(opponent->item == ITEM_LUM_BERRY); + } +} + +#include "mail.h" +SINGLE_BATTLE_TEST("Bestow fails if the user is holding Mail") +{ + KNOWN_FAILING; + + GIVEN { + ASSUME(ItemIsMail(ITEM_ORANGE_MAIL)); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_ORANGE_MAIL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BESTOW); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(player->item == ITEM_ORANGE_MAIL); + EXPECT(opponent->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Bestow fails if the user's held item is a Mega Stone") +{ + GIVEN { + PLAYER(SPECIES_BLAZIKEN) { Item(ITEM_BLAZIKENITE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BESTOW); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(player->item == ITEM_BLAZIKENITE); + EXPECT(opponent->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Bestow fails if the user's held item is a Z-Crystal") +{ + GIVEN { + ASSUME(ItemId_GetHoldEffect(ITEM_FIGHTINIUM_Z) == HOLD_EFFECT_Z_CRYSTAL); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_FIGHTINIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BESTOW); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(player->item == ITEM_FIGHTINIUM_Z); + EXPECT(opponent->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Bestow fails if the target is behind a Substitute") +{ + KNOWN_FAILING; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); Speed(50); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(100); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_BESTOW); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(player->item == ITEM_SITRUS_BERRY); + EXPECT(opponent->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Bestow fails if the user's held item changes its form") +{ + KNOWN_FAILING; + + GIVEN { + PLAYER(SPECIES_GIRATINA_ORIGIN) { Item(ITEM_GRISEOUS_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BESTOW); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(player->item == ITEM_GRISEOUS_ORB); + EXPECT(opponent->item == ITEM_NONE); + } +} + diff --git a/test/battle/move_effect/captivate.c b/test/battle/move_effect/captivate.c index 0da58983c7..224e6bef07 100644 --- a/test/battle/move_effect/captivate.c +++ b/test/battle/move_effect/captivate.c @@ -1,7 +1,112 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Captivate decreases the target's Sp. Attack if they're opposite gender from the user"); -TO_DO_BATTLE_TEST("Captivate fails if the target and user share gender"); -TO_DO_BATTLE_TEST("Captivate fails if the target is genderless"); -TO_DO_BATTLE_TEST("Captivate fails if the user is genderless"); +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_CAPTIVATE) == EFFECT_CAPTIVATE); + ASSUME(gSpeciesInfo[SPECIES_NIDOKING].genderRatio == MON_MALE); + ASSUME(gSpeciesInfo[SPECIES_NIDOQUEEN].genderRatio == MON_FEMALE); + ASSUME(gSpeciesInfo[SPECIES_STARMIE].genderRatio == MON_GENDERLESS); +} + +SINGLE_BATTLE_TEST("Captivate decreases the target's Sp. Attack if they're opposite gender from the user") +{ + GIVEN { + PLAYER(SPECIES_NIDOQUEEN); + OPPONENT(SPECIES_NIDOKING); + } WHEN { + TURN { MOVE(player, MOVE_CAPTIVATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CAPTIVATE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Nidoking's Sp. Atk harshly fell!"); + } THEN { + EXPECT(opponent->statStages[STAT_SPATK] == 4); + } +} + +SINGLE_BATTLE_TEST("Captivate fails if the target and user share gender") +{ + GIVEN { + PLAYER(SPECIES_NIDOKING); + OPPONENT(SPECIES_NIDOKING); + } WHEN { + TURN { MOVE(player, MOVE_CAPTIVATE); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(opponent->statStages[STAT_SPATK] == 6); + } +} + +SINGLE_BATTLE_TEST("Captivate fails if the target is genderless") +{ + GIVEN { + PLAYER(SPECIES_NIDOQUEEN); + OPPONENT(SPECIES_STARMIE); + } WHEN { + TURN { MOVE(player, MOVE_CAPTIVATE); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(opponent->statStages[STAT_SPATK] == 6); + } +} + +SINGLE_BATTLE_TEST("Captivate fails if the user is genderless") +{ + GIVEN { + PLAYER(SPECIES_STARMIE); + OPPONENT(SPECIES_NIDOQUEEN); + } WHEN { + TURN { MOVE(player, MOVE_CAPTIVATE); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(opponent->statStages[STAT_SPATK] == 6); + } +} + +SINGLE_BATTLE_TEST("Captivate fails if both the user and the opponent are genderless") +{ + GIVEN { + PLAYER(SPECIES_STARMIE); + OPPONENT(SPECIES_STARMIE); + } WHEN { + TURN { MOVE(player, MOVE_CAPTIVATE); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(opponent->statStages[STAT_SPATK] == 6); + } +} + +SINGLE_BATTLE_TEST("Attract fails when used by a genderless Pokémon") +{ + GIVEN { + PLAYER(SPECIES_STARMIE); + OPPONENT(SPECIES_NIDOQUEEN); + } WHEN { + TURN { MOVE(player, MOVE_ATTRACT); } + } SCENE { + MESSAGE("Starmie used Attract!"); + MESSAGE("But it failed!"); + } THEN { + EXPECT(!(opponent->status2 & STATUS2_INFATUATION)); + } +} + +SINGLE_BATTLE_TEST("Attract fails if both the user and the target are genderless") +{ + GIVEN { + PLAYER(SPECIES_STARMIE); + OPPONENT(SPECIES_STARMIE); + } WHEN { + TURN { MOVE(player, MOVE_ATTRACT); } + } SCENE { + MESSAGE("Starmie used Attract!"); + MESSAGE("But it failed!"); + } THEN { + EXPECT(!(opponent->status2 & STATUS2_INFATUATION)); + } +} diff --git a/test/battle/move_effect/entrainment.c b/test/battle/move_effect/entrainment.c index cccae86759..fd47937f04 100644 --- a/test/battle/move_effect/entrainment.c +++ b/test/battle/move_effect/entrainment.c @@ -14,6 +14,48 @@ AI_DOUBLE_BATTLE_TEST("AI prefers Entrainment'ing good abilities onto partner wi } } -TO_DO_BATTLE_TEST("Entrainment changes the target's Ability to match the user's"); -TO_DO_BATTLE_TEST("Entrainment fails if the user's ability has cantBeCopied flag"); -TO_DO_BATTLE_TEST("Entrainment fails if the targets's ability has cantBeOverwritten flag"); +SINGLE_BATTLE_TEST("Entrainment changes the target's Ability to match the user's") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); } + } WHEN { + TURN { MOVE(player, MOVE_ENTRAINMENT); } + } THEN { + EXPECT(opponent->ability == ABILITY_TELEPATHY); + } +} + +SINGLE_BATTLE_TEST("Entrainment fails if the user's ability has cantBeCopied flag") +{ + GIVEN { + ASSUME(gAbilitiesInfo[ABILITY_MULTITYPE].cantBeCopied); + PLAYER(SPECIES_ARCEUS) { Ability(ABILITY_MULTITYPE); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); } + } WHEN { + TURN { MOVE(player, MOVE_ENTRAINMENT); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(player->ability == ABILITY_MULTITYPE); + EXPECT(opponent->ability == ABILITY_SHADOW_TAG); + } +} + +SINGLE_BATTLE_TEST("Entrainment fails if the target's ability has cantBeOverwritten flag") +{ + GIVEN { + ASSUME(gAbilitiesInfo[ABILITY_MULTITYPE].cantBeOverwritten); + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); } + OPPONENT(SPECIES_ARCEUS) { Ability(ABILITY_MULTITYPE); } + } WHEN { + TURN { MOVE(player, MOVE_ENTRAINMENT); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(player->ability == ABILITY_TELEPATHY); + EXPECT(opponent->ability == ABILITY_MULTITYPE); + } +} + +TO_DO_BATTLE_TEST("Entrainment fails on Dynamaxed Pokémon"); diff --git a/test/battle/move_effect_secondary/bug_bite.c b/test/battle/move_effect_secondary/bug_bite.c index e086941a5e..70e0966caa 100644 --- a/test/battle/move_effect_secondary/bug_bite.c +++ b/test/battle/move_effect_secondary/bug_bite.c @@ -133,3 +133,18 @@ SINGLE_BATTLE_TEST("Tanga Berry activates before Bug Bite") EXPECT_EQ(player->item, ITEM_NONE); } } + +SINGLE_BATTLE_TEST("Bug Bite ignores Unnerve") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_TYRANITAR) { Ability(ABILITY_UNNERVE); Item(ITEM_ORAN_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_BUG_BITE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BUG_BITE, player); + HP_BAR(player); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +}