diff --git a/README.md b/README.md index 11666a16a53a..8245e5e0357c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,38 @@ +image + +# Trait System (Release 1.14.2) + +This is the full release of the Multi-Ability function I'm calling the Trait System! +Currently updated to Pokeemerald Expansion 1.14.2 + +The Trait System allows you to assign more than one ability to each pokemon for more complex and more interesting setups. + +- General terminology I'm going for is: + - Ability = Same as vanilla. + - Innate = Additional abilities that are the same for all members of a species. + - Trait = Encompassing term for either one + Note: for the sake of making merging a little easier, "Ability" is still used in many places when "Trait" is intended. +- Abilities work exactly the same as vanilla where a pokemon could have one of 3 ability options, however Innates are fixed to each species and don't change. +- To add Innates you just need to add a new .innates parameter underneath the existing .abilities one using the same formatting. Example innate setups have been included commented out for all pokemon in the Gen 1 families. + - ex: .innates = { ABILITY_PROTEAN, ABILITY_ROUGH_SKIN, ABILITY_CLEAR_BODY }, +- Uses the MAX_MON_INNATES variable to control how many Innates are available, default is 3 totaling up to 4 active abilities per pokemon. If you assign more innates than the max, surplus entries will simply be ignored. This means you could even set MAX_MON_INATES to 0 and you would functionally just get the original vanilla system. +- There is a new Summary Page "Traits" to display the four slots along with some color changes across the vanilla pages for color balance. +- Most effects that target Abilities still only target a pokemon's primary Ability, ignoring their Innates. Neutralizing Gas, Worry Seed, Trace, and Mummy for example all only affect Abilities but not Innates. Mold Breaker type Traits however work on everything, including Innates. (NOTE: Trace is also not designed to be an Innate since it replaces itself as part of its effect. Trace in particular should ALWAYS be assigned as an Ability or else you'll get an infinite loop lock.) +- The basic code design is all Ability checks have been replaced with Trait checks, reading all passives a pokemon has whenever an Ability is looked for. All previously mutually exclusive abilities like the weather ones which use a Switch Case format has been replaced with If statements so that they can all be called anyway (though natually any abilities that actually conflict will overwrite by code order, Drought and Snow Warning will both activate, but Snow Warning is later in the list so ultimately the weather will be snow/hail. Really this is only a consideration for future randomizer settings.) +- Reffer to the AbilityEffect enum table for a more detailed list of how timing interactions work. Most abilities will interact fine but there are exceptions such as abilities which activate during a terrain or weather change where only one ability in that timing window will activate at a time. +- If activated at the same time, Harvest, Pickup, and Ball Fetch will activate in that respective priority order. +- Ability popups have been modified into a Stack system so that when multiple abilities are triggered at once, they are stored then read out in the correct order. Battle Message logic has also been updated to account for the new timings. +- Make Test system updated to account for Innates as well, all vanilla tests involving Abilities are given a second copy suffixed (Multi) where the tested Ability is instead an Innate. There is also a new multi_abilities.c test file which contains more intensive innate specific tests such as potential conflicts in Traits or timings. +- A useful template for organizing pokemon and assigning Traits can be found here: https://docs.google.com/spreadsheets/d/1pNtGGapXx20svfM0PpztHYHJnbgvXHS8tc_i-h0a0Po/edit?gid=0#gid=0 +Note that the Data sheet includes a collumn for automatically generating the .innate line to be added into Expansion's lists based on how you fill out the Pokemon's innate list. + +- The AI system largely works the same just with Innates added on top and fixed. This means the AI can still treat Abilities as unknown until they learn what the Ability is directly, but Innates will always be treated as known. + +Basic code bedrock design comes from old Emerald Redux code with permission. + +Huge thanks to the RH Hideout discord community for their help, advice, and testing, especially Alex, Surskitty, Kleem, Meister_anon, and MGriffin who helped make this possible. + + # About `pokeemerald-expansion` ![Gif that shows debugging functionality that is unique to pokeemerald-expansion such as rerolling Trainer ID, Cheat Start, PC from Debug Menu, Debug PC Fill, Pokémon Sprite Visualizer, Debug Warp to Map, and Battle Debug Menu](https://github.com/user-attachments/assets/cf9dfbee-4c6b-4bca-8e0a-07f116ef891c) ![Gif that shows overworld functionality that is unique to pokeemerald-expansion such as indoor running, BW2 style map popups, overworld followers, DNA Splicers, Gen 1 style fishing, OW Item descriptions, Quick Run from Battle, Use Last Ball, Wild Double Battles, and Catch from EXP](https://github.com/user-attachments/assets/383af243-0904-4d41-bced-721492fbc48e) ![Gif that shows off a number of modern Pokémon battle mechanics happening in the pokeemerald-expansion engine: 2 vs 1 battles, modern Pokémon, items, moves, abilities, fully customizable opponents and partners, Trainer Slides, and generational gimmicks](https://github.com/user-attachments/assets/50c576bc-415e-4d66-a38f-ad712f3316be) diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index d8a8be7612f9..b66cda7ac858 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1252,8 +1252,9 @@ .4byte \jumpInstr .endm - .macro tryrecycleitem failInstr:req + .macro tryrecycleitem type:req, failInstr:req .byte 0xea + .byte \type .4byte \failInstr .endm @@ -2469,3 +2470,9 @@ .macro tryabsorbtoxicspikesonfaint callnative BS_TryAbsorbToxicSpikesOnFaint .endm + + .macro pushtraitstack battler:req, ability:req + callnative BS_PushTraitStack + .byte \battler + .2byte \ability + .endm diff --git a/charmap.txt b/charmap.txt index dd2123f8f93b..cedd586291f2 100644 --- a/charmap.txt +++ b/charmap.txt @@ -426,6 +426,10 @@ B_PARTNER_NAME_WITH_CLASS = FD 44 B_ATK_TRAINER_NAME_WITH_CLASS = FD 45 B_EFF_TEAM1 = FD 46 B_EFF_TEAM2 = FD 47 +B_EFF2_TEAM1 = FD 48 +B_EFF2_TEAM2 = FD 49 +B_DEF_ABILITY2 = FD 4A +B_ATK_PARTNER_NAME_WITH_PREFIX = FD 4B @ indicates the end of a town/city name (before " TOWN" or " CITY") NAME_END = FC 00 diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 4f0645838b56..dda6c4d7d6ca 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -517,6 +517,7 @@ BattleScript_Teatimevul: moveendcase MOVEEND_CLEAR_BITS goto BattleScript_MoveEnd BattleScript_Teatimesorb: + pushtraitstack BS_TARGET ABILITY_VOLT_ABSORB @ Generates placeholder popup entry call BattleScript_AbilityPopUpTarget tryhealquarterhealth BS_TARGET, BattleScript_Teatimesorb_end healthbarupdate BS_TARGET, PASSIVE_HP_UPDATE @@ -529,6 +530,7 @@ BattleScript_Teatimesorb_end: moveendcase MOVEEND_CLEAR_BITS goto BattleScript_MoveEnd BattleScript_Teatimerod: + pushtraitstack BS_TARGET ABILITY_LIGHTNING_ROD call BattleScript_AbilityPopUpTarget setstatchanger STAT_SPATK, 1, FALSE statbuffchange BS_TARGET, STAT_CHANGE_ALLOW_PTR, BattleScript_TeatimeBuffer @@ -540,6 +542,7 @@ BattleScript_Teatimerod: moveendcase MOVEEND_CLEAR_BITS goto BattleScript_MoveEnd BattleScript_Teatimemotor: + pushtraitstack BS_TARGET ABILITY_MOTOR_DRIVE call BattleScript_AbilityPopUpTarget setstatchanger STAT_SPEED, 1, FALSE statbuffchange BS_TARGET, STAT_CHANGE_ALLOW_PTR, BattleScript_TeatimeBuffer @@ -1066,6 +1069,7 @@ BattleScript_StrengthSapManipulateDmg: waitmessage B_WAIT_TIME_LONG goto BattleScript_MoveEnd BattleScript_StrengthSapLiquidOoze: + pushtraitstack BS_TARGET ABILITY_LIQUID_OOZE call BattleScript_AbilityPopUpTarget manipulatedamage DMG_CHANGE_SIGN setbyte cMULTISTRING_CHOOSER, B_MSG_ABSORB_OOZE @@ -1218,10 +1222,8 @@ BattleScript_EffectPartingShotPrintWontDecrease: return BattleScript_EffectPartingShotPrintWontDecreaseContrary: - swapattackerwithtarget - printstring STRINGID_STATSWONTDECREASE2 + printstring STRINGID_STATSWONTDECREASECONTRARY2 waitmessage B_WAIT_TIME_LONG - swapattackerwithtarget return BattleScript_EffectPowder:: @@ -2533,6 +2535,7 @@ BattleScript_FlowerVeilProtects:: BattleScript_SweetVeilProtectsRet:: pause B_WAIT_TIME_SHORT + pushtraitstack BS_TARGET ABILITY_SWEET_VEIL call BattleScript_AbilityPopUp printstring STRINGID_FLOWERVEILPROTECTED waitmessage B_WAIT_TIME_LONG @@ -3051,6 +3054,7 @@ BattleScript_MoveEffectAuroraVeil:: return BattleScript_VoltAbsorbHeal: + pushtraitstack BS_TARGET ABILITY_VOLT_ABSORB copybyte gBattlerAbility, gBattlerTarget tryhealquarterhealth BS_TARGET, BattleScript_MonMadeMoveUseless @ Check if max hp goto BattleScript_MoveHPDrain @@ -3516,6 +3520,7 @@ BattleScript_PerishSongLoopIncrement:: BattleScript_PerishSongBlocked:: copybyte sBATTLER, gBattlerTarget + call BattleScript_AbilityPopUp printstring STRINGID_PKMNSXBLOCKSY2 waitmessage B_WAIT_TIME_LONG goto BattleScript_PerishSongLoopIncrement @@ -3887,6 +3892,10 @@ BattleScript_NotAffectedAbilityPopUp:: waitmessage B_WAIT_TIME_LONG goto BattleScript_MoveEnd +BattleScript_GenerateAbilityPopUp:: + call BattleScript_AbilityPopUp + return + BattleScript_EffectStockpile:: attackcanceler stockpile 0 @@ -4168,7 +4177,7 @@ BattleScript_EffectMagicCoat:: BattleScript_EffectRecycle:: attackcanceler - tryrecycleitem BattleScript_ButItFailed + tryrecycleitem RECYCLE_ITEM_RECYCLE BattleScript_ButItFailed attackanimation waitanimation printstring STRINGID_XFOUNDONEY @@ -4247,9 +4256,11 @@ BattleScript_EffectSkillSwap:: waitanimation jumpiftargetally BattleScript_EffectSkillSwap_AfterAbilityPopUp copybyte gBattlerAbility, gBattlerAttacker + pushtraitstack BS_ATTACKER ABILITY_VOLT_ABSORB @ Generates placeholder popup entry call BattleScript_AbilityPopUpOverwriteThenNormal copybyte gBattlerAbility, gBattlerTarget copyhword sABILITY_OVERWRITE, gLastUsedAbility + pushtraitstack BS_TARGET ABILITY_VOLT_ABSORB @ Generates placeholder popup entry call BattleScript_AbilityPopUpOverwriteThenNormal BattleScript_EffectSkillSwap_AfterAbilityPopUp: recordability BS_ATTACKER @@ -4983,6 +4994,7 @@ BattleScript_SafeguardEnds:: BattleScript_LeechSeedTurnDrainLiquidOoze:: call BattleScript_LeechSeedTurnDrain copybyte gBattlerAbility, gBattlerAttacker + pushtraitstack BS_ATTACKER ABILITY_LIQUID_OOZE call BattleScript_AbilityPopUp copybyte gBattlerAttacker, gBattlerTarget @ needed to get liquid ooze message correct jumpifability BS_TARGET, ABILITY_MAGIC_GUARD, BattleScript_LeechSeedTurnDrainHealBlockEnd2 @@ -5406,6 +5418,13 @@ BattleScript_WindPowerActivates:: waitmessage B_WAIT_TIME_LONG return +BattleScript_ElectromorphosisActivates:: + call BattleScript_AbilityPopUp + setvolatile BS_TARGET, VOLATILE_CHARGE_TIMER, 1 + printstring STRINGID_BEINGHITCHARGEDPKMNWITHPOWER + waitmessage B_WAIT_TIME_LONG + return + BattleScript_ToxicDebrisActivates:: call BattleScript_AbilityPopUp pause B_WAIT_TIME_SHORT @@ -6057,11 +6076,10 @@ BattleScript_CottonDownLoopIncrement: return BattleScript_AnticipationActivates:: - pause 5 call BattleScript_AbilityPopUp printstring STRINGID_ANTICIPATIONACTIVATES waitmessage B_WAIT_TIME_LONG - return + end3 BattleScript_AftermathDmg:: pause B_WAIT_TIME_SHORT @@ -6075,12 +6093,25 @@ BattleScript_AftermathDmg:: BattleScript_AftermathDmgRet: return +BattleScript_InnardsOutDmg:: + pause B_WAIT_TIME_SHORT + call BattleScript_AbilityPopUpScripting + jumpifability BS_ATTACKER, ABILITY_MAGIC_GUARD, BattleScript_InnardsOutDmgRet + healthbarupdate BS_ATTACKER, PASSIVE_HP_UPDATE + datahpupdate BS_ATTACKER, PASSIVE_HP_UPDATE + printstring STRINGID_AFTERMATHDMG + waitmessage B_WAIT_TIME_LONG + tryfaintmon BS_ATTACKER +BattleScript_InnardsOutDmgRet: + return + BattleScript_DampPreventsAftermath:: pause B_WAIT_TIME_SHORT call BattleScript_AbilityPopUp pause 40 copybyte gBattlerAbility, sBATTLER call BattleScript_AbilityPopUp + sethword gDisplayAbility2, ABILITY_AFTERMATH printstring STRINGID_PKMNSABILITYPREVENTSABILITY waitmessage B_WAIT_TIME_LONG return @@ -6182,7 +6213,8 @@ BattleScript_PowderMoveNoEffect:: printstring STRINGID_SAFETYGOGGLESPROTECTED goto BattleScript_PowderMoveNoEffectWaitMsg BattleScript_PowderMoveNoEffectOvercoat: - call BattleScript_AbilityPopUpTarget + pushtraitstack BS_TARGET ABILITY_OVERCOAT + call BattleScript_AbilityPopUp BattleScript_PowderMoveNoEffectPrint: printstring STRINGID_ITDOESNTAFFECT BattleScript_PowderMoveNoEffectWaitMsg: @@ -6463,6 +6495,7 @@ BattleScript_DrizzleActivates:: BattleScript_AbilityRaisesDefenderStat:: pause B_WAIT_TIME_SHORT statbuffchange BS_TARGET, STAT_CHANGE_ONLY_CHECKING, BattleScript_AbilityCantRaiseDefenderStat + pause B_WAIT_TIME_SHORT call BattleScript_AbilityPopUp statbuffchange BS_TARGET, 0, BattleScript_AbilityCantRaiseDefenderStat printstring STRINGID_DEFENDERSSTATROSE @@ -6634,7 +6667,7 @@ BattleScript_CheekPouchActivates:: BattleScript_PickupActivates:: pause 5 - tryrecycleitem BattleScript_PickupActivatesEnd + tryrecycleitem RECYCLE_ITEM_PICKUP BattleScript_PickupActivatesEnd call BattleScript_AbilityPopUp printstring STRINGID_XFOUNDONEY waitmessage B_WAIT_TIME_LONG @@ -6644,7 +6677,7 @@ BattleScript_PickupActivatesEnd: BattleScript_HarvestActivates:: pause 5 - tryrecycleitem BattleScript_HarvestActivatesEnd + tryrecycleitem RECYCLE_ITEM_HARVEST BattleScript_HarvestActivatesEnd call BattleScript_AbilityPopUp printstring STRINGID_HARVESTBERRY waitmessage B_WAIT_TIME_LONG @@ -6663,8 +6696,8 @@ BattleScript_SolarPowerActivates:: BattleScript_HealerActivates:: call BattleScript_AbilityPopUp - curestatus BS_SCRIPTING - updatestatusicon BS_SCRIPTING + curestatus BS_SCRIPTING_PARTNER + updatestatusicon BS_SCRIPTING_PARTNER printstring STRINGID_HEALERCURE waitmessage B_WAIT_TIME_LONG end2 @@ -6727,6 +6760,7 @@ BattleScript_IntimidateActivates:: call BattleScript_AbilityPopUp setbyte gBattlerTarget, 0 BattleScript_IntimidateLoop: + setbyte gDisplayAbility ABILITY_INTIMIDATE jumpiftargetally BattleScript_IntimidateLoopIncrement jumpifabsent BS_TARGET, BattleScript_IntimidateLoopIncrement jumpifvolatile BS_TARGET, VOLATILE_SUBSTITUTE, BattleScript_IntimidateLoopIncrement @@ -6735,6 +6769,7 @@ BattleScript_IntimidateEffect: copybyte sBATTLER, gBattlerAttacker setstatchanger STAT_ATK, 1, TRUE statbuffchange BS_TARGET, STAT_CHANGE_NOT_PROTECT_AFFECTED | STAT_CHANGE_ALLOW_PTR, BattleScript_IntimidateLoopIncrement + jumpifability BS_TARGET, ABILITY_CONTRARY, BattleScript_IntimidateContrary jumpifbyte CMP_EQUAL, cMULTISTRING_CHOOSER, B_MSG_STAT_WONT_CHANGE, BattleScript_IntimidateWontDecrease printstring STRINGID_PKMNCUTSATTACKWITH BattleScript_IntimidateEffect_WaitString: @@ -6765,6 +6800,11 @@ BattleScript_IntimidateWontDecrease: printstring STRINGID_STATSWONTDECREASE goto BattleScript_IntimidateEffect_WaitString +BattleScript_IntimidateContrary: + pushtraitstack BS_TARGET ABILITY_CONTRARY + printstring STRINGID_PKMNINTIMIDATECONTRARYRATTLED + goto BattleScript_IntimidateEffect_WaitString + BattleScript_IntimidateInReverse:: copybyte sBATTLER, gBattlerTarget call BattleScript_AbilityPopUpTarget @@ -6869,9 +6909,27 @@ BattleScript_QuarkDriveActivates:: waitmessage B_WAIT_TIME_MED end3 -BattleScript_RuinAbilityActivates:: +BattleScript_RuinAbilityActivatesVessel:: + call BattleScript_AbilityPopUp + printstring STRINGID_ABILITYWEAKENEDSURROUNDINGMONSSPATK + waitmessage B_WAIT_TIME_LONG + end3 + +BattleScript_RuinAbilityActivatesSword:: call BattleScript_AbilityPopUp - printstring STRINGID_ABILITYWEAKENEDSURROUNDINGMONSSTAT + printstring STRINGID_ABILITYWEAKENEDSURROUNDINGMONSDEF + waitmessage B_WAIT_TIME_LONG + end3 + +BattleScript_RuinAbilityActivatesTablets:: + call BattleScript_AbilityPopUp + printstring STRINGID_ABILITYWEAKENEDSURROUNDINGMONSATK + waitmessage B_WAIT_TIME_LONG + end3 + +BattleScript_RuinAbilityActivatesBeads:: + call BattleScript_AbilityPopUp + printstring STRINGID_ABILITYWEAKENEDSURROUNDINGMONSSPDEF waitmessage B_WAIT_TIME_LONG end3 @@ -7167,6 +7225,7 @@ BattleScript_FlinchPrevention:: BattleScript_OwnTempoPrevents:: pause B_WAIT_TIME_SHORT + pushtraitstack BS_TARGET ABILITY_OWN_TEMPO call BattleScript_AbilityPopUp copybyte sBATTLER, gBattlerTarget printstring STRINGID_PKMNPREVENTSCONFUSIONWITH @@ -7221,6 +7280,7 @@ BattleScript_StickyHoldActivates:: BattleScript_StickyHoldActivatesRet:: pause B_WAIT_TIME_SHORT + pushtraitstack BS_TARGET ABILITY_STICKY_HOLD call BattleScript_AbilityPopUp printstring STRINGID_PKMNSXMADEYINEFFECTIVE waitmessage B_WAIT_TIME_LONG @@ -7265,6 +7325,10 @@ BattleScript_MummyActivates:: call BattleScript_AbilityPopUpOverwriteThenNormal recordability BS_TARGET recordability BS_ATTACKER + setbyte gDisplayAbility, ABILITY_MUMMY + jumpifability BS_TARGET, ABILITY_MUMMY, BattleScript_MummyCalled + sethword gDisplayAbility, ABILITY_LINGERING_AROMA +BattleScript_MummyCalled:: printstring STRINGID_ATTACKERACQUIREDABILITY waitmessage B_WAIT_TIME_LONG trytoclearprimalweather @@ -7278,9 +7342,11 @@ BattleScript_WanderingSpiritActivates:: savetarget copybyte gBattlerAbility, gBattlerTarget sethword sABILITY_OVERWRITE, ABILITY_WANDERING_SPIRIT + pushtraitstack BS_TARGET ABILITY_WANDERING_SPIRIT @ Generates placeholder popup entry call BattleScript_AbilityPopUpOverwriteThenNormal copybyte gBattlerAbility, gBattlerAttacker copyhword sABILITY_OVERWRITE, gLastUsedAbility + pushtraitstack BS_ATTACKER ABILITY_WANDERING_SPIRIT @ Generates placeholder popup entry call BattleScript_AbilityPopUpOverwriteThenNormal recordability BS_TARGET recordability BS_ATTACKER @@ -7302,13 +7368,74 @@ BattleScript_TargetsStatWasMaxedOut:: BattleScript_TargetsStatWasMaxedOutRet: return -BattleScript_BattlerAbilityStatRaiseOnSwitchIn:: +BattleScript_BattlerAbilityStatRaiseOnSwitchInIntrepid:: + call BattleScript_AbilityPopUpScripting + setstatchanger STAT_ATK, 1, FALSE + statbuffchange BS_SCRIPTING, STAT_CHANGE_NOT_PROTECT_AFFECTED | STAT_CHANGE_CERTAIN, BattleScript_BattlerAbilityStatRaiseOnSwitchInIntrepidRet + waitanimation + printstring STRINGID_SCRIPTINGABILITYSTATRAISE + waitmessage B_WAIT_TIME_LONG +BattleScript_BattlerAbilityStatRaiseOnSwitchInIntrepidRet: + end3 + +BattleScript_BattlerAbilityStatRaiseOnSwitchInDauntless:: call BattleScript_AbilityPopUpScripting - statbuffchange BS_SCRIPTING, STAT_CHANGE_NOT_PROTECT_AFFECTED | STAT_CHANGE_CERTAIN, BattleScript_BattlerAbilityStatRaiseOnSwitchInRet + setstatchanger STAT_DEF, 1, FALSE + statbuffchange BS_SCRIPTING, STAT_CHANGE_NOT_PROTECT_AFFECTED | STAT_CHANGE_CERTAIN, BattleScript_BattlerAbilityStatRaiseOnSwitchInDauntlessRet waitanimation printstring STRINGID_SCRIPTINGABILITYSTATRAISE waitmessage B_WAIT_TIME_LONG -BattleScript_BattlerAbilityStatRaiseOnSwitchInRet: +BattleScript_BattlerAbilityStatRaiseOnSwitchInDauntlessRet: + end3 + +BattleScript_BattlerAbilityStatRaiseOnSwitchInWindRider:: + call BattleScript_AbilityPopUpScripting + setstatchanger STAT_ATK, 1, FALSE + statbuffchange BS_SCRIPTING, STAT_CHANGE_NOT_PROTECT_AFFECTED | STAT_CHANGE_CERTAIN, BattleScript_BattlerAbilityStatRaiseOnSwitchInWindRiderRet + waitanimation + printstring STRINGID_SCRIPTINGABILITYSTATRAISE + waitmessage B_WAIT_TIME_LONG +BattleScript_BattlerAbilityStatRaiseOnSwitchInWindRiderRet: + end3 + +BattleScript_BattlerAbilityStatRaiseOnSwitchInEmbodyAspectTeal:: + call BattleScript_AbilityPopUpScripting + setstatchanger STAT_SPEED, 1, FALSE + statbuffchange BS_SCRIPTING, STAT_CHANGE_NOT_PROTECT_AFFECTED | STAT_CHANGE_CERTAIN, BattleScript_BattlerAbilityStatRaiseOnSwitchInEmbodyAspectTealRet + waitanimation + printstring STRINGID_SCRIPTINGABILITYSTATRAISE + waitmessage B_WAIT_TIME_LONG +BattleScript_BattlerAbilityStatRaiseOnSwitchInEmbodyAspectTealRet: + end3 + +BattleScript_BattlerAbilityStatRaiseOnSwitchInEmbodyAspectHearthFlame:: + call BattleScript_AbilityPopUpScripting + setstatchanger STAT_ATK, 1, FALSE + statbuffchange BS_SCRIPTING, STAT_CHANGE_NOT_PROTECT_AFFECTED | STAT_CHANGE_CERTAIN, BattleScript_BattlerAbilityStatRaiseOnSwitchInEmbodyAspectHearthFlameRet + waitanimation + printstring STRINGID_SCRIPTINGABILITYSTATRAISE + waitmessage B_WAIT_TIME_LONG +BattleScript_BattlerAbilityStatRaiseOnSwitchInEmbodyAspectHearthFlameRet: + end3 + +BattleScript_BattlerAbilityStatRaiseOnSwitchInEmbodyAspectWellSpring:: + call BattleScript_AbilityPopUpScripting + setstatchanger STAT_SPDEF, 1, FALSE + statbuffchange BS_SCRIPTING, STAT_CHANGE_NOT_PROTECT_AFFECTED | STAT_CHANGE_CERTAIN, BattleScript_BattlerAbilityStatRaiseOnSwitchInEmbodyAspectWellSpringRet + waitanimation + printstring STRINGID_SCRIPTINGABILITYSTATRAISE + waitmessage B_WAIT_TIME_LONG +BattleScript_BattlerAbilityStatRaiseOnSwitchInEmbodyAspectWellSpringRet: + end3 + +BattleScript_BattlerAbilityStatRaiseOnSwitchInEmbodyAspectCornerStone:: + call BattleScript_AbilityPopUpScripting + setstatchanger STAT_DEF, 1, FALSE + statbuffchange BS_SCRIPTING, STAT_CHANGE_NOT_PROTECT_AFFECTED | STAT_CHANGE_CERTAIN, BattleScript_BattlerAbilityStatRaiseOnSwitchInEmbodyAspectCornerStoneRet + waitanimation + printstring STRINGID_SCRIPTINGABILITYSTATRAISE + waitmessage B_WAIT_TIME_LONG +BattleScript_BattlerAbilityStatRaiseOnSwitchInEmbodyAspectCornerStoneRet: end3 BattleScript_ScriptingAbilityStatRaise:: @@ -7358,16 +7485,56 @@ BattleScript_WeakArmorSpeedPrintString: BattleScript_WeakArmorActivatesEnd: return -BattleScript_RaiseStatOnFaintingTarget:: - statbuffchange BS_ATTACKER, STAT_CHANGE_ALLOW_PTR | STAT_CHANGE_ONLY_CHECKING, BattleScript_RaiseStatOnFaintingTarget_End +BattleScript_RaiseStatOnFaintingTargetMoxie:: + statbuffchange BS_ATTACKER, STAT_CHANGE_ALLOW_PTR | STAT_CHANGE_ONLY_CHECKING, BattleScript_RaiseStatOnFaintingTargetMoxie_End copybyte gBattlerAbility, gBattlerAttacker call BattleScript_AbilityPopUp - statbuffchange BS_ATTACKER, STAT_CHANGE_ALLOW_PTR, BattleScript_RaiseStatOnFaintingTarget_End + statbuffchange BS_ATTACKER, STAT_CHANGE_ALLOW_PTR, BattleScript_RaiseStatOnFaintingTargetMoxie_End printfromtable gStatUpStringIds waitmessage B_WAIT_TIME_LONG -BattleScript_RaiseStatOnFaintingTarget_End: +BattleScript_RaiseStatOnFaintingTargetMoxie_End: return +BattleScript_RaiseStatOnFaintingTargetChilling:: + statbuffchange BS_ATTACKER, STAT_CHANGE_ALLOW_PTR | STAT_CHANGE_ONLY_CHECKING, BattleScript_RaiseStatOnFaintingTargetChilling_End + copybyte gBattlerAbility, gBattlerAttacker + call BattleScript_AbilityPopUp + statbuffchange BS_ATTACKER, STAT_CHANGE_ALLOW_PTR, BattleScript_RaiseStatOnFaintingTargetChilling_End + printfromtable gStatUpStringIds + waitmessage B_WAIT_TIME_LONG +BattleScript_RaiseStatOnFaintingTargetChilling_End: + return + +BattleScript_RaiseStatOnFaintingTargetGrim:: + statbuffchange BS_ATTACKER, STAT_CHANGE_ALLOW_PTR | STAT_CHANGE_ONLY_CHECKING, BattleScript_RaiseStatOnFaintingTargetGrim_End + copybyte gBattlerAbility, gBattlerAttacker + call BattleScript_AbilityPopUp + statbuffchange BS_ATTACKER, STAT_CHANGE_ALLOW_PTR, BattleScript_RaiseStatOnFaintingTargetGrim_End + printfromtable gStatUpStringIds + waitmessage B_WAIT_TIME_LONG +BattleScript_RaiseStatOnFaintingTargetGrim_End: + return + +BattleScript_RaiseStatOnFaintingTargetBeastBoost:: + statbuffchange BS_ATTACKER, STAT_CHANGE_ALLOW_PTR | STAT_CHANGE_ONLY_CHECKING, BattleScript_RaiseStatOnFaintingTargetBeastBoost_End + copybyte gBattlerAbility, gBattlerAttacker + call BattleScript_AbilityPopUp + statbuffchange BS_ATTACKER, STAT_CHANGE_ALLOW_PTR, BattleScript_RaiseStatOnFaintingTargetBeastBoost_End + printfromtable gStatUpStringIds + waitmessage B_WAIT_TIME_LONG +BattleScript_RaiseStatOnFaintingTargetBeastBoost_End: + return + +BattleScript_AttackerDownloadStatRaise:: + statbuffchange BS_SCRIPTING, STAT_CHANGE_ALLOW_PTR | STAT_CHANGE_ONLY_CHECKING, BattleScript_AttackerDownloadStatRaise_End + call BattleScript_AbilityPopUpScripting + statbuffchange BS_SCRIPTING, STAT_CHANGE_ALLOW_PTR, BattleScript_AttackerDownloadStatRaise_End + printstring STRINGID_ATTACKERABILITYSTATRAISE + waitmessage B_WAIT_TIME_LONG +BattleScript_AttackerDownloadStatRaise_End: + restoreattacker + end3 + BattleScript_AttackerAbilityStatRaise:: statbuffchange BS_SCRIPTING, STAT_CHANGE_ALLOW_PTR | STAT_CHANGE_ONLY_CHECKING, BattleScript_AttackerAbilityStatRaise_End call BattleScript_AbilityPopUpScripting @@ -7407,15 +7574,26 @@ BattleScript_SwitchInAbilityMsgRet:: waitmessage B_WAIT_TIME_LONG return +BattleScript_NeutralizingGasActivates:: + call BattleScript_AbilityPopUp + printstring STRINGID_NEUTRALIZINGGASENTERS + waitmessage B_WAIT_TIME_LONG + end3 + +BattleScript_NeutralizingGasActivatesRet:: + call BattleScript_AbilityPopUp + printstring STRINGID_NEUTRALIZINGGASENTERS + waitmessage B_WAIT_TIME_LONG + return + BattleScript_ActivateAsOne:: call BattleScript_AbilityPopUp - printfromtable gSwitchInAbilityStringIds + printstring STRINGID_ASONEENTERS waitmessage B_WAIT_TIME_LONG @ show unnerve - sethword sABILITY_OVERWRITE, ABILITY_UNNERVE - setbyte cMULTISTRING_CHOOSER, B_MSG_SWITCHIN_UNNERVE + pushtraitstack BS_ABILITY_BATTLER ABILITY_UNNERVE call BattleScript_AbilityPopUp - printfromtable gSwitchInAbilityStringIds + printstring STRINGID_UNNERVEENTERS waitmessage B_WAIT_TIME_LONG end3 @@ -7447,17 +7625,114 @@ BattleScript_ImposterActivates:: restoretarget end3 -BattleScript_HurtAttacker: +BattleScript_MoldBreakerActivates:: + call BattleScript_AbilityPopUp + printstring STRINGID_MOLDBREAKERENTERS + waitmessage B_WAIT_TIME_LONG + end3 + +BattleScript_TeravoltActivates:: + call BattleScript_AbilityPopUp + printstring STRINGID_TERAVOLTENTERS + waitmessage B_WAIT_TIME_LONG + end3 + +BattleScript_TurboblazeActivates:: + call BattleScript_AbilityPopUp + printstring STRINGID_TURBOBLAZEENTERS + waitmessage B_WAIT_TIME_LONG + end3 + +BattleScript_SlowStartActivates:: + call BattleScript_AbilityPopUp + printstring STRINGID_SLOWSTARTENTERS + waitmessage B_WAIT_TIME_LONG + end3 + +BattleScript_UnnerveActivates:: + call BattleScript_AbilityPopUp + printstring STRINGID_UNNERVEENTERS + waitmessage B_WAIT_TIME_LONG + end3 + +BattleScript_ForewarnActivates:: + call BattleScript_AbilityPopUp + printstring STRINGID_FOREWARNACTIVATES + waitmessage B_WAIT_TIME_LONG + end3 + +BattleScript_PressureActivates:: + call BattleScript_AbilityPopUp + printstring STRINGID_PRESSUREENTERS + waitmessage B_WAIT_TIME_LONG + end3 + +BattleScript_DarkAuraActivates:: + call BattleScript_AbilityPopUp + printstring STRINGID_DARKAURAENTERS + waitmessage B_WAIT_TIME_LONG + end3 + +BattleScript_FairyAuraActivates:: + call BattleScript_AbilityPopUp + printstring STRINGID_FAIRYAURAENTERS + waitmessage B_WAIT_TIME_LONG + end3 + +BattleScript_AuraBreakActivates:: + call BattleScript_AbilityPopUp + printstring STRINGID_AURABREAKENTERS + waitmessage B_WAIT_TIME_LONG + end3 + +BattleScript_ComatoseActivates:: + call BattleScript_AbilityPopUp + printstring STRINGID_COMATOSEENTERS + waitmessage B_WAIT_TIME_LONG + end3 + +BattleScript_ScreenCleanerActivates:: + call BattleScript_AbilityPopUp + printstring STRINGID_SCREENCLEANERENTERS + waitmessage B_WAIT_TIME_LONG + end3 + +BattleScript_CuriousMedicineActivates:: + call BattleScript_AbilityPopUp + printstring STRINGID_CURIOUSMEDICINEENTERS + waitmessage B_WAIT_TIME_LONG + end3 + +BattleScript_PasteVeilActivates:: + call BattleScript_AbilityPopUp + printstring STRINGID_PASTELVEILENTERS + waitmessage B_WAIT_TIME_LONG + end3 + +BattleScript_HurtAttackerItem: + healthbarupdate BS_ATTACKER, PASSIVE_HP_UPDATE + datahpupdate BS_ATTACKER, PASSIVE_HP_UPDATE + printstring STRINGID_PKMNHURTSWITHITEM + waitmessage B_WAIT_TIME_LONG + tryfaintmon BS_ATTACKER + return + +BattleScript_HurtAttackerAbility: healthbarupdate BS_ATTACKER, PASSIVE_HP_UPDATE datahpupdate BS_ATTACKER, PASSIVE_HP_UPDATE - printstring STRINGID_PKMNHURTSWITH + printstring STRINGID_PKMNHURTSWITHABILITY waitmessage B_WAIT_TIME_LONG tryfaintmon BS_ATTACKER return BattleScript_RoughSkinActivates:: call BattleScript_AbilityPopUp - call BattleScript_HurtAttacker + call BattleScript_HurtAttackerAbility + return + +BattleScript_IronBarbsActivates:: + call BattleScript_AbilityPopUp + call BattleScript_HurtAttackerAbility return BattleScript_RockyHelmetActivates:: @@ -7466,7 +7741,7 @@ BattleScript_RockyHelmetActivates:: playanimation BS_TARGET, B_ANIM_HELD_ITEM_EFFECT waitanimation BattleScript_RockyHelmetActivatesDmg: - call BattleScript_HurtAttacker + call BattleScript_HurtAttackerItem return BattleScript_SpikyShieldEffect:: @@ -7474,7 +7749,7 @@ BattleScript_SpikyShieldEffect:: clearmoveresultflags MOVE_RESULT_NO_EFFECT healthbarupdate BS_ATTACKER, PASSIVE_HP_UPDATE datahpupdate BS_ATTACKER, PASSIVE_HP_UPDATE - printstring STRINGID_PKMNHURTSWITH + printstring STRINGID_PKMNHURTSWITHITEM waitmessage B_WAIT_TIME_LONG tryfaintmon BS_ATTACKER setmoveresultflags MOVE_RESULT_MISSED @@ -7514,8 +7789,26 @@ BattleScript_GooeyActivates:: BattleScript_GooeyActivatesRet: return -BattleScript_AbilityStatusEffect:: +BattleScript_TanglingHairActivates:: + statbuffchange BS_ATTACKER, STAT_CHANGE_ONLY_CHECKING, BattleScript_TanglingHairActivatesRet + waitstate + call BattleScript_AbilityPopUp + swapattackerwithtarget @ for defiant, mirror armor + seteffectsecondary BS_ATTACKER, BS_TARGET, MOVE_EFFECT_SPD_MINUS_1 + swapattackerwithtarget +BattleScript_TanglingHairActivatesRet: + return + +BattleScript_AbilityStatusEffectAtk:: + waitstate + copybyte gEffectBattler, gBattlerTarget + call BattleScript_AbilityPopUp + setnonvolatilestatus TRIGGER_ON_ABILITY + return + +BattleScript_AbilityStatusEffectDef:: waitstate + copybyte gEffectBattler, gBattlerAttacker call BattleScript_AbilityPopUp setnonvolatilestatus TRIGGER_ON_ABILITY return @@ -8186,7 +8479,7 @@ BattleScript_JabocaRowapBerryActivate_Anim: playanimation BS_TARGET, B_ANIM_HELD_ITEM_EFFECT waitanimation BattleScript_JabocaRowapBerryActivate_Dmg: - call BattleScript_HurtAttacker + call BattleScript_HurtAttackerItem removeitem BS_TARGET return @@ -8343,6 +8636,7 @@ BattleScript_RedCardIngrainContinue: restoreattacker return BattleScript_RedCardSuctionCups: + sethword gDisplayAbility, ABILITY_SUCTION_CUPS printstring STRINGID_PKMNANCHORSITSELFWITH goto BattleScript_RedCardIngrainContinue BattleScript_RedCardDynamaxed: @@ -8432,7 +8726,7 @@ BattleScript_PastelVeilCurePoison: call BattleScript_AbilityPopUp setbyte gBattleCommunication + 1, 1 BattleScript_PastelVeilCurePoisonNoPopUp: @ Only show Pastel Veil pop up once if it cures two mons - printfromtable gSwitchInAbilityStringIds + printstring STRINGID_PASTELVEILENTERS waitmessage B_WAIT_TIME_LONG curestatus BS_TARGET updatestatusicon BS_TARGET @@ -8477,13 +8771,68 @@ BattleScript_SymbiosisActivates:: waitmessage B_WAIT_TIME_LONG return -BattleScript_TargetAbilityStatRaiseRet:: +BattleScript_TargetAbilityStatRaiseRetJustified:: + setstatchanger STAT_ATK, 1, FALSE + call BattleScript_AbilityPopUp + statbuffchange BS_TARGET, STAT_CHANGE_CERTAIN, BattleScript_TargetAbilityStatRaiseRetJustified_End + call BattleScript_StatUp +BattleScript_TargetAbilityStatRaiseRetJustified_End: + return + +BattleScript_TargetAbilityStatRaiseRetRattled:: + setstatchanger STAT_SPEED, 1, FALSE + call BattleScript_AbilityPopUp + statbuffchange BS_TARGET, STAT_CHANGE_CERTAIN, BattleScript_TargetAbilityStatRaiseRetRattled_End + call BattleScript_StatUp +BattleScript_TargetAbilityStatRaiseRetRattled_End: + return + +BattleScript_TargetAbilityStatRaiseRetWaterCompaction:: + setstatchanger STAT_DEF, 2, FALSE + call BattleScript_AbilityPopUp + statbuffchange BS_TARGET, STAT_CHANGE_CERTAIN, BattleScript_TargetAbilityStatRaiseRetWaterCompaction_End + call BattleScript_StatUp +BattleScript_TargetAbilityStatRaiseRetWaterCompaction_End: + return + +BattleScript_TargetAbilityStatRaiseRetStamina:: + setstatchanger STAT_DEF, 1, FALSE + call BattleScript_AbilityPopUp + statbuffchange BS_TARGET, STAT_CHANGE_CERTAIN, BattleScript_TargetAbilityStatRaiseRetStamina_End + call BattleScript_StatUp +BattleScript_TargetAbilityStatRaiseRetStamina_End: + return + +BattleScript_TargetAbilityStatRaiseRetBerserk:: saveattacker copybyte gBattlerAttacker, gEffectBattler + setstatchanger STAT_SPATK, 1, FALSE + call BattleScript_AbilityPopUp + statbuffchange BS_ATTACKER, STAT_CHANGE_CERTAIN, BattleScript_TargetAbilityStatRaiseRetBerserk_End + call BattleScript_StatUp +BattleScript_TargetAbilityStatRaiseRetBerserk_End: + restoreattacker + return + +BattleScript_TargetAbilityStatRaiseRetSteam:: + saveattacker + copybyte gBattlerAttacker, gEffectBattler + setstatchanger STAT_SPEED, 6, FALSE + call BattleScript_AbilityPopUp + statbuffchange BS_ATTACKER, STAT_CHANGE_CERTAIN, BattleScript_TargetAbilityStatRaiseRetSteam_End + call BattleScript_StatUp +BattleScript_TargetAbilityStatRaiseRetSteam_End: + restoreattacker + return + +BattleScript_TargetAbilityStatRaiseRetThermal:: + saveattacker + copybyte gBattlerAttacker, gEffectBattler + setstatchanger STAT_ATK, 1, FALSE call BattleScript_AbilityPopUp - statbuffchange BS_ATTACKER, STAT_CHANGE_CERTAIN, BattleScript_TargetAbilityStatRaiseRet_End + statbuffchange BS_ATTACKER, STAT_CHANGE_CERTAIN, BattleScript_TargetAbilityStatRaiseRetThermal_End call BattleScript_StatUp -BattleScript_TargetAbilityStatRaiseRet_End: +BattleScript_TargetAbilityStatRaiseRetThermal_End: restoreattacker return diff --git a/graphics/pokemon/arcanine/hisui/back.png b/graphics/pokemon/arcanine/hisui/back.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/arcanine/hisui/front.png b/graphics/pokemon/arcanine/hisui/front.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/arcanine/hisui/normal.pal b/graphics/pokemon/arcanine/hisui/normal.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/arcanine/hisui/shiny.pal b/graphics/pokemon/arcanine/hisui/shiny.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/avalugg/hisui/back.png b/graphics/pokemon/avalugg/hisui/back.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/avalugg/hisui/front.png b/graphics/pokemon/avalugg/hisui/front.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/avalugg/hisui/normal.pal b/graphics/pokemon/avalugg/hisui/normal.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/avalugg/hisui/shiny.pal b/graphics/pokemon/avalugg/hisui/shiny.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/braviary/hisui/back.png b/graphics/pokemon/braviary/hisui/back.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/braviary/hisui/front.png b/graphics/pokemon/braviary/hisui/front.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/braviary/hisui/icon.png b/graphics/pokemon/braviary/hisui/icon.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/braviary/hisui/normal.pal b/graphics/pokemon/braviary/hisui/normal.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/braviary/hisui/shiny.pal b/graphics/pokemon/braviary/hisui/shiny.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/decidueye/hisui/back.png b/graphics/pokemon/decidueye/hisui/back.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/decidueye/hisui/front.png b/graphics/pokemon/decidueye/hisui/front.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/decidueye/hisui/icon.png b/graphics/pokemon/decidueye/hisui/icon.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/decidueye/hisui/normal.pal b/graphics/pokemon/decidueye/hisui/normal.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/decidueye/hisui/shiny.pal b/graphics/pokemon/decidueye/hisui/shiny.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/goodra/hisui/back.png b/graphics/pokemon/goodra/hisui/back.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/goodra/hisui/front.png b/graphics/pokemon/goodra/hisui/front.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/goodra/hisui/normal.pal b/graphics/pokemon/goodra/hisui/normal.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/goodra/hisui/shiny.pal b/graphics/pokemon/goodra/hisui/shiny.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/growlithe/hisui/back.png b/graphics/pokemon/growlithe/hisui/back.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/growlithe/hisui/front.png b/graphics/pokemon/growlithe/hisui/front.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/growlithe/hisui/normal.pal b/graphics/pokemon/growlithe/hisui/normal.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/growlithe/hisui/shiny.pal b/graphics/pokemon/growlithe/hisui/shiny.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/oinkologne/f/icon.png b/graphics/pokemon/oinkologne/f/icon.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/qwilfish/hisui/back.png b/graphics/pokemon/qwilfish/hisui/back.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/qwilfish/hisui/front.png b/graphics/pokemon/qwilfish/hisui/front.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/qwilfish/hisui/normal.pal b/graphics/pokemon/qwilfish/hisui/normal.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/qwilfish/hisui/shiny.pal b/graphics/pokemon/qwilfish/hisui/shiny.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/samurott/hisui/back.png b/graphics/pokemon/samurott/hisui/back.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/samurott/hisui/front.png b/graphics/pokemon/samurott/hisui/front.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/samurott/hisui/normal.pal b/graphics/pokemon/samurott/hisui/normal.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/samurott/hisui/shiny.pal b/graphics/pokemon/samurott/hisui/shiny.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/sliggoo/hisui/back.png b/graphics/pokemon/sliggoo/hisui/back.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/sliggoo/hisui/front.png b/graphics/pokemon/sliggoo/hisui/front.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/sliggoo/hisui/normal.pal b/graphics/pokemon/sliggoo/hisui/normal.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/sliggoo/hisui/shiny.pal b/graphics/pokemon/sliggoo/hisui/shiny.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/sneasel/hisui/back.png b/graphics/pokemon/sneasel/hisui/back.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/sneasel/hisui/front.png b/graphics/pokemon/sneasel/hisui/front.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/sneasel/hisui/normal.pal b/graphics/pokemon/sneasel/hisui/normal.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/sneasel/hisui/shiny.pal b/graphics/pokemon/sneasel/hisui/shiny.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/squawkabilly/blue/icon.png b/graphics/pokemon/squawkabilly/blue/icon.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/squawkabilly/white/icon.png b/graphics/pokemon/squawkabilly/white/icon.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/squawkabilly/yellow/icon.png b/graphics/pokemon/squawkabilly/yellow/icon.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/typhlosion/hisui/back.png b/graphics/pokemon/typhlosion/hisui/back.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/typhlosion/hisui/front.png b/graphics/pokemon/typhlosion/hisui/front.png old mode 100755 new mode 100644 diff --git a/graphics/pokemon/typhlosion/hisui/normal.pal b/graphics/pokemon/typhlosion/hisui/normal.pal old mode 100755 new mode 100644 diff --git a/graphics/pokemon/typhlosion/hisui/shiny.pal b/graphics/pokemon/typhlosion/hisui/shiny.pal old mode 100755 new mode 100644 diff --git a/graphics/summary_screen/effect_cancel.bin b/graphics/summary_screen/effect_cancel.bin index 0a19222a3292..2f2ed599a002 100644 Binary files a/graphics/summary_screen/effect_cancel.bin and b/graphics/summary_screen/effect_cancel.bin differ diff --git a/graphics/summary_screen/page_battle_moves.bin b/graphics/summary_screen/page_battle_moves.bin index f835047d19de..e5cb606e22d8 100644 Binary files a/graphics/summary_screen/page_battle_moves.bin and b/graphics/summary_screen/page_battle_moves.bin differ diff --git a/graphics/summary_screen/page_contest_moves.bin b/graphics/summary_screen/page_contest_moves.bin index 9c11ccc4f021..2551eed2c297 100644 Binary files a/graphics/summary_screen/page_contest_moves.bin and b/graphics/summary_screen/page_contest_moves.bin differ diff --git a/graphics/summary_screen/page_info.bin b/graphics/summary_screen/page_info.bin index 771d4db93206..f45d3a48aeb5 100644 Binary files a/graphics/summary_screen/page_info.bin and b/graphics/summary_screen/page_info.bin differ diff --git a/graphics/summary_screen/page_memos.bin b/graphics/summary_screen/page_memos.bin new file mode 100644 index 000000000000..7983bec3e234 Binary files /dev/null and b/graphics/summary_screen/page_memos.bin differ diff --git a/graphics/summary_screen/page_skills.bin b/graphics/summary_screen/page_skills.bin index 19cb94637637..a83571a5525f 100644 Binary files a/graphics/summary_screen/page_skills.bin and b/graphics/summary_screen/page_skills.bin differ diff --git a/graphics/summary_screen/page_traits.bin b/graphics/summary_screen/page_traits.bin new file mode 100644 index 000000000000..656f422814f5 Binary files /dev/null and b/graphics/summary_screen/page_traits.bin differ diff --git a/graphics/summary_screen/tiles.png b/graphics/summary_screen/tiles.png index 915742f1a7c3..6bd69c375e6c 100644 Binary files a/graphics/summary_screen/tiles.png and b/graphics/summary_screen/tiles.png differ diff --git a/include/battle.h b/include/battle.h old mode 100755 new mode 100644 index 6c528866cf06..a8af6f4c7c3c --- a/include/battle.h +++ b/include/battle.h @@ -101,6 +101,7 @@ struct DisableStruct u8 octolock:1; u8 cudChew:1; u8 weatherAbilityDone:1; + u8 transformWeatherAbilityDone:1; u8 terrainAbilityDone:1; u8 syrupBombIsShiny:1; u8 usedProteanLibero:1; @@ -158,6 +159,8 @@ struct ProtectStruct u16 specialDmg; u8 physicalBattlerId:4; u8 specialBattlerId:4; + u8 contraryDefiant; // 1 - Contrary + Defiant triggered (do not repeat). 0 - abilities not triggered together yet + u8 contraryCompetitive; // 1 - Contrary + Competitive triggered (do not repeat). 0 - abilities not triggered together yet }; // Cleared at the start of HandleAction_ActionFinished @@ -195,6 +198,8 @@ struct SpecialStatus u8 dancerOriginalTarget:3; u8 padding3:5; // End of byte + bool8 switchInTraitDone[MAX_MON_TRAITS]; + bool8 endTurnTraitDone[MAX_MON_TRAITS]; }; struct SideTimer @@ -295,6 +300,7 @@ struct SimulatedDamage struct AiLogicData { enum Ability abilities[MAX_BATTLERS_COUNT]; + enum Ability innates[MAX_BATTLERS_COUNT][MAX_MON_INNATES]; u16 items[MAX_BATTLERS_COUNT]; u16 holdEffects[MAX_BATTLERS_COUNT]; u8 holdEffectParams[MAX_BATTLERS_COUNT]; @@ -852,7 +858,8 @@ static inline bool32 IsBattleMoveStatus(u32 move) #define SET_STAT_BUFF_VALUE(n) ((((n) << 3) & 0xF8)) #define SET_STATCHANGER(statId, stage, goesDown) (gBattleScripting.statChanger = (statId) + ((stage) << 3) + (goesDown << 7)) -#define SET_STATCHANGER2(dst, statId, stage, goesDown)(dst = (statId) + ((stage) << 3) + (goesDown << 7)) +#define SET_STATCHANGER2(dst, statId, stage, goesDown)(dst = (statId) + ((stage) << 3) + (goesDown << 7)) // Moody +#define SET_STATCHANGER3(dst, statId, stage, goesDown)(dst = (statId) + ((stage) << 3) + (goesDown << 7)) // Speed Boost // NOTE: The members of this struct have hard-coded offsets // in include/constants/battle_script_commands.h @@ -1037,10 +1044,15 @@ extern u16 gCalledMove; extern s32 gBideDmg[MAX_BATTLERS_COUNT]; extern u16 gLastUsedItem; extern enum Ability gLastUsedAbility; +extern enum Ability gDisplayAbility; +extern enum Ability gDisplayAbility2; +extern u8 gDisplayBattler; +extern enum Ability gTraitStack[MAX_BATTLERS_COUNT * MAX_MON_TRAITS][2]; extern u8 gBattlerAttacker; extern u8 gBattlerTarget; extern u8 gBattlerFainted; extern u8 gEffectBattler; +extern u8 gEffectBattler2; extern u8 gPotentialItemEffectBattler; extern u8 gAbsentBattlerFlags; extern u8 gMultiHitCounter; diff --git a/include/battle_ai_main.h b/include/battle_ai_main.h index 4330f9d2bca0..b69871b4acb3 100644 --- a/include/battle_ai_main.h +++ b/include/battle_ai_main.h @@ -122,6 +122,22 @@ enum MoveComparisonResult return score; \ } +#define AI_BATTLER_HAS_TRAIT(battlerID, abilityToCheck) (gAiLogicData->abilities[battlerID] == abilityToCheck || BattlerHasInnate(battlerID, abilityToCheck)) //Useful to make calculations faster, used only for AI stuff + +#define AI_STORE_BATTLER_TRAITS(battlerID) \ +({for (int traitLoop = 0; traitLoop < MAX_MON_TRAITS; traitLoop++)\ +{if(traitLoop == 0){AIBattlerTraits[traitLoop] = gAiLogicData->abilities[battlerID];}else{AIBattlerTraits[traitLoop] = GetBattlerTrait(battlerID, traitLoop, FALSE);}}}) + +static inline u32 AISearchTraits(u16 *AIBattlerTraits, u32 abilityToCheck) +{ + for (u32 i = 0; i < MAX_MON_TRAITS; i++) + { + if (AIBattlerTraits[i] == abilityToCheck) + return i + 1; + } + return 0; +} + void BattleAI_SetupItems(void); void BattleAI_SetupFlags(void); void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler); diff --git a/include/battle_ai_switch_items.h b/include/battle_ai_switch_items.h index 7612d2084af7..aa0f80078fc0 100644 --- a/include/battle_ai_switch_items.h +++ b/include/battle_ai_switch_items.h @@ -46,7 +46,7 @@ void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId); void AI_TrySwitchOrUseItem(u32 battler); u32 GetMostSuitableMonToSwitchInto(u32 battler, enum SwitchType switchType); bool32 ShouldSwitch(u32 battler); -bool32 IsMonGrounded(enum HoldEffect heldItemEffect, enum Ability ability, enum Type type1, enum Type type2); +bool32 IsMonGrounded(enum HoldEffect heldItemEffect, enum Ability ability, enum Type type1, enum Type type2, u16 species); void ModifySwitchAfterMoveScoring(u32 battler); #endif // GUARD_BATTLE_AI_SWITCH_ITEMS_H diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index 2c8760893560..dd5e1cb78fa1 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -9,6 +9,10 @@ #define MIN_ROLL_PERCENTAGE DMG_ROLL_PERCENT_LO #define DMG_ROLL_PERCENTAGE ((MAX_ROLL_PERCENTAGE + MIN_ROLL_PERCENTAGE + 1) / 2) // Controls the damage roll the AI sees for the default roll. By default the 9th roll is seen +#define BATTLER_NONE 0 +#define BATTLER_ABILITY 1 +#define BATTLER_INNATE 2 + enum DamageRollType { DMG_ROLL_LOWEST, @@ -111,7 +115,7 @@ bool32 CanTargetMoveFaintAi(u32 move, u32 battlerDef, u32 battlerAtk, u32 nHits) bool32 CanTargetFaintAiWithMod(u32 battlerDef, u32 battlerAtk, s32 hpMod, s32 dmgMod); enum Ability AI_DecideKnownAbilityForTurn(u32 battlerId); enum HoldEffect AI_DecideHoldEffectForTurn(u32 battlerId); -bool32 DoesBattlerIgnoreAbilityChecks(u32 battlerAtk, enum Ability atkAbility, u32 move); +bool32 DoesBattlerIgnoreAbilityChecks(u32 battlerAtk, u32 move); u32 AI_GetWeather(void); u32 AI_GetSwitchinWeather(struct BattlePokemon battleMon); enum WeatherState IsWeatherActive(u32 flags); @@ -120,23 +124,23 @@ bool32 CanIndexMoveFaintTarget(u32 battlerAtk, u32 battlerDef, u32 index, enum D bool32 HasDamagingMove(u32 battler); bool32 HasDamagingMoveOfType(u32 battler, enum Type type); u32 GetBattlerSecondaryDamage(u32 battlerId); -bool32 BattlerWillFaintFromWeather(u32 battler, enum Ability ability); -bool32 BattlerWillFaintFromSecondaryDamage(u32 battler, enum Ability ability); -bool32 ShouldTryOHKO(u32 battlerAtk, u32 battlerDef, enum Ability atkAbility, enum Ability defAbility, u32 move); +bool32 BattlerWillFaintFromWeather(u32 battler); +bool32 BattlerWillFaintFromSecondaryDamage(u32 battler); +bool32 ShouldTryOHKO(u32 battlerAtk, u32 battlerDef, u32 move); bool32 ShouldUseRecoilMove(u32 battlerAtk, u32 battlerDef, u32 recoilDmg, u32 moveIndex); bool32 ShouldAbsorb(u32 battlerAtk, u32 battlerDef, u32 move, s32 damage); bool32 ShouldRecover(u32 battlerAtk, u32 battlerDef, u32 move, u32 healPercent); bool32 ShouldSetScreen(u32 battlerAtk, u32 battlerDef, enum BattleMoveEffects moveEffect); bool32 ShouldCureStatus(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData); bool32 ShouldCureStatusWithItem(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData); -enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, enum Ability defAbility, u32 move, u32 moveIndex); +enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, u32 move, u32 moveIndex); bool32 IsRecycleEncouragedItem(u32 item); bool32 ShouldRestoreHpBerry(u32 battlerAtk, u32 item); bool32 IsStatBoostingBerry(u32 item); bool32 CanKnockOffItem(u32 battler, u32 item); bool32 IsAbilityOfRating(enum Ability ability, s8 rating); bool32 AI_IsAbilityOnSide(u32 battlerId, enum Ability ability); -bool32 AI_MoveMakesContact(enum Ability ability, enum HoldEffect holdEffect, u32 move); +bool32 AI_MoveMakesContact(enum HoldEffect holdEffect, u32 move, u32 battlerAtk); bool32 IsConsideringZMove(u32 battlerAtk, u32 battlerDef, u32 move); bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove); void SetAIUsingGimmick(u32 battler, enum AIConsiderGimmick use); @@ -147,7 +151,7 @@ bool32 CanEndureHit(u32 battler, u32 battlerTarget, u32 move); // stat stage checks bool32 AnyStatIsRaised(u32 battlerId); bool32 CanLowerStat(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData, enum Stat stat); -bool32 BattlerStatCanRise(u32 battler, enum Ability battlerAbility, enum Stat stat); +bool32 BattlerStatCanRise(u32 battler, enum Stat stat); bool32 AreBattlersStatsMaxed(u32 battler); u32 CountPositiveStatStages(u32 battlerId); u32 CountNegativeStatStages(u32 battlerId); @@ -157,7 +161,7 @@ bool32 Ai_IsPriorityBlocked(u32 battlerAtk, u32 battlerDef, u32 move, struct AiL bool32 MovesWithCategoryUnusable(u32 attacker, u32 target, enum DamageCategory category); enum MoveComparisonResult AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef, s32 noOfHitsToKo); struct SimulatedDamage AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, uq4_12_t *typeEffectiveness, enum AIConsiderGimmick considerGimmickAtk, enum AIConsiderGimmick considerGimmickDef); -bool32 IsAdditionalEffectBlocked(u32 battlerAtk, u32 abilityAtk, u32 battlerDef, u32 abilityDef); +bool32 IsAdditionalEffectBlocked(u32 battlerAtk, u32 battlerDef); struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, uq4_12_t *typeEffectiveness, enum AIConsiderGimmick considerGimmickAtk, enum AIConsiderGimmick considerGimmickDef, u32 weather); bool32 AI_IsDamagedByRecoil(u32 battler); u32 GetNoOfHitsToKO(u32 dmg, s32 hp); @@ -189,7 +193,7 @@ bool32 HasMoveWithLowAccuracy(u32 battlerAtk, u32 battlerDef, u32 accCheck, bool bool32 HasAnyKnownMove(u32 battlerId); bool32 IsAromaVeilProtectedEffect(enum BattleMoveEffects moveEffect); bool32 IsNonVolatileStatusMove(u32 moveEffect); -bool32 IsMoveRedirectionPrevented(u32 battlerAtk, u32 move, enum Ability atkAbility); +bool32 IsMoveRedirectionPrevented(u32 battlerAtk, u32 move); bool32 IsHazardMove(u32 move); bool32 IsTwoTurnNotSemiInvulnerableMove(u32 battlerAtk, u32 move); bool32 IsBattlerDamagedByStatus(u32 battler); @@ -220,29 +224,29 @@ bool32 IsHazardClearingMove(u32 move); bool32 IsSubstituteEffect(enum BattleMoveEffects effect); // status checks -bool32 AI_CanBeConfused(u32 battlerAtk, u32 battlerDef, u32 move, enum Ability ability); -bool32 IsBattlerIncapacitated(u32 battler, enum Ability ability); -bool32 AI_CanPutToSleep(u32 battlerAtk, u32 battlerDef, enum Ability defAbility, u32 move, u32 partnerMove); +bool32 AI_CanBeConfused(u32 battlerAtk, u32 battlerDef, u32 move); +bool32 IsBattlerIncapacitated(u32 battler); +bool32 AI_CanPutToSleep(u32 battlerAtk, u32 battlerDef, u32 move, u32 partnerMove); bool32 ShouldPoison(u32 battlerAtk, u32 battlerDef); -bool32 AI_CanPoison(u32 battlerAtk, u32 battlerDef, enum Ability defAbility, u32 move, u32 partnerMove); -bool32 AI_CanParalyze(u32 battlerAtk, u32 battlerDef, enum Ability defAbility, u32 move, u32 partnerMove); -bool32 AI_CanConfuse(u32 battlerAtk, u32 battlerDef, enum Ability defAbility, u32 battlerAtkPartner, u32 move, u32 partnerMove); -bool32 ShouldBurn(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef); -bool32 ShouldFreezeOrFrostbite(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef); -bool32 ShouldParalyze(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef); -bool32 AI_CanBurn(u32 battlerAtk, u32 battlerDef, enum Ability defAbility, u32 battlerAtkPartner, u32 move, u32 partnerMove); -bool32 AI_CanGiveFrostbite(u32 battlerAtk, u32 battlerDef, enum Ability defAbility, u32 battlerAtkPartner, u32 move, u32 partnerMove); -bool32 AI_CanBeInfatuated(u32 battlerAtk, u32 battlerDef, enum Ability defAbility); +bool32 AI_CanPoison(u32 battlerAtk, u32 battlerDef, u32 move, u32 partnerMove); +bool32 AI_CanParalyze(u32 battlerAtk, u32 battlerDef, u32 move, u32 partnerMove); +bool32 AI_CanConfuse(u32 battlerAtk, u32 battlerDef, u32 battlerAtkPartner, u32 move, u32 partnerMove); +bool32 ShouldBurn(u32 battlerAtk, u32 battlerDef); +bool32 ShouldFreezeOrFrostbite(u32 battlerAtk, u32 battlerDef); +bool32 ShouldParalyze(u32 battlerAtk, u32 battlerDef); +bool32 AI_CanBurn(u32 battlerAtk, u32 battlerDef, u32 battlerAtkPartner, u32 move, u32 partnerMove); +bool32 AI_CanGiveFrostbite(u32 battlerAtk, u32 battlerDef, u32 battlerAtkPartner, u32 move, u32 partnerMove); +bool32 AI_CanBeInfatuated(u32 battlerAtk, u32 battlerDef); bool32 AnyPartyMemberStatused(u32 battlerId, bool32 checkSoundproof); -u32 ShouldTryToFlinch(u32 battlerAtk, u32 battlerDef, enum Ability atkAbility, enum Ability defAbility, u32 move); +u32 ShouldTryToFlinch(u32 battlerAtk, u32 battlerDef, u32 move); bool32 ShouldTrap(u32 battlerAtk, u32 battlerDef, u32 move); bool32 IsWakeupTurn(u32 battler); bool32 AI_IsBattlerAsleepOrComatose(u32 battlerId); // ability logic -bool32 IsMoxieTypeAbility(enum Ability ability); -bool32 DoesAbilityRaiseStatsWhenLowered(enum Ability ability); -bool32 ShouldTriggerAbility(u32 battlerAtk, u32 battlerDef, enum Ability ability); +bool32 HasMoxieTypeAbility(u32 battler); +bool32 DoesAbilityRaiseStatsWhenLowered(u32 battler); +bool32 ShouldTriggerAbility(u32 battlerAtk, u32 battlerDef); bool32 CanEffectChangeAbility(u32 battlerAtk, u32 battlerDef, u32 move, struct AiLogicData *aiData); void AbilityChangeScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score, struct AiLogicData *aiData); s32 BattlerBenefitsFromAbilityScore(u32 battler, enum Ability ability, struct AiLogicData *aiData); diff --git a/include/battle_main.h b/include/battle_main.h index c08da1d9caeb..ede041568d15 100644 --- a/include/battle_main.h +++ b/include/battle_main.h @@ -98,9 +98,9 @@ u8 IsRunningFromBattleImpossible(u32 battler); void SwitchTwoBattlersInParty(u32 battler, u32 battler2); void SwitchPartyOrder(u32 battler); void SwapTurnOrder(u8 id1, u8 id2); -u32 GetBattlerTotalSpeedStat(u32 battler, enum Ability ability, enum HoldEffect holdEffect); -s32 GetChosenMovePriority(u32 battler, enum Ability ability); -s32 GetBattleMovePriority(u32 battler, enum Ability ability, u32 move); +u32 GetBattlerTotalSpeedStat(u32 battler, enum HoldEffect holdEffect); +s32 GetChosenMovePriority(u32 battler); +s32 GetBattleMovePriority(u32 battler, u32 move); s32 GetWhichBattlerFasterArgs(struct BattleContext *ctx, bool32 ignoreChosenMoves, u32 speedBattler1, u32 speedBattler2, s32 priority1, s32 priority2); s32 GetWhichBattlerFasterOrTies(struct BattleContext *ctx, bool32 ignoreChosenMoves); s32 GetWhichBattlerFaster(struct BattleContext *ctx, bool32 ignoreChosenMoves); @@ -135,4 +135,14 @@ extern const u8 gStatusConditionString_LoveJpn[8]; extern const u8 *const gStatusConditionStringsTable[7][2]; +static inline u32 SearchTraits(u16 *battlerTraits, u32 abilityToCheck) +{ + for (u32 i = 0; i < MAX_MON_TRAITS; i++) + { + if (battlerTraits[i] == abilityToCheck) + return i + 1; + } + return 0; +} + #endif // GUARD_BATTLE_MAIN_H diff --git a/include/battle_message.h b/include/battle_message.h index 9809589243cc..b5f454f89464 100644 --- a/include/battle_message.h +++ b/include/battle_message.h @@ -87,6 +87,10 @@ #define B_TXT_ATK_TRAINER_NAME_WITH_CLASS 0x45 #define B_TXT_EFF_TEAM1 0x46 #define B_TXT_EFF_TEAM2 0x47 +#define B_TXT_EFF2_TEAM1 0x48 +#define B_TXT_EFF2_TEAM2 0x49 +#define B_TXT_DEF_ABILITY2 0x4A +#define B_TXT_ATK_PARTNER_NAME_WITH_PREFIX 0x4B #define B_BUFF_STRING 0 #define B_BUFF_NUMBER 1 diff --git a/include/battle_script_commands.h b/include/battle_script_commands.h index d011b509477d..37a8afee7aa4 100644 --- a/include/battle_script_commands.h +++ b/include/battle_script_commands.h @@ -40,8 +40,8 @@ union TRANSPARENT StatChangeFlags }; }; -s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, enum Ability abilityAtk, enum Ability abilityDef, enum HoldEffect holdEffectAtk); -s32 CalcCritChanceStageGen1(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, enum Ability abilityAtk, enum Ability abilityDef, enum HoldEffect holdEffectAtk); +s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, enum HoldEffect holdEffectAtk); +s32 CalcCritChanceStageGen1(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, enum HoldEffect holdEffectAtk); s32 GetCritHitOdds(s32 critChanceIndex); bool32 HasBattlerActedThisTurn(u32 battler); u32 GetBattlerTurnOrderNum(u32 battler); @@ -59,14 +59,14 @@ bool32 DoesSubstituteBlockMove(u32 battlerAtk, u32 battlerDef, u32 move); bool32 DoesDisguiseBlockMove(u32 battler, u32 move); bool32 CanUseLastResort(u8 battlerId); u32 IsFlowerVeilProtected(u32 battler); -u32 IsLeafGuardProtected(u32 battler, enum Ability ability); -bool32 IsShieldsDownProtected(u32 battler, enum Ability ability); -u32 IsAbilityStatusProtected(u32 battler, enum Ability ability); +u32 IsLeafGuardProtected(u32 battler); +bool32 IsShieldsDownProtected(u32 battler); +u32 IsAbilityStatusProtected(u32 battler); bool32 TryResetBattlerStatChanges(u8 battler); bool32 CanCamouflage(u8 battlerId); void StealTargetItem(u8 battlerStealer, u8 battlerItem); u8 GetCatchingBattler(void); -bool32 ProteanTryChangeType(u32 battler, enum Ability ability, u32 move, enum Type moveType); +bool32 ProteanTryChangeType(u32 battler, u32 move, enum Type moveType); bool32 IsMoveNotAllowedInSkyBattles(u32 move); bool32 DoSwitchInAbilities(u32 battlerId); u8 GetFirstFaintedPartyIndex(u8 battlerId); diff --git a/include/battle_scripts.h b/include/battle_scripts.h index e2f9f1636ca4..d2331646d394 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -194,8 +194,10 @@ extern const u8 BattleScript_StickyHoldActivates[]; extern const u8 BattleScript_StickyHoldActivatesRet[]; extern const u8 BattleScript_ColorChangeActivates[]; extern const u8 BattleScript_RoughSkinActivates[]; +extern const u8 BattleScript_IronBarbsActivates[]; extern const u8 BattleScript_CuteCharmActivates[]; -extern const u8 BattleScript_AbilityStatusEffect[]; +extern const u8 BattleScript_AbilityStatusEffectAtk[]; +extern const u8 BattleScript_AbilityStatusEffectDef[]; extern const u8 BattleScript_SynchronizeActivates[]; extern const u8 BattleScript_NoItemSteal[]; extern const u8 BattleScript_AbilityCuredStatus[]; @@ -273,6 +275,8 @@ extern const u8 BattleScript_PoisonHealActivates[]; extern const u8 BattleScript_BadDreamsActivates[]; extern const u8 BattleScript_SwitchInAbilityMsg[]; extern const u8 BattleScript_SwitchInAbilityMsgRet[]; +extern const u8 BattleScript_NeutralizingGasActivates[]; +extern const u8 BattleScript_NeutralizingGasActivatesRet[]; extern const u8 BattleScript_ToxicSpikesPoisoned[]; extern const u8 BattleScript_ToxicSpikesBadlyPoisoned[]; extern const u8 BattleScript_ToxicSpikesAbsorbed[]; @@ -280,6 +284,7 @@ extern const u8 BattleScript_StickyWebOnSwitchIn[]; extern const u8 BattleScript_SolarPowerActivates[]; extern const u8 BattleScript_CursedBodyActivates[]; extern const u8 BattleScript_MummyActivates[]; +extern const u8 BattleScript_MummyCalled[]; extern const u8 BattleScript_WeakArmorActivates[]; extern const u8 BattleScript_FellStingerRaisesStat[]; extern const u8 BattleScript_RemoveTerrain[]; @@ -288,6 +293,20 @@ extern const u8 BattleScript_SnowWarningActivatesSnow[]; extern const u8 BattleScript_PickupActivates[]; extern const u8 BattleScript_HarvestActivates[]; extern const u8 BattleScript_ImposterActivates[]; +extern const u8 BattleScript_MoldBreakerActivates[]; +extern const u8 BattleScript_TeravoltActivates[]; +extern const u8 BattleScript_TurboblazeActivates[]; +extern const u8 BattleScript_SlowStartActivates[]; +extern const u8 BattleScript_UnnerveActivates[]; +extern const u8 BattleScript_ForewarnActivates[]; +extern const u8 BattleScript_PressureActivates[]; +extern const u8 BattleScript_DarkAuraActivates[]; +extern const u8 BattleScript_FairyAuraActivates[]; +extern const u8 BattleScript_AuraBreakActivates[]; +extern const u8 BattleScript_ComatoseActivates[]; +extern const u8 BattleScript_ScreenCleanerActivates[]; +extern const u8 BattleScript_CuriousMedicineActivates[]; +extern const u8 BattleScript_PasteVeilActivates[]; extern const u8 BattleScript_SelectingNotAllowedMoveAssaultVest[]; extern const u8 BattleScript_SelectingNotAllowedMoveAssaultVestInPalace[]; extern const u8 BattleScript_SelectingNotAllowedPlaceholder[]; @@ -357,6 +376,7 @@ extern const u8 BattleScript_IllusionOffEnd3[]; extern const u8 BattleScript_IllusionOffAndTerastallization[]; extern const u8 BattleScript_DancerActivates[]; extern const u8 BattleScript_AftermathDmg[]; +extern const u8 BattleScript_InnardsOutDmg[]; extern const u8 BattleScript_BattlerFormChange[]; extern const u8 BattleScript_BattlerFormChangeEnd2[]; extern const u8 BattleScript_BattlerFormChangeEnd3[]; @@ -381,13 +401,23 @@ extern const u8 BattleScript_TotemVar[]; extern const u8 BattleScript_TotemFlaredToLife[]; extern const u8 BattleScript_AnnounceAirLockCloudNine[]; extern const u8 BattleScript_ActivateTeraformZero[]; -extern const u8 BattleScript_BattlerAbilityStatRaiseOnSwitchIn[]; +extern const u8 BattleScript_BattlerAbilityStatRaiseOnSwitchInIntrepid[]; +extern const u8 BattleScript_BattlerAbilityStatRaiseOnSwitchInDauntless[]; +extern const u8 BattleScript_BattlerAbilityStatRaiseOnSwitchInWindRider[]; +extern const u8 BattleScript_BattlerAbilityStatRaiseOnSwitchInEmbodyAspectTeal[]; +extern const u8 BattleScript_BattlerAbilityStatRaiseOnSwitchInEmbodyAspectHearthFlame[]; +extern const u8 BattleScript_BattlerAbilityStatRaiseOnSwitchInEmbodyAspectWellSpring[]; +extern const u8 BattleScript_BattlerAbilityStatRaiseOnSwitchInEmbodyAspectCornerStone[]; extern const u8 BattleScript_CottonDownActivates[]; extern const u8 BattleScript_BallFetch[]; extern const u8 BattleScript_SandSpitActivates[]; extern const u8 BattleScript_PerishBodyActivates[]; extern const u8 BattleScript_ActivateAsOne[]; -extern const u8 BattleScript_RaiseStatOnFaintingTarget[]; +extern const u8 BattleScript_RaiseStatOnFaintingTargetMoxie[]; +extern const u8 BattleScript_RaiseStatOnFaintingTargetChilling[]; +extern const u8 BattleScript_RaiseStatOnFaintingTargetGrim[]; +extern const u8 BattleScript_RaiseStatOnFaintingTargetBeastBoost[]; +extern const u8 BattleScript_AttackerDownloadStatRaise[]; extern const u8 BattleScript_QuickClawActivation[]; extern const u8 BattleScript_QuickDrawActivation[]; extern const u8 BattleScript_CustapBerryActivation[]; @@ -395,6 +425,7 @@ extern const u8 BattleScript_MicleBerryActivateEnd2[]; extern const u8 BattleScript_MicleBerryActivateRet[]; extern const u8 BattleScript_JabocaRowapBerryActivates[]; extern const u8 BattleScript_NotAffectedAbilityPopUp[]; +extern const u8 BattleScript_GenerateAbilityPopUp[]; extern const u8 BattleScript_BattlerShookOffTaunt[]; extern const u8 BattleScript_BattlerGotOverItsInfatuation[]; extern const u8 BattleScript_Pickpocket[]; @@ -434,6 +465,7 @@ extern const u8 BattleScript_SelectingNotAllowedMoveGorillaTacticsInPalace[]; extern const u8 BattleScript_WanderingSpiritActivates[]; extern const u8 BattleScript_MirrorArmorReflect[]; extern const u8 BattleScript_GooeyActivates[]; +extern const u8 BattleScript_TanglingHairActivates[]; extern const u8 BattleScript_PastelVeilActivates[]; extern const u8 BattleScript_BattlerFormChangeEnd3NoPopup[]; extern const u8 BattleScript_AttackerFormChangeMoveEffect[]; @@ -450,7 +482,13 @@ extern const u8 BattleScript_SymbiosisActivates[]; extern const u8 BattleScript_ScaleShot[]; extern const u8 BattleScript_MultiHitPrintStrings[]; extern const u8 BattleScript_RemoveFireType[]; -extern const u8 BattleScript_TargetAbilityStatRaiseRet[]; +extern const u8 BattleScript_TargetAbilityStatRaiseRetJustified[]; +extern const u8 BattleScript_TargetAbilityStatRaiseRetRattled[]; +extern const u8 BattleScript_TargetAbilityStatRaiseRetWaterCompaction[]; +extern const u8 BattleScript_TargetAbilityStatRaiseRetStamina[]; +extern const u8 BattleScript_TargetAbilityStatRaiseRetBerserk[]; +extern const u8 BattleScript_TargetAbilityStatRaiseRetSteam[]; +extern const u8 BattleScript_TargetAbilityStatRaiseRetThermal[]; extern const u8 BattleScript_RemoveElectricType[]; extern const u8 BattleScript_SeedSowerActivates[]; extern const u8 BattleScript_BerserkActivates[]; @@ -458,10 +496,14 @@ extern const u8 BattleScript_AngerShellActivates[]; extern const u8 BattleScript_WellBakedBodyActivates[]; extern const u8 BattleScript_WindRiderActivatesMoveEnd[]; extern const u8 BattleScript_WindPowerActivates[]; +extern const u8 BattleScript_ElectromorphosisActivates[]; extern const u8 BattleScript_ProtosynthesisActivates[]; extern const u8 BattleScript_QuarkDriveActivates[]; extern const u8 BattleScript_GoodAsGoldActivates[]; -extern const u8 BattleScript_RuinAbilityActivates[]; +extern const u8 BattleScript_RuinAbilityActivatesVessel[]; +extern const u8 BattleScript_RuinAbilityActivatesSword[]; +extern const u8 BattleScript_RuinAbilityActivatesTablets[]; +extern const u8 BattleScript_RuinAbilityActivatesBeads[]; extern const u8 BattleScript_CudChewActivates[]; extern const u8 BattleScript_SupremeOverlordActivates[]; extern const u8 BattleScript_CostarActivates[]; diff --git a/include/battle_util.h b/include/battle_util.h index edd303f6df53..7eccaf1ed507 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -49,24 +49,35 @@ enum FieldEffectCases enum AbilityEffect { - ABILITYEFFECT_ON_SWITCHIN, - ABILITYEFFECT_ENDTURN, - ABILITYEFFECT_MOVE_END_ATTACKER, - ABILITYEFFECT_COLOR_CHANGE, // Color Change, Berserk, Anger Shell - ABILITYEFFECT_MOVE_END, - ABILITYEFFECT_IMMUNITY, + ABILITYEFFECT_ON_SWITCHIN, // Activates all available abilities (Multi) + ABILITYEFFECT_ENDTURN, // Loops until all available abilities have activated one by one (Multi) + ABILITYEFFECT_MOVE_END_ATTACKER, // Only activates one ability (Multi) + ABILITYEFFECT_COLOR_CHANGE, // Activates all available abilities (Multi) + ABILITYEFFECT_MOVE_END, // Activates all available abilities (Multi) + ABILITYEFFECT_IMMUNITY, // Only activates one ability (Multi) ABILITYEFFECT_SYNCHRONIZE, ABILITYEFFECT_ATK_SYNCHRONIZE, ABILITYEFFECT_MOVE_END_OTHER, ABILITYEFFECT_NEUTRALIZINGGAS, ABILITYEFFECT_NEUTRALIZINGGAS_FIRST_TURN, - ABILITYEFFECT_ON_WEATHER, - ABILITYEFFECT_ON_TERRAIN, + ABILITYEFFECT_ON_WEATHER, // Only activates one ability (Multi) + ABILITYEFFECT_ON_TERRAIN, // Only activates one ability (Multi) ABILITYEFFECT_OPPORTUNIST, ABILITYEFFECT_OPPORTUNIST_FIRST_TURN, - ABILITYEFFECT_ON_SWITCHIN_IMMUNITIES, + ABILITYEFFECT_ON_SWITCHIN_IMMUNITIES, // Only activates one ability (Multi) }; +#define STORE_BATTLER_TRAITS(battler) \ +({for (int traitLoop = 0; traitLoop < MAX_MON_TRAITS; traitLoop++)\ +{battlerTraits[traitLoop] = GetBattlerTrait(battler, traitLoop, FALSE);\ +}}) +//DebugPrintf("%S - Battler[%d] - Trait[%d]: %S", GetSpeciesName(gBattleMons[battler].species), battler, traitLoop, gAbilitiesInfo[battlerTraits[traitLoop]].name);\ + +#define STORE_BATTLER_TRAITS_IGNORE_MOLDBREAKER(battler) \ +({for (int traitLoop = 0; traitLoop < MAX_MON_TRAITS; traitLoop++)\ +{battlerTraits[traitLoop] = GetBattlerTrait(battler, traitLoop, TRUE);\ +}}) + enum ItemEffect { ITEM_NO_EFFECT, @@ -239,7 +250,7 @@ void MarkBattlerReceivedLinkData(u32 battler); const u8 *CancelMultiTurnMoves(u32 battler, enum SkyDropState skyDropState); bool32 WasUnableToUseMove(u32 battler); bool32 IsLastMonToMove(u32 battler); -bool32 ShouldDefiantCompetitiveActivate(u32 battler, enum Ability ability); +bool32 ShouldDefiantCompetitiveActivate(u32 battler); void PrepareStringBattle(enum StringID stringId, u32 battler); void ResetSentPokesToOpponentValue(void); void OpponentSwitchInResetSentPokesToOpponentValue(u32 battler); @@ -253,21 +264,21 @@ u32 CheckMoveLimitations(u32 battler, u8 unusableMoves, u16 check); bool32 AreAllMovesUnusable(u32 battler); u8 GetImprisonedMovesCount(u32 battler, u16 move); s32 GetDrainedBigRootHp(u32 battler, s32 hp); -bool32 IsAbilityAndRecord(u32 battler, enum Ability battlerAbility, enum Ability abilityToCheck); +bool32 IsAbilityAndRecord(u32 battler, enum Ability abilityToCheck); u32 DoEndTurnEffects(void); bool32 HandleFaintedMonActions(void); void TryClearRageAndFuryCutter(void); enum MoveCanceler AtkCanceler_MoveSuccessOrder(struct BattleContext *ctx); bool32 HasNoMonsToSwitch(u32 battler, u8 partyIdBattlerOn1, u8 partyIdBattlerOn2); -bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, u32 ability); +bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, bool32 viaAbility); bool32 TryChangeBattleTerrain(u32 battler, u32 statusFlag); -bool32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef, u32 move, enum FunctionCallOption option); -bool32 CanAbilityAbsorbMove(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef, u32 move, enum Type moveType, enum FunctionCallOption option); +bool32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, u32 move, enum FunctionCallOption option); +bool32 CanAbilityAbsorbMove(u32 battlerAtk, u32 battlerDef, u32 move, enum Type moveType, enum FunctionCallOption option); bool32 TryFieldEffects(enum FieldEffectCases caseId); -u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ability, u32 special, u32 moveArg); +u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, u32 special, u32 moveArg); bool32 TryPrimalReversion(u32 battler); bool32 IsNeutralizingGasOnField(void); -bool32 IsMoldBreakerTypeAbility(u32 battler, enum Ability ability); +bool32 HasMoldBreakerTypeAbility(u32 battler); u32 GetBattlerAbilityIgnoreMoldBreaker(u32 battler); u32 GetBattlerAbilityNoAbilityShield(u32 battler); u32 GetBattlerAbilityInternal(u32 battler, u32 ignoreMoldBreaker, u32 noAbilityShield); @@ -290,11 +301,11 @@ u8 GetAttackerObedienceForAction(); enum HoldEffect GetBattlerHoldEffect(u32 battler); enum HoldEffect GetBattlerHoldEffectIgnoreAbility(u32 battler); enum HoldEffect GetBattlerHoldEffectIgnoreNegation(u32 battler); -enum HoldEffect GetBattlerHoldEffectInternal(u32 battler, u32 ability); +enum HoldEffect GetBattlerHoldEffectInternal(u32 battler, bool32 ignoreAbility); u32 GetBattlerHoldEffectParam(u32 battler); -bool32 CanBattlerAvoidContactEffects(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum HoldEffect holdEffectAtk, u32 move); -bool32 IsMoveMakingContact(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum HoldEffect holdEffectAtk, u32 move); -bool32 IsBattlerGrounded(u32 battler, enum Ability ability, enum HoldEffect holdEffect); +bool32 CanBattlerAvoidContactEffects(u32 battlerAtk, u32 battlerDef, enum HoldEffect holdEffectAtk, u32 move); +bool32 IsMoveMakingContact(u32 battlerAtk, u32 battlerDef, enum HoldEffect holdEffectAtk, u32 move); +bool32 IsBattlerGrounded(u32 battler, enum HoldEffect holdEffect); u32 GetMoveSlot(u16 *moves, u32 move); u32 GetBattlerWeight(u32 battler); u32 CalcRolloutBasePower(u32 battlerAtk, u32 basePower, u32 rolloutTimer); @@ -304,7 +315,7 @@ s32 CalculateMoveDamageVars(struct DamageContext *ctx); s32 DoFixedDamageMoveCalc(struct DamageContext *ctx); s32 ApplyModifiersAfterDmgRoll(struct DamageContext *ctx, s32 dmg); uq4_12_t CalcTypeEffectivenessMultiplier(struct DamageContext *ctx); -uq4_12_t CalcPartyMonTypeEffectivenessMultiplier(u16 move, u16 speciesDef, enum Ability abilityDef); +uq4_12_t CalcPartyMonTypeEffectivenessMultiplier(u16 move, u16 speciesDef, struct Pokemon *mon); uq4_12_t GetTypeModifier(enum Type atkType, enum Type defType); uq4_12_t GetOverworldTypeEffectiveness(struct Pokemon *mon, enum Type moveType); void UpdateMoveResultFlags(uq4_12_t modifier, u16 *resultFlags); @@ -338,19 +349,20 @@ bool32 CanFling(u32 battler); bool32 IsTelekinesisBannedSpecies(u16 species); bool32 IsHealBlockPreventingMove(u32 battler, u32 move); bool32 IsBelchPreventingMove(u32 battler, u32 move); -bool32 HasEnoughHpToEatBerry(u32 battler, enum Ability ability, u32 hpFraction, u32 itemId); +bool32 HasEnoughHpToEatBerry(u32 battler, u32 hpFraction, u32 itemId); bool32 IsPartnerMonFromSameTrainer(u32 battler); enum DamageCategory GetCategoryBasedOnStats(u32 battler); void SetShellSideArmCategory(void); bool32 MoveIsAffectedBySheerForce(u32 move); -bool32 IsSheerForceAffected(u16 move, enum Ability ability); +bool32 IsSheerForceAffected(u16 move, u32 battler); void TryRestoreHeldItems(void); bool32 CanStealItem(u32 battlerStealing, u32 battlerItem, u16 item); void TrySaveExchangedItem(u32 battler, u16 stolenItem); bool32 IsPartnerMonFromSameTrainer(u32 battler); bool32 IsBattlerAffectedByHazards(u32 battler, bool32 toxicSpikes); void SortBattlersBySpeed(u8 *battlers, bool32 slowToFast); -bool32 CompareStat(u32 battler, enum Stat statId, u8 cmpTo, u8 cmpKind, enum Ability ability); +bool32 CompareStat(u32 battler, enum Stat statId, u8 cmpTo, u8 cmpKind); +bool32 CompareStatIgnoreContrary(u32 battler, enum Stat statId, u8 cmpTo, u8 cmpKind); bool32 BlocksPrankster(u16 move, u32 battlerPrankster, u32 battlerDef, bool32 checkTarget); bool32 PickupHasValidTarget(u32 battler); bool32 CantPickupItem(u32 battler); @@ -370,20 +382,20 @@ bool32 CanTargetPartner(u32 battlerAtk, u32 battlerDef); bool32 TargetFullyImmuneToCurrMove(u32 battlerAtk, u32 battlerDef); bool32 MoodyCantRaiseStat(u32 stat); bool32 MoodyCantLowerStat(u32 stat); -bool32 IsBattlerTerrainAffected(u32 battler, enum Ability ability, enum HoldEffect holdEffect, u32 terrainFlag); +bool32 IsBattlerTerrainAffected(u32 battler, enum HoldEffect holdEffect, u32 terrainFlag); u32 GetHighestStatId(u32 battler); u32 GetParadoxHighestStatId(u32 battler); u32 GetParadoxBoostedStatId(u32 battler); -bool32 CanBeSlept(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef, enum SleepClauseBlock isBlockedBySleepClause); -bool32 CanBePoisoned(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef); -bool32 CanBeBurned(u32 battlerAtk, u32 battlerDef, enum Ability ability); -bool32 CanBeParalyzed(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef); -bool32 CanBeFrozen(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef); -bool32 CanGetFrostbite(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef); -bool32 CanSetNonVolatileStatus(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef, enum MoveEffect secondaryMoveEffect, enum FunctionCallOption option); +bool32 CanBeSlept(u32 battlerAtk, u32 battlerDef, enum SleepClauseBlock isBlockedBySleepClause); +bool32 CanBePoisoned(u32 battlerAtk, u32 battlerDef); +bool32 CanBeBurned(u32 battlerAtk, u32 battlerDef); +bool32 CanBeParalyzed(u32 battlerAtk, u32 battlerDef); +bool32 CanBeFrozen(u32 battlerAtk, u32 battlerDef); +bool32 CanGetFrostbite(u32 battlerAtk, u32 battlerDef); +bool32 CanSetNonVolatileStatus(u32 battlerAtk, u32 battlerDef, enum MoveEffect secondaryMoveEffect, enum FunctionCallOption option); bool32 CanBeConfused(u32 battler); -bool32 IsSafeguardProtected(u32 battlerAtk, u32 battlerDef, u32 abilityAtk); +bool32 IsSafeguardProtected(u32 battlerAtk, u32 battlerDef); u32 GetBattlerAffectionHearts(u32 battler); void TryToRevertMimicryAndFlags(void); bool32 BattleArenaTurnEnd(void); @@ -394,8 +406,8 @@ void RemoveConfusionStatus(u32 battler); u8 GetBattlerGender(u32 battler); bool32 AreBattlersOfOppositeGender(u32 battler1, u32 battler2); bool32 AreBattlersOfSameGender(u32 battler1, u32 battler2); -u32 CalcSecondaryEffectChance(u32 battler, enum Ability battlerAbility, const struct AdditionalEffect *additionalEffect); -bool32 MoveEffectIsGuaranteed(u32 battler, enum Ability battlerAbility, const struct AdditionalEffect *additionalEffect); +u32 CalcSecondaryEffectChance(u32 battler, const struct AdditionalEffect *additionalEffect); +bool32 MoveEffectIsGuaranteed(u32 battler, const struct AdditionalEffect *additionalEffect); void GetBattlerTypes(u32 battler, bool32 ignoreTera, enum Type types[static 3]); enum Type GetBattlerType(u32 battler, u32 typeIndex, bool32 ignoreTera); bool8 CanMonParticipateInSkyBattle(struct Pokemon *mon); @@ -408,7 +420,7 @@ bool32 IsSleepClauseActiveForSide(u32 battlerSide); bool32 IsSleepClauseEnabled(); void ClearDamageCalcResults(void); u32 DoesDestinyBondFail(u32 battler); -bool32 IsMoveEffectBlockedByTarget(enum Ability ability); +bool32 IsMoveEffectBlockedByTarget(void); bool32 IsPursuitTargetSet(void); void ClearPursuitValuesIfSet(u32 battler); void ClearPursuitValues(void); @@ -427,18 +439,27 @@ bool32 AreAnyHazardsOnSide(u32 side); void RemoveAllHazardsFromField(u32 side); bool32 IsHazardOnSideAndClear(u32 side, enum Hazards hazardType); void RemoveHazardFromField(u32 side, enum Hazards hazardType); -bool32 CanMoveSkipAccuracyCalc(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef, u32 move, enum FunctionCallOption option); -u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, enum Ability atkAbility, enum Ability defAbility, enum HoldEffect atkHoldEffect, enum HoldEffect defHoldEffect); +bool32 CanMoveSkipAccuracyCalc(u32 battlerAtk, u32 battlerDef, u32 move, enum FunctionCallOption option); +u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, enum HoldEffect atkHoldEffect, enum HoldEffect defHoldEffect); bool32 IsSemiInvulnerable(u32 battler, enum SemiInvulnerableExclusion excludeCommander); bool32 BreaksThroughSemiInvulnerablity(u32 battler, u32 move); bool32 HasPartnerTrainer(u32 battler); -bool32 IsAffectedByPowderMove(u32 battler, u32 ability, enum HoldEffect holdEffect); +bool32 IsAffectedByPowderMove(u32 battler, enum HoldEffect holdEffect); u32 GetNaturePowerMove(u32 battler); u32 GetNaturePowerMove(u32 battler); void RemoveAbilityFlags(u32 battler); -bool32 IsDazzlingAbility(enum Ability ability); +bool32 HasDazzlingAbility(u32 battler); bool32 IsAllowedToUseBag(void); bool32 IsAnyTargetTurnDamaged(u32 battlerAtk); bool32 IsMimikyuDisguised(u32 battler); +enum Ability GetBattlerTrait(u8 battler, u8 traitNum, u32 ignoreMoldBreaker); +u8 BattlerHasInnate(u8 battlerId, enum Ability ability); +u8 BattlerHasTrait(u8 battlerId, enum Ability ability); //Returns the trait slot number of the given ability. Starts at 1 for the primary Ability and returns 0 if the ability is not found. +u8 BattlerHasTraitPlain(u8 battlerId, enum Ability ability); //BattlerHasTrait for functions already under GetBattlerAbility to avoid infinite loops. +void PushTraitStack(u8 battlerId, enum Ability ability); //Pushes an ability to the trait stack +u8 PullTraitStackBattler(void); //Pulls a battler from the trait stack +enum Ability PullTraitStackAbility(void); //Pulls a battler from the trait stack +void PopTraitStack(void); //Pops an ability from the trait stack and clears the slot + #endif // GUARD_BATTLE_UTIL_H diff --git a/include/config/test.h b/include/config/test.h index 46dbe17a997d..358ad0b04a73 100644 --- a/include/config/test.h +++ b/include/config/test.h @@ -1149,4 +1149,7 @@ // Move animation testing #define T_SHOULD_RUN_MOVE_ANIM FALSE // If TRUE, enables the move animation tests, these are very computationally heavy and takes a long time to run. +// Innate testing +#define T_SHOULD_TEST_DEFAULT_INNATES FALSE // If TRUE, tests will use a pokemon's default Innates whenever one is not specified in the test. Not recommended unless you've heavily adjusted tests to accomodate. + #endif // GUARD_CONFIG_TEST_H diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 78745aad8f06..7f4d6e13c0ab 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -291,7 +291,7 @@ enum BattleScriptOpcode #define sSPECIAL_TRAINER_BATTLE_TYPE (gBattleScripting + 0x26) // specialTrainerBattleType #define sMON_CAUGHT (gBattleScripting + 0x27) // monCaught #define sSAVED_DMG (gBattleScripting + 0x28) // savedDmg -#define sUNUSED_0x2C (gBattleScripting + 0x2C) // unused_0x2c +#define sUNUSED_0x2C (gBattleScripting + 0x2C) // unused_0x2c #define sMOVE_EFFECT (gBattleScripting + 0x2E) // moveEffect #define sUNUSED_0x30 (gBattleScripting + 0x30) // unused_0x30 #define sILLUSION_NICK_HACK (gBattleScripting + 0x32) // illusionNickHack @@ -335,6 +335,7 @@ enum BattleScriptOpcode #define BS_OPPONENT2 14 #define BS_ABILITY_BATTLER 15 #define BS_ATTACKER_PARTNER 16 +#define BS_SCRIPTING_PARTNER 17 // for BattleScript_HealerActivates // Cmd_accuracycheck #define NO_ACC_CALC_CHECK_LOCK_ON 0xFFFF diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index 462c64cda199..c61c97e97a8a 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -26,6 +26,7 @@ enum StringID STRINGID_ATTACKMISSED, STRINGID_PKMNPROTECTEDITSELF, STRINGID_STATSWONTINCREASE2, + STRINGID_STATSWONTINCREASECONTRARY2, STRINGID_AVOIDEDDAMAGE, STRINGID_ITDOESNTAFFECT, STRINGID_BATTLERFAINTED, @@ -199,7 +200,9 @@ enum StringID STRINGID_PKMNANCHORSITSELFWITH, STRINGID_PKMNCUTSATTACKWITH, STRINGID_PKMNPREVENTSSTATLOSSWITH, - STRINGID_PKMNHURTSWITH, + STRINGID_PKMNINTIMIDATECONTRARYRATTLED, + STRINGID_PKMNHURTSWITHITEM, + STRINGID_PKMNHURTSWITHABILITY, STRINGID_PKMNTRACED, STRINGID_STATSHARPLY, STRINGID_STATROSE, @@ -336,6 +339,7 @@ enum StringID STRINGID_PKMNSXPREVENTSFLINCHING, STRINGID_PKMNALREADYHASBURN, STRINGID_STATSWONTDECREASE2, + STRINGID_STATSWONTDECREASECONTRARY2, STRINGID_PKMNSXBLOCKSY2, STRINGID_PKMNSXWOREOFF, STRINGID_THEWALLSHATTERED, @@ -608,7 +612,10 @@ enum StringID STRINGID_SUNLIGHTACTIVATEDABILITY, STRINGID_STATWASHEIGHTENED, STRINGID_ELECTRICTERRAINACTIVATEDABILITY, - STRINGID_ABILITYWEAKENEDSURROUNDINGMONSSTAT, + STRINGID_ABILITYWEAKENEDSURROUNDINGMONSSPATK, + STRINGID_ABILITYWEAKENEDSURROUNDINGMONSDEF, + STRINGID_ABILITYWEAKENEDSURROUNDINGMONSATK, + STRINGID_ABILITYWEAKENEDSURROUNDINGMONSSPDEF, STRINGID_ATTACKERGAINEDSTRENGTHFROMTHEFALLEN, STRINGID_PKMNSABILITYPREVENTSABILITY, STRINGID_PREPARESHELLTRAP, diff --git a/include/constants/global.h b/include/constants/global.h index fa0ebbcc0fb2..a877b63777f5 100644 --- a/include/constants/global.h +++ b/include/constants/global.h @@ -115,6 +115,9 @@ #define TRAINER_ID_LENGTH 4 #define MAX_MON_MOVES 4 #define ALL_MOVES_MASK ((1 << MAX_MON_MOVES) - 1) +#define MAX_MON_INNATES 3 // The max number of Innates that are enabled in gameplay +#define MAX_MON_INNATES_INTERNAL 3 // The max number of Innates that a pokemon can have in the species definition lists. +#define MAX_MON_TRAITS (MAX_MON_INNATES + 1) // The max number of Innates with Abilities included #define CONTESTANT_COUNT 4 #define CONTEST_CATEGORY_COOL 0 @@ -198,6 +201,14 @@ #define CONNECTION_DIVE 5 #define CONNECTION_EMERGE 6 +//Traits +#define RECYCLE_ITEM_RECYCLE 1 +#define RECYCLE_ITEM_PICKUP 2 +#define RECYCLE_ITEM_HARVEST 3 +#define TRIGGER_HARVEST 1 +#define TRIGGER_PICKUP 2 +#define TRIGGER_BALL_FETCH 3 + #if TESTING #include "config/test.h" #endif diff --git a/include/graphics.h b/include/graphics.h index 8ee6cfb667bc..e827dadb3daf 100644 --- a/include/graphics.h +++ b/include/graphics.h @@ -3343,4 +3343,7 @@ extern const u32 gBattleIcons_Gfx2[]; extern const u16 gBattleIcons_Pal1[]; extern const u16 gBattleIcons_Pal2[]; +//New Summary Pages +extern const u32 gSummaryPage_Traits_Tilemap[]; + #endif //GUARD_GRAPHICS_H diff --git a/include/malloc.h b/include/malloc.h index b3f176e33040..16dd3ef59906 100644 --- a/include/malloc.h +++ b/include/malloc.h @@ -41,7 +41,7 @@ struct MemBlock u8 data[0]; }; -#define HEAP_SIZE 0x1C300 +#define HEAP_SIZE 0x1D000 extern u8 gHeap[HEAP_SIZE]; #if TESTING || !defined(NDEBUG) diff --git a/include/pokemon.h b/include/pokemon.h index 9ca274330085..85097ca01139 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -126,6 +126,9 @@ enum MonData { MON_DATA_GIGANTAMAX_FACTOR, MON_DATA_TERA_TYPE, MON_DATA_EVOLUTION_TRACKER, + MON_DATA_INNATE1, + MON_DATA_INNATE2, + MON_DATA_INNATE3, }; struct PokemonSubstruct0 @@ -391,6 +394,7 @@ struct BattlePokemon /*0x5D*/ u32 otId; /*0x61*/ u8 metLevel; /*0x62*/ bool8 isShiny; + /*0x64*/ enum Ability innates[MAX_MON_INNATES_INTERNAL]; }; struct EvolutionParam @@ -437,6 +441,7 @@ struct SpeciesInfo /*0xC4*/ u8 eggGroups[2]; enum Ability abilities[NUM_ABILITY_SLOTS]; // 3 abilities, no longer u8 because we have over 255 abilities now. u8 safariZoneFleeRate; + enum Ability innates[MAX_MON_INNATES_INTERNAL]; // Pokédex data u8 categoryName[13]; @@ -919,4 +924,8 @@ u8 *GetSavedPlayerPartyCount(void); void SavePlayerPartyMon(u32 index, struct Pokemon *mon); bool32 IsSpeciesOfType(u32 species, enum Type type); +u8 SpeciesHasInnate(u16 species, u16 ability); +enum Ability GetSpeciesInnate(u16 species, u8 traitNum); +bool8 BoxMonHasInnate(struct BoxPokemon* boxmon, u16 ability); +bool8 MonHasTrait(struct Pokemon* mon, u16 ability); #endif // GUARD_POKEMON_H diff --git a/include/pokemon_summary_screen.h b/include/pokemon_summary_screen.h index 6c4128dba416..02508bdcaaad 100755 --- a/include/pokemon_summary_screen.h +++ b/include/pokemon_summary_screen.h @@ -37,6 +37,7 @@ enum PokemonSummaryScreenMode enum PokemonSummaryScreenPage { PSS_PAGE_INFO, + PSS_PAGE_TRAITS, PSS_PAGE_SKILLS, PSS_PAGE_BATTLE_MOVES, PSS_PAGE_CONTEST_MOVES, diff --git a/include/strings.h b/include/strings.h index a9264b69fda8..1b689cfbe059 100644 --- a/include/strings.h +++ b/include/strings.h @@ -2415,4 +2415,7 @@ extern const u8 gText_CannotSendMonToBoxHM[]; extern const u8 gText_CannotSendMonToBoxActive[]; extern const u8 gText_CannotSendMonToBoxPartner[]; +//New Summary Pages +extern const u8 gText_PkmnTraits[]; + #endif // GUARD_STRINGS_H diff --git a/include/test/battle.h b/include/test/battle.h index 7c4d285f6fb2..adcc2b0f8f5e 100644 --- a/include/test/battle.h +++ b/include/test/battle.h @@ -751,6 +751,7 @@ struct BattleTestData u8 nature; bool8 isShiny; enum Ability forcedAbilities[MAX_BATTLERS_COUNT][PARTY_SIZE]; + enum Ability forcedInnates[MAX_BATTLERS_COUNT][PARTY_SIZE][MAX_MON_INNATES]; u8 chosenGimmick[NUM_BATTLE_SIDES][PARTY_SIZE]; u8 forcedEnvironment; @@ -982,6 +983,7 @@ struct moveWithPP { #define Gender(gender) Gender_(__LINE__, gender) #define Nature(nature) Nature_(__LINE__, nature) #define Ability(ability) Ability_(__LINE__, ability) +#define Innates(innate1, ... ) do { enum Ability innates_[MAX_MON_INNATES] = {innate1, __VA_ARGS__}; Innates_(__LINE__, innates_); } while(0) #define Level(level) Level_(__LINE__, level) #define MaxHP(maxHP) MaxHP_(__LINE__, maxHP) #define HP(hp) HP_(__LINE__, hp) @@ -1023,6 +1025,7 @@ void AILogScores(u32 sourceLine); void Gender_(u32 sourceLine, u32 gender); void Nature_(u32 sourceLine, u32 nature); void Ability_(u32 sourceLine, enum Ability ability); +void Innates_(u32 sourceLine, enum Ability innates[MAX_MON_INNATES]); void Level_(u32 sourceLine, u32 level); void MaxHP_(u32 sourceLine, u32 maxHP); void HP_(u32 sourceLine, u32 hp); diff --git a/include/test_runner.h b/include/test_runner.h index b15ae349ada8..2bb5f466eeb1 100644 --- a/include/test_runner.h +++ b/include/test_runner.h @@ -32,6 +32,7 @@ void TestRunner_CheckMemory(void); void TestRunner_Battle_CheckBattleRecordActionType(u32 battlerId, u32 recordIndex, u32 actionType); u32 TestRunner_Battle_GetForcedAbility(u32 array, u32 partyIndex); +u32 TestRunner_Battle_GetForcedInnates(u32 array, u32 partyIndex, s32 i); u32 TestRunner_Battle_GetChosenGimmick(u32 side, u32 partyIndex); u32 TestRunner_Battle_GetForcedEnvironment(void); @@ -56,6 +57,8 @@ u32 TestRunner_Battle_GetForcedEnvironment(void); #define TestRunner_Battle_GetForcedAbility(...) (u32)0 +#define TestRunner_Battle_GetForcedInnates(...) (u32)0 + #define TestRunner_Battle_GetChosenGimmick(...) (u32)0 #define TestRunner_Battle_GetForcedEnvironment(...) (u8)0 diff --git a/src/battle_ai_field_statuses.c b/src/battle_ai_field_statuses.c index 030d99a94eb5..eca11a9fcee7 100644 --- a/src/battle_ai_field_statuses.c +++ b/src/battle_ai_field_statuses.c @@ -23,8 +23,8 @@ #include "constants/moves.h" #include "constants/items.h" -static bool32 DoesAbilityBenefitFromWeather(enum Ability ability, u32 weather); -static bool32 DoesAbilityBenefitFromFieldStatus(enum Ability ability, u32 fieldStatus); +static bool32 DoesBattlerBenefitFromWeather(u32 battler, u32 weather); +static bool32 DoesBattlerBenefitFromFieldStatus(u32 battler, u32 fieldStatus); // A move is light sensitive if it is boosted by Sunny Day and weakened by low light weathers. static bool32 IsLightSensitiveMove(u32 move); static bool32 HasLightSensitiveMove(u32 battler); @@ -127,62 +127,60 @@ bool32 FieldStatusChecker(u32 battler, u32 fieldStatus, enum FieldEffectOutcome return (result == desiredResult); } -static bool32 DoesAbilityBenefitFromWeather(enum Ability ability, u32 weather) +static bool32 DoesBattlerBenefitFromWeather(u32 battler, u32 weather) { - switch (ability) - { - case ABILITY_FORECAST: + enum Ability AIBattlerTraits[MAX_MON_TRAITS]; + AI_STORE_BATTLER_TRAITS(battler); + + if (AISearchTraits(AIBattlerTraits, ABILITY_FORECAST)) return (weather & (B_WEATHER_RAIN | B_WEATHER_SUN | B_WEATHER_ICY_ANY)); - case ABILITY_MAGIC_GUARD: - case ABILITY_OVERCOAT: + if (AISearchTraits(AIBattlerTraits, ABILITY_MAGIC_GUARD) + || AISearchTraits(AIBattlerTraits, ABILITY_OVERCOAT)) return (weather & B_WEATHER_DAMAGING_ANY); - case ABILITY_SAND_FORCE: - case ABILITY_SAND_RUSH: - case ABILITY_SAND_VEIL: + if (AISearchTraits(AIBattlerTraits, ABILITY_SAND_FORCE) + || AISearchTraits(AIBattlerTraits, ABILITY_SAND_RUSH) + || AISearchTraits(AIBattlerTraits, ABILITY_SAND_VEIL)) return (weather & B_WEATHER_SANDSTORM); - case ABILITY_ICE_BODY: - case ABILITY_ICE_FACE: - case ABILITY_SNOW_CLOAK: + if (AISearchTraits(AIBattlerTraits, ABILITY_ICE_BODY) + || AISearchTraits(AIBattlerTraits, ABILITY_ICE_FACE) + || AISearchTraits(AIBattlerTraits, ABILITY_SNOW_CLOAK)) return (weather & B_WEATHER_ICY_ANY); - case ABILITY_SLUSH_RUSH: + if (AISearchTraits(AIBattlerTraits, ABILITY_SLUSH_RUSH)) return (weather & B_WEATHER_SNOW); - case ABILITY_DRY_SKIN: - case ABILITY_HYDRATION: - case ABILITY_RAIN_DISH: - case ABILITY_SWIFT_SWIM: + if (AISearchTraits(AIBattlerTraits, ABILITY_DRY_SKIN) + || AISearchTraits(AIBattlerTraits, ABILITY_HYDRATION) + || AISearchTraits(AIBattlerTraits, ABILITY_RAIN_DISH) + || AISearchTraits(AIBattlerTraits, ABILITY_SWIFT_SWIM)) return (weather & B_WEATHER_RAIN); - case ABILITY_CHLOROPHYLL: - case ABILITY_FLOWER_GIFT: - case ABILITY_HARVEST: - case ABILITY_LEAF_GUARD: - case ABILITY_ORICHALCUM_PULSE: - case ABILITY_PROTOSYNTHESIS: - case ABILITY_SOLAR_POWER: + if (AISearchTraits(AIBattlerTraits, ABILITY_CHLOROPHYLL) + || AISearchTraits(AIBattlerTraits, ABILITY_FLOWER_GIFT) + || AISearchTraits(AIBattlerTraits, ABILITY_HARVEST) + || AISearchTraits(AIBattlerTraits, ABILITY_LEAF_GUARD) + || AISearchTraits(AIBattlerTraits, ABILITY_ORICHALCUM_PULSE) + || AISearchTraits(AIBattlerTraits, ABILITY_PROTOSYNTHESIS) + || AISearchTraits(AIBattlerTraits, ABILITY_SOLAR_POWER)) return (weather & B_WEATHER_SUN); - default: - break; - } + return FALSE; } -static bool32 DoesAbilityBenefitFromFieldStatus(enum Ability ability, u32 fieldStatus) +static bool32 DoesBattlerBenefitFromFieldStatus(u32 battler, u32 fieldStatus) { - switch (ability) - { - case ABILITY_MIMICRY: + enum Ability AIBattlerTraits[MAX_MON_TRAITS]; + AI_STORE_BATTLER_TRAITS(battler); + + if (AISearchTraits(AIBattlerTraits, ABILITY_MIMICRY)) return (fieldStatus & STATUS_FIELD_TERRAIN_ANY); - case ABILITY_HADRON_ENGINE: - case ABILITY_QUARK_DRIVE: - case ABILITY_SURGE_SURFER: + if (AISearchTraits(AIBattlerTraits, ABILITY_HADRON_ENGINE) + || AISearchTraits(AIBattlerTraits, ABILITY_QUARK_DRIVE) + || AISearchTraits(AIBattlerTraits, ABILITY_SURGE_SURFER)) return (fieldStatus & STATUS_FIELD_ELECTRIC_TERRAIN); - case ABILITY_GRASS_PELT: + if (AISearchTraits(AIBattlerTraits, ABILITY_GRASS_PELT)) return (fieldStatus & STATUS_FIELD_GRASSY_TERRAIN); // no abilities inherently benefit from Misty or Psychic Terrains // return (fieldStatus & STATUS_FIELD_MISTY_TERRAIN); // return (fieldStatus & STATUS_FIELD_PSYCHIC_TERRAIN); - default: - break; - } + return FALSE; } @@ -219,23 +217,24 @@ static bool32 HasLightSensitiveMove(u32 battler) // Utility Umbrella does NOT block Ancient Pokemon from their stat boosts. static enum FieldEffectOutcome BenefitsFromSun(u32 battler) { - enum Ability ability = gAiLogicData->abilities[battler]; + enum Ability AIBattlerTraits[MAX_MON_TRAITS]; + AI_STORE_BATTLER_TRAITS(battler); if (gAiLogicData->holdEffects[battler] == HOLD_EFFECT_UTILITY_UMBRELLA) { - if (ability == ABILITY_ORICHALCUM_PULSE || ability == ABILITY_PROTOSYNTHESIS) + if (AISearchTraits(AIBattlerTraits, ABILITY_ORICHALCUM_PULSE) || AISearchTraits(AIBattlerTraits, ABILITY_PROTOSYNTHESIS)) return FIELD_EFFECT_POSITIVE; else return FIELD_EFFECT_NEUTRAL; } - if (DoesAbilityBenefitFromWeather(ability, B_WEATHER_SUN) + if (DoesBattlerBenefitFromWeather(battler, B_WEATHER_SUN) || HasLightSensitiveMove(battler) || HasDamagingMoveOfType(battler, TYPE_FIRE) || HasMoveWithEffect(battler, EFFECT_HYDRO_STEAM)) return FIELD_EFFECT_POSITIVE; - if (HasMoveWithFlag(battler, MoveHas50AccuracyInSun) || HasDamagingMoveOfType(battler, TYPE_WATER) || gAiLogicData->abilities[battler] == ABILITY_DRY_SKIN) + if (HasMoveWithFlag(battler, MoveHas50AccuracyInSun) || HasDamagingMoveOfType(battler, TYPE_WATER) || AISearchTraits(AIBattlerTraits, ABILITY_DRY_SKIN)) return FIELD_EFFECT_NEGATIVE; return FIELD_EFFECT_NEUTRAL; @@ -244,7 +243,7 @@ static enum FieldEffectOutcome BenefitsFromSun(u32 battler) // Sandstorm static enum FieldEffectOutcome BenefitsFromSandstorm(u32 battler) { - if (DoesAbilityBenefitFromWeather(gAiLogicData->abilities[battler], B_WEATHER_SANDSTORM) + if (DoesBattlerBenefitFromWeather(battler, B_WEATHER_SANDSTORM) || IS_BATTLER_OF_TYPE(battler, TYPE_ROCK)) return FIELD_EFFECT_POSITIVE; @@ -252,7 +251,7 @@ static enum FieldEffectOutcome BenefitsFromSandstorm(u32 battler) { if (!(IS_BATTLER_ANY_TYPE(LEFT_FOE(battler), TYPE_ROCK, TYPE_GROUND, TYPE_STEEL)) || gAiLogicData->holdEffects[LEFT_FOE(battler)] == HOLD_EFFECT_SAFETY_GOGGLES - || DoesAbilityBenefitFromWeather(gAiLogicData->abilities[LEFT_FOE(battler)], B_WEATHER_SANDSTORM)) + || DoesBattlerBenefitFromWeather(LEFT_FOE(battler), B_WEATHER_SANDSTORM)) return FIELD_EFFECT_POSITIVE; else return FIELD_EFFECT_NEUTRAL; @@ -264,7 +263,7 @@ static enum FieldEffectOutcome BenefitsFromSandstorm(u32 battler) // Hail or Snow static enum FieldEffectOutcome BenefitsFromHailOrSnow(u32 battler, u32 weather) { - if (DoesAbilityBenefitFromWeather(gAiLogicData->abilities[battler], weather) + if (DoesBattlerBenefitFromWeather(battler, weather) || IS_BATTLER_OF_TYPE(battler, TYPE_ICE) || HasMoveWithFlag(battler, MoveAlwaysHitsInHailSnow) || HasBattlerSideMoveWithEffect(battler, EFFECT_AURORA_VEIL)) @@ -288,7 +287,7 @@ static enum FieldEffectOutcome BenefitsFromRain(u32 battler) if (gAiLogicData->holdEffects[battler] == HOLD_EFFECT_UTILITY_UMBRELLA) return FIELD_EFFECT_NEUTRAL; - if (DoesAbilityBenefitFromWeather(gAiLogicData->abilities[battler], B_WEATHER_RAIN) + if (DoesBattlerBenefitFromWeather(battler, B_WEATHER_RAIN) || HasMoveWithFlag(battler, MoveAlwaysHitsInRain) || HasDamagingMoveOfType(battler, TYPE_WATER)) return FIELD_EFFECT_POSITIVE; @@ -305,7 +304,7 @@ static enum FieldEffectOutcome BenefitsFromRain(u32 battler) //TODO: when is electric terrain bad? static enum FieldEffectOutcome BenefitsFromElectricTerrain(u32 battler) { - if (DoesAbilityBenefitFromFieldStatus(gAiLogicData->abilities[battler], STATUS_FIELD_ELECTRIC_TERRAIN)) + if (DoesBattlerBenefitFromFieldStatus(battler, STATUS_FIELD_ELECTRIC_TERRAIN)) return FIELD_EFFECT_POSITIVE; if (HasMoveWithEffect(battler, EFFECT_RISING_VOLTAGE)) @@ -334,7 +333,7 @@ static enum FieldEffectOutcome BenefitsFromElectricTerrain(u32 battler) //TODO: when is grassy terrain bad? static enum FieldEffectOutcome BenefitsFromGrassyTerrain(u32 battler) { - if (DoesAbilityBenefitFromFieldStatus(gAiLogicData->abilities[battler], STATUS_FIELD_GRASSY_TERRAIN)) + if (DoesBattlerBenefitFromFieldStatus(battler, STATUS_FIELD_GRASSY_TERRAIN)) return FIELD_EFFECT_POSITIVE; if (HasBattlerSideMoveWithEffect(battler, EFFECT_GRASSY_GLIDE)) @@ -362,7 +361,7 @@ static enum FieldEffectOutcome BenefitsFromGrassyTerrain(u32 battler) //TODO: when is misty terrain bad? static enum FieldEffectOutcome BenefitsFromMistyTerrain(u32 battler) { - if (DoesAbilityBenefitFromFieldStatus(gAiLogicData->abilities[battler], STATUS_FIELD_MISTY_TERRAIN)) + if (DoesBattlerBenefitFromFieldStatus(battler, STATUS_FIELD_MISTY_TERRAIN)) return FIELD_EFFECT_POSITIVE; if (HasBattlerSideMoveWithEffect(battler, EFFECT_MISTY_EXPLOSION)) @@ -395,7 +394,7 @@ static enum FieldEffectOutcome BenefitsFromMistyTerrain(u32 battler) //TODO: when is Psychic Terrain negative? static enum FieldEffectOutcome BenefitsFromPsychicTerrain(u32 battler) { - if (DoesAbilityBenefitFromFieldStatus(gAiLogicData->abilities[battler], STATUS_FIELD_PSYCHIC_TERRAIN)) + if (DoesBattlerBenefitFromFieldStatus(battler, STATUS_FIELD_PSYCHIC_TERRAIN)) return FIELD_EFFECT_POSITIVE; if (HasBattlerSideMoveWithEffect(battler, EFFECT_EXPANDING_FORCE)) @@ -480,7 +479,7 @@ static enum FieldEffectOutcome BenefitsFromTrickRoom(u32 battler) for (int i = 0; i < MAX_MON_MOVES; i++) { u16 move = aiMoves[i]; - if (GetBattleMovePriority(battler, gAiLogicData->abilities[battler], move) > 0 && !(GetMovePriority(move) > 0 && IsBattleMoveStatus(move))) + if (GetBattleMovePriority(battler, move) > 0 && !(GetMovePriority(move) > 0 && IsBattleMoveStatus(move))) { return FIELD_EFFECT_POSITIVE; } diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index e0490b67120b..b442c060d4b2 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -550,17 +550,16 @@ void RecordStatusMoves(u32 battler) void SetBattlerAiData(u32 battler, struct AiLogicData *aiData) { - enum Ability ability; u32 holdEffect; - ability = aiData->abilities[battler] = AI_DecideKnownAbilityForTurn(battler); + aiData->abilities[battler] = AI_DecideKnownAbilityForTurn(battler); aiData->items[battler] = gBattleMons[battler].item; holdEffect = aiData->holdEffects[battler] = AI_DecideHoldEffectForTurn(battler); aiData->holdEffectParams[battler] = GetBattlerHoldEffectParam(battler); aiData->lastUsedMove[battler] = (gLastMoves[battler] == MOVE_UNAVAILABLE) ? MOVE_NONE : gLastMoves[battler]; aiData->hpPercents[battler] = GetHealthPercentage(battler); aiData->moveLimitations[battler] = CheckMoveLimitations(battler, 0, MOVE_LIMITATIONS_ALL); - aiData->speedStats[battler] = GetBattlerTotalSpeedStat(battler, ability, holdEffect); + aiData->speedStats[battler] = GetBattlerTotalSpeedStat(battler, holdEffect); if (IsAiBattlerAssumingStab()) RecordMovesBasedOnStab(battler); @@ -573,15 +572,14 @@ void SetBattlerAiData(u32 battler, struct AiLogicData *aiData) static u32 Ai_SetMoveAccuracy(struct AiLogicData *aiData, u32 battlerAtk, u32 battlerDef, u32 move) { u32 accuracy; - enum Ability abilityAtk = aiData->abilities[battlerAtk]; - enum Ability abilityDef = aiData->abilities[battlerDef]; - if (CanMoveSkipAccuracyCalc(battlerAtk, battlerDef, abilityAtk, abilityDef, move, AI_CHECK)) + + if (CanMoveSkipAccuracyCalc(battlerAtk, battlerDef, move, AI_CHECK)) { accuracy = BYPASSES_ACCURACY_CALC; } else { - accuracy = GetTotalAccuracy(battlerAtk, battlerDef, move, abilityAtk, abilityDef, aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef]); + accuracy = GetTotalAccuracy(battlerAtk, battlerDef, move, aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef]); // Cap normal accuracy at 100 for ai calcs. // Done for comparison with moves that bypass accuracy checks (will be seen as 101 for ai calcs)) accuracy = (accuracy > 100) ? 100 : accuracy; @@ -715,12 +713,11 @@ static u32 PpStallReduction(u32 move, u32 battlerAtk) continue; PokemonToBattleMon(&gPlayerParty[partyIndex], &gBattleMons[tempBattleMonIndex]); u32 species = GetMonData(&gPlayerParty[partyIndex], MON_DATA_SPECIES); - enum Ability abilityAtk = ABILITY_NONE; - enum Ability abilityDef = GetPartyMonAbility(&gPlayerParty[partyIndex]); + struct Pokemon *mon = &gPlayerParty[partyIndex]; enum Type moveType = GetBattleMoveType(move); // Probably doesn't handle dynamic types right now - if (CanAbilityAbsorbMove(battlerAtk, tempBattleMonIndex, abilityDef, move, moveType, CHECK_TRIGGER) - || CanAbilityBlockMove(battlerAtk, tempBattleMonIndex, abilityAtk, abilityDef, move, CHECK_TRIGGER) - || (CalcPartyMonTypeEffectivenessMultiplier(move, species, abilityDef) == 0)) + if (CanAbilityAbsorbMove(battlerAtk, tempBattleMonIndex, move, moveType, CHECK_TRIGGER) + || CanAbilityBlockMove(battlerAtk, tempBattleMonIndex, move, CHECK_TRIGGER) + || (CalcPartyMonTypeEffectivenessMultiplier(move, species, mon) == 0)) { totalStallValue += currentStallValue; } @@ -1086,14 +1083,15 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) u32 weather; u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData); u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData); - enum Ability abilityAtk = aiData->abilities[battlerAtk]; - enum Ability abilityDef = aiData->abilities[battlerDef]; - s32 atkPriority = GetBattleMovePriority(battlerAtk, abilityAtk, move); + s32 atkPriority = GetBattleMovePriority(battlerAtk, move); + bool32 ignoreAbility = FALSE; + enum Ability AIBattlerTraits[MAX_MON_TRAITS]; + AI_STORE_BATTLER_TRAITS(battlerDef); SetTypeBeforeUsingMove(move, battlerAtk); moveType = GetBattleMoveType(move); - if (IsPowderMove(move) && !IsAffectedByPowderMove(battlerDef, aiData->abilities[battlerDef], aiData->holdEffects[battlerDef])) + if (IsPowderMove(move) && !IsAffectedByPowderMove(battlerDef, aiData->holdEffects[battlerDef])) RETURN_SCORE_MINUS(10); if (!BreaksThroughSemiInvulnerablity(battlerDef, move) && moveEffect != EFFECT_SEMI_INVULNERABLE && AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)) @@ -1133,8 +1131,8 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if (effectiveness == UQ_4_12(0.0)) RETURN_SCORE_MINUS(20); - if (DoesBattlerIgnoreAbilityChecks(battlerAtk, abilityAtk, move)) - abilityDef = ABILITY_NONE; + if (DoesBattlerIgnoreAbilityChecks(battlerAtk, move)) + ignoreAbility = TRUE; // If a pokemon can be guaranteed flinched, don't target the pokemon that can't be flinched. if (hasTwoOpponents @@ -1148,144 +1146,122 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if (Ai_IsPriorityBlocked(battlerAtk, battlerDef, move, aiData)) RETURN_SCORE_MINUS(20); - if (CanAbilityBlockMove(battlerAtk, battlerDef, abilityAtk, abilityDef, move, AI_CHECK)) + if (!ignoreAbility && CanAbilityBlockMove(battlerAtk, battlerDef, move, AI_CHECK)) RETURN_SCORE_MINUS(20); - if (CanAbilityAbsorbMove(battlerAtk, battlerDef, abilityDef, move, moveType, AI_CHECK)) + if (!ignoreAbility && CanAbilityAbsorbMove(battlerAtk, battlerDef, move, moveType, AI_CHECK)) RETURN_SCORE_MINUS(20); - switch (abilityDef) - { - case ABILITY_MAGIC_GUARD: - switch (moveEffect) - { - case EFFECT_LEECH_SEED: - ADJUST_SCORE(-5); - break; - case EFFECT_CURSE: - if (IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GHOST)) // Don't use Curse if you're a ghost type vs a Magic Guard user, they'll take no damage. + if (AISearchTraits(AIBattlerTraits, ABILITY_MAGIC_GUARD)) + { + switch (moveEffect) + { + case EFFECT_LEECH_SEED: + ADJUST_SCORE(-5); + break; + case EFFECT_CURSE: + if (IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GHOST)) // Don't use Curse if you're a ghost type vs a Magic Guard user, they'll take no damage. + ADJUST_SCORE(-5); + break; + default: + break; + } + + switch(nonVolatileStatus) + { + case MOVE_EFFECT_POISON: + case MOVE_EFFECT_TOXIC: + case MOVE_EFFECT_BURN: ADJUST_SCORE(-5); - break; - default: - break; + break; + default: + break; + } } - - switch(nonVolatileStatus) + if (AISearchTraits(AIBattlerTraits, ABILITY_WONDER_GUARD)) { - case MOVE_EFFECT_POISON: - case MOVE_EFFECT_TOXIC: - case MOVE_EFFECT_BURN: - ADJUST_SCORE(-5); - break; - default: - break; + if (effectiveness < UQ_4_12(2.0)) + RETURN_SCORE_MINUS(20); } - break; - case ABILITY_WONDER_GUARD: - if (effectiveness < UQ_4_12(2.0)) - RETURN_SCORE_MINUS(20); - break; - case ABILITY_JUSTIFIED: - if (moveType == TYPE_DARK && !IsBattleMoveStatus(move) && !IsTargetingPartner(battlerAtk, battlerDef)) + if (AISearchTraits(AIBattlerTraits, ABILITY_JUSTIFIED) + && moveType == TYPE_DARK && !IsBattleMoveStatus(move) && !IsTargetingPartner(battlerAtk, battlerDef)) RETURN_SCORE_MINUS(10); - break; - case ABILITY_RATTLED: - if (!IsBattleMoveStatus(move) - && (moveType == TYPE_DARK || moveType == TYPE_GHOST || moveType == TYPE_BUG)) + if (AISearchTraits(AIBattlerTraits, ABILITY_RATTLED) + && (!IsBattleMoveStatus(move) + && (moveType == TYPE_DARK || moveType == TYPE_GHOST || moveType == TYPE_BUG))) RETURN_SCORE_MINUS(10); - break; - case ABILITY_AROMA_VEIL: - if (IsAromaVeilProtectedEffect(moveEffect)) + if (AISearchTraits(AIBattlerTraits, ABILITY_AROMA_VEIL) + && IsAromaVeilProtectedEffect(moveEffect)) RETURN_SCORE_MINUS(10); - break; - case ABILITY_SWEET_VEIL: - if (nonVolatileStatus == MOVE_EFFECT_SLEEP) + if (AISearchTraits(AIBattlerTraits, ABILITY_SWEET_VEIL) + && (nonVolatileStatus == MOVE_EFFECT_SLEEP)) RETURN_SCORE_MINUS(10); - break; - case ABILITY_FLOWER_VEIL: - if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) && (IsNonVolatileStatusMove(move))) + if (AISearchTraits(AIBattlerTraits, ABILITY_FLOWER_VEIL) + && (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) && (IsNonVolatileStatusMove(move)))) RETURN_SCORE_MINUS(10); - break; - case ABILITY_MAGIC_BOUNCE: - if (MoveCanBeBouncedBack(move)) + if (AISearchTraits(AIBattlerTraits, ABILITY_MAGIC_BOUNCE) + && MoveCanBeBouncedBack(move)) RETURN_SCORE_MINUS(20); - break; - case ABILITY_CONTRARY: - if (IsStatLoweringEffect(moveEffect)) + if (AISearchTraits(AIBattlerTraits, ABILITY_CONTRARY) + && IsStatLoweringEffect(moveEffect)) RETURN_SCORE_MINUS(20); - break; - case ABILITY_COMATOSE: - if (IsNonVolatileStatusMove(move)) + if (AISearchTraits(AIBattlerTraits, ABILITY_COMATOSE) + && IsNonVolatileStatusMove(moveEffect)) RETURN_SCORE_MINUS(10); - break; - case ABILITY_SHIELDS_DOWN: - if (IsShieldsDownProtected(battlerAtk, aiData->abilities[battlerAtk]) && IsNonVolatileStatusMove(move)) + if (AISearchTraits(AIBattlerTraits, ABILITY_SHIELDS_DOWN) + && IsShieldsDownProtected(battlerAtk) && IsNonVolatileStatusMove(move)) RETURN_SCORE_MINUS(10); - break; - case ABILITY_LEAF_GUARD: - if ((AI_GetWeather() & B_WEATHER_SUN) - && aiData->holdEffects[battlerDef] != HOLD_EFFECT_UTILITY_UMBRELLA - && IsNonVolatileStatusMove(move)) + if (AISearchTraits(AIBattlerTraits, ABILITY_LEAF_GUARD) + && ((AI_GetWeather() & B_WEATHER_SUN) + && aiData->holdEffects[battlerDef] != HOLD_EFFECT_UTILITY_UMBRELLA + && IsNonVolatileStatusMove(move))) RETURN_SCORE_MINUS(10); - break; - default: - break; - } // def ability checks + + // def ability checks - // target partner ability checks & not attacking partner - if (hasTwoOpponents) - { - switch (aiData->abilities[BATTLE_PARTNER(battlerDef)]) + // target partner ability checks & not attacking partner + if (hasTwoOpponents) { - case ABILITY_LIGHTNING_ROD: - if (moveType == TYPE_ELECTRIC && !IsMoveRedirectionPrevented(battlerAtk, move, aiData->abilities[battlerAtk])) + if (AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerDef), ABILITY_LIGHTNING_ROD) + && moveType == TYPE_ELECTRIC && !IsMoveRedirectionPrevented(battlerAtk, move)) RETURN_SCORE_MINUS(20); - break; - case ABILITY_STORM_DRAIN: - if (moveType == TYPE_WATER && !IsMoveRedirectionPrevented(battlerAtk, move, aiData->abilities[battlerAtk])) + if (AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerDef), ABILITY_STORM_DRAIN) + && moveType == TYPE_WATER && !IsMoveRedirectionPrevented(battlerAtk, move)) RETURN_SCORE_MINUS(20); - break; - case ABILITY_MAGIC_BOUNCE: - if (MoveCanBeBouncedBack(move) && moveTarget & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY | MOVE_TARGET_OPPONENTS_FIELD)) + if (AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerDef), ABILITY_MAGIC_BOUNCE) + && (MoveCanBeBouncedBack(move) && moveTarget & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY | MOVE_TARGET_OPPONENTS_FIELD))) RETURN_SCORE_MINUS(20); - break; - case ABILITY_SWEET_VEIL: - if (nonVolatileStatus == MOVE_EFFECT_SLEEP) + if (AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerDef), ABILITY_SWEET_VEIL) + && (nonVolatileStatus == MOVE_EFFECT_SLEEP)) RETURN_SCORE_MINUS(20); - break; - case ABILITY_FLOWER_VEIL: - if ((IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS)) && (IsNonVolatileStatusMove(move) || IsStatLoweringEffect(moveEffect))) + if (AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerDef), ABILITY_FLOWER_VEIL) + && ((IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS)) && (IsNonVolatileStatusMove(moveEffect) || IsStatLoweringEffect(moveEffect)))) RETURN_SCORE_MINUS(10); - break; - case ABILITY_AROMA_VEIL: - if (IsAromaVeilProtectedEffect(moveEffect)) + if (AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerDef), ABILITY_AROMA_VEIL) + && (IsAromaVeilProtectedEffect(moveEffect))) RETURN_SCORE_MINUS(10); - break; - default: - break; - } - } // def partner ability checks + } // def partner ability checks // gen7+ dark type mons immune to priority->elevated moves from prankster if (GetConfig(CONFIG_PRANKSTER_DARK_TYPES) >= GEN_7 && IS_BATTLER_OF_TYPE(battlerDef, TYPE_DARK) - && aiData->abilities[battlerAtk] == ABILITY_PRANKSTER && IsBattleMoveStatus(move) + && AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_PRANKSTER) && IsBattleMoveStatus(move) && !(moveTarget & (MOVE_TARGET_OPPONENTS_FIELD | MOVE_TARGET_USER))) RETURN_SCORE_MINUS(10); // terrain & effect checks - if (IsBattlerTerrainAffected(battlerDef, abilityDef, aiData->holdEffects[battlerDef], STATUS_FIELD_ELECTRIC_TERRAIN)) + if (IsBattlerTerrainAffected(battlerDef, aiData->holdEffects[battlerDef], STATUS_FIELD_ELECTRIC_TERRAIN)) { if (nonVolatileStatus == MOVE_EFFECT_SLEEP) RETURN_SCORE_MINUS(20); } - if (IsBattlerTerrainAffected(battlerDef, abilityDef, aiData->holdEffects[battlerDef], STATUS_FIELD_MISTY_TERRAIN)) + if (IsBattlerTerrainAffected(battlerDef, aiData->holdEffects[battlerDef], STATUS_FIELD_MISTY_TERRAIN)) { if (IsNonVolatileStatusMove(move) || IsConfusionMoveEffect(moveEffect)) RETURN_SCORE_MINUS(20); } - if (IsBattlerTerrainAffected(battlerAtk, abilityAtk, aiData->holdEffects[battlerAtk], STATUS_FIELD_PSYCHIC_TERRAIN) && atkPriority > 0) + if (IsBattlerTerrainAffected(battlerAtk, aiData->holdEffects[battlerAtk], STATUS_FIELD_PSYCHIC_TERRAIN) && atkPriority > 0) { RETURN_SCORE_MINUS(20); } @@ -1333,10 +1309,12 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } // check move effects + AI_STORE_BATTLER_TRAITS(battlerAtk); + switch (moveEffect) { case EFFECT_HIT: // only applies to Vital Throw - if (GetBattleMovePriority(battlerAtk, aiData->abilities[battlerAtk], move) < 0 && AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY) && aiData->hpPercents[battlerAtk] < 40) + if (GetBattleMovePriority(battlerAtk, move) < 0 && AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY) && aiData->hpPercents[battlerAtk] < 40) ADJUST_SCORE(-2); // don't want to move last break; default: @@ -1350,7 +1328,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { ADJUST_SCORE(-10); } - else if (IsAbilityOnField(ABILITY_DAMP) && !DoesBattlerIgnoreAbilityChecks(battlerAtk, aiData->abilities[battlerAtk], move)) + else if (IsAbilityOnField(ABILITY_DAMP) && !DoesBattlerIgnoreAbilityChecks(battlerAtk, move)) { ADJUST_SCORE(-10); } @@ -1366,7 +1344,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_ATTACK_UP: case EFFECT_ATTACK_UP_2: case EFFECT_ATTACK_UP_USER_ALLY: - if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + if (!BattlerStatCanRise(battlerAtk, STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) ADJUST_SCORE(-10); break; case EFFECT_STUFF_CHEEKS: @@ -1377,65 +1355,65 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_DEFENSE_UP_2: case EFFECT_DEFENSE_UP_3: case EFFECT_DEFENSE_CURL: - if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_DEF)) + if (!BattlerStatCanRise(battlerAtk, STAT_DEF)) ADJUST_SCORE(-10); break; case EFFECT_SPECIAL_ATTACK_UP: case EFFECT_SPECIAL_ATTACK_UP_2: case EFFECT_SPECIAL_ATTACK_UP_3: - if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL)) + if (!BattlerStatCanRise(battlerAtk, STAT_SPATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL)) ADJUST_SCORE(-10); break; case EFFECT_SPECIAL_DEFENSE_UP: case EFFECT_SPECIAL_DEFENSE_UP_2: - if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF)) + if (!BattlerStatCanRise(battlerAtk, STAT_SPDEF)) ADJUST_SCORE(-10); break; case EFFECT_ACCURACY_UP: case EFFECT_ACCURACY_UP_2: - if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ACC)) + if (!BattlerStatCanRise(battlerAtk, STAT_ACC)) ADJUST_SCORE(-10); break; case EFFECT_EVASION_UP: case EFFECT_EVASION_UP_2: case EFFECT_MINIMIZE: - if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_EVASION)) + if (!BattlerStatCanRise(battlerAtk, STAT_EVASION)) ADJUST_SCORE(-10); break; case EFFECT_COSMIC_POWER: - if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_DEF)) + if (!BattlerStatCanRise(battlerAtk, STAT_DEF)) ADJUST_SCORE(-10); - else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF)) + else if (!BattlerStatCanRise(battlerAtk, STAT_SPDEF)) ADJUST_SCORE(-8); break; case EFFECT_BULK_UP: - if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + if (!BattlerStatCanRise(battlerAtk, STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) ADJUST_SCORE(-10); - else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_DEF)) + else if (!BattlerStatCanRise(battlerAtk, STAT_DEF)) ADJUST_SCORE(-8); break; case EFFECT_CALM_MIND: - if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPATK)) + if (!BattlerStatCanRise(battlerAtk, STAT_SPATK)) ADJUST_SCORE(-10); - else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF)) + else if (!BattlerStatCanRise(battlerAtk, STAT_SPDEF)) ADJUST_SCORE(-8); break; case EFFECT_DRAGON_DANCE: - if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + if (!BattlerStatCanRise(battlerAtk, STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) ADJUST_SCORE(-10); - else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPEED)) + else if (!BattlerStatCanRise(battlerAtk, STAT_SPEED)) ADJUST_SCORE(-8); break; case EFFECT_COIL: - if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ACC)) + if (!BattlerStatCanRise(battlerAtk, STAT_ACC)) ADJUST_SCORE(-10); - else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + else if (!BattlerStatCanRise(battlerAtk, STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) ADJUST_SCORE(-8); - else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_DEF)) + else if (!BattlerStatCanRise(battlerAtk, STAT_DEF)) ADJUST_SCORE(-6); break; case EFFECT_ATTACK_ACCURACY_UP: //hone claws - if (aiData->abilities[battlerAtk] != ABILITY_CONTRARY) + if (!AISearchTraits(AIBattlerTraits, ABILITY_CONTRARY)) { if (gBattleMons[battlerAtk].statStages[STAT_ATK] >= MAX_STAT_STAGE && (gBattleMons[battlerAtk].statStages[STAT_ACC] >= MAX_STAT_STAGE || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL))) @@ -1453,7 +1431,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) else if (!HasMoveWithType(battlerAtk, TYPE_ELECTRIC)) ADJUST_SCORE(-10); else if (B_CHARGE_SPDEF_RAISE >= GEN_5 - && !BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF)) + && !BattlerStatCanRise(battlerAtk, STAT_SPDEF)) ADJUST_SCORE(-5); break; case EFFECT_QUIVER_DANCE: @@ -1462,46 +1440,46 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); if (gBattleMons[battlerAtk].statStages[STAT_SPATK] >= MAX_STAT_STAGE || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL)) ADJUST_SCORE(-10); - else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPEED)) + else if (!BattlerStatCanRise(battlerAtk, STAT_SPEED)) ADJUST_SCORE(-8); - else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF)) + else if (!BattlerStatCanRise(battlerAtk, STAT_SPDEF)) ADJUST_SCORE(-6); break; case EFFECT_VICTORY_DANCE: if (gBattleMons[battlerAtk].statStages[STAT_ATK] >= MAX_STAT_STAGE || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) ADJUST_SCORE(-10); - else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPEED)) + else if (!BattlerStatCanRise(battlerAtk, STAT_SPEED)) ADJUST_SCORE(-8); - else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_DEF)) + else if (!BattlerStatCanRise(battlerAtk, STAT_DEF)) ADJUST_SCORE(-6); break; case EFFECT_SHIFT_GEAR: - if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + if (!BattlerStatCanRise(battlerAtk, STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) ADJUST_SCORE(-10); - else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPEED)) + else if (!BattlerStatCanRise(battlerAtk, STAT_SPEED)) ADJUST_SCORE(-8); break; case EFFECT_SHELL_SMASH: - if (aiData->abilities[battlerAtk] == ABILITY_CONTRARY) + if (AISearchTraits(AIBattlerTraits, ABILITY_CONTRARY)) { - if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_DEF)) + if (!BattlerStatCanRise(battlerAtk, STAT_DEF)) ADJUST_SCORE(-10); - else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF)) + else if (!BattlerStatCanRise(battlerAtk, STAT_SPDEF)) ADJUST_SCORE(-8); } else { - if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + if (!BattlerStatCanRise(battlerAtk, STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) ADJUST_SCORE(-10); - else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL)) + else if (!BattlerStatCanRise(battlerAtk, STAT_SPATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL)) ADJUST_SCORE(-8); - else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPEED)) + else if (!BattlerStatCanRise(battlerAtk, STAT_SPEED)) ADJUST_SCORE(-6); } break; case EFFECT_GROWTH: case EFFECT_ATTACK_SPATK_UP: // work up - if ((!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) && !BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPATK)) + if ((!BattlerStatCanRise(battlerAtk, STAT_ATK) && !BattlerStatCanRise(battlerAtk, STAT_SPATK)) || (!HasDamagingMove(battlerAtk))) ADJUST_SCORE(-10); break; @@ -1510,42 +1488,42 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { if (!(IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GRASS) && AI_IsBattlerGrounded(battlerAtk) - && (BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) || BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPATK))) + && (BattlerStatCanRise(battlerAtk, STAT_ATK) || BattlerStatCanRise(battlerAtk, STAT_SPATK))) && !(IS_BATTLER_OF_TYPE(BATTLE_PARTNER(battlerAtk), TYPE_GRASS) && AI_IsBattlerGrounded(BATTLE_PARTNER(battlerAtk)) - && aiData->abilities[BATTLE_PARTNER(battlerAtk)] != ABILITY_CONTRARY - && (BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), aiData->abilities[BATTLE_PARTNER(battlerAtk)], STAT_ATK) - || BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), aiData->abilities[BATTLE_PARTNER(battlerAtk)], STAT_SPATK)))) + && !AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerAtk), ABILITY_CONTRARY) + && (BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), STAT_ATK) + || BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), STAT_SPATK)))) { ADJUST_SCORE(-10); } } else if (!(IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GRASS) && AI_IsBattlerGrounded(battlerAtk) - && (BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) || BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPATK)))) + && (BattlerStatCanRise(battlerAtk, STAT_ATK) || BattlerStatCanRise(battlerAtk, STAT_SPATK)))) { ADJUST_SCORE(-10); } break; case EFFECT_GEAR_UP: - if (aiData->abilities[battlerAtk] == ABILITY_PLUS || aiData->abilities[battlerAtk] == ABILITY_MINUS) + if (AISearchTraits(AIBattlerTraits, ABILITY_PLUS) || AISearchTraits(AIBattlerTraits, ABILITY_MINUS)) { // same as growth, work up - if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + if (!BattlerStatCanRise(battlerAtk, STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) ADJUST_SCORE(-10); - else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL)) + else if (!BattlerStatCanRise(battlerAtk, STAT_SPATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL)) ADJUST_SCORE(-8); break; } else if (hasPartner) { - if (aiData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_PLUS || aiData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_MINUS) + if (AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerAtk), ABILITY_PLUS) || AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerAtk), ABILITY_MINUS)) { - if ((!BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), aiData->abilities[BATTLE_PARTNER(battlerAtk)], STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) - && (!BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), aiData->abilities[BATTLE_PARTNER(battlerAtk)], STAT_SPATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL))) + if ((!BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + && (!BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), STAT_SPATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL))) ADJUST_SCORE(-10); } - else if (aiData->abilities[battlerAtk] != ABILITY_PLUS && aiData->abilities[battlerAtk] != ABILITY_MINUS) + else if (!AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerAtk), ABILITY_PLUS) && !AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerAtk), ABILITY_MINUS)) { ADJUST_SCORE(-10); // nor our or our partner's ability is plus/minus } @@ -1560,23 +1538,23 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); break; case EFFECT_MAGNETIC_FLUX: - if (aiData->abilities[battlerAtk] == ABILITY_PLUS || aiData->abilities[battlerAtk] == ABILITY_MINUS) + if (AISearchTraits(AIBattlerTraits, ABILITY_PLUS) || AISearchTraits(AIBattlerTraits, ABILITY_MINUS)) { - if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_DEF)) + if (!BattlerStatCanRise(battlerAtk, STAT_DEF)) ADJUST_SCORE(-10); - else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF)) + else if (!BattlerStatCanRise(battlerAtk, STAT_SPDEF)) ADJUST_SCORE(-8); } else if (hasPartner) { - if (aiData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_PLUS || aiData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_MINUS) + if (AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerAtk), ABILITY_PLUS) || AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerAtk), ABILITY_MINUS)) { - if (!BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), aiData->abilities[BATTLE_PARTNER(battlerAtk)], STAT_DEF)) + if (!BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), STAT_DEF)) ADJUST_SCORE(-10); - else if (!BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), aiData->abilities[BATTLE_PARTNER(battlerAtk)], STAT_SPDEF)) + else if (!BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), STAT_SPDEF)) ADJUST_SCORE(-8); } - else if (aiData->abilities[battlerAtk] != ABILITY_PLUS && aiData->abilities[battlerAtk] != ABILITY_MINUS) + else if (!AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerAtk), ABILITY_PLUS) && !AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerAtk), ABILITY_MINUS)) { ADJUST_SCORE(-10); // nor our or our partner's ability is plus/minus } @@ -1674,10 +1652,10 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_FIXED_HP_DAMAGE: case EFFECT_FOCUS_PUNCH: // AI_CBM_HighRiskForDamage - if (aiData->abilities[battlerDef] == ABILITY_WONDER_GUARD && effectiveness < UQ_4_12(2.0)) + if (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_WONDER_GUARD) && effectiveness < UQ_4_12(2.0)) ADJUST_SCORE(-10); if (HasDamagingMove(battlerDef) && !(gBattleMons[battlerAtk].volatiles.substitute - || IsBattlerIncapacitated(battlerDef, abilityDef) + || IsBattlerIncapacitated(battlerDef) || gBattleMons[battlerDef].volatiles.infatuation || gBattleMons[battlerDef].volatiles.confusionTurns)) ADJUST_SCORE(-10); @@ -1688,7 +1666,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case EFFECT_COUNTER: case EFFECT_MIRROR_COAT: - if (IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef]) + if (IsBattlerIncapacitated(battlerDef) || gBattleMons[battlerDef].volatiles.infatuation || gBattleMons[battlerDef].volatiles.confusionTurns > 0) ADJUST_SCORE(-1); @@ -1701,7 +1679,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_ROAR: if (CountUsablePartyMons(battlerDef) == 0) ADJUST_SCORE(-10); - else if (aiData->abilities[battlerDef] == ABILITY_SUCTION_CUPS) + else if (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_SUCTION_CUPS)) ADJUST_SCORE(-10); else if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) ADJUST_SCORE(-10); @@ -1709,7 +1687,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_TOXIC_THREAD: if (!CanLowerStat(battlerAtk, battlerDef, aiData, STAT_SPEED)) ADJUST_SCORE(-1); // may still want to just poison - if (!AI_CanPoison(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove)) + if (!AI_CanPoison(battlerAtk, battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); break; case EFFECT_LIGHT_SCREEN: @@ -1733,7 +1711,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) RETURN_SCORE_MINUS(20); // fallthrough case EFFECT_OHKO: - if (!ShouldTryOHKO(battlerAtk, battlerDef, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], move)) + if (!ShouldTryOHKO(battlerAtk, battlerDef, move)) ADJUST_SCORE(-10); else if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) ADJUST_SCORE(-10); @@ -1755,11 +1733,11 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_SWAGGER: case EFFECT_FLATTER: if (DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove) - || !AI_CanConfuse(battlerAtk, battlerDef, aiData->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + || !AI_CanConfuse(battlerAtk, battlerDef, BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) ADJUST_SCORE(-10); break; case EFFECT_SUBSTITUTE: - if (gBattleMons[battlerAtk].volatiles.substitute || aiData->abilities[battlerDef] == ABILITY_INFILTRATOR) + if (gBattleMons[battlerAtk].volatiles.substitute || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_INFILTRATOR)) ADJUST_SCORE(-8); else if (aiData->hpPercents[battlerAtk] <= 25) ADJUST_SCORE(-10); @@ -1769,7 +1747,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_SHED_TAIL: if (CountUsablePartyMons(battlerAtk) == 0) ADJUST_SCORE(-10); - if (gBattleMons[battlerAtk].volatiles.substitute || aiData->abilities[battlerDef] == ABILITY_INFILTRATOR) + if (gBattleMons[battlerAtk].volatiles.substitute || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_INFILTRATOR)) ADJUST_SCORE(-8); else if (aiData->hpPercents[battlerAtk] <= 50) ADJUST_SCORE(-10); @@ -1781,7 +1759,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) || IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); - else if (aiData->abilities[battlerDef] == ABILITY_LIQUID_OOZE) + else if (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_LIQUID_OOZE)) ADJUST_SCORE(-3); break; case EFFECT_DISABLE: @@ -1858,9 +1836,9 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } else // regular curse { - if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) + if (!BattlerStatCanRise(battlerAtk, STAT_ATK) || !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL)) ADJUST_SCORE(-10); - else if (!BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_DEF)) + else if (!BattlerStatCanRise(battlerAtk, STAT_DEF)) ADJUST_SCORE(-8); } break; @@ -1900,15 +1878,15 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if (!isBattle1v1) { if (CountUsablePartyMons(battlerAtk) == 0 - && aiData->abilities[battlerAtk] != ABILITY_SOUNDPROOF + && !AISearchTraits(AIBattlerTraits, ABILITY_SOUNDPROOF) && CountUsablePartyMons(battlerDef) >= 1 - && (aiData->abilities[BATTLE_PARTNER(battlerAtk)] != ABILITY_SOUNDPROOF || !IsBattlerAlive(BATTLE_PARTNER(battlerAtk)))) + && (!AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerAtk), ABILITY_SOUNDPROOF) || !IsBattlerAlive(BATTLE_PARTNER(battlerAtk)))) { ADJUST_SCORE(-10); //Don't wipe your team if you're going to lose } - else if ((!IsBattlerAlive(LEFT_FOE(battlerAtk)) || aiData->abilities[LEFT_FOE(battlerAtk)] == ABILITY_SOUNDPROOF + else if ((!IsBattlerAlive(LEFT_FOE(battlerAtk)) || AI_BATTLER_HAS_TRAIT(LEFT_FOE(battlerAtk), ABILITY_SOUNDPROOF) || gBattleMons[LEFT_FOE(battlerAtk)].volatiles.perishSong) - && (!IsBattlerAlive(RIGHT_FOE(battlerAtk)) || aiData->abilities[RIGHT_FOE(battlerAtk)] == ABILITY_SOUNDPROOF + && (!IsBattlerAlive(RIGHT_FOE(battlerAtk)) || AI_BATTLER_HAS_TRAIT(RIGHT_FOE(battlerAtk), ABILITY_SOUNDPROOF) || gBattleMons[RIGHT_FOE(battlerAtk)].volatiles.perishSong)) { ADJUST_SCORE(-10); //Both enemies are perish songed @@ -1920,11 +1898,11 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } else { - if (CountUsablePartyMons(battlerAtk) == 0 && aiData->abilities[battlerAtk] != ABILITY_SOUNDPROOF + if (CountUsablePartyMons(battlerAtk) == 0 && !AISearchTraits(AIBattlerTraits, ABILITY_SOUNDPROOF) && CountUsablePartyMons(battlerDef) >= 1) ADJUST_SCORE(-10); - if (gBattleMons[battlerDef].volatiles.perishSong || aiData->abilities[battlerDef] == ABILITY_SOUNDPROOF) + if (gBattleMons[battlerDef].volatiles.perishSong || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_SOUNDPROOF)) ADJUST_SCORE(-10); } break; @@ -1950,7 +1928,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-8); break; case EFFECT_ATTRACT: - if (!AI_CanBeInfatuated(battlerAtk, battlerDef, aiData->abilities[battlerDef])) + if (!AI_CanBeInfatuated(battlerAtk, battlerDef)) ADJUST_SCORE(-10); break; case EFFECT_SAFEGUARD: @@ -1959,7 +1937,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); break; case EFFECT_MAGNITUDE: - if (aiData->abilities[battlerDef] == ABILITY_LEVITATE) + if (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_LEVITATE)) ADJUST_SCORE(-10); break; case EFFECT_PARTING_SHOT: @@ -1992,7 +1970,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_FILLET_AWAY: if (AI_IsAbilityOnSide(battlerDef, ABILITY_UNAWARE)) ADJUST_SCORE(-10); - if (aiData->abilities[battlerAtk] == ABILITY_CONTRARY) + if (AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_CONTRARY)) ADJUST_SCORE(-10); else if (aiData->hpPercents[battlerAtk] <= 60 && !IsConsideringZMove(battlerAtk, battlerDef, move)) ADJUST_SCORE(-10); @@ -2074,7 +2052,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); case EFFECT_KNOCK_OFF: case EFFECT_CORROSIVE_GAS: - if (aiData->abilities[battlerDef] == ABILITY_STICKY_HOLD) + if (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_STICKY_HOLD)) ADJUST_SCORE(-10); break; case EFFECT_INGRAIN: @@ -2099,17 +2077,17 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); break; case EFFECT_PSYCHO_SHIFT: - if (gBattleMons[battlerAtk].status1 & STATUS1_PSN_ANY && !AI_CanPoison(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove)) + if (gBattleMons[battlerAtk].status1 & STATUS1_PSN_ANY && !AI_CanPoison(battlerAtk, battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); else if (gBattleMons[battlerAtk].status1 & STATUS1_BURN && !AI_CanBurn(battlerAtk, battlerDef, - aiData->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) ADJUST_SCORE(-10); else if (gBattleMons[battlerAtk].status1 & STATUS1_FROSTBITE && !AI_CanGiveFrostbite(battlerAtk, battlerDef, - aiData->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) ADJUST_SCORE(-10); - else if (gBattleMons[battlerAtk].status1 & STATUS1_PARALYSIS && !AI_CanParalyze(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove)) + else if (gBattleMons[battlerAtk].status1 & STATUS1_PARALYSIS && !AI_CanParalyze(battlerAtk, battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); - else if (gBattleMons[battlerAtk].status1 & STATUS1_SLEEP && !AI_CanPutToSleep(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove)) + else if (gBattleMons[battlerAtk].status1 & STATUS1_SLEEP && !AI_CanPutToSleep(battlerAtk, battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); else ADJUST_SCORE(-10); // attacker has no status to transmit @@ -2127,11 +2105,11 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); break; case EFFECT_ABSORB: - if (aiData->abilities[battlerDef] == ABILITY_LIQUID_OOZE) + if (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_LIQUID_OOZE)) ADJUST_SCORE(-6); break; case EFFECT_STRENGTH_SAP: - if (aiData->abilities[battlerDef] == ABILITY_CONTRARY) + if (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_CONTRARY)) ADJUST_SCORE(-10); else if (!CanLowerStat(battlerAtk, battlerDef, aiData, STAT_ATK)) ADJUST_SCORE(-10); @@ -2149,7 +2127,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); break; case EFFECT_AROMATIC_MIST: - if (!hasPartner || !BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), aiData->abilities[BATTLE_PARTNER(battlerAtk)], STAT_SPDEF)) + if (!hasPartner || !BattlerStatCanRise(BATTLE_PARTNER(battlerAtk), STAT_SPDEF)) ADJUST_SCORE(-10); break; case EFFECT_BIDE: @@ -2172,7 +2150,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); break; case EFFECT_REST: - if (!CanBeSlept(battlerAtk, battlerAtk, aiData->abilities[battlerAtk], NOT_BLOCKED_BY_SLEEP_CLAUSE)) + if (!CanBeSlept(battlerAtk, battlerAtk, NOT_BLOCKED_BY_SLEEP_CLAUSE)) ADJUST_SCORE(-10); //fallthrough case EFFECT_RESTORE_HP: @@ -2221,7 +2199,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } break; case EFFECT_RECOIL_IF_MISS: - if (aiData->abilities[battlerAtk] != ABILITY_MAGIC_GUARD && gAiLogicData->moveAccuracy[battlerAtk][battlerDef][gAiThinkingStruct->movesetIndex] < 75 + if (!AISearchTraits(AIBattlerTraits, ABILITY_MAGIC_GUARD) && gAiLogicData->moveAccuracy[battlerAtk][battlerDef][gAiThinkingStruct->movesetIndex] < 75 && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY)) ADJUST_SCORE(-6); break; @@ -2252,15 +2230,15 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case EFFECT_LOCK_ON: if (gBattleMons[battlerDef].volatiles.lockOn - || aiData->abilities[battlerAtk] == ABILITY_NO_GUARD - || aiData->abilities[battlerDef] == ABILITY_NO_GUARD + || AISearchTraits(AIBattlerTraits, ABILITY_NO_GUARD) + || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_NO_GUARD) || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); break; case EFFECT_LASER_FOCUS: if (gBattleMons[battlerDef].volatiles.laserFocus) ADJUST_SCORE(-10); - else if (aiData->abilities[battlerDef] == ABILITY_SHELL_ARMOR || aiData->abilities[battlerDef] == ABILITY_BATTLE_ARMOR) + else if (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_SHELL_ARMOR) || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_BATTLE_ARMOR)) ADJUST_SCORE(-8); break; case EFFECT_SKETCH: @@ -2323,7 +2301,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if (decreased) break; - if (IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef])) + if (IsBattlerIncapacitated(battlerDef)) { ADJUST_SCORE(-10); break; @@ -2334,7 +2312,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) && protectMethod != PROTECT_CRAFTY_SHIELD) //These moves have infinite usage { if (GetBattlerSecondaryDamage(battlerAtk) >= gBattleMons[battlerAtk].hp - && !(IsMoxieTypeAbility(aiData->abilities[battlerDef]))) + && !(HasMoxieTypeAbility(battlerDef))) { ADJUST_SCORE(-10); //Don't protect if you're going to faint after protecting } @@ -2417,7 +2395,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) // evasion check if (gBattleMons[battlerDef].statStages[STAT_EVASION] == MIN_STAT_STAGE - || ((aiData->abilities[battlerDef] == ABILITY_CONTRARY) && !IsTargetingPartner(battlerAtk, battlerDef))) // don't want to raise target stats unless its your partner + || ((AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_CONTRARY)) && !IsTargetingPartner(battlerAtk, battlerDef))) // don't want to raise target stats unless its your partner ADJUST_SCORE(-10); break; case EFFECT_PSYCH_UP: // haze stats check @@ -2440,7 +2418,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) && GetMoveEffect(predictedMove) == EFFECT_SEMI_INVULNERABLE) ADJUST_SCORE(-10); // Don't Fly/dig/etc if opponent is going to fly/dig/etc after you - if (BattlerWillFaintFromWeather(battlerAtk, aiData->abilities[battlerAtk]) + if (BattlerWillFaintFromWeather(battlerAtk) && GetMoveTwoTurnAttackStatus(move) == STATE_ON_AIR) ADJUST_SCORE(-10); // Attacker will faint while in the air break; @@ -2487,7 +2465,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_YAWN: if (gBattleMons[battlerDef].volatiles.yawn) ADJUST_SCORE(-10); - else if (!AI_CanPutToSleep(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove)) + else if (!AI_CanPutToSleep(battlerAtk, battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); if (PartnerMoveActivatesSleepClause(aiData->partnerMove)) ADJUST_SCORE(-20); @@ -2896,7 +2874,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_SKY_DROP: if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_FLYING)) ADJUST_SCORE(-10); - if (BattlerWillFaintFromWeather(battlerAtk, aiData->abilities[battlerAtk]) + if (BattlerWillFaintFromWeather(battlerAtk) || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) || GetBattlerWeight(battlerDef) >= 2000) //200.0 kg ADJUST_SCORE(-10); @@ -2938,21 +2916,21 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if ((!(gBattleMons[battlerAtk].status1 & STATUS1_ANY) || PartnerMoveEffectIs(BATTLE_PARTNER(battlerAtk), aiData->partnerMove, EFFECT_JUNGLE_HEALING) || PartnerMoveEffectIs(BATTLE_PARTNER(battlerAtk), aiData->partnerMove, EFFECT_HEAL_BELL)) - && !BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPATK) - && !BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF)) + && !BattlerStatCanRise(battlerAtk, STAT_SPATK) + && !BattlerStatCanRise(battlerAtk, STAT_SPDEF)) ADJUST_SCORE(-10); break; case EFFECT_SPICY_EXTRACT: if (battlerAtk != BATTLE_PARTNER(battlerDef) && (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL) - || aiData->abilities[battlerDef] == ABILITY_CLEAR_BODY - || aiData->abilities[battlerDef] == ABILITY_GOOD_AS_GOLD + || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_CLEAR_BODY) + || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_GOOD_AS_GOLD) || aiData->holdEffects[battlerDef] == HOLD_EFFECT_CLEAR_AMULET)) ADJUST_SCORE(-10); break; case EFFECT_UPPER_HAND: { - u32 defPrio = GetBattleMovePriority(battlerDef, aiData->abilities[battlerDef], predictedMove); + u32 defPrio = GetBattleMovePriority(battlerDef, predictedMove); if (predictedMove == MOVE_NONE || IsBattleMoveStatus(predictedMove) || AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY) @@ -2982,27 +2960,27 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { case MOVE_EFFECT_POISON: case MOVE_EFFECT_TOXIC: - if (!AI_CanPoison(battlerAtk, battlerDef, abilityDef, move, aiData->partnerMove)) + if (!AI_CanPoison(battlerAtk, battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); if (!ShouldPoison(battlerAtk, battlerDef)) ADJUST_SCORE(-5); break; case MOVE_EFFECT_SLEEP: - if (!AI_CanPutToSleep(battlerAtk, battlerDef, abilityDef, move, aiData->partnerMove)) + if (!AI_CanPutToSleep(battlerAtk, battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); if (PartnerMoveActivatesSleepClause(aiData->partnerMove)) ADJUST_SCORE(-20); break; case MOVE_EFFECT_PARALYSIS: - if (!AI_CanParalyze(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove)) + if (!AI_CanParalyze(battlerAtk, battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); - if (!ShouldParalyze(battlerAtk, battlerDef, aiData->abilities[battlerDef])) + if (!ShouldParalyze(battlerAtk, battlerDef)) ADJUST_SCORE(-5); break; case MOVE_EFFECT_BURN: - if (!AI_CanBurn(battlerAtk, battlerDef, aiData->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + if (!AI_CanBurn(battlerAtk, battlerDef, BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) ADJUST_SCORE(-10); - if (!ShouldBurn(battlerAtk, battlerDef, aiData->abilities[battlerDef])) + if (!ShouldBurn(battlerAtk, battlerDef)) ADJUST_SCORE(-5); break; } @@ -3064,7 +3042,7 @@ static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } else if (CanTargetFaintAi(battlerDef, battlerAtk) && AI_GetWhichBattlerFasterOrTies(battlerAtk, battlerDef, TRUE) != AI_IS_FASTER - && GetBattleMovePriority(battlerAtk, gAiLogicData->abilities[battlerAtk], move) > 0) + && GetBattleMovePriority(battlerAtk, move) > 0) { if (RandomPercentage(RNG_AI_PRIORITIZE_LAST_CHANCE, PRIORITIZE_LAST_CHANCE_CHANCE)) ADJUST_SCORE(SLOW_KILL + 2); // Don't outscore Fast Kill (which gets a bonus point in AI_CompareDamagingMoves), but do outscore Slow Kill getting the same @@ -3138,7 +3116,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } // check partner move effect // Adjust for always crit moves - if (MoveAlwaysCrits(aiData->partnerMove) && aiData->abilities[battlerAtk] == ABILITY_ANGER_POINT) + if (MoveAlwaysCrits(aiData->partnerMove) && AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_ANGER_POINT)) { if (AI_IsSlower(battlerAtk, battlerAtkPartner, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)) // Partner moving first { @@ -3303,11 +3281,11 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } // Alternatively, it benefits from the ally's death, and it will probably die anyway. - if (IsMoxieTypeAbility(aiData->abilities[battlerAtk])) + if (HasMoxieTypeAbility(battlerAtk)) { ADJUST_SCORE(GOOD_EFFECT); } - if ((aiData->abilities[battlerAtk] == ABILITY_RECEIVER) && !partnerHasBadAbility) + if ((AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_RECEIVER) || AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_RECEIVER)) && !partnerHasBadAbility) { ADJUST_SCORE(GOOD_EFFECT); } @@ -3366,15 +3344,16 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } // partner ability checks - if (!partnerProtecting && moveTarget != MOVE_TARGET_BOTH && !DoesBattlerIgnoreAbilityChecks(battlerAtk, aiData->abilities[battlerAtk], move)) + if (!partnerProtecting && moveTarget != MOVE_TARGET_BOTH && !DoesBattlerIgnoreAbilityChecks(battlerAtk, move)) { - switch (atkPartnerAbility) + enum Ability AIBattlerTraits[MAX_MON_TRAITS]; + AI_STORE_BATTLER_TRAITS(battlerAtkPartner); + if (AISearchTraits(AIBattlerTraits, ABILITY_ANGER_POINT)) { - case ABILITY_ANGER_POINT: - if (MoveAlwaysCrits(move) - && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK) + if ((MoveAlwaysCrits(move) + && BattlerStatCanRise(battlerAtkPartner, STAT_ATK) && AI_IsFaster(battlerAtk, battlerAtkPartner, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY) - && isFriendlyFireOK) + && isFriendlyFireOK)) { if (MoveAlwaysCrits(move)) { @@ -3390,13 +3369,14 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { isMoveAffectedByPartnerAbility = FALSE; } - break; - case ABILITY_LIGHTNING_ROD: - case ABILITY_MOTOR_DRIVE: - case ABILITY_VOLT_ABSORB: - if (moveType == TYPE_ELECTRIC) - { - if (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) < GEN_5 && atkPartnerAbility == ABILITY_LIGHTNING_ROD) + } + if (moveType == TYPE_ELECTRIC) + { + if ((AISearchTraits(AIBattlerTraits, ABILITY_LIGHTNING_ROD) + || AISearchTraits(AIBattlerTraits, ABILITY_MOTOR_DRIVE) + || AISearchTraits(AIBattlerTraits, ABILITY_VOLT_ABSORB))) + { + if (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) < GEN_5 && BattlerHasTrait(battlerAtkPartner, ABILITY_LIGHTNING_ROD)) { RETURN_SCORE_MINUS(10); } @@ -3405,7 +3385,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { ADJUST_SCORE(DECENT_EFFECT); } - else if (ShouldTriggerAbility(battlerAtk, battlerAtkPartner, atkPartnerAbility)) + else if (ShouldTriggerAbility(battlerAtk, battlerAtkPartner)) { RETURN_SCORE_PLUS(WEAK_EFFECT); } @@ -3418,16 +3398,17 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { isMoveAffectedByPartnerAbility = FALSE; } - break; - case ABILITY_EARTH_EATER: - case ABILITY_LEVITATE: + } + if (AISearchTraits(AIBattlerTraits, ABILITY_EARTH_EATER) + || AISearchTraits(AIBattlerTraits, ABILITY_LEVITATE)) + { if (moveType == TYPE_GROUND) { if (moveTarget == MOVE_TARGET_FOES_AND_ALLY) { ADJUST_SCORE(DECENT_EFFECT); } - else if (atkPartnerAbility == ABILITY_EARTH_EATER && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_HP_AWARE)) + else if ((AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerAtk), ABILITY_EARTH_EATER)) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_HP_AWARE)) { RETURN_SCORE_MINUS(10); } @@ -3436,13 +3417,14 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { isMoveAffectedByPartnerAbility = FALSE; } - break; // handled in AI_HPAware - case ABILITY_DRY_SKIN: - case ABILITY_WATER_ABSORB: - case ABILITY_STORM_DRAIN: - if (moveType == TYPE_WATER) + } + if (AISearchTraits(AIBattlerTraits, ABILITY_DRY_SKIN) + || AISearchTraits(AIBattlerTraits, ABILITY_WATER_ABSORB) + || AISearchTraits(AIBattlerTraits, ABILITY_STORM_DRAIN)) + { + if (moveType == TYPE_WATER) { - if (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) < GEN_5 && atkPartnerAbility == ABILITY_STORM_DRAIN) + if (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) < GEN_5 && BattlerHasTrait(battlerAtkPartner, ABILITY_STORM_DRAIN)) { RETURN_SCORE_MINUS(10); } @@ -3451,7 +3433,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { ADJUST_SCORE(DECENT_EFFECT); } - else if (ShouldTriggerAbility(battlerAtk, battlerAtkPartner, atkPartnerAbility)) + else if (ShouldTriggerAbility(battlerAtk, battlerAtkPartner)) { RETURN_SCORE_PLUS(WEAK_EFFECT); } @@ -3461,13 +3443,14 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } } else - { + { isMoveAffectedByPartnerAbility = FALSE; } - break; - case ABILITY_WATER_COMPACTION: + } + if (AISearchTraits(AIBattlerTraits, ABILITY_WATER_COMPACTION)) + { if (moveType == TYPE_WATER && isFriendlyFireOK - && ShouldTriggerAbility(battlerAtk, battlerAtkPartner, atkPartnerAbility)) + && ShouldTriggerAbility(battlerAtk, battlerAtkPartner)) { if (moveTarget == MOVE_TARGET_FOES_AND_ALLY) { @@ -3483,10 +3466,11 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { isMoveAffectedByPartnerAbility = FALSE; } - break; - case ABILITY_STEAM_ENGINE: + } + if (AISearchTraits(AIBattlerTraits, ABILITY_STEAM_ENGINE)) + { if (isFriendlyFireOK && (moveType == TYPE_WATER || moveType == TYPE_FIRE) - && ShouldTriggerAbility(battlerAtk, battlerAtkPartner, atkPartnerAbility)) + && ShouldTriggerAbility(battlerAtk, battlerAtkPartner)) { if (moveTarget == MOVE_TARGET_FOES_AND_ALLY) { @@ -3498,11 +3482,12 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { isMoveAffectedByPartnerAbility = FALSE; } - break; - case ABILITY_THERMAL_EXCHANGE: + } + if (AISearchTraits(AIBattlerTraits, ABILITY_THERMAL_EXCHANGE)) + { if (moveType == TYPE_FIRE && isFriendlyFireOK - && !IsBattleMoveStatus(move) - && ShouldTriggerAbility(battlerAtk, battlerAtkPartner, atkPartnerAbility)) + && !IsBattleMoveStatus(move) + && ShouldTriggerAbility(battlerAtk, battlerAtkPartner)) { if (moveTarget == MOVE_TARGET_FOES_AND_ALLY) { @@ -3520,16 +3505,18 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { isMoveAffectedByPartnerAbility = FALSE; } - break; - case ABILITY_FLASH_FIRE: - case ABILITY_WELL_BAKED_BODY: + } + + if (AISearchTraits(AIBattlerTraits, ABILITY_FLASH_FIRE) + || AISearchTraits(AIBattlerTraits, ABILITY_WELL_BAKED_BODY)) + { if (moveType == TYPE_FIRE) { if (moveTarget == MOVE_TARGET_FOES_AND_ALLY) { ADJUST_SCORE(DECENT_EFFECT); } - if (ShouldTriggerAbility(battlerAtk, battlerAtkPartner, atkPartnerAbility)) + if (ShouldTriggerAbility(battlerAtk, battlerAtkPartner)) { RETURN_SCORE_PLUS(WEAK_EFFECT); } @@ -3538,8 +3525,9 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { isMoveAffectedByPartnerAbility = FALSE; } - break; - case ABILITY_SAP_SIPPER: + } + if (AISearchTraits(AIBattlerTraits, ABILITY_SAP_SIPPER)) + { if (moveType == TYPE_GRASS) { if (moveTarget == MOVE_TARGET_FOES_AND_ALLY) @@ -3547,7 +3535,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(DECENT_EFFECT); } - if (ShouldTriggerAbility(battlerAtk, battlerAtkPartner, atkPartnerAbility)) + if (ShouldTriggerAbility(battlerAtk, battlerAtkPartner)) { RETURN_SCORE_PLUS(WEAK_EFFECT); } @@ -3556,11 +3544,12 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { isMoveAffectedByPartnerAbility = FALSE; } - break; - case ABILITY_JUSTIFIED: + } + if (AISearchTraits(AIBattlerTraits, ABILITY_JUSTIFIED)) + { if (moveType == TYPE_DARK && isFriendlyFireOK && !IsBattleMoveStatus(move) - && ShouldTriggerAbility(battlerAtk, battlerAtkPartner, atkPartnerAbility)) + && ShouldTriggerAbility(battlerAtk, battlerAtkPartner)) { if (moveTarget == MOVE_TARGET_FOES_AND_ALLY) { @@ -3578,11 +3567,12 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { isMoveAffectedByPartnerAbility = FALSE; } - break; - case ABILITY_RATTLED: + } + if (AISearchTraits(AIBattlerTraits, ABILITY_RATTLED)) + { if (!IsBattleMoveStatus(move) && isFriendlyFireOK && (moveType == TYPE_DARK || moveType == TYPE_GHOST || moveType == TYPE_BUG) - && ShouldTriggerAbility(battlerAtk, battlerAtkPartner, atkPartnerAbility)) + && ShouldTriggerAbility(battlerAtk, battlerAtkPartner)) { if (moveTarget == MOVE_TARGET_FOES_AND_ALLY) { @@ -3594,11 +3584,12 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { isMoveAffectedByPartnerAbility = FALSE; } - break; - case ABILITY_CONTRARY: - case ABILITY_DEFIANT: - case ABILITY_COMPETITIVE: - if (IsStatLoweringEffect(effect) && isFriendlyFireOK && ShouldTriggerAbility(battlerAtk, battlerAtkPartner, atkPartnerAbility)) + } + if (AISearchTraits(AIBattlerTraits, ABILITY_CONTRARY) + || AISearchTraits(AIBattlerTraits, ABILITY_DEFIANT) + || AISearchTraits(AIBattlerTraits, ABILITY_COMPETITIVE)) + { + if (IsStatLoweringEffect(effect) && isFriendlyFireOK && ShouldTriggerAbility(battlerAtk, battlerAtkPartner)) { if (moveTarget == MOVE_TARGET_FOES_AND_ALLY) { @@ -3610,7 +3601,6 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { isMoveAffectedByPartnerAbility = FALSE; } - break; } } // ability checks @@ -3659,7 +3649,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_SWAGGER: if (gBattleMons[battlerAtkPartner].statStages[STAT_ATK] < MAX_STAT_STAGE && HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_PHYSICAL) - && (!AI_CanBeConfused(battlerAtk, battlerAtkPartner, move, atkPartnerAbility) + && (!AI_CanBeConfused(battlerAtk, battlerAtkPartner, move) || atkPartnerHoldEffect == HOLD_EFFECT_CURE_CONFUSION || atkPartnerHoldEffect == HOLD_EFFECT_CURE_STATUS)) { @@ -3669,7 +3659,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_FLATTER: if (gBattleMons[battlerAtkPartner].statStages[STAT_SPATK] < MAX_STAT_STAGE && HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_SPECIAL) - && (!AI_CanBeConfused(battlerAtk, battlerAtkPartner, move, atkPartnerAbility) + && (!AI_CanBeConfused(battlerAtk, battlerAtkPartner, move) || atkPartnerHoldEffect == HOLD_EFFECT_CURE_CONFUSION || atkPartnerHoldEffect == HOLD_EFFECT_CURE_STATUS)) { @@ -3677,12 +3667,12 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } break; case EFFECT_BEAT_UP: - if (atkPartnerAbility == ABILITY_JUSTIFIED + if (AI_BATTLER_HAS_TRAIT(battlerAtkPartner, ABILITY_JUSTIFIED) && moveType == TYPE_DARK - && !DoesBattlerIgnoreAbilityChecks(battlerAtk, aiData->abilities[battlerAtk], move) + && !DoesBattlerIgnoreAbilityChecks(battlerAtk, move) && !IsBattleMoveStatus(move) && HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_PHYSICAL) - && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK) + && BattlerStatCanRise(battlerAtkPartner, STAT_ATK) && !wouldPartnerFaint) { if (isFriendlyFireOK) @@ -3693,7 +3683,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } break; case EFFECT_SOAK: - if (atkPartnerAbility == ABILITY_WONDER_GUARD + if (BattlerHasTrait(battlerAtk, ABILITY_WONDER_GUARD) && !IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_WATER) && GetActiveGimmick(battlerAtkPartner) != GIMMICK_TERA) { @@ -3717,6 +3707,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } break; case EFFECT_AFTER_YOU: + if (!(gFieldStatuses & STATUS_FIELD_TRICK_ROOM) && HasMoveWithEffect(battlerAtkPartner, EFFECT_TRICK_ROOM)) ADJUST_SCORE(DECENT_EFFECT); @@ -3784,8 +3775,9 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(WORST_EFFECT); break; } - default: - break; + default:{ + + break;} } // attacker move effects } // check partner protecting @@ -3803,7 +3795,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) switch (effect) { case EFFECT_SKILL_SWAP: - if (aiData->abilities[battlerAtk] == ABILITY_TRUANT) + if (AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_TRUANT)) ADJUST_SCORE(GOOD_EFFECT); else if (IsAbilityOfRating(aiData->abilities[battlerAtk], 0) || IsAbilityOfRating(aiData->abilities[battlerDef], 10)) ADJUST_SCORE(DECENT_EFFECT); // we want to transfer our bad ability or take their awesome ability @@ -4096,6 +4088,9 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru bool32 moveTargetsBothOpponents = hasTwoOpponents && (GetMoveTarget(move) & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY | MOVE_TARGET_ALL_BATTLERS)); u32 i; + enum Ability AIBattlerTraits[MAX_MON_TRAITS]; + AI_STORE_BATTLER_TRAITS(battlerAtk); + // The AI should understand that while Dynamaxed, status moves function like Protect. if (GetActiveGimmick(battlerAtk) == GIMMICK_DYNAMAX && GetMoveCategory(move) == DAMAGE_CATEGORY_STATUS) moveEffect = EFFECT_PROTECT; @@ -4110,19 +4105,18 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru && (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerAtk] != HOLD_EFFECT_MENTAL_HERB)) { if (!AI_IsAbilityOnSide(battlerAtk, ABILITY_AROMA_VEIL) - || IsMoldBreakerTypeAbility(battlerDef, aiData->abilities[battlerDef]) - || aiData->abilities[battlerDef] == ABILITY_MYCELIUM_MIGHT - || IsMoldBreakerTypeAbility(BATTLE_PARTNER(battlerDef), aiData->abilities[BATTLE_PARTNER(battlerDef)]) - || aiData->abilities[BATTLE_PARTNER(battlerDef)] == ABILITY_MYCELIUM_MIGHT) + || HasMoldBreakerTypeAbility(battlerDef) + || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_MYCELIUM_MIGHT) + || HasMoldBreakerTypeAbility(BATTLE_PARTNER(battlerDef)) + || AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerDef), ABILITY_MYCELIUM_MIGHT)) return score; } - // check thawing moves if (gBattleMons[battlerAtk].status1 & STATUS1_ICY_ANY && MoveThawsUser(move)) ADJUST_SCORE(10); // check burn / frostbite - if (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_SMART_SWITCHING && aiData->abilities[battlerAtk] == ABILITY_NATURAL_CURE) + if (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_SMART_SWITCHING && AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_NATURAL_CURE)) { if ((gBattleMons[battlerAtk].status1 & STATUS1_BURN && HasOnlyMovesWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL, TRUE)) || (gBattleMons[battlerAtk].status1 & STATUS1_FROSTBITE && HasOnlyMovesWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL, TRUE))) @@ -4285,24 +4279,24 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru ADJUST_SCORE(IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPATK_2)); break; case EFFECT_GEAR_UP: - if (aiData->abilities[battlerAtk] == ABILITY_PLUS || aiData->abilities[battlerAtk] == ABILITY_MINUS) + if (AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_PLUS) || AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_MINUS)) { ADJUST_SCORE(IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_ATK)); ADJUST_SCORE(IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPATK)); } - if (hasPartner && (aiData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_PLUS || aiData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_MINUS)) + if (hasPartner && (AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerAtk), ABILITY_PLUS) || AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerAtk), ABILITY_MINUS))) { ADJUST_SCORE(IncreaseStatUpScore(BATTLE_PARTNER(battlerAtk), battlerDef, STAT_CHANGE_ATK)); ADJUST_SCORE(IncreaseStatUpScore(BATTLE_PARTNER(battlerAtk), battlerDef, STAT_CHANGE_SPATK)); } break; case EFFECT_MAGNETIC_FLUX: - if (aiData->abilities[battlerAtk] == ABILITY_PLUS || aiData->abilities[battlerAtk] == ABILITY_MINUS) + if (AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_PLUS) || AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_MINUS)) { ADJUST_SCORE(IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF)); ADJUST_SCORE(IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPDEF)); } - if (hasPartner && (aiData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_PLUS || aiData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_MINUS)) + if (hasPartner && (AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerAtk), ABILITY_PLUS) || AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerAtk), ABILITY_MINUS))) { ADJUST_SCORE(IncreaseStatUpScore(BATTLE_PARTNER(battlerAtk), battlerDef, STAT_CHANGE_DEF)); ADJUST_SCORE(IncreaseStatUpScore(BATTLE_PARTNER(battlerAtk), battlerDef, STAT_CHANGE_SPDEF)); @@ -4330,14 +4324,14 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru } if (IS_BATTLER_OF_TYPE(LEFT_FOE(battlerAtk), TYPE_GRASS) && AI_IsBattlerGrounded(LEFT_FOE(battlerAtk))) { - if (aiData->abilities[LEFT_FOE(battlerAtk)] == ABILITY_CONTRARY) + if (AI_BATTLER_HAS_TRAIT(LEFT_FOE(battlerAtk), ABILITY_CONTRARY)) ADJUST_SCORE(WEAK_EFFECT); else ADJUST_SCORE(AWFUL_EFFECT); } if (IS_BATTLER_OF_TYPE(RIGHT_FOE(battlerAtk), TYPE_GRASS) && AI_IsBattlerGrounded(RIGHT_FOE(battlerAtk))) { - if (aiData->abilities[RIGHT_FOE(battlerAtk)] == ABILITY_CONTRARY) + if (AI_BATTLER_HAS_TRAIT(RIGHT_FOE(battlerAtk), ABILITY_CONTRARY)) ADJUST_SCORE(WEAK_EFFECT); else ADJUST_SCORE(AWFUL_EFFECT); @@ -4354,14 +4348,14 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru } if (IS_BATTLER_OF_TYPE(LEFT_FOE(battlerAtk), TYPE_GRASS)) { - if (aiData->abilities[LEFT_FOE(battlerAtk)] == ABILITY_CONTRARY) + if (AI_BATTLER_HAS_TRAIT(LEFT_FOE(battlerAtk), ABILITY_CONTRARY)) ADJUST_SCORE(WEAK_EFFECT); else ADJUST_SCORE(AWFUL_EFFECT); } if (IS_BATTLER_OF_TYPE(RIGHT_FOE(battlerAtk), TYPE_GRASS)) { - if (aiData->abilities[RIGHT_FOE(battlerAtk)] == ABILITY_CONTRARY) + if (AI_BATTLER_HAS_TRAIT(RIGHT_FOE(battlerAtk), ABILITY_CONTRARY)) ADJUST_SCORE(WEAK_EFFECT); else ADJUST_SCORE(AWFUL_EFFECT); @@ -4374,8 +4368,8 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru score += AI_TryToClearStats(battlerAtk, battlerDef, moveTargetsBothOpponents); break; case EFFECT_ROAR: - if ((IsSoundMove(move) && aiData->abilities[battlerDef] == ABILITY_SOUNDPROOF) - || aiData->abilities[battlerDef] == ABILITY_SUCTION_CUPS) + if ((IsSoundMove(move) && AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_SOUNDPROOF)) + || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_SUCTION_CUPS)) break; else if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) break; @@ -4384,8 +4378,8 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru case EFFECT_MULTI_HIT: case EFFECT_TRIPLE_KICK: case EFFECT_POPULATION_BOMB: - if (AI_MoveMakesContact(aiData->abilities[battlerAtk], aiData->holdEffects[battlerAtk], move) - && aiData->abilities[battlerAtk] != ABILITY_MAGIC_GUARD + if (AI_MoveMakesContact(aiData->holdEffects[battlerAtk], move, battlerAtk) + && !AISearchTraits(AIBattlerTraits, ABILITY_MAGIC_GUARD) && aiData->holdEffects[battlerDef] == HOLD_EFFECT_ROCKY_HELMET) ADJUST_SCORE(-2); break; @@ -4393,7 +4387,7 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru if (!IS_BATTLER_OF_TYPE(battlerAtk, GetMoveType(gBattleMons[battlerAtk].moves[0]))) { ADJUST_SCORE(WEAK_EFFECT); - if (aiData->abilities[battlerAtk] == ABILITY_ADAPTABILITY) + if (AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_ADAPTABILITY)) ADJUST_SCORE(WEAK_EFFECT); if (IsConsideringZMove(battlerAtk, battlerDef, move)) ADJUST_SCORE(BEST_EFFECT); @@ -4452,7 +4446,7 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru } break; case EFFECT_REST: - if (!(CanBeSlept(battlerAtk, battlerAtk, aiData->abilities[battlerAtk], NOT_BLOCKED_BY_SLEEP_CLAUSE))) + if (!(CanBeSlept(battlerAtk, battlerAtk, NOT_BLOCKED_BY_SLEEP_CLAUSE))) { break; } @@ -4462,9 +4456,9 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru || aiData->holdEffects[battlerAtk] == HOLD_EFFECT_CURE_STATUS || HasMoveWithEffect(battlerAtk, EFFECT_SLEEP_TALK) || HasMoveWithEffect(battlerAtk, EFFECT_SNORE) - || aiData->abilities[battlerAtk] == ABILITY_SHED_SKIN - || aiData->abilities[battlerAtk] == ABILITY_EARLY_BIRD - || (AI_GetWeather() & B_WEATHER_RAIN && gWishFutureKnock.weatherDuration != 1 && aiData->abilities[battlerAtk] == ABILITY_HYDRATION && aiData->holdEffects[battlerAtk] != HOLD_EFFECT_UTILITY_UMBRELLA)) + || AISearchTraits(AIBattlerTraits, ABILITY_SHED_SKIN) + || AISearchTraits(AIBattlerTraits, ABILITY_EARLY_BIRD) + || (AI_GetWeather() & B_WEATHER_RAIN && gWishFutureKnock.weatherDuration != 1 && AISearchTraits(AIBattlerTraits, ABILITY_HYDRATION) && aiData->holdEffects[battlerAtk] != HOLD_EFFECT_UTILITY_UMBRELLA)) ADJUST_SCORE(GOOD_EFFECT); } break; @@ -4481,8 +4475,8 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru break; case EFFECT_FOCUS_ENERGY: case EFFECT_LASER_FOCUS: - if (aiData->abilities[battlerAtk] == ABILITY_SUPER_LUCK - || aiData->abilities[battlerAtk] == ABILITY_SNIPER + if (AISearchTraits(AIBattlerTraits, ABILITY_SUPER_LUCK) + || AISearchTraits(AIBattlerTraits, ABILITY_SNIPER) || aiData->holdEffects[battlerAtk] == HOLD_EFFECT_SCOPE_LENS || HasMoveWithFlag(battlerAtk, GetMoveCriticalHitStage)) ADJUST_SCORE(GOOD_EFFECT); @@ -4506,8 +4500,8 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) || gBattleMons[battlerDef].volatiles.leechSeed || HasMoveWithEffect(battlerDef, EFFECT_RAPID_SPIN) - || aiData->abilities[battlerDef] == ABILITY_LIQUID_OOZE - || aiData->abilities[battlerDef] == ABILITY_MAGIC_GUARD) + || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_LIQUID_OOZE) + || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_MAGIC_GUARD)) break; ADJUST_SCORE(GOOD_EFFECT); if (!HasDamagingMove(battlerDef) @@ -4532,7 +4526,7 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru case EFFECT_CHILLY_RECEPTION: if (!hasPartner) { - switch (ShouldPivot(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, movesetIndex)) + switch (ShouldPivot(battlerAtk, battlerDef, move, movesetIndex)) { case DONT_PIVOT: ADJUST_SCORE(-10); // technically should go in CheckBadMove, but this is easier/less computationally demanding @@ -4661,7 +4655,7 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru } else if (hasPartner && GetBattlerMoveTargetType(BATTLE_PARTNER(battlerAtk), aiData->partnerMove) & MOVE_TARGET_FOES_AND_ALLY) { - if (aiData->abilities[battlerAtk] != ABILITY_TELEPATHY) + if (!AISearchTraits(AIBattlerTraits, ABILITY_TELEPATHY)) ADJUST_SCORE(ProtectChecks(battlerAtk, battlerDef, move, predictedMove)); } break; @@ -4676,9 +4670,9 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru ADJUST_SCORE(ProtectChecks(battlerAtk, battlerDef, move, predictedMove)); break; case PROTECT_KINGS_SHIELD: - if (aiData->abilities[battlerAtk] == ABILITY_STANCE_CHANGE //Special logic for Aegislash + if (AISearchTraits(AIBattlerTraits, ABILITY_STANCE_CHANGE) //Special logic for Aegislash && gBattleMons[battlerAtk].species == SPECIES_AEGISLASH_BLADE - && !IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef])) + && !IsBattlerIncapacitated(battlerDef)) { ADJUST_SCORE(GOOD_EFFECT); break; @@ -4715,7 +4709,7 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru } break; case EFFECT_FORESIGHT: - if (aiData->abilities[battlerAtk] == ABILITY_SCRAPPY || aiData->abilities[battlerAtk] == ABILITY_MINDS_EYE) + if (AISearchTraits(AIBattlerTraits, ABILITY_SCRAPPY) || AISearchTraits(AIBattlerTraits, ABILITY_MINDS_EYE)) break; else if (gBattleMons[battlerDef].statStages[STAT_EVASION] > DEFAULT_STAT_STAGE || (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GHOST) @@ -4816,7 +4810,7 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru break; case EFFECT_FELL_STINGER: if (gBattleMons[battlerAtk].statStages[STAT_ATK] < MAX_STAT_STAGE - && aiData->abilities[battlerAtk] != ABILITY_CONTRARY + && !AISearchTraits(AIBattlerTraits, ABILITY_CONTRARY) && CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, AI_ATTACKING)) ADJUST_SCORE(BEST_EFFECT); break; @@ -4824,7 +4818,7 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru if (!CanTargetFaintAi(battlerDef, battlerAtk) && gBattleMons[battlerAtk].statStages[STAT_ATK] < MAX_STAT_STAGE - 2 && HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL) - && aiData->abilities[battlerAtk] != ABILITY_CONTRARY) + && !AISearchTraits(AIBattlerTraits, ABILITY_CONTRARY)) ADJUST_SCORE(BEST_EFFECT); break; case EFFECT_PSYCH_UP: @@ -4853,7 +4847,7 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru ADJUST_SCORE(BEST_EFFECT); break; case EFFECT_STOCKPILE: - if (aiData->abilities[battlerAtk] == ABILITY_CONTRARY) + if (AISearchTraits(AIBattlerTraits, ABILITY_CONTRARY)) break; if (HasMoveWithEffect(battlerAtk, EFFECT_SWALLOW) || HasMoveWithEffect(battlerAtk, EFFECT_SPIT_UP)) ADJUST_SCORE(DECENT_EFFECT); @@ -4865,7 +4859,7 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru || HasBattlerSideMoveWithEffect(battlerAtk, EFFECT_PSYCH_UP) || HasBattlerSideMoveWithEffect(battlerAtk, EFFECT_SPECTRAL_THIEF)) ADJUST_SCORE(DECENT_EFFECT); - if (aiData->abilities[battlerDef] == ABILITY_CONTRARY) + if (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_CONTRARY)) ADJUST_SCORE(GOOD_EFFECT); IncreaseConfusionScore(battlerAtk, battlerDef, move, &score); break; @@ -4873,7 +4867,7 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru if (HasBattlerSideMoveWithEffect(battlerAtk, EFFECT_PSYCH_UP) || HasBattlerSideMoveWithEffect(battlerAtk, EFFECT_SPECTRAL_THIEF)) ADJUST_SCORE(DECENT_EFFECT); - if (aiData->abilities[battlerDef] == ABILITY_CONTRARY) + if (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_CONTRARY)) ADJUST_SCORE(GOOD_EFFECT); IncreaseConfusionScore(battlerAtk, battlerDef, move, &score); break; @@ -4884,7 +4878,7 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru case EFFECT_ATTRACT: if (isBattle1v1 && (AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)) - && BattlerWillFaintFromSecondaryDamage(battlerDef, aiData->abilities[battlerDef])) + && BattlerWillFaintFromSecondaryDamage(battlerDef)) break; // Don't use if the attract won't have a change to activate if (gBattleMons[battlerDef].status1 & STATUS1_ANY || gBattleMons[battlerDef].volatiles.confusionTurns > 0 @@ -4894,7 +4888,7 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru ADJUST_SCORE(DECENT_EFFECT); break; case EFFECT_SAFEGUARD: - if (!IsBattlerTerrainAffected(battlerAtk, aiData->abilities[battlerAtk], aiData->holdEffects[battlerAtk], STATUS_FIELD_MISTY_TERRAIN) || !AI_IsBattlerGrounded(battlerAtk)) + if (!IsBattlerTerrainAffected(battlerAtk, aiData->holdEffects[battlerAtk], STATUS_FIELD_MISTY_TERRAIN) || !AI_IsBattlerGrounded(battlerAtk)) ADJUST_SCORE(DECENT_EFFECT); // TODO: check if opp has status move? //if (CountUsablePartyMons(battlerDef) != 0) //ADJUST_SCORE(8); @@ -4942,8 +4936,8 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru case EFFECT_FOLLOW_ME: if (hasPartner && GetMoveTarget(move) == MOVE_TARGET_USER - && !IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef]) - && (!IsPowderMove(move) || IsAffectedByPowderMove(battlerDef, aiData->abilities[battlerDef], aiData->holdEffects[battlerDef]))) + && !IsBattlerIncapacitated(battlerDef) + && (!IsPowderMove(move) || IsAffectedByPowderMove(battlerDef, aiData->holdEffects[battlerDef]))) // Rage Powder doesn't affect powder immunities { u32 predictedMoveOnPartner = aiData->lastUsedMove[BATTLE_PARTNER(battlerAtk)]; @@ -4983,11 +4977,11 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru ADJUST_SCORE(DECENT_EFFECT); break; case HOLD_EFFECT_FLAME_ORB: - if (!ShouldBurn(battlerAtk, battlerAtk, aiData->abilities[battlerAtk])) + if (!ShouldBurn(battlerAtk, battlerAtk)) ADJUST_SCORE(DECENT_EFFECT); break; case HOLD_EFFECT_BLACK_SLUDGE: - if (!IS_BATTLER_OF_TYPE(battlerDef, TYPE_POISON) && aiData->abilities[battlerDef] != ABILITY_MAGIC_GUARD) + if (!IS_BATTLER_OF_TYPE(battlerDef, TYPE_POISON) && !AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_MAGIC_GUARD)) ADJUST_SCORE(DECENT_EFFECT); break; case HOLD_EFFECT_IRON_BALL: @@ -4999,21 +4993,17 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru ADJUST_SCORE(DECENT_EFFECT); break; case HOLD_EFFECT_UTILITY_UMBRELLA: - if (aiData->abilities[battlerAtk] != ABILITY_SOLAR_POWER && aiData->abilities[battlerAtk] != ABILITY_DRY_SKIN) + if (!AISearchTraits(AIBattlerTraits, ABILITY_SOLAR_POWER) && !AISearchTraits(AIBattlerTraits, ABILITY_DRY_SKIN)) { - switch (aiData->abilities[battlerDef]) + if (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_SWIFT_SWIM) + && (AI_GetWeather() & B_WEATHER_RAIN)) { - case ABILITY_SWIFT_SWIM: - if (AI_GetWeather() & B_WEATHER_RAIN) - ADJUST_SCORE(DECENT_EFFECT); // Slow 'em down - break; - case ABILITY_CHLOROPHYLL: - case ABILITY_FLOWER_GIFT: - if (AI_GetWeather() & B_WEATHER_SUN) - ADJUST_SCORE(DECENT_EFFECT); // Slow 'em down - break; - default: - break; + ADJUST_SCORE(DECENT_EFFECT); // Slow 'em down + } + if ((AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_CHLOROPHYLL) || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_FLOWER_GIFT)) + && (AI_GetWeather() & B_WEATHER_SUN)) + { + ADJUST_SCORE(DECENT_EFFECT); // Slow 'em down } } break; @@ -5035,11 +5025,11 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru ADJUST_SCORE(DECENT_EFFECT); break; case HOLD_EFFECT_FLAME_ORB: - if (ShouldBurn(battlerAtk, battlerAtk, aiData->abilities[battlerAtk])) + if (ShouldBurn(battlerAtk, battlerAtk)) ADJUST_SCORE(DECENT_EFFECT); break; case HOLD_EFFECT_BLACK_SLUDGE: - if (IS_BATTLER_OF_TYPE(battlerAtk, TYPE_POISON) || aiData->abilities[battlerAtk] == ABILITY_MAGIC_GUARD) + if (IS_BATTLER_OF_TYPE(battlerAtk, TYPE_POISON) || AISearchTraits(AIBattlerTraits, ABILITY_MAGIC_GUARD)) ADJUST_SCORE(DECENT_EFFECT); break; case HOLD_EFFECT_IRON_BALL: @@ -5088,7 +5078,7 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru ADJUST_SCORE(WEAK_EFFECT); if (IsRecycleEncouragedItem(GetBattlerPartyState(battlerAtk)->usedHeldItem)) ADJUST_SCORE(WEAK_EFFECT); - if (aiData->abilities[battlerAtk] == ABILITY_RIPEN) + if (AISearchTraits(AIBattlerTraits, ABILITY_RIPEN)) { u32 item = GetBattlerPartyState(battlerAtk)->usedHeldItem; u32 toHeal = (GetItemHoldEffectParam(item) == 10) ? 10 : gBattleMons[battlerAtk].maxHP / GetItemHoldEffectParam(item); @@ -5131,8 +5121,8 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru break; case EFFECT_TAKE_HEART: if (gBattleMons[battlerAtk].status1 & STATUS1_ANY - || BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPATK) - || BattlerStatCanRise(battlerAtk, aiData->abilities[battlerAtk], STAT_SPDEF)) + || BattlerStatCanRise(battlerAtk, STAT_SPATK) + || BattlerStatCanRise(battlerAtk, STAT_SPDEF)) ADJUST_SCORE(DECENT_EFFECT); break; case EFFECT_PSYCHO_SHIFT: @@ -5393,9 +5383,9 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru } break; case EFFECT_ION_DELUGE: - if ((aiData->abilities[battlerAtk] == ABILITY_VOLT_ABSORB - || aiData->abilities[battlerAtk] == ABILITY_MOTOR_DRIVE - || (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) >= GEN_5 && aiData->abilities[battlerAtk] == ABILITY_LIGHTNING_ROD)) + if ((AISearchTraits(AIBattlerTraits, ABILITY_VOLT_ABSORB) + || AISearchTraits(AIBattlerTraits, ABILITY_MOTOR_DRIVE) + || (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) >= GEN_5 && AISearchTraits(AIBattlerTraits, ABILITY_LIGHTNING_ROD))) && predictedType == TYPE_NORMAL) ADJUST_SCORE(DECENT_EFFECT); break; @@ -5448,14 +5438,14 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru ADJUST_SCORE(DECENT_EFFECT); // Get some super effective moves break; case EFFECT_THIRD_TYPE: - if (aiData->abilities[battlerDef] == ABILITY_WONDER_GUARD) + if (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_WONDER_GUARD)) ADJUST_SCORE(DECENT_EFFECT); // Give target more weaknesses break; case EFFECT_ELECTRIFY: if (predictedMove != MOVE_NONE - && (aiData->abilities[battlerAtk] == ABILITY_VOLT_ABSORB - || aiData->abilities[battlerAtk] == ABILITY_MOTOR_DRIVE - || (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) >= GEN_5 && aiData->abilities[battlerAtk] == ABILITY_LIGHTNING_ROD))) + && (AISearchTraits(AIBattlerTraits, ABILITY_VOLT_ABSORB) + || AISearchTraits(AIBattlerTraits, ABILITY_MOTOR_DRIVE) + || (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) >= GEN_5 && AISearchTraits(AIBattlerTraits, ABILITY_LIGHTNING_ROD)))) { ADJUST_SCORE(DECENT_EFFECT); } @@ -5543,13 +5533,13 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru ADJUST_SCORE(IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_SPEED)); break; case EFFECT_COUNTER: - if ((!IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef]) && predictedMove != MOVE_NONE) + if ((!IsBattlerIncapacitated(battlerDef) && predictedMove != MOVE_NONE) && (GetNoOfHitsToKOBattler(battlerDef, battlerAtk, predictedMoveSlot, AI_DEFENDING) >= 2) && (GetBattleMoveCategory(predictedMove) == DAMAGE_CATEGORY_PHYSICAL)) ADJUST_SCORE(GOOD_EFFECT); break; case EFFECT_MIRROR_COAT: - if ((!IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef]) && predictedMove != MOVE_NONE) + if ((!IsBattlerIncapacitated(battlerDef) && predictedMove != MOVE_NONE) && (GetNoOfHitsToKOBattler(battlerDef, battlerAtk, predictedMoveSlot, AI_DEFENDING) >= 2) && (GetBattleMoveCategory(predictedMove) == DAMAGE_CATEGORY_SPECIAL)) ADJUST_SCORE(GOOD_EFFECT); @@ -5639,7 +5629,7 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru && CanBattlerGetOrLoseItem(battlerDef, aiData->items[battlerDef]) && CanBattlerGetOrLoseItem(battlerAtk, aiData->items[battlerDef]) && !HasMoveWithEffect(battlerAtk, EFFECT_ACROBATICS) - && aiData->abilities[battlerDef] != ABILITY_STICKY_HOLD) + && !AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_STICKY_HOLD)) { switch (aiData->holdEffects[battlerDef]) { @@ -5655,7 +5645,7 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move, stru ADJUST_SCORE(DECENT_EFFECT); break; case HOLD_EFFECT_FLAME_ORB: - if (ShouldBurn(battlerAtk, battlerAtk, aiData->abilities[battlerAtk])) + if (ShouldBurn(battlerAtk, battlerAtk)) ADJUST_SCORE(DECENT_EFFECT); break; case HOLD_EFFECT_BLACK_SLUDGE: @@ -5694,7 +5684,7 @@ static s32 AI_CalcAdditionalEffectScore(u32 battlerAtk, u32 battlerDef, u32 move u32 i; u32 additionalEffectCount = GetMoveAdditionalEffectCount(move); - if (IsSheerForceAffected(move, aiData->abilities[battlerAtk])) + if (IsSheerForceAffected(move, battlerAtk)) return score; // check move additional effects that are likely to happen @@ -5703,7 +5693,7 @@ static s32 AI_CalcAdditionalEffectScore(u32 battlerAtk, u32 battlerDef, u32 move const struct AdditionalEffect *additionalEffect = GetMoveAdditionalEffectById(move, i); // Only consider effects with a guaranteed chance to happen - if (!MoveEffectIsGuaranteed(battlerAtk, aiData->abilities[battlerAtk], additionalEffect)) + if (!MoveEffectIsGuaranteed(battlerAtk, additionalEffect)) continue; // Consider move effects that target self @@ -5711,7 +5701,7 @@ static s32 AI_CalcAdditionalEffectScore(u32 battlerAtk, u32 battlerDef, u32 move { enum StatChange StageStatId; - if (aiData->abilities[battlerAtk] != ABILITY_CONTRARY) + if (!AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_CONTRARY)) { switch (additionalEffect->moveEffect) { @@ -5791,13 +5781,13 @@ static s32 AI_CalcAdditionalEffectScore(u32 battlerAtk, u32 battlerDef, u32 move } else // consider move effects that hinder the target { - if (IsAdditionalEffectBlocked(battlerAtk, aiData->abilities[battlerAtk], battlerDef, aiData->abilities[battlerDef])) + if (IsAdditionalEffectBlocked(battlerAtk, battlerDef)) continue; switch (additionalEffect->moveEffect) { case MOVE_EFFECT_FLINCH: - score += ShouldTryToFlinch(battlerAtk, battlerDef, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], move); + score += ShouldTryToFlinch(battlerAtk, battlerDef, move); break; case MOVE_EFFECT_SPD_MINUS_1: case MOVE_EFFECT_SPD_MINUS_2: @@ -5870,13 +5860,13 @@ static s32 AI_CalcAdditionalEffectScore(u32 battlerAtk, u32 battlerDef, u32 move break; } case MOVE_EFFECT_BUG_BITE: // And pluck - if (gBattleMons[battlerDef].volatiles.substitute || aiData->abilities[battlerDef] == ABILITY_STICKY_HOLD) + if (gBattleMons[battlerDef].volatiles.substitute || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_STICKY_HOLD)) break; else if (GetItemPocket(aiData->items[battlerDef]) == POCKET_BERRIES) ADJUST_SCORE(DECENT_EFFECT); break; case MOVE_EFFECT_INCINERATE: - if (gBattleMons[battlerDef].volatiles.substitute || aiData->abilities[battlerDef] == ABILITY_STICKY_HOLD) + if (gBattleMons[battlerDef].volatiles.substitute || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_STICKY_HOLD)) break; else if (GetItemPocket(aiData->items[battlerDef]) == POCKET_BERRIES || aiData->holdEffects[battlerDef] == HOLD_EFFECT_GEMS) ADJUST_SCORE(DECENT_EFFECT); @@ -6044,7 +6034,7 @@ static s32 AI_ForceSetupFirstTurn(u32 battlerAtk, u32 battlerDef, u32 move, s32 if (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_SMART_SWITCHING && AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY) && CanTargetFaintAi(battlerDef, battlerAtk) - && GetBattleMovePriority(battlerAtk, gAiLogicData->abilities[battlerAtk], move) == 0) + && GetBattleMovePriority(battlerAtk, move) == 0) { RETURN_SCORE_MINUS(20); // No point in setting up if you will faint. Should just switch if possible.. } @@ -6323,10 +6313,13 @@ static s32 AI_HPAware(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if (IsTargetingPartner(battlerAtk, battlerDef)) { + enum Ability AIBattlerTraits[MAX_MON_TRAITS]; + AI_STORE_BATTLER_TRAITS(BATTLE_PARTNER(battlerAtk)); + if ((effect == EFFECT_HEAL_PULSE || effect == EFFECT_HIT_ENEMY_HEAL_ALLY) - || (moveType == TYPE_ELECTRIC && gAiLogicData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_VOLT_ABSORB) - || (moveType == TYPE_GROUND && gAiLogicData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_EARTH_EATER) - || (moveType == TYPE_WATER && (gAiLogicData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_DRY_SKIN || gAiLogicData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_WATER_ABSORB))) + || (moveType == TYPE_ELECTRIC && AISearchTraits(AIBattlerTraits, ABILITY_VOLT_ABSORB)) + || (moveType == TYPE_GROUND && AISearchTraits(AIBattlerTraits, ABILITY_EARTH_EATER)) + || (moveType == TYPE_WATER && (AISearchTraits(AIBattlerTraits, ABILITY_DRY_SKIN) || AISearchTraits(AIBattlerTraits, ABILITY_WATER_ABSORB)))) { if (gBattleMons[battlerDef].volatiles.healBlock) return 0; @@ -6625,7 +6618,6 @@ static s32 AI_PredictSwitch(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { u32 i; u32 unmodifiedScore = score; - enum Ability ability = gBattleMons[battlerAtk].ability; bool32 opposingHazardFlags = DoesSideHaveDamagingHazards(GetBattlerSide(battlerDef)); bool32 aiHazardFlags = AreAnyHazardsOnSide(GetBattlerSide(battlerAtk)); enum BattleMoveEffects moveEffect = GetMoveEffect(move); @@ -6653,10 +6645,10 @@ static s32 AI_PredictSwitch(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(DECENT_EFFECT); if (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CHECK_BAD_MOVE) { - if (aiData->abilities[battlerDef] == ABILITY_WONDER_GUARD && effectiveness < UQ_4_12(2.0)) + if ((AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_WONDER_GUARD)) && effectiveness < UQ_4_12(2.0)) ADJUST_SCORE(10); if (HasDamagingMove(battlerDef) && !(gBattleMons[battlerAtk].volatiles.substitute - || IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef]) + || IsBattlerIncapacitated(battlerDef) || gBattleMons[battlerDef].volatiles.infatuation || gBattleMons[battlerDef].volatiles.confusionTurns > 0)) ADJUST_SCORE(10); @@ -6770,7 +6762,7 @@ static s32 AI_PredictSwitch(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } // Take advantage of ability damage bonus - if ((ability == ABILITY_STAKEOUT || ability == ABILITY_ANALYTIC) && IsBattleMoveStatus(move)) + if ((AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_STAKEOUT) || AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_ANALYTIC)) && IsBattleMoveStatus(move)) ADJUST_SCORE(BAD_EFFECT); // This must be last or the player can gauge whether the AI is predicting based on how long it thinks @@ -6803,7 +6795,7 @@ static s32 AI_Roaming(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if (AI_CanBattlerEscape(battlerAtk)) roamerCanFlee = TRUE; - else if (gAiLogicData->abilities[battlerAtk] == ABILITY_RUN_AWAY) + else if (AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_RUN_AWAY)) roamerCanFlee = TRUE; else if (gAiLogicData->holdEffects[battlerAtk] == HOLD_EFFECT_CAN_ALWAYS_RUN) roamerCanFlee = TRUE; diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index defc1c3c8955..e0d30fbbdbcc 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -28,7 +28,7 @@ static bool32 AiExpectsToFaintPlayer(u32 battler); static bool32 AI_ShouldHeal(u32 battler, u32 healAmount); static bool32 AI_OpponentCanFaintAiWithMod(u32 battler, u32 healAmount); static u32 GetSwitchinHazardsDamage(u32 battler, struct BattlePokemon *battleMon); -static bool32 CanAbilityTrapOpponent(enum Ability ability, u32 opponent); +static bool32 CanAbilityTrapOpponent(enum Ability ability, u32 opponent, u32 species); static u32 GetHPHealAmount(u8 itemEffectParam, struct Pokemon *mon); static u32 GetBattleMonTypeMatchup(struct BattlePokemon opposingBattleMon, struct BattlePokemon battleMon); @@ -206,7 +206,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) //Variable initialization u8 opposingPosition; s32 i, damageDealt = 0, maxDamageDealt = 0, damageTaken = 0, maxDamageTaken = 0, maxDamageTakenPriority = 0; - u32 aiMove, playerMove, bestPlayerPriorityMove = MOVE_NONE, bestPlayerMove = MOVE_NONE, expectedMove = MOVE_NONE, aiAbility = gAiLogicData->abilities[battler], opposingBattler; + u32 aiMove, playerMove, bestPlayerPriorityMove = MOVE_NONE, bestPlayerMove = MOVE_NONE, expectedMove = MOVE_NONE, opposingBattler; bool32 getsOneShot = FALSE, hasStatusMove = FALSE, hasSuperEffectiveMove = FALSE; u32 typeMatchup; enum BattleMoveEffects aiMoveEffect; @@ -237,7 +237,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) maxDamageTaken = damageTaken; bestPlayerMove = playerMove; } - if (GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], playerMove) > 0 && damageTaken > maxDamageTakenPriority && !AI_DoesChoiceEffectBlockMove(opposingBattler, playerMove)) + if (GetBattleMovePriority(opposingBattler, playerMove) > 0 && damageTaken > maxDamageTakenPriority && !AI_DoesChoiceEffectBlockMove(opposingBattler, playerMove)) { maxDamageTakenPriority = damageTaken; bestPlayerPriorityMove = playerMove; @@ -298,7 +298,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) // Check if mon gets one shot if (maxDamageTaken > gBattleMons[battler].hp - && !(gItemsInfo[gBattleMons[battler].item].holdEffect == HOLD_EFFECT_FOCUS_SASH || (!IsMoldBreakerTypeAbility(opposingBattler, gAiLogicData->abilities[opposingBattler]) && GetConfig(CONFIG_STURDY) >= GEN_5 && aiAbility == ABILITY_STURDY))) + && !(gItemsInfo[gBattleMons[battler].item].holdEffect == HOLD_EFFECT_FOCUS_SASH || (!HasMoldBreakerTypeAbility(opposingBattler) && GetConfig(CONFIG_STURDY) >= GEN_5 && AI_BATTLER_HAS_TRAIT(battler, ABILITY_STURDY)))) { getsOneShot = TRUE; } @@ -314,7 +314,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) // Start assessing whether or not mon has bad odds // Jump straight to switching out in cases where mon gets OHKO'd if ((getsOneShot && !canBattlerWin1v1) && (gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2 // And the current mon has at least 1/2 their HP, or 1/4 HP and Regenerator - || (aiAbility == ABILITY_REGENERATOR && gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 4))) + || (AI_BATTLER_HAS_TRAIT(battler, ABILITY_REGENERATOR) && gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 4))) { // 50% chance to stay in regardless if (RandomPercentage(RNG_AI_SWITCH_HASBADODDS, (100 - GetSwitchChance(SHOULD_SWITCH_HASBADODDS))) && !gAiLogicData->aiPredictionInProgress) @@ -329,7 +329,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) { if (!hasSuperEffectiveMove // If the AI doesn't have a super effective move && (gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2 // And the current mon has at least 1/2 their HP, or 1/4 HP and Regenerator - || (aiAbility == ABILITY_REGENERATOR + || (AI_BATTLER_HAS_TRAIT(battler, ABILITY_REGENERATOR) && gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 4))) { // Then check if they have an important status move, which is worth using even in a bad matchup @@ -350,7 +350,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) static bool32 ShouldSwitchIfTruant(u32 battler) { // Switch if mon with truant is bodied by Protect or invulnerability spam - if (gAiLogicData->abilities[battler] == ABILITY_TRUANT + if (AI_BATTLER_HAS_TRAIT(battler, ABILITY_TRUANT) && IsTruantMonVulnerable(battler, gBattlerTarget) && gDisableStructs[battler].truantCounter && gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2 @@ -420,8 +420,8 @@ static bool32 ShouldSwitchIfAllMovesBad(u32 battler) { aiMove = gBattleMons[battler].moves[moveIndex]; if (gAiLogicData->effectiveness[battler][opposingBattler][moveIndex] > UQ_4_12(0.0) && aiMove != MOVE_NONE - && !CanAbilityAbsorbMove(battler, opposingBattler, gAiLogicData->abilities[opposingBattler], aiMove, GetBattleMoveType(aiMove), AI_CHECK) - && !CanAbilityBlockMove(battler, opposingBattler, gBattleMons[battler].ability, gAiLogicData->abilities[opposingBattler], aiMove, AI_CHECK) + && !CanAbilityAbsorbMove(battler, opposingBattler, aiMove, GetBattleMoveType(aiMove), AI_CHECK) + && !CanAbilityBlockMove(battler, opposingBattler, aiMove, AI_CHECK) && (!ALL_MOVES_BAD_STATUS_MOVES_BAD || GetMovePower(aiMove) != 0)) // If using ALL_MOVES_BAD_STATUS_MOVES_BAD, then need power to be non-zero return FALSE; } @@ -446,7 +446,7 @@ static bool32 ShouldSwitchIfWonderGuard(u32 battler) if (IsDoubleBattle()) return FALSE; - if (gAiLogicData->abilities[opposingBattler] != ABILITY_WONDER_GUARD) + if (!AI_BATTLER_HAS_TRAIT(opposingBattler, ABILITY_WONDER_GUARD)) return FALSE; // Check if Pokémon has a super effective move. @@ -475,7 +475,6 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) s32 firstId; s32 lastId; struct Pokemon *party; - enum Ability monAbility; u16 aiMove; u32 opposingBattler = GetOppositeBattler(battler); u32 incomingMove = GetIncomingMove(battler, opposingBattler, gAiLogicData); @@ -493,9 +492,8 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) return FALSE; if (AreStatsRaised(battler)) return FALSE; - if (IsMoldBreakerTypeAbility(opposingBattler, gAiLogicData->abilities[opposingBattler])) + if (HasMoldBreakerTypeAbility(opposingBattler)) return FALSE; - // Don't switch if mon could OHKO for (i = 0; i < MAX_MON_MOVES; i++) { @@ -510,7 +508,6 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) } } } - if (IsDoubleBattle()) { battlerIn1 = battler; @@ -524,7 +521,6 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) battlerIn1 = battler; battlerIn2 = battler; } - // Create an array of possible absorb abilities so the AI considers all of them if (incomingType == TYPE_FIRE) { @@ -574,14 +570,15 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) { return FALSE; } - // Check current mon for all absorbing abilities for (i = 0; i < numAbsorbingAbilities; i++) { - if (gBattleMons[battler].ability == absorbingTypeAbilities[i]) - return FALSE; + if (BattlerHasTrait(battler, absorbingTypeAbilities[i])) + { + + return FALSE; + } } - // Check party for mon with ability that absorbs move GetAIPartyIndexes(battler, &firstId, &lastId); party = GetBattlerParty(battler); @@ -601,12 +598,12 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) if (IsAceMon(battler, i)) continue; - monAbility = GetMonAbility(&party[i]); + //monAbility = GetMonAbility(&party[i]); for (j = 0; j < numAbsorbingAbilities; j++) { // Found a mon - if (absorbingTypeAbilities[j] == monAbility && RandomPercentage(RNG_AI_SWITCH_ABSORBING, GetSwitchChance(SHOULD_SWITCH_ABSORBS_MOVE))) + if (MonHasTrait(&party[i], absorbingTypeAbilities[j]) && RandomPercentage(RNG_AI_SWITCH_ABSORBING, GetSwitchChance(SHOULD_SWITCH_ABSORBS_MOVE))) return SetSwitchinAndSwitch(battler, i); } } @@ -637,6 +634,7 @@ static bool32 ShouldSwitchIfTrapperInParty(u32 battler) struct Pokemon *party; s32 i; enum Ability monAbility; + u32 species; s32 opposingBattler = GetOppositeBattler(battler); // Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer @@ -644,7 +642,7 @@ static bool32 ShouldSwitchIfTrapperInParty(u32 battler) return FALSE; // Check if current mon has an ability that traps opponent - if (CanAbilityTrapOpponent(gBattleMons[battler].ability, opposingBattler)) + if (CanAbilityTrapOpponent(gBattleMons[battler].ability, opposingBattler, gBattleMons[battler].species)) return FALSE; // Check party for mon with ability that traps opponent @@ -657,8 +655,8 @@ static bool32 ShouldSwitchIfTrapperInParty(u32 battler) return FALSE; monAbility = GetMonAbility(&party[i]); - - if (CanAbilityTrapOpponent(monAbility, opposingBattler) || (CanAbilityTrapOpponent(gAiLogicData->abilities[opposingBattler], opposingBattler) && monAbility == ABILITY_TRACE)) + species = GetMonData(&party[i], MON_DATA_SPECIES); + if (CanAbilityTrapOpponent(monAbility, opposingBattler, species) || (CanAbilityTrapOpponent(gAiLogicData->abilities[opposingBattler], battler, gBattleMons[opposingBattler].species) && monAbility == ABILITY_TRACE)) { // If mon in slot i is the most suitable switchin candidate, then it's a trapper than wins 1v1 if (i == gAiLogicData->mostSuitableMonId[battler] && RandomPercentage(RNG_AI_SWITCH_TRAPPER, GetSwitchChance(SHOULD_SWITCH_TRAPPER))) @@ -671,16 +669,17 @@ static bool32 ShouldSwitchIfTrapperInParty(u32 battler) static bool32 ShouldSwitchIfBadlyStatused(u32 battler) { bool32 switchMon = FALSE; - enum Ability monAbility = gAiLogicData->abilities[battler]; enum HoldEffect holdEffect = gAiLogicData->holdEffects[battler]; u8 opposingPosition = BATTLE_OPPOSITE(GetBattlerPosition(battler)); u8 opposingBattler = GetBattlerAtPosition(opposingPosition); bool32 hasStatRaised = AnyStatIsRaised(battler); + enum Ability AIBattlerTraits[MAX_MON_TRAITS]; + AI_STORE_BATTLER_TRAITS(battler); //Perish Song if (gBattleMons[battler].volatiles.perishSong && gDisableStructs[battler].perishSongTimer == 0 - && monAbility != ABILITY_SOUNDPROOF + && !AISearchTraits(AIBattlerTraits, ABILITY_SOUNDPROOF) && RandomPercentage(RNG_AI_SWITCH_PERISH_SONG, GetSwitchChance(SHOULD_SWITCH_PERISH_SONG))) return SetSwitchinAndSwitch(battler, PARTY_SIZE); @@ -688,7 +687,7 @@ static bool32 ShouldSwitchIfBadlyStatused(u32 battler) { //Yawn if (gBattleMons[battler].volatiles.yawn - && CanBeSlept(battler, battler, monAbility, BLOCKED_BY_SLEEP_CLAUSE) // TODO: ask for help from pawwkie + && CanBeSlept(battler, battler, BLOCKED_BY_SLEEP_CLAUSE) // TODO: ask for help from pawwkie && gBattleMons[battler].hp > gBattleMons[battler].maxHP / 3 && RandomPercentage(RNG_AI_SWITCH_YAWN, GetSwitchChance(SHOULD_SWITCH_YAWN))) { @@ -704,23 +703,26 @@ static bool32 ShouldSwitchIfBadlyStatused(u32 battler) switchMon = FALSE; // Checks to see if active Pokemon can do something against sleep - if ((monAbility == ABILITY_NATURAL_CURE - || monAbility == ABILITY_SHED_SKIN - || monAbility == ABILITY_EARLY_BIRD) + if ((AISearchTraits(AIBattlerTraits, ABILITY_NATURAL_CURE) + || AISearchTraits(AIBattlerTraits, ABILITY_SHED_SKIN) + || AISearchTraits(AIBattlerTraits, ABILITY_EARLY_BIRD)) || holdEffect == (HOLD_EFFECT_CURE_SLP | HOLD_EFFECT_CURE_STATUS) || HasMove(battler, MOVE_SLEEP_TALK) || (HasMove(battler, MOVE_SNORE) && gAiLogicData->effectiveness[battler][opposingBattler][GetIndexInMoveArray(battler, MOVE_SNORE)] >= UQ_4_12(2.0)) - || (IsBattlerGrounded(battler, monAbility, gAiLogicData->holdEffects[battler]) + || (IsBattlerGrounded(battler, gAiLogicData->holdEffects[battler]) && (HasMove(battler, MOVE_MISTY_TERRAIN) || HasMove(battler, MOVE_ELECTRIC_TERRAIN))) ) switchMon = FALSE; // Check if Active Pokemon evasion boosted and might be able to dodge until awake + enum Ability AIBattlerTraits[MAX_MON_TRAITS]; + AI_STORE_BATTLER_TRAITS(opposingBattler); + if (gBattleMons[battler].statStages[STAT_EVASION] > (DEFAULT_STAT_STAGE + 3) - && gAiLogicData->abilities[opposingBattler] != ABILITY_UNAWARE - && gAiLogicData->abilities[opposingBattler] != ABILITY_KEEN_EYE - && gAiLogicData->abilities[opposingBattler] != ABILITY_MINDS_EYE - && (GetConfig(CONFIG_ILLUMINATE_EFFECT) >= GEN_9 && gAiLogicData->abilities[opposingBattler] != ABILITY_ILLUMINATE) + && !AISearchTraits(AIBattlerTraits, ABILITY_UNAWARE) + && !AISearchTraits(AIBattlerTraits, ABILITY_KEEN_EYE) + && !AISearchTraits(AIBattlerTraits, ABILITY_MINDS_EYE) + && !(AISearchTraits(AIBattlerTraits, ABILITY_ILLUMINATE) && GetConfig(CONFIG_ILLUMINATE_EFFECT) >= GEN_9) && !gBattleMons[battler].volatiles.foresight && !gBattleMons[battler].volatiles.miracleEye) switchMon = FALSE; @@ -730,7 +732,7 @@ static bool32 ShouldSwitchIfBadlyStatused(u32 battler) } // Secondary Damage - if (monAbility != ABILITY_MAGIC_GUARD + if (!AI_BATTLER_HAS_TRAIT(battler, ABILITY_MAGIC_GUARD) && !AiExpectsToFaintPlayer(battler) && gAiLogicData->mostSuitableMonId[battler] != PARTY_SIZE) { @@ -777,44 +779,34 @@ static bool32 ShouldSwitchIfAbilityBenefit(u32 battler) || IsNeutralizingGasOnField()) return FALSE; - switch(gAiLogicData->abilities[battler]) + if (AI_BATTLER_HAS_TRAIT(battler, ABILITY_NATURAL_CURE)) { - case ABILITY_NATURAL_CURE: - //Attempt to cure bad ailment - if (gBattleMons[battler].status1 & (STATUS1_SLEEP | STATUS1_FREEZE | STATUS1_TOXIC_POISON) - && gAiLogicData->mostSuitableMonId[battler] != PARTY_SIZE - && (hasStatRaised ? RandomPercentage(RNG_AI_SWITCH_NATURAL_CURE, GetSwitchChance(SHOULD_SWITCH_NATURAL_CURE_STRONG_STATS_RAISED)) : RandomPercentage(RNG_AI_SWITCH_NATURAL_CURE, GetSwitchChance(SHOULD_SWITCH_NATURAL_CURE_STRONG)))) - break; - //Attempt to cure lesser ailment - if ((gBattleMons[battler].status1 & STATUS1_ANY) - && (gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2) - && gAiLogicData->mostSuitableMonId[battler] != PARTY_SIZE - && (hasStatRaised ? RandomPercentage(RNG_AI_SWITCH_NATURAL_CURE, GetSwitchChance(SHOULD_SWITCH_NATURAL_CURE_WEAK_STATS_RAISED)) : RandomPercentage(RNG_AI_SWITCH_NATURAL_CURE, GetSwitchChance(SHOULD_SWITCH_NATURAL_CURE_WEAK)))) - break; - - return FALSE; - - case ABILITY_REGENERATOR: - //Don't switch if ailment - if (gBattleMons[battler].status1 & STATUS1_ANY) - return FALSE; - if ((gBattleMons[battler].hp <= ((gBattleMons[battler].maxHP * 2) / 3)) - && gAiLogicData->mostSuitableMonId[battler] != PARTY_SIZE - && (hasStatRaised ? RandomPercentage(RNG_AI_SWITCH_REGENERATOR, GetSwitchChance(SHOULD_SWITCH_REGENERATOR_STATS_RAISED)) : RandomPercentage(RNG_AI_SWITCH_REGENERATOR, GetSwitchChance(SHOULD_SWITCH_REGENERATOR)))) - break; - - return FALSE; - - case ABILITY_ZERO_TO_HERO: - // Want to activate Palafin-Zero at all costs - if (gBattleMons[battler].species == SPECIES_PALAFIN_ZERO) - break; - - default: + //Attempt to cure bad ailment + if (gBattleMons[battler].status1 & (STATUS1_SLEEP | STATUS1_FREEZE | STATUS1_TOXIC_POISON) + && gAiLogicData->mostSuitableMonId[battler] != PARTY_SIZE + && (hasStatRaised ? RandomPercentage(RNG_AI_SWITCH_NATURAL_CURE, GetSwitchChance(SHOULD_SWITCH_NATURAL_CURE_STRONG_STATS_RAISED)) : RandomPercentage(RNG_AI_SWITCH_NATURAL_CURE, GetSwitchChance(SHOULD_SWITCH_NATURAL_CURE_STRONG)))) + return SetSwitchinAndSwitch(battler, PARTY_SIZE); + //Attempt to cure lesser ailment + if ((gBattleMons[battler].status1 & STATUS1_ANY) + && (gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2) + && gAiLogicData->mostSuitableMonId[battler] != PARTY_SIZE + && (hasStatRaised ? RandomPercentage(RNG_AI_SWITCH_NATURAL_CURE, GetSwitchChance(SHOULD_SWITCH_NATURAL_CURE_WEAK_STATS_RAISED)) : RandomPercentage(RNG_AI_SWITCH_NATURAL_CURE, GetSwitchChance(SHOULD_SWITCH_NATURAL_CURE_WEAK)))) + return SetSwitchinAndSwitch(battler, PARTY_SIZE); + } + if (AI_BATTLER_HAS_TRAIT(battler, ABILITY_REGENERATOR)) + { + //Don't switch if ailment + if (gBattleMons[battler].status1 & STATUS1_ANY) return FALSE; + if ((gBattleMons[battler].hp <= ((gBattleMons[battler].maxHP * 2) / 3)) + && gAiLogicData->mostSuitableMonId[battler] != PARTY_SIZE + && (hasStatRaised ? RandomPercentage(RNG_AI_SWITCH_REGENERATOR, GetSwitchChance(SHOULD_SWITCH_REGENERATOR_STATS_RAISED)) : RandomPercentage(RNG_AI_SWITCH_REGENERATOR, GetSwitchChance(SHOULD_SWITCH_REGENERATOR)))) + return SetSwitchinAndSwitch(battler, PARTY_SIZE); } - - return SetSwitchinAndSwitch(battler, PARTY_SIZE); + if (AI_BATTLER_HAS_TRAIT(battler, ABILITY_ZERO_TO_HERO)) + return TRUE; + + return FALSE; } static bool32 CanUseSuperEffectiveMoveAgainstOpponents(u32 battler) @@ -900,9 +892,9 @@ static bool32 FindMonWithFlagsAndSuperEffective(u32 battler, u16 flags, u32 perc for (i = firstId; i < lastId; i++) { u16 species; - enum Ability monAbility; uq4_12_t typeMultiplier; u16 moveFlags = 0; + struct Pokemon *mon; if (!IsValidForBattle(&party[i])) continue; @@ -918,8 +910,8 @@ static bool32 FindMonWithFlagsAndSuperEffective(u32 battler, u16 flags, u32 perc continue; species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG); - monAbility = GetMonAbility(&party[i]); - typeMultiplier = CalcPartyMonTypeEffectivenessMultiplier(gLastLandedMoves[battler], species, monAbility); + mon = &gPlayerParty[i]; + typeMultiplier = CalcPartyMonTypeEffectivenessMultiplier(gLastLandedMoves[battler], species, mon); UpdateMoveResultFlags(typeMultiplier, &moveFlags); if (moveFlags & flags) { @@ -944,11 +936,11 @@ static bool32 CanMonSurviveHazardSwitchin(u32 battler) { u32 battlerIn1, battlerIn2; u32 hazardDamage = 0, battlerHp = gBattleMons[battler].hp; - enum Ability ability = gAiLogicData->abilities[battler], aiMove; + u32 aiMove; s32 firstId, lastId, i, j; struct Pokemon *party; - if (ability == ABILITY_REGENERATOR) + if (AI_BATTLER_HAS_TRAIT(battler, ABILITY_REGENERATOR)) battlerHp = (battlerHp * 133) / 100; // Account for Regenerator healing hazardDamage = GetSwitchinHazardsDamage(battler, &gBattleMons[battler]); @@ -1037,14 +1029,14 @@ static bool32 ShouldSwitchIfBadChoiceLock(u32 battler) bool32 moveAffectsTarget = TRUE; if (lastUsedMove != MOVE_NONE && (AI_GetMoveEffectiveness(lastUsedMove, battler, opposingBattler) == UQ_4_12(0.0) - || CanAbilityAbsorbMove(battler, opposingBattler, gAiLogicData->abilities[opposingBattler], lastUsedMove, CheckDynamicMoveType(GetBattlerMon(battler), lastUsedMove, battler, MON_IN_BATTLE), AI_CHECK) - || CanAbilityBlockMove(battler, opposingBattler, gAiLogicData->abilities[battler], gAiLogicData->abilities[opposingBattler], lastUsedMove, AI_CHECK))) + || CanAbilityAbsorbMove(battler, opposingBattler, lastUsedMove, CheckDynamicMoveType(GetBattlerMon(battler), lastUsedMove, battler, MON_IN_BATTLE), AI_CHECK) + || CanAbilityBlockMove(battler, opposingBattler, lastUsedMove, AI_CHECK))) moveAffectsTarget = FALSE; if (IsHoldEffectChoice(holdEffect) && IsBattlerItemEnabled(battler)) { if ((GetMoveCategory(lastUsedMove) == DAMAGE_CATEGORY_STATUS || !moveAffectsTarget) && RandomPercentage(RNG_AI_SWITCH_CHOICE_LOCKED, GetSwitchChance(SHOULD_SWITCH_CHOICE_LOCKED))) - return SetSwitchinAndSwitch(battler, PARTY_SIZE); + return SetSwitchinAndSwitch(battler, PARTY_SIZE); } return FALSE; @@ -1540,14 +1532,14 @@ static u32 GetFirstNonInvalidMon(u32 firstId, u32 lastId, u32 invalidMons) return chosenMonId; } -bool32 IsMonGrounded(enum HoldEffect heldItemEffect, enum Ability ability, enum Type type1, enum Type type2) +bool32 IsMonGrounded(enum HoldEffect heldItemEffect, enum Ability ability, enum Type type1, enum Type type2, u16 species) { // List that makes mon not grounded - if (type1 == TYPE_FLYING || type2 == TYPE_FLYING || ability == ABILITY_LEVITATE - || (heldItemEffect == HOLD_EFFECT_AIR_BALLOON && !(ability == ABILITY_KLUTZ || (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM)))) + if (type1 == TYPE_FLYING || type2 == TYPE_FLYING || (ability == ABILITY_LEVITATE || SpeciesHasInnate(species, ABILITY_LEVITATE)) + || (heldItemEffect == HOLD_EFFECT_AIR_BALLOON && !(ability == ABILITY_KLUTZ || SpeciesHasInnate(species, ABILITY_KLUTZ) || (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM)))) { // List that overrides being off the ground - if ((heldItemEffect == HOLD_EFFECT_IRON_BALL && !(ability == ABILITY_KLUTZ || (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM))) || (gFieldStatuses & STATUS_FIELD_GRAVITY) || (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM)) + if ((heldItemEffect == HOLD_EFFECT_IRON_BALL && !(ability == ABILITY_KLUTZ || SpeciesHasInnate(species, ABILITY_KLUTZ) || (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM))) || (gFieldStatuses & STATUS_FIELD_GRAVITY)) return TRUE; else return FALSE; @@ -1562,14 +1554,14 @@ static u32 GetSwitchinHazardsDamage(u32 battler, struct BattlePokemon *battleMon enum Type defType1 = battleMon->types[0], defType2 = battleMon->types[1]; u8 tSpikesLayers; u16 heldItemEffect = GetItemHoldEffect(battleMon->item); - u32 maxHP = battleMon->maxHP; + u32 maxHP = battleMon->maxHP, species = battleMon->species; enum Ability ability = battleMon->ability, status = battleMon->status1; u32 spikesDamage = 0, tSpikesDamage = 0, hazardDamage = 0; u32 side = GetBattlerSide(battler); // Check ways mon might avoid all hazards - if (ability != ABILITY_MAGIC_GUARD || (heldItemEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS && - !((gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) || ability == ABILITY_KLUTZ))) + if ((ability != ABILITY_MAGIC_GUARD && !SpeciesHasInnate(species, ABILITY_MAGIC_GUARD)) || (heldItemEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS && + !((gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) || (ability == ABILITY_LEVITATE || SpeciesHasInnate(species, ABILITY_LEVITATE))))) { // Stealth Rock if (IsHazardOnSide(side, HAZARDS_STEALTH_ROCK) && heldItemEffect != HOLD_EFFECT_HEAVY_DUTY_BOOTS) @@ -1578,7 +1570,7 @@ static u32 GetSwitchinHazardsDamage(u32 battler, struct BattlePokemon *battleMon if (IsHazardOnSide(side, HAZARDS_STEELSURGE) && heldItemEffect != HOLD_EFFECT_HEAVY_DUTY_BOOTS) hazardDamage += GetStealthHazardDamageByTypesAndHP(TYPE_SIDE_HAZARD_SHARP_STEEL, defType1, defType2, battleMon->maxHP); // Spikes - if (IsHazardOnSide(side, HAZARDS_SPIKES) && IsMonGrounded(heldItemEffect, ability, defType1, defType2)) + if (IsHazardOnSide(side, HAZARDS_SPIKES) && IsMonGrounded(heldItemEffect, ability, defType1, defType2, species)) { spikesDamage = maxHP / ((5 - gSideTimers[GetBattlerSide(battler)].spikesAmount) * 2); if (spikesDamage == 0) @@ -1588,14 +1580,14 @@ static u32 GetSwitchinHazardsDamage(u32 battler, struct BattlePokemon *battleMon if (IsHazardOnSide(side, HAZARDS_TOXIC_SPIKES) && (defType1 != TYPE_POISON && defType2 != TYPE_POISON && defType1 != TYPE_STEEL && defType2 != TYPE_STEEL - && ability != ABILITY_IMMUNITY && ability != ABILITY_POISON_HEAL && ability != ABILITY_COMATOSE + && (ability != ABILITY_IMMUNITY && !SpeciesHasInnate(species, ABILITY_IMMUNITY)) && (ability != ABILITY_POISON_HEAL && !SpeciesHasInnate(species, ABILITY_POISON_HEAL)) && (ability != ABILITY_COMATOSE && !SpeciesHasInnate(species, ABILITY_COMATOSE)) && status == 0 && !(gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SAFEGUARD) && !IsAbilityOnSide(battler, ABILITY_PASTEL_VEIL) - && !IsBattlerTerrainAffected(battler, ability, gAiLogicData->holdEffects[battler], STATUS_FIELD_MISTY_TERRAIN) - && !IsAbilityStatusProtected(battler, ability) + && !IsBattlerTerrainAffected(battler, gAiLogicData->holdEffects[battler], STATUS_FIELD_MISTY_TERRAIN) + && !IsAbilityStatusProtected(battler) && heldItemEffect != HOLD_EFFECT_CURE_PSN && heldItemEffect != HOLD_EFFECT_CURE_STATUS - && IsMonGrounded(heldItemEffect, ability, defType1, defType2))) + && IsMonGrounded(heldItemEffect, ability, defType1, defType2, species))) { tSpikesLayers = gSideTimers[GetBattlerSide(battler)].toxicSpikesAmount; if (tSpikesLayers == 1) @@ -1619,6 +1611,7 @@ static u32 GetSwitchinHazardsDamage(u32 battler, struct BattlePokemon *battleMon // Gets damage / healing from weather static s32 GetSwitchinWeatherImpact(void) { + u32 species = gAiLogicData->switchinCandidate.battleMon.species; s32 weatherImpact = 0, maxHP = gAiLogicData->switchinCandidate.battleMon.maxHP; enum Ability ability = gAiLogicData->switchinCandidate.battleMon.ability; enum HoldEffect holdEffect = GetItemHoldEffect(gAiLogicData->switchinCandidate.battleMon.item); @@ -1626,11 +1619,12 @@ static s32 GetSwitchinWeatherImpact(void) if (HasWeatherEffect()) { // Damage - if (holdEffect != HOLD_EFFECT_SAFETY_GOGGLES && ability != ABILITY_MAGIC_GUARD && ability != ABILITY_OVERCOAT) + if (holdEffect != HOLD_EFFECT_SAFETY_GOGGLES && (ability != ABILITY_MAGIC_GUARD && !SpeciesHasInnate(species, ABILITY_MAGIC_GUARD)) && (ability != ABILITY_OVERCOAT && !SpeciesHasInnate(species, ABILITY_OVERCOAT))) { if ((gBattleWeather & B_WEATHER_HAIL) && (gAiLogicData->switchinCandidate.battleMon.types[0] != TYPE_ICE || gAiLogicData->switchinCandidate.battleMon.types[1] != TYPE_ICE) - && ability != ABILITY_SNOW_CLOAK && ability != ABILITY_ICE_BODY) + && (ability != ABILITY_SNOW_CLOAK && !SpeciesHasInnate(species, ABILITY_SNOW_CLOAK)) + && (ability != ABILITY_ICE_BODY && !SpeciesHasInnate(species, ABILITY_ICE_BODY))) { weatherImpact = maxHP / 16; if (weatherImpact == 0) @@ -1640,7 +1634,9 @@ static s32 GetSwitchinWeatherImpact(void) && (gAiLogicData->switchinCandidate.battleMon.types[0] != TYPE_GROUND && gAiLogicData->switchinCandidate.battleMon.types[1] != TYPE_GROUND && gAiLogicData->switchinCandidate.battleMon.types[0] != TYPE_ROCK && gAiLogicData->switchinCandidate.battleMon.types[1] != TYPE_ROCK && gAiLogicData->switchinCandidate.battleMon.types[0] != TYPE_STEEL && gAiLogicData->switchinCandidate.battleMon.types[1] != TYPE_STEEL - && ability != ABILITY_SAND_VEIL && ability != ABILITY_SAND_RUSH && ability != ABILITY_SAND_FORCE)) + && (ability != ABILITY_SAND_VEIL && !SpeciesHasInnate(species, ABILITY_SAND_VEIL)) + && (ability != ABILITY_SAND_RUSH && !SpeciesHasInnate(species, ABILITY_SAND_RUSH)) + && (ability != ABILITY_SAND_FORCE && !SpeciesHasInnate(species, ABILITY_SAND_FORCE)))) { weatherImpact = maxHP / 16; if (weatherImpact == 0) @@ -1648,7 +1644,8 @@ static s32 GetSwitchinWeatherImpact(void) } } if ((gBattleWeather & B_WEATHER_SUN) && holdEffect != HOLD_EFFECT_UTILITY_UMBRELLA - && (ability == ABILITY_SOLAR_POWER || ability == ABILITY_DRY_SKIN)) + && ((ability == ABILITY_SOLAR_POWER || SpeciesHasInnate(species, ABILITY_SOLAR_POWER)) + || (ability == ABILITY_DRY_SKIN || SpeciesHasInnate(species, ABILITY_DRY_SKIN)))) { weatherImpact = maxHP / 8; if (weatherImpact == 0) @@ -1658,20 +1655,20 @@ static s32 GetSwitchinWeatherImpact(void) // Healing if (gBattleWeather & B_WEATHER_RAIN && holdEffect != HOLD_EFFECT_UTILITY_UMBRELLA) { - if (ability == ABILITY_DRY_SKIN) + if (ability == ABILITY_DRY_SKIN || SpeciesHasInnate(species, ABILITY_DRY_SKIN)) { weatherImpact = -(maxHP / 8); if (weatherImpact == 0) weatherImpact = -1; } - else if (ability == ABILITY_RAIN_DISH) + else if (ability == ABILITY_RAIN_DISH || SpeciesHasInnate(species, ABILITY_RAIN_DISH)) { weatherImpact = -(maxHP / 16); if (weatherImpact == 0) weatherImpact = -1; } } - if (((gBattleWeather & B_WEATHER_HAIL) || (gBattleWeather & B_WEATHER_SNOW)) && ability == ABILITY_ICE_BODY) + if (((gBattleWeather & B_WEATHER_HAIL) || (gBattleWeather & B_WEATHER_SNOW)) && (ability == ABILITY_ICE_BODY || SpeciesHasInnate(species, ABILITY_ICE_BODY))) { weatherImpact = -(maxHP / 16); if (weatherImpact == 0) @@ -1684,12 +1681,12 @@ static s32 GetSwitchinWeatherImpact(void) // Gets one turn of recurring healing static u32 GetSwitchinRecurringHealing(void) { - u32 recurringHealing = 0, maxHP = gAiLogicData->switchinCandidate.battleMon.maxHP; + u32 recurringHealing = 0, maxHP = gAiLogicData->switchinCandidate.battleMon.maxHP, species = gAiLogicData->switchinCandidate.battleMon.species; enum Ability ability = gAiLogicData->switchinCandidate.battleMon.ability; enum HoldEffect holdEffect = GetItemHoldEffect(gAiLogicData->switchinCandidate.battleMon.item); // Items - if (ability != ABILITY_KLUTZ) + if (ability != ABILITY_KLUTZ && !SpeciesHasInnate(species, ABILITY_KLUTZ)) { if (holdEffect == HOLD_EFFECT_BLACK_SLUDGE && (gAiLogicData->switchinCandidate.battleMon.types[0] == TYPE_POISON || gAiLogicData->switchinCandidate.battleMon.types[1] == TYPE_POISON)) { @@ -1706,7 +1703,7 @@ static u32 GetSwitchinRecurringHealing(void) } // Intentionally omitting Shell Bell for its inconsistency // Abilities - if (ability == ABILITY_POISON_HEAL && (gAiLogicData->switchinCandidate.battleMon.status1 & STATUS1_POISON)) + if ((ability != ABILITY_POISON_HEAL && !SpeciesHasInnate(species, ABILITY_POISON_HEAL)) && (gAiLogicData->switchinCandidate.battleMon.status1 & STATUS1_POISON)) { u32 healing = maxHP / 8; if (healing == 0) @@ -1719,12 +1716,12 @@ static u32 GetSwitchinRecurringHealing(void) // Gets one turn of recurring damage static u32 GetSwitchinRecurringDamage(void) { - u32 passiveDamage = 0, maxHP = gAiLogicData->switchinCandidate.battleMon.maxHP; + u32 passiveDamage = 0, maxHP = gAiLogicData->switchinCandidate.battleMon.maxHP, species = gAiLogicData->switchinCandidate.battleMon.species; enum Ability ability = gAiLogicData->switchinCandidate.battleMon.ability; enum HoldEffect holdEffect = GetItemHoldEffect(gAiLogicData->switchinCandidate.battleMon.item); // Items - if (ability != ABILITY_MAGIC_GUARD && ability != ABILITY_KLUTZ) + if ((ability != ABILITY_MAGIC_GUARD && !SpeciesHasInnate(species, ABILITY_MAGIC_GUARD)) && (ability != ABILITY_KLUTZ && !SpeciesHasInnate(species, ABILITY_KLUTZ))) { if (holdEffect == HOLD_EFFECT_BLACK_SLUDGE && gAiLogicData->switchinCandidate.battleMon.types[0] != TYPE_POISON && gAiLogicData->switchinCandidate.battleMon.types[1] != TYPE_POISON) { @@ -1732,7 +1729,7 @@ static u32 GetSwitchinRecurringDamage(void) if (passiveDamage == 0) passiveDamage = 1; } - else if (holdEffect == HOLD_EFFECT_LIFE_ORB && ability != ABILITY_SHEER_FORCE) + else if (holdEffect == HOLD_EFFECT_LIFE_ORB && (ability != ABILITY_SHEER_FORCE && !SpeciesHasInnate(species, ABILITY_SHEER_FORCE))) { passiveDamage = maxHP / 10; if (passiveDamage == 0) @@ -1754,12 +1751,12 @@ static u32 GetSwitchinStatusDamage(u32 battler) enum Type defType1 = gAiLogicData->switchinCandidate.battleMon.types[0], defType2 = gAiLogicData->switchinCandidate.battleMon.types[1]; u8 tSpikesLayers = gSideTimers[GetBattlerSide(battler)].toxicSpikesAmount; u16 heldItemEffect = GetItemHoldEffect(gAiLogicData->switchinCandidate.battleMon.item); - u32 status = gAiLogicData->switchinCandidate.battleMon.status1; + u32 status = gAiLogicData->switchinCandidate.battleMon.status1, species = gAiLogicData->switchinCandidate.battleMon.species; enum Ability ability = gAiLogicData->switchinCandidate.battleMon.ability, maxHP = gAiLogicData->switchinCandidate.battleMon.maxHP; u32 statusDamage = 0; // Status condition damage - if ((status != 0) && gAiLogicData->switchinCandidate.battleMon.ability != ABILITY_MAGIC_GUARD) + if ((status != 0) && (ability != ABILITY_MAGIC_GUARD && !SpeciesHasInnate(species, ABILITY_MAGIC_GUARD))) { if (status & STATUS1_BURN) { @@ -1767,7 +1764,7 @@ static u32 GetSwitchinStatusDamage(u32 battler) statusDamage = maxHP / 16; else statusDamage = maxHP / 8; - if (ability == ABILITY_HEATPROOF) + if (ability == ABILITY_HEATPROOF || SpeciesHasInnate(species, ABILITY_HEATPROOF)) statusDamage = statusDamage / 2; if (statusDamage == 0) statusDamage = 1; @@ -1781,13 +1778,13 @@ static u32 GetSwitchinStatusDamage(u32 battler) if (statusDamage == 0) statusDamage = 1; } - else if ((status & STATUS1_POISON) && ability != ABILITY_POISON_HEAL) + else if ((status & STATUS1_POISON) && (ability != ABILITY_POISON_HEAL && !SpeciesHasInnate(species, ABILITY_POISON_HEAL))) { statusDamage = maxHP / 8; if (statusDamage == 0) statusDamage = 1; } - else if ((status & STATUS1_TOXIC_POISON) && ability != ABILITY_POISON_HEAL) + else if ((status & STATUS1_TOXIC_POISON) && (ability != ABILITY_POISON_HEAL && !SpeciesHasInnate(species, ABILITY_POISON_HEAL))) { if ((status & STATUS1_TOXIC_COUNTER) != STATUS1_TOXIC_TURN(15)) // not 16 turns gAiLogicData->switchinCandidate.battleMon.status1 += STATUS1_TOXIC_TURN(1); @@ -1801,12 +1798,12 @@ static u32 GetSwitchinStatusDamage(u32 battler) // Apply hypothetical poisoning from Toxic Spikes, which means the first turn of damage already added in GetSwitchinHazardsDamage // Do this last to skip one iteration of Poison / Toxic damage, and start counting Toxic damage one turn later. if (tSpikesLayers != 0 && (defType1 != TYPE_POISON && defType2 != TYPE_POISON - && ability != ABILITY_IMMUNITY && ability != ABILITY_POISON_HEAL + && (ability != ABILITY_IMMUNITY && !SpeciesHasInnate(species, ABILITY_IMMUNITY)) && (ability != ABILITY_POISON_HEAL && !SpeciesHasInnate(species, ABILITY_POISON_HEAL)) && status == 0 && !(heldItemEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS - && (((gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) || ability == ABILITY_KLUTZ))) + && (((gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) || (ability == ABILITY_KLUTZ || SpeciesHasInnate(species, ABILITY_KLUTZ))))) && heldItemEffect != HOLD_EFFECT_CURE_PSN && heldItemEffect != HOLD_EFFECT_CURE_STATUS - && IsMonGrounded(heldItemEffect, ability, defType1, defType2))) + && IsMonGrounded(heldItemEffect, ability, defType1, defType2, species))) { if (tSpikesLayers == 1) { @@ -1835,8 +1832,7 @@ static u32 GetSwitchinHitsToKO(s32 damageTaken, u32 battler) u16 maxHP = gAiLogicData->switchinCandidate.battleMon.maxHP, item = gAiLogicData->switchinCandidate.battleMon.item, heldItemEffect = GetItemHoldEffect(item); u8 weatherDuration = gWishFutureKnock.weatherDuration, holdEffectParam = GetItemHoldEffectParam(item); u32 opposingBattler = GetOppositeBattler(battler); - enum Ability opposingAbility = gAiLogicData->abilities[opposingBattler], ability = gAiLogicData->switchinCandidate.battleMon.ability; - bool32 usedSingleUseHealingItem = FALSE, opponentCanBreakMold = IsMoldBreakerTypeAbility(opposingBattler, opposingAbility); + bool32 usedSingleUseHealingItem = FALSE, opponentCanBreakMold = HasMoldBreakerTypeAbility(opposingBattler); s32 currentHP = startingHP, singleUseItemHeal = 0; // No damage being dealt @@ -1858,7 +1854,7 @@ static u32 GetSwitchinHitsToKO(s32 damageTaken, u32 battler) currentHP = currentHP - damageTaken; // One shot prevention effects - if (damageTaken >= maxHP && startingHP == maxHP && (heldItemEffect == HOLD_EFFECT_FOCUS_SASH || (!opponentCanBreakMold && GetConfig(CONFIG_STURDY) >= GEN_5 && ability == ABILITY_STURDY)) && hitsToKO < 1) + if (damageTaken >= maxHP && startingHP == maxHP && (heldItemEffect == HOLD_EFFECT_FOCUS_SASH || (!opponentCanBreakMold && GetConfig(CONFIG_STURDY) >= GEN_5 && (gAiLogicData->switchinCandidate.battleMon.ability == ABILITY_STURDY || SpeciesHasInnate(gAiLogicData->switchinCandidate.battleMon.species, ABILITY_STURDY)))) && hitsToKO < 1) currentHP = 1; // If mon is still alive, apply weather impact first, as it might KO the mon before it can heal with its item (order is weather -> item -> status) @@ -1866,8 +1862,8 @@ static u32 GetSwitchinHitsToKO(s32 damageTaken, u32 battler) currentHP = currentHP - weatherImpact; // Check if we're at a single use healing item threshold - if (currentHP > 0 && gAiLogicData->switchinCandidate.battleMon.ability != ABILITY_KLUTZ && usedSingleUseHealingItem == FALSE - && !(opposingAbility == ABILITY_UNNERVE && GetItemPocket(item) == POCKET_BERRIES)) + if ((currentHP > 0 && gAiLogicData->switchinCandidate.battleMon.ability != ABILITY_KLUTZ && !SpeciesHasInnate(gAiLogicData->switchinCandidate.battleMon.species, ABILITY_KLUTZ)) && usedSingleUseHealingItem == FALSE + && !(BattlerHasTrait(opposingBattler, ABILITY_UNNERVE) && GetItemPocket(item) == POCKET_BERRIES)) { switch (heldItemEffect) { @@ -2029,7 +2025,7 @@ static s32 GetMaxPriorityDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposi if (gBattleStruct->choicedMove[opposingBattler] !=MOVE_NONE && GetMovePriority(gBattleStruct->choicedMove[opposingBattler]) < 1) break; playerMove = SMART_SWITCHING_OMNISCIENT ? gBattleMons[opposingBattler].moves[i] : playerMoves[i]; - if (GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], playerMove) > 0 + if (GetBattleMovePriority(opposingBattler, playerMove) > 0 && playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH && gBattleMons[opposingBattler].pp[i] > 0) { damageTaken = AI_CalcPartyMonDamage(playerMove, opposingBattler, battler, battleMon, &effectiveness, AI_DEFENDING); @@ -2048,20 +2044,20 @@ static s32 GetMaxPriorityDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposi return maxDamageTaken; } -static bool32 CanAbilityTrapOpponent(enum Ability ability, u32 opponent) +static bool32 CanAbilityTrapOpponent(enum Ability ability, u32 opponent, u32 species) { if ((GetConfig(CONFIG_GHOSTS_ESCAPE) >= GEN_6 && IS_BATTLER_OF_TYPE(opponent, TYPE_GHOST))) return FALSE; - else if (ability == ABILITY_SHADOW_TAG) + else if ((ability == ABILITY_SHADOW_TAG || SpeciesHasInnate(species, ABILITY_SHADOW_TAG))) { - if (B_SHADOW_TAG_ESCAPE >= GEN_4 && gAiLogicData->abilities[opponent] == ABILITY_SHADOW_TAG) // Check if ability exists in species + if (B_SHADOW_TAG_ESCAPE >= GEN_4 && AI_BATTLER_HAS_TRAIT(opponent, ABILITY_SHADOW_TAG)) // Check if ability exists in species return FALSE; else return TRUE; } - else if (ability == ABILITY_ARENA_TRAP && IsBattlerGrounded(opponent, gAiLogicData->abilities[opponent], gAiLogicData->holdEffects[opponent])) + else if (ability == ABILITY_ARENA_TRAP && IsBattlerGrounded(opponent, gAiLogicData->holdEffects[opponent])) return TRUE; - else if (ability == ABILITY_MAGNET_PULL && IS_BATTLER_OF_TYPE(opponent, TYPE_STEEL)) + else if ((ability == ABILITY_MAGNET_PULL || SpeciesHasInnate(species, ABILITY_MAGNET_PULL)) && IS_BATTLER_OF_TYPE(opponent, TYPE_STEEL)) return TRUE; else return FALSE; @@ -2080,9 +2076,9 @@ static inline bool32 IsFreeSwitch(enum SwitchType switchType, u32 battlerSwitchi return TRUE; if (gAiLogicData->ejectPackSwitch) { - enum Ability opposingAbility = GetBattlerAbilityIgnoreMoldBreaker(opposingBattler); // If faster, not a free switch; likely lowered own stats - if (!movedSecond && opposingAbility != ABILITY_INTIMIDATE && opposingAbility != ABILITY_SUPERSWEET_SYRUP) // Intimidate triggers switches before turn starts + // BattlerHasTrait used beause Intimidate/SuperSweet Syrup are always known + if (!movedSecond && !BattlerHasTrait(opposingBattler, ABILITY_INTIMIDATE) && !BattlerHasTrait(opposingBattler, ABILITY_SUPERSWEET_SYRUP)) // Intimidate triggers switches before turn starts return FALSE; // Otherwise, free switch return TRUE; @@ -2164,7 +2160,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, InitializeSwitchinCandidate(&party[i]); // While not really invalid per se, not really wise to switch into this mon - if (gAiLogicData->switchinCandidate.battleMon.ability == ABILITY_TRUANT && IsTruantMonVulnerable(battler, opposingBattler)) + if ((gAiLogicData->switchinCandidate.battleMon.ability == ABILITY_TRUANT || SpeciesHasInnate(gAiLogicData->switchinCandidate.battleMon.species, ABILITY_TRUANT))&& IsTruantMonVulnerable(battler, opposingBattler)) continue; // Get max number of hits for player to KO AI mon and type matchup for defensive switching @@ -2189,7 +2185,6 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, isSwitchinFirst = AI_IsPartyMonFaster(battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, aiMove, bestPlayerMove, CONSIDER_PRIORITY); isSwitchinFirstPriority = AI_IsPartyMonFaster(battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, aiMove, bestPlayerPriorityMove, CONSIDER_PRIORITY); canSwitchinWin1v1 = CanSwitchinWin1v1(hitsToKOAI, hitsToKOPlayer, isSwitchinFirst, isFreeSwitch) && CanSwitchinWin1v1(hitsToKOAIPriority, hitsToKOPlayer, isSwitchinFirstPriority, isFreeSwitch); // AI must successfully 1v1 with and without priority to be considered a good option - // Check for Baton Pass; hitsToKO requirements mean mon can boost and BP without dying whether it's slower or not if (GetMoveEffect(aiMove) == EFFECT_BATON_PASS) { @@ -2277,8 +2272,8 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, } // If mon can trap - if ((CanAbilityTrapOpponent(gAiLogicData->switchinCandidate.battleMon.ability, opposingBattler) - || (CanAbilityTrapOpponent(gAiLogicData->abilities[opposingBattler], opposingBattler) && gAiLogicData->switchinCandidate.battleMon.ability == ABILITY_TRACE)) + if ((CanAbilityTrapOpponent(gAiLogicData->switchinCandidate.battleMon.ability, opposingBattler, gAiLogicData->switchinCandidate.battleMon.species) + || (CanAbilityTrapOpponent(gAiLogicData->abilities[opposingBattler], opposingBattler, gAiLogicData->switchinCandidate.battleMon.species) && gAiLogicData->switchinCandidate.battleMon.ability == ABILITY_TRACE)) && CountUsablePartyMons(opposingBattler) > 0 && canSwitchinWin1v1) trapperId = i; @@ -2413,7 +2408,7 @@ u32 GetMostSuitableMonToSwitchInto(u32 battler, enum SwitchType switchType) || gBattlerPartyIndexes[battlerIn2] == i || i == gBattleStruct->monToSwitchIntoId[battlerIn1] || i == gBattleStruct->monToSwitchIntoId[battlerIn2] - || (GetMonAbility(&party[i]) == ABILITY_TRUANT && IsTruantMonVulnerable(battler, opposingBattler))) // While not really invalid per se, not really wise to switch into this mon. + || (MonHasTrait(&party[i], ABILITY_TRUANT) && IsTruantMonVulnerable(battler, opposingBattler))) // While not really invalid per se, not really wise to switch into this mon. { invalidMons |= 1u << i; } diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 6bb814b66fdf..431325e43319 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -27,17 +27,6 @@ static u32 GetAIEffectGroup(enum BattleMoveEffects effect); static u32 GetAIEffectGroupFromMove(u32 battler, u32 move); // Functions -static u32 AI_GetMoldBreakerSanitizedAbility(u32 battlerAtk, enum Ability abilityAtk, enum Ability abilityDef, u32 holdEffectDef, u32 move) -{ - if (MoveIgnoresTargetAbility(move)) - return ABILITY_NONE; - - if (holdEffectDef != HOLD_EFFECT_ABILITY_SHIELD && IsMoldBreakerTypeAbility(battlerAtk, abilityAtk)) - return ABILITY_NONE; - - return abilityDef; -} - static bool32 AI_IsDoubleSpreadMove(u32 battlerAtk, u32 move) { u32 numOfTargets = 0; @@ -66,7 +55,7 @@ static bool32 AI_IsDoubleSpreadMove(u32 battlerAtk, u32 move) bool32 AI_IsBattlerGrounded(u32 battler) { - return IsBattlerGrounded(battler, gAiLogicData->abilities[battler], gAiLogicData->holdEffects[battler]); + return IsBattlerGrounded(battler, gAiLogicData->holdEffects[battler]); } u32 AI_GetDamage(u32 battlerAtk, u32 battlerDef, u32 moveIndex, enum DamageCalcContext calcContext, struct AiLogicData *aiData) @@ -360,7 +349,7 @@ bool32 ShouldRecordStatusMove(u32 move) return RandomPercentage(RNG_AI_ASSUME_ALL_STATUS, ASSUME_ALL_STATUS_ODDS) && IsBattleMoveStatus(move); } -static bool32 ShouldFailForIllusion(u32 illusionSpecies, u32 battlerId) +static bool32 ShouldFallForIllusion(u32 illusionSpecies, u32 battlerId) { u32 i, j; const struct LevelUpMove *learnset; @@ -406,7 +395,7 @@ void SetBattlerData(u32 battlerId) // Simulate Illusion species = gBattleMons[battlerId].species; illusionSpecies = GetIllusionMonSpecies(battlerId); - if (illusionSpecies != SPECIES_NONE && ShouldFailForIllusion(illusionSpecies, battlerId)) + if (illusionSpecies != SPECIES_NONE && ShouldFallForIllusion(illusionSpecies, battlerId)) { // If the battler's type has not been changed, AI assumes the types of the illusion mon. if (gBattleMons[battlerId].types[0] == GetSpeciesType(species, 0) @@ -542,18 +531,18 @@ bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler) bool32 Ai_IsPriorityBlocked(u32 battlerAtk, u32 battlerDef, u32 move, struct AiLogicData *aiData) { - s32 atkPriority = GetBattleMovePriority(battlerAtk, aiData->abilities[battlerAtk], move); + s32 atkPriority = GetBattleMovePriority(battlerAtk, move); if (atkPriority <= 0 || IsBattlerAlly(battlerAtk, battlerDef)) return FALSE; - if (IsMoldBreakerTypeAbility(battlerAtk, aiData->abilities[battlerAtk]) || MoveIgnoresTargetAbility(move)) + if (HasMoldBreakerTypeAbility(battlerAtk) || MoveIgnoresTargetAbility(move)) return FALSE; - if (IsDazzlingAbility(aiData->abilities[battlerDef])) + if (HasDazzlingAbility(battlerDef)) return TRUE; - if (IsDoubleBattle() && IsDazzlingAbility(aiData->abilities[BATTLE_PARTNER(battlerDef)])) + if (IsDoubleBattle() && HasDazzlingAbility(BATTLE_PARTNER(battlerDef))) return TRUE; return FALSE; @@ -571,8 +560,6 @@ bool32 MovesWithCategoryUnusable(u32 attacker, u32 target, enum DamageCategory c ctx.battlerAtk = attacker; ctx.battlerDef = target; ctx.updateFlags = FALSE; - ctx.abilityAtk = gAiLogicData->abilities[attacker]; - ctx.abilityDef = gAiLogicData->abilities[target]; ctx.holdEffectAtk = gAiLogicData->holdEffects[attacker]; ctx.holdEffectDef = gAiLogicData->holdEffects[target]; @@ -633,9 +620,8 @@ static inline s32 DmgRoll(s32 dmg) bool32 IsDamageMoveUnusable(struct DamageContext *ctx) { - enum Ability battlerDefAbility; - enum Ability partnerDefAbility; struct AiLogicData *aiData = gAiLogicData; + bool32 ignoreAbility = FALSE; if (ctx->typeEffectivenessModifier == UQ_4_12(0.0)) return TRUE; @@ -643,30 +629,22 @@ bool32 IsDamageMoveUnusable(struct DamageContext *ctx) return TRUE; // aiData->abilities does not check for Mold Breaker since it happens during combat so it needs to be done manually - if (IsMoldBreakerTypeAbility(ctx->battlerAtk, ctx->abilityAtk) || MoveIgnoresTargetAbility(ctx->move)) - { - battlerDefAbility = ABILITY_NONE; - partnerDefAbility = ABILITY_NONE; - } - else - { - battlerDefAbility = ctx->abilityDef; - partnerDefAbility = aiData->abilities[BATTLE_PARTNER(ctx->battlerDef)]; - } + if (HasMoldBreakerTypeAbility(ctx->battlerAtk) || MoveIgnoresTargetAbility(ctx->move)) + ignoreAbility = TRUE; if (Ai_IsPriorityBlocked(ctx->battlerAtk, ctx->battlerDef, ctx->move, aiData)) return TRUE; - if (CanAbilityBlockMove(ctx->battlerAtk, ctx->battlerDef, ctx->abilityAtk, battlerDefAbility, ctx->move, AI_CHECK)) + if (!ignoreAbility && CanAbilityBlockMove(ctx->battlerAtk, ctx->battlerDef, ctx->move, AI_CHECK)) return TRUE; - if (CanAbilityAbsorbMove(ctx->battlerAtk, ctx->battlerDef, battlerDefAbility, ctx->move, ctx->moveType, AI_CHECK)) + if (!ignoreAbility && CanAbilityAbsorbMove(ctx->battlerAtk, ctx->battlerDef, ctx->move, ctx->moveType, AI_CHECK)) return TRUE; // Limited to Lighning Rod and Storm Drain because otherwise the AI would consider Water Absorb, etc... - if (partnerDefAbility == ABILITY_LIGHTNING_ROD || partnerDefAbility == ABILITY_STORM_DRAIN) + if (!ignoreAbility && (BattlerHasTrait(BATTLE_PARTNER(ctx->battlerDef), ABILITY_LIGHTNING_ROD) || BattlerHasTrait(BATTLE_PARTNER(ctx->battlerDef), ABILITY_STORM_DRAIN))) { - if (CanAbilityAbsorbMove(ctx->battlerAtk, BATTLE_PARTNER(ctx->battlerDef), partnerDefAbility, ctx->move, ctx->moveType, AI_CHECK)) + if (CanAbilityAbsorbMove(ctx->battlerAtk, BATTLE_PARTNER(ctx->battlerDef), ctx->move, ctx->moveType, AI_CHECK)) return TRUE; } @@ -678,7 +656,7 @@ bool32 IsDamageMoveUnusable(struct DamageContext *ctx) return TRUE; } - if (IsMoveDampBanned(ctx->move) && (battlerDefAbility == ABILITY_DAMP || partnerDefAbility == ABILITY_DAMP)) + if (!ignoreAbility && IsMoveDampBanned(ctx->move) && (BattlerHasTrait(ctx->battlerDef, ABILITY_DAMP) || BattlerHasTrait(BATTLE_PARTNER(ctx->battlerDef), ABILITY_DAMP))) return TRUE; switch (GetMoveEffect(ctx->move)) @@ -723,12 +701,12 @@ bool32 IsDamageMoveUnusable(struct DamageContext *ctx) return FALSE; } -bool32 IsAdditionalEffectBlocked(u32 battlerAtk, u32 abilityAtk, u32 battlerDef, u32 abilityDef) +bool32 IsAdditionalEffectBlocked(u32 battlerAtk, u32 battlerDef) { if (gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_COVERT_CLOAK) return TRUE; - if (abilityDef == ABILITY_SHIELD_DUST && !IsMoldBreakerTypeAbility(battlerAtk, abilityAtk)) + if (BattlerHasTrait(battlerDef, ABILITY_SHIELD_DUST) && !HasMoldBreakerTypeAbility(battlerAtk)) return TRUE; return FALSE; @@ -783,6 +761,8 @@ static inline void CalcDynamicMoveDamage(struct DamageContext *ctx, u16 *medianD u16 median = *medianDamage; u16 minimum = *minimumDamage; u16 maximum = *maximumDamage; + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(ctx->battlerAtk); switch (effect) { @@ -793,7 +773,7 @@ static inline void CalcDynamicMoveDamage(struct DamageContext *ctx, u16 *medianD minimum *= 3; maximum *= 3; } - else if (ctx->abilityAtk == ABILITY_SKILL_LINK) + else if (SearchTraits(battlerTraits, ABILITY_SKILL_LINK)) { median *= 5; minimum *= 5; @@ -845,7 +825,7 @@ static inline void CalcDynamicMoveDamage(struct DamageContext *ctx, u16 *medianD maximum *= strikeCount; } - if (ctx->abilityAtk == ABILITY_PARENTAL_BOND + if (BattlerHasTrait(ctx->battlerAtk, ABILITY_PARENTAL_BOND) && !strikeCount && effect != EFFECT_TRIPLE_KICK && effect != EFFECT_MULTI_HIT @@ -874,9 +854,9 @@ static inline bool32 ShouldCalcCritDamage(u32 battlerAtk, u32 battlerDef, u32 mo // Get crit chance if (GetConfig(CONFIG_CRIT_CHANCE) == GEN_1) - critChanceIndex = CalcCritChanceStageGen1(battlerAtk, battlerDef, move, FALSE, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], aiData->holdEffects[battlerAtk]); + critChanceIndex = CalcCritChanceStageGen1(battlerAtk, battlerDef, move, FALSE, aiData->holdEffects[battlerAtk]); else - critChanceIndex = CalcCritChanceStage(battlerAtk, battlerDef, move, FALSE, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], aiData->holdEffects[battlerAtk]); + critChanceIndex = CalcCritChanceStage(battlerAtk, battlerDef, move, FALSE, aiData->holdEffects[battlerAtk]); if (critChanceIndex == CRITICAL_HIT_ALWAYS) return TRUE; @@ -942,8 +922,6 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u ctx.fixedBasePower = SetFixedMoveBasePower(battlerAtk, move); ctx.holdEffectAtk = aiData->holdEffects[battlerAtk]; ctx.holdEffectDef = aiData->holdEffects[battlerDef]; - ctx.abilityAtk = aiData->abilities[battlerAtk]; - ctx.abilityDef = AI_GetMoldBreakerSanitizedAbility(battlerAtk, ctx.abilityAtk, aiData->abilities[battlerDef], ctx.holdEffectDef, move); ctx.typeEffectivenessModifier = CalcTypeEffectivenessMultiplier(&ctx); u32 movePower = GetMovePower(move); @@ -954,7 +932,7 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u { enum Type types[3]; AI_StoreBattlerTypes(battlerAtk, types); - ProteanTryChangeType(battlerAtk, aiData->abilities[battlerAtk], move, ctx.moveType); + ProteanTryChangeType(battlerAtk, move, ctx.moveType); s32 fixedDamage = DoFixedDamageMoveCalc(&ctx); if (fixedDamage != INT32_MAX) @@ -1021,8 +999,7 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u bool32 AI_IsDamagedByRecoil(u32 battler) { - enum Ability ability = gAiLogicData->abilities[battler]; - if (ability == ABILITY_MAGIC_GUARD || ability == ABILITY_ROCK_HEAD) + if (AI_BATTLER_HAS_TRAIT(battler, ABILITY_MAGIC_GUARD) || AI_BATTLER_HAS_TRAIT(battler, ABILITY_ROCK_HEAD)) return FALSE; return TRUE; } @@ -1031,20 +1008,18 @@ bool32 AI_IsDamagedByRecoil(u32 battler) static bool32 AI_IsMoveEffectInPlus(u32 battlerAtk, u32 battlerDef, u32 move, s32 noOfHitsToKo) { u32 i; - enum Ability abilityDef = gAiLogicData->abilities[battlerDef]; - enum Ability abilityAtk = gAiLogicData->abilities[battlerAtk]; - if (IsSheerForceAffected(move, abilityAtk)) + if (IsSheerForceAffected(move, battlerAtk)) return FALSE; switch (GetMoveEffect(move)) { case EFFECT_HIT_ESCAPE: - if (CountUsablePartyMons(battlerAtk) != 0 && ShouldPivot(battlerAtk, battlerDef, abilityDef, move, gAiThinkingStruct->movesetIndex)) + if (CountUsablePartyMons(battlerAtk) != 0 && ShouldPivot(battlerAtk, battlerDef, move, gAiThinkingStruct->movesetIndex)) return TRUE; break; case EFFECT_FELL_STINGER: - if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_ATK) && noOfHitsToKo == 1) + if (BattlerStatCanRise(battlerAtk, STAT_ATK) && noOfHitsToKo == 1) return TRUE; break; case EFFECT_PURSUIT: @@ -1067,81 +1042,81 @@ static bool32 AI_IsMoveEffectInPlus(u32 battlerAtk, u32 battlerDef, u32 move, s3 { case MOVE_EFFECT_ATK_MINUS_1: case MOVE_EFFECT_ATK_MINUS_2: - if (abilityAtk == ABILITY_CONTRARY && BattlerStatCanRise(battlerAtk, abilityAtk, STAT_ATK)) + if (AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_CONTRARY) && BattlerStatCanRise(battlerAtk, STAT_ATK)) return TRUE; break; case MOVE_EFFECT_DEF_MINUS_1: case MOVE_EFFECT_DEF_MINUS_2: - if (abilityAtk == ABILITY_CONTRARY && BattlerStatCanRise(battlerAtk, abilityAtk, STAT_ATK)) + if (AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_CONTRARY) && BattlerStatCanRise(battlerAtk, STAT_ATK)) return TRUE; break; case MOVE_EFFECT_SPD_MINUS_1: case MOVE_EFFECT_SPD_MINUS_2: - if (abilityAtk == ABILITY_CONTRARY && BattlerStatCanRise(battlerAtk, abilityAtk, STAT_DEF)) + if (AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_CONTRARY) && BattlerStatCanRise(battlerAtk, STAT_DEF)) return TRUE; break; case MOVE_EFFECT_SP_ATK_MINUS_1: case MOVE_EFFECT_SP_ATK_MINUS_2: - if (abilityAtk == ABILITY_CONTRARY && BattlerStatCanRise(battlerAtk, abilityAtk, STAT_SPATK)) + if (AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_CONTRARY) && BattlerStatCanRise(battlerAtk, STAT_SPATK)) return TRUE; break; case MOVE_EFFECT_SP_DEF_MINUS_1: case MOVE_EFFECT_SP_DEF_MINUS_2: - if (abilityAtk == ABILITY_CONTRARY && BattlerStatCanRise(battlerAtk, abilityAtk, STAT_SPDEF)) + if (AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_CONTRARY) && BattlerStatCanRise(battlerAtk, STAT_SPDEF)) return TRUE; break; case MOVE_EFFECT_EVS_MINUS_1: case MOVE_EFFECT_EVS_MINUS_2: - if (abilityAtk == ABILITY_CONTRARY && BattlerStatCanRise(battlerAtk, abilityAtk, STAT_EVASION)) + if (AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_CONTRARY) && BattlerStatCanRise(battlerAtk, STAT_EVASION)) return TRUE; break; case MOVE_EFFECT_ACC_MINUS_1: case MOVE_EFFECT_ACC_MINUS_2: - if (abilityAtk == ABILITY_CONTRARY && BattlerStatCanRise(battlerAtk, abilityAtk, STAT_ACC)) + if (AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_CONTRARY) && BattlerStatCanRise(battlerAtk, STAT_ACC)) return TRUE; break; case MOVE_EFFECT_ATK_DEF_DOWN: - if (abilityAtk == ABILITY_CONTRARY && (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_ATK) || BattlerStatCanRise(battlerAtk, abilityAtk, STAT_DEF))) + if (AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_CONTRARY) && (BattlerStatCanRise(battlerAtk, STAT_ATK) || BattlerStatCanRise(battlerAtk, STAT_DEF))) return TRUE; break; case MOVE_EFFECT_DEF_SPDEF_DOWN: - if (abilityAtk == ABILITY_CONTRARY && (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_DEF) || BattlerStatCanRise(battlerAtk, abilityAtk, STAT_SPDEF))) + if (AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_CONTRARY) && (BattlerStatCanRise(battlerAtk, STAT_DEF) || BattlerStatCanRise(battlerAtk, STAT_SPDEF))) return TRUE; break; case MOVE_EFFECT_ATK_PLUS_1: case MOVE_EFFECT_ATK_PLUS_2: - if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_ATK)) + if (BattlerStatCanRise(battlerAtk, STAT_ATK)) return TRUE; break; case MOVE_EFFECT_DEF_PLUS_1: case MOVE_EFFECT_DEF_PLUS_2: - if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_DEF)) + if (BattlerStatCanRise(battlerAtk, STAT_DEF)) return TRUE; break; case MOVE_EFFECT_SPD_PLUS_1: case MOVE_EFFECT_SPD_PLUS_2: - if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_SPEED)) + if (BattlerStatCanRise(battlerAtk, STAT_SPEED)) return TRUE; break; case MOVE_EFFECT_SP_ATK_PLUS_1: case MOVE_EFFECT_SP_ATK_PLUS_2: - if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_SPATK)) + if (BattlerStatCanRise(battlerAtk, STAT_SPATK)) return TRUE; break; case MOVE_EFFECT_EVS_PLUS_1: case MOVE_EFFECT_EVS_PLUS_2: - if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_EVASION)) + if (BattlerStatCanRise(battlerAtk, STAT_EVASION)) return TRUE; break; case MOVE_EFFECT_ACC_PLUS_1: case MOVE_EFFECT_ACC_PLUS_2: - if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_ACC)) + if (BattlerStatCanRise(battlerAtk, STAT_ACC)) return TRUE; break; case MOVE_EFFECT_ALL_STATS_UP: for (i = STAT_ATK; i <= NUM_STATS; i++) { - if (BattlerStatCanRise(battlerAtk, abilityAtk, i)) + if (BattlerStatCanRise(battlerAtk, i)) return TRUE; } break; @@ -1151,34 +1126,34 @@ static bool32 AI_IsMoveEffectInPlus(u32 battlerAtk, u32 battlerDef, u32 move, s3 } else // consider move effects that hinder the target { - if (IsAdditionalEffectBlocked(battlerAtk, abilityAtk, battlerDef, abilityDef)) + if (IsAdditionalEffectBlocked(battlerAtk, battlerDef)) continue; switch (additionalEffect->moveEffect) { case MOVE_EFFECT_POISON: case MOVE_EFFECT_TOXIC: - if (AI_CanPoison(battlerAtk, battlerDef, abilityDef, move, MOVE_NONE)) + if (AI_CanPoison(battlerAtk, battlerDef, move, MOVE_NONE)) return TRUE; break; case MOVE_EFFECT_BURN: - if (AI_CanBurn(battlerAtk, battlerDef, abilityDef, BATTLE_PARTNER(battlerAtk), move, MOVE_NONE)) + if (AI_CanBurn(battlerAtk, battlerDef, BATTLE_PARTNER(battlerAtk), move, MOVE_NONE)) return TRUE; break; case MOVE_EFFECT_FREEZE_OR_FROSTBITE: - if (CanBeFrozen(battlerAtk, battlerDef, abilityDef)) + if (CanBeFrozen(battlerAtk, battlerDef)) return TRUE; break; case MOVE_EFFECT_PARALYSIS: - if (AI_CanParalyze(battlerAtk, battlerDef, abilityDef, move, MOVE_NONE)) + if (AI_CanParalyze(battlerAtk, battlerDef, move, MOVE_NONE)) return TRUE; break; case MOVE_EFFECT_CONFUSION: - if (AI_CanConfuse(battlerAtk, battlerDef, abilityDef, BATTLE_PARTNER(battlerAtk), move, MOVE_NONE)) + if (AI_CanConfuse(battlerAtk, battlerDef, BATTLE_PARTNER(battlerAtk), move, MOVE_NONE)) return TRUE; break; case MOVE_EFFECT_FLINCH: - if (ShouldTryToFlinch(battlerAtk, battlerDef, abilityAtk, abilityDef, move)) + if (ShouldTryToFlinch(battlerAtk, battlerDef, move)) return TRUE; break; case MOVE_EFFECT_ATK_MINUS_1: @@ -1212,8 +1187,6 @@ static bool32 AI_IsMoveEffectInPlus(u32 battlerAtk, u32 battlerDef, u32 move, s3 static bool32 AI_IsMoveEffectInMinus(u32 battlerAtk, u32 battlerDef, u32 move, s32 noOfHitsToKo) { - enum Ability abilityAtk = gAiLogicData->abilities[battlerAtk]; - enum Ability abilityDef = gAiLogicData->abilities[battlerDef]; u8 i; switch (GetMoveEffect(move)) @@ -1254,8 +1227,8 @@ static bool32 AI_IsMoveEffectInMinus(u32 battlerAtk, u32 battlerDef, u32 move, s case MOVE_EFFECT_V_CREATE: case MOVE_EFFECT_ATK_DEF_DOWN: case MOVE_EFFECT_DEF_SPDEF_DOWN: - if ((additionalEffect->self && abilityAtk != ABILITY_CONTRARY) - || (noOfHitsToKo > 1 && !additionalEffect->self && abilityDef == ABILITY_CONTRARY && !DoesBattlerIgnoreAbilityChecks(battlerAtk, abilityAtk, move))) + if ((additionalEffect->self && !AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_CONTRARY)) + || (noOfHitsToKo > 1 && !additionalEffect->self && AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_CONTRARY) && !DoesBattlerIgnoreAbilityChecks(battlerAtk, move))) return TRUE; break; case MOVE_EFFECT_RECHARGE: @@ -1275,8 +1248,8 @@ static bool32 AI_IsMoveEffectInMinus(u32 battlerAtk, u32 battlerDef, u32 move, s case MOVE_EFFECT_EVS_PLUS_2: case MOVE_EFFECT_ACC_PLUS_2: case MOVE_EFFECT_ALL_STATS_UP: - if ((additionalEffect->self && abilityAtk == ABILITY_CONTRARY) - || (noOfHitsToKo > 1 && !additionalEffect->self && !(abilityDef == ABILITY_CONTRARY && !DoesBattlerIgnoreAbilityChecks(battlerAtk, abilityAtk, move)))) + if ((additionalEffect->self && AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_CONTRARY)) + || (noOfHitsToKo > 1 && !additionalEffect->self && !(AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_CONTRARY) && !DoesBattlerIgnoreAbilityChecks(battlerAtk, move)))) return TRUE; break; default: @@ -1293,13 +1266,11 @@ static bool32 AI_IsMoveEffectInMinus(u32 battlerAtk, u32 battlerDef, u32 move, s enum MoveComparisonResult AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef, s32 noOfHitsToKo) { bool32 effect1, effect2; - enum Ability defAbility = gAiLogicData->abilities[battlerDef]; - enum Ability atkAbility = gAiLogicData->abilities[battlerAtk]; // Check if physical moves hurt. - if (gAiLogicData->holdEffects[battlerAtk] != HOLD_EFFECT_PROTECTIVE_PADS && atkAbility != ABILITY_LONG_REACH + if (gAiLogicData->holdEffects[battlerAtk] != HOLD_EFFECT_PROTECTIVE_PADS && !AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_LONG_REACH) && (gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_ROCKY_HELMET - || defAbility == ABILITY_IRON_BARBS || defAbility == ABILITY_ROUGH_SKIN)) + || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_IRON_BARBS) || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_ROUGH_SKIN))) { bool32 moveContact1 = MoveMakesContact(move1); bool32 moveContact2 = MoveMakesContact(move2); @@ -1392,8 +1363,6 @@ uq4_12_t AI_GetMoveEffectiveness(u32 move, u32 battlerAtk, u32 battlerDef) ctx.move = ctx.chosenMove = move; ctx.moveType = GetBattleMoveType(move); ctx.updateFlags = FALSE; - ctx.abilityAtk = gAiLogicData->abilities[battlerAtk]; - ctx.abilityDef = gAiLogicData->abilities[battlerDef]; ctx.holdEffectAtk = gAiLogicData->holdEffects[battlerAtk]; ctx.holdEffectDef = gAiLogicData->holdEffects[battlerDef]; typeEffectiveness = CalcTypeEffectivenessMultiplier(&ctx); @@ -1415,13 +1384,11 @@ s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler, u32 aiMoveConsidered, u32 pla u32 speedBattlerAI, speedBattler; enum HoldEffect holdEffectAI = gAiLogicData->holdEffects[battlerAI]; enum HoldEffect holdEffectPlayer = gAiLogicData->holdEffects[battler]; - enum Ability abilityAI = gAiLogicData->abilities[battlerAI]; - enum Ability abilityPlayer = gAiLogicData->abilities[battler]; if (considerPriority == CONSIDER_PRIORITY) { - s8 aiPriority = GetBattleMovePriority(battlerAI, abilityAI, aiMoveConsidered); - s8 playerPriority = GetBattleMovePriority(battler, abilityPlayer, playerMoveConsidered); + s8 aiPriority = GetBattleMovePriority(battlerAI, aiMoveConsidered); + s8 playerPriority = GetBattleMovePriority(battler, playerMoveConsidered); if (aiPriority > playerPriority) return AI_IS_FASTER; @@ -1429,17 +1396,17 @@ s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler, u32 aiMoveConsidered, u32 pla return AI_IS_SLOWER; } - speedBattlerAI = GetBattlerTotalSpeedStat(battlerAI, abilityAI, holdEffectAI); - speedBattler = GetBattlerTotalSpeedStat(battler, abilityPlayer, holdEffectPlayer); + speedBattlerAI = GetBattlerTotalSpeedStat(battlerAI, holdEffectAI); + speedBattler = GetBattlerTotalSpeedStat(battler, holdEffectPlayer); if (holdEffectAI == HOLD_EFFECT_LAGGING_TAIL && holdEffectPlayer != HOLD_EFFECT_LAGGING_TAIL) return AI_IS_SLOWER; else if (holdEffectAI != HOLD_EFFECT_LAGGING_TAIL && holdEffectPlayer == HOLD_EFFECT_LAGGING_TAIL) return AI_IS_FASTER; - if (abilityAI == ABILITY_STALL && abilityPlayer != ABILITY_STALL) + if (AI_BATTLER_HAS_TRAIT(battlerAI, ABILITY_STALL) && !AI_BATTLER_HAS_TRAIT(battler, ABILITY_STALL)) return AI_IS_SLOWER; - else if (abilityAI != ABILITY_STALL && abilityPlayer == ABILITY_STALL) + else if (!AI_BATTLER_HAS_TRAIT(battlerAI, ABILITY_STALL) && AI_BATTLER_HAS_TRAIT(battler, ABILITY_STALL)) return AI_IS_FASTER; if (speedBattlerAI > speedBattler) @@ -1467,16 +1434,16 @@ s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler, u32 aiMoveConsidered, u32 pla bool32 CanEndureHit(u32 battler, u32 battlerTarget, u32 move) { enum BattleMoveEffects effect = GetMoveEffect(move); - if (!AI_BattlerAtMaxHp(battlerTarget) || effect == EFFECT_MULTI_HIT || gAiLogicData->abilities[battler] == ABILITY_PARENTAL_BOND) + if (!AI_BattlerAtMaxHp(battlerTarget) || effect == EFFECT_MULTI_HIT || AI_BATTLER_HAS_TRAIT(battler, ABILITY_PARENTAL_BOND)) return FALSE; if (GetMoveStrikeCount(move) > 1 && !(effect == EFFECT_DRAGON_DARTS && !HasTwoOpponents(battler))) return FALSE; if (gAiLogicData->holdEffects[battlerTarget] == HOLD_EFFECT_FOCUS_SASH) return TRUE; - if (!DoesBattlerIgnoreAbilityChecks(battler, gAiLogicData->abilities[battler], move)) + if (!DoesBattlerIgnoreAbilityChecks(battler, move)) { - if (GetConfig(CONFIG_STURDY) >= GEN_5 && gAiLogicData->abilities[battlerTarget] == ABILITY_STURDY) + if (GetConfig(CONFIG_STURDY) >= GEN_5 && AI_BATTLER_HAS_TRAIT(battlerTarget, ABILITY_STURDY)) return TRUE; if (IsMimikyuDisguised(battlerTarget)) return TRUE; @@ -1697,9 +1664,9 @@ bool32 CanTargetFaintAiWithMod(u32 battlerDef, u32 battlerAtk, s32 hpMod, s32 dm bool32 AI_IsAbilityOnSide(u32 battlerId, enum Ability ability) { - if (IsBattlerAlive(battlerId) && gAiLogicData->abilities[battlerId] == ability) + if (IsBattlerAlive(battlerId) && BattlerHasTrait(battlerId, ability)) return TRUE; - else if (IsBattlerAlive(BATTLE_PARTNER(battlerId)) && gAiLogicData->abilities[BATTLE_PARTNER(battlerId)] == ability) + else if (IsBattlerAlive(BATTLE_PARTNER(battlerId)) && BattlerHasTrait(BATTLE_PARTNER(battlerId), ability)) return TRUE; else return FALSE; @@ -1713,6 +1680,8 @@ enum Ability AI_DecideKnownAbilityForTurn(u32 battlerId) enum Ability knownAbility = GetBattlerAbilityIgnoreMoldBreaker(battlerId); enum Ability indexAbility; enum Ability abilityAiRatings[NUM_ABILITY_SLOTS] = {0}; + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battlerId); // We've had ability overwritten by e.g. Worry Seed. It is not part of gAiPartyData in case of switching if (gDisableStructs[battlerId].overwrittenAbility) @@ -1730,7 +1699,7 @@ enum Ability AI_DecideKnownAbilityForTurn(u32 battlerId) return gAiPartyData->mons[GetBattlerSide(battlerId)][gBattlerPartyIndexes[battlerId]].ability; // Abilities that prevent fleeing - treat as always known - if (knownAbility == ABILITY_SHADOW_TAG || knownAbility == ABILITY_MAGNET_PULL || knownAbility == ABILITY_ARENA_TRAP) + if (SearchTraits(battlerTraits, ABILITY_SHADOW_TAG) || SearchTraits(battlerTraits, ABILITY_MAGNET_PULL) || SearchTraits(battlerTraits, ABILITY_ARENA_TRAP)) return knownAbility; for (i = 0; i < NUM_ABILITY_SLOTS; i++) @@ -1771,21 +1740,21 @@ enum HoldEffect AI_DecideHoldEffectForTurn(u32 battlerId) return HOLD_EFFECT_NONE; if (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) return HOLD_EFFECT_NONE; - if (gAiLogicData->abilities[battlerId] == ABILITY_KLUTZ && !gBattleMons[battlerId].volatiles.gastroAcid) + if (AI_BATTLER_HAS_TRAIT(battlerId, ABILITY_KLUTZ) && !gBattleMons[battlerId].volatiles.gastroAcid) return HOLD_EFFECT_NONE; return holdEffect; } -bool32 DoesBattlerIgnoreAbilityChecks(u32 battlerAtk, enum Ability atkAbility, u32 move) +bool32 DoesBattlerIgnoreAbilityChecks(u32 battlerAtk, u32 move) { if (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_NEGATE_UNAWARE) return FALSE; // AI handicap flag: doesn't understand ability suppression concept - if (atkAbility == ABILITY_MYCELIUM_MIGHT && IsBattleMoveStatus(move)) + if (BattlerHasTrait(battlerAtk, ABILITY_MYCELIUM_MIGHT) && IsBattleMoveStatus(move)) return TRUE; - if (IsMoldBreakerTypeAbility(battlerAtk, atkAbility) || MoveIgnoresTargetAbility(move)) + if (HasMoldBreakerTypeAbility(battlerAtk) || MoveIgnoresTargetAbility(move)) return TRUE; return FALSE; @@ -1815,25 +1784,23 @@ u32 AI_GetSwitchinWeather(struct BattlePokemon battleMon) // Forced weather behaviour if (!AI_WeatherHasEffect()) return B_WEATHER_NONE; - if (ability == ABILITY_CLOUD_NINE || ability == ABILITY_AIR_LOCK) + if ((ability == ABILITY_CLOUD_NINE || SpeciesHasInnate(battleMon.species, ABILITY_CLOUD_NINE)) + || (ability == ABILITY_AIR_LOCK || SpeciesHasInnate(battleMon.species, ABILITY_AIR_LOCK))) return B_WEATHER_NONE; if (gBattleWeather & B_WEATHER_PRIMAL_ANY) return gBattleWeather; // Switchin will introduce new weather - switch(ability) - { - case ABILITY_DRIZZLE: + if (ability == ABILITY_DRIZZLE || SpeciesHasInnate(battleMon.species, ABILITY_DRIZZLE)) return B_WEATHER_RAIN_NORMAL; - case ABILITY_DROUGHT: + if (ability == ABILITY_DROUGHT || SpeciesHasInnate(battleMon.species, ABILITY_DROUGHT)) return B_WEATHER_SUN_NORMAL; - case ABILITY_SAND_STREAM: + if (ability == ABILITY_SAND_STREAM || SpeciesHasInnate(battleMon.species, ABILITY_SAND_STREAM)) return B_WEATHER_SANDSTORM; - case ABILITY_SNOW_WARNING: + if (ability == ABILITY_SNOW_WARNING || SpeciesHasInnate(battleMon.species, ABILITY_SNOW_WARNING)) return GetConfig(CONFIG_SNOW_WARNING) >= GEN_9 ? B_WEATHER_SNOW : B_WEATHER_HAIL; - default: - return gBattleWeather; - } + + return gBattleWeather; } enum WeatherState IsWeatherActive(u32 flags) @@ -1903,6 +1870,8 @@ bool32 IsHazardMove(u32 move) case EFFECT_STONE_AXE: case EFFECT_TOXIC_SPIKES: return TRUE; + default: + return FALSE; } u32 additionalEffectCount = GetMoveAdditionalEffectCount(move); @@ -1934,6 +1903,8 @@ bool32 IsHazardClearingMove(u32 move) if (GetConfig(CONFIG_DEFOG_EFFECT_CLEARING) >= GEN_6) return TRUE; break; + default: + return FALSE; } u32 additionalEffectCount = GetMoveAdditionalEffectCount(move); @@ -1994,7 +1965,7 @@ bool32 IsAllyProtectingFromMove(u32 battlerAtk, u32 attackerMove, u32 allyMove) case PROTECT_KINGS_SHIELD: return !IsBattleMoveStatus(attackerMove); case PROTECT_QUICK_GUARD: - return (GetChosenMovePriority(battlerAtk, gAiLogicData->abilities[battlerAtk]) > 0); + return (GetChosenMovePriority(battlerAtk) > 0); case PROTECT_MAT_BLOCK: return !IsBattleMoveStatus(attackerMove); default: @@ -2002,7 +1973,7 @@ bool32 IsAllyProtectingFromMove(u32 battlerAtk, u32 attackerMove, u32 allyMove) } } -bool32 IsMoveRedirectionPrevented(u32 battlerAtk, u32 move, enum Ability atkAbility) +bool32 IsMoveRedirectionPrevented(u32 battlerAtk, u32 move) { if (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_NEGATE_UNAWARE) return FALSE; @@ -2010,13 +1981,13 @@ bool32 IsMoveRedirectionPrevented(u32 battlerAtk, u32 move, enum Ability atkAbil enum BattleMoveEffects effect = GetMoveEffect(move); if (effect == EFFECT_SKY_DROP || effect == EFFECT_SNIPE_SHOT - || atkAbility == ABILITY_PROPELLER_TAIL - || atkAbility == ABILITY_STALWART) + || AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_PROPELLER_TAIL) + || AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_STALWART)) return TRUE; return FALSE; } -bool32 ShouldTryOHKO(u32 battlerAtk, u32 battlerDef, enum Ability atkAbility, enum Ability defAbility, u32 move) +bool32 ShouldTryOHKO(u32 battlerAtk, u32 battlerDef, u32 move) { enum HoldEffect holdEffect = gAiLogicData->holdEffects[battlerDef]; u32 accuracy = gAiLogicData->moveAccuracy[battlerAtk][battlerDef][gAiThinkingStruct->movesetIndex]; @@ -2027,12 +1998,12 @@ bool32 ShouldTryOHKO(u32 battlerAtk, u32 battlerDef, enum Ability atkAbility, en else if (holdEffect == HOLD_EFFECT_FOCUS_SASH && AI_BattlerAtMaxHp(battlerDef)) return FALSE; - if (!DoesBattlerIgnoreAbilityChecks(battlerAtk, atkAbility, move) && defAbility == ABILITY_STURDY) + if (!DoesBattlerIgnoreAbilityChecks(battlerAtk, move) && AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_STURDY)) return FALSE; if (((gBattleMons[battlerDef].volatiles.lockOn && gDisableStructs[battlerDef].battlerWithSureHit == battlerAtk) - || atkAbility == ABILITY_NO_GUARD || defAbility == ABILITY_NO_GUARD) + || AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_NO_GUARD) || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_NO_GUARD)) && gBattleMons[battlerAtk].level >= gBattleMons[battlerDef].level) { return TRUE; @@ -2169,6 +2140,10 @@ s32 ProtectChecks(u32 battlerAtk, u32 battlerDef, u32 move, u32 predictedMove) // stat stages bool32 CanLowerStat(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData, enum Stat stat) { + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battlerDef); //Normal storage used since the AI ability is set manually + battlerTraits[0] = aiData->abilities[battlerDef]; //First trait set manually to deal with timing issue + if (gBattleMons[battlerDef].statStages[stat] == MIN_STAT_STAGE) return FALSE; @@ -2176,46 +2151,35 @@ bool32 CanLowerStat(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData, return FALSE; u32 move = gAiThinkingStruct->moveConsidered; - enum Ability abilityAtk = aiData->abilities[battlerAtk]; - if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_MIST && abilityAtk != ABILITY_INFILTRATOR) + if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_MIST && !AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_INFILTRATOR)) return FALSE; - if (!DoesBattlerIgnoreAbilityChecks(battlerAtk, abilityAtk, move)) + if (!DoesBattlerIgnoreAbilityChecks(battlerAtk, move)) { if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) && AI_IsAbilityOnSide(battlerDef, ABILITY_FLOWER_VEIL)) return FALSE; - switch (aiData->abilities[battlerDef]) - { - case ABILITY_SPEED_BOOST: + if (SearchTraits(battlerTraits, ABILITY_SPEED_BOOST)) if (stat == STAT_SPEED) return FALSE; - case ABILITY_HYPER_CUTTER: + if (SearchTraits(battlerTraits, ABILITY_HYPER_CUTTER)) if (stat == STAT_ATK) return FALSE; - case ABILITY_BIG_PECKS: + if (SearchTraits(battlerTraits, ABILITY_BIG_PECKS)) if (stat == STAT_DEF) return FALSE; - case ABILITY_ILLUMINATE: - if (GetConfig(CONFIG_ILLUMINATE_EFFECT) < GEN_9) - break; - case ABILITY_KEEN_EYE: - case ABILITY_MINDS_EYE: + if ((SearchTraits(battlerTraits, ABILITY_ILLUMINATE) && GetConfig(CONFIG_ILLUMINATE_EFFECT) >= GEN_9) + || SearchTraits(battlerTraits, ABILITY_KEEN_EYE) + || SearchTraits(battlerTraits, ABILITY_MINDS_EYE)) if (stat == STAT_ACC) return FALSE; - case ABILITY_CONTRARY: - case ABILITY_CLEAR_BODY: - case ABILITY_WHITE_SMOKE: - case ABILITY_FULL_METAL_BODY: + if (SearchTraits(battlerTraits, ABILITY_CONTRARY) + || SearchTraits(battlerTraits, ABILITY_CLEAR_BODY) + || SearchTraits(battlerTraits, ABILITY_WHITE_SMOKE) + || SearchTraits(battlerTraits, ABILITY_FULL_METAL_BODY) + || (SearchTraits(battlerTraits, ABILITY_SHIELD_DUST) && !IsBattleMoveStatus(move) && GetActiveGimmick(battlerAtk) != GIMMICK_DYNAMAX)) return FALSE; - case ABILITY_SHIELD_DUST: - if (!IsBattleMoveStatus(move) && GetActiveGimmick(battlerAtk) != GIMMICK_DYNAMAX) - return FALSE; - break; - default: - break; - } } if (stat == STAT_SPEED) @@ -2242,7 +2206,7 @@ u32 IncreaseStatDownScore(u32 battlerAtk, u32 battlerDef, enum Stat stat) if (GetBattlerSecondaryDamage(battlerDef) >= gBattleMons[battlerDef].hp) return NO_INCREASE; - if (DoesAbilityRaiseStatsWhenLowered(gAiLogicData->abilities[battlerDef])) + if (DoesAbilityRaiseStatsWhenLowered(battlerDef)) return NO_INCREASE; // TODO: Avoid decreasing stat if @@ -2303,10 +2267,10 @@ u32 IncreaseStatDownScore(u32 battlerAtk, u32 battlerDef, enum Stat stat) return (tempScore > BEST_EFFECT) ? BEST_EFFECT : tempScore; // don't inflate score so only max +4 } -bool32 BattlerStatCanRise(u32 battler, enum Ability battlerAbility, enum Stat stat) +bool32 BattlerStatCanRise(u32 battler, enum Stat stat) { - if ((gBattleMons[battler].statStages[stat] < MAX_STAT_STAGE && battlerAbility != ABILITY_CONTRARY) - || (battlerAbility == ABILITY_CONTRARY && gBattleMons[battler].statStages[stat] > MIN_STAT_STAGE)) + if ((gBattleMons[battler].statStages[stat] < MAX_STAT_STAGE && !AI_BATTLER_HAS_TRAIT(battler, ABILITY_CONTRARY)) + || (AI_BATTLER_HAS_TRAIT(battler, ABILITY_CONTRARY) && gBattleMons[battler].statStages[stat] > MIN_STAT_STAGE)) return TRUE; return FALSE; } @@ -3140,7 +3104,7 @@ static u32 GetPoisonDamage(u32 battlerId) { u32 damage = 0; - if (gAiLogicData->abilities[battlerId] == ABILITY_POISON_HEAL) + if (AI_BATTLER_HAS_TRAIT(battlerId, ABILITY_POISON_HEAL)) return damage; if (gBattleMons[battlerId].status1 & STATUS1_POISON) @@ -3162,30 +3126,29 @@ static u32 GetPoisonDamage(u32 battlerId) return damage; } -static bool32 BattlerAffectedBySandstorm(u32 battlerId, enum Ability ability) +static bool32 BattlerAffectedBySandstorm(u32 battlerId) { if (!IS_BATTLER_ANY_TYPE(battlerId, TYPE_ROCK, TYPE_GROUND, TYPE_STEEL) - && ability != ABILITY_SAND_VEIL - && ability != ABILITY_SAND_FORCE - && ability != ABILITY_SAND_RUSH - && ability != ABILITY_OVERCOAT) + && !AI_BATTLER_HAS_TRAIT(battlerId, ABILITY_SAND_VEIL) + && !AI_BATTLER_HAS_TRAIT(battlerId, ABILITY_SAND_FORCE) + && !AI_BATTLER_HAS_TRAIT(battlerId, ABILITY_SAND_RUSH) + && !AI_BATTLER_HAS_TRAIT(battlerId, ABILITY_OVERCOAT)) return TRUE; return FALSE; } -static bool32 BattlerAffectedByHail(u32 battlerId, enum Ability ability) +static bool32 BattlerAffectedByHail(u32 battlerId) { if (!IS_BATTLER_OF_TYPE(battlerId, TYPE_ICE) - && ability != ABILITY_SNOW_CLOAK - && ability != ABILITY_OVERCOAT - && ability != ABILITY_ICE_BODY) + && !AI_BATTLER_HAS_TRAIT(battlerId, ABILITY_SNOW_CLOAK) + && !AI_BATTLER_HAS_TRAIT(battlerId, ABILITY_OVERCOAT) + && !AI_BATTLER_HAS_TRAIT(battlerId, ABILITY_ICE_BODY)) return TRUE; return FALSE; } static u32 GetWeatherDamage(u32 battlerId) { - enum Ability ability = gAiLogicData->abilities[battlerId]; enum HoldEffect holdEffect = gAiLogicData->holdEffects[battlerId]; u32 damage = 0; u32 weather = AI_GetWeather(); @@ -3194,7 +3157,7 @@ static u32 GetWeatherDamage(u32 battlerId) if (weather & B_WEATHER_SANDSTORM) { - if (BattlerAffectedBySandstorm(battlerId, ability) + if (BattlerAffectedBySandstorm(battlerId) && gBattleMons[battlerId].volatiles.semiInvulnerable != STATE_UNDERGROUND && gBattleMons[battlerId].volatiles.semiInvulnerable != STATE_UNDERWATER && holdEffect != HOLD_EFFECT_SAFETY_GOGGLES) @@ -3204,9 +3167,9 @@ static u32 GetWeatherDamage(u32 battlerId) damage = 1; } } - if ((weather & B_WEATHER_HAIL) && ability != ABILITY_ICE_BODY) + if ((weather & B_WEATHER_HAIL) && !AI_BATTLER_HAS_TRAIT(battlerId, ABILITY_ICE_BODY)) { - if (BattlerAffectedByHail(battlerId, ability) + if (BattlerAffectedByHail(battlerId) && gBattleMons[battlerId].volatiles.semiInvulnerable != STATE_UNDERGROUND && gBattleMons[battlerId].volatiles.semiInvulnerable != STATE_UNDERWATER && holdEffect != HOLD_EFFECT_SAFETY_GOGGLES) @@ -3223,7 +3186,7 @@ u32 GetBattlerSecondaryDamage(u32 battlerId) { u32 secondaryDamage; - if (gAiLogicData->abilities[battlerId] == ABILITY_MAGIC_GUARD) + if (AI_BATTLER_HAS_TRAIT(battlerId, ABILITY_MAGIC_GUARD)) return FALSE; secondaryDamage = GetLeechSeedDamage(battlerId) @@ -3236,16 +3199,16 @@ u32 GetBattlerSecondaryDamage(u32 battlerId) return secondaryDamage; } -bool32 BattlerWillFaintFromWeather(u32 battler, enum Ability ability) +bool32 BattlerWillFaintFromWeather(u32 battler) { - if ((BattlerAffectedBySandstorm(battler, ability) || BattlerAffectedByHail(battler, ability)) + if ((BattlerAffectedBySandstorm(battler) || BattlerAffectedByHail(battler)) && gBattleMons[battler].hp <= max(1, gBattleMons[battler].maxHP / 16)) return TRUE; return FALSE; } -bool32 BattlerWillFaintFromSecondaryDamage(u32 battler, enum Ability ability) +bool32 BattlerWillFaintFromSecondaryDamage(u32 battler) { if (GetBattlerSecondaryDamage(battler) != 0 && gBattleMons[battler].hp <= max(1, gBattleMons[battler].maxHP / 16)) @@ -3285,7 +3248,6 @@ static bool32 AnyUsefulStatIsRaised(u32 battler) static bool32 PartyBattlerShouldAvoidHazards(u32 currBattler, u32 switchBattler) { struct Pokemon *mon = &GetBattlerParty(currBattler)[switchBattler]; - enum Ability ability = GetMonAbility(mon); // we know our own party data enum HoldEffect holdEffect; u32 species = GetMonData(mon, MON_DATA_SPECIES); s32 hazardDamage = 0; @@ -3297,9 +3259,9 @@ static bool32 PartyBattlerShouldAvoidHazards(u32 currBattler, u32 switchBattler) if (!AreAnyHazardsOnSide(side)) return FALSE; - if (ability == ABILITY_MAGIC_GUARD) + if (BattlerHasTrait(currBattler, ABILITY_MAGIC_GUARD) || BattlerHasTrait(switchBattler, ABILITY_MAGIC_GUARD)) return FALSE; - if (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM || ability == ABILITY_KLUTZ) + if (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM || BattlerHasTrait(currBattler, ABILITY_KLUTZ) || BattlerHasTrait(switchBattler, ABILITY_KLUTZ)) holdEffect = HOLD_EFFECT_NONE; else holdEffect = gItemsInfo[GetMonData(mon, MON_DATA_HELD_ITEM)].holdEffect; @@ -3312,7 +3274,7 @@ static bool32 PartyBattlerShouldAvoidHazards(u32 currBattler, u32 switchBattler) hazardDamage += GetStealthHazardDamageByTypesAndHP(TYPE_SIDE_HAZARD_SHARP_STEEL, type1, type2, maxHp); if (IsHazardOnSide(side, HAZARDS_SPIKES) && ((type1 != TYPE_FLYING && type2 != TYPE_FLYING - && ability != ABILITY_LEVITATE && holdEffect != HOLD_EFFECT_AIR_BALLOON) + && !(BattlerHasTrait(currBattler, ABILITY_LEVITATE) || BattlerHasTrait(switchBattler, ABILITY_LEVITATE)) && holdEffect != HOLD_EFFECT_AIR_BALLOON) || holdEffect == HOLD_EFFECT_IRON_BALL || gFieldStatuses & STATUS_FIELD_GRAVITY)) { s32 spikesDmg = maxHp / ((5 - gSideTimers[GetBattlerSide(currBattler)].spikesAmount) * 2); @@ -3326,7 +3288,7 @@ static bool32 PartyBattlerShouldAvoidHazards(u32 currBattler, u32 switchBattler) return FALSE; } -enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, enum Ability defAbility, u32 move, u32 moveIndex) +enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, u32 move, u32 moveIndex) { bool32 hasStatBoost = AnyUsefulStatIsRaised(battlerAtk) || gBattleMons[battlerDef].statStages[STAT_EVASION] >= 9; //Significant boost in evasion for any class u32 battlerToSwitch; @@ -3334,7 +3296,7 @@ enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, enum Ability defAbility // Palafin always wants to activate Zero to Hero if (gBattleMons[battlerAtk].species == SPECIES_PALAFIN_ZERO - && gBattleMons[battlerAtk].ability == ABILITY_ZERO_TO_HERO + && BattlerHasTrait(battlerAtk, ABILITY_ZERO_TO_HERO) && CountUsablePartyMons(battlerAtk) != 0) return SHOULD_PIVOT; @@ -3357,6 +3319,9 @@ enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, enum Ability defAbility { if (!CanAIFaintTarget(battlerAtk, battlerDef, 0)) // Can't KO foe otherwise { + enum Ability AIBattlerTraits[MAX_MON_TRAITS]; + AI_STORE_BATTLER_TRAITS(battlerDef); + if (CanAIFaintTarget(battlerAtk, battlerDef, 2)) { // attacker can kill target in two hits (theoretically) @@ -3365,17 +3330,17 @@ enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, enum Ability defAbility if (!IsBattleMoveStatus(move) && ((gAiLogicData->shouldSwitch & (1u << battlerAtk)) || (AI_BattlerAtMaxHp(battlerDef) && (gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_FOCUS_SASH - || (GetConfig(CONFIG_STURDY) >= GEN_5 && defAbility == ABILITY_STURDY) - || defAbility == ABILITY_MULTISCALE - || defAbility == ABILITY_SHADOW_SHIELD)))) + || (GetConfig(CONFIG_STURDY) >= GEN_5 && AISearchTraits(AIBattlerTraits, ABILITY_STURDY)) + || AISearchTraits(AIBattlerTraits, ABILITY_MULTISCALE) + || AISearchTraits(AIBattlerTraits, ABILITY_SHADOW_SHIELD))))) return SHOULD_PIVOT; // pivot to break sash/sturdy/multiscale } else if (!hasStatBoost) { if (!IsBattleMoveStatus(move) && (AI_BattlerAtMaxHp(battlerDef) && (gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_FOCUS_SASH - || (GetConfig(CONFIG_STURDY) >= GEN_5 && defAbility == ABILITY_STURDY) - || defAbility == ABILITY_MULTISCALE - || defAbility == ABILITY_SHADOW_SHIELD))) + || (GetConfig(CONFIG_STURDY) >= GEN_5 && AISearchTraits(AIBattlerTraits, ABILITY_STURDY)) + || AISearchTraits(AIBattlerTraits, ABILITY_MULTISCALE) + || AISearchTraits(AIBattlerTraits, ABILITY_SHADOW_SHIELD)))) return SHOULD_PIVOT; // pivot to break sash/sturdy/multiscale if (gAiLogicData->shouldSwitch & (1u << battlerAtk)) @@ -3428,7 +3393,7 @@ enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, enum Ability defAbility { if (CanAIFaintTarget(battlerAtk, battlerDef, 0)) { - if (!BattlerWillFaintFromSecondaryDamage(battlerAtk, gAiLogicData->abilities[battlerAtk])) + if (!BattlerWillFaintFromSecondaryDamage(battlerAtk)) return CAN_TRY_PIVOT; // Use this move to KO if you must } else // Can't KO the foe @@ -3440,7 +3405,7 @@ enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, enum Ability defAbility { if (CanAIFaintTarget(battlerAtk, battlerDef, 0)) { - if (!BattlerWillFaintFromSecondaryDamage(battlerAtk, gAiLogicData->abilities[battlerAtk]) // This is the only move that can KO + if (!BattlerWillFaintFromSecondaryDamage(battlerAtk) // This is the only move that can KO && !hasStatBoost) //You're not wasting a valuable stat boost { return CAN_TRY_PIVOT; @@ -3518,7 +3483,7 @@ bool32 CanKnockOffItem(u32 battler, u32 item) )) && IsOnPlayerSide(battler)) return FALSE; - if (gAiLogicData->abilities[battler] == ABILITY_STICKY_HOLD) + if (AI_BATTLER_HAS_TRAIT(battler, ABILITY_STICKY_HOLD)) return FALSE; if (!CanBattlerGetOrLoseItem(battler, item)) @@ -3528,7 +3493,7 @@ bool32 CanKnockOffItem(u32 battler, u32 item) } // status checks -bool32 IsBattlerIncapacitated(u32 battler, enum Ability ability) +bool32 IsBattlerIncapacitated(u32 battler) { if ((gBattleMons[battler].status1 & STATUS1_FREEZE) && !HasThawingMove(battler)) return TRUE; // if battler has thawing move we assume they will definitely use it, and thus being frozen should be neglected @@ -3536,41 +3501,43 @@ bool32 IsBattlerIncapacitated(u32 battler, enum Ability ability) if (gBattleMons[battler].status1 & STATUS1_SLEEP && !HasMoveWithEffect(battler, EFFECT_SLEEP_TALK)) return TRUE; - if (gDisableStructs[battler].rechargeTimer > 0 || (ability == ABILITY_TRUANT && gDisableStructs[battler].truantCounter != 0)) + if (gDisableStructs[battler].rechargeTimer > 0 || (AI_BATTLER_HAS_TRAIT(battler, ABILITY_TRUANT) && gDisableStructs[battler].truantCounter != 0)) return TRUE; return FALSE; } -bool32 AI_CanPutToSleep(u32 battlerAtk, u32 battlerDef, enum Ability defAbility, u32 move, u32 partnerMove) +bool32 AI_CanPutToSleep(u32 battlerAtk, u32 battlerDef, u32 move, u32 partnerMove) { - if (!CanBeSlept(battlerAtk, battlerDef, defAbility, BLOCKED_BY_SLEEP_CLAUSE) + if (!CanBeSlept(battlerAtk, battlerDef, BLOCKED_BY_SLEEP_CLAUSE) || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) || PartnerMoveEffectIsStatusSameTarget(BATTLE_PARTNER(battlerAtk), battlerDef, partnerMove)) // shouldn't try to sleep mon that partner is trying to make sleep return FALSE; return TRUE; } -static inline bool32 DoesBattlerBenefitFromAllVolatileStatus(u32 battler, enum Ability ability) +static inline bool32 DoesBattlerBenefitFromAllVolatileStatus(u32 battler) { - if (ability == ABILITY_MARVEL_SCALE - || ability == ABILITY_QUICK_FEET - || ability == ABILITY_MAGIC_GUARD - || (ability == ABILITY_GUTS && HasMoveWithCategory(battler, DAMAGE_CATEGORY_PHYSICAL)) - || HasMoveWithEffect(battler, EFFECT_FACADE) - || HasMoveWithEffect(battler, EFFECT_PSYCHO_SHIFT)) - return TRUE; + enum Ability AIBattlerTraits[MAX_MON_TRAITS]; + AI_STORE_BATTLER_TRAITS(battler); + + if (AISearchTraits(AIBattlerTraits, ABILITY_MARVEL_SCALE) + || AISearchTraits(AIBattlerTraits, ABILITY_QUICK_FEET) + || AISearchTraits(AIBattlerTraits, ABILITY_MAGIC_GUARD) + || (AISearchTraits(AIBattlerTraits, ABILITY_GUTS) && HasMoveWithCategory(battler, DAMAGE_CATEGORY_PHYSICAL)) + || HasMoveWithEffect(battler, EFFECT_FACADE) + || HasMoveWithEffect(battler, EFFECT_PSYCHO_SHIFT)) + return TRUE; // battler can be poisoned and has move/ability that synergizes with being poisoned return FALSE; } bool32 ShouldPoison(u32 battlerAtk, u32 battlerDef) { - enum Ability abilityDef = gAiLogicData->abilities[battlerDef]; // Battler can be poisoned and has move/ability that synergizes with being poisoned - if (CanBePoisoned(battlerAtk, battlerDef, gAiLogicData->abilities[battlerAtk], abilityDef) && ( - DoesBattlerBenefitFromAllVolatileStatus(battlerDef, abilityDef) - || abilityDef == ABILITY_POISON_HEAL - || (abilityDef == ABILITY_TOXIC_BOOST && HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL)))) + if (CanBePoisoned(battlerAtk, battlerDef) && ( + DoesBattlerBenefitFromAllVolatileStatus(battlerDef) + || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_POISON_HEAL) + || (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_TOXIC_BOOST) && HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL)))) { if (battlerAtk == battlerDef) // Targeting self return TRUE; @@ -3583,13 +3550,13 @@ bool32 ShouldPoison(u32 battlerAtk, u32 battlerDef) return TRUE; } -bool32 ShouldBurn(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef) +bool32 ShouldBurn(u32 battlerAtk, u32 battlerDef) { // Battler can be burned and has move/ability that synergizes with being burned - if (CanBeBurned(battlerAtk, battlerDef, abilityDef) && ( - DoesBattlerBenefitFromAllVolatileStatus(battlerDef, abilityDef) - || abilityDef == ABILITY_HEATPROOF - || (abilityDef == ABILITY_FLARE_BOOST && HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL)))) + if (CanBeBurned(battlerAtk, battlerDef) && ( + DoesBattlerBenefitFromAllVolatileStatus(battlerDef) + || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_HEATPROOF) + || (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_FLARE_BOOST) && HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL)))) { if (battlerAtk == battlerDef) // Targeting self return TRUE; @@ -3603,11 +3570,11 @@ bool32 ShouldBurn(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef) return TRUE; } -bool32 ShouldFreezeOrFrostbite(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef) +bool32 ShouldFreezeOrFrostbite(u32 battlerAtk, u32 battlerDef) { if (!B_USE_FROSTBITE) { - if (CanBeFrozen(battlerAtk, battlerDef, abilityDef)) + if (CanBeFrozen(battlerAtk, battlerDef)) { if (battlerAtk == battlerDef) // Targeting self return FALSE; @@ -3619,8 +3586,8 @@ bool32 ShouldFreezeOrFrostbite(u32 battlerAtk, u32 battlerDef, enum Ability abil else { // Battler can be frostbitten and has move/ability that synergizes with being frostbitten - if (CanBeFrozen(battlerAtk, battlerDef, abilityDef) - && DoesBattlerBenefitFromAllVolatileStatus(battlerDef, abilityDef)) + if (CanBeFrozen(battlerAtk, battlerDef) + && DoesBattlerBenefitFromAllVolatileStatus(battlerDef)) { if (battlerAtk == battlerDef) // Targeting self return TRUE; @@ -3635,11 +3602,11 @@ bool32 ShouldFreezeOrFrostbite(u32 battlerAtk, u32 battlerDef, enum Ability abil } } -bool32 ShouldParalyze(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef) +bool32 ShouldParalyze(u32 battlerAtk, u32 battlerDef) { // Battler can be paralyzed and has move/ability that synergizes with being paralyzed - if (CanBeParalyzed(battlerAtk, battlerDef, abilityDef) && ( - DoesBattlerBenefitFromAllVolatileStatus(battlerDef, abilityDef))) + if (CanBeParalyzed(battlerAtk, battlerDef) && ( + DoesBattlerBenefitFromAllVolatileStatus(battlerDef))) { if (battlerAtk == battlerDef) // Targeting self return TRUE; @@ -3652,9 +3619,9 @@ bool32 ShouldParalyze(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef) return TRUE; } -bool32 AI_CanPoison(u32 battlerAtk, u32 battlerDef, enum Ability defAbility, u32 move, u32 partnerMove) +bool32 AI_CanPoison(u32 battlerAtk, u32 battlerDef, u32 move, u32 partnerMove) { - if (!CanBePoisoned(battlerAtk, battlerDef, gAiLogicData->abilities[battlerAtk], defAbility) + if (!CanBePoisoned(battlerAtk, battlerDef) || gAiLogicData->effectiveness[battlerAtk][battlerDef][gAiThinkingStruct->movesetIndex] == UQ_4_12(0.0) || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) || PartnerMoveEffectIsStatusSameTarget(BATTLE_PARTNER(battlerAtk), battlerDef, partnerMove)) @@ -3663,9 +3630,9 @@ bool32 AI_CanPoison(u32 battlerAtk, u32 battlerDef, enum Ability defAbility, u32 return TRUE; } -bool32 AI_CanParalyze(u32 battlerAtk, u32 battlerDef, enum Ability defAbility, u32 move, u32 partnerMove) +bool32 AI_CanParalyze(u32 battlerAtk, u32 battlerDef, u32 move, u32 partnerMove) { - if (!CanBeParalyzed(battlerAtk, battlerDef, defAbility) + if (!CanBeParalyzed(battlerAtk, battlerDef) || gAiLogicData->effectiveness[battlerAtk][battlerDef][gAiThinkingStruct->movesetIndex] == UQ_4_12(0.0) || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) || PartnerMoveEffectIsStatusSameTarget(BATTLE_PARTNER(battlerAtk), battlerDef, partnerMove)) @@ -3673,34 +3640,34 @@ bool32 AI_CanParalyze(u32 battlerAtk, u32 battlerDef, enum Ability defAbility, u return TRUE; } -bool32 AI_CanBeConfused(u32 battlerAtk, u32 battlerDef, u32 move, enum Ability abilityDef) +bool32 AI_CanBeConfused(u32 battlerAtk, u32 battlerDef, u32 move) { if (gBattleMons[battlerDef].volatiles.confusionTurns > 0 - || (abilityDef == ABILITY_OWN_TEMPO && !DoesBattlerIgnoreAbilityChecks(battlerAtk, gAiLogicData->abilities[battlerAtk], move)) - || IsBattlerTerrainAffected(battlerDef, abilityDef, gAiLogicData->holdEffects[battlerDef], STATUS_FIELD_MISTY_TERRAIN) + || (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_OWN_TEMPO) && !DoesBattlerIgnoreAbilityChecks(battlerAtk, move)) + || IsBattlerTerrainAffected(battlerDef, gAiLogicData->holdEffects[battlerDef], STATUS_FIELD_MISTY_TERRAIN) || gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_SAFEGUARD || DoesSubstituteBlockMove(battlerAtk, battlerDef, move)) return FALSE; return TRUE; } -bool32 AI_CanConfuse(u32 battlerAtk, u32 battlerDef, enum Ability defAbility, u32 battlerAtkPartner, u32 move, u32 partnerMove) +bool32 AI_CanConfuse(u32 battlerAtk, u32 battlerDef, u32 battlerAtkPartner, u32 move, u32 partnerMove) { if (GetBattlerMoveTargetType(battlerAtk, move) == MOVE_TARGET_FOES_AND_ALLY - && AI_CanBeConfused(battlerAtk, battlerDef, move, defAbility) - && !AI_CanBeConfused(battlerAtk, BATTLE_PARTNER(battlerDef), move, gAiLogicData->abilities[BATTLE_PARTNER(battlerDef)])) + && AI_CanBeConfused(battlerAtk, battlerDef, move) + && !AI_CanBeConfused(battlerAtk, BATTLE_PARTNER(battlerDef), move)) return FALSE; - if (!AI_CanBeConfused(battlerAtk, battlerDef, move, defAbility) + if (!AI_CanBeConfused(battlerAtk, battlerDef, move) || DoesPartnerHaveSameMoveEffect(battlerAtkPartner, battlerDef, move, partnerMove)) return FALSE; return TRUE; } -bool32 AI_CanBurn(u32 battlerAtk, u32 battlerDef, enum Ability defAbility, u32 battlerAtkPartner, u32 move, u32 partnerMove) +bool32 AI_CanBurn(u32 battlerAtk, u32 battlerDef, u32 battlerAtkPartner, u32 move, u32 partnerMove) { - if (!CanBeBurned(battlerAtk, battlerDef, defAbility) + if (!CanBeBurned(battlerAtk, battlerDef) || gAiLogicData->effectiveness[battlerAtk][battlerDef][gAiThinkingStruct->movesetIndex] == UQ_4_12(0.0) || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) || PartnerMoveEffectIsStatusSameTarget(battlerAtkPartner, battlerDef, partnerMove)) @@ -3710,9 +3677,9 @@ bool32 AI_CanBurn(u32 battlerAtk, u32 battlerDef, enum Ability defAbility, u32 b return TRUE; } -bool32 AI_CanGiveFrostbite(u32 battlerAtk, u32 battlerDef, enum Ability defAbility, u32 battlerAtkPartner, u32 move, u32 partnerMove) +bool32 AI_CanGiveFrostbite(u32 battlerAtk, u32 battlerDef, u32 battlerAtkPartner, u32 move, u32 partnerMove) { - if (!CanBeFrozen(battlerAtk, battlerDef, defAbility) + if (!CanBeFrozen(battlerAtk, battlerDef) || gAiLogicData->effectiveness[battlerAtk][battlerDef][gAiThinkingStruct->movesetIndex] == UQ_4_12(0.0) || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) || PartnerMoveEffectIsStatusSameTarget(battlerAtkPartner, battlerDef, partnerMove)) @@ -3722,28 +3689,28 @@ bool32 AI_CanGiveFrostbite(u32 battlerAtk, u32 battlerDef, enum Ability defAbili return TRUE; } -bool32 AI_CanBeInfatuated(u32 battlerAtk, u32 battlerDef, enum Ability defAbility) +bool32 AI_CanBeInfatuated(u32 battlerAtk, u32 battlerDef) { if (gBattleMons[battlerDef].volatiles.infatuation || gAiLogicData->effectiveness[battlerAtk][battlerDef][gAiThinkingStruct->movesetIndex] == UQ_4_12(0.0) - || defAbility == ABILITY_OBLIVIOUS + || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_OBLIVIOUS) || !AreBattlersOfOppositeGender(battlerAtk, battlerDef) || AI_IsAbilityOnSide(battlerDef, ABILITY_AROMA_VEIL)) return FALSE; return TRUE; } -u32 ShouldTryToFlinch(u32 battlerAtk, u32 battlerDef, enum Ability atkAbility, enum Ability defAbility, u32 move) +u32 ShouldTryToFlinch(u32 battlerAtk, u32 battlerDef, u32 move) { u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData); - if (((!IsMoldBreakerTypeAbility(battlerAtk, gAiLogicData->abilities[battlerAtk]) && (defAbility == ABILITY_SHIELD_DUST || defAbility == ABILITY_INNER_FOCUS)) + if (((!HasMoldBreakerTypeAbility(battlerAtk) && (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_SHIELD_DUST) || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_INNER_FOCUS))) || gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_COVERT_CLOAK || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) || AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY))) // Opponent goes first { return 0; } - else if ((atkAbility == ABILITY_SERENE_GRACE + else if ((AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_SERENE_GRACE) || gBattleMons[battlerDef].status1 & STATUS1_PARALYSIS || gBattleMons[battlerDef].volatiles.infatuation || gBattleMons[battlerDef].volatiles.confusionTurns > 0) @@ -3763,7 +3730,7 @@ bool32 ShouldTrap(u32 battlerAtk, u32 battlerDef, u32 move) if (IsBattlerTrapped(battlerAtk, battlerDef)) return FALSE; - if (BattlerWillFaintFromSecondaryDamage(battlerDef, gAiLogicData->abilities[battlerDef])) + if (BattlerWillFaintFromSecondaryDamage(battlerDef)) return TRUE; // battler is taking secondary damage with low HP if (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_STALL) @@ -3791,15 +3758,15 @@ bool32 IsFlinchGuaranteed(u32 battlerAtk, u32 battlerDef, u32 move) { const struct AdditionalEffect *additionalEffect = GetMoveAdditionalEffectById(move, i); // Only consider effects with a guaranteed chance to happen - if (!MoveEffectIsGuaranteed(battlerAtk, gAiLogicData->abilities[battlerAtk], additionalEffect)) + if (!MoveEffectIsGuaranteed(battlerAtk, additionalEffect)) continue; if (additionalEffect->moveEffect == MOVE_EFFECT_FLINCH) { if (gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_COVERT_CLOAK || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) - || (!IsMoldBreakerTypeAbility(battlerAtk, gAiLogicData->abilities[battlerAtk]) - && (gAiLogicData->abilities[battlerDef] == ABILITY_SHIELD_DUST || gAiLogicData->abilities[battlerDef] == ABILITY_INNER_FOCUS))) + || (!HasMoldBreakerTypeAbility(battlerAtk) + && (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_SHIELD_DUST) || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_SHIELD_DUST) || AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_INNER_FOCUS)))) return FALSE; else return TRUE; @@ -3810,11 +3777,10 @@ bool32 IsFlinchGuaranteed(u32 battlerAtk, u32 battlerDef, u32 move) bool32 HasChoiceEffect(u32 battler) { - enum Ability ability = gAiLogicData->abilities[battler]; - if (ability == ABILITY_GORILLA_TACTICS) + if (AI_BATTLER_HAS_TRAIT(battler, ABILITY_GORILLA_TACTICS)) return TRUE; - if (ability == ABILITY_KLUTZ) + if (AI_BATTLER_HAS_TRAIT(battler, ABILITY_KLUTZ)) return FALSE; enum HoldEffect holdEffect = gAiLogicData->holdEffects[battler]; @@ -3850,7 +3816,7 @@ bool32 AnyPartyMemberStatused(u32 battlerId, bool32 checkSoundproof) { struct Pokemon *party; u32 i, battlerOnField1, battlerOnField2; - bool32 hasStatusToCure = FALSE; + bool32 hasStatusToCure = FALSE; party = GetBattlerParty(battlerId); @@ -3860,7 +3826,7 @@ bool32 AnyPartyMemberStatused(u32 battlerId, bool32 checkSoundproof) battlerOnField2 = gBattlerPartyIndexes[GetPartnerBattler(battlerId)]; // Check partner's status if ((GetConfig(CONFIG_HEAL_BELL_SOUNDPROOF) == GEN_5 - || gAiLogicData->abilities[BATTLE_PARTNER(battlerId)] != ABILITY_SOUNDPROOF + || !AI_BATTLER_HAS_TRAIT(BATTLE_PARTNER(battlerId), ABILITY_SOUNDPROOF) || !checkSoundproof) && GetMonData(&party[battlerOnField2], MON_DATA_STATUS) != STATUS1_NONE && ShouldCureStatus(battlerId, BATTLE_PARTNER(battlerId), gAiLogicData)) @@ -3875,7 +3841,7 @@ bool32 AnyPartyMemberStatused(u32 battlerId, bool32 checkSoundproof) // Check attacker's status if ((GetConfig(CONFIG_HEAL_BELL_SOUNDPROOF) == GEN_5 || GetConfig(CONFIG_HEAL_BELL_SOUNDPROOF) >= GEN_8 - || gAiLogicData->abilities[battlerId] != ABILITY_SOUNDPROOF || !checkSoundproof) + || !AI_BATTLER_HAS_TRAIT(battlerId, ABILITY_SOUNDPROOF) || !checkSoundproof) && GetMonData(&party[battlerOnField1], MON_DATA_STATUS) != STATUS1_NONE && ShouldCureStatus(battlerId, battlerId, gAiLogicData)) hasStatusToCure = TRUE; @@ -3887,7 +3853,7 @@ bool32 AnyPartyMemberStatused(u32 battlerId, bool32 checkSoundproof) continue; if (GetConfig(CONFIG_HEAL_BELL_SOUNDPROOF) < GEN_5 && checkSoundproof - && GetMonAbility(&party[i]) == ABILITY_SOUNDPROOF) + && MonHasTrait(&party[i], ABILITY_SOUNDPROOF)) continue; if (GetMonData(&party[i], MON_DATA_STATUS) != STATUS1_NONE) return TRUE; @@ -4032,7 +3998,7 @@ static bool32 ShouldCureStatusInternal(u32 battlerAtk, u32 battlerDef, bool32 us bool32 isHarmless = FALSE; - if (DoesBattlerBenefitFromAllVolatileStatus(battlerDef, aiData->abilities[battlerDef])) + if (DoesBattlerBenefitFromAllVolatileStatus(battlerDef)) isHarmless = TRUE; if (status & STATUS1_PSN_ANY) @@ -4040,10 +4006,10 @@ static bool32 ShouldCureStatusInternal(u32 battlerAtk, u32 battlerDef, bool32 us if (aiData->holdEffects[battlerDef] == HOLD_EFFECT_TOXIC_ORB) return FALSE; - if (aiData->abilities[battlerDef] == ABILITY_POISON_HEAL) + if (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_POISON_HEAL)) isHarmless = TRUE; - if (aiData->abilities[battlerDef] == ABILITY_TOXIC_BOOST && !isHarmless) + if (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_TOXIC_BOOST) && !isHarmless) { if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL)) isHarmless = TRUE; @@ -4057,7 +4023,7 @@ static bool32 ShouldCureStatusInternal(u32 battlerAtk, u32 battlerDef, bool32 us if (aiData->holdEffects[battlerDef] == HOLD_EFFECT_FLAME_ORB) return FALSE; - if (aiData->abilities[battlerDef] == ABILITY_FLARE_BOOST && !isHarmless) + if (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_FLARE_BOOST) && !isHarmless) { if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL)) isHarmless = TRUE; @@ -4409,7 +4375,7 @@ bool32 ShouldUseWishAromatherapy(u32 battlerAtk, u32 battlerDef, u32 move) party = GetBattlerParty(battlerAtk); if (CountUsablePartyMons(battlerAtk) == 0 - && (CanTargetFaintAi(battlerDef, battlerAtk) || BattlerWillFaintFromSecondaryDamage(battlerAtk, gAiLogicData->abilities[battlerAtk]))) + && (CanTargetFaintAi(battlerDef, battlerAtk) || BattlerWillFaintFromSecondaryDamage(battlerAtk))) return FALSE; // Don't heal if last mon and will faint for (i = 0; i < PARTY_SIZE; i++) @@ -4479,23 +4445,17 @@ void FreeRestoreBattleMons(struct BattlePokemon *savedBattleMons) // Set potential field effect from ability for switch in static void SetBattlerFieldStatusForSwitchin(u32 battler) { - switch (gAiLogicData->abilities[battler]) - { - case ABILITY_VESSEL_OF_RUIN: + enum Ability AIBattlerTraits[MAX_MON_TRAITS]; + AI_STORE_BATTLER_TRAITS(battler); + + if (AISearchTraits(AIBattlerTraits, ABILITY_VESSEL_OF_RUIN)) gBattleMons[battler].volatiles.vesselOfRuin = TRUE; - break; - case ABILITY_SWORD_OF_RUIN: + if (AISearchTraits(AIBattlerTraits, ABILITY_SWORD_OF_RUIN)) gBattleMons[battler].volatiles.swordOfRuin = TRUE; - break; - case ABILITY_TABLETS_OF_RUIN: + if (AISearchTraits(AIBattlerTraits, ABILITY_TABLETS_OF_RUIN)) gBattleMons[battler].volatiles.tabletsOfRuin = TRUE; - break; - case ABILITY_BEADS_OF_RUIN: + if (AISearchTraits(AIBattlerTraits, ABILITY_BEADS_OF_RUIN)) gBattleMons[battler].volatiles.beadsOfRuin = TRUE; - break; - default: - break; - } } // party logic @@ -4848,7 +4808,7 @@ static enum AIScore IncreaseStatUpScoreInternal(u32 battlerAtk, u32 battlerDef, enum Stat statId = GetStatBeingChanged(statChange); u32 stages = GetStagesOfStatChange(statChange); - if (considerContrary && gAiLogicData->abilities[battlerAtk] == ABILITY_CONTRARY) + if (considerContrary && AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_CONTRARY)) return NO_INCREASE; if (!ShouldRaiseAnyStat(battlerAtk, battlerDef)) @@ -4867,7 +4827,7 @@ static enum AIScore IncreaseStatUpScoreInternal(u32 battlerAtk, u32 battlerDef, return NO_INCREASE; // Stat stages are effectively doubled under Simple. - if (gAiLogicData->abilities[battlerAtk] == ABILITY_SIMPLE) + if (AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_SIMPLE)) stages *= 2; // Predicting switch @@ -4976,7 +4936,7 @@ void IncreasePoisonScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) || gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_CURE_PSN || gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) return; - if (AI_CanPoison(battlerAtk, battlerDef, gAiLogicData->abilities[battlerDef], move, gAiLogicData->partnerMove) && gAiLogicData->hpPercents[battlerDef] > 20) + if (AI_CanPoison(battlerAtk, battlerDef, move, gAiLogicData->partnerMove) && gAiLogicData->hpPercents[battlerDef] > 20) { if (!HasDamagingMove(battlerDef)) ADJUST_SCORE_PTR(DECENT_EFFECT); @@ -4985,8 +4945,8 @@ void IncreasePoisonScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) ADJUST_SCORE_PTR(WEAK_EFFECT); // stall tactic if (IsPowerBasedOnStatus(battlerAtk, EFFECT_DOUBLE_POWER_ON_ARG_STATUS, STATUS1_PSN_ANY) - || HasMoveWithEffect(battlerAtk, EFFECT_VENOM_DRENCH) - || gAiLogicData->abilities[battlerAtk] == ABILITY_MERCILESS) + || HasMoveWithEffect(battlerAtk, EFFECT_VENOM_DRENCH) + || AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_MERCILESS)) ADJUST_SCORE_PTR(DECENT_EFFECT); else ADJUST_SCORE_PTR(WEAK_EFFECT); @@ -4999,7 +4959,7 @@ void IncreaseBurnScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) || gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_CURE_BRN || gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) return; - if (AI_CanBurn(battlerAtk, battlerDef, gAiLogicData->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, gAiLogicData->partnerMove)) + if (AI_CanBurn(battlerAtk, battlerDef, BATTLE_PARTNER(battlerAtk), move, gAiLogicData->partnerMove)) { if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL) || (!(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_OMNISCIENT) // Not Omniscient but expects physical attacker @@ -5023,7 +4983,7 @@ void IncreaseParalyzeScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) || gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_CURE_PAR || gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) return; - if (AI_CanParalyze(battlerAtk, battlerDef, gAiLogicData->abilities[battlerDef], move, gAiLogicData->partnerMove)) + if (AI_CanParalyze(battlerAtk, battlerDef, move, gAiLogicData->partnerMove)) { u32 atkSpeed = gAiLogicData->speedStats[battlerAtk]; u32 defSpeed = gAiLogicData->speedStats[battlerDef]; @@ -5045,7 +5005,7 @@ void IncreaseSleepScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) || gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_CURE_SLP || gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) return; - if (AI_CanPutToSleep(battlerAtk, battlerDef, gAiLogicData->abilities[battlerDef], move, gAiLogicData->partnerMove)) + if (AI_CanPutToSleep(battlerAtk, battlerDef, move, gAiLogicData->partnerMove)) ADJUST_SCORE_PTR(DECENT_EFFECT); else return; @@ -5065,13 +5025,13 @@ void IncreaseConfusionScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score || gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_CURE_CONFUSION || gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_CURE_STATUS) return; - if (AI_CanConfuse(battlerAtk, battlerDef, gAiLogicData->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, gAiLogicData->partnerMove) + if (AI_CanConfuse(battlerAtk, battlerDef, BATTLE_PARTNER(battlerAtk), move, gAiLogicData->partnerMove) && gAiLogicData->holdEffects[battlerDef] != HOLD_EFFECT_CURE_CONFUSION && gAiLogicData->holdEffects[battlerDef] != HOLD_EFFECT_CURE_STATUS) { if (gBattleMons[battlerDef].status1 & STATUS1_PARALYSIS || gBattleMons[battlerDef].volatiles.infatuation - || (gAiLogicData->abilities[battlerAtk] == ABILITY_SERENE_GRACE && HasMoveWithMoveEffectExcept(battlerAtk, MOVE_EFFECT_FLINCH, EFFECT_FIRST_TURN_ONLY))) + || (AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_SERENE_GRACE) && HasMoveWithMoveEffectExcept(battlerAtk, MOVE_EFFECT_FLINCH, EFFECT_FIRST_TURN_ONLY))) ADJUST_SCORE_PTR(GOOD_EFFECT); else ADJUST_SCORE_PTR(DECENT_EFFECT); @@ -5083,7 +5043,7 @@ void IncreaseFrostbiteScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return; - if (AI_CanGiveFrostbite(battlerAtk, battlerDef, gAiLogicData->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, gAiLogicData->partnerMove)) + if (AI_CanGiveFrostbite(battlerAtk, battlerDef, BATTLE_PARTNER(battlerAtk), move, gAiLogicData->partnerMove)) { if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL) || (!(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_OMNISCIENT) // Not Omniscient but expects special attacker @@ -5101,16 +5061,15 @@ void IncreaseFrostbiteScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score } } -bool32 AI_MoveMakesContact(enum Ability ability, enum HoldEffect holdEffect, u32 move) +bool32 AI_MoveMakesContact(enum HoldEffect holdEffect, u32 move, u32 battlerAtk) { if (MoveMakesContact(move) - && ability != ABILITY_LONG_REACH + && !AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_LONG_REACH) && holdEffect != HOLD_EFFECT_PROTECTIVE_PADS) return TRUE; return FALSE; } - bool32 IsConsideringZMove(u32 battlerAtk, u32 battlerDef, u32 move) { if (GetMovePower(move) == 0 && GetMoveZEffect(move) == Z_EFFECT_NONE) @@ -5258,11 +5217,11 @@ bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove) uq4_12_t effectiveness; struct SimulatedDamage dmg; - if (gBattleMons[battlerDef].ability == ABILITY_DISGUISE + if (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_DISGUISE) && !MoveIgnoresTargetAbility(zMove) && IsMimikyuDisguised(battlerDef)) return FALSE; // Don't waste a Z-Move busting disguise - if (gBattleMons[battlerDef].ability == ABILITY_ICE_FACE + if (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_ICE_FACE) && !MoveIgnoresTargetAbility(zMove) && gBattleMons[battlerDef].species == SPECIES_EISCUE_ICE && IsBattleMovePhysical(chosenMove)) return FALSE; // Don't waste a Z-Move busting Ice Face @@ -5408,7 +5367,7 @@ enum AIConsiderGimmick ShouldTeraFromCalcs(u32 battler, u32 opposingBattler, str if (dealtWithTera[i].median >= oppHp) { u16 move = aiMoves[i]; - if (killingMove == MOVE_NONE || GetBattleMovePriority(battler, gAiLogicData->abilities[battler], move) > GetBattleMovePriority(battler, gAiLogicData->abilities[battler], killingMove)) + if (killingMove == MOVE_NONE || GetBattleMovePriority(battler, move) > GetBattleMovePriority(battler, killingMove)) killingMove = move; } if (dealtWithoutTera[i].median >= oppHp) @@ -5440,7 +5399,7 @@ enum AIConsiderGimmick ShouldTeraFromCalcs(u32 battler, u32 opposingBattler, str if (takenWithTera[i].maximum >= aiHp) { u16 move = oppMoves[i]; - if (hardPunishingMove == MOVE_NONE || GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], move) > GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], hardPunishingMove)) + if (hardPunishingMove == MOVE_NONE || GetBattleMovePriority(opposingBattler, move) > GetBattleMovePriority(opposingBattler, hardPunishingMove)) hardPunishingMove = move; } } @@ -5492,7 +5451,7 @@ enum AIConsiderGimmick ShouldTeraFromCalcs(u32 battler, u32 opposingBattler, str { u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battler, opposingBattler, gAiLogicData); // will we go first? - if (AI_WhoStrikesFirst(battler, opposingBattler, killingMove, predictedMoveSpeedCheck, CONSIDER_PRIORITY) == AI_IS_FASTER && GetBattleMovePriority(battler, gAiLogicData->abilities[battler], killingMove) >= GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], hardPunishingMove)) + if (AI_WhoStrikesFirst(battler, opposingBattler, killingMove, predictedMoveSpeedCheck, CONSIDER_PRIORITY) == AI_IS_FASTER && GetBattleMovePriority(battler, killingMove) >= GetBattleMovePriority(opposingBattler, hardPunishingMove)) return USE_GIMMICK; } } @@ -5543,7 +5502,7 @@ enum AIConsiderGimmick ShouldTeraFromCalcs(u32 battler, u32 opposingBattler, str bool32 AI_IsBattlerAsleepOrComatose(u32 battlerId) { - return (gBattleMons[battlerId].status1 & STATUS1_SLEEP) || gAiLogicData->abilities[battlerId] == ABILITY_COMATOSE; + return (gBattleMons[battlerId].status1 & STATUS1_SLEEP) || AI_BATTLER_HAS_TRAIT(battlerId, ABILITY_COMATOSE); } s32 AI_TryToClearStats(u32 battlerAtk, u32 battlerDef, bool32 isDoubleBattle) @@ -5596,16 +5555,16 @@ bool32 AI_ShouldSetUpHazards(u32 battlerAtk, u32 battlerDef, u32 move, struct Ai { if (HasMoveWithEffect(battlerDef, EFFECT_MAGIC_COAT)) return FALSE; - if (DoesBattlerIgnoreAbilityChecks(battlerAtk, aiData->abilities[battlerAtk], move)) + if (DoesBattlerIgnoreAbilityChecks(battlerAtk, move)) return TRUE; - if (aiData->abilities[battlerDef] == ABILITY_MAGIC_BOUNCE) + if (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_MAGIC_BOUNCE)) return FALSE; } else { - if (DoesBattlerIgnoreAbilityChecks(battlerAtk, aiData->abilities[battlerAtk], move)) + if (DoesBattlerIgnoreAbilityChecks(battlerAtk, move)) return TRUE; - if (aiData->abilities[battlerDef] == ABILITY_SHIELD_DUST) + if (AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_SHIELD_DUST)) return FALSE; } return TRUE; @@ -5632,13 +5591,12 @@ void IncreaseTidyUpScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) bool32 AI_ShouldSpicyExtract(u32 battlerAtk, u32 battlerAtkPartner, u32 move, struct AiLogicData *aiData) { u32 preventsStatLoss; - enum Ability partnerAbility = aiData->abilities[battlerAtkPartner]; u32 opposingPosition = BATTLE_OPPOSITE(GetBattlerPosition(battlerAtk)); u32 opposingBattler = GetBattlerAtPosition(opposingPosition); if (gBattleMons[battlerAtkPartner].statStages[STAT_ATK] == MAX_STAT_STAGE - || partnerAbility == ABILITY_CONTRARY - || partnerAbility == ABILITY_GOOD_AS_GOLD + || (AI_BATTLER_HAS_TRAIT(battlerAtkPartner, ABILITY_CONTRARY)) + || (AI_BATTLER_HAS_TRAIT(battlerAtkPartner, ABILITY_GOOD_AS_GOLD)) || HasBattlerSideMoveWithEffect(LEFT_FOE(battlerAtk), EFFECT_FOUL_PLAY)) return FALSE; @@ -5673,7 +5631,7 @@ u32 IncreaseSubstituteMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) } else if (effect == EFFECT_SHED_TAIL) // Shed Tail specific { - if ((ShouldPivot(battlerAtk, battlerDef, gAiLogicData->abilities[battlerDef], move, gAiThinkingStruct->movesetIndex)) + if ((ShouldPivot(battlerAtk, battlerDef, move, gAiThinkingStruct->movesetIndex)) && (HasAnyKnownMove(battlerDef) && (GetBestDmgFromBattler(battlerDef, battlerAtk, AI_DEFENDING) < gBattleMons[battlerAtk].maxHP / 2))) scoreIncrease += BEST_EFFECT; } @@ -5710,8 +5668,9 @@ bool32 IsBattlerItemEnabled(u32 battler) return FALSE; if (gBattleMons[battler].volatiles.embargo) return FALSE; - if (gBattleMons[battler].ability == ABILITY_KLUTZ && !gBattleMons[battler].volatiles.gastroAcid) + if (AI_BATTLER_HAS_TRAIT(battler, ABILITY_KLUTZ) && !gBattleMons[battler].volatiles.gastroAcid) return FALSE; + return TRUE; } @@ -5727,97 +5686,122 @@ u32 GetFriendlyFireKOThreshold(u32 battler) return FRIENDLY_FIRE_NORMAL_THRESHOLD; } -bool32 IsMoxieTypeAbility(enum Ability ability) +bool32 HasMoxieTypeAbility(u32 battler) { - switch (ability) - { - case ABILITY_MOXIE: - case ABILITY_BEAST_BOOST: - case ABILITY_CHILLING_NEIGH: - case ABILITY_AS_ONE_ICE_RIDER: - case ABILITY_GRIM_NEIGH: - case ABILITY_AS_ONE_SHADOW_RIDER: + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battler); + + // Use AI Ability knowledge if this is an AI check + if(gAiLogicData->aiCalcInProgress) + battlerTraits[0] = gAiLogicData->abilities[battler]; + + if ((SearchTraits(battlerTraits, ABILITY_MOXIE)) + || (SearchTraits(battlerTraits, ABILITY_BEAST_BOOST)) + || (SearchTraits(battlerTraits, ABILITY_CHILLING_NEIGH)) + || (SearchTraits(battlerTraits, ABILITY_AS_ONE_ICE_RIDER)) + || (SearchTraits(battlerTraits, ABILITY_GRIM_NEIGH)) + || (SearchTraits(battlerTraits, ABILITY_AS_ONE_SHADOW_RIDER))) return TRUE; - default: - return FALSE; - } + + return FALSE; } -bool32 DoesAbilityRaiseStatsWhenLowered(enum Ability ability) +bool32 DoesAbilityRaiseStatsWhenLowered(u32 battler) { - switch (ability) - { - case ABILITY_CONTRARY: - case ABILITY_COMPETITIVE: - case ABILITY_DEFIANT: + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battler); + + // Use AI Ability knowledge if this is an AI check + if(gAiLogicData->aiCalcInProgress) + battlerTraits[0] = gAiLogicData->abilities[battler]; + + if ((SearchTraits(battlerTraits, ABILITY_CONTRARY)) + || (SearchTraits(battlerTraits, ABILITY_COMPETITIVE)) + || (SearchTraits(battlerTraits, ABILITY_DEFIANT))) return TRUE; - default: - return FALSE; - } + + return FALSE; } -bool32 DoesIntimidateRaiseStats(enum Ability ability) +bool32 DoesIntimidateRaiseStats(u32 battler) { - switch (ability) - { - case ABILITY_COMPETITIVE: - case ABILITY_CONTRARY: - case ABILITY_DEFIANT: - case ABILITY_GUARD_DOG: - case ABILITY_RATTLED: + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battler); + + // Use AI Ability knowledge if this is an AI check + if(gAiLogicData->aiCalcInProgress) + battlerTraits[0] = gAiLogicData->abilities[battler]; + + if ((SearchTraits(battlerTraits, ABILITY_COMPETITIVE)) + || (SearchTraits(battlerTraits, ABILITY_CONTRARY)) + || (SearchTraits(battlerTraits, ABILITY_DEFIANT)) + || (SearchTraits(battlerTraits, ABILITY_GUARD_DOG)) + || (SearchTraits(battlerTraits, ABILITY_RATTLED))) return TRUE; - default: - return FALSE; - } + + return FALSE; } // TODO: work out when to attack into the player's contextually 'beneficial' ability -bool32 ShouldTriggerAbility(u32 battlerAtk, u32 battlerDef, enum Ability ability) +bool32 ShouldTriggerAbility(u32 battlerAtk, u32 battlerDef) { + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battlerDef); + if (IsTargetingPartner(battlerAtk, battlerDef)) { - switch (ability) + if ((SearchTraits(battlerTraits, ABILITY_LIGHTNING_ROD) + || SearchTraits(battlerTraits, ABILITY_STORM_DRAIN)) + && GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) < GEN_5) { - case ABILITY_LIGHTNING_ROD: - case ABILITY_STORM_DRAIN: - if (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) < GEN_5) - return FALSE; - else - return (BattlerStatCanRise(battlerDef, ability, STAT_SPATK) && HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL)); - - case ABILITY_DEFIANT: - case ABILITY_JUSTIFIED: - case ABILITY_MOXIE: - case ABILITY_SAP_SIPPER: - case ABILITY_THERMAL_EXCHANGE: - return (BattlerStatCanRise(battlerDef, ability, STAT_ATK) && HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL)); - - case ABILITY_COMPETITIVE: - return (BattlerStatCanRise(battlerDef, ability, STAT_SPATK) && HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL)); - + return (BattlerStatCanRise(battlerDef, STAT_SPATK) && HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL)); + } + + if (SearchTraits(battlerTraits, ABILITY_DEFIANT) + || SearchTraits(battlerTraits, ABILITY_JUSTIFIED) + || SearchTraits(battlerTraits, ABILITY_MOXIE) + || SearchTraits(battlerTraits, ABILITY_SAP_SIPPER) + || SearchTraits(battlerTraits, ABILITY_THERMAL_EXCHANGE)) + { + return (BattlerStatCanRise(battlerDef, STAT_ATK) && HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL)); + } + + if (SearchTraits(battlerTraits, ABILITY_COMPETITIVE)) + { + return (BattlerStatCanRise(battlerDef, STAT_SPATK) && HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL)); + } + // TODO: logic for when to trigger Contrary - case ABILITY_CONTRARY: + if (SearchTraits(battlerTraits, ABILITY_CONTRARY)) + { return TRUE; + } - case ABILITY_DRY_SKIN: - case ABILITY_VOLT_ABSORB: - case ABILITY_WATER_ABSORB: + if (SearchTraits(battlerTraits, ABILITY_DRY_SKIN) + || SearchTraits(battlerTraits, ABILITY_VOLT_ABSORB) + || SearchTraits(battlerTraits, ABILITY_WATER_ABSORB)) + { return (gAiThinkingStruct->aiFlags[battlerDef] & AI_FLAG_HP_AWARE); + } - case ABILITY_RATTLED: - case ABILITY_STEAM_ENGINE: - return BattlerStatCanRise(battlerDef, ability, STAT_SPEED); + if (SearchTraits(battlerTraits, ABILITY_RATTLED) + || SearchTraits(battlerTraits, ABILITY_STEAM_ENGINE)) + { + return BattlerStatCanRise(battlerDef, STAT_SPEED); + } - case ABILITY_FLASH_FIRE: + if (SearchTraits(battlerTraits, ABILITY_FLASH_FIRE)) + { return (HasMoveWithType(battlerDef, TYPE_FIRE) && !gDisableStructs[battlerDef].flashFireBoosted); + } - case ABILITY_WATER_COMPACTION: - case ABILITY_WELL_BAKED_BODY: - return (BattlerStatCanRise(battlerDef, ability, STAT_DEF)); - - default: - return FALSE; + if (SearchTraits(battlerTraits, ABILITY_WATER_COMPACTION) + || SearchTraits(battlerTraits, ABILITY_WELL_BAKED_BODY)) + { + return (BattlerStatCanRise(battlerDef, STAT_DEF)); } + + return FALSE; } else { @@ -6084,8 +6068,7 @@ s32 BattlerBenefitsFromAbilityScore(u32 battler, enum Ability ability, struct Ai break; case ABILITY_INTIMIDATE: { - enum Ability abilityDef = aiData->abilities[LEFT_FOE(battler)]; - if (DoesIntimidateRaiseStats(abilityDef)) + if (DoesIntimidateRaiseStats(LEFT_FOE(battler))) { return AWFUL_EFFECT; } @@ -6093,8 +6076,7 @@ s32 BattlerBenefitsFromAbilityScore(u32 battler, enum Ability ability, struct Ai { if (HasTwoOpponents(battler)) { - abilityDef = aiData->abilities[RIGHT_FOE(battler)]; - if (DoesIntimidateRaiseStats(abilityDef)) + if (DoesIntimidateRaiseStats(RIGHT_FOE(battler))) { return AWFUL_EFFECT; } diff --git a/src/battle_anim_effects_1.c b/src/battle_anim_effects_1.c index ba5dbbc68d80..09229d4339c7 100644 --- a/src/battle_anim_effects_1.c +++ b/src/battle_anim_effects_1.c @@ -1,5 +1,7 @@ #include "global.h" #include "malloc.h" +#include "battle.h" +#include "battle_ai_main.h" #include "battle_anim.h" #include "battle_anim_internal.h" #include "battle_interface.h" @@ -6988,12 +6990,11 @@ static void AnimTask_AllySwitchDataSwap(u8 taskId) // For Snipe Shot and abilities Stalwart/Propeller Tail - keep the original target. for (i = 0; i < gBattlersCount; i++) { - enum Ability ability = GetBattlerAbility(i); // if not targeting a slot that got switched, continue if (!IsBattlerAlly(gBattleStruct->moveTarget[i], battlerAtk)) continue; - if (GetMoveEffect(gChosenMoveByBattler[i]) == EFFECT_SNIPE_SHOT || ability == ABILITY_PROPELLER_TAIL || ability == ABILITY_STALWART) + if (GetMoveEffect(gChosenMoveByBattler[i]) == EFFECT_SNIPE_SHOT || BattlerHasTrait(i, ABILITY_PROPELLER_TAIL) || BattlerHasTrait(i, ABILITY_STALWART)) gBattleStruct->moveTarget[i] ^= BIT_FLANK; } diff --git a/src/battle_anim_new.c b/src/battle_anim_new.c index cc8338b687a7..24bc00bb8837 100644 --- a/src/battle_anim_new.c +++ b/src/battle_anim_new.c @@ -9058,7 +9058,7 @@ void AnimTask_ShellSideArm(u8 taskId) void AnimTask_TerrainPulse(u8 taskId) { - if (IsBattlerTerrainAffected(gBattleAnimAttacker, GetBattlerAbility(gBattleAnimAttacker), GetBattlerHoldEffect(gBattleAnimAttacker), STATUS_FIELD_TERRAIN_ANY)) + if (IsBattlerTerrainAffected(gBattleAnimAttacker, GetBattlerHoldEffect(gBattleAnimAttacker), STATUS_FIELD_TERRAIN_ANY)) { if (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN) gBattleAnimArgs[0] = TYPE_ELECTRIC; diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index a68dc170daf0..f8a7402a0432 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -2373,8 +2373,6 @@ static u32 CheckTypeEffectiveness(u32 battlerAtk, u32 battlerDef) ctx.move = moveInfo->moves[gMoveSelectionCursor[battlerAtk]]; ctx.moveType = CheckDynamicMoveType(GetBattlerMon(battlerAtk), ctx.move, battlerAtk, MON_IN_BATTLE); ctx.updateFlags = FALSE; - ctx.abilityAtk = GetBattlerAbility(battlerAtk); - ctx.abilityDef = GetBattlerAbility(battlerDef); ctx.holdEffectAtk = GetBattlerHoldEffect(battlerAtk); ctx.holdEffectDef = GetBattlerHoldEffect(battlerDef); diff --git a/src/battle_controllers.c b/src/battle_controllers.c index dd8abcf213aa..ed839d4b24c6 100644 --- a/src/battle_controllers.c +++ b/src/battle_controllers.c @@ -1395,6 +1395,7 @@ static u32 GetBattlerMonData(u32 battler, struct Pokemon *party, u32 monId, u8 * s16 data16; u32 data32; s32 size = 0; + u32 i; switch (gBattleResources->bufferA[battler][1]) { @@ -1432,6 +1433,21 @@ static u32 GetBattlerMonData(u32 battler, struct Pokemon *party, u32 monId, u8 * GetMonData(&party[monId], MON_DATA_NICKNAME, nickname); StringCopy_Nickname(battleMon.nickname, nickname); GetMonData(&party[monId], MON_DATA_OT_NAME, battleMon.otName); + + for (i = 0; i < MAX_MON_INNATES; i++) + { + #if TESTING + if (gTestRunnerEnabled) + { + u32 array = (!IsPartnerMonFromSameTrainer(battler)) ? battler : GetBattlerSide(battler); + battleMon.innates[i] = TestRunner_Battle_GetForcedInnates(array, monId, i); + gBattleMons[battler].innates[i] = TestRunner_Battle_GetForcedInnates(array, monId, i); + } + #else + battleMon.innates[i] = GetSpeciesInnate(battleMon.species, i + 1); + #endif + } + src = (u8 *)&battleMon; for (size = 0; size < sizeof(battleMon); size++) dst[size] = src[size]; diff --git a/src/battle_dome.c b/src/battle_dome.c index 30075fad88b8..c3128d2908f4 100644 --- a/src/battle_dome.c +++ b/src/battle_dome.c @@ -2404,7 +2404,7 @@ static int GetTypeEffectivenessPoints(int move, int targetSpecies, int mode) defAbility = GetSpeciesAbility(targetSpecies, 0); moveType = GetMoveType(move); - if (defAbility == ABILITY_LEVITATE && moveType == TYPE_GROUND) + if ((defAbility == ABILITY_LEVITATE || SpeciesHasInnate(targetSpecies, ABILITY_LEVITATE)) && moveType == TYPE_GROUND) { // They likely meant to return here, as 8 is the number of points normally used in this mode for moves with no effect. // Because there's no return the value instead gets interpreted by the switch, and the number of points becomes 0. @@ -2425,7 +2425,7 @@ static int GetTypeEffectivenessPoints(int move, int targetSpecies, int mode) if (defType2 != defType1) typePower = (typeEffectiveness2 * typePower) / 10; - if (defAbility == ABILITY_WONDER_GUARD && typeEffectiveness1 != TYPE_x1 && typeEffectiveness2 != TYPE_x1) + if ((defAbility == ABILITY_WONDER_GUARD || SpeciesHasInnate(targetSpecies, ABILITY_WONDER_GUARD)) && typeEffectiveness1 != TYPE_x1 && typeEffectiveness2 != TYPE_x1) typePower = 0; } @@ -5141,7 +5141,6 @@ static u16 GetWinningMove(int winnerTournamentId, int loserTournamentId, u8 roun { u32 personality = 0; u32 targetSpecies = 0; - enum Ability targetAbility = 0; uq4_12_t typeMultiplier = 0; do { @@ -5150,12 +5149,7 @@ static u16 GetWinningMove(int winnerTournamentId, int loserTournamentId, u8 roun targetSpecies = gFacilityTrainerMons[DOME_MONS[loserTournamentId][k]].species; - if (personality & 1) - targetAbility = GetSpeciesAbility(targetSpecies, 1); - else - targetAbility = GetSpeciesAbility(targetSpecies, 0); - - typeMultiplier = CalcPartyMonTypeEffectivenessMultiplier(moves[i * 4 + j], targetSpecies, targetAbility); + typeMultiplier = CalcPartyMonTypeEffectivenessMultiplier(moves[i * 4 + j], targetSpecies, 0); if (typeMultiplier == UQ_4_12(0)) moveScores[i * MAX_MON_MOVES + j] += 0; else if (typeMultiplier >= UQ_4_12(2.0)) diff --git a/src/battle_end_turn.c b/src/battle_end_turn.c index 096b307b8b01..e533e67ab0f9 100644 --- a/src/battle_end_turn.c +++ b/src/battle_end_turn.c @@ -96,8 +96,9 @@ static bool32 HandleEndTurnWeatherDamage(u32 battler) { bool32 effect = FALSE; - enum Ability ability = GetBattlerAbility(battler); u32 currBattleWeather = GetCurrentBattleWeather(); + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battler); if (currBattleWeather == 0xFF) { @@ -121,30 +122,30 @@ static bool32 HandleEndTurnWeatherDamage(u32 battler) case BATTLE_WEATHER_RAIN: case BATTLE_WEATHER_RAIN_PRIMAL: case BATTLE_WEATHER_RAIN_DOWNPOUR: - if (ability == ABILITY_DRY_SKIN || ability == ABILITY_RAIN_DISH) + if (SearchTraits(battlerTraits, ABILITY_DRY_SKIN) || SearchTraits(battlerTraits, ABILITY_RAIN_DISH)) { - if (AbilityBattleEffects(ABILITYEFFECT_ENDTURN, battler, ability, 0, MOVE_NONE)) + if (AbilityBattleEffects(ABILITYEFFECT_ENDTURN, battler, 0, MOVE_NONE)) effect = TRUE; } break; case BATTLE_WEATHER_SUN: case BATTLE_WEATHER_SUN_PRIMAL: - if (ability == ABILITY_DRY_SKIN || ability == ABILITY_SOLAR_POWER) + if (SearchTraits(battlerTraits, ABILITY_DRY_SKIN) || SearchTraits(battlerTraits, ABILITY_SOLAR_POWER)) { - if (AbilityBattleEffects(ABILITYEFFECT_ENDTURN, battler, ability, 0, MOVE_NONE)) + if (AbilityBattleEffects(ABILITYEFFECT_ENDTURN, battler, 0, MOVE_NONE)) effect = TRUE; } break; case BATTLE_WEATHER_SANDSTORM: - if (ability != ABILITY_SAND_VEIL - && ability != ABILITY_SAND_FORCE - && ability != ABILITY_SAND_RUSH - && ability != ABILITY_OVERCOAT + if (!BattlerHasTrait(battler, ABILITY_SAND_VEIL) + && !BattlerHasTrait(battler, ABILITY_SAND_FORCE) + && !BattlerHasTrait(battler, ABILITY_SAND_RUSH) + && !BattlerHasTrait(battler, ABILITY_OVERCOAT) && !IS_BATTLER_ANY_TYPE(battler, TYPE_ROCK, TYPE_GROUND, TYPE_STEEL) && gBattleMons[battler].volatiles.semiInvulnerable != STATE_UNDERGROUND && gBattleMons[battler].volatiles.semiInvulnerable != STATE_UNDERWATER - && GetBattlerHoldEffect(battler) != HOLD_EFFECT_SAFETY_GOGGLES - && !IsAbilityAndRecord(battler, ability, ABILITY_MAGIC_GUARD)) + && GetBattlerHoldEffect(gBattlerAttacker) != HOLD_EFFECT_SAFETY_GOGGLES + && !IsAbilityAndRecord(battler, ABILITY_MAGIC_GUARD)) { SetPassiveDamageAmount(battler, GetNonDynamaxMaxHP(battler) / 16); gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SANDSTORM; @@ -154,20 +155,20 @@ static bool32 HandleEndTurnWeatherDamage(u32 battler) break; case BATTLE_WEATHER_HAIL: case BATTLE_WEATHER_SNOW: - if (ability == ABILITY_ICE_BODY) + if (SearchTraits(battlerTraits, ABILITY_ICE_BODY)) { - if (AbilityBattleEffects(ABILITYEFFECT_ENDTURN, battler, ability, 0, MOVE_NONE)) + if (AbilityBattleEffects(ABILITYEFFECT_ENDTURN, battler, 0, MOVE_NONE)) effect = TRUE; } else if (currBattleWeather == BATTLE_WEATHER_HAIL) { - if (ability != ABILITY_SNOW_CLOAK - && ability != ABILITY_OVERCOAT + if (!BattlerHasTrait(battler, ABILITY_SNOW_CLOAK) + && !BattlerHasTrait(battler, ABILITY_OVERCOAT) && !IS_BATTLER_OF_TYPE(battler, TYPE_ICE) && gBattleMons[battler].volatiles.semiInvulnerable != STATE_UNDERGROUND && gBattleMons[battler].volatiles.semiInvulnerable != STATE_UNDERWATER - && GetBattlerHoldEffect(battler) != HOLD_EFFECT_SAFETY_GOGGLES - && !IsAbilityAndRecord(battler, ability, ABILITY_MAGIC_GUARD)) + && GetBattlerHoldEffect(gBattlerAttacker) != HOLD_EFFECT_SAFETY_GOGGLES + && !IsAbilityAndRecord(battler, ABILITY_MAGIC_GUARD)) { SetPassiveDamageAmount(battler, GetNonDynamaxMaxHP(battler) / 16); gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_HAIL; @@ -184,15 +185,12 @@ static bool32 HandleEndTurnWeatherDamage(u32 battler) static bool32 HandleEndTurnEmergencyExit(u32 battler) { bool32 effect = FALSE; - enum Ability ability = GetBattlerAbility(battler); gBattleStruct->eventState.endTurnBattler++; if (EmergencyExitCanBeTriggered(battler)) { gBattlerAbility = battler; - gLastUsedAbility = ability; - if (gBattleTypeFlags & BATTLE_TYPE_TRAINER) BattleScriptExecute(BattleScript_EmergencyExitEnd2); else @@ -315,7 +313,7 @@ static bool32 HandleEndTurnFirstEventBlock(u32 battler) if (gSideStatuses[side] & SIDE_STATUS_DAMAGE_NON_TYPES) { if (!IS_BATTLER_OF_TYPE(battler, gSideTimers[side].damageNonTypesType) - && !IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_MAGIC_GUARD)) + && !IsAbilityAndRecord(battler, ABILITY_MAGIC_GUARD)) { SetPassiveDamageAmount(battler, GetNonDynamaxMaxHP(battler) / 6); ChooseDamageNonTypesString(gSideTimers[side].damageNonTypesType); @@ -364,7 +362,7 @@ static bool32 HandleEndTurnFirstEventBlock(u32 battler) && !IsBattlerAtMaxHp(battler) && !gBattleMons[battler].volatiles.healBlock && !IsSemiInvulnerable(battler, CHECK_ALL) - && IsBattlerGrounded(battler, GetBattlerAbility(battler), GetBattlerHoldEffect(battler))) + && IsBattlerGrounded(battler, GetBattlerHoldEffect(battler))) { SetHealAmount(battler, GetNonDynamaxMaxHP(battler) / 16); BattleScriptExecute(BattleScript_GrassyTerrainHeals); @@ -374,19 +372,14 @@ static bool32 HandleEndTurnFirstEventBlock(u32 battler) break; case FIRST_EVENT_BLOCK_ABILITIES: { - enum Ability ability = GetBattlerAbility(battler); - switch (ability) - { - case ABILITY_HEALER: - case ABILITY_HYDRATION: - case ABILITY_SHED_SKIN: - if (AbilityBattleEffects(ABILITYEFFECT_ENDTURN, battler, ability, 0, MOVE_NONE)) + if (BattlerHasTrait(battler, ABILITY_HEALER) + || BattlerHasTrait(battler, ABILITY_HYDRATION) + || BattlerHasTrait(battler, ABILITY_SHED_SKIN)) + if (AbilityBattleEffects(ABILITYEFFECT_ENDTURN, battler, 0, MOVE_NONE)) effect = TRUE; - break; - default: - break; - } - gBattleStruct->eventState.endTurnBlock++; + + if (effect == FALSE) //Loop End Turn abilities until none activate anymore (Multi) + gBattleStruct->eventState.endTurnBlock++; break; } case FIRST_EVENT_BLOCK_HEAL_ITEMS: @@ -447,18 +440,19 @@ static bool32 HandleEndTurnLeechSeed(u32 battler) if (gBattleMons[battler].volatiles.leechSeed && IsBattlerAlive(gBattleMons[battler].volatiles.leechSeed - 1) && IsBattlerAlive(battler) - && !IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_MAGIC_GUARD)) + && !IsAbilityAndRecord(battler, ABILITY_MAGIC_GUARD)) { gBattlerTarget = gBattleMons[battler].volatiles.leechSeed - 1; // leech seed receiver gBattleScripting.animArg1 = gBattlerTarget; gBattleScripting.animArg2 = gBattlerAttacker; s32 drainAmount = GetNonDynamaxMaxHP(gBattlerAttacker) / 8; s32 healAmount = GetDrainedBigRootHp(gBattlerTarget, drainAmount); - if (GetBattlerAbility(battler) == ABILITY_LIQUID_OOZE) + if (BattlerHasTrait(battler, ABILITY_LIQUID_OOZE)) { SetPassiveDamageAmount(gBattlerAttacker, drainAmount); SetPassiveDamageAmount(gBattlerTarget, healAmount); gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_LEECH_SEED_OOZE; + PushTraitStack(battler, ABILITY_LIQUID_OOZE); BattleScriptExecute(BattleScript_LeechSeedTurnDrainLiquidOoze); } else if (gBattleMons[gBattlerTarget].volatiles.healBlock) @@ -482,19 +476,18 @@ static bool32 HandleEndTurnPoison(u32 battler) { bool32 effect = FALSE; - enum Ability ability = GetBattlerAbility(battler); - gBattleStruct->eventState.endTurnBattler++; if ((gBattleMons[battler].status1 & STATUS1_POISON || gBattleMons[battler].status1 & STATUS1_TOXIC_POISON) && IsBattlerAlive(battler) - && !IsAbilityAndRecord(battler, ability, ABILITY_MAGIC_GUARD)) + && !IsAbilityAndRecord(battler, ABILITY_MAGIC_GUARD)) { - if (ability == ABILITY_POISON_HEAL) + if (BattlerHasTrait(battler, ABILITY_POISON_HEAL)) { if (!IsBattlerAtMaxHp(battler) && !gBattleMons[battler].volatiles.healBlock) { SetHealAmount(battler, GetNonDynamaxMaxHP(battler) / 8); + PushTraitStack(battler, ABILITY_POISON_HEAL); BattleScriptExecute(BattleScript_PoisonHealActivates); effect = TRUE; } @@ -523,22 +516,21 @@ static bool32 HandleEndTurnBurn(u32 battler) { bool32 effect = FALSE; - enum Ability ability = GetBattlerAbility(battler); - gBattleStruct->eventState.endTurnBattler++; if (gBattleMons[battler].status1 & STATUS1_BURN && IsBattlerAlive(battler) - && !IsAbilityAndRecord(battler, ability, ABILITY_MAGIC_GUARD)) + && !IsAbilityAndRecord(battler, ABILITY_MAGIC_GUARD)) { s32 burnDamage = GetNonDynamaxMaxHP(battler) / ((GetConfig(CONFIG_BURN_DAMAGE) >= GEN_7 || GetConfig(CONFIG_BURN_DAMAGE) == GEN_1) ? 16 : 8); - if (ability == ABILITY_HEATPROOF) + if (BattlerHasTrait(battler, ABILITY_HEATPROOF)) { if (burnDamage > (burnDamage / 2) + 1) // Record ability if the burn takes less damage than it normally would. RecordAbilityBattle(battler, ABILITY_HEATPROOF); burnDamage /= 2; } SetPassiveDamageAmount(battler, burnDamage); + PushTraitStack(battler, ABILITY_HEATPROOF); BattleScriptExecute(BattleScript_BurnTurnDmg); effect = TRUE; } @@ -554,7 +546,7 @@ static bool32 HandleEndTurnFrostbite(u32 battler) if (gBattleMons[battler].status1 & STATUS1_FROSTBITE && IsBattlerAlive(battler) - && !IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_MAGIC_GUARD)) + && !IsAbilityAndRecord(battler, ABILITY_MAGIC_GUARD)) { SetPassiveDamageAmount(battler, GetNonDynamaxMaxHP(battler) / ((GetConfig(CONFIG_BURN_DAMAGE) >= GEN_7 || GetConfig(CONFIG_BURN_DAMAGE) == GEN_1) ? 16 : 8)); BattleScriptExecute(BattleScript_FrostbiteTurnDmg); @@ -572,7 +564,7 @@ static bool32 HandleEndTurnNightmare(u32 battler) if (gBattleMons[battler].volatiles.nightmare && IsBattlerAlive(battler) - && !IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_MAGIC_GUARD)) + && !IsAbilityAndRecord(battler, ABILITY_MAGIC_GUARD)) { if (gBattleMons[battler].status1 & STATUS1_SLEEP || GetBattlerAbility(battler) == ABILITY_COMATOSE) { @@ -597,7 +589,7 @@ static bool32 HandleEndTurnCurse(u32 battler) if (gBattleMons[battler].volatiles.cursed && IsBattlerAlive(battler) - && !IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_MAGIC_GUARD)) + && !IsAbilityAndRecord(battler, ABILITY_MAGIC_GUARD)) { SetPassiveDamageAmount(battler, GetNonDynamaxMaxHP(battler) / 4); BattleScriptExecute(BattleScript_CurseTurnDmg); @@ -618,7 +610,7 @@ static bool32 HandleEndTurnWrap(u32 battler) if (gDisableStructs[battler].wrapTurns != 0) { gDisableStructs[battler].wrapTurns--; - if (IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_MAGIC_GUARD)) + if (IsAbilityAndRecord(battler, ABILITY_MAGIC_GUARD)) return effect; gBattleScripting.animArg1 = gBattleMons[battler].volatiles.wrappedMove; @@ -652,7 +644,7 @@ static bool32 HandleEndTurnSaltCure(u32 battler) if (gBattleMons[battler].volatiles.saltCure && IsBattlerAlive(battler) - && !IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_MAGIC_GUARD)) + && !IsAbilityAndRecord(battler, ABILITY_MAGIC_GUARD)) { s32 saltCureDamage = 0; if (IS_BATTLER_ANY_TYPE(battler, TYPE_STEEL, TYPE_WATER)) @@ -867,8 +859,6 @@ static bool32 HandleEndTurnYawn(u32 battler) { bool32 effect = FALSE; - enum Ability ability = GetBattlerAbility(battler); - gBattleStruct->eventState.endTurnBattler++; if (gBattleMons[battler].volatiles.yawn > 0) @@ -876,19 +866,19 @@ static bool32 HandleEndTurnYawn(u32 battler) gBattleMons[battler].volatiles.yawn--; if (!gBattleMons[battler].volatiles.yawn && !(gBattleMons[battler].status1 & STATUS1_ANY) - && ability != ABILITY_VITAL_SPIRIT - && ability != ABILITY_INSOMNIA + && !BattlerHasTrait(battler, ABILITY_VITAL_SPIRIT) + && !BattlerHasTrait(battler, ABILITY_INSOMNIA) && !UproarWakeUpCheck(battler) - && !IsLeafGuardProtected(battler, ability)) + && !IsLeafGuardProtected(battler)) { gEffectBattler = gBattlerTarget = battler; enum HoldEffect holdEffect = GetBattlerHoldEffect(battler); - if (IsBattlerTerrainAffected(battler, ability, holdEffect, STATUS_FIELD_ELECTRIC_TERRAIN)) + if (IsBattlerTerrainAffected(battler, holdEffect, STATUS_FIELD_ELECTRIC_TERRAIN)) { gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAINPREVENTS_ELECTRIC; BattleScriptExecute(BattleScript_TerrainPreventsEnd2); } - else if (IsBattlerTerrainAffected(battler, ability, holdEffect, STATUS_FIELD_MISTY_TERRAIN)) + else if (IsBattlerTerrainAffected(battler, holdEffect, STATUS_FIELD_MISTY_TERRAIN)) { gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAINPREVENTS_MISTY; BattleScriptExecute(BattleScript_TerrainPreventsEnd2); @@ -902,6 +892,7 @@ static bool32 HandleEndTurnYawn(u32 battler) gBattleScripting.battler--; gLastUsedAbility = ABILITY_SWEET_VEIL; gBattlerAbility = gBattleScripting.battler; + PushTraitStack(gBattleScripting.battler, ABILITY_SWEET_VEIL); RecordAbilityBattle(gBattleScripting.battler, ABILITY_SWEET_VEIL); BattleScriptExecute(BattleScript_ImmunityProtectedEnd2); } @@ -1232,7 +1223,7 @@ static bool32 HandleEndTurnThirdEventBlock(u32 battler) for (gEffectBattler = 0; gEffectBattler < gBattlersCount; gEffectBattler++) { if ((gBattleMons[gEffectBattler].status1 & STATUS1_SLEEP) - && GetBattlerAbility(gEffectBattler) != ABILITY_SOUNDPROOF) + && !BattlerHasTrait(gEffectBattler, ABILITY_SOUNDPROOF)) { gBattleMons[gEffectBattler].status1 &= ~STATUS1_SLEEP; gBattleMons[gEffectBattler].volatiles.nightmare = FALSE; @@ -1271,25 +1262,34 @@ static bool32 HandleEndTurnThirdEventBlock(u32 battler) break; case THIRD_EVENT_BLOCK_ABILITIES: { - enum Ability ability = GetBattlerAbility(battler); - switch (ability) - { - case ABILITY_TRUANT: // Not fully accurate but it has to be handled somehow. TODO: Find a better way. - case ABILITY_CUD_CHEW: - case ABILITY_SLOW_START: - case ABILITY_BAD_DREAMS: - case ABILITY_BALL_FETCH: - case ABILITY_HARVEST: - case ABILITY_MOODY: - case ABILITY_PICKUP: - case ABILITY_SPEED_BOOST: - if (AbilityBattleEffects(ABILITYEFFECT_ENDTURN, battler, ability, 0, MOVE_NONE)) + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battler); + + if (SearchTraits(battlerTraits, ABILITY_TRUANT) // Not fully accurate but it has to be handled somehow. TODO: Find a better way. + || SearchTraits(battlerTraits, ABILITY_CUD_CHEW) + || SearchTraits(battlerTraits, ABILITY_SLOW_START) + || SearchTraits(battlerTraits, ABILITY_BAD_DREAMS) + || SearchTraits(battlerTraits, ABILITY_BALL_FETCH) + || SearchTraits(battlerTraits, ABILITY_HARVEST) + || SearchTraits(battlerTraits, ABILITY_MOODY) + || SearchTraits(battlerTraits, ABILITY_PICKUP) + || SearchTraits(battlerTraits, ABILITY_SPEED_BOOST)) + if (AbilityBattleEffects(ABILITYEFFECT_ENDTURN, battler, 0, MOVE_NONE)) effect = TRUE; - break; - default: - break; - } - gBattleStruct->eventState.endTurnBlock++; + + if (effect == FALSE) //Loop End Turn abilities until none activate anymore (Multi) + gBattleStruct->eventState.endTurnBlock++; + + if (SearchTraits(battlerTraits, ABILITY_TRUANT) + || SearchTraits(battlerTraits, ABILITY_CUD_CHEW) + || SearchTraits(battlerTraits, ABILITY_SLOW_START) + || SearchTraits(battlerTraits, ABILITY_BAD_DREAMS) + || SearchTraits(battlerTraits, ABILITY_BALL_FETCH) + || SearchTraits(battlerTraits, ABILITY_HARVEST) + || SearchTraits(battlerTraits, ABILITY_MOODY) + || SearchTraits(battlerTraits, ABILITY_PICKUP) + || SearchTraits(battlerTraits, ABILITY_SPEED_BOOST)) + effect = TRUE; // Set effect again outside above loop break; } case THIRD_EVENT_BLOCK_ITEMS: @@ -1324,22 +1324,18 @@ static bool32 HandleEndTurnFormChangeAbilities(u32 battler) { bool32 effect = FALSE; - enum Ability ability = GetBattlerAbility(battler); - gBattleStruct->eventState.endTurnBattler++; - switch (ability) - { - case ABILITY_POWER_CONSTRUCT: - case ABILITY_SCHOOLING: - case ABILITY_SHIELDS_DOWN: - case ABILITY_ZEN_MODE: - case ABILITY_HUNGER_SWITCH: - if (AbilityBattleEffects(ABILITYEFFECT_ENDTURN, battler, ability, 0, MOVE_NONE)) + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battler); + + if (SearchTraits(battlerTraits, ABILITY_POWER_CONSTRUCT) + || SearchTraits(battlerTraits, ABILITY_SCHOOLING) + || SearchTraits(battlerTraits, ABILITY_SHIELDS_DOWN) + || SearchTraits(battlerTraits, ABILITY_ZEN_MODE) + || SearchTraits(battlerTraits, ABILITY_HUNGER_SWITCH)) + if (AbilityBattleEffects(ABILITYEFFECT_ENDTURN, battler, 0, MOVE_NONE)) effect = TRUE; - default: - break; - } return effect; } @@ -1455,4 +1451,4 @@ u32 DoEndTurnEffects(void) if (sEndTurnEffectHandlers[gBattleStruct->eventState.endTurn](battler)) return TRUE; } -} +} \ No newline at end of file diff --git a/src/battle_hold_effects.c b/src/battle_hold_effects.c index 9f363ea12082..6f43e643a71d 100644 --- a/src/battle_hold_effects.c +++ b/src/battle_hold_effects.c @@ -51,16 +51,22 @@ static enum ItemEffect TryDoublePrize(u32 battler) return effect; } -enum ItemEffect TryBoosterEnergy(u32 battler, enum Ability ability, ActivationTiming timing) +enum ItemEffect TryBoosterEnergy(u32 battler, ActivationTiming timing) { enum ItemEffect effect = ITEM_NO_EFFECT; + enum Ability ability = ABILITY_NONE; if (gDisableStructs[battler].boosterEnergyActivated || gBattleMons[battler].volatiles.transformed) return ITEM_NO_EFFECT; - if (((ability == ABILITY_PROTOSYNTHESIS) && !((gBattleWeather & B_WEATHER_SUN) && HasWeatherEffect())) - || ((ability == ABILITY_QUARK_DRIVE) && !(gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN))) + if (BattlerHasTrait(battler, ABILITY_PROTOSYNTHESIS) && !((gBattleWeather & B_WEATHER_SUN) && HasWeatherEffect())) + ability = ABILITY_PROTOSYNTHESIS; + else if (BattlerHasTrait(battler, ABILITY_QUARK_DRIVE) && !(gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN)) + ability = ABILITY_QUARK_DRIVE; + + if (ability != ABILITY_NONE) { + PushTraitStack(battler, ability); gDisableStructs[battler].paradoxBoostedStat = GetParadoxHighestStatId(battler); PREPARE_STAT_BUFFER(gBattleTextBuff1, gDisableStructs[battler].paradoxBoostedStat); gBattlerAbility = gBattleScripting.battler = battler; @@ -78,7 +84,7 @@ enum ItemEffect TryBoosterEnergy(u32 battler, enum Ability ability, ActivationTi static enum ItemEffect TryRoomService(u32 battler, ActivationTiming timing) { - if (gFieldStatuses & STATUS_FIELD_TRICK_ROOM && CompareStat(battler, STAT_SPEED, MIN_STAT_STAGE, CMP_GREATER_THAN, GetBattlerAbility(battler))) + if (gFieldStatuses & STATUS_FIELD_TRICK_ROOM && CompareStat(battler, STAT_SPEED, MIN_STAT_STAGE, CMP_GREATER_THAN)) { gEffectBattler = gBattleScripting.battler = battler; SET_STATCHANGER(STAT_SPEED, 1, TRUE); @@ -98,7 +104,7 @@ static enum ItemEffect TryRoomService(u32 battler, ActivationTiming timing) enum ItemEffect TryHandleSeed(u32 battler, u32 terrainFlag, enum Stat statId, ActivationTiming timing) { - if (gFieldStatuses & terrainFlag && CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(battler))) + if (gFieldStatuses & terrainFlag && CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN)) { gEffectBattler = gBattleScripting.battler = battler; SET_STATCHANGER(statId, 1, FALSE); @@ -138,9 +144,8 @@ static enum ItemEffect TryTerrainSeeds(u32 battler, u32 item, ActivationTiming t static bool32 CanBeInfinitelyConfused(u32 battler) { - enum Ability ability = GetBattlerAbility(battler); - if (ability == ABILITY_OWN_TEMPO - || IsBattlerTerrainAffected(battler, ability, GetBattlerHoldEffect(battler), STATUS_FIELD_MISTY_TERRAIN) + if (BattlerHasTrait(battler, ABILITY_OWN_TEMPO) + || IsBattlerTerrainAffected(battler, GetBattlerHoldEffect(battler), STATUS_FIELD_MISTY_TERRAIN) || gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SAFEGUARD) return FALSE; return TRUE; @@ -213,14 +218,13 @@ static enum ItemEffect TryKingsRock(u32 battlerAtk, u32 battlerDef, u32 item) || MoveHasAdditionalEffect(gCurrentMove, MOVE_EFFECT_FLINCH)) return effect; - enum Ability ability = GetBattlerAbility(battlerAtk); u32 holdEffectParam = GetItemHoldEffectParam(item); - if (B_SERENE_GRACE_BOOST >= GEN_5 && ability == ABILITY_SERENE_GRACE) + if (B_SERENE_GRACE_BOOST >= GEN_5 && BattlerHasTrait(battlerAtk, ABILITY_SERENE_GRACE)) holdEffectParam *= 2; if (gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_RAINBOW && gCurrentMove != MOVE_SECRET_POWER) holdEffectParam *= 2; - if (ability != ABILITY_STENCH && RandomPercentage(RNG_HOLD_EFFECT_FLINCH, holdEffectParam)) + if (!BattlerHasTrait(battlerAtk, ABILITY_STENCH) && RandomPercentage(RNG_HOLD_EFFECT_FLINCH, holdEffectParam)) { SetMoveEffect(battlerAtk, battlerDef, MOVE_EFFECT_FLINCH, gBattlescriptCurrInstr, NO_FLAGS); effect = ITEM_EFFECT_OTHER; @@ -258,12 +262,11 @@ static enum ItemEffect TryAirBalloon(u32 battler, ActivationTiming timing) static enum ItemEffect TryRockyHelmet(u32 battlerDef, u32 battlerAtk, u32 item) { enum ItemEffect effect = ITEM_NO_EFFECT; - enum Ability ability = GetBattlerAbility(battlerAtk); if (IsBattlerTurnDamaged(battlerDef) && IsBattlerAlive(battlerAtk) - && !CanBattlerAvoidContactEffects(battlerAtk, battlerDef, ability, GetBattlerHoldEffect(battlerAtk), gCurrentMove) - && !IsAbilityAndRecord(battlerAtk, ability, ABILITY_MAGIC_GUARD)) + && !CanBattlerAvoidContactEffects(battlerAtk, battlerDef, GetBattlerHoldEffect(battlerAtk), gCurrentMove) + && !IsAbilityAndRecord(battlerAtk, ABILITY_MAGIC_GUARD)) { SetPassiveDamageAmount(battlerAtk, GetNonDynamaxMaxHP(battlerAtk) / 6); PREPARE_ITEM_BUFFER(gBattleTextBuff1, item); @@ -361,10 +364,10 @@ static enum ItemEffect TryJabocaBerry(u32 battlerDef, u32 battlerAtk, u32 item) && IsBattlerTurnDamaged(battlerDef) && !DoesSubstituteBlockMove(battlerAtk, battlerDef, gCurrentMove) && IsBattleMovePhysical(gCurrentMove) - && !IsAbilityAndRecord(battlerAtk, GetBattlerAbility(battlerAtk), ABILITY_MAGIC_GUARD)) + && !IsAbilityAndRecord(battlerAtk, ABILITY_MAGIC_GUARD)) { s32 jabocaDamage = GetNonDynamaxMaxHP(battlerAtk) / 8; - if (GetBattlerAbility(battlerDef) == ABILITY_RIPEN) + if (BattlerHasTrait(battlerDef, ABILITY_RIPEN)) jabocaDamage *= 2; SetPassiveDamageAmount(battlerAtk, jabocaDamage); BattleScriptCall(BattleScript_JabocaRowapBerryActivates); @@ -383,10 +386,10 @@ static enum ItemEffect TryRowapBerry(u32 battlerDef, u32 battlerAtk, u32 item) && IsBattlerTurnDamaged(battlerDef) && !DoesSubstituteBlockMove(battlerAtk, battlerDef, gCurrentMove) && IsBattleMoveSpecial(gCurrentMove) - && !IsAbilityAndRecord(battlerAtk, GetBattlerAbility(battlerAtk), ABILITY_MAGIC_GUARD)) + && !IsAbilityAndRecord(battlerAtk, ABILITY_MAGIC_GUARD)) { s32 rowapDamage = GetNonDynamaxMaxHP(battlerAtk) / 8; - if (GetBattlerAbility(battlerDef) == ABILITY_RIPEN) + if (BattlerHasTrait(battlerDef, ABILITY_RIPEN)) rowapDamage *= 2; SetPassiveDamageAmount(battlerAtk, rowapDamage); BattleScriptCall(BattleScript_JabocaRowapBerryActivates); @@ -408,7 +411,7 @@ static enum ItemEffect TrySetEnigmaBerry(u32 battlerDef, u32 battlerAtk) && !(B_HEAL_BLOCKING >= GEN_5 && gBattleMons[battlerDef].volatiles.healBlock)) { s32 healAmount = gBattleMons[battlerDef].maxHP * 25 / 100; - if (GetBattlerAbility(battlerDef) == ABILITY_RIPEN) + if (BattlerHasTrait(battlerDef, ABILITY_RIPEN)) healAmount *= 2; SetHealAmount(battlerDef, healAmount); BattleScriptCall(BattleScript_ItemHealHP_RemoveItemRet); @@ -424,7 +427,7 @@ static enum ItemEffect TryBlunderPolicy(u32 battlerAtk) if (gBattleStruct->blunderPolicy && IsBattlerAlive(battlerAtk) - && CompareStat(battlerAtk, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(battlerAtk))) + && CompareStat(battlerAtk, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN)) { gBattleStruct->blunderPolicy = FALSE; SET_STATCHANGER(STAT_SPEED, 2, FALSE); @@ -503,7 +506,7 @@ static enum ItemEffect TryThroatSpray(u32 battlerAtk) && gMultiHitCounter == 0 && IsBattlerAlive(battlerAtk) && IsAnyTargetTurnDamaged(battlerAtk) - && CompareStat(battlerAtk, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(battlerAtk)) + && CompareStat(battlerAtk, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN) && !NoAliveMonsForEitherParty()) // Don't activate if battle will end { SET_STATCHANGER(STAT_SPATK, 1, FALSE); @@ -518,7 +521,7 @@ static enum ItemEffect DamagedStatBoostBerryEffect(u32 battlerDef, u32 battlerAt { enum ItemEffect effect = ITEM_NO_EFFECT; - if (!IsBattlerAlive(battlerDef) || !CompareStat(battlerDef, statId, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(battlerDef))) + if (!IsBattlerAlive(battlerDef) || !CompareStat(battlerDef, statId, MAX_STAT_STAGE, CMP_LESS_THAN)) return effect; if (gBattleScripting.overrideBerryRequirements @@ -526,7 +529,7 @@ static enum ItemEffect DamagedStatBoostBerryEffect(u32 battlerDef, u32 battlerAt && GetBattleMoveCategory(gCurrentMove) == category && IsBattlerTurnDamaged(battlerDef))) { - if (GetBattlerAbility(battlerDef) == ABILITY_RIPEN) + if (BattlerHasTrait(battlerDef, ABILITY_RIPEN)) SET_STATCHANGER(statId, 2, FALSE); else SET_STATCHANGER(statId, 1, FALSE); @@ -568,7 +571,7 @@ static enum ItemEffect TryLifeOrb(u32 battlerAtk) if (IsBattlerAlive(battlerAtk) && !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) && (IsAnyTargetTurnDamaged(battlerAtk) || gBattleScripting.savedDmg > 0) - && !IsAbilityAndRecord(battlerAtk, GetBattlerAbility(battlerAtk), ABILITY_MAGIC_GUARD) + && !IsAbilityAndRecord(battlerAtk, ABILITY_MAGIC_GUARD) && GetMoveEffect(gCurrentMove) != EFFECT_PAIN_SPLIT && !IsFutureSightAttackerInParty(battlerAtk, gBattlerTarget, gCurrentMove)) { @@ -585,7 +588,7 @@ static enum ItemEffect TryStickyBarbOnTargetHit(u32 battlerDef, u32 battlerAtk, enum ItemEffect effect = ITEM_NO_EFFECT; if (IsBattlerTurnDamaged(battlerDef) - && !CanBattlerAvoidContactEffects(battlerAtk, battlerDef, GetBattlerAbility(battlerAtk), GetBattlerHoldEffect(battlerAtk), gCurrentMove) + && !CanBattlerAvoidContactEffects(battlerAtk, battlerDef, GetBattlerHoldEffect(battlerAtk), gCurrentMove) && !DoesSubstituteBlockMove(battlerAtk, battlerDef, gCurrentMove) && IsBattlerAlive(battlerAtk) && CanStealItem(battlerAtk, battlerDef, item) @@ -605,7 +608,7 @@ static enum ItemEffect TryStickyBarbOnEndTurn(u32 battler, u32 item) { enum ItemEffect effect = ITEM_NO_EFFECT; - if (!IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_MAGIC_GUARD)) + if (!IsAbilityAndRecord(battler, ABILITY_MAGIC_GUARD)) { SetPassiveDamageAmount(battler, GetNonDynamaxMaxHP(battler) / 8); PREPARE_ITEM_BUFFER(gBattleTextBuff1, item); @@ -619,9 +622,8 @@ static enum ItemEffect TryStickyBarbOnEndTurn(u32 battler, u32 item) static enum ItemEffect TryToxicOrb(u32 battler) { enum ItemEffect effect = ITEM_NO_EFFECT; - enum Ability ability = GetBattlerAbility(battler); - if (CanBePoisoned(battler, battler, ability, ability)) // Can corrosion trigger toxic orb on itself? + if (CanBePoisoned(battler, battler)) // Can corrosion trigger toxic orb on itself? { gBattleMons[battler].status1 = STATUS1_TOXIC_POISON; BattleScriptExecute(BattleScript_ToxicOrb); @@ -634,9 +636,8 @@ static enum ItemEffect TryToxicOrb(u32 battler) static enum ItemEffect TryFlameOrb(u32 battler) { enum ItemEffect effect = ITEM_NO_EFFECT; - enum Ability ability = GetBattlerAbility(battler); - if (CanBeBurned(battler, battler, ability)) + if (CanBeBurned(battler, battler)) { gBattleMons[battler].status1 = STATUS1_BURN; BattleScriptExecute(BattleScript_FlameOrb); @@ -666,7 +667,7 @@ static enum ItemEffect TryBlackSludgeDamage(u32 battler, enum HoldEffect holdEff { enum ItemEffect effect = ITEM_NO_EFFECT; - if (!IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_MAGIC_GUARD)) + if (!IsAbilityAndRecord(battler, ABILITY_MAGIC_GUARD)) { SetPassiveDamageAmount(battler, GetNonDynamaxMaxHP(battler) / 8); RecordItemEffectBattle(battler, holdEffect); @@ -860,11 +861,10 @@ enum HealAmount static u32 ItemHealHp(u32 battler, u32 itemId, enum HealAmount percentHeal, ActivationTiming timing) { enum ItemEffect effect = ITEM_NO_EFFECT; - enum Ability ability = GetBattlerAbility(battler); if (!(gBattleScripting.overrideBerryRequirements && gBattleMons[battler].hp == gBattleMons[battler].maxHP) && !(B_HEAL_BLOCKING >= GEN_5 && gBattleMons[battler].volatiles.healBlock) - && HasEnoughHpToEatBerry(battler, ability, 2, itemId)) + && HasEnoughHpToEatBerry(battler, 2, itemId)) { s32 healAmount = 0; if (percentHeal == PERCENT_HEAL_AMOUNT) @@ -872,7 +872,7 @@ static u32 ItemHealHp(u32 battler, u32 itemId, enum HealAmount percentHeal, Acti else healAmount = GetItemHoldEffectParam(itemId); - if (ability == ABILITY_RIPEN && GetItemPocket(itemId) == POCKET_BERRIES) + if (BattlerHasTrait(battler, ABILITY_RIPEN) && GetItemPocket(itemId) == POCKET_BERRIES) healAmount *= 2; SetHealAmount(battler, healAmount); @@ -892,7 +892,6 @@ static u32 ItemRestorePp(u32 battler, u32 itemId, ActivationTiming timing) enum ItemEffect effect = ITEM_NO_EFFECT; struct Pokemon *mon = GetBattlerMon(battler); u32 i, changedPP = 0; - enum Ability ability = GetBattlerAbility(battler); for (i = 0; i < MAX_MON_MOVES; i++) { @@ -904,7 +903,7 @@ static u32 ItemRestorePp(u32 battler, u32 itemId, ActivationTiming timing) { u32 ppRestored = GetItemHoldEffectParam(itemId); - if (ability == ABILITY_RIPEN) + if (BattlerHasTrait(battler, ABILITY_RIPEN)) { ppRestored *= 2; gBattlerAbility = battler; @@ -936,13 +935,12 @@ static enum ItemEffect HealConfuseBerry(u32 battler, u32 itemId, u32 flavorId, A { enum ItemEffect effect = ITEM_NO_EFFECT; u32 hpFraction = B_CONFUSE_BERRIES_HEAL >= GEN_7 ? 4 : 2; - u32 ability = GetBattlerAbility(battler); - if (HasEnoughHpToEatBerry(battler, ability, hpFraction, itemId) + if (HasEnoughHpToEatBerry(battler, hpFraction, itemId) && !(B_HEAL_BLOCKING >= GEN_5 && gBattleMons[battler].volatiles.healBlock)) { s32 healAmount = GetNonDynamaxMaxHP(battler) / GetItemHoldEffectParam(itemId); - if (ability == ABILITY_RIPEN) + if (BattlerHasTrait(battler, ABILITY_RIPEN)) healAmount *= 2; SetHealAmount(battler, healAmount); if (timing == IsOnSwitchInFirstTurnActivation) @@ -969,13 +967,12 @@ static enum ItemEffect HealConfuseBerry(u32 battler, u32 itemId, u32 flavorId, A static enum ItemEffect StatRaiseBerry(u32 battler, u32 itemId, enum Stat statId, ActivationTiming timing) { enum ItemEffect effect = ITEM_NO_EFFECT; - enum Ability ability = GetBattlerAbility(battler); - if (CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN, ability) - && HasEnoughHpToEatBerry(battler, ability, GetItemHoldEffectParam(itemId), itemId)) + if (CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN) + && HasEnoughHpToEatBerry(battler, GetItemHoldEffectParam(itemId), itemId)) { gEffectBattler = gBattleScripting.battler = battler; - SET_STATCHANGER(statId, ability == ABILITY_RIPEN ? 2 : 1, FALSE); + SET_STATCHANGER(statId, BattlerHasTrait(battler, ABILITY_RIPEN) ? 2 : 1, FALSE); gBattleScripting.animArg1 = STAT_ANIM_PLUS1 + statId; gBattleScripting.animArg2 = 0; @@ -995,7 +992,7 @@ static enum ItemEffect CriticalHitRatioUp(u32 battler, u32 itemId, ActivationTim if (!gBattleMons[battler].volatiles.focusEnergy && !gBattleMons[battler].volatiles.dragonCheer - && HasEnoughHpToEatBerry(battler, GetBattlerAbility(battler), GetItemHoldEffectParam(itemId), itemId)) + && HasEnoughHpToEatBerry(battler, GetItemHoldEffectParam(itemId), itemId)) { gBattleMons[battler].volatiles.focusEnergy = TRUE; if (timing == IsOnSwitchInFirstTurnActivation) @@ -1012,31 +1009,30 @@ static enum ItemEffect RandomStatRaiseBerry(u32 battler, u32 itemId, ActivationT { enum ItemEffect effect = ITEM_NO_EFFECT; enum Stat stat; - enum Ability ability = GetBattlerAbility(battler); for (stat = STAT_ATK; stat < NUM_STATS; stat++) { - if (CompareStat(battler, stat, MAX_STAT_STAGE, CMP_LESS_THAN, ability)) + if (CompareStat(battler, stat, MAX_STAT_STAGE, CMP_LESS_THAN)) break; } if (stat == NUM_STATS) return effect; - if (HasEnoughHpToEatBerry(battler, ability, GetItemHoldEffectParam(itemId), itemId)) + if (HasEnoughHpToEatBerry(battler, GetItemHoldEffectParam(itemId), itemId)) { u32 savedAttacker = gBattlerAttacker; // MoodyCantRaiseStat requires that the battler is set to gBattlerAttacker gBattlerAttacker = gBattleScripting.battler = battler; gBattleScripting.statChanger = 0; - if (ability != ABILITY_CONTRARY) + if (!BattlerHasTrait(battler, ABILITY_CONTRARY)) stat = RandomUniformExcept(RNG_RANDOM_STAT_UP, STAT_ATK, NUM_STATS - 1, MoodyCantRaiseStat); else stat = RandomUniformExcept(RNG_RANDOM_STAT_UP, STAT_ATK, NUM_STATS - 1, MoodyCantLowerStat); gBattlerAttacker = savedAttacker; PREPARE_STAT_BUFFER(gBattleTextBuff1, stat); - SET_STATCHANGER(stat, ability == ABILITY_RIPEN ? 4 : 2, FALSE); + SET_STATCHANGER(stat, BattlerHasTrait(battler, ABILITY_RIPEN) ? 4 : 2, FALSE); gBattleScripting.animArg1 = STAT_ANIM_PLUS2 + stat; gBattleScripting.animArg2 = 0; if (timing == IsOnSwitchInFirstTurnActivation) @@ -1053,7 +1049,7 @@ static enum ItemEffect TrySetMicleBerry(u32 battler, u32 itemId, ActivationTimin { enum ItemEffect effect = ITEM_NO_EFFECT; - if (HasEnoughHpToEatBerry(battler, GetBattlerAbility(battler), 4, itemId)) + if (HasEnoughHpToEatBerry(battler, 4, itemId)) { gBattleStruct->battlerState[battler].usedMicleBerry = TRUE; if (timing == IsOnSwitchInFirstTurnActivation) @@ -1101,7 +1097,7 @@ enum ItemEffect ItemBattleEffects(u32 itemBattler, u32 battler, enum HoldEffect effect = TryBerserkGene(itemBattler, timing); break; case HOLD_EFFECT_BOOSTER_ENERGY: - effect = TryBoosterEnergy(itemBattler, GetBattlerAbility(itemBattler), timing); + effect = TryBoosterEnergy(itemBattler, timing); break; case HOLD_EFFECT_WHITE_HERB: effect = RestoreWhiteHerbStats(itemBattler, timing); diff --git a/src/battle_interface.c b/src/battle_interface.c index a48225b966b5..534dc62d0643 100644 --- a/src/battle_interface.c +++ b/src/battle_interface.c @@ -2,6 +2,7 @@ #include "malloc.h" #include "battle.h" #include "pokemon.h" +#include "battle_ai_main.h" #include "battle_controllers.h" #include "battle_interface.h" #include "battle_z_move.h" @@ -2699,6 +2700,7 @@ void UpdateAbilityPopup(u8 battler) u8 *spriteIds = gBattleStruct->abilityPopUpSpriteIds[battler]; enum Ability ability = (gBattleScripting.abilityPopupOverwrite) ? gBattleScripting.abilityPopupOverwrite : gBattleMons[battler].ability; + PopTraitStack(); PrintAbilityOnAbilityPopUp(ability, spriteIds[0], spriteIds[1]); } diff --git a/src/battle_main.c b/src/battle_main.c index 8607e22e06b5..2fc92589ff60 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -169,10 +169,15 @@ EWRAM_DATA u16 gCalledMove = 0; EWRAM_DATA s32 gBideDmg[MAX_BATTLERS_COUNT] = {0}; EWRAM_DATA u16 gLastUsedItem = 0; EWRAM_DATA enum Ability gLastUsedAbility = 0; +EWRAM_DATA enum Ability gDisplayAbility = 0; +EWRAM_DATA enum Ability gDisplayAbility2 = 0; +EWRAM_DATA u8 gDisplayBattler = 0; +EWRAM_DATA enum Ability gTraitStack[MAX_BATTLERS_COUNT * MAX_MON_TRAITS][2] = {0}; EWRAM_DATA u8 gBattlerAttacker = 0; EWRAM_DATA u8 gBattlerTarget = 0; EWRAM_DATA u8 gBattlerFainted = 0; EWRAM_DATA u8 gEffectBattler = 0; +EWRAM_DATA u8 gEffectBattler2 = 0; // Backup for when gEffectBattler is already in use (Multi) EWRAM_DATA u8 gPotentialItemEffectBattler = 0; EWRAM_DATA u8 gAbsentBattlerFlags = 0; EWRAM_DATA u8 gMultiHitCounter = 0; @@ -3269,6 +3274,13 @@ void SwitchInClearSetData(u32 battler, struct Volatiles *volatilesCopy) // Restore struct member so replacement does not miss timing gSpecialStatuses[battler].switchInAbilityDone = FALSE; + // Restore struct member so replacement does not miss timing (Traits) + for(int j=0; j<=MAX_MON_INNATES; j++) + { + gSpecialStatuses[battler].switchInTraitDone[j] = FALSE; + gSpecialStatuses[battler].endTurnTraitDone[j] = FALSE; + } + // Reset damage to prevent things like red card activating if the switched-in mon is holding it gSpecialStatuses[battler].physicalDmg = 0; gSpecialStatuses[battler].specialDmg = 0; @@ -3424,10 +3436,9 @@ const u8* FaintClearSetData(u32 battler) // If the released mon can be confused, do so. // Don't use CanBeConfused here, since it can cause issues in edge cases. - enum Ability ability = GetBattlerAbility(otherSkyDropper); - if (!(ability == ABILITY_OWN_TEMPO + if (!(BattlerHasTrait(otherSkyDropper, ABILITY_OWN_TEMPO) || gBattleMons[otherSkyDropper].volatiles.confusionTurns - || IsBattlerTerrainAffected(otherSkyDropper, ability, GetBattlerHoldEffect(otherSkyDropper), STATUS_FIELD_MISTY_TERRAIN))) + || IsBattlerTerrainAffected(otherSkyDropper, GetBattlerHoldEffect(otherSkyDropper), STATUS_FIELD_MISTY_TERRAIN))) { gBattleMons[otherSkyDropper].volatiles.confusionTurns = ((Random()) % 4) + 2; gBattlerAttacker = otherSkyDropper; @@ -3860,7 +3871,7 @@ static void TryDoEventsBeforeFirstTurn(void) while (gBattleStruct->switchInBattlerCounter < gBattlersCount) // From fastest to slowest { i = gBattlerByTurnOrder[gBattleStruct->switchInBattlerCounter++]; - if (AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS_FIRST_TURN, i, 0, 0, 0) != 0) + if (AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS_FIRST_TURN, i, 0, 0) != 0) return; } gBattleStruct->switchInBattlerCounter = 0; @@ -3873,11 +3884,11 @@ static void TryDoEventsBeforeFirstTurn(void) if (TryPrimalReversion(battler)) return; - if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, battler, 0, 0, 0)) + if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, battler, 0, 0)) return; if (TryClearIllusion(battler, ABILITYEFFECT_ON_SWITCHIN)) return; - if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN_IMMUNITIES, battler, 0, 0, 0) != 0) + if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN_IMMUNITIES, battler, 0, 0) != 0) return; } gBattleStruct->switchInBattlerCounter = 0; @@ -3907,7 +3918,7 @@ static void TryDoEventsBeforeFirstTurn(void) while (gBattleStruct->switchInBattlerCounter < gBattlersCount) // From fastest to slowest { u32 battler = gBattlerByTurnOrder[gBattleStruct->switchInBattlerCounter++]; - if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST_FIRST_TURN, battler, GetBattlerAbility(battler), 0, 0)) + if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST_FIRST_TURN, battler, 0, 0)) return; } gBattleStruct->switchInBattlerCounter = 0; @@ -3986,6 +3997,8 @@ static void HandleEndTurn_ContinueBattle(void) gBattleStruct->eventState.endTurnBlock = 0; gBattleStruct->eventState.endTurnBattler = 0; gBattleStruct->eventState.endTurn = 0; + while(PullTraitStackAbility() != ABILITY_NONE) //Clear trait stack + PopTraitStack(); } } @@ -4112,13 +4125,23 @@ u8 IsRunningFromBattleImpossible(u32 battler) return BATTLE_RUN_SUCCESS; if (gBattleTypeFlags & BATTLE_TYPE_LINK) return BATTLE_RUN_SUCCESS; - if (GetBattlerAbility(battler) == ABILITY_RUN_AWAY) + if (BattlerHasTrait(battler, ABILITY_RUN_AWAY)) return BATTLE_RUN_SUCCESS; if ((i = IsAbilityPreventingEscape(battler))) { + enum Ability ability = ABILITY_NONE; + + if ((BattlerHasTrait(i - 1, ABILITY_SHADOW_TAG)) + && (B_SHADOW_TAG_ESCAPE >= GEN_4 && !BattlerHasTrait(battler, ABILITY_SHADOW_TAG))) + ability = ABILITY_SHADOW_TAG; + if ((BattlerHasTrait(i - 1, ABILITY_ARENA_TRAP)) && IsBattlerGrounded(battler, GetBattlerHoldEffect(battler))) + ability = ABILITY_ARENA_TRAP; + if ((BattlerHasTrait(i - 1, ABILITY_MAGNET_PULL)) && IS_BATTLER_OF_TYPE(battler, TYPE_STEEL)) + ability = ABILITY_MAGNET_PULL; + gBattleScripting.battler = i - 1; - gLastUsedAbility = gBattleMons[i - 1].ability; + gLastUsedAbility = ability; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_PREVENTS_ESCAPE; return BATTLE_RUN_FAILURE; } @@ -4518,7 +4541,7 @@ static void HandleTurnActionSelectionState(void) gBattleStruct->chosenMovePositions[battler] = gBattleResources->bufferB[battler][2] & ~RET_GIMMICK; gChosenMoveByBattler[battler] = GetChosenMoveFromPosition(battler); gBattleStruct->moveTarget[battler] = gBattleResources->bufferB[battler][3]; - if (IsBattleMoveStatus(gChosenMoveByBattler[battler]) && GetBattlerAbility(battler) == ABILITY_MYCELIUM_MIGHT) + if (IsBattleMoveStatus(gChosenMoveByBattler[battler]) && BattlerHasTrait(battler, ABILITY_MYCELIUM_MIGHT)) gProtectStructs[battler].myceliumMight = TRUE; if (GetBattlerHoldEffect(battler) == HOLD_EFFECT_LAGGING_TAIL) gProtectStructs[battler].laggingTail = TRUE; @@ -4758,9 +4781,16 @@ void SwapTurnOrder(u8 id1, u8 id2) } // For AI, so it doesn't 'cheat' by knowing player's ability -u32 GetBattlerTotalSpeedStat(u32 battler, enum Ability ability, enum HoldEffect holdEffect) +u32 GetBattlerTotalSpeedStat(u32 battler, enum HoldEffect holdEffect) { u32 speed = gBattleMons[battler].speed; + u32 baseSpeed = gBattleMons[battler].speed; + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battler); + + // Use AI Ability knowledge if this is an AI check + if(gAiLogicData->aiCalcInProgress) + battlerTraits[0] = gAiLogicData->abilities[battler]; // stat stages speed *= gStatStageRatios[gBattleMons[battler].statStages[STAT_SPEED]][0]; @@ -4769,29 +4799,29 @@ u32 GetBattlerTotalSpeedStat(u32 battler, enum Ability ability, enum HoldEffect // weather abilities if (HasWeatherEffect()) { - if (ability == ABILITY_SWIFT_SWIM && holdEffect != HOLD_EFFECT_UTILITY_UMBRELLA && gBattleWeather & B_WEATHER_RAIN) - speed *= 2; - else if (ability == ABILITY_CHLOROPHYLL && holdEffect != HOLD_EFFECT_UTILITY_UMBRELLA && gBattleWeather & B_WEATHER_SUN) - speed *= 2; - else if (ability == ABILITY_SAND_RUSH && gBattleWeather & B_WEATHER_SANDSTORM) - speed *= 2; - else if (ability == ABILITY_SLUSH_RUSH && (gBattleWeather & (B_WEATHER_HAIL | B_WEATHER_SNOW))) - speed *= 2; + if (SearchTraits(battlerTraits, ABILITY_SWIFT_SWIM) && holdEffect != HOLD_EFFECT_UTILITY_UMBRELLA && gBattleWeather & B_WEATHER_RAIN) + speed += baseSpeed; + if (SearchTraits(battlerTraits, ABILITY_CHLOROPHYLL) && holdEffect != HOLD_EFFECT_UTILITY_UMBRELLA && gBattleWeather & B_WEATHER_SUN) + speed += baseSpeed; + if (SearchTraits(battlerTraits, ABILITY_SAND_RUSH) && gBattleWeather & B_WEATHER_SANDSTORM) + speed += baseSpeed; + if (SearchTraits(battlerTraits, ABILITY_SLUSH_RUSH) && (gBattleWeather & (B_WEATHER_HAIL | B_WEATHER_SNOW))) + speed += baseSpeed; } // other abilities - if (ability == ABILITY_QUICK_FEET && gBattleMons[battler].status1 & STATUS1_ANY) - speed = (speed * 150) / 100; - else if (ability == ABILITY_SURGE_SURFER && gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN) + if (SearchTraits(battlerTraits, ABILITY_QUICK_FEET) && gBattleMons[battler].status1 & STATUS1_ANY) + speed += baseSpeed / 2; + if (SearchTraits(battlerTraits, ABILITY_SURGE_SURFER) && gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN) + speed += baseSpeed; + if (SearchTraits(battlerTraits, ABILITY_PROTOSYNTHESIS) && !(gBattleMons[battler].volatiles.transformed) && ((gBattleWeather & B_WEATHER_SUN && HasWeatherEffect()) || gDisableStructs[battler].boosterEnergyActivated)) + speed += (GetHighestStatId(battler) == STAT_SPEED) ? baseSpeed / 2 : 0; + if (SearchTraits(battlerTraits, ABILITY_QUARK_DRIVE) && !(gBattleMons[battler].volatiles.transformed) && (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gDisableStructs[battler].boosterEnergyActivated)) + speed += (GetHighestStatId(battler) == STAT_SPEED) ? baseSpeed / 2 : 0; + if (SearchTraits(battlerTraits, ABILITY_UNBURDEN) && gDisableStructs[battler].unburdenActive) speed *= 2; - else if (ability == ABILITY_SLOW_START && gDisableStructs[battler].slowStartTimer != 0) + if (SearchTraits(battlerTraits, ABILITY_SLOW_START) && gDisableStructs[battler].slowStartTimer != 0) speed /= 2; - else if (ability == ABILITY_PROTOSYNTHESIS && !(gBattleMons[battler].volatiles.transformed) && ((gBattleWeather & B_WEATHER_SUN && HasWeatherEffect()) || gDisableStructs[battler].boosterEnergyActivated)) - speed = (GetParadoxBoostedStatId(battler) == STAT_SPEED) ? (speed * 150) / 100 : speed; - else if (ability == ABILITY_QUARK_DRIVE && !(gBattleMons[battler].volatiles.transformed) && (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gDisableStructs[battler].boosterEnergyActivated)) - speed = (GetParadoxBoostedStatId(battler) == STAT_SPEED) ? (speed * 150) / 100 : speed; - else if (ability == ABILITY_UNBURDEN && gDisableStructs[battler].unburdenActive) - speed *= 2; // player's badge boost if (!(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK | BATTLE_TYPE_FRONTIER)) @@ -4804,11 +4834,11 @@ u32 GetBattlerTotalSpeedStat(u32 battler, enum Ability ability, enum HoldEffect // item effects if (holdEffect == HOLD_EFFECT_MACHO_BRACE || holdEffect == HOLD_EFFECT_POWER_ITEM) speed /= 2; - else if (holdEffect == HOLD_EFFECT_IRON_BALL) + if (holdEffect == HOLD_EFFECT_IRON_BALL) speed /= 2; - else if (holdEffect == HOLD_EFFECT_CHOICE_SCARF && GetActiveGimmick(battler) != GIMMICK_DYNAMAX) + if (holdEffect == HOLD_EFFECT_CHOICE_SCARF && GetActiveGimmick(battler) != GIMMICK_DYNAMAX) speed = (speed * 150) / 100; - else if (holdEffect == HOLD_EFFECT_QUICK_POWDER && gBattleMons[battler].species == SPECIES_DITTO && !(gBattleMons[battler].volatiles.transformed)) + if (holdEffect == HOLD_EFFECT_QUICK_POWDER && gBattleMons[battler].species == SPECIES_DITTO && !(gBattleMons[battler].volatiles.transformed)) speed *= 2; // various effects @@ -4816,7 +4846,7 @@ u32 GetBattlerTotalSpeedStat(u32 battler, enum Ability ability, enum HoldEffect speed *= 2; // paralysis drop - if (gBattleMons[battler].status1 & STATUS1_PARALYSIS && ability != ABILITY_QUICK_FEET) + if (gBattleMons[battler].status1 & STATUS1_PARALYSIS && !SearchTraits(battlerTraits, ABILITY_QUICK_FEET)) speed /= GetConfig(CONFIG_PARALYSIS_SPEED) >= GEN_7 ? 2 : 4; if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SWAMP) @@ -4825,7 +4855,7 @@ u32 GetBattlerTotalSpeedStat(u32 battler, enum Ability ability, enum HoldEffect return speed; } -s32 GetChosenMovePriority(u32 battler, enum Ability ability) +s32 GetChosenMovePriority(u32 battler) { u16 move; @@ -4835,12 +4865,14 @@ s32 GetChosenMovePriority(u32 battler, enum Ability ability) else move = GetChosenMoveFromPosition(battler); - return GetBattleMovePriority(battler, ability, move); + return GetBattleMovePriority(battler, move); } -s32 GetBattleMovePriority(u32 battler, enum Ability ability, u32 move) +s32 GetBattleMovePriority(u32 battler, u32 move) { s32 priority = 0; + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battler); if (GetActiveGimmick(battler) == GIMMICK_Z_MOVE && !IsBattleMoveStatus(move)) move = GetUsableZMove(battler, move); @@ -4855,25 +4887,23 @@ s32 GetBattleMovePriority(u32 battler, enum Ability ability, u32 move) { priority = -8; } - else if (ability == ABILITY_GALE_WINGS + if (SearchTraits(battlerTraits, ABILITY_GALE_WINGS) && (GetConfig(CONFIG_GALE_WINGS) < GEN_7 || IsBattlerAtMaxHp(battler)) && GetMoveType(move) == TYPE_FLYING) { priority++; } - else if (IsBattleMoveStatus(move) && IsAbilityAndRecord(battler, ability, ABILITY_PRANKSTER)) + if (IsBattleMoveStatus(move) && IsAbilityAndRecord(battler, ABILITY_PRANKSTER)) { gProtectStructs[battler].pranksterElevated = 1; priority++; } - else if (GetMoveEffect(move) == EFFECT_GRASSY_GLIDE && IsBattlerTerrainAffected(battler, ability, GetBattlerHoldEffect(battler), STATUS_FIELD_GRASSY_TERRAIN) && GetActiveGimmick(gBattlerAttacker) != GIMMICK_DYNAMAX && !IsGimmickSelected(battler, GIMMICK_DYNAMAX)) + if (GetMoveEffect(move) == EFFECT_GRASSY_GLIDE && IsBattlerTerrainAffected(battler, GetBattlerHoldEffect(battler), STATUS_FIELD_GRASSY_TERRAIN) && GetActiveGimmick(gBattlerAttacker) != GIMMICK_DYNAMAX && !IsGimmickSelected(battler, GIMMICK_DYNAMAX)) { priority++; } - else if (ability == ABILITY_TRIAGE && IsHealingMove(move)) - { + if (SearchTraits(battlerTraits, ABILITY_TRIAGE) && IsHealingMove(move)) priority += 3; - } return priority; } @@ -4889,8 +4919,8 @@ s32 GetWhichBattlerFasterArgs(struct BattleContext *ctx, bool32 ignoreChosenMove // Lagging Tail - always last bool32 battler1HasQuickEffect = gProtectStructs[ctx->battlerAtk].quickDraw || gProtectStructs[ctx->battlerAtk].usedCustapBerry; bool32 battler2HasQuickEffect = gProtectStructs[ctx->battlerDef].quickDraw || gProtectStructs[ctx->battlerDef].usedCustapBerry; - bool32 battler1HasStallingAbility = ctx->abilities[ctx->battlerAtk] == ABILITY_STALL || gProtectStructs[ctx->battlerAtk].myceliumMight; - bool32 battler2HasStallingAbility = ctx->abilities[ctx->battlerDef] == ABILITY_STALL || gProtectStructs[ctx->battlerDef].myceliumMight; + bool32 battler1HasStallingAbility = BattlerHasTrait(ctx->battlerAtk, ABILITY_STALL) || gProtectStructs[ctx->battlerAtk].myceliumMight; + bool32 battler2HasStallingAbility = BattlerHasTrait(ctx->battlerDef, ABILITY_STALL) || gProtectStructs[ctx->battlerDef].myceliumMight; if (battler1HasQuickEffect && !battler2HasQuickEffect) strikesFirst = 1; @@ -4943,15 +4973,15 @@ s32 GetWhichBattlerFasterArgs(struct BattleContext *ctx, bool32 ignoreChosenMove s32 GetWhichBattlerFasterOrTies(struct BattleContext *ctx, bool32 ignoreChosenMoves) { s32 priority1 = 0, priority2 = 0; - u32 speedBattler1 = GetBattlerTotalSpeedStat(ctx->battlerAtk, ctx->abilities[ctx->battlerAtk], ctx->holdEffects[ctx->battlerAtk]); - u32 speedBattler2 = GetBattlerTotalSpeedStat(ctx->battlerDef, ctx->abilities[ctx->battlerDef], ctx->holdEffects[ctx->battlerDef]); + u32 speedBattler1 = GetBattlerTotalSpeedStat(ctx->battlerAtk, ctx->holdEffects[ctx->battlerAtk]); + u32 speedBattler2 = GetBattlerTotalSpeedStat(ctx->battlerDef, ctx->holdEffects[ctx->battlerDef]); if (!ignoreChosenMoves) { if (gChosenActionByBattler[ctx->battlerAtk] == B_ACTION_USE_MOVE) - priority1 = GetChosenMovePriority(ctx->battlerAtk, ctx->abilities[ctx->battlerAtk]); + priority1 = GetChosenMovePriority(ctx->battlerAtk); if (gChosenActionByBattler[ctx->battlerDef] == B_ACTION_USE_MOVE) - priority2 = GetChosenMovePriority(ctx->battlerDef, ctx->abilities[ctx->battlerDef]); + priority2 = GetChosenMovePriority(ctx->battlerDef); } return GetWhichBattlerFasterArgs( @@ -5290,29 +5320,27 @@ static void TryChangingTurnOrderEffects(struct BattleContext *ctx, u32 *quickCla { u32 battler1 = ctx->battlerAtk; u32 battler2 = ctx->battlerDef; - enum Ability ability1 = ctx->abilities[ctx->battlerAtk]; - enum Ability ability2 = ctx->abilities[ctx->battlerDef]; enum HoldEffect holdEffectBattler1 = ctx->holdEffects[ctx->battlerAtk]; enum HoldEffect holdEffectBattler2 = ctx->holdEffects[ctx->battlerDef]; // Battler 1 // Quick Draw - if (ability1 == ABILITY_QUICK_DRAW && !IsBattleMoveStatus(gChosenMoveByBattler[battler1]) && quickDrawRandom[battler1]) + if (BattlerHasTrait(battler1, ABILITY_QUICK_DRAW) && !IsBattleMoveStatus(gChosenMoveByBattler[battler1]) && quickDrawRandom[battler1]) gProtectStructs[battler1].quickDraw = TRUE; // Quick Claw and Custap Berry if (!gProtectStructs[battler1].quickDraw && ((holdEffectBattler1 == HOLD_EFFECT_QUICK_CLAW && quickClawRandom[battler1]) - || (holdEffectBattler1 == HOLD_EFFECT_CUSTAP_BERRY && HasEnoughHpToEatBerry(battler1, ability1, 4, gBattleMons[battler1].item)))) + || (holdEffectBattler1 == HOLD_EFFECT_CUSTAP_BERRY && HasEnoughHpToEatBerry(battler1, 4, gBattleMons[battler1].item)))) gProtectStructs[battler1].usedCustapBerry = TRUE; // Battler 2 // Quick Draw - if (ability2 == ABILITY_QUICK_DRAW && !IsBattleMoveStatus(gChosenMoveByBattler[battler2]) && quickDrawRandom[battler2]) + if (BattlerHasTrait(battler2, ABILITY_QUICK_DRAW) && !IsBattleMoveStatus(gChosenMoveByBattler[battler2]) && quickDrawRandom[battler2]) gProtectStructs[battler2].quickDraw = TRUE; // Quick Claw and Custap Berry if (!gProtectStructs[battler2].quickDraw && ((holdEffectBattler2 == HOLD_EFFECT_QUICK_CLAW && quickClawRandom[battler2]) - || (holdEffectBattler2 == HOLD_EFFECT_CUSTAP_BERRY && HasEnoughHpToEatBerry(battler2, ability2, 4, gBattleMons[battler2].item)))) + || (holdEffectBattler2 == HOLD_EFFECT_CUSTAP_BERRY && HasEnoughHpToEatBerry(battler2, 4, gBattleMons[battler2].item)))) gProtectStructs[battler2].usedCustapBerry = TRUE; } @@ -5351,9 +5379,10 @@ static void CheckChangingTurnOrderEffects(void) else if (gProtectStructs[battler].quickDraw) { gBattlerAbility = battler; - gLastUsedAbility = gBattleMons[battler].ability; + gLastUsedAbility = ABILITY_QUICK_DRAW; + PushTraitStack(battler, ABILITY_QUICK_DRAW); PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); - RecordAbilityBattle(battler, gLastUsedAbility); + RecordAbilityBattle(battler, ABILITY_QUICK_DRAW); BattleScriptExecute(BattleScript_QuickDrawActivation); } return; @@ -5834,24 +5863,14 @@ enum Type TrySetAteType(u32 move, u32 battlerAtk, enum Ability attackerAbility) break; } - switch (attackerAbility) - { - case ABILITY_PIXILATE: + if (BattlerHasTrait(battlerAtk, ABILITY_PIXILATE)) ateType = TYPE_FAIRY; - break; - case ABILITY_REFRIGERATE: + else if (BattlerHasTrait(battlerAtk, ABILITY_REFRIGERATE)) ateType = TYPE_ICE; - break; - case ABILITY_AERILATE: + else if (BattlerHasTrait(battlerAtk, ABILITY_AERILATE)) ateType = TYPE_FLYING; - break; - case ABILITY_GALVANIZE: + else if (BattlerHasTrait(battlerAtk, ABILITY_GALVANIZE)) ateType = TYPE_ELECTRIC; - break; - default: - ateType = TYPE_NONE; - break; - } return ateType; } @@ -5863,7 +5882,6 @@ enum Type GetDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler, enum Mo enum BattleMoveEffects moveEffect = GetMoveEffect(move); u32 species, heldItem; enum Type type1, type2, type3; - enum Ability ability; enum HoldEffect holdEffect; enum Gimmick gimmick = GetActiveGimmick(battler); @@ -5875,7 +5893,6 @@ enum Type GetDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler, enum Mo species = gBattleMons[battler].species; heldItem = gBattleMons[battler].item; holdEffect = GetBattlerHoldEffect(battler); - ability = GetBattlerAbility(battler); type1 = gBattleMons[battler].types[0]; type2 = gBattleMons[battler].types[1]; type3 = gBattleMons[battler].types[2]; @@ -5885,7 +5902,6 @@ enum Type GetDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler, enum Mo species = GetMonData(mon, MON_DATA_SPECIES); heldItem = GetMonData(mon, MON_DATA_HELD_ITEM, 0); holdEffect = GetItemHoldEffect(heldItem); - ability = GetMonAbility(mon); type1 = GetSpeciesType(species, 0); type2 = GetSpeciesType(species, 1); type3 = TYPE_MYSTERY; @@ -6016,7 +6032,7 @@ enum Type GetDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler, enum Mo case EFFECT_TERRAIN_PULSE: if (state == MON_IN_BATTLE) { - if (IsBattlerTerrainAffected(battler, GetBattlerAbility(battler), GetBattlerHoldEffect(battler), STATUS_FIELD_TERRAIN_ANY)) + if (IsBattlerTerrainAffected(battler, GetBattlerHoldEffect(battler), STATUS_FIELD_TERRAIN_ANY)) { if (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN) return TYPE_ELECTRIC; @@ -6059,22 +6075,22 @@ enum Type GetDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler, enum Mo break; } - if (IsSoundMove(move) && ability == ABILITY_LIQUID_VOICE) + if (IsSoundMove(move) && BattlerHasTrait(battler, ABILITY_LIQUID_VOICE)) { return TYPE_WATER; } else if (moveEffect == EFFECT_AURA_WHEEL - && species == SPECIES_MORPEKO_HANGRY - && ability != ABILITY_NORMALIZE) + && species == SPECIES_MORPEKO_HANGRY + && !BattlerHasTrait(battler, ABILITY_NORMALIZE)) { return TYPE_DARK; } else if (moveType == TYPE_NORMAL - && ability != ABILITY_NORMALIZE + && !BattlerHasTrait(battler, ABILITY_NORMALIZE) && gimmick != GIMMICK_DYNAMAX && gimmick != GIMMICK_Z_MOVE) { - u32 ateType = TrySetAteType(move, battler, ability); + u32 ateType = TrySetAteType(move, battler, 0); if (ateType != TYPE_NONE && state == MON_IN_BATTLE) gBattleStruct->battlerState[battler].ateBoost = TRUE; return ateType; @@ -6084,7 +6100,7 @@ enum Type GetDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler, enum Mo && moveEffect != EFFECT_NATURAL_GIFT && moveEffect != EFFECT_HIDDEN_POWER && moveEffect != EFFECT_WEATHER_BALL - && ability == ABILITY_NORMALIZE + && BattlerHasTrait(battler, ABILITY_NORMALIZE) && gimmick != GIMMICK_Z_MOVE) { if (state == MON_IN_BATTLE && gimmick != GIMMICK_DYNAMAX) diff --git a/src/battle_message.c b/src/battle_message.c index 7a5655b38f5a..96cdf5c5e4a5 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -186,6 +186,7 @@ const u8 *const gBattleStringsTable[STRINGID_COUNT] = [STRINGID_ATTACKMISSED] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX}'s attack missed!"), [STRINGID_PKMNPROTECTEDITSELF] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX} protected itself!"), [STRINGID_STATSWONTINCREASE2] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX}'s stats won't go any higher!"), + [STRINGID_STATSWONTINCREASECONTRARY2] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX}'s stats won't go any higher!"), //exception for Parting Shot Contrary [STRINGID_AVOIDEDDAMAGE] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX} avoided damage with {B_DEF_ABILITY}!"), //not in gen 5+, ability popup [STRINGID_ITDOESNTAFFECT] = COMPOUND_STRING("It doesn't affect {B_DEF_NAME_WITH_PREFIX2}…"), [STRINGID_BATTLERFAINTED] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX} fainted!\p"), @@ -197,26 +198,26 @@ const u8 *const gBattleStringsTable[STRINGID_COUNT] = [STRINGID_PREVENTSESCAPE] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX} prevents escape with {B_SCR_ABILITY}!\p"), [STRINGID_HITXTIMES] = COMPOUND_STRING("The Pokémon was hit {B_BUFF1} time(s)!"), //SV has dynamic plural here [STRINGID_PKMNFELLASLEEP] = COMPOUND_STRING("{B_EFF_NAME_WITH_PREFIX} fell asleep!"), - [STRINGID_PKMNMADESLEEP] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_BUFF1} made {B_EFF_NAME_WITH_PREFIX2} sleep!"), //not in gen 5+, ability popup + [STRINGID_PKMNMADESLEEP] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_DEF_ABILITY} made {B_EFF_NAME_WITH_PREFIX2} sleep!"), //not in gen 5+, ability popup [STRINGID_PKMNALREADYASLEEP] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX} is already asleep!"), [STRINGID_PKMNALREADYASLEEP2] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} is already asleep!"), [STRINGID_PKMNWASPOISONED] = COMPOUND_STRING("{B_EFF_NAME_WITH_PREFIX} was poisoned!"), - [STRINGID_PKMNPOISONEDBY] = COMPOUND_STRING("{B_EFF_NAME_WITH_PREFIX} was poisoned by {B_SCR_NAME_WITH_PREFIX2}'s {B_BUFF1}!"), //not in gen 5+, ability popup + [STRINGID_PKMNPOISONEDBY] = COMPOUND_STRING("{B_EFF_NAME_WITH_PREFIX} was poisoned by {B_SCR_NAME_WITH_PREFIX2}'s {B_DEF_ABILITY}!"), //not in gen 5+, ability popup [STRINGID_PKMNHURTBYPOISON] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} was hurt by its poisoning!"), [STRINGID_PKMNALREADYPOISONED] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX} is already poisoned!"), [STRINGID_PKMNBADLYPOISONED] = COMPOUND_STRING("{B_EFF_NAME_WITH_PREFIX} was badly poisoned!"), [STRINGID_PKMNENERGYDRAINED] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX} had its energy drained!"), [STRINGID_PKMNWASBURNED] = COMPOUND_STRING("{B_EFF_NAME_WITH_PREFIX} was burned!"), - [STRINGID_PKMNBURNEDBY] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_BUFF1} burned {B_EFF_NAME_WITH_PREFIX2}!"), //not in gen 5+, ability popup + [STRINGID_PKMNBURNEDBY] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_DEF_ABILITY} burned {B_EFF_NAME_WITH_PREFIX2}!"), //not in gen 5+, ability popup [STRINGID_PKMNHURTBYBURN] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} was hurt by its burn!"), [STRINGID_PKMNWASFROZEN] = COMPOUND_STRING("{B_EFF_NAME_WITH_PREFIX} was frozen solid!"), - [STRINGID_PKMNFROZENBY] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_BUFF1} froze {B_EFF_NAME_WITH_PREFIX2} solid!"), //not in gen 5+, ability popup + [STRINGID_PKMNFROZENBY] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_DEF_ABILITY} froze {B_EFF_NAME_WITH_PREFIX2} solid!"), //not in gen 5+, ability popup [STRINGID_PKMNISFROZEN] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} is frozen solid!"), [STRINGID_PKMNWASDEFROSTED] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX} thawed out!"), [STRINGID_PKMNWASDEFROSTED2] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} thawed out!"), [STRINGID_PKMNWASDEFROSTEDBY] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX}'s {B_CURRENT_MOVE} melted the ice!"), [STRINGID_PKMNWASPARALYZED] = COMPOUND_STRING("{B_EFF_NAME_WITH_PREFIX} is paralyzed, so it may be unable to move!"), - [STRINGID_PKMNWASPARALYZEDBY] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_BUFF1} paralyzed {B_EFF_NAME_WITH_PREFIX2}, so it may be unable to move!"), //not in gen 5+, ability popup + [STRINGID_PKMNWASPARALYZEDBY] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_DEF_ABILITY} paralyzed {B_EFF_NAME_WITH_PREFIX2}, so it may be unable to move!"), //not in gen 5+, ability popup [STRINGID_PKMNISPARALYZED] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} couldn't move because it's paralyzed!"), [STRINGID_PKMNISALREADYPARALYZED] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX} is already paralyzed!"), [STRINGID_PKMNHEALEDPARALYSIS] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX} was cured of paralysis!"), @@ -359,7 +360,9 @@ const u8 *const gBattleStringsTable[STRINGID_COUNT] = [STRINGID_PKMNANCHORSITSELFWITH] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX} anchors itself with {B_DEF_ABILITY}!"), //not in gen 5+, ability popup [STRINGID_PKMNCUTSATTACKWITH] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_SCR_ABILITY} cuts {B_DEF_NAME_WITH_PREFIX2}'s Attack!"), //not in gen 5+, ability popup [STRINGID_PKMNPREVENTSSTATLOSSWITH] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_SCR_ABILITY} prevents stat loss!"), //not in gen 5+, ability popup - [STRINGID_PKMNHURTSWITH] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} was hurt by {B_DEF_NAME_WITH_PREFIX2}'s {B_BUFF1}!"), + [STRINGID_PKMNINTIMIDATECONTRARYRATTLED] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_SCR_ABILITY} startles the target!"), //Workaround for ContraryIntimidate to trigger Rattled + [STRINGID_PKMNHURTSWITHITEM] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} was hurt by {B_DEF_NAME_WITH_PREFIX2}'s {B_BUFF1}!"), + [STRINGID_PKMNHURTSWITHABILITY] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} was hurt by {B_DEF_NAME_WITH_PREFIX2}'s {B_DEF_ABILITY}!"), [STRINGID_PKMNTRACED] = COMPOUND_STRING("It traced {B_BUFF1}'s {B_BUFF2}!"), [STRINGID_STATSHARPLY] = gText_StatSharply, [STRINGID_STATROSE] = gText_StatRose, @@ -496,6 +499,7 @@ const u8 *const gBattleStringsTable[STRINGID_COUNT] = [STRINGID_PKMNSXPREVENTSFLINCHING] = COMPOUND_STRING("{B_EFF_NAME_WITH_PREFIX}'s {B_EFF_ABILITY} prevents flinching!"), //not in gen 5+, ability popup [STRINGID_PKMNALREADYHASBURN] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX} is already burned!"), [STRINGID_STATSWONTDECREASE2] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX}'s stats won't go any lower!"), + [STRINGID_STATSWONTDECREASECONTRARY2] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX}'s stats won't go any lower!"), //exception for Parting Shot Contrary [STRINGID_PKMNSXBLOCKSY2] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_SCR_ABILITY} blocks {B_CURRENT_MOVE}!"), //not in gen 5+, ability popup [STRINGID_PKMNSXWOREOFF] = COMPOUND_STRING("{B_ATK_TEAM1} team's {B_BUFF1} wore off!"), [STRINGID_THEWALLSHATTERED] = COMPOUND_STRING("The wall shattered!"), //not in gen5+, uses "your teams light screen wore off!" etc instead @@ -606,7 +610,7 @@ const u8 *const gBattleStringsTable[STRINGID_COUNT] = [STRINGID_ICEBODYHPGAIN] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX}'s {B_ATK_ABILITY} healed it a little bit!"), //don't think this message is displayed anymore [STRINGID_SNOWWARNINGHAIL] = COMPOUND_STRING("It started to hail!"), [STRINGID_FRISKACTIVATES] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} frisked {B_DEF_NAME_WITH_PREFIX2} and found its {B_LAST_ITEM}!"), - [STRINGID_UNNERVEENTERS] = COMPOUND_STRING("{B_EFF_TEAM1} team is too nervous to eat Berries!"), + [STRINGID_UNNERVEENTERS] = COMPOUND_STRING("{B_EFF2_TEAM1} team is too nervous to eat Berries!"), [STRINGID_HARVESTBERRY] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} harvested its {B_LAST_ITEM}!"), [STRINGID_MAGICBOUNCEACTIVATES] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX} bounced the {B_ATK_NAME_WITH_PREFIX2} back!"), [STRINGID_PROTEANTYPECHANGE] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX}'s {B_ATK_ABILITY} transformed it into the {B_BUFF1} type!"), @@ -619,7 +623,7 @@ const u8 *const gBattleStringsTable[STRINGID_COUNT] = [STRINGID_HEALINGWISHCAMETRUE] = COMPOUND_STRING("The healing wish came true for {B_ATK_NAME_WITH_PREFIX2}!"), [STRINGID_HEALINGWISHHEALED] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} regained health!"), [STRINGID_LUNARDANCECAMETRUE] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} became cloaked in mystical moonlight!"), - [STRINGID_CURSEDBODYDISABLED] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX}'s {B_BUFF1} was disabled by {B_DEF_NAME_WITH_PREFIX2}'s {B_DEF_ABILITY}!"), + [STRINGID_CURSEDBODYDISABLED] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX}'s {B_BUFF3} was disabled by {B_DEF_NAME_WITH_PREFIX2}'s {B_DEF_ABILITY}!"), [STRINGID_ATTACKERACQUIREDABILITY] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} acquired {B_ATK_ABILITY}!"), [STRINGID_TARGETABILITYSTATLOWER] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX}'s {B_DEF_ABILITY} {B_BUFF2}lowered its {B_BUFF1}!"), [STRINGID_TARGETSTATWONTGOHIGHER] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX}'s {B_BUFF1} won't go any higher!"), @@ -679,7 +683,7 @@ const u8 *const gBattleStringsTable[STRINGID_COUNT] = [STRINGID_ILLUSIONWOREOFF] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s illusion wore off!"), [STRINGID_ATTACKERCUREDTARGETSTATUS] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} cured {B_DEF_NAME_WITH_PREFIX2}'s problem!"), [STRINGID_ATTACKERLOSTFIRETYPE] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} burned itself out!"), - [STRINGID_HEALERCURE] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX}'s {B_LAST_ABILITY} cured {B_SCR_NAME_WITH_PREFIX2}'s problem!"), + [STRINGID_HEALERCURE] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX}'s {B_LAST_ABILITY} cured {B_ATK_PARTNER_NAME_WITH_PREFIX}'s problem!"), [STRINGID_SCRIPTINGABILITYSTATRAISE] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_SCR_ABILITY} {B_BUFF2}raised its {B_BUFF1}!"), [STRINGID_RECEIVERABILITYTAKEOVER] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_SCR_ABILITY} was taken over!"), [STRINGID_PKNMABSORBINGPOWER] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} is absorbing power!"), @@ -769,7 +773,10 @@ const u8 *const gBattleStringsTable[STRINGID_COUNT] = [STRINGID_SUNLIGHTACTIVATEDABILITY] = COMPOUND_STRING("The harsh sunlight activated {B_SCR_NAME_WITH_PREFIX2}'s Protosynthesis!"), [STRINGID_STATWASHEIGHTENED] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_BUFF1} was heightened!"), [STRINGID_ELECTRICTERRAINACTIVATEDABILITY] = COMPOUND_STRING("The Electric Terrain activated {B_SCR_NAME_WITH_PREFIX2}'s Quark Drive!"), - [STRINGID_ABILITYWEAKENEDSURROUNDINGMONSSTAT] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_SCR_ABILITY} weakened the {B_BUFF1} of all surrounding Pokémon!\p"), + [STRINGID_ABILITYWEAKENEDSURROUNDINGMONSSPATK] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_SCR_ABILITY} weakened the Sp. Atk of all surrounding Pokémon!\p"), + [STRINGID_ABILITYWEAKENEDSURROUNDINGMONSDEF] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_SCR_ABILITY} weakened the Defense of all surrounding Pokémon!\p"), + [STRINGID_ABILITYWEAKENEDSURROUNDINGMONSATK] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_SCR_ABILITY} weakened the Attack of all surrounding Pokémon!\p"), + [STRINGID_ABILITYWEAKENEDSURROUNDINGMONSSPDEF] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_SCR_ABILITY} weakened the Sp. Def of all surrounding Pokémon!\p"), [STRINGID_ATTACKERGAINEDSTRENGTHFROMTHEFALLEN] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX} gained strength from the fallen!"), [STRINGID_PKMNSABILITYPREVENTSABILITY] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_SCR_ABILITY} prevents {B_DEF_NAME_WITH_PREFIX2}'s {B_DEF_ABILITY} from working!"), //not in gen 5+, ability popup [STRINGID_PREPARESHELLTRAP] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} set a shell trap!"), @@ -2744,6 +2751,9 @@ u32 BattleStringExpandPlaceholders(const u8 *src, u8 *dst, u32 dstSize) case B_TXT_ATK_NAME_WITH_PREFIX: // attacker name with prefix HANDLE_NICKNAME_STRING_CASE(gBattlerAttacker) break; + case B_TXT_ATK_PARTNER_NAME_WITH_PREFIX: // attacker partner name with prefix + HANDLE_NICKNAME_STRING_LOWERCASE(BATTLE_PARTNER(gBattleScripting.battler)) + break; case B_TXT_DEF_NAME_WITH_PREFIX: // target name with prefix HANDLE_NICKNAME_STRING_CASE(gBattlerTarget) break; @@ -2823,19 +2833,19 @@ u32 BattleStringExpandPlaceholders(const u8 *src, u8 *dst, u32 dstSize) } break; case B_TXT_LAST_ABILITY: // last used ability - toCpy = gAbilitiesInfo[gLastUsedAbility].name; + toCpy = gAbilitiesInfo[gDisplayAbility].name; break; case B_TXT_ATK_ABILITY: // attacker ability - toCpy = gAbilitiesInfo[sBattlerAbilities[gBattlerAttacker]].name; + toCpy = gAbilitiesInfo[gDisplayAbility].name; break; case B_TXT_DEF_ABILITY: // target ability - toCpy = gAbilitiesInfo[sBattlerAbilities[gBattlerTarget]].name; + toCpy = gAbilitiesInfo[gDisplayAbility].name; break; case B_TXT_SCR_ACTIVE_ABILITY: // scripting active ability - toCpy = gAbilitiesInfo[sBattlerAbilities[gBattleScripting.battler]].name; + toCpy = gAbilitiesInfo[gDisplayAbility].name; break; case B_TXT_EFF_ABILITY: // effect battler ability - toCpy = gAbilitiesInfo[sBattlerAbilities[gEffectBattler]].name; + toCpy = gAbilitiesInfo[gDisplayAbility].name; break; case B_TXT_TRAINER1_CLASS: // trainer class name toCpy = BattleStringGetOpponentClassByTrainerId(TRAINER_BATTLE_PARAM.opponentA); @@ -3159,6 +3169,18 @@ u32 BattleStringExpandPlaceholders(const u8 *src, u8 *dst, u32 dstSize) else toCpy = sText_Opposing2; break; + case B_TXT_EFF2_TEAM1: + if (IsOnPlayerSide(gEffectBattler2)) + toCpy = sText_Your1; + else + toCpy = sText_Opposing1; + break; + case B_TXT_EFF2_TEAM2: + if (IsOnPlayerSide(gEffectBattler2)) + toCpy = sText_Your2; + else + toCpy = sText_Opposing2; + break; case B_TXT_ATK_NAME_WITH_PREFIX2: HANDLE_NICKNAME_STRING_LOWERCASE(gBattlerAttacker) break; @@ -3217,7 +3239,7 @@ static void IllusionNickHack(u32 battler, u32 partyId, u8 *dst) // we know it's gEnemyParty struct Pokemon *mon = &gEnemyParty[partyId], *partnerMon; - if (GetMonAbility(mon) == ABILITY_ILLUSION) + if (MonHasTrait(mon, ABILITY_ILLUSION)) { if (IsBattlerAlive(BATTLE_PARTNER(battler))) partnerMon = GetBattlerMon(BATTLE_PARTNER(battler)); diff --git a/src/battle_pike.c b/src/battle_pike.c index 199a5eea5036..8624923dd6f4 100644 --- a/src/battle_pike.c +++ b/src/battle_pike.c @@ -5,6 +5,7 @@ #include "fieldmap.h" #include "save.h" #include "battle.h" +#include "battle_ai_main.h" #include "random.h" #include "task.h" #include "battle_tower.h" @@ -811,33 +812,34 @@ static void HealMon(struct Pokemon *mon) static bool8 DoesAbilityPreventStatus(struct Pokemon *mon, u32 status) { - enum Ability ability = GetMonAbility(mon); bool8 ret = FALSE; + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(gBattlerTarget); - if (ability == ABILITY_COMATOSE) + if (SearchTraits(battlerTraits, ABILITY_COMATOSE)) return TRUE; switch (status) { case STATUS1_FREEZE: case STATUS1_FROSTBITE: - if (ability == ABILITY_MAGMA_ARMOR) + if (SearchTraits(battlerTraits, ABILITY_MAGMA_ARMOR)) ret = TRUE; break; case STATUS1_BURN: - if (ability == ABILITY_WATER_VEIL || ability == ABILITY_WATER_BUBBLE) + if (SearchTraits(battlerTraits, ABILITY_WATER_VEIL) || SearchTraits(battlerTraits, ABILITY_WATER_BUBBLE)) ret = TRUE; break; case STATUS1_PARALYSIS: - if (ability == ABILITY_LIMBER) + if (SearchTraits(battlerTraits, ABILITY_LIMBER)) ret = TRUE; break; case STATUS1_SLEEP: - if (ability == ABILITY_INSOMNIA || ability == ABILITY_VITAL_SPIRIT) + if (SearchTraits(battlerTraits, ABILITY_INSOMNIA) || SearchTraits(battlerTraits, ABILITY_VITAL_SPIRIT)) ret = TRUE; break; case STATUS1_TOXIC_POISON: - if (ability == ABILITY_IMMUNITY || ability == ABILITY_PASTEL_VEIL) + if (SearchTraits(battlerTraits, ABILITY_IMMUNITY) || SearchTraits(battlerTraits, ABILITY_PASTEL_VEIL)) ret = TRUE; break; } @@ -1623,8 +1625,8 @@ static bool8 CanEncounterWildMon(u8 enemyMonLevel) { if (!GetMonData(&gPlayerParty[0], MON_DATA_SANITY_IS_EGG)) { - enum Ability monAbility = GetMonAbility(&gPlayerParty[0]); - if (monAbility == ABILITY_KEEN_EYE || monAbility == ABILITY_INTIMIDATE) + if (MonHasTrait(&gPlayerParty[0], ABILITY_KEEN_EYE) + || MonHasTrait(&gPlayerParty[0], ABILITY_INTIMIDATE)) { u8 playerMonLevel = GetMonData(&gPlayerParty[0], MON_DATA_LEVEL); if (playerMonLevel > 5 && enemyMonLevel <= playerMonLevel - 5 && Random() % 2 == 0) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 759fcaee9281..4a34650d778a 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -349,7 +349,7 @@ static bool32 SetTargetToNextPursuiter(u32 battlerDef); void ApplyExperienceMultipliers(s32 *expAmount, u8 expGetterMonId, u8 faintedBattler); static void RemoveAllWeather(void); static void RemoveAllTerrains(void); -static bool32 CanAbilityPreventStatLoss(enum Ability abilityDef); +static bool8 CanBattlerPreventStatLoss(u16 battler); static u32 GetNextTarget(u32 moveTarget, bool32 excludeCurrent); static void TryUpdateEvolutionTracker(u32 evolutionCondition, u32 upAmount, u16 usedMove); static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u8 *failInstr, u16 move); @@ -1019,15 +1019,20 @@ static bool32 NoTargetPresent(u8 battler, u32 move) return FALSE; } -bool32 ProteanTryChangeType(u32 battler, enum Ability ability, u32 move, enum Type moveType) +bool32 ProteanTryChangeType(u32 battler, u32 move, enum Type moveType) { - if ((ability == ABILITY_PROTEAN || ability == ABILITY_LIBERO) + if (((gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battler, ABILITY_PROTEAN) : BattlerHasTrait(battler, ABILITY_PROTEAN)) + || (gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battler, ABILITY_LIBERO) : BattlerHasTrait(battler, ABILITY_LIBERO))) && !gDisableStructs[gBattlerAttacker].usedProteanLibero && (gBattleMons[battler].types[0] != moveType || gBattleMons[battler].types[1] != moveType || (gBattleMons[battler].types[2] != moveType && gBattleMons[battler].types[2] != TYPE_MYSTERY)) && move != MOVE_STRUGGLE && GetActiveGimmick(battler) != GIMMICK_TERA) { + if (BattlerHasTrait(battler, ABILITY_PROTEAN)) + PushTraitStack(battler, ABILITY_PROTEAN); + else if (BattlerHasTrait(battler, ABILITY_LIBERO)) + PushTraitStack(battler, ABILITY_LIBERO); SET_BATTLER_TYPE(battler, moveType); return TRUE; } @@ -1103,7 +1108,7 @@ bool32 IsPowderMoveBlocked(struct BattleContext *ctx) { if (!IsPowderMove(ctx->currentMove) || ctx->battlerAtk == ctx->battlerDef - || IsAffectedByPowderMove(ctx->battlerDef, ctx->abilities[ctx->battlerDef], GetBattlerHoldEffect(ctx->battlerDef))) + || IsAffectedByPowderMove(ctx->battlerDef, GetBattlerHoldEffect(ctx->battlerDef))) return FALSE; gBattlescriptCurrInstr = BattleScript_PowderMoveNoEffect; @@ -1112,9 +1117,14 @@ bool32 IsPowderMoveBlocked(struct BattleContext *ctx) bool32 EmergencyExitCanBeTriggered(u32 battler) { - enum Ability ability = GetBattlerAbility(battler); + enum Ability ability = ABILITY_NONE; + + if (BattlerHasTrait(battler, ABILITY_EMERGENCY_EXIT)) + ability = ABILITY_EMERGENCY_EXIT; + else if (BattlerHasTrait(battler, ABILITY_WIMP_OUT)) + ability = ABILITY_WIMP_OUT; - if (ability != ABILITY_EMERGENCY_EXIT && ability != ABILITY_WIMP_OUT) + if (ability == ABILITY_NONE) return FALSE; if (IsBattlerAlive(battler) @@ -1122,8 +1132,12 @@ bool32 EmergencyExitCanBeTriggered(u32 battler) && (CanBattlerSwitch(battler) || !(gBattleTypeFlags & BATTLE_TYPE_TRAINER)) && !(gBattleTypeFlags & BATTLE_TYPE_ARENA) && gBattleMons[battler].volatiles.semiInvulnerable != STATE_SKY_DROP) + { + gLastUsedAbility = ability; + PushTraitStack(battler, ability); return TRUE; - + } + return FALSE; } @@ -1177,7 +1191,7 @@ static void Cmd_attackcanceler(void) return; if (gSpecialStatuses[gBattlerAttacker].parentalBondState == PARENTAL_BOND_OFF - && ctx.abilities[ctx.battlerAtk] == ABILITY_PARENTAL_BOND + && BattlerHasTrait(ctx.battlerAtk, ABILITY_PARENTAL_BOND) && IsMoveAffectedByParentalBond(gCurrentMove, gBattlerAttacker) && !(gAbsentBattlerFlags & (1u << gBattlerTarget)) && GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE) @@ -1191,8 +1205,6 @@ static void Cmd_attackcanceler(void) if (CanAbilityBlockMove( ctx.battlerAtk, ctx.battlerDef, - ctx.abilities[ctx.battlerAtk], - ctx.abilities[ctx.battlerDef], ctx.currentMove, RUN_SCRIPT)) return; @@ -1202,7 +1214,6 @@ static void Cmd_attackcanceler(void) if (CanAbilityAbsorbMove( ctx.battlerAtk, ctx.battlerDef, - ctx.abilities[ctx.battlerDef], ctx.currentMove, GetBattleMoveType(ctx.currentMove), RUN_SCRIPT)) @@ -1272,14 +1283,14 @@ static void Cmd_attackcanceler(void) { u32 battler = gBattlerTarget; - if (ctx.abilities[ctx.battlerDef] == ABILITY_MAGIC_BOUNCE) + if (BattlerHasTrait(ctx.battlerDef, ABILITY_MAGIC_BOUNCE)) { battler = gBattlerTarget; gBattleStruct->bouncedMoveIsUsed = TRUE; } else if (IsDoubleBattle() && GetBattlerMoveTargetType(battler, gCurrentMove) == MOVE_TARGET_OPPONENTS_FIELD - && GetBattlerAbility(BATTLE_PARTNER(gBattlerTarget)) == ABILITY_MAGIC_BOUNCE) + && BattlerHasTrait(BATTLE_PARTNER(gBattlerTarget), ABILITY_MAGIC_BOUNCE)) { gBattlerTarget = battler = BATTLE_PARTNER(gBattlerTarget); gBattleStruct->bouncedMoveIsUsed = TRUE; @@ -1287,6 +1298,7 @@ static void Cmd_attackcanceler(void) if (gBattleStruct->bouncedMoveIsUsed) { + PushTraitStack(gBattlerTarget, ABILITY_MAGIC_BOUNCE); BattleScriptCall(BattleScript_MagicBounce); gBattlerAbility = battler; return; @@ -1316,17 +1328,30 @@ static void Cmd_attackcanceler(void) } } + // Only one redirection needs to activate per pokemon if (gSpecialStatuses[gBattlerTarget].abilityRedirected) { - gSpecialStatuses[gBattlerTarget].abilityRedirected = FALSE; - BattleScriptCall(BattleScript_TookAttack); + if (BattlerHasTrait(gBattlerTarget, ABILITY_LIGHTNING_ROD)) + { + gSpecialStatuses[gBattlerTarget].abilityRedirected = FALSE; + gDisplayAbility = ABILITY_LIGHTNING_ROD; + BattleScriptCall(BattleScript_TookAttack); + RecordAbilityBattle(gBattlerTarget, ABILITY_LIGHTNING_ROD); + } + else if (BattlerHasTrait(gBattlerTarget, ABILITY_STORM_DRAIN)) + { + gSpecialStatuses[gBattlerTarget].abilityRedirected = FALSE; + gDisplayAbility = ABILITY_STORM_DRAIN; + BattleScriptCall(BattleScript_TookAttack); + RecordAbilityBattle(gBattlerTarget, ABILITY_STORM_DRAIN); + } } else if (IsBattlerProtected(gBattlerAttacker, gBattlerTarget, gCurrentMove) && (moveEffect != EFFECT_CURSE || IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_GHOST)) && (!gBattleMoveEffects[moveEffect].twoTurnEffect || (gBattleMons[gBattlerAttacker].volatiles.multipleTurns)) && moveEffect != EFFECT_COUNTER) { - if (!CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), gCurrentMove)) + if (!CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerHoldEffect(gBattlerAttacker), gCurrentMove)) gProtectStructs[gBattlerAttacker].touchedProtectLike = TRUE; CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_ATTACKCANCELER_CHECK); gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED; @@ -1343,7 +1368,7 @@ static void Cmd_attackcanceler(void) gBattlescriptCurrInstr = cmd->nextInstr; } else if (IsBattlerUsingBeakBlast(gBattlerTarget) - && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), gCurrentMove)) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerHoldEffect(gBattlerAttacker), gCurrentMove)) { gProtectStructs[gBattlerAttacker].touchedProtectLike = TRUE; gBattlescriptCurrInstr = cmd->nextInstr; @@ -1367,7 +1392,6 @@ static void JumpIfMoveFailed(u32 adder, u32 move, u32 moveType, const u8 *failIn { if (CanAbilityAbsorbMove(gBattlerAttacker, gBattlerTarget, - GetBattlerAbility(gBattlerTarget), move, moveType, RUN_SCRIPT)) @@ -1442,7 +1466,6 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u else CanAbilityAbsorbMove(gBattlerAttacker, gBattlerTarget, - GetBattlerAbility(gBattlerTarget), gCurrentMove, GetBattleMoveType(gCurrentMove), RUN_SCRIPT); @@ -1450,13 +1473,12 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u return; } - enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker); enum HoldEffect holdEffectAtk = GetBattlerHoldEffect(gBattlerAttacker); enum BattleMoveEffects effect = GetMoveEffect(move); if (gSpecialStatuses[gBattlerAttacker].parentalBondState == PARENTAL_BOND_2ND_HIT || (gSpecialStatuses[gBattlerAttacker].multiHitOn - && (abilityAtk == ABILITY_SKILL_LINK || holdEffectAtk == HOLD_EFFECT_LOADED_DICE + && (BattlerHasTrait(gBattlerAttacker, ABILITY_SKILL_LINK) || holdEffectAtk == HOLD_EFFECT_LOADED_DICE || !(effect == EFFECT_TRIPLE_KICK || effect == EFFECT_POPULATION_BOMB)))) { // No acc checks for second hit of Parental Bond or multi hit moves, except Triple Kick/Triple Axel/Population Bomb @@ -1482,17 +1504,14 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u continue; numTargets++; - enum Ability abilityDef = GetBattlerAbility(battlerDef); if (JumpIfMoveAffectedByProtect(move, battlerDef, FALSE, failInstr) - || CanMoveSkipAccuracyCalc(gBattlerAttacker, battlerDef, abilityAtk, abilityDef, move, RUN_SCRIPT)) + || CanMoveSkipAccuracyCalc(gBattlerAttacker, battlerDef, move, RUN_SCRIPT)) continue; u32 holdEffectDef = GetBattlerHoldEffect(battlerDef); u32 accuracy = GetTotalAccuracy(gBattlerAttacker, battlerDef, move, - abilityAtk, - abilityDef, holdEffectAtk, holdEffectDef); @@ -1527,8 +1546,6 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u ctx.chosenMove = gChosenMove; ctx.moveType = moveType; ctx.updateFlags = TRUE; - ctx.abilityAtk = abilityAtk; - ctx.abilityDef = abilityDef; ctx.holdEffectAtk = holdEffectAtk; ctx.holdEffectDef = holdEffectDef; @@ -1629,9 +1646,10 @@ static inline u32 GetHoldEffectCritChanceIncrease(u32 battler, enum HoldEffect h return critStageIncrease; } -s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, enum Ability abilityAtk, enum Ability abilityDef, enum HoldEffect holdEffectAtk) +s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, enum HoldEffect holdEffectAtk) { s32 critChance = 0; + enum Ability abilityDef = ABILITY_NONE; if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_LUCKY_CHANT) { @@ -1639,7 +1657,8 @@ s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordA } else if (gBattleMons[battlerAtk].volatiles.laserFocus || MoveAlwaysCrits(move) - || (abilityAtk == ABILITY_MERCILESS && gBattleMons[battlerDef].status1 & STATUS1_PSN_ANY)) + || ((gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_MERCILESS) : BattlerHasTrait(battlerAtk, ABILITY_MERCILESS)) + && gBattleMons[battlerDef].status1 & STATUS1_PSN_ANY)) { critChance = CRITICAL_HIT_ALWAYS; } @@ -1650,14 +1669,18 @@ s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordA + GetMoveCriticalHitStage(move) + GetHoldEffectCritChanceIncrease(battlerAtk, holdEffectAtk) + ((B_AFFECTION_MECHANICS == TRUE && GetBattlerAffectionHearts(battlerAtk) == AFFECTION_FIVE_HEARTS) ? 2 : 0) - + (abilityAtk == ABILITY_SUPER_LUCK ? 1 : 0) + + ((gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_SUPER_LUCK) : BattlerHasTrait(battlerAtk, ABILITY_SUPER_LUCK)) ? 1 : 0) + gBattleMons[battlerAtk].volatiles.bonusCritStages; - if (critChance >= ARRAY_COUNT(sCriticalHitOdds)) critChance = ARRAY_COUNT(sCriticalHitOdds) - 1; } - if (critChance != CRITICAL_HIT_BLOCKED && (abilityDef == ABILITY_BATTLE_ARMOR || abilityDef == ABILITY_SHELL_ARMOR)) + if (gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_BATTLE_ARMOR) : BattlerHasTrait(battlerDef, ABILITY_BATTLE_ARMOR)) + abilityDef = ABILITY_BATTLE_ARMOR; + else if (gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_SHELL_ARMOR) : BattlerHasTrait(battlerDef, ABILITY_SHELL_ARMOR)) + abilityDef = ABILITY_SHELL_ARMOR; + + if (critChance != CRITICAL_HIT_BLOCKED && abilityDef) { // Record ability only if move had 100% chance to get a crit if (recordAbility) @@ -1678,9 +1701,10 @@ s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordA // Threshold = Base Speed / 2 // High crit move = 8 * (Base Speed / 2) // Focus Energy = 4 * (Base Speed / 2) -s32 CalcCritChanceStageGen1(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, enum Ability abilityAtk, enum Ability abilityDef, enum HoldEffect holdEffectAtk) +s32 CalcCritChanceStageGen1(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, enum HoldEffect holdEffectAtk) { s32 critChance = 0; + enum Ability abilityDef = ABILITY_NONE; s32 moveCritStage = GetMoveCriticalHitStage(gCurrentMove); s32 bonusCritStage = gBattleMons[battlerAtk].volatiles.bonusCritStages; // G-Max Chi Strike u32 holdEffectCritStage = GetHoldEffectCritChanceIncrease(battlerAtk, holdEffectAtk); @@ -1703,16 +1727,21 @@ s32 CalcCritChanceStageGen1(u32 battlerAtk, u32 battlerDef, u32 move, bool32 rec if (holdEffectCritStage > 0) critChance *= 4 * holdEffectCritStage; - if (abilityAtk == ABILITY_SUPER_LUCK) + if (gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_SUPER_LUCK) : BattlerHasTrait(battlerAtk, ABILITY_SUPER_LUCK)) critChance *= 4; if (critChance > 255) critChance = 255; + if (gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_BATTLE_ARMOR) : BattlerHasTrait(battlerDef, ABILITY_BATTLE_ARMOR)) + abilityDef = ABILITY_BATTLE_ARMOR; + else if (gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_SHELL_ARMOR) : BattlerHasTrait(battlerDef, ABILITY_SHELL_ARMOR)) + abilityDef = ABILITY_SHELL_ARMOR; + // Prevented crits if (gSideStatuses[battlerDef] & SIDE_STATUS_LUCKY_CHANT) critChance = CRITICAL_HIT_BLOCKED; - else if (abilityDef == ABILITY_BATTLE_ARMOR || abilityDef == ABILITY_SHELL_ARMOR) + else if (abilityDef) { if (recordAbility) RecordAbilityBattle(battlerDef, abilityDef); @@ -1722,7 +1751,7 @@ s32 CalcCritChanceStageGen1(u32 battlerAtk, u32 battlerDef, u32 move, bool32 rec // Guaranteed crits else if (gBattleMons[battlerAtk].volatiles.laserFocus || MoveAlwaysCrits(move) - || (abilityAtk == ABILITY_MERCILESS && gBattleMons[battlerDef].status1 & STATUS1_PSN_ANY)) + || (BattlerHasTrait(battlerAtk, ABILITY_MERCILESS) && gBattleMons[battlerDef].status1 & STATUS1_PSN_ANY)) { critChance = CRITICAL_HIT_ALWAYS; } @@ -1762,13 +1791,10 @@ static void Cmd_critcalc(void) || gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_NO_EFFECT) continue; - enum Ability abilityDef = GetBattlerAbility(battlerDef); - enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker); - if (GetConfig(CONFIG_CRIT_CHANCE) == GEN_1) - gBattleStruct->critChance[battlerDef] = CalcCritChanceStageGen1(gBattlerAttacker, battlerDef, gCurrentMove, TRUE, abilityAtk, abilityDef, holdEffectAtk); + gBattleStruct->critChance[battlerDef] = CalcCritChanceStageGen1(gBattlerAttacker, battlerDef, gCurrentMove, TRUE, holdEffectAtk); else - gBattleStruct->critChance[battlerDef] = CalcCritChanceStage(gBattlerAttacker, battlerDef, gCurrentMove, TRUE, abilityAtk, abilityDef, holdEffectAtk); + gBattleStruct->critChance[battlerDef] = CalcCritChanceStage(gBattlerAttacker, battlerDef, gCurrentMove, TRUE, holdEffectAtk); if (gBattleTypeFlags & (BATTLE_TYPE_WALLY_TUTORIAL | BATTLE_TYPE_FIRST_BATTLE)) gSpecialStatuses[battlerDef].criticalHit = FALSE; @@ -1866,8 +1892,6 @@ static void Cmd_typecalc(void) ctx.chosenMove = gChosenMove; ctx.moveType = GetBattleMoveType(gCurrentMove); ctx.updateFlags = TRUE; - ctx.abilityAtk = GetBattlerAbility(gBattlerAttacker); - ctx.abilityDef = GetBattlerAbility(gBattlerTarget); ctx.holdEffectAtk = GetBattlerHoldEffect(gBattlerAttacker); ctx.holdEffectDef = GetBattlerHoldEffect(gBattlerTarget); @@ -1912,7 +1936,7 @@ static void Cmd_adjustdamage(void) continue; } - if (GetBattlerAbility(battlerDef) == ABILITY_ICE_FACE && IsBattleMovePhysical(gCurrentMove) && gBattleMons[battlerDef].species == SPECIES_EISCUE) + if (BattlerHasTrait(battlerDef, ABILITY_ICE_FACE) && IsBattleMovePhysical(gCurrentMove) && gBattleMons[battlerDef].species == SPECIES_EISCUE) { // Damage deals typeless 0 HP. gBattleStruct->moveResultFlags[battlerDef] &= ~(MOVE_RESULT_SUPER_EFFECTIVE | MOVE_RESULT_NOT_VERY_EFFECTIVE); @@ -1949,9 +1973,10 @@ static void Cmd_adjustdamage(void) gLastUsedItem = gBattleMons[battlerDef].item; gBattleStruct->moveResultFlags[battlerDef] |= MOVE_RESULT_FOE_HUNG_ON; } - else if (GetConfig(CONFIG_STURDY) >= GEN_5 && GetBattlerAbility(battlerDef) == ABILITY_STURDY && IsBattlerAtMaxHp(battlerDef)) + else if (GetConfig(CONFIG_STURDY) >= GEN_5 && BattlerHasTrait(battlerDef, ABILITY_STURDY) && IsBattlerAtMaxHp(battlerDef)) { enduredHit |= 1u << battlerDef; + PushTraitStack(gBattlerTarget, ABILITY_STURDY); RecordAbilityBattle(battlerDef, ABILITY_STURDY); gLastUsedAbility = ABILITY_STURDY; gBattleStruct->moveResultFlags[battlerDef] |= MOVE_RESULT_STURDIED; @@ -2029,13 +2054,12 @@ static void Cmd_multihitresultmessage(void) static inline bool32 DoesBattlerNegateDamage(u32 battler) { u32 species = gBattleMons[battler].species; - enum Ability ability = GetBattlerAbility(battler); if (gBattleMons[battler].volatiles.transformed) return FALSE; - if (ability == ABILITY_DISGUISE && species == SPECIES_MIMIKYU) + if (BattlerHasTrait(battler, ABILITY_DISGUISE) && species == SPECIES_MIMIKYU) return TRUE; - if (ability == ABILITY_ICE_FACE && species == SPECIES_EISCUE && GetBattleMoveCategory(gCurrentMove) == DAMAGE_CATEGORY_PHYSICAL) + if (BattlerHasTrait(battler, ABILITY_ICE_FACE) && species == SPECIES_EISCUE && GetBattleMoveCategory(gCurrentMove) == DAMAGE_CATEGORY_PHYSICAL) return TRUE; return FALSE; @@ -2099,6 +2123,7 @@ static inline bool32 TryTeraShellDistortTypeMatchups(u32 battlerDef) { gSpecialStatuses[battlerDef].teraShellAbilityDone = FALSE; gBattleScripting.battler = battlerDef; + PushTraitStack(gBattlerTarget, ABILITY_TERA_SHELL); BattleScriptCall(BattleScript_TeraShellDistortingTypeMatchups); return TRUE; } @@ -2619,6 +2644,7 @@ static void Cmd_resultmessage(void) GetBattlerPartyState(gBattlerTarget)->changedSpecies = gBattleMons[gBattlerTarget].species; gBattleMons[gBattlerTarget].species = SPECIES_EISCUE_NOICE; gBattleScripting.battler = gBattlerTarget; // For STRINGID_PKMNTRANSFORMED + PushTraitStack(gBattlerTarget, ABILITY_ICE_FACE); BattleScriptCall(BattleScript_IceFaceNullsDamage); return; } @@ -2872,7 +2898,7 @@ u32 GetBattlerTurnOrderNum(u32 battler) static void CheckSetUnburden(u8 battler) { - if (IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_UNBURDEN)) + if (IsAbilityAndRecord(battler, ABILITY_UNBURDEN)) gDisableStructs[battler].unburdenActive = TRUE; } @@ -2905,7 +2931,7 @@ void StealTargetItem(u8 battlerStealer, u8 itemBattler) BtlController_EmitSetMonData(itemBattler, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[itemBattler].item), &gBattleMons[itemBattler].item); // remove target item MarkBattlerForControllerExec(itemBattler); - if (GetBattlerAbility(itemBattler) != ABILITY_GORILLA_TACTICS) + if (!BattlerHasTrait(itemBattler, ABILITY_GORILLA_TACTICS)) gBattleStruct->choicedMove[itemBattler] = MOVE_NONE; TrySaveExchangedItem(itemBattler, gLastUsedItem); @@ -3036,7 +3062,7 @@ void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, c bool32 primary = effectFlags & EFFECT_PRIMARY; bool32 certain = effectFlags & EFFECT_CERTAIN; bool32 affectsUser = (battler == effectBattler); - bool32 mirrorArmorReflected = (GetBattlerAbility(gBattlerTarget) == ABILITY_MIRROR_ARMOR); + bool32 mirrorArmorReflected = (BattlerHasTrait(gBattlerTarget, ABILITY_MIRROR_ARMOR)); union StatChangeFlags flags = {0}; u32 battlerAbility; bool32 activateAfterFaint = FALSE; @@ -3069,10 +3095,10 @@ void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, c gEffectBattler = effectBattler; battlerAbility = GetBattlerAbility(gEffectBattler); - if (!primary && !affectsUser && IsMoveEffectBlockedByTarget(battlerAbility)) + if (!primary && !affectsUser && IsMoveEffectBlockedByTarget()) moveEffect = MOVE_EFFECT_NONE; else if (!primary - && IsSheerForceAffected(gCurrentMove, GetBattlerAbility(battler)) + && IsSheerForceAffected(gCurrentMove, battler) && !(GetMoveEffect(gCurrentMove) == EFFECT_ORDER_UP && gBattleStruct->battlerState[gBattlerAttacker].commanderSpecies != SPECIES_NONE)) moveEffect = MOVE_EFFECT_NONE; else if (!IsBattlerAlive(gEffectBattler) && !activateAfterFaint) @@ -3080,6 +3106,9 @@ void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, c else if (DoesSubstituteBlockMove(gBattlerAttacker, gEffectBattler, gCurrentMove) && !affectsUser) moveEffect = MOVE_EFFECT_NONE; + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(gEffectBattler); + gBattleScripting.moveEffect = moveEffect; // ChangeStatBuffs still needs the global moveEffect switch (moveEffect) @@ -3094,13 +3123,11 @@ void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, c case MOVE_EFFECT_PARALYSIS: case MOVE_EFFECT_TOXIC: case MOVE_EFFECT_FROSTBITE: - if (IsSafeguardProtected(gBattlerAttacker, gEffectBattler, GetBattlerAbility(gBattlerAttacker)) && !primary) + if (IsSafeguardProtected(gBattlerAttacker, gEffectBattler) && !primary) gBattlescriptCurrInstr = battleScript; else if (CanSetNonVolatileStatus( gBattlerAttacker, gEffectBattler, - GetBattlerAbility(gBattlerAttacker), - battlerAbility, moveEffect, CHECK_TRIGGER)) SetNonVolatileStatus(gEffectBattler, moveEffect, battleScript, TRIGGER_ON_MOVE); @@ -3108,7 +3135,7 @@ void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, c case MOVE_EFFECT_CONFUSION: if (!CanBeConfused(gEffectBattler) || gBattleMons[gEffectBattler].volatiles.confusionTurns - || (IsSafeguardProtected(gBattlerAttacker, gEffectBattler, GetBattlerAbility(gBattlerAttacker)) && !primary)) + || (IsSafeguardProtected(gBattlerAttacker, gEffectBattler) && !primary)) { gBattlescriptCurrInstr = battleScript; } @@ -3132,13 +3159,14 @@ void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, c } break; case MOVE_EFFECT_FLINCH: - if (battlerAbility == ABILITY_INNER_FOCUS) + if (SearchTraits(battlerTraits, ABILITY_INNER_FOCUS)) { // Inner Focus ALWAYS prevents flinching but only activates // on a move that's supposed to flinch, like Fake Out if (primary || certain) { gLastUsedAbility = ABILITY_INNER_FOCUS; + PushTraitStack(gEffectBattler, ABILITY_INNER_FOCUS); gBattlerAbility = gEffectBattler; RecordAbilityBattle(gEffectBattler, ABILITY_INNER_FOCUS); BattleScriptPush(battleScript); @@ -3408,7 +3436,7 @@ void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, c s32 recoil = (gBattleMons[gEffectBattler].maxHP) / 4; if (recoil == 0) recoil = 1; - if (GetBattlerAbility(gEffectBattler) == ABILITY_PARENTAL_BOND) + if (SearchTraits(battlerTraits, ABILITY_PARENTAL_BOND)) recoil *= 2; SetPassiveDamageAmount(gEffectBattler, recoil); TryUpdateEvolutionTracker(IF_RECOIL_DAMAGE_GE, gBattleStruct->passiveHpUpdate[gBattlerAttacker], MOVE_NONE); @@ -3446,7 +3474,7 @@ void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, c case MOVE_EFFECT_FLAME_BURST: if (IsBattlerAlive(BATTLE_PARTNER(gBattlerTarget)) && !IsSemiInvulnerable(BATTLE_PARTNER(gBattlerTarget), CHECK_ALL) - && GetBattlerAbility(BATTLE_PARTNER(gBattlerTarget)) != ABILITY_MAGIC_GUARD) + && !BattlerHasTrait(BATTLE_PARTNER(gBattlerTarget), ABILITY_MAGIC_GUARD)) { u32 partnerTarget = BATTLE_PARTNER(gBattlerTarget); gBattleScripting.battler = partnerTarget; @@ -3499,7 +3527,7 @@ void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, c case MOVE_EFFECT_INCINERATE: if (((gBattleMons[gEffectBattler].item >= FIRST_BERRY_INDEX && gBattleMons[gEffectBattler].item <= LAST_BERRY_INDEX) || (B_INCINERATE_GEMS >= GEN_6 && GetBattlerHoldEffect(gEffectBattler) == HOLD_EFFECT_GEMS)) - && battlerAbility != ABILITY_STICKY_HOLD) + && !BattlerHasTrait(gEffectBattler, ABILITY_STICKY_HOLD)) { gLastUsedItem = gBattleMons[gEffectBattler].item; gBattleMons[gEffectBattler].item = 0; @@ -3518,7 +3546,7 @@ void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, c gBattlescriptCurrInstr = battleScript; } else if (GetItemPocket(gBattleMons[gEffectBattler].item) == POCKET_BERRIES - && battlerAbility != ABILITY_STICKY_HOLD) + && !BattlerHasTrait(gEffectBattler, ABILITY_STICKY_HOLD)) { // target loses their berry gLastUsedItem = gBattleMons[gEffectBattler].item; @@ -3629,6 +3657,7 @@ void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, c if (battlerAbility) { gBattlerAbility = battlerAbility - 1; + PushTraitStack(gBattlerAbility, ABILITY_AROMA_VEIL); BattleScriptPush(battleScript); gBattlescriptCurrInstr = BattleScript_AromaVeilProtectsRet; } @@ -3675,8 +3704,7 @@ void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, c SET_STAT_BUFF_VALUE(1), stat, STAT_CHANGE_UPDATE_MOVE_EFFECT, - 0, - 0) == STAT_CHANGE_DIDNT_WORK) + 0, 0) == STAT_CHANGE_DIDNT_WORK) { gBattlescriptCurrInstr = battleScript; } @@ -3880,7 +3908,7 @@ void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, c default: break; } - if (TryChangeBattleWeather(gBattlerAttacker, weather, ABILITY_NONE)) + if (TryChangeBattleWeather(gBattlerAttacker, weather, FALSE)) { gBattleCommunication[MULTISTRING_CHOOSER] = msg; BattleScriptPush(battleScript); @@ -4015,7 +4043,7 @@ void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, c case MOVE_EFFECT_YAWN_FOE: { if (gBattleMons[gBattlerTarget].volatiles.yawn == 0 - && CanBeSlept(gBattlerTarget, gBattlerTarget, GetBattlerAbility(gBattlerTarget), BLOCKED_BY_SLEEP_CLAUSE) + && CanBeSlept(gBattlerTarget, gBattlerTarget, BLOCKED_BY_SLEEP_CLAUSE) && RandomPercentage(RNG_G_MAX_SNOOZE, 50)) { gBattleMons[gBattlerTarget].volatiles.yawn = 2; @@ -4101,7 +4129,7 @@ void SetMoveEffect(u32 battler, u32 effectBattler, enum MoveEffect moveEffect, c { u32 argStatus = GetMoveEffectArg_Status(gCurrentMove); if ((gBattleMons[gEffectBattler].status1 & argStatus) - && (NumAffectedSpreadMoveTargets() > 1 || !IsMoveEffectBlockedByTarget(GetBattlerAbility(gEffectBattler)))) + && (NumAffectedSpreadMoveTargets() > 1 || !IsMoveEffectBlockedByTarget())) { gBattleMons[gEffectBattler].status1 &= ~(argStatus); BtlController_EmitSetMonData(gEffectBattler, 0, REQUEST_STATUS_BATTLE, 0, 4, &gBattleMons[gEffectBattler].status1); @@ -4168,11 +4196,10 @@ static void SetToxicChainPriority(void) { if (gBattleStruct->toxicChainPriority) return; - - enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker); - if (abilityAtk == ABILITY_TOXIC_CHAIN + + if (BattlerHasTrait(gBattlerAttacker, ABILITY_TOXIC_CHAIN) && IsBattlerAlive(gBattlerTarget) - && CanBePoisoned(gBattlerAttacker, gBattlerTarget, abilityAtk, GetBattlerAbility(gBattlerTarget)) + && CanBePoisoned(gBattlerAttacker, gBattlerTarget) && IsBattlerTurnDamaged(gBattlerTarget) && RandomWeighted(RNG_TOXIC_CHAIN, 7, 3)) gBattleStruct->toxicChainPriority = TRUE; @@ -4195,7 +4222,7 @@ static void Cmd_setadditionaleffects(void) // Various checks for if this move effect can be applied this turn if (CanApplyAdditionalEffect(additionalEffect)) { - percentChance = CalcSecondaryEffectChance(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), additionalEffect); + percentChance = CalcSecondaryEffectChance(gBattlerAttacker, additionalEffect); // Activate effect if it's primary (chance == 0) or if RNGesus says so if ((percentChance == 0) || RandomPercentage(RNG_SECONDARY_EFFECT + gBattleStruct->additionalEffectsCounter, percentChance)) @@ -4430,7 +4457,7 @@ static void Cmd_jumpifability(void) { default: battler = GetBattlerForBattleScript(cmd->battler); - if (GetBattlerAbility(battler) == ability) + if (BattlerHasTrait(battler, ability)) hasAbility = TRUE; break; case BS_ATTACKER_SIDE: @@ -4454,8 +4481,10 @@ static void Cmd_jumpifability(void) if (hasAbility) { gLastUsedAbility = ability; + if (ability != ABILITY_COMATOSE) // Abilities that don't need an extra popup + PushTraitStack(battler, ability); gBattlescriptCurrInstr = cmd->jumpInstr; - RecordAbilityBattle(battler, gLastUsedAbility); + RecordAbilityBattle(battler, ability); gBattlerAbility = battler; } else @@ -4486,7 +4515,7 @@ static void Cmd_jumpifstat(void) u8 value = cmd->value; u8 comparison = cmd->comparison; - ret = CompareStat(battler, stat, value, comparison, GetBattlerAbility(battler)); + ret = CompareStat(battler, stat, value, comparison); if (ret) gBattlescriptCurrInstr = cmd->jumpInstr; @@ -4504,7 +4533,7 @@ static void Cmd_jumpifstatignorecontrary(void) u8 value = cmd->value; u8 comparison = cmd->comparison; - ret = CompareStat(battler, stat, value, comparison, ABILITY_NONE); + ret = CompareStatIgnoreContrary(battler, stat, value, comparison); if (ret) gBattlescriptCurrInstr = cmd->jumpInstr; @@ -5405,12 +5434,13 @@ static void Cmd_isdmgblockedbydisguise(void) if (!IsMimikyuDisguised(gBattlerAttacker) || gBattleMons[gBattlerAttacker].volatiles.transformed - || !IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_DISGUISE)) + || !IsAbilityAndRecord(gBattlerAttacker, ABILITY_DISGUISE)) { gBattlescriptCurrInstr = cmd->nextInstr; return; } + PushTraitStack(gBattlerAttacker, ABILITY_DISGUISE); gBattleScripting.battler = gBattlerAttacker; if (GetBattlerPartyState(gBattlerAttacker)->changedSpecies == SPECIES_NONE) GetBattlerPartyState(gBattlerAttacker)->changedSpecies = gBattleMons[gBattlerAttacker].species; @@ -5582,7 +5612,7 @@ static void Cmd_unused_0x48(void) static inline bool32 TryTriggerSymbiosis(u32 battler, u32 ally) { - return GetBattlerAbility(ally) == ABILITY_SYMBIOSIS + return BattlerHasTrait(ally, ABILITY_SYMBIOSIS) && gBattleMons[battler].item == ITEM_NONE && gBattleMons[ally].item != ITEM_NONE && CanBattlerGetOrLoseItem(battler, gBattleMons[ally].item) @@ -5646,12 +5676,13 @@ static inline bool32 CanEjectPackTrigger(u32 battlerAtk, u32 battlerDef, enum Ba static bool32 HandleMoveEndAbilityBlock(u32 battlerAtk, u32 battlerDef, u32 move) { bool32 effect = FALSE; - enum Ability abilityAtk = GetBattlerAbility(battlerAtk); + u32 stat; + u32 numMonsFainted; + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battlerAtk); - switch (abilityAtk) - { - case ABILITY_MAGICIAN: - if (GetMoveEffect(move) != EFFECT_FLING + if (SearchTraits(battlerTraits, ABILITY_MAGICIAN) + && GetMoveEffect(move) != EFFECT_FLING && GetMoveEffect(move) != EFFECT_NATURAL_GIFT && gBattleMons[battlerAtk].item == ITEM_NONE && IsBattlerAlive(battlerAtk) @@ -5668,131 +5699,178 @@ static bool32 HandleMoveEndAbilityBlock(u32 battlerAtk, u32 battlerDef, u32 move && CanStealItem(battlerAtk, i, gBattleMons[i].item) && !(gWishFutureKnock.knockedOffMons[GetBattlerSide(i)] & (1u << gBattlerPartyIndexes[i])) && !DoesSubstituteBlockMove(battlerAtk, i, move) - && (GetBattlerAbility(i) != ABILITY_STICKY_HOLD || !IsBattlerAlive(i))) + && (!BattlerHasTrait(i, ABILITY_STICKY_HOLD) || !IsBattlerAlive(i))) { magicianTargets |= 1u << i; numMagicianTargets++; } } - if (numMagicianTargets == 0) + if (numMagicianTargets != 0) { - effect = FALSE; - break; - } + u8 battlers[4] = {0, 1, 2, 3}; + if (numMagicianTargets > 1) + SortBattlersBySpeed(battlers, FALSE); - u8 battlers[4] = {0, 1, 2, 3}; - if (numMagicianTargets > 1) - SortBattlersBySpeed(battlers, FALSE); + for (u32 i = 0; i < gBattlersCount; i++) + { + u32 battler = battlers[i]; - for (u32 i = 0; i < gBattlersCount; i++) - { - u32 battler = battlers[i]; + if (!(magicianTargets & 1u << battler)) + continue; - if (!(magicianTargets & 1u << battler)) - continue; + gLastUsedAbility = ABILITY_MAGICIAN; + StealTargetItem(battlerAtk, battler); + gBattlerAbility = battlerAtk; + gEffectBattler = battler; + PushTraitStack(battlerAtk, ABILITY_MAGICIAN); + BattleScriptCall(BattleScript_MagicianActivates); + effect = TRUE; + break; // found target to steal from + } + } + } +if (SearchTraits(battlerTraits, ABILITY_MOXIE)) + { + if (IsBattlerAlive(battlerAtk) && !NoAliveMonsForEitherParty()) + { + u32 stat = STAT_ATK; + u32 numMonsFainted = NumFaintedBattlersByAttacker(battlerAtk); - StealTargetItem(battlerAtk, battler); - gBattlerAbility = battlerAtk; - gEffectBattler = battler; - BattleScriptCall(BattleScript_MagicianActivates); + if (numMonsFainted && CompareStat(battlerAtk, stat, MAX_STAT_STAGE, CMP_LESS_THAN)) + { + gLastUsedAbility = ABILITY_MOXIE; + SET_STATCHANGER(stat, numMonsFainted, FALSE); + PREPARE_STAT_BUFFER(gBattleTextBuff1, stat); + gBattleScripting.animArg1 = GET_STAT_BUFF_ID(stat) + (numMonsFainted > 1 ? STAT_ANIM_PLUS2 : STAT_ANIM_PLUS1); + PushTraitStack(battlerAtk, ABILITY_MOXIE); + BattleScriptCall(BattleScript_RaiseStatOnFaintingTargetMoxie); effect = TRUE; - break; // found target to steal from } } - break; - case ABILITY_MOXIE: - case ABILITY_CHILLING_NEIGH: - case ABILITY_AS_ONE_ICE_RIDER: - case ABILITY_GRIM_NEIGH: - case ABILITY_AS_ONE_SHADOW_RIDER: - case ABILITY_BEAST_BOOST: - { - if (!IsBattlerAlive(battlerAtk) || NoAliveMonsForEitherParty()) - break; + } + if (SearchTraits(battlerTraits, ABILITY_CHILLING_NEIGH) + || SearchTraits(battlerTraits, ABILITY_AS_ONE_ICE_RIDER)) + { + if (IsBattlerAlive(battlerAtk) && !NoAliveMonsForEitherParty()) + { + stat = STAT_ATK; + numMonsFainted = NumFaintedBattlersByAttacker(battlerAtk); - enum Stat stat = STAT_ATK; - u32 numMonsFainted = NumFaintedBattlersByAttacker(battlerAtk); + if (numMonsFainted && CompareStat(battlerAtk, stat, MAX_STAT_STAGE, CMP_LESS_THAN)) + { + gLastUsedAbility = ABILITY_CHILLING_NEIGH; + if (SearchTraits(battlerTraits, ABILITY_AS_ONE_ICE_RIDER)) + gBattleScripting.abilityPopupOverwrite = ABILITY_CHILLING_NEIGH; - if (abilityAtk == ABILITY_BEAST_BOOST) - stat = GetHighestStatId(battlerAtk); - else if (abilityAtk == ABILITY_GRIM_NEIGH || abilityAtk == ABILITY_AS_ONE_SHADOW_RIDER) - stat = STAT_SPATK; + SET_STATCHANGER(stat, numMonsFainted, FALSE); + PREPARE_STAT_BUFFER(gBattleTextBuff1, stat); + gBattleScripting.animArg1 = GET_STAT_BUFF_ID(stat) + (numMonsFainted > 1 ? STAT_ANIM_PLUS2 : STAT_ANIM_PLUS1); + PushTraitStack(battlerAtk, ABILITY_CHILLING_NEIGH); + BattleScriptCall(BattleScript_RaiseStatOnFaintingTargetChilling); + effect = TRUE; + } + } + } + if (SearchTraits(battlerTraits, ABILITY_GRIM_NEIGH) + || SearchTraits(battlerTraits, ABILITY_AS_ONE_SHADOW_RIDER)) + { + if (IsBattlerAlive(battlerAtk) && !NoAliveMonsForEitherParty()) + { + stat = STAT_SPATK; + numMonsFainted = NumFaintedBattlersByAttacker(battlerAtk); - if (numMonsFainted && CompareStat(battlerAtk, stat, MAX_STAT_STAGE, CMP_LESS_THAN, abilityAtk)) + if (numMonsFainted && CompareStat(battlerAtk, stat, MAX_STAT_STAGE, CMP_LESS_THAN)) { - gLastUsedAbility = abilityAtk; - if (abilityAtk == ABILITY_AS_ONE_ICE_RIDER) - gBattleScripting.abilityPopupOverwrite = gLastUsedAbility = ABILITY_CHILLING_NEIGH; - else if (abilityAtk == ABILITY_AS_ONE_SHADOW_RIDER) - gBattleScripting.abilityPopupOverwrite = gLastUsedAbility = ABILITY_GRIM_NEIGH; + + gLastUsedAbility = ABILITY_GRIM_NEIGH; + if (SearchTraits(battlerTraits, ABILITY_AS_ONE_SHADOW_RIDER)) + gBattleScripting.abilityPopupOverwrite = ABILITY_GRIM_NEIGH; SET_STATCHANGER(stat, numMonsFainted, FALSE); PREPARE_STAT_BUFFER(gBattleTextBuff1, stat); gBattleScripting.animArg1 = GET_STAT_BUFF_ID(stat) + (numMonsFainted > 1 ? STAT_ANIM_PLUS2 : STAT_ANIM_PLUS1); - BattleScriptCall(BattleScript_RaiseStatOnFaintingTarget); + PushTraitStack(battlerAtk, ABILITY_GRIM_NEIGH); + BattleScriptCall(BattleScript_RaiseStatOnFaintingTargetGrim); effect = TRUE; } } - break; - case ABILITY_BATTLE_BOND: + } + if (SearchTraits(battlerTraits, ABILITY_BEAST_BOOST)) + { + if (IsBattlerAlive(battlerAtk) && !NoAliveMonsForEitherParty()) { - if (!IsBattlerAlive(battlerAtk) - || NoAliveMonsForEitherParty() - || NumFaintedBattlersByAttacker(battlerAtk) == 0) - break; - - if (GetBattlerPartyState(battlerAtk)->battleBondBoost) - break; + stat = GetHighestStatId(battlerAtk); + numMonsFainted = NumFaintedBattlersByAttacker(battlerAtk); - if (GetConfig(CONFIG_BATTLE_BOND) < GEN_9 && gBattleMons[battlerAtk].species == SPECIES_GRENINJA_BATTLE_BOND) + if (numMonsFainted && CompareStat(battlerAtk, stat, MAX_STAT_STAGE, CMP_LESS_THAN)) { - // TODO: Convert this to a proper FORM_CHANGE type. - gLastUsedAbility = abilityAtk; - GetBattlerPartyState(battlerAtk)->battleBondBoost = TRUE; - PREPARE_SPECIES_BUFFER(gBattleTextBuff1, gBattleMons[battlerAtk].species); - GetBattlerPartyState(battlerAtk)->changedSpecies = gBattleMons[battlerAtk].species; - gBattleMons[battlerAtk].species = SPECIES_GRENINJA_ASH; - BattleScriptCall(BattleScript_BattleBondActivatesOnMoveEndAttacker); + gLastUsedAbility = ABILITY_BEAST_BOOST; + SET_STATCHANGER(stat, numMonsFainted, FALSE); + PREPARE_STAT_BUFFER(gBattleTextBuff1, stat); + gBattleScripting.animArg1 = GET_STAT_BUFF_ID(stat) + (numMonsFainted > 1 ? STAT_ANIM_PLUS2 : STAT_ANIM_PLUS1); + PushTraitStack(battlerAtk, ABILITY_BEAST_BOOST); + BattleScriptCall(BattleScript_RaiseStatOnFaintingTargetBeastBoost); effect = TRUE; } - else + } + } + if (SearchTraits(battlerTraits, ABILITY_BATTLE_BOND)) + { + if (IsBattlerAlive(battlerAtk) + && !NoAliveMonsForEitherParty() + && !NumFaintedBattlersByAttacker(battlerAtk) == 0) + { + + if (!GetBattlerPartyState(battlerAtk)->battleBondBoost) { - u32 numStatBuffs = 0; - if (CompareStat(battlerAtk, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, abilityAtk)) - { - gBattleScripting.animArg1 = GET_STAT_BUFF_ID(STAT_ATK) + STAT_ANIM_PLUS1; - numStatBuffs++; - } - if (CompareStat(battlerAtk, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN, abilityAtk)) + if (GetConfig(CONFIG_BATTLE_BOND) < GEN_9 && gBattleMons[battlerAtk].species == SPECIES_GRENINJA_BATTLE_BOND) { - gBattleScripting.animArg1 = GET_STAT_BUFF_ID(STAT_SPATK) + STAT_ANIM_PLUS1; - numStatBuffs++; - } - if (CompareStat(battlerAtk, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, abilityAtk)) - { - gBattleScripting.animArg1 = GET_STAT_BUFF_ID(STAT_SPEED) + STAT_ANIM_PLUS1; - numStatBuffs++; + // TODO: Convert this to a proper FORM_CHANGE type. + gLastUsedAbility = ABILITY_BATTLE_BOND; + GetBattlerPartyState(battlerAtk)->battleBondBoost = TRUE; + PREPARE_SPECIES_BUFFER(gBattleTextBuff1, gBattleMons[battlerAtk].species); + GetBattlerPartyState(battlerAtk)->changedSpecies = gBattleMons[battlerAtk].species; + gBattleMons[battlerAtk].species = SPECIES_GRENINJA_ASH; + PushTraitStack(battlerAtk, ABILITY_BATTLE_BOND); + BattleScriptCall(BattleScript_BattleBondActivatesOnMoveEndAttacker); + effect = TRUE; } - - if (numStatBuffs > 0) + else { - if (numStatBuffs > 1) - gBattleScripting.animArg1 = STAT_ANIM_MULTIPLE_PLUS1; + u32 numStatBuffs = 0; + if (CompareStat(battlerAtk, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + { + gBattleScripting.animArg1 = GET_STAT_BUFF_ID(STAT_ATK) + STAT_ANIM_PLUS1; + numStatBuffs++; + } + if (CompareStat(battlerAtk, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + { + gBattleScripting.animArg1 = GET_STAT_BUFF_ID(STAT_SPATK) + STAT_ANIM_PLUS1; + numStatBuffs++; + } + if (CompareStat(battlerAtk, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN)) + { + gBattleScripting.animArg1 = GET_STAT_BUFF_ID(STAT_SPEED) + STAT_ANIM_PLUS1; + numStatBuffs++; + } - gLastUsedAbility = abilityAtk; - gBattlerAbility = battlerAtk; - GetBattlerPartyState(battlerAtk)->battleBondBoost = TRUE; - BattleScriptCall(BattleScript_EffectBattleBondStatIncrease); - effect = TRUE; + if (numStatBuffs > 0) + { + if (numStatBuffs > 1) + gBattleScripting.animArg1 = STAT_ANIM_MULTIPLE_PLUS1; + + gLastUsedAbility = ABILITY_BATTLE_BOND; + gBattlerAbility = battlerAtk; + PushTraitStack(battlerAtk, ABILITY_BATTLE_BOND); + GetBattlerPartyState(battlerAtk)->battleBondBoost = TRUE; + BattleScriptCall(BattleScript_EffectBattleBondStatIncrease); + effect = TRUE; + } } } } - break; - default: - break; } - return effect; } @@ -5816,7 +5894,7 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect) { u32 side = GetBattlerSide(gBattlerTarget); - if (GetBattlerAbility(gBattlerTarget) == ABILITY_STICKY_HOLD) + if (BattlerHasTrait(gBattlerTarget, ABILITY_STICKY_HOLD)) { gBattlerAbility = gBattlerTarget; BattleScriptPushCursor(); @@ -5826,7 +5904,7 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect) } gLastUsedItem = gBattleMons[gBattlerTarget].item; gBattleMons[gBattlerTarget].item = 0; - if (gBattleMons[gBattlerTarget].ability != ABILITY_GORILLA_TACTICS) + if (!BattlerHasTrait(gBattlerTarget, ABILITY_GORILLA_TACTICS)) gBattleStruct->choicedMove[gBattlerTarget] = MOVE_NONE; CheckSetUnburden(gBattlerTarget); @@ -5856,11 +5934,12 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect) { effect = FALSE; } - else if (GetBattlerAbility(gBattlerTarget) == ABILITY_STICKY_HOLD) + else if (BattlerHasTrait(gBattlerTarget, ABILITY_STICKY_HOLD)) { + PushTraitStack(gBattlerTarget, ABILITY_STICKY_HOLD); BattleScriptCall(BattleScript_NoItemSteal); - gLastUsedAbility = gBattleMons[gBattlerTarget].ability; - RecordAbilityBattle(gBattlerTarget, gLastUsedAbility); + gLastUsedAbility = ABILITY_STICKY_HOLD; + RecordAbilityBattle(gBattlerTarget, ABILITY_STICKY_HOLD); effect = TRUE; } else @@ -5884,12 +5963,12 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect) && IsBattlerAlive(gBattlerAttacker) && gBattleMons[BATTLE_PARTNER(gBattlerTarget)].volatiles.semiInvulnerable != STATE_COMMANDER) { - u32 targetAbility = GetBattlerAbility(gBattlerTarget); - if (targetAbility == ABILITY_GUARD_DOG) + if (BattlerHasTrait(gBattlerTarget, ABILITY_GUARD_DOG)) return FALSE; - if (targetAbility == ABILITY_SUCTION_CUPS) + if (BattlerHasTrait(gBattlerTarget, ABILITY_SUCTION_CUPS)) { + PushTraitStack(gBattlerTarget, ABILITY_SUCTION_CUPS); BattleScriptCall(BattleScript_AbilityPreventsPhasingOutRet); } else if (gBattleMons[gBattlerTarget].volatiles.root) @@ -5909,7 +5988,7 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect) } break; case EFFECT_SMACK_DOWN: - if (!IsBattlerGrounded(gBattlerTarget, GetBattlerAbility(gBattlerTarget), GetBattlerHoldEffect(gBattlerTarget)) + if (!IsBattlerGrounded(gBattlerTarget, GetBattlerHoldEffect(gBattlerTarget)) && IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerTarget) && !DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove)) @@ -5959,9 +6038,8 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect) case EFFECT_RECOIL: if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker) && gBattleStruct->moveDamage[gBattlerTarget] > 0) { - enum Ability ability = GetBattlerAbility(gBattlerAttacker); - if (IsAbilityAndRecord(gBattlerAttacker, ability, ABILITY_ROCK_HEAD) - || IsAbilityAndRecord(gBattlerAttacker, ability, ABILITY_MAGIC_GUARD)) + if (IsAbilityAndRecord(gBattlerAttacker, ABILITY_ROCK_HEAD) + || IsAbilityAndRecord(gBattlerAttacker, ABILITY_MAGIC_GUARD)) break; SetPassiveDamageAmount(gBattlerAttacker, gBattleScripting.savedDmg * max(1, GetMoveRecoil(gCurrentMove)) / 100); @@ -5982,7 +6060,7 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect) case EFFECT_MAX_HP_50_RECOIL: if (IsBattlerAlive(gBattlerAttacker) && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_FAILED) - && !IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_MAGIC_GUARD)) + && !IsAbilityAndRecord(gBattlerAttacker, ABILITY_MAGIC_GUARD)) { s32 recoil = (GetNonDynamaxMaxHP(gBattlerAttacker) + 1) / 2; // Half of Max HP Rounded UP SetPassiveDamageAmount(gBattlerAttacker, recoil); @@ -5994,9 +6072,8 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect) case EFFECT_CHLOROBLAST: if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker)) { - enum Ability ability = GetBattlerAbility(gBattlerAttacker); - if (IsAbilityAndRecord(gBattlerAttacker, ability, ABILITY_ROCK_HEAD) - || IsAbilityAndRecord(gBattlerAttacker, ability, ABILITY_MAGIC_GUARD)) + if (IsAbilityAndRecord(gBattlerAttacker, ABILITY_ROCK_HEAD) + || IsAbilityAndRecord(gBattlerAttacker, ABILITY_MAGIC_GUARD)) break; s32 recoil = (GetNonDynamaxMaxHP(gBattlerAttacker) + 1) / 2; // Half of Max HP Rounded UP @@ -6018,12 +6095,11 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect) && !IsBattlerAlive(gBattlerTarget) && IsBattlerTurnDamaged(gBattlerTarget) && !NoAliveMonsForEitherParty() - && CompareStat(gBattlerAttacker, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(gBattlerAttacker))) + && CompareStat(gBattlerAttacker, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) { SET_STATCHANGER(STAT_ATK, GetConfig(CONFIG_FELL_STINGER_STAT_RAISE) >= GEN_7 ? 3 : 2, FALSE); PREPARE_STAT_BUFFER(gBattleTextBuff1, STAT_ATK); - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_FellStingerRaisesStat; + BattleScriptCall(BattleScript_FellStingerRaisesStat); effect = TRUE; } break; @@ -6033,8 +6109,7 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect) && IsBattlerAlive(gBattlerAttacker)) { gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_POINTEDSTONESFLOAT; - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_StealthRockActivates; + BattleScriptCall(BattleScript_StealthRockActivates); effect = TRUE; } break; @@ -6051,8 +6126,7 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect) } else { - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_SpikesActivates; + BattleScriptCall(BattleScript_SpikesActivates); effect = TRUE; } } @@ -6105,7 +6179,7 @@ static void Cmd_moveend(void) case PROTECT_SPIKY_SHIELD: if (moveEffect != EFFECT_COUNTER && !IsProtectivePadsProtected(gBattlerAttacker, GetBattlerHoldEffect(gBattlerAttacker)) - && !IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_MAGIC_GUARD)) + && !IsAbilityAndRecord(gBattlerAttacker, ABILITY_MAGIC_GUARD)) { gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE; SetPassiveDamageAmount(gBattlerAttacker, GetNonDynamaxMaxHP(gBattlerAttacker) / 8); @@ -6129,7 +6203,7 @@ static void Cmd_moveend(void) break; case PROTECT_BANEFUL_BUNKER: if (!IsProtectivePadsProtected(gBattlerAttacker, GetBattlerHoldEffect(gBattlerAttacker)) - && CanBePoisoned(gBattlerTarget, gBattlerAttacker, GetBattlerAbility(gBattlerTarget), GetBattlerAbility(gBattlerAttacker))) + && CanBePoisoned(gBattlerTarget, gBattlerAttacker)) { gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE; gBattleScripting.moveEffect = MOVE_EFFECT_POISON; @@ -6139,7 +6213,7 @@ static void Cmd_moveend(void) break; case PROTECT_BURNING_BULWARK: if (!IsProtectivePadsProtected(gBattlerAttacker, GetBattlerHoldEffect(gBattlerAttacker)) - && CanBeBurned(gBattlerTarget, gBattlerAttacker, GetBattlerAbility(gBattlerAttacker))) + && CanBeBurned(gBattlerTarget, gBattlerAttacker)) { gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE; gBattleScripting.moveEffect = MOVE_EFFECT_BURN; @@ -6173,7 +6247,7 @@ static void Cmd_moveend(void) // Not strictly a protect effect, but works the same way if (IsBattlerUsingBeakBlast(gBattlerTarget) - && CanBeBurned(gBattlerAttacker, gBattlerAttacker, GetBattlerAbility(gBattlerAttacker)) + && CanBeBurned(gBattlerAttacker, gBattlerAttacker) && IsBattlerTurnDamaged(gBattlerTarget)) { gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE; @@ -6242,7 +6316,7 @@ static void Cmd_moveend(void) healAmount = GetDrainedBigRootHp(gBattlerAttacker, healAmount); effect = TRUE; if ((moveEffect == EFFECT_DREAM_EATER && GetConfig(CONFIG_DREAM_EATER_LIQUID_OOZE) < GEN_5) - || GetBattlerAbility(gBattlerTarget) != ABILITY_LIQUID_OOZE) + || !BattlerHasTrait(gBattlerTarget, ABILITY_LIQUID_OOZE)) { SetHealAmount(gBattlerAttacker, healAmount); gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ABSORB; @@ -6272,7 +6346,7 @@ static void Cmd_moveend(void) && !IsBattlerAlly(gBattlerAttacker, gBattlerTarget) && IsBattlerTurnDamaged(gBattlerTarget) && !IsBattleMoveStatus(gCurrentMove) - && CompareStat(gBattlerTarget, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(gBattlerTarget))) + && CompareStat(gBattlerTarget, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) { SET_STATCHANGER(STAT_ATK, 1, FALSE); BattleScriptCall(BattleScript_RageIsBuilding); @@ -6281,33 +6355,35 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_SYNCHRONIZE_TARGET: // target synchronize - if (AbilityBattleEffects(ABILITYEFFECT_SYNCHRONIZE, gBattlerTarget, 0, 0, 0)) + if (AbilityBattleEffects(ABILITYEFFECT_SYNCHRONIZE, gBattlerTarget, 0, 0)) effect = TRUE; gBattleScripting.moveendState++; break; case MOVEEND_ABILITIES: // Such as abilities activating on contact(Poison Spore, Rough Skin, etc.). - if (AbilityBattleEffects(ABILITYEFFECT_MOVE_END, gBattlerTarget, 0, 0, 0)) - effect = TRUE; - else if (TryClearIllusion(gBattlerTarget, ABILITYEFFECT_MOVE_END)) - effect = TRUE; + { + if (AbilityBattleEffects(ABILITYEFFECT_MOVE_END, gBattlerTarget, 0, 0)) + effect = TRUE; + else if (TryClearIllusion(gBattlerTarget, ABILITYEFFECT_MOVE_END)) + effect = TRUE; + } gBattleScripting.moveendState++; break; case MOVEEND_ABILITIES_ATTACKER: // Poison Touch, possibly other in the future - if (AbilityBattleEffects(ABILITYEFFECT_MOVE_END_ATTACKER, gBattlerAttacker, 0, 0, 0)) + if (AbilityBattleEffects(ABILITYEFFECT_MOVE_END_ATTACKER, gBattlerAttacker, 0, 0)) effect = TRUE; gBattleScripting.moveendState++; break; case MOVEEND_STATUS_IMMUNITY_ABILITIES: // status immunities for (u16 battler = 0; battler < gBattlersCount; battler++) { - if (AbilityBattleEffects(ABILITYEFFECT_IMMUNITY, battler, 0, 0, 0)) + if (AbilityBattleEffects(ABILITYEFFECT_IMMUNITY, battler, 0, 0)) effect = TRUE; } if (!effect) gBattleScripting.moveendState++; break; case MOVEEND_SYNCHRONIZE_ATTACKER: // attacker synchronize - if (AbilityBattleEffects(ABILITYEFFECT_ATK_SYNCHRONIZE, gBattlerAttacker, 0, 0, 0)) + if (AbilityBattleEffects(ABILITYEFFECT_ATK_SYNCHRONIZE, gBattlerAttacker, 0, 0)) effect = TRUE; gBattleScripting.moveendState++; break; @@ -6338,12 +6414,12 @@ static void Cmd_moveend(void) && TryTriggerSymbiosis(i, BATTLE_PARTNER(i))) { BestowItem(BATTLE_PARTNER(i), i); - gLastUsedAbility = gBattleMons[BATTLE_PARTNER(i)].ability; + gLastUsedAbility = ABILITY_SYMBIOSIS; + PushTraitStack(BATTLE_PARTNER(i), ABILITY_SYMBIOSIS); gEffectBattler = i; gBattleScripting.battler = gBattlerAbility = BATTLE_PARTNER(i); gBattlerAttacker = i; - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_SymbiosisActivates; + BattleScriptCall(BattleScript_SymbiosisActivates); effect = TRUE; } } @@ -6437,7 +6513,7 @@ static void Cmd_moveend(void) && gBattlerTarget != gBattlerAttacker && !IsBattlerAlly(gBattlerTarget, gBattlerAttacker) && gProtectStructs[gBattlerTarget].physicalBattlerId == gBattlerAttacker - && !IsSheerForceAffected(gCurrentMove, GetBattlerAbility(gBattlerAttacker))) + && !IsSheerForceAffected(gCurrentMove, gBattlerAttacker)) { gProtectStructs[gBattlerTarget].shellTrap = TRUE; // Change move order in double battles, so the hit mon with shell trap moves immediately after being hit. @@ -6697,7 +6773,7 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_SHEER_FORCE: - if (IsSheerForceAffected(gCurrentMove, GetBattlerAbility(gBattlerAttacker))) + if (IsSheerForceAffected(gCurrentMove, gBattlerAttacker)) gBattleScripting.moveendState = MOVEEND_EJECT_PACK; else gBattleScripting.moveendState++; @@ -6708,7 +6784,7 @@ static void Cmd_moveend(void) u32 battler = gBattleStruct->eventState.moveEndBattler++; if (battler == gBattlerAttacker) continue; - if (AbilityBattleEffects(ABILITYEFFECT_COLOR_CHANGE, battler, GetBattlerAbility(battler), 0, 0)) + if (AbilityBattleEffects(ABILITYEFFECT_COLOR_CHANGE, battler, 0, 0)) return; } gBattleStruct->eventState.moveEndBattler = 0; @@ -6761,13 +6837,12 @@ static void Cmd_moveend(void) SaveBattlerAttacker(gBattlerAttacker); gBattleScripting.battler = battler; gEffectBattler = gBattlerAttacker; - BattleScriptPushCursor(); if (gBattleStruct->battlerState[gBattlerAttacker].commanderSpecies != SPECIES_NONE - || GetBattlerAbility(gBattlerAttacker) == ABILITY_GUARD_DOG + || BattlerHasTrait(gBattlerAttacker, ABILITY_GUARD_DOG) || GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX) - gBattlescriptCurrInstr = BattleScript_RedCardActivationNoSwitch; + BattleScriptCall(BattleScript_RedCardActivationNoSwitch); else - gBattlescriptCurrInstr = BattleScript_RedCardActivates; + BattleScriptCall(BattleScript_RedCardActivates); break; // Only fastest red card activates } } @@ -6984,7 +7059,7 @@ static void Cmd_moveend(void) u32 battler = gBattleStruct->eventState.moveEndBattler++; if (!IsBattlerAlive(battler)) continue; - if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, GetBattlerAbility(battler), 0, 0)) + if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, 0, 0)) return; } gBattleStruct->eventState.moveEndBattler = 0; @@ -7027,9 +7102,9 @@ static void Cmd_moveend(void) u8 battler = battlers[i]; // Attacker is mon who made contact, battler is mon with pickpocket if (battler != gBattlerAttacker // Cannot pickpocket yourself - && GetBattlerAbility(battler) == ABILITY_PICKPOCKET // Target must have pickpocket ability + && BattlerHasTrait(battler, ABILITY_PICKPOCKET) // Target must have pickpocket ability && IsBattlerTurnDamaged(battler) // Target needs to have been damaged - && IsMoveMakingContact(gBattlerAttacker, battler, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), gCurrentMove) // Pickpocket requires contact + && IsMoveMakingContact(gBattlerAttacker, battler, GetBattlerHoldEffect(gBattlerAttacker), gCurrentMove) // Pickpocket requires contact && !(gBattleStruct->moveResultFlags[battler] & MOVE_RESULT_NO_EFFECT) // Move needs to have affected this battler && !DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove) // Subsitute unaffected && IsBattlerAlive(battler) // Battler must be alive to pickpocket @@ -7043,10 +7118,11 @@ static void Cmd_moveend(void) gBattleStruct->changedItems[gBattlerAttacker] = ITEM_NONE; } // Battle scripting is super brittle so we shall do the item exchange now (if possible) - if (GetBattlerAbility(gBattlerAttacker) != ABILITY_STICKY_HOLD) + if (!BattlerHasTrait(gBattlerAttacker, ABILITY_STICKY_HOLD)) StealTargetItem(gBattlerTarget, gBattlerAttacker); // Target takes attacker's item gEffectBattler = gBattlerAttacker; + PushTraitStack(battler, ABILITY_PICKPOCKET); BattleScriptCall(BattleScript_Pickpocket); // Includes sticky hold check to print separate string effect = TRUE; break; // Pickpocket activates on fastest mon, so exit loop. @@ -7211,13 +7287,13 @@ static void Cmd_moveend(void) } for (battler = 0; battler < gBattlersCount; battler++) { - if (GetBattlerAbility(battler) == ABILITY_DANCER && !gSpecialStatuses[battler].dancerUsedMove) + if (BattlerHasTrait(battler, ABILITY_DANCER) && !gSpecialStatuses[battler].dancerUsedMove) { if (!nextDancer || (gBattleMons[battler].speed < gBattleMons[nextDancer & 0x3].speed)) nextDancer = battler | 0x4; } } - if (nextDancer && AbilityBattleEffects(ABILITYEFFECT_MOVE_END_OTHER, nextDancer & 0x3, 0, 0, gCurrentMove)) + if (nextDancer && AbilityBattleEffects(ABILITYEFFECT_MOVE_END_OTHER, nextDancer & 0x3, 0, gCurrentMove)) effect = TRUE; } } @@ -7362,6 +7438,7 @@ static void Cmd_switchindataupdate(void) gBattleMons[battler].types[1] = GetSpeciesType(gBattleMons[battler].species, 1); gBattleMons[battler].types[2] = TYPE_MYSTERY; gBattleMons[battler].ability = GetAbilityBySpecies(gBattleMons[battler].species, gBattleMons[battler].abilityNum); + #if TESTING if (gTestRunnerEnabled) { @@ -7883,9 +7960,9 @@ static void Cmd_switchhandleorder(void) bool32 DoSwitchInAbilities(u32 battler) { return (TryPrimalReversion(battler) - || AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, battler, 0, 0, 0) - || (gBattleWeather & B_WEATHER_ANY && HasWeatherEffect() && AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, battler, 0, 0, 0)) - || (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY && AbilityBattleEffects(ABILITYEFFECT_ON_TERRAIN, battler, 0, 0, 0))); + || AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, battler, 0, 0) + || (gBattleWeather & B_WEATHER_ANY && HasWeatherEffect() && AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, battler, 0, 0)) + || (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY && AbilityBattleEffects(ABILITYEFFECT_ON_TERRAIN, battler, 0, 0))); } static void UpdateSentMonFlags(u32 battler) @@ -7913,25 +7990,25 @@ static void SetDmgHazardsBattlescript(u8 battler, u8 multistringId) void TryHazardsOnSwitchIn(u32 battler, u32 side, enum Hazards hazardType) { + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battler); + switch (hazardType) { case HAZARDS_NONE: break; case HAZARDS_SPIKES: - { - enum Ability ability = GetBattlerAbility(battler); - if (ability != ABILITY_MAGIC_GUARD + if (!SearchTraits(battlerTraits, ABILITY_MAGIC_GUARD) && IsBattlerAffectedByHazards(battler, FALSE) - && IsBattlerGrounded(battler, ability, GetBattlerHoldEffect(battler))) + && IsBattlerGrounded(battler, GetBattlerHoldEffect(battler))) { s32 spikesDmg = GetNonDynamaxMaxHP(battler) / ((5 - gSideTimers[side].spikesAmount) * 2); SetPassiveDamageAmount(battler, spikesDmg); SetDmgHazardsBattlescript(battler, B_MSG_PKMNHURTBYSPIKES); } break; - } case HAZARDS_STICKY_WEB: - if (IsBattlerAffectedByHazards(battler, FALSE) && IsBattlerGrounded(battler, GetBattlerAbility(battler), GetBattlerHoldEffect(battler))) + if (IsBattlerAffectedByHazards(battler, FALSE) && IsBattlerGrounded(battler, GetBattlerHoldEffect(battler))) { gBattleScripting.battler = battler; SET_STATCHANGER(STAT_SPEED, 1, TRUE); @@ -7939,7 +8016,7 @@ void TryHazardsOnSwitchIn(u32 battler, u32 side, enum Hazards hazardType) } break; case HAZARDS_TOXIC_SPIKES: - if (!IsBattlerGrounded(battler, GetBattlerAbility(battler), GetBattlerHoldEffect(battler))) + if (!IsBattlerGrounded(battler, GetBattlerHoldEffect(battler))) break; if (IS_BATTLER_OF_TYPE(battler, TYPE_POISON)) // Absorb the toxic spikes. @@ -7951,18 +8028,17 @@ void TryHazardsOnSwitchIn(u32 battler, u32 side, enum Hazards hazardType) BattleScriptCall(BattleScript_ToxicSpikesAbsorbed); } else if (IsBattlerAffectedByHazards(battler, TRUE) - && CanBePoisoned(battler, battler, GetBattlerAbility(battler), GetBattlerAbility(battler))) + && CanBePoisoned(battler, battler)) { gBattleScripting.battler = battler; - BattleScriptPushCursor(); if (gSideTimers[GetBattlerSide(battler)].toxicSpikesAmount >= 2) { - gBattlescriptCurrInstr = BattleScript_ToxicSpikesBadlyPoisoned; + BattleScriptCall(BattleScript_ToxicSpikesBadlyPoisoned); gBattleMons[battler].status1 |= STATUS1_TOXIC_POISON; } else { - gBattlescriptCurrInstr = BattleScript_ToxicSpikesPoisoned; + BattleScriptCall(BattleScript_ToxicSpikesPoisoned); gBattleMons[battler].status1 |= STATUS1_POISON; } @@ -7971,7 +8047,7 @@ void TryHazardsOnSwitchIn(u32 battler, u32 side, enum Hazards hazardType) } break; case HAZARDS_STEALTH_ROCK: - if (IsBattlerAffectedByHazards(battler, FALSE) && GetBattlerAbility(battler) != ABILITY_MAGIC_GUARD) + if (IsBattlerAffectedByHazards(battler, FALSE) && !SearchTraits(battlerTraits, ABILITY_MAGIC_GUARD)) { gBattleStruct->passiveHpUpdate[battler] = GetStealthHazardDamage(TYPE_SIDE_HAZARD_POINTED_STONES, battler); if (gBattleStruct->passiveHpUpdate[battler] != 0) @@ -7979,7 +8055,7 @@ void TryHazardsOnSwitchIn(u32 battler, u32 side, enum Hazards hazardType) } break; case HAZARDS_STEELSURGE: - if (IsBattlerAffectedByHazards(battler, FALSE) && GetBattlerAbility(battler) != ABILITY_MAGIC_GUARD) + if (IsBattlerAffectedByHazards(battler, FALSE) && !SearchTraits(battlerTraits, ABILITY_MAGIC_GUARD)) { gBattleStruct->passiveHpUpdate[battler] = GetStealthHazardDamage(TYPE_SIDE_HAZARD_SHARP_STEEL, battler); if (gBattleStruct->passiveHpUpdate[battler] != 0) @@ -7995,8 +8071,11 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler) { u32 i = 0; u32 side = GetBattlerSide(battler); + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battler); + // Neutralizing Gas announces itself before hazards - if (AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, battler, 0, 0, 0)) + if (AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, battler, 0, 0)) { return TRUE; } @@ -8057,10 +8136,9 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler) } else { - enum Ability battlerAbility = GetBattlerAbility(battler); // There is a hack here to ensure the truant counter will be 0 when the battler's next turn starts. // The truant counter is not updated in the case where a mon switches in after a lost judgment in the battle arena. - if (battlerAbility == ABILITY_TRUANT + if (SearchTraits(battlerTraits, ABILITY_TRUANT) && gCurrentActionFuncId != B_ACTION_USE_MOVE && !gDisableStructs[battler].truantSwitchInHack) gDisableStructs[battler].truantCounter = 1; @@ -8077,24 +8155,15 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler) { if (i == battler) continue; - - enum Ability ability = GetBattlerAbility(i); - switch (ability) - { - case ABILITY_TRACE: - case ABILITY_COMMANDER: - if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, i, ability, 0, 0)) + STORE_BATTLER_TRAITS(i); + if (SearchTraits(battlerTraits, ABILITY_TRACE) || SearchTraits(battlerTraits, ABILITY_COMMANDER)) + if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, i, 0, 0)) return TRUE; - break; - case ABILITY_FORECAST: - case ABILITY_FLOWER_GIFT: - case ABILITY_PROTOSYNTHESIS: - if (AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, i, ability, 0, 0)) + if (SearchTraits(battlerTraits, ABILITY_FORECAST) + || SearchTraits(battlerTraits, ABILITY_FLOWER_GIFT) + || SearchTraits(battlerTraits, ABILITY_PROTOSYNTHESIS)) + if (AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, i, 0, 0)) return TRUE; - break; - default: - break; - } if (TryClearIllusion(i, ABILITYEFFECT_ON_SWITCHIN)) return TRUE; } @@ -8106,7 +8175,7 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler) } for (i = 0; i < gBattlersCount; i++) { - if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, i, GetBattlerAbility(i), 0, 0)) + if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, i, 0, 0)) return TRUE; } for (i = 0; i < gBattlersCount; i++) @@ -8832,13 +8901,14 @@ static void Cmd_setgravity(void) static bool32 TryCheekPouch(u32 battler, u32 itemId, const u8 *nextInstr) { if (GetItemPocket(itemId) == POCKET_BERRIES - && GetBattlerAbility(battler) == ABILITY_CHEEK_POUCH + && BattlerHasTrait(battler, ABILITY_CHEEK_POUCH) && !gBattleMons[battler].volatiles.healBlock && GetBattlerPartyState(battler)->ateBerry && !IsBattlerAtMaxHp(battler)) { gBattlerAbility = battler; SetHealAmount(battler, GetNonDynamaxMaxHP(battler) / 3); + PushTraitStack(battler, ABILITY_CHEEK_POUCH); BattleScriptPush(nextInstr); gBattlescriptCurrInstr = BattleScript_CheekPouchActivates; return TRUE; @@ -8875,7 +8945,8 @@ static bool32 TrySymbiosis(u32 battler, u32 itemId, bool32 moveEnd) && TryTriggerSymbiosis(battler, BATTLE_PARTNER(battler))) { BestowItem(BATTLE_PARTNER(battler), battler); - gLastUsedAbility = gBattleMons[BATTLE_PARTNER(battler)].ability; + gLastUsedAbility = ABILITY_SYMBIOSIS; + PushTraitStack(BATTLE_PARTNER(battler), ABILITY_SYMBIOSIS); gEffectBattler = battler; gBattleScripting.battler = gBattlerAbility = BATTLE_PARTNER(battler); if (moveEnd) @@ -9528,30 +9599,43 @@ static bool32 TryTidyUpClear(u32 battlerAtk, bool32 clear) u32 IsFlowerVeilProtected(u32 battler) { - if (IS_BATTLER_OF_TYPE(battler, TYPE_GRASS)) - return IsAbilityOnSide(battler, ABILITY_FLOWER_VEIL); + if (IS_BATTLER_OF_TYPE(battler, TYPE_GRASS) && IsAbilityOnSide(battler, ABILITY_FLOWER_VEIL)) + { + PushTraitStack(IsAbilityOnSide(battler, ABILITY_FLOWER_VEIL) - 1, ABILITY_FLOWER_VEIL); + return TRUE; + } else return 0; } -u32 IsLeafGuardProtected(u32 battler, enum Ability ability) +u32 IsLeafGuardProtected(u32 battler) { - if (IsBattlerWeatherAffected(battler, B_WEATHER_SUN)) - return ability == ABILITY_LEAF_GUARD; + if (IsBattlerWeatherAffected(battler, B_WEATHER_SUN) + && (gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battler, ABILITY_LEAF_GUARD) : BattlerHasTrait(battler, ABILITY_LEAF_GUARD))) + { + PushTraitStack(battler, ABILITY_LEAF_GUARD); + return TRUE; + } else return 0; } -bool32 IsShieldsDownProtected(u32 battler, enum Ability ability) +bool32 IsShieldsDownProtected(u32 battler) { - return (ability == ABILITY_SHIELDS_DOWN - && GetFormIdFromFormSpeciesId(gBattleMons[battler].species) < GetFormIdFromFormSpeciesId(SPECIES_MINIOR_CORE_RED)); // Minior is not in core form + if((gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battler, ABILITY_SHIELDS_DOWN) : BattlerHasTrait(battler, ABILITY_SHIELDS_DOWN)) + && GetFormIdFromFormSpeciesId(gBattleMons[battler].species) < GetFormIdFromFormSpeciesId(SPECIES_MINIOR_CORE_RED)) // Minior is not in core form + { + PushTraitStack(battler, ABILITY_SHIELDS_DOWN); + return TRUE; + } + else + return FALSE; } -u32 IsAbilityStatusProtected(u32 battler, enum Ability ability) +u32 IsAbilityStatusProtected(u32 battler) { - return IsLeafGuardProtected(battler, ability) - || IsShieldsDownProtected(battler, ability) + return IsLeafGuardProtected(battler) + || IsShieldsDownProtected(battler) || IsFlowerVeilProtected(battler); } @@ -9559,7 +9643,7 @@ static bool32 IsRototillerAffected(u32 battler) { if (!IsBattlerAlive(battler)) return FALSE; - if (!IsBattlerGrounded(battler, GetBattlerAbility(battler), GetBattlerHoldEffect(battler))) + if (!IsBattlerGrounded(battler, GetBattlerHoldEffect(battler))) return FALSE; // Only grounded battlers affected if (!IS_BATTLER_OF_TYPE(battler, TYPE_GRASS)) return FALSE; // Only grass types affected @@ -9582,8 +9666,8 @@ static bool32 IsElectricAbilityAffected(u32 battler, enum Ability ability) moveType = GetMoveType(gCurrentMove); if (moveType == TYPE_ELECTRIC - && (ability != ABILITY_LIGHTNING_ROD || GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) >= GEN_5) - && GetBattlerAbility(battler) == ability) + && (!BattlerHasTrait(battler, ABILITY_LIGHTNING_ROD) || GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) >= GEN_5) + && BattlerHasTrait(battler, ability)) return TRUE; else return FALSE; @@ -9884,7 +9968,7 @@ static void Cmd_setfieldweather(void) u8 battleWeatherId = cmd->weather; - if (!TryChangeBattleWeather(gBattlerAttacker, battleWeatherId, ABILITY_NONE)) + if (!TryChangeBattleWeather(gBattlerAttacker, battleWeatherId, FALSE)) { gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_WEATHER_FAILED; @@ -9979,14 +10063,13 @@ static void Cmd_trysetrest(void) gBattlerTarget = gBattlerAttacker; SetHealAmount(gBattlerTarget, gBattleMons[gBattlerTarget].maxHP); - enum Ability ability = GetBattlerAbility(gBattlerTarget); enum HoldEffect holdEffect = GetBattlerHoldEffect(gBattlerTarget); - if (IsBattlerTerrainAffected(gBattlerTarget, ability, holdEffect, STATUS_FIELD_ELECTRIC_TERRAIN)) + if (IsBattlerTerrainAffected(gBattlerTarget, holdEffect, STATUS_FIELD_ELECTRIC_TERRAIN)) { gBattlescriptCurrInstr = BattleScript_ElectricTerrainPrevents; } - else if (IsBattlerTerrainAffected(gBattlerTarget, ability, holdEffect, STATUS_FIELD_MISTY_TERRAIN)) + else if (IsBattlerTerrainAffected(gBattlerTarget, holdEffect, STATUS_FIELD_MISTY_TERRAIN)) { gBattlescriptCurrInstr = BattleScript_MistyTerrainPrevents; } @@ -10015,7 +10098,7 @@ static void Cmd_unused_0x83(void) bool8 UproarWakeUpCheck(u8 battler) { s32 i; - bool32 hasSoundproof = (B_UPROAR_IGNORE_SOUNDPROOF < GEN_5 && GetBattlerAbility(battler) == ABILITY_SOUNDPROOF); + bool32 hasSoundproof = (B_UPROAR_IGNORE_SOUNDPROOF < GEN_5 && BattlerHasTrait(battler, ABILITY_SOUNDPROOF)); for (i = 0; i < gBattlersCount; i++) { @@ -10210,6 +10293,8 @@ static void TryPlayStatChangeAnimation(u32 battler, enum Ability ability, u32 st u32 changeableStatsCount = 1; // current stat is counted automatically u32 statAnimId = statId; bool32 statChangeByTwo = statValue > 1 || statValue < -1; + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battler); if (statValue <= -1) // goes down { @@ -10230,10 +10315,10 @@ static void TryPlayStatChangeAnimation(u32 battler, enum Ability ability, u32 st break; } } - else if (!((ability == ABILITY_KEEN_EYE || ability == ABILITY_MINDS_EYE) && currStat == STAT_ACC) - && !(GetConfig(CONFIG_ILLUMINATE_EFFECT) >= GEN_9 && ability == ABILITY_ILLUMINATE && currStat == STAT_ACC) - && !(ability == ABILITY_HYPER_CUTTER && currStat == STAT_ATK) - && !(ability == ABILITY_BIG_PECKS && currStat == STAT_DEF)) + else if (!((SearchTraits(battlerTraits, ABILITY_KEEN_EYE) || SearchTraits(battlerTraits, ABILITY_MINDS_EYE)) && currStat == STAT_ACC) + && !(GetConfig(CONFIG_ILLUMINATE_EFFECT) >= GEN_9 && SearchTraits(battlerTraits, ABILITY_ILLUMINATE) && currStat == STAT_ACC) + && !(SearchTraits(battlerTraits, ABILITY_HYPER_CUTTER) && currStat == STAT_ATK) + && !(SearchTraits(battlerTraits, ABILITY_BIG_PECKS) && currStat == STAT_DEF)) { if (gBattleMons[battler].statStages[currStat] > MIN_STAT_STAGE) { @@ -10299,19 +10384,21 @@ static u32 ChangeStatBuffs(u32 battler, s8 statValue, enum Stat statId, union St battlerAbility = GetBattlerAbility(battler); battlerHoldEffect = GetBattlerHoldEffect(battler); gSpecialStatuses[battler].changedStatsBattlerId = gBattlerAttacker; + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battler); - if (battlerAbility == ABILITY_CONTRARY) + if (SearchTraits(battlerTraits, ABILITY_CONTRARY)) { statValue ^= STAT_BUFF_NEGATIVE; if (!flags.onlyChecking) { gBattleScripting.statChanger ^= STAT_BUFF_NEGATIVE; - RecordAbilityBattle(battler, battlerAbility); + RecordAbilityBattle(battler, ABILITY_CONTRARY); if (flags.updateMoveEffect) gBattleScripting.moveEffect = ReverseStatChangeMoveEffect(gBattleScripting.moveEffect); } } - else if (battlerAbility == ABILITY_SIMPLE && !flags.onlyChecking) + else if (SearchTraits(battlerTraits, ABILITY_SIMPLE) && !flags.onlyChecking) { statValue = (SET_STAT_BUFF_VALUE(GET_STAT_BUFF_VALUE(statValue) * 2)) | ((statValue <= -1) ? STAT_BUFF_NEGATIVE : 0); RecordAbilityBattle(battler, battlerAbility); @@ -10323,7 +10410,7 @@ static u32 ChangeStatBuffs(u32 battler, s8 statValue, enum Stat statId, union St { if (gSideTimers[GetBattlerSide(battler)].mistTimer && !flags.certain && gCurrentMove != MOVE_CURSE - && !(battler == gBattlerTarget && GetBattlerAbility(gBattlerAttacker) == ABILITY_INFILTRATOR && !IsBattlerAlly(gBattlerAttacker, battler))) + && !(battler == gBattlerTarget && BattlerHasTrait(gBattlerAttacker, ABILITY_INFILTRATOR) && !IsBattlerAlly(gBattlerAttacker, battler))) { if (flags.allowPtr) { @@ -10346,7 +10433,7 @@ static u32 ChangeStatBuffs(u32 battler, s8 statValue, enum Stat statId, union St { return STAT_CHANGE_DIDNT_WORK; } - else if ((battlerHoldEffect == HOLD_EFFECT_CLEAR_AMULET || CanAbilityPreventStatLoss(battlerAbility)) + else if ((battlerHoldEffect == HOLD_EFFECT_CLEAR_AMULET || CanBattlerPreventStatLoss(battler)) && (flags.statDropPrevention || gBattlerAttacker != gBattlerTarget || flags.mirrorArmored) && !flags.certain && gCurrentMove != MOVE_CURSE) { if (flags.allowPtr) @@ -10367,11 +10454,19 @@ static u32 ChangeStatBuffs(u32 battler, s8 statValue, enum Stat statId, union St } else { + if (SearchTraits(battlerTraits, ABILITY_CLEAR_BODY)) + battlerAbility = ABILITY_CLEAR_BODY; + else if (SearchTraits(battlerTraits, ABILITY_FULL_METAL_BODY)) + battlerAbility = ABILITY_FULL_METAL_BODY; + else if (SearchTraits(battlerTraits, ABILITY_WHITE_SMOKE)) + battlerAbility = ABILITY_WHITE_SMOKE; + gBattlerAbility = battler; BattleScriptPush(BS_ptr); gBattlescriptCurrInstr = BattleScript_AbilityNoStatLoss; gLastUsedAbility = battlerAbility; - RecordAbilityBattle(battler, gLastUsedAbility); + PushTraitStack(battler, battlerAbility); + RecordAbilityBattle(battler, battlerAbility); } gSpecialStatuses[battler].statLowered = TRUE; } @@ -10399,34 +10494,46 @@ static u32 ChangeStatBuffs(u32 battler, s8 statValue, enum Stat statId, union St return STAT_CHANGE_DIDNT_WORK; } else if (!flags.certain - && (((battlerAbility == ABILITY_KEEN_EYE || battlerAbility == ABILITY_MINDS_EYE) && statId == STAT_ACC) - || (GetConfig(CONFIG_ILLUMINATE_EFFECT) >= GEN_9 && battlerAbility == ABILITY_ILLUMINATE && statId == STAT_ACC) - || (battlerAbility == ABILITY_HYPER_CUTTER && statId == STAT_ATK) - || (battlerAbility == ABILITY_BIG_PECKS && statId == STAT_DEF))) + && (((SearchTraits(battlerTraits, ABILITY_KEEN_EYE) || SearchTraits(battlerTraits, ABILITY_MINDS_EYE)) && statId == STAT_ACC) + || (GetConfig(CONFIG_ILLUMINATE_EFFECT) >= GEN_9 && SearchTraits(battlerTraits, ABILITY_ILLUMINATE) && statId == STAT_ACC) + || (SearchTraits(battlerTraits, ABILITY_HYPER_CUTTER) && statId == STAT_ATK) + || (SearchTraits(battlerTraits, ABILITY_BIG_PECKS) && statId == STAT_DEF))) { if (flags.allowPtr) { + if (SearchTraits(battlerTraits, ABILITY_KEEN_EYE) && statId == STAT_ACC) + battlerAbility = ABILITY_KEEN_EYE; + else if (SearchTraits(battlerTraits, ABILITY_MINDS_EYE) && statId == STAT_ACC) + battlerAbility = ABILITY_MINDS_EYE; + else if (SearchTraits(battlerTraits, ABILITY_ILLUMINATE) && statId == STAT_ACC) + battlerAbility = ABILITY_ILLUMINATE; + else if (SearchTraits(battlerTraits, ABILITY_HYPER_CUTTER) && statId == STAT_ATK) + battlerAbility = ABILITY_HYPER_CUTTER; + else if (SearchTraits(battlerTraits, ABILITY_BIG_PECKS)&& statId == STAT_DEF) + battlerAbility = ABILITY_BIG_PECKS; BattleScriptPush(BS_ptr); gBattleScripting.battler = battler; gBattlerAbility = battler; gBattlescriptCurrInstr = BattleScript_AbilityNoSpecificStatLoss; gLastUsedAbility = battlerAbility; - RecordAbilityBattle(battler, gLastUsedAbility); + PushTraitStack(battler, battlerAbility); + RecordAbilityBattle(battler, battlerAbility); } return STAT_CHANGE_DIDNT_WORK; } - else if (battlerAbility == ABILITY_MIRROR_ARMOR && !flags.mirrorArmored && gBattlerAttacker != gBattlerTarget && battler == gBattlerTarget) + else if (SearchTraits(battlerTraits, ABILITY_MIRROR_ARMOR) && !flags.mirrorArmored && gBattlerAttacker != gBattlerTarget && battler == gBattlerTarget) { if (GetMoveEffect(gCurrentMove) == EFFECT_PARTING_SHOT) gBattleScripting.animTargetsHit = 1; if (flags.allowPtr) { SET_STATCHANGER(statId, GET_STAT_BUFF_VALUE(statValue) | STAT_BUFF_NEGATIVE, TRUE); + PushTraitStack(battler, ABILITY_MIRROR_ARMOR); BattleScriptPush(BS_ptr); gBattleScripting.battler = battler; gBattlerAbility = battler; gBattlescriptCurrInstr = BattleScript_MirrorArmorReflect; - RecordAbilityBattle(battler, gBattleMons[battler].ability); + RecordAbilityBattle(battler, ABILITY_MIRROR_ARMOR); } return STAT_CHANGE_DIDNT_WORK; } @@ -10504,7 +10611,6 @@ static u32 ChangeStatBuffs(u32 battler, s8 statValue, enum Stat statId, union St gProtectStructs[battler].statRaised = TRUE; gBattleScripting.statChanger &= ~STAT_BUFF_NEGATIVE; - if (statIncrease) { // Check Mirror Herb / Opportunist @@ -10513,10 +10619,11 @@ static u32 ChangeStatBuffs(u32 battler, s8 statValue, enum Stat statId, union St if (IsBattlerAlly(index, battler)) continue; // Only triggers on opposing side - if (GetBattlerAbility(index) == ABILITY_OPPORTUNIST + if (BattlerHasTrait(index, ABILITY_OPPORTUNIST) && gProtectStructs[battler].activateOpportunist == 0) // don't activate opportunist on other mon's opportunist raises { - gProtectStructs[index].activateOpportunist = 2; // set stats to copy + PushTraitStack(index, ABILITY_OPPORTUNIST); + gProtectStructs[index].activateOpportunist = 2; // set stats to copy } if (GetBattlerHoldEffect(index) == HOLD_EFFECT_MIRROR_HERB) { @@ -10549,7 +10656,7 @@ static u32 ChangeStatBuffs(u32 battler, s8 statValue, enum Stat statId, union St if (gBattleMons[battler].statStages[statId] > MAX_STAT_STAGE) gBattleMons[battler].statStages[statId] = MAX_STAT_STAGE; - if (ShouldDefiantCompetitiveActivate(battler, battlerAbility)) + if (ShouldDefiantCompetitiveActivate(battler)) stats = 0; // use single stat animations when Defiant/Competitive activate else stats &= ~(1u << statId); @@ -10642,8 +10749,6 @@ static void Cmd_trynonvolatilestatus(void) if (!CanSetNonVolatileStatus( gBattlerAttacker, gBattlerTarget, - GetBattlerAbility(gBattlerAttacker), - GetBattlerAbility(gBattlerTarget), GetMoveNonVolatileStatus(gCurrentMove), RUN_SCRIPT)) canInflictStatus = FALSE; @@ -10998,7 +11103,6 @@ static void Cmd_tryKO(void) enum BattleMoveEffects effect = GetMoveEffect(gCurrentMove); enum HoldEffect holdEffect = GetBattlerHoldEffect(gBattlerTarget); - enum Ability targetAbility = GetBattlerAbility(gBattlerTarget); u32 rand = Random() % 100; u32 affectionScore = GetBattlerAffectionHearts(gBattlerTarget); u32 endured = NOT_ENDURED; @@ -11032,10 +11136,11 @@ static void Cmd_tryKO(void) endured = AFFECTION_ENDURED; } - if (targetAbility == ABILITY_STURDY) + if (BattlerHasTrait(gBattlerTarget, ABILITY_STURDY)) { gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED; gLastUsedAbility = ABILITY_STURDY; + PushTraitStack(gBattlerTarget, ABILITY_STURDY); gBattlescriptCurrInstr = BattleScript_SturdyPreventsOHKO; gBattlerAbility = gBattlerTarget; } @@ -11045,8 +11150,8 @@ static void Cmd_tryKO(void) if (gBattleMons[gBattlerTarget].level > gBattleMons[gBattlerAttacker].level) lands = NO_HIT; else if ((gBattleMons[gBattlerTarget].volatiles.lockOn && gDisableStructs[gBattlerTarget].battlerWithSureHit == gBattlerAttacker) - || IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_NO_GUARD) - || IsAbilityAndRecord(gBattlerTarget, targetAbility, ABILITY_NO_GUARD)) + || IsAbilityAndRecord(gBattlerAttacker, ABILITY_NO_GUARD) + || IsAbilityAndRecord(gBattlerTarget, ABILITY_NO_GUARD)) lands = SURE_HIT; else if (IsSemiInvulnerable(gBattlerTarget, CHECK_ALL)) lands = NO_HIT; @@ -11113,8 +11218,6 @@ static void Cmd_checknonvolatiletrigger(void) if (!CanSetNonVolatileStatus( gBattlerAttacker, gBattlerTarget, - GetBattlerAbility(gBattlerAttacker), - GetBattlerAbility(gBattlerTarget), cmd->nonVolatile, CHECK_TRIGGER)) gBattlescriptCurrInstr = cmd->failInstr; @@ -11143,10 +11246,11 @@ static void Cmd_tryinfatuating(void) { CMD_ARGS(const u8 *failInstr); - if (GetBattlerAbility(gBattlerTarget) == ABILITY_OBLIVIOUS) + if (BattlerHasTrait(gBattlerTarget, ABILITY_OBLIVIOUS)) { gBattlescriptCurrInstr = BattleScript_NotAffectedAbilityPopUp; gLastUsedAbility = ABILITY_OBLIVIOUS; + PushTraitStack(gBattlerTarget, ABILITY_OBLIVIOUS); RecordAbilityBattle(gBattlerTarget, ABILITY_OBLIVIOUS); } else @@ -11844,7 +11948,7 @@ static void Cmd_healpartystatus(void) if (GetConfig(CONFIG_HEAL_BELL_SOUNDPROOF) == GEN_5 || GetConfig(CONFIG_HEAL_BELL_SOUNDPROOF) >= GEN_8 - || !(isSoundMove && GetBattlerAbility(gBattlerAttacker) == ABILITY_SOUNDPROOF)) + || !(isSoundMove && BattlerHasTrait(gBattlerAttacker, ABILITY_SOUNDPROOF))) { if (isSoundMove) gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_BELL; @@ -11855,7 +11959,10 @@ static void Cmd_healpartystatus(void) } else { - RecordAbilityBattle(gBattlerAttacker, gBattleMons[gBattlerAttacker].ability); + if (BattlerHasTrait(gBattlerAttacker, ABILITY_SOUNDPROOF)) + RecordAbilityBattle(gBattlerAttacker, ABILITY_SOUNDPROOF); + else + RecordAbilityBattle(gBattlerAttacker, gBattleMons[gBattlerAttacker].ability); gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_BELL_SOUNDPROOF_ATTACKER; } @@ -11864,14 +11971,17 @@ static void Cmd_healpartystatus(void) if (IsBattlerAlive(partner)) { if (GetConfig(CONFIG_HEAL_BELL_SOUNDPROOF) == GEN_5 - || !(isSoundMove && GetBattlerAbility(partner) == ABILITY_SOUNDPROOF)) + || !(isSoundMove && BattlerHasTrait(partner, ABILITY_SOUNDPROOF))) { gBattleMons[partner].status1 = 0; gBattleMons[partner].volatiles.nightmare = FALSE; } else { - RecordAbilityBattle(partner, gBattleMons[partner].ability); + if (BattlerHasTrait(partner, ABILITY_SOUNDPROOF)) + RecordAbilityBattle(partner, ABILITY_SOUNDPROOF); + else + RecordAbilityBattle(partner, gBattleMons[partner].ability); gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_BELL_SOUNDPROOF_PARTNER; } } @@ -11881,11 +11991,10 @@ static void Cmd_healpartystatus(void) for (i = 0; i < PARTY_SIZE; i++) { u16 species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG); - u8 abilityNum = GetMonData(&party[i], MON_DATA_ABILITY_NUM); if (species != SPECIES_NONE && species != SPECIES_EGG) { - enum Ability ability; + enum Ability ability = ABILITY_NONE; bool32 isAttacker = gBattlerPartyIndexes[gBattlerAttacker] == i; bool32 isDoublesPartner = gBattlerPartyIndexes[partner] == i && IsBattlerAlive(partner); @@ -11894,19 +12003,39 @@ static void Cmd_healpartystatus(void) ability = ABILITY_NONE; else if (GetConfig(CONFIG_HEAL_BELL_SOUNDPROOF) > GEN_5 && !isAttacker && !isDoublesPartner) ability = ABILITY_NONE; - else if (isAttacker) - ability = GetBattlerAbility(gBattlerAttacker); - else if (isDoublesPartner) - ability = GetBattlerAbility(partner); + else if (isAttacker && BattlerHasTrait(gBattlerAttacker, ABILITY_SOUNDPROOF)) + ability = ABILITY_SOUNDPROOF; + else if (isDoublesPartner && BattlerHasTrait(partner, ABILITY_SOUNDPROOF)) + ability = ABILITY_SOUNDPROOF; else { - ability = GetAbilityBySpecies(species, abilityNum); + if (MonHasTrait(&party[i], ABILITY_SOUNDPROOF)) + ability = ABILITY_SOUNDPROOF; + #if TESTING if (gTestRunnerEnabled) { u32 array = (!IsPartnerMonFromSameTrainer(gBattlerAttacker)) ? gBattlerAttacker : GetBattlerSide(gBattlerAttacker); - if (TestRunner_Battle_GetForcedAbility(array, i)) - ability = TestRunner_Battle_GetForcedAbility(array, i); + + for (u32 j = 0; j < MAX_MON_TRAITS; j++) + { + if ( j == 0 ) + { + if (TestRunner_Battle_GetForcedAbility(array, i) == ABILITY_SOUNDPROOF) + { + ability = ABILITY_SOUNDPROOF; + break; + } + } + else + { + if (TestRunner_Battle_GetForcedInnates(array, i, j - 1) == ABILITY_SOUNDPROOF) + { + ability = ABILITY_SOUNDPROOF; + break; + } + } + } } #endif } @@ -11981,7 +12110,7 @@ static void Cmd_trysetperishsong(void) for (i = 0; i < gBattlersCount; i++) { if (gBattleMons[i].volatiles.perishSong - || GetBattlerAbility(i) == ABILITY_SOUNDPROOF + || BattlerHasTrait(i, ABILITY_SOUNDPROOF) || BlocksPrankster(gCurrentMove, gBattlerAttacker, i, TRUE) || gBattleMons[i].volatiles.semiInvulnerable == STATE_COMMANDER) { @@ -12032,7 +12161,7 @@ static void Cmd_jumpifconfusedandstatmaxed(void) CMD_ARGS(u8 stat, const u8 *jumpInstr); if (gBattleMons[gBattlerTarget].volatiles.confusionTurns > 0 - && !CompareStat(gBattlerTarget, cmd->stat, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(gBattlerTarget))) + && !CompareStat(gBattlerTarget, cmd->stat, MAX_STAT_STAGE, CMP_LESS_THAN)) gBattlescriptCurrInstr = cmd->jumpInstr; // Fails if we're confused AND stat cannot be raised else gBattlescriptCurrInstr = cmd->nextInstr; @@ -12668,10 +12797,11 @@ static void Cmd_settaunt(void) { CMD_ARGS(const u8 *failInstr); - if (GetConfig(CONFIG_OBLIVIOUS_TAUNT) >= GEN_6 && GetBattlerAbility(gBattlerTarget) == ABILITY_OBLIVIOUS) + if (GetConfig(CONFIG_OBLIVIOUS_TAUNT) >= GEN_6 && BattlerHasTrait(gBattlerTarget, ABILITY_OBLIVIOUS)) { gBattlescriptCurrInstr = BattleScript_NotAffectedAbilityPopUp; gLastUsedAbility = ABILITY_OBLIVIOUS; + PushTraitStack(gBattlerTarget, ABILITY_OBLIVIOUS); RecordAbilityBattle(gBattlerTarget, ABILITY_OBLIVIOUS); } else if (gDisableStructs[gBattlerTarget].tauntTimer == 0) @@ -12770,11 +12900,11 @@ static void Cmd_tryswapitems(void) gBattlescriptCurrInstr = cmd->failInstr; } // check if ability prevents swapping - else if (GetBattlerAbility(gBattlerTarget) == ABILITY_STICKY_HOLD) + else if (BattlerHasTrait(gBattlerTarget, ABILITY_STICKY_HOLD)) { gBattlescriptCurrInstr = BattleScript_StickyHoldActivates; gLastUsedAbility = gBattleMons[gBattlerTarget].ability; - RecordAbilityBattle(gBattlerTarget, gLastUsedAbility); + RecordAbilityBattle(gBattlerTarget, ABILITY_STICKY_HOLD); } // took a while, but all checks passed and items can be safely swapped else @@ -12797,9 +12927,9 @@ static void Cmd_tryswapitems(void) BtlController_EmitSetMonData(gBattlerTarget, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].item), &gBattleMons[gBattlerTarget].item); MarkBattlerForControllerExec(gBattlerTarget); - if (GetBattlerAbility(gBattlerTarget) != ABILITY_GORILLA_TACTICS) + if (!BattlerHasTrait(gBattlerTarget, ABILITY_GORILLA_TACTICS)) gBattleStruct->choicedMove[gBattlerTarget] = MOVE_NONE; - if (GetBattlerAbility(gBattlerAttacker) != ABILITY_GORILLA_TACTICS) + if (!BattlerHasTrait(gBattlerAttacker, ABILITY_GORILLA_TACTICS)) gBattleStruct->choicedMove[gBattlerAttacker] = MOVE_NONE; gBattlescriptCurrInstr = cmd->nextInstr; @@ -12822,7 +12952,7 @@ static void Cmd_tryswapitems(void) } else if (oldItemAtk == ITEM_NONE && *newItemAtk != ITEM_NONE) { - if (GetBattlerAbility(gBattlerAttacker) == ABILITY_UNBURDEN && gDisableStructs[gBattlerAttacker].unburdenActive) + if (BattlerHasTrait(gBattlerAttacker, ABILITY_UNBURDEN) && gDisableStructs[gBattlerAttacker].unburdenActive) gDisableStructs[gBattlerAttacker].unburdenActive = FALSE; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ITEM_SWAP_TAKEN; // nothing -> <- target's item @@ -12873,6 +13003,7 @@ static void Cmd_trycopyability(void) else { RemoveAbilityFlags(battler); + PushTraitStack(battler, defAbility); //Not actually displayed but used to populate the slot gBattleScripting.abilityPopupOverwrite = gBattleMons[battler].ability; gBattleMons[battler].ability = gDisableStructs[battler].overwrittenAbility = defAbility; gLastUsedAbility = defAbility; @@ -12944,7 +13075,6 @@ static void Cmd_setgastroacid(void) static void Cmd_setyawn(void) { CMD_ARGS(const u8 *failInstr); - enum Ability ability = GetBattlerAbility(gBattlerTarget); enum HoldEffect holdEffect = GetBattlerHoldEffect(gBattlerTarget); if (gBattleMons[gBattlerTarget].volatiles.yawn @@ -12952,13 +13082,13 @@ static void Cmd_setyawn(void) { gBattlescriptCurrInstr = cmd->failInstr; } - else if (IsBattlerTerrainAffected(gBattlerTarget, ability, holdEffect, STATUS_FIELD_ELECTRIC_TERRAIN)) + else if (IsBattlerTerrainAffected(gBattlerTarget, holdEffect, STATUS_FIELD_ELECTRIC_TERRAIN)) { // When Yawn is used while Electric Terrain is set and drowsiness is set from Yawn being used against target in the previous turn: // "But it failed" will display first. gBattlescriptCurrInstr = BattleScript_ElectricTerrainPrevents; } - else if (IsBattlerTerrainAffected(gBattlerTarget, ability, holdEffect, STATUS_FIELD_MISTY_TERRAIN)) + else if (IsBattlerTerrainAffected(gBattlerTarget, holdEffect, STATUS_FIELD_MISTY_TERRAIN)) { // When Yawn is used while Misty Terrain is set and drowsiness is set from Yawn being used against target in the previous turn: // "But it failed" will display first. @@ -13206,34 +13336,31 @@ static void Cmd_switchoutabilities(void) } } - switch (GetBattlerAbility(battler)) - { - case ABILITY_NATURAL_CURE: + if (BattlerHasTrait(battler, ABILITY_NATURAL_CURE)) + { if (gBattleMons[battler].status1 & STATUS1_SLEEP) TryDeactivateSleepClause(GetBattlerSide(battler), gBattlerPartyIndexes[battler]); - gBattleMons[battler].status1 = 0; - BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, - 1u << gBattleStruct->battlerPartyIndexes[battler], - sizeof(gBattleMons[battler].status1), - &gBattleMons[battler].status1); - MarkBattlerForControllerExec(battler); - break; - case ABILITY_REGENERATOR: - { - u32 regenerate = GetNonDynamaxMaxHP(battler) / 3; - regenerate += gBattleMons[battler].hp; - if (regenerate > gBattleMons[battler].maxHP) - regenerate = gBattleMons[battler].maxHP; - BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_HP_BATTLE, - 1u << gBattleStruct->battlerPartyIndexes[battler], - sizeof(regenerate), - ®enerate); - MarkBattlerForControllerExec(battler); - break; - } - default: - break; + gBattleMons[battler].status1 = 0; + BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, + 1u << gBattleStruct->battlerPartyIndexes[battler], + sizeof(gBattleMons[battler].status1), + &gBattleMons[battler].status1); + MarkBattlerForControllerExec(battler); + } + if (BattlerHasTrait(battler, ABILITY_REGENERATOR)) + { + { + u32 regenerate = GetNonDynamaxMaxHP(battler) / 3; + regenerate += gBattleMons[battler].hp; + if (regenerate > gBattleMons[battler].maxHP) + regenerate = gBattleMons[battler].maxHP; + BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_HP_BATTLE, + 1u << gBattleStruct->battlerPartyIndexes[battler], + sizeof(regenerate), + ®enerate); + MarkBattlerForControllerExec(battler); + } } gBattlescriptCurrInstr = cmd->nextInstr; @@ -13262,7 +13389,6 @@ static void Cmd_pickup(void) u32 i, j; u16 species, heldItem; u8 lvlDivBy10; - enum Ability ability; if (!InBattlePike()) // No items in Battle Pike. { @@ -13275,9 +13401,7 @@ static void Cmd_pickup(void) if (lvlDivBy10 > 9) lvlDivBy10 = 9; - ability = GetSpeciesAbility(species, GetMonData(&gPlayerParty[i], MON_DATA_ABILITY_NUM)); - - if (ability == ABILITY_PICKUP + if (MonHasTrait(&gPlayerParty[i], ABILITY_PICKUP) && species != SPECIES_NONE && species != SPECIES_EGG && heldItem == ITEM_NONE @@ -13304,7 +13428,7 @@ static void Cmd_pickup(void) } } } - else if (ability == ABILITY_HONEY_GATHER + else if (MonHasTrait(&gPlayerParty[i], ABILITY_HONEY_GATHER) && species != 0 && species != SPECIES_EGG && heldItem == ITEM_NONE) @@ -13408,7 +13532,7 @@ bool32 DoesSubstituteBlockMove(u32 battlerAtk, u32 battlerDef, u32 move) return TRUE; else if (GetMoveEffect(move) == EFFECT_TRANSFORM || GetMoveEffect(move) == EFFECT_SKY_DROP) return TRUE; - else if (IsAbilityAndRecord(battlerAtk, GetBattlerAbility(battlerAtk), ABILITY_INFILTRATOR)) + else if (IsAbilityAndRecord(battlerAtk, ABILITY_INFILTRATOR)) return FALSE; else return TRUE; @@ -13419,9 +13543,10 @@ bool32 DoesDisguiseBlockMove(u32 battler, u32 move) if (!IsMimikyuDisguised(battler) || gBattleMons[battler].volatiles.transformed || (!gProtectStructs[battler].confusionSelfDmg && IsBattleMoveStatus(move)) - || !IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_DISGUISE)) + || !IsAbilityAndRecord(battler, ABILITY_DISGUISE)) return FALSE; else + PushTraitStack(battler, ABILITY_DISGUISE); return TRUE; } @@ -13437,11 +13562,11 @@ static void Cmd_jumpifsubstituteblocks(void) static void Cmd_tryrecycleitem(void) { - CMD_ARGS(const u8 *failInstr); + CMD_ARGS(u8 type, const u8 *failInstr); u16 *usedHeldItem; - if (gCurrentMove == MOVE_NONE && GetBattlerAbility(gBattlerAttacker) == ABILITY_PICKUP) + if (gCurrentMove == MOVE_NONE && cmd->type == RECYCLE_ITEM_PICKUP) usedHeldItem = &GetBattlerPartyState(gBattlerTarget)->usedHeldItem; else usedHeldItem = &GetBattlerPartyState(gBattlerAttacker)->usedHeldItem; @@ -13782,7 +13907,7 @@ static void Cmd_handleballthrow(void) } break; case BALL_DREAM: - if (B_DREAM_BALL_MODIFIER >= GEN_8 && (gBattleMons[gBattlerTarget].status1 & STATUS1_SLEEP || GetBattlerAbility(gBattlerTarget) == ABILITY_COMATOSE)) + if (B_DREAM_BALL_MODIFIER >= GEN_8 && (gBattleMons[gBattlerTarget].status1 & STATUS1_SLEEP || BattlerHasTrait(gBattlerTarget, ABILITY_COMATOSE))) ballMultiplier = 400; break; case BALL_BEAST: @@ -14416,10 +14541,11 @@ static void Cmd_jumpifcaptivateaffected(void) { CMD_ARGS(const u8 *jumpInstr); - if (GetBattlerAbility(gBattlerTarget) == ABILITY_OBLIVIOUS) + if (BattlerHasTrait(gBattlerTarget, ABILITY_OBLIVIOUS)) { gBattlescriptCurrInstr = BattleScript_NotAffectedAbilityPopUp; gLastUsedAbility = ABILITY_OBLIVIOUS; + PushTraitStack(gBattlerTarget, ABILITY_OBLIVIOUS); RecordAbilityBattle(gBattlerTarget, ABILITY_OBLIVIOUS); } else if (AreBattlersOfOppositeGender(gBattlerAttacker, gBattlerTarget)) @@ -14458,7 +14584,7 @@ static void Cmd_tryoverwriteability(void) CMD_ARGS(const u8 *failInstr); if (gAbilitiesInfo[gBattleMons[gBattlerTarget].ability].cantBeOverwritten - || gBattleMons[gBattlerTarget].ability == GetMoveOverwriteAbility(gCurrentMove)) + || BattlerHasTrait(gBattlerTarget, GetMoveOverwriteAbility(gCurrentMove))) { RecordAbilityBattle(gBattlerTarget, gBattleMons[gBattlerTarget].ability); gBattlescriptCurrInstr = cmd->failInstr; @@ -14474,6 +14600,7 @@ static void Cmd_tryoverwriteability(void) gSpecialStatuses[gBattlerTarget].neutralizingGasRemoved = TRUE; RemoveAbilityFlags(gBattlerTarget); + PushTraitStack(gBattlerTarget, GetMoveOverwriteAbility(gCurrentMove)); gBattleScripting.abilityPopupOverwrite = gBattleMons[gBattlerTarget].ability; gBattleMons[gBattlerTarget].ability = gDisableStructs[gBattlerTarget].overwrittenAbility = GetMoveOverwriteAbility(gCurrentMove); gBattlescriptCurrInstr = cmd->nextInstr; @@ -14719,17 +14846,16 @@ static bool32 IsFinalStrikeEffect(enum MoveEffect moveEffect) } } -static bool32 CanAbilityPreventStatLoss(enum Ability abilityDef) +static bool8 CanBattlerPreventStatLoss(u16 battler) { - switch (abilityDef) - { - case ABILITY_CLEAR_BODY: - case ABILITY_FULL_METAL_BODY: - case ABILITY_WHITE_SMOKE: + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battler); + + if (SearchTraits(battlerTraits, ABILITY_CLEAR_BODY) + || SearchTraits(battlerTraits, ABILITY_FULL_METAL_BODY) + || SearchTraits(battlerTraits, ABILITY_WHITE_SMOKE)) return TRUE; - default: - break; - } + return FALSE; } @@ -14792,6 +14918,7 @@ void BS_TrySymbiosis(void) gLastUsedAbility = gBattleMons[partner].ability; gBattleScripting.battler = gBattlerAbility = partner; gEffectBattler = battler; + PushTraitStack(partner, ABILITY_SYMBIOSIS); BattleScriptCall(BattleScript_SymbiosisActivates); return; } @@ -15220,7 +15347,7 @@ void BS_JumpIfTerrainAffected(void) NATIVE_ARGS(u8 battler, u32 flags, const u8 *jumpInstr); u32 battler = GetBattlerForBattleScript(cmd->battler); - if (IsBattlerTerrainAffected(battler, GetBattlerAbility(battler), GetBattlerHoldEffect(battler), cmd->flags)) + if (IsBattlerTerrainAffected(battler,GetBattlerHoldEffect(battler), cmd->flags)) gBattlescriptCurrInstr = cmd->jumpInstr; else gBattlescriptCurrInstr = cmd->nextInstr; @@ -15458,7 +15585,7 @@ void BS_TryHealPulse(void) else { s32 healAmount = 0; - if (GetBattlerAbility(gBattlerAttacker) == ABILITY_MEGA_LAUNCHER && IsPulseMove(gCurrentMove)) + if (BattlerHasTrait(gBattlerAttacker, ABILITY_MEGA_LAUNCHER) && IsPulseMove(gCurrentMove)) healAmount = GetNonDynamaxMaxHP(gBattlerTarget) * 75 / 100; else if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN && GetMoveEffectArg_MoveProperty(gCurrentMove) == MOVE_EFFECT_FLORAL_HEALING) healAmount = GetNonDynamaxMaxHP(gBattlerTarget) * 2 / 3; @@ -15623,12 +15750,13 @@ void BS_TryGulpMissile(void) if ((gBattleMons[gBattlerAttacker].species == SPECIES_CRAMORANT) && (gCurrentMove == MOVE_DIVE) - && GetBattlerAbility(gBattlerAttacker) == ABILITY_GULP_MISSILE + && BattlerHasTrait(gBattlerAttacker, ABILITY_GULP_MISSILE) && TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_HP_PERCENT)) { gBattleScripting.battler = gBattlerAttacker; + PushTraitStack(gBattlerAttacker, ABILITY_GULP_MISSILE); gBattlescriptCurrInstr = BattleScript_GulpMissileFormChange; - } + } else { gBattlescriptCurrInstr = cmd->nextInstr; @@ -15643,9 +15771,10 @@ void BS_TryActivateGulpMissile(void) && IsBattlerAlive(gBattlerAttacker) && IsBattlerTurnDamaged(gBattlerTarget) && gBattleMons[gBattlerTarget].species != SPECIES_CRAMORANT - && GetBattlerAbility(gBattlerTarget) == ABILITY_GULP_MISSILE) + && BattlerHasTrait(gBattlerTarget, ABILITY_GULP_MISSILE)) { - if (!IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_MAGIC_GUARD)) + PushTraitStack(gBattlerAttacker, ABILITY_GULP_MISSILE); + if (!BattlerHasTrait(gBattlerAttacker, ABILITY_MAGIC_GUARD)) SetPassiveDamageAmount(gBattlerTarget, GetNonDynamaxMaxHP(gBattlerAttacker) / 4); switch(gBattleMons[gBattlerTarget].species) @@ -15803,7 +15932,7 @@ void BS_CanTarShotWork(void) NATIVE_ARGS(const u8 *failInstr); // Tar Shot fails if the target can't be made weaker to fire and it's speed can't be lowered further if (!(gDisableStructs[gBattlerTarget].tarShot || GetActiveGimmick(gBattlerTarget) == GIMMICK_TERA) - || CompareStat(gBattlerTarget, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(gBattlerTarget))) + || CompareStat(gBattlerTarget, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN)) gBattlescriptCurrInstr = cmd->nextInstr; else gBattlescriptCurrInstr = cmd->failInstr; @@ -15813,11 +15942,12 @@ void BS_JumpIfBlockedBySoundproof(void) { NATIVE_ARGS(u8 battler, const u8 *jumpInstr); u32 battler = GetBattlerForBattleScript(cmd->battler); - if (IsSoundMove(gCurrentMove) && GetBattlerAbility(battler) == ABILITY_SOUNDPROOF) + if (IsSoundMove(gCurrentMove) && BattlerHasTrait(battler, ABILITY_SOUNDPROOF)) { gLastUsedAbility = ABILITY_SOUNDPROOF; + PushTraitStack(battler, ABILITY_SOUNDPROOF); gBattlescriptCurrInstr = cmd->jumpInstr; - RecordAbilityBattle(battler, gLastUsedAbility); + RecordAbilityBattle(battler, ABILITY_SOUNDPROOF); gBattlerAbility = battler; } else @@ -15870,12 +16000,18 @@ void BS_TryWindRiderPower(void) NATIVE_ARGS(u8 battler, const u8 *failInstr); u32 battler = GetBattlerForBattleScript(cmd->battler); - enum Ability ability = GetBattlerAbility(battler); + enum Ability ability = ABILITY_NONE; + + if (BattlerHasTrait(battler, ABILITY_WIND_RIDER)) + ability = ABILITY_WIND_RIDER; + else if (BattlerHasTrait(battler, ABILITY_WIND_POWER)) + ability = ABILITY_WIND_POWER; + if (IsBattlerAlly(battler, gBattlerAttacker) - && (ability == ABILITY_WIND_RIDER || ability == ABILITY_WIND_POWER)) + && ability != ABILITY_NONE) { gLastUsedAbility = ability; - RecordAbilityBattle(battler, gLastUsedAbility); + RecordAbilityBattle(battler, ability); gBattlerAbility = gBattleScripting.battler = battler; gBattlescriptCurrInstr = cmd->nextInstr; } @@ -15891,7 +16027,7 @@ void BS_ActivateWeatherChangeAbilities(void) u32 battler = GetBattlerForBattleScript(cmd->battler); gBattlescriptCurrInstr = cmd->nextInstr; - AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, battler, 0, 0, 0); + AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, battler, 0, 0); } void BS_ActivateTerrainChangeAbilities(void) @@ -15900,7 +16036,7 @@ void BS_ActivateTerrainChangeAbilities(void) u32 battler = GetBattlerForBattleScript(cmd->battler); gBattlescriptCurrInstr = cmd->nextInstr; - AbilityBattleEffects(ABILITYEFFECT_ON_TERRAIN, battler, 0, 0, 0); + AbilityBattleEffects(ABILITYEFFECT_ON_TERRAIN, battler, 0, 0); } void BS_ResetTerrainAbilityFlags(void) @@ -15986,17 +16122,17 @@ static void UpdatePokeFlutePartyStatus(struct Pokemon* party, u8 position) s32 i; u8 battler; u32 monToCheck, status; - u16 species, abilityNum; + u16 species; //abilityNum; monToCheck = 0; for (i = 0; i < PARTY_SIZE; i++) { species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG); - abilityNum = GetMonData(&party[i], MON_DATA_ABILITY_NUM); + //abilityNum = GetMonData(&party[i], MON_DATA_ABILITY_NUM); status = GetMonData(&party[i], MON_DATA_STATUS); if (species != SPECIES_NONE && species != SPECIES_EGG && status & AILMENT_FNT - && GetAbilityBySpecies(species, abilityNum) != ABILITY_SOUNDPROOF) + && !MonHasTrait(&party[i], ABILITY_SOUNDPROOF)) monToCheck |= (1 << i); } if (monToCheck) @@ -16017,7 +16153,7 @@ void BS_CheckPokeFlute(void) s32 i; for (i = 0; i < gBattlersCount; i++) { - if (GetBattlerAbility(i) != ABILITY_SOUNDPROOF) + if (!BattlerHasTrait(i, ABILITY_SOUNDPROOF)) { gBattleMons[i].status1 &= ~STATUS1_SLEEP; gBattleMons[i].volatiles.nightmare = FALSE; @@ -16057,7 +16193,7 @@ void BS_TrySpectralThiefSteal(void) return; } - bool32 contrary = GetBattlerAbility(gBattlerAttacker) == ABILITY_CONTRARY; + bool32 contrary = BattlerHasTrait(gBattlerAttacker, ABILITY_CONTRARY); gBattleStruct->stolenStats[0] = 0; // Stats to steal. gBattleScripting.animArg1 = 0; for (enum Stat stat = STAT_ATK; stat < NUM_BATTLE_STATS; stat++) @@ -16203,7 +16339,7 @@ void BS_SwapStats(void) static void TrySetParalysis(const u8 *nextInstr, const u8 *failInstr) { - if (CanBeParalyzed(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerTarget))) + if (CanBeParalyzed(gBattlerAttacker, gBattlerTarget)) SetNonVolatileStatus(gBattlerTarget, MOVE_EFFECT_PARALYSIS, nextInstr, TRIGGER_ON_MOVE); else gBattlescriptCurrInstr = failInstr; @@ -16211,7 +16347,7 @@ static void TrySetParalysis(const u8 *nextInstr, const u8 *failInstr) static void TrySetPoison(const u8 *nextInstr, const u8 *failInstr) { - if (CanBePoisoned(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerAbility(gBattlerTarget))) + if (CanBePoisoned(gBattlerAttacker, gBattlerTarget)) SetNonVolatileStatus(gBattlerTarget, MOVE_EFFECT_POISON, nextInstr, TRIGGER_ON_MOVE); else gBattlescriptCurrInstr = failInstr; @@ -16219,7 +16355,7 @@ static void TrySetPoison(const u8 *nextInstr, const u8 *failInstr) static void TrySetSleep(const u8 *nextInstr, const u8 *failInstr) { - if (CanBeSlept(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerTarget), BLOCKED_BY_SLEEP_CLAUSE)) + if (CanBeSlept(gBattlerAttacker, gBattlerTarget, BLOCKED_BY_SLEEP_CLAUSE)) SetNonVolatileStatus(gBattlerTarget, MOVE_EFFECT_SLEEP, nextInstr, TRIGGER_ON_MOVE); else gBattlescriptCurrInstr = failInstr; @@ -16287,7 +16423,7 @@ void BS_TrySetInfatuation(void) NATIVE_ARGS(const u8 *failInstr); if (!gBattleMons[gBattlerTarget].volatiles.infatuation - && gBattleMons[gBattlerTarget].ability != ABILITY_OBLIVIOUS + && !BattlerHasTrait(gBattlerTarget, ABILITY_OBLIVIOUS) && !IsAbilityOnSide(gBattlerTarget, ABILITY_AROMA_VEIL) && AreBattlersOfOppositeGender(gBattlerAttacker, gBattlerTarget)) { @@ -16392,39 +16528,38 @@ void BS_JumpIfIntimidateAbilityPrevented(void) { NATIVE_ARGS(); - bool32 hasAbility = FALSE; - enum Ability ability = GetBattlerAbility(gBattlerTarget); + enum Ability ability = ABILITY_NONE; + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(gBattlerTarget); - switch (ability) + if (SearchTraits(battlerTraits, ABILITY_INNER_FOCUS)) + ability = ABILITY_INNER_FOCUS; + else if (SearchTraits(battlerTraits, ABILITY_SCRAPPY)) + ability = ABILITY_SCRAPPY; + else if (SearchTraits(battlerTraits, ABILITY_OWN_TEMPO)) + ability = ABILITY_OWN_TEMPO; + else if (SearchTraits(battlerTraits, ABILITY_OBLIVIOUS)) + ability = ABILITY_OBLIVIOUS; + + if (SearchTraits(battlerTraits, ABILITY_GUARD_DOG)) { - case ABILITY_INNER_FOCUS: - case ABILITY_SCRAPPY: - case ABILITY_OWN_TEMPO: - case ABILITY_OBLIVIOUS: - if (GetConfig(CONFIG_UPDATED_INTIMIDATE) >= GEN_8) - { - hasAbility = TRUE; - gBattlescriptCurrInstr = BattleScript_IntimidatePrevented; - } - else - { - gBattlescriptCurrInstr = cmd->nextInstr; - } - break; - case ABILITY_GUARD_DOG: - hasAbility = TRUE; + gLastUsedAbility = ability = ABILITY_GUARD_DOG; + gBattlerAbility = gBattlerTarget; + PushTraitStack(gBattlerTarget, ABILITY_GUARD_DOG); gBattlescriptCurrInstr = BattleScript_IntimidateInReverse; - break; - default: - gBattlescriptCurrInstr = cmd->nextInstr; - break; + RecordAbilityBattle(gBattlerTarget, ABILITY_GUARD_DOG); } - - if (hasAbility) + else if (ability != ABILITY_NONE && GetConfig(CONFIG_UPDATED_INTIMIDATE) >= GEN_8) { gLastUsedAbility = ability; gBattlerAbility = gBattlerTarget; - RecordAbilityBattle(gBattlerTarget, gLastUsedAbility); + PushTraitStack(gBattlerTarget, ability); + gBattlescriptCurrInstr = BattleScript_IntimidatePrevented; + RecordAbilityBattle(gBattlerTarget, ability); + } + else + { + gBattlescriptCurrInstr = cmd->nextInstr; } } @@ -16446,7 +16581,7 @@ void BS_TryFlingHoldEffect(void) enum HoldEffect holdEffect = GetItemHoldEffect(gBattleStruct->flingItem); gBattleStruct->flingItem = ITEM_NONE; - if (IsMoveEffectBlockedByTarget(GetBattlerAbility(gBattlerTarget))) + if (IsMoveEffectBlockedByTarget()) { gBattlescriptCurrInstr = BattleScript_FlingBlockedByShieldDust; return; @@ -16511,9 +16646,8 @@ void BS_TryBoosterEnergy(void) if (holdEffect != HOLD_EFFECT_BOOSTER_ENERGY) continue; - enum Ability ability = GetBattlerAbility(battler); - if (!(ability == ABILITY_PROTOSYNTHESIS && cmd->onFieldStatus != ON_TERRAIN) - && !(ability == ABILITY_QUARK_DRIVE && cmd->onFieldStatus != ON_WEATHER)) + if (!(BattlerHasTrait(battler, ABILITY_PROTOSYNTHESIS) && cmd->onFieldStatus != ON_TERRAIN) + && !(BattlerHasTrait(battler, ABILITY_QUARK_DRIVE) && cmd->onFieldStatus != ON_WEATHER)) continue; if (ItemBattleEffects(battler, 0, holdEffect, IsOnEffectActivation)) @@ -16806,7 +16940,7 @@ void BS_TryAcupressure(void) u32 bits = 0; for (enum Stat stat = STAT_ATK; stat < NUM_BATTLE_STATS; stat++) { - if (CompareStat(gBattlerTarget, stat, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(gBattlerTarget))) + if (CompareStat(gBattlerTarget, stat, MAX_STAT_STAGE, CMP_LESS_THAN)) bits |= 1u << stat; } if (bits) @@ -17124,16 +17258,16 @@ void BS_SwitchinAbilities(void) NATIVE_ARGS(u8 battler); u32 battler = GetBattlerForBattleScript(cmd->battler); gBattlescriptCurrInstr = cmd->nextInstr; - AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, battler, 0, 0, 0); - AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, battler, 0, 0, 0); - AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, 0, 0, 0); - AbilityBattleEffects(ABILITYEFFECT_IMMUNITY, battler, 0, 0, 0); + AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, battler, 0, 0); + AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, battler, 0, 0); + AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, 0, 0); + AbilityBattleEffects(ABILITYEFFECT_IMMUNITY, battler, 0, 0); if (gBattleWeather & B_WEATHER_ANY && HasWeatherEffect()) - AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, battler, 0, 0, 0); + AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, battler, 0, 0); if (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY) - AbilityBattleEffects(ABILITYEFFECT_ON_TERRAIN, battler, 0, 0, 0); + AbilityBattleEffects(ABILITYEFFECT_ON_TERRAIN, battler, 0, 0); } void BS_InstantHpDrop(void) @@ -17183,11 +17317,24 @@ void BS_TryActivateReceiver(void) u32 battler = GetBattlerForBattleScript(cmd->battler); gBattlerAbility = BATTLE_PARTNER(battler); u32 partnerAbility = GetBattlerAbility(gBattlerAbility); + + if (BattlerHasTrait(gBattlerAbility, ABILITY_RECEIVER) > 1) + { + // Receiver replaces your main Ability, so it should not be an Innate. + DebugPrintf("Receiver not set as main Ability"); + } + if (BattlerHasTrait(gBattlerAbility, ABILITY_POWER_OF_ALCHEMY) > 1) + { + // Power of Alchemy replaces your main Ability, so it should not be an Innate. + DebugPrintf("Power of Alchemy not set as main Ability"); + } + if (IsBattlerAlive(gBattlerAbility) - && (partnerAbility == ABILITY_RECEIVER || partnerAbility == ABILITY_POWER_OF_ALCHEMY) + && (partnerAbility == ABILITY_RECEIVER || partnerAbility == ABILITY_POWER_OF_ALCHEMY) // Should not be Innates (Multi) && GetBattlerHoldEffectIgnoreAbility(battler) != HOLD_EFFECT_ABILITY_SHIELD && !gAbilitiesInfo[gBattleMons[battler].ability].cantBeCopied) { + PushTraitStack(gBattlerAbility, partnerAbility); // Should not be Innates (Multi) gBattleStruct->tracedAbility[gBattlerAbility] = gBattleMons[battler].ability; // re-using the variable for trace gBattleScripting.battler = battler; BattleScriptPush(cmd->nextInstr); @@ -17205,14 +17352,14 @@ void BS_TryActivateSoulheart(void) while (gBattleStruct->soulheartBattlerId < gBattlersCount) { gBattleScripting.battler = gBattleStruct->soulheartBattlerId++; - u32 ability = GetBattlerAbility(gBattleScripting.battler); - if (ability == ABILITY_SOUL_HEART + if (BattlerHasTrait(gBattleScripting.battler, ABILITY_SOUL_HEART) && IsBattlerAlive(gBattleScripting.battler) && !NoAliveMonsForEitherParty() - && CompareStat(gBattleScripting.battler, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN, ability)) + && CompareStat(gBattleScripting.battler, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN)) { SET_STATCHANGER(STAT_SPATK, 1, FALSE); PREPARE_STAT_BUFFER(gBattleTextBuff1, STAT_SPATK); + PushTraitStack(gBattleScripting.battler, ABILITY_SOUL_HEART); BattleScriptCall(BattleScript_ScriptingAbilityStatRaise); return; } @@ -17278,7 +17425,7 @@ void BS_TryEntrainment(void) else { RemoveAbilityFlags(gBattlerTarget); - gBattleMons[gBattlerTarget].ability = gDisableStructs[gBattlerTarget].overwrittenAbility = gBattleMons[gBattlerAttacker].ability; + gBattleMons[gBattlerTarget].ability = gDisableStructs[gBattlerTarget].overwrittenAbility = gDisplayAbility = gBattleMons[gBattlerAttacker].ability; gBattlescriptCurrInstr = cmd->nextInstr; } } @@ -17416,7 +17563,16 @@ void BS_TryInstruct(void) void BS_ShowAbilityPopup(void) { NATIVE_ARGS(); - CreateAbilityPopUp(gBattlerAbility, gBattleMons[gBattlerAbility].ability, (IsDoubleBattle()) != 0); + + gDisplayBattler = PullTraitStackBattler(); + gDisplayAbility = PullTraitStackAbility(); + + if (gDisplayBattler != MAX_BATTLERS_COUNT) + gBattleScripting.battler = gDisplayBattler; + + PopTraitStack(); + + CreateAbilityPopUp(gDisplayBattler, gDisplayAbility, (IsDoubleBattle()) != 0); gBattlescriptCurrInstr = cmd->nextInstr; } @@ -17439,29 +17595,29 @@ void BS_JumpIfTargetAlly(void) void BS_TryPsychoShift(void) { NATIVE_ARGS(const u8 *failInstr, const u8 *sleepClauseFailInstr); - u32 targetAbility = GetBattlerAbility(gBattlerTarget); + // Psycho shift works - if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_POISON) && CanBePoisoned(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), targetAbility)) + if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_POISON) && CanBePoisoned(gBattlerAttacker, gBattlerTarget)) { gBattleCommunication[MULTISTRING_CHOOSER] = 0; } - else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_TOXIC_POISON) && CanBePoisoned(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), targetAbility)) + else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_TOXIC_POISON) && CanBePoisoned(gBattlerAttacker, gBattlerTarget)) { gBattleCommunication[MULTISTRING_CHOOSER] = 1; } - else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_BURN) && CanBeBurned(gBattlerAttacker, gBattlerTarget, targetAbility)) + else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_BURN) && CanBeBurned(gBattlerAttacker, gBattlerTarget)) { gBattleCommunication[MULTISTRING_CHOOSER] = 2; } - else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_PARALYSIS) && CanBeParalyzed(gBattlerAttacker, gBattlerTarget, targetAbility)) + else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_PARALYSIS) && CanBeParalyzed(gBattlerAttacker, gBattlerTarget)) { gBattleCommunication[MULTISTRING_CHOOSER] = 3; } - else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_SLEEP) && CanBeSlept(gBattlerAttacker, gBattlerTarget, targetAbility, BLOCKED_BY_SLEEP_CLAUSE)) + else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_SLEEP) && CanBeSlept(gBattlerAttacker, gBattlerTarget, BLOCKED_BY_SLEEP_CLAUSE)) { gBattleCommunication[MULTISTRING_CHOOSER] = 4; } - else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_FROSTBITE) && CanBeFrozen(gBattlerAttacker, gBattlerTarget, targetAbility)) + else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_FROSTBITE) && CanBeFrozen(gBattlerAttacker, gBattlerTarget)) { gBattleCommunication[MULTISTRING_CHOOSER] = 5; } @@ -17874,10 +18030,9 @@ void BS_TryToClearPrimalWeather(void) for (u32 i = 0; i < gBattlersCount; i++) { - enum Ability ability = GetBattlerAbility(i); - if (((ability == ABILITY_DESOLATE_LAND && gBattleWeather & B_WEATHER_SUN_PRIMAL) - || (ability == ABILITY_PRIMORDIAL_SEA && gBattleWeather & B_WEATHER_RAIN_PRIMAL) - || (ability == ABILITY_DELTA_STREAM && gBattleWeather & B_WEATHER_STRONG_WINDS)) + if (((BattlerHasTrait(i, ABILITY_DESOLATE_LAND) && gBattleWeather & B_WEATHER_SUN_PRIMAL) + || (BattlerHasTrait(i, ABILITY_PRIMORDIAL_SEA) && gBattleWeather & B_WEATHER_RAIN_PRIMAL) + || (BattlerHasTrait(i, ABILITY_DELTA_STREAM) && gBattleWeather & B_WEATHER_STRONG_WINDS)) && IsBattlerAlive(i)) shouldNotClear = TRUE; } @@ -18011,10 +18166,9 @@ void BS_JumpIfAbilityPreventsRest(void) { NATIVE_ARGS(u8 battler, const u8 *jumpInstr); u32 battler = GetBattlerForBattleScript(cmd->battler); - u32 ability = GetBattlerAbility(battler); - if (GetConfig(CONFIG_LEAF_GUARD_PREVENTS_REST) >= GEN_5 && IsLeafGuardProtected(battler, ability)) + if (GetConfig(CONFIG_LEAF_GUARD_PREVENTS_REST) >= GEN_5 && IsLeafGuardProtected(battler)) gBattlescriptCurrInstr = cmd->jumpInstr; - else if (IsShieldsDownProtected(battler, ability)) + else if (IsShieldsDownProtected(battler)) gBattlescriptCurrInstr = cmd->jumpInstr; else gBattlescriptCurrInstr = cmd->nextInstr; @@ -18036,11 +18190,10 @@ void BS_CutOneThirdHpAndRaiseStats(void) { NATIVE_ARGS(const u8 *failInstr); bool32 atLeastOneStatBoosted = FALSE; - u32 ability = GetBattlerAbility(gBattlerAttacker); for (u32 stat = 1; stat < NUM_STATS; stat++) { - if (CompareStat(gBattlerAttacker, stat, MAX_STAT_STAGE, CMP_LESS_THAN, ability)) + if (CompareStat(gBattlerAttacker, stat, MAX_STAT_STAGE, CMP_LESS_THAN)) { atLeastOneStatBoosted = TRUE; break; @@ -18126,7 +18279,7 @@ void BS_TryAbsorbToxicSpikesOnFaint(void) return; } - if (IsBattlerGrounded(battler, GetBattlerAbility(battler), GetBattlerHoldEffect(battler)) + if (IsBattlerGrounded(battler, GetBattlerHoldEffect(battler)) && IS_BATTLER_OF_TYPE(battler, TYPE_POISON)) { gSideTimers[side].toxicSpikesAmount = 0; @@ -18178,3 +18331,11 @@ void BS_UndoDynamax(void) gBattlescriptCurrInstr = cmd->nextInstr; } + +void BS_PushTraitStack(void) +{ + NATIVE_ARGS(u8 battler, u16 ability); + u32 battler = GetBattlerForBattleScript(cmd->battler); + PushTraitStack(battler, cmd->ability); + gBattlescriptCurrInstr = cmd->nextInstr; +} diff --git a/src/battle_terastal.c b/src/battle_terastal.c index f0e720c93e6e..2cc02da68d2d 100644 --- a/src/battle_terastal.c +++ b/src/battle_terastal.c @@ -1,5 +1,6 @@ #include "global.h" #include "battle.h" +#include "battle_ai_main.h" #include "battle_anim.h" #include "battle_controllers.h" #include "battle_interface.h" @@ -134,6 +135,7 @@ bool32 IsTypeStellarBoosted(u32 battler, enum Type type) uq4_12_t GetTeraMultiplier(struct DamageContext *ctx) { enum Type teraType = GetBattlerTeraType(ctx->battlerAtk); + bool32 hasAdaptability = (BattlerHasTrait(ctx->battlerAtk, ABILITY_ADAPTABILITY)); // Safety check. if (GetActiveGimmick(ctx->battlerAtk) != GIMMICK_TERA) @@ -158,7 +160,7 @@ uq4_12_t GetTeraMultiplier(struct DamageContext *ctx) // Base and Tera type. if (ctx->moveType == teraType && IS_BATTLER_OF_BASE_TYPE(ctx->battlerAtk, ctx->moveType)) { - if (ctx->abilityAtk == ABILITY_ADAPTABILITY) + if (hasAdaptability) return UQ_4_12(2.25); else return UQ_4_12(2.0); @@ -167,7 +169,7 @@ uq4_12_t GetTeraMultiplier(struct DamageContext *ctx) else if ((ctx->moveType == teraType && !IS_BATTLER_OF_BASE_TYPE(ctx->battlerAtk, ctx->moveType)) || (ctx->moveType != teraType && IS_BATTLER_OF_BASE_TYPE(ctx->battlerAtk, ctx->moveType))) { - if (ctx->abilityAtk == ABILITY_ADAPTABILITY) + if (hasAdaptability) return UQ_4_12(2.0); else return UQ_4_12(1.5); diff --git a/src/battle_util.c b/src/battle_util.c index bcff0c4aee3b..10af9b76d7e9 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -216,7 +216,7 @@ static const struct BattleWeatherInfo sBattleWeatherInfo[BATTLE_WEATHER_COUNT] = // Helper function for actual dmg calcs during battle. For simulated AI dmg, CalcTypeEffectivenessMultiplier should be used directly // This should stay a static function. Ideally everything else is handled through CalcTypeEffectivenessMultiplier just like AI -static uq4_12_t CalcTypeEffectivenessMultiplierHelper(u32 move, enum Type moveType, u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef, bool32 recordAbilities) +static uq4_12_t CalcTypeEffectivenessMultiplierHelper(u32 move, enum Type moveType, u32 battlerAtk, u32 battlerDef, bool32 recordAbilities) { struct DamageContext ctx = {0}; ctx.battlerAtk = battlerAtk; @@ -224,8 +224,6 @@ static uq4_12_t CalcTypeEffectivenessMultiplierHelper(u32 move, enum Type moveTy ctx.move = ctx.chosenMove = move; ctx.moveType = moveType; ctx.updateFlags = recordAbilities; - ctx.abilityAtk = abilityAtk; - ctx.abilityDef = abilityDef; ctx.holdEffectAtk = GetBattlerHoldEffect(battlerAtk); ctx.holdEffectDef = GetBattlerHoldEffect(battlerDef); @@ -261,6 +259,7 @@ bool32 EndOrContinueWeather(void) for (u32 battler = 0; battler < gBattlersCount; battler++) { gDisableStructs[battler].weatherAbilityDone = FALSE; + gDisableStructs[battler].transformWeatherAbilityDone = FALSE; ResetParadoxWeatherStat(battler); } gBattleCommunication[MULTISTRING_CHOOSER] = sBattleWeatherInfo[currBattleWeather].endMessage; @@ -278,6 +277,14 @@ bool32 EndOrContinueWeather(void) return FALSE; } +static inline u32 CommonSwitchInAbilities(u32 battler, u16 trait, u8 traitDone, const u8 *BS_ptr) +{ + gSpecialStatuses[battler].switchInTraitDone[traitDone - 1] = TRUE; + PushTraitStack(battler, trait); + BattleScriptPushCursorAndCallback(BS_ptr); + return 1; // simulate effect ++ +} + // Gen5+ static u32 CalcBeatUpPower(void) { @@ -313,7 +320,7 @@ static bool32 ShouldTeraShellDistortTypeMatchups(u32 move, u32 battlerDef, enum && gBattleMons[battlerDef].species == SPECIES_TERAPAGOS_TERASTAL && gBattleMons[battlerDef].hp == gBattleMons[battlerDef].maxHP && !IsBattleMoveStatus(move) - && abilityDef == ABILITY_TERA_SHELL) + && BattlerHasTrait(battlerDef, ABILITY_TERA_SHELL)) return TRUE; return FALSE; @@ -354,16 +361,10 @@ static bool32 IsUnnerveAbilityOnOpposingSide(u32 battler) if (!IsBattlerAlive(battlerDef)) continue; - enum Ability ability = GetBattlerAbility(battlerDef); - switch (ability) - { - case ABILITY_UNNERVE: - case ABILITY_AS_ONE_ICE_RIDER: - case ABILITY_AS_ONE_SHADOW_RIDER: + if (BattlerHasTrait(battlerDef, ABILITY_UNNERVE) + || BattlerHasTrait(battlerDef, ABILITY_AS_ONE_ICE_RIDER) + || BattlerHasTrait(battlerDef, ABILITY_AS_ONE_SHADOW_RIDER)) return TRUE; - default: - break; - } } return FALSE; @@ -371,21 +372,20 @@ static bool32 IsUnnerveAbilityOnOpposingSide(u32 battler) bool32 IsAffectedByFollowMe(u32 battlerAtk, u32 defSide, u32 move) { - enum Ability ability = GetBattlerAbility(battlerAtk); enum BattleMoveEffects effect = GetMoveEffect(move); if (gSideTimers[defSide].followmeTimer == 0 || (!IsBattlerAlive(gSideTimers[defSide].followmeTarget) && !IsDragonDartsSecondHit(effect)) || effect == EFFECT_SNIPE_SHOT || effect == EFFECT_SKY_DROP - || IsAbilityAndRecord(battlerAtk, ability, ABILITY_PROPELLER_TAIL) - || IsAbilityAndRecord(battlerAtk, ability, ABILITY_STALWART)) + || IsAbilityAndRecord(battlerAtk, ABILITY_PROPELLER_TAIL) + || IsAbilityAndRecord(battlerAtk, ABILITY_STALWART)) return FALSE; if (effect == EFFECT_PURSUIT && IsPursuitTargetSet()) return FALSE; - if (gSideTimers[defSide].followmePowder && !IsAffectedByPowderMove(battlerAtk, ability, GetBattlerHoldEffect(battlerAtk))) + if (gSideTimers[defSide].followmePowder && !IsAffectedByPowderMove(battlerAtk, GetBattlerHoldEffect(battlerAtk))) return FALSE; return TRUE; @@ -398,7 +398,6 @@ bool32 HandleMoveTargetRedirection(void) enum Type moveType = GetBattleMoveType(gCurrentMove); enum BattleMoveEffects moveEffect = GetMoveEffect(gCurrentMove); u32 side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker)); - enum Ability ability = GetBattlerAbility(gBattleStruct->moveTarget[gBattlerAttacker]); if (IsAffectedByFollowMe(gBattlerAttacker, side, gCurrentMove) && moveTarget == MOVE_TARGET_SELECTED @@ -410,36 +409,40 @@ bool32 HandleMoveTargetRedirection(void) 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))) + && ((!BattlerHasTrait(gBattleStruct->moveTarget[gBattlerAttacker], ABILITY_LIGHTNING_ROD) && moveType == TYPE_ELECTRIC) + || (!BattlerHasTrait(gBattleStruct->moveTarget[gBattlerAttacker], ABILITY_STORM_DRAIN) && moveType == TYPE_WATER))) { // Find first battler that redirects the move (in turn order) - enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker); u32 battler; for (battler = 0; battler < gBattlersCount; battler++) { - 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)) + && ((BattlerHasTrait(battler, ABILITY_LIGHTNING_ROD) && moveType == TYPE_ELECTRIC) + || (BattlerHasTrait(battler, ABILITY_STORM_DRAIN) && moveType == TYPE_WATER)) && GetBattlerTurnOrderNum(battler) < redirectorOrderNum && moveEffect != EFFECT_SNIPE_SHOT && moveEffect != EFFECT_PLEDGE - && !IsAbilityAndRecord(gBattlerAttacker, abilityAtk, ABILITY_PROPELLER_TAIL) - && !IsAbilityAndRecord(gBattlerAttacker, abilityAtk, ABILITY_STALWART)) + && !IsAbilityAndRecord(gBattlerAttacker, ABILITY_PROPELLER_TAIL) + && !IsAbilityAndRecord(gBattlerAttacker, ABILITY_STALWART)) { redirectorOrderNum = GetBattlerTurnOrderNum(battler); } } if (redirectorOrderNum != MAX_BATTLERS_COUNT && gCurrentMove != MOVE_TEATIME) { - enum Ability battlerAbility; battler = gBattlerByTurnOrder[redirectorOrderNum]; - battlerAbility = GetBattlerAbility(battler); - RecordAbilityBattle(battler, battlerAbility); - gSpecialStatuses[battler].abilityRedirected = TRUE; + if (BattlerHasTrait(battler, ABILITY_LIGHTNING_ROD)) + { + gSpecialStatuses[battler].abilityRedirected = TRUE; + RecordAbilityBattle(battler, ABILITY_LIGHTNING_ROD); + } + else if (BattlerHasTrait(battler, ABILITY_STORM_DRAIN)) + { + gSpecialStatuses[battler].abilityRedirected = TRUE; + RecordAbilityBattle(battler, ABILITY_STORM_DRAIN); + } gBattlerTarget = battler; return TRUE; } @@ -699,7 +702,7 @@ bool32 TryRunFromBattle(u32 battler) { effect++; } - else if (GetBattlerAbility(battler) == ABILITY_RUN_AWAY) + else if (BattlerHasTrait(battler, ABILITY_RUN_AWAY)) { if (CurrentBattlePyramidLocation() != PYRAMID_LOCATION_NONE) { @@ -708,14 +711,16 @@ bool32 TryRunFromBattle(u32 battler) speedVar = (gBattleMons[battler].speed * pyramidMultiplier) / (gBattleMons[BATTLE_OPPOSITE(battler)].speed) + (gBattleStruct->runTries * 30); if (speedVar > (Random() & 0xFF)) { - gLastUsedAbility = ABILITY_RUN_AWAY; + gLastUsedAbility = gDisplayAbility = ABILITY_RUN_AWAY; + PushTraitStack(battler, ABILITY_RUN_AWAY); gProtectStructs[battler].fleeType = FLEE_ABILITY; effect++; } } else { - gLastUsedAbility = ABILITY_RUN_AWAY; + gLastUsedAbility = gDisplayAbility = ABILITY_RUN_AWAY; + PushTraitStack(battler, ABILITY_RUN_AWAY); gProtectStructs[battler].fleeType = FLEE_ABILITY; effect++; } @@ -805,7 +810,7 @@ void HandleAction_Run(void) else { if (GetBattlerHoldEffect(gBattlerAttacker) != HOLD_EFFECT_CAN_ALWAYS_RUN - && GetBattlerAbility(gBattlerAttacker) != ABILITY_RUN_AWAY + && !BattlerHasTrait(gBattlerAttacker, ABILITY_RUN_AWAY) && !CanBattlerEscape(gBattlerAttacker)) { gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ATTACKER_CANT_ESCAPE; @@ -1069,6 +1074,9 @@ u8 GetBattlerForBattleScript(u8 caseId) case BS_ABILITY_BATTLER: ret = gBattlerAbility; break; + case BS_SCRIPTING_PARTNER: + ret = BATTLE_PARTNER(gBattleScripting.battler); + break; } return ret; } @@ -1135,10 +1143,9 @@ const u8 *CheckSkyDropState(u32 battler, enum SkyDropState skyDropState) // If the target can be confused, confuse them. // Don't use CanBeConfused, can cause issues in edge cases. - enum Ability ability = GetBattlerAbility(otherSkyDropper); if (!(gBattleMons[otherSkyDropper].volatiles.confusionTurns > 0 - || IsAbilityAndRecord(otherSkyDropper, ability, ABILITY_OWN_TEMPO) - || IsBattlerTerrainAffected(otherSkyDropper, ability, GetBattlerHoldEffect(otherSkyDropper), STATUS_FIELD_MISTY_TERRAIN))) + || IsAbilityAndRecord(otherSkyDropper, ABILITY_OWN_TEMPO) + || IsBattlerTerrainAffected(otherSkyDropper, GetBattlerHoldEffect(otherSkyDropper), STATUS_FIELD_MISTY_TERRAIN))) { // Set confused status gBattleMons[otherSkyDropper].volatiles.confusionTurns = ((Random()) % 4) + 2; @@ -1236,10 +1243,10 @@ bool32 IsLastMonToMove(u32 battler) return TRUE; } -bool32 ShouldDefiantCompetitiveActivate(u32 battler, enum Ability ability) +bool32 ShouldDefiantCompetitiveActivate(u32 battler) { u32 side = GetBattlerSide(battler); - if (ability != ABILITY_DEFIANT && ability != ABILITY_COMPETITIVE) + if (!BattlerHasTrait(battler, ABILITY_DEFIANT) && !BattlerHasTrait(battler, ABILITY_COMPETITIVE)) return FALSE; // if an ally dropped the stats (except for Sticky Web), don't activate if (IsBattlerAlly(gSpecialStatuses[battler].changedStatsBattlerId, battler) && !gBattleScripting.stickyWebStatDrop) @@ -1253,8 +1260,10 @@ bool32 ShouldDefiantCompetitiveActivate(u32 battler, enum Ability ability) void PrepareStringBattle(enum StringID stringId, u32 battler) { - u16 battlerAbility = GetBattlerAbility(battler); - u16 targetAbility = GetBattlerAbility(gBattlerTarget); + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(gBattlerTarget); + bool32 hasContrary = (BattlerHasTrait(gBattlerTarget, ABILITY_CONTRARY)); + // Support for Contrary ability. // If a move attempted to raise stat - print "won't increase". // If a move attempted to lower stat - print "won't decrease". @@ -1275,25 +1284,42 @@ void PrepareStringBattle(enum StringID stringId, u32 battler) stringId = gStatUpStringIds[gBattleCommunication[MULTISTRING_CHOOSER]]; break; case STRINGID_STATSWONTINCREASE2: - if (battlerAbility == ABILITY_CONTRARY) + if (hasContrary) stringId = STRINGID_STATSWONTDECREASE2; break; case STRINGID_STATSWONTDECREASE2: - if (battlerAbility == ABILITY_CONTRARY) + if (hasContrary) stringId = STRINGID_STATSWONTINCREASE2; break; + case STRINGID_STATSWONTINCREASECONTRARY2: + if (hasContrary) + stringId = STRINGID_STATSWONTDECREASECONTRARY2; + break; + case STRINGID_STATSWONTDECREASECONTRARY2: + if (hasContrary) + stringId = STRINGID_STATSWONTINCREASECONTRARY2; + break; case STRINGID_PKMNCUTSATTACKWITH: if (GetConfig(CONFIG_UPDATED_INTIMIDATE) >= GEN_8 - && targetAbility == ABILITY_RATTLED - && CompareStat(gBattlerTarget, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, targetAbility)) + && SearchTraits(battlerTraits, ABILITY_RATTLED) + && CompareStat(gBattlerTarget, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN)) { gBattlerAbility = gBattlerTarget; + PushTraitStack(gBattlerTarget, ABILITY_RATTLED); BattleScriptCall(BattleScript_AbilityRaisesDefenderStat); SET_STATCHANGER(STAT_SPEED, 1, FALSE); } - else if (targetAbility == ABILITY_CONTRARY) + break; + case STRINGID_PKMNINTIMIDATECONTRARYRATTLED: // Special string for Intimidate + Contrary + Rattled. Rattled should activate even if Attack is raised. + stringId = STRINGID_DEFENDERSSTATROSE; + if (GetConfig(CONFIG_UPDATED_INTIMIDATE) >= GEN_8 + && SearchTraits(battlerTraits, ABILITY_RATTLED) + && CompareStat(gBattlerTarget, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN)) { - stringId = STRINGID_DEFENDERSSTATROSE; + gBattlerAbility = gBattlerTarget; + BattleScriptCall(BattleScript_AbilityRaisesDefenderStat); + PushTraitStack(gBattlerTarget, ABILITY_RATTLED); + SET_STATCHANGER(STAT_SPEED, 1, FALSE); } break; case STRINGID_ITDOESNTAFFECT: @@ -1304,17 +1330,121 @@ void PrepareStringBattle(enum StringID stringId, u32 battler) break; } - if ((stringId == STRINGID_PKMNCUTSATTACKWITH || stringId == STRINGID_DEFENDERSSTATFELL) - && ShouldDefiantCompetitiveActivate(gBattlerTarget, targetAbility)) + if (!hasContrary + && (stringId == STRINGID_PKMNCUTSATTACKWITH || stringId == STRINGID_DEFENDERSSTATFELL) + && ShouldDefiantCompetitiveActivate(gBattlerTarget)) { gBattlerAbility = gBattlerTarget; BattleScriptCall(BattleScript_AbilityRaisesDefenderStat); - if (targetAbility == ABILITY_DEFIANT) + if (SearchTraits(battlerTraits, ABILITY_DEFIANT)) + { + PushTraitStack(gBattlerTarget, ABILITY_DEFIANT); SET_STATCHANGER(STAT_ATK, 2, FALSE); - else + } + if (SearchTraits(battlerTraits, ABILITY_COMPETITIVE)) + { + PushTraitStack(gBattlerTarget, ABILITY_COMPETITIVE); SET_STATCHANGER(STAT_SPATK, 2, FALSE); + } + } + // Check Defiant and Competitive when Contrary is present + else if (stringId == STRINGID_DEFENDERSSTATFELL + && hasContrary + && gSpecialStatuses[gBattlerTarget].changedStatsBattlerId != BATTLE_PARTNER(gBattlerTarget) + && gSpecialStatuses[gBattlerTarget].changedStatsBattlerId != gBattlerTarget) // Defiant/Competitive should not trigger on self afflicted changes + { + // First case: Competitive on its own + // Second case: Defiant on its own will work normally, ignoring the second call for Competitive. + // Third case: Defiant triggers but NOT Competitive, Defiant then triggers Comptitive resulting in only one activation each. + // Fourth case: ATK is maxed out, Defiant can't trigger but an exclusion is included to manually trigger Competitive. + + // Competitive on its own + if (SearchTraits(battlerTraits, ABILITY_COMPETITIVE) + && !SearchTraits(battlerTraits, ABILITY_DEFIANT) + && gProtectStructs[gBattlerTarget].contraryCompetitive == FALSE) + { + if (CompareStat(gBattlerTarget, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + { + gProtectStructs[gBattlerTarget].contraryCompetitive = TRUE; + gBattlerAbility = gBattlerTarget; + BattleScriptCall(BattleScript_AbilityRaisesDefenderStat); + PushTraitStack(gBattlerTarget, ABILITY_COMPETITIVE); + SET_STATCHANGER(STAT_SPATK, 2, FALSE); + } + else + { + gProtectStructs[gBattlerTarget].contraryCompetitive = TRUE; + stringId = STRINGID_STATSWONTDECREASE; + } + } + // Defiant either on its own or paired with Competitive + else if (SearchTraits(battlerTraits, ABILITY_DEFIANT) + && gProtectStructs[gBattlerTarget].contraryDefiant == FALSE) + { + if (CompareStat(gBattlerTarget, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + { + gProtectStructs[gBattlerTarget].contraryDefiant = TRUE; + gBattlerAbility = gBattlerTarget; + BattleScriptCall(BattleScript_AbilityRaisesDefenderStat); + PushTraitStack(gBattlerTarget, ABILITY_DEFIANT); + SET_STATCHANGER(STAT_ATK, 2, FALSE); + } + else //If Defiant triggered but ATK is maxed, display Won't Increase message and manually trigger Competitive + { + gProtectStructs[gBattlerTarget].contraryDefiant = TRUE; + stringId = STRINGID_STATSWONTDECREASE; + BtlController_EmitPrintString(battler, B_COMM_TO_CONTROLLER, stringId); + + if (SearchTraits(battlerTraits, ABILITY_COMPETITIVE) + && gProtectStructs[gBattlerTarget].contraryCompetitive == FALSE) + { + if (CompareStat(gBattlerTarget, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + { + gProtectStructs[gBattlerTarget].contraryCompetitive = TRUE; + gBattlerAbility = gBattlerTarget; + BattleScriptCall(BattleScript_AbilityRaisesDefenderStat); + PushTraitStack(gBattlerTarget, ABILITY_COMPETITIVE); + SET_STATCHANGER(STAT_SPATK, 2, FALSE); + } + else + { + gProtectStructs[gBattlerTarget].contraryCompetitive = TRUE; + stringId = STRINGID_STATSWONTDECREASE; + } + } + } + } // else if used because Contrary Defiant will already retrigger the loop, so this enforces only Defiant triggers and then only Competitive + else if (SearchTraits(battlerTraits, ABILITY_COMPETITIVE) + && gProtectStructs[gBattlerTarget].contraryCompetitive == FALSE) + { + if (CompareStat(gBattlerTarget, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + { + gProtectStructs[gBattlerTarget].contraryCompetitive = TRUE; + gBattlerAbility = gBattlerTarget; + BattleScriptCall(BattleScript_AbilityRaisesDefenderStat); + PushTraitStack(gBattlerTarget, ABILITY_COMPETITIVE); + SET_STATCHANGER(STAT_SPATK, 2, FALSE); + } + else + { + gProtectStructs[gBattlerTarget].contraryCompetitive = TRUE; + stringId = STRINGID_STATSWONTDECREASE; + } + } } + // Generate ability popup for Contrary, only when a status change happens outside of Defiant/Competitive wich have their own popup calls + // Contrary does not have popup in vanilla + // if (hasContrary + // && ((stringId == STRINGID_DEFENDERSSTATFELL || stringId == STRINGID_STATSWONTINCREASE || stringId == STRINGID_STATSWONTDECREASE || stringId == STRINGID_STATSWONTINCREASE2 || stringId == STRINGID_STATSWONTDECREASE2 + // stringId == STRINGID_STATSWONTINCREASECONTRARY2 || stringId == STRINGID_STATSWONTDECREASECONTRARY2) + // || (stringId == STRINGID_DEFENDERSSTATROSE && !((gProtectStructs[gBattlerTarget].contraryCompetitive || gProtectStructs[gBattlerTarget].contraryDefiant)) + // && (gSpecialStatuses[gBattlerTarget].changedStatsBattlerId != BATTLE_PARTNER(gBattlerTarget) || gSpecialStatuses[gBattlerTarget].changedStatsBattlerId != gBattlerTarget)))) + // { + // PushTraitStack(gBattlerTarget, ABILITY_CONTRARY); + // BattleScriptCall(BattleScript_GenerateAbilityPopUp); + // } + BtlController_EmitPrintString(battler, B_COMM_TO_CONTROLLER, stringId); MarkBattlerForControllerExec(battler); } @@ -1632,7 +1762,7 @@ u32 TrySetCantSelectMoveBattleScript(u32 battler) limitations++; } } - if (DYNAMAX_BYPASS_CHECK && (GetBattlerAbility(battler) == ABILITY_GORILLA_TACTICS) && *choicedMove != MOVE_NONE + if (DYNAMAX_BYPASS_CHECK && (BattlerHasTrait(battler, ABILITY_GORILLA_TACTICS)) && *choicedMove != MOVE_NONE && *choicedMove != MOVE_UNAVAILABLE && *choicedMove != move) { gCurrentMove = *choicedMove; @@ -1739,7 +1869,7 @@ u32 CheckMoveLimitations(u32 battler, u8 unusableMoves, u16 check) else if (check & MOVE_LIMITATION_STUFF_CHEEKS && moveEffect == EFFECT_STUFF_CHEEKS && GetItemPocket(gBattleMons[battler].item) != POCKET_BERRIES) unusableMoves |= 1u << i; // Gorilla Tactics - else if (check & MOVE_LIMITATION_CHOICE_ITEM && GetBattlerAbility(battler) == ABILITY_GORILLA_TACTICS && *choicedMove != MOVE_NONE && *choicedMove != MOVE_UNAVAILABLE && *choicedMove != move) + else if (check & MOVE_LIMITATION_CHOICE_ITEM && BattlerHasTrait(battler, ABILITY_GORILLA_TACTICS) && *choicedMove != MOVE_NONE && *choicedMove != MOVE_UNAVAILABLE && *choicedMove != move) unusableMoves |= 1u << i; // Can't Use Twice flag else if (check & MOVE_LIMITATION_CANT_USE_TWICE && MoveCantBeUsedTwice(move) && move == gLastResultingMoves[battler]) @@ -1811,13 +1941,13 @@ u32 GetBattlerAffectionHearts(u32 battler) // gBattlerAttacker is the battler that's trying to raise their stats and due to limitations of RandomUniformExcept, cannot be an argument bool32 MoodyCantRaiseStat(u32 stat) { - return CompareStat(gBattlerAttacker, stat, MAX_STAT_STAGE, CMP_EQUAL, GetBattlerAbility(gBattlerAttacker)); + return CompareStat(gBattlerAttacker, stat, MAX_STAT_STAGE, CMP_EQUAL); } // gBattlerAttacker is the battler that's trying to lower their stats and due to limitations of RandomUniformExcept, cannot be an argument bool32 MoodyCantLowerStat(u32 stat) { - return stat == GET_STAT_BUFF_ID(gBattleScripting.statChanger) || CompareStat(gBattlerAttacker, stat, MIN_STAT_STAGE, CMP_EQUAL, GetBattlerAbility(gBattlerAttacker)); + return stat == GET_STAT_BUFF_ID(gBattleScripting.statChanger) || CompareStat(gBattlerAttacker, stat, MIN_STAT_STAGE, CMP_EQUAL); } void TryToRevertMimicryAndFlags(void) @@ -1826,7 +1956,7 @@ void TryToRevertMimicryAndFlags(void) { gDisableStructs[battler].terrainAbilityDone = FALSE; ResetParadoxTerrainStat(battler); - if (IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_MIMICRY)) + if (IsAbilityAndRecord(battler, ABILITY_MIMICRY)) RESTORE_BATTLER_TYPE(battler); } } @@ -1859,9 +1989,9 @@ s32 GetDrainedBigRootHp(u32 battler, s32 hp) } // Should always be the last check. Otherwise the ability might be wrongly recorded. -bool32 IsAbilityAndRecord(u32 battler, enum Ability battlerAbility, enum Ability abilityToCheck) +bool32 IsAbilityAndRecord(u32 battler, enum Ability abilityToCheck) { - if (battlerAbility != abilityToCheck) + if (!BattlerHasTrait(battler, abilityToCheck)) return FALSE; RecordAbilityBattle(battler, abilityToCheck); @@ -2051,7 +2181,7 @@ static enum MoveCanceler CancelerAsleepOrFrozen(struct BattleContext *ctx) else { u8 toSub; - if (IsAbilityAndRecord(ctx->battlerAtk, ctx->abilities[ctx->battlerAtk], ABILITY_EARLY_BIRD)) + if (IsAbilityAndRecord(ctx->battlerAtk, ABILITY_EARLY_BIRD)) toSub = 2; else toSub = 1; @@ -2172,12 +2302,13 @@ static enum MoveCanceler CancelerPowerPoints(struct BattleContext *ctx) static enum MoveCanceler CancelerTruant(struct BattleContext *ctx) { - if (GetBattlerAbility(ctx->battlerAtk) == ABILITY_TRUANT && gDisableStructs[ctx->battlerAtk].truantCounter) + if (BattlerHasTrait(ctx->battlerAtk, ABILITY_TRUANT) && gDisableStructs[ctx->battlerAtk].truantCounter) { CancelMultiTurnMoves(ctx->battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK); gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_LOAFING; gBattlerAbility = ctx->battlerAtk; + PushTraitStack(gBattlerAttacker, ABILITY_TRUANT); gBattlescriptCurrInstr = BattleScript_TruantLoafingAround; gBattleStruct->moveResultFlags[ctx->battlerDef] |= MOVE_RESULT_MISSED; return MOVE_STEP_FAILURE; @@ -2319,7 +2450,7 @@ static enum MoveCanceler CancelerConfused(struct BattleContext *ctx) static enum MoveCanceler CancelerParalyzed(struct BattleContext *ctx) { if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_PARALYSIS - && !(B_MAGIC_GUARD == GEN_4 && IsAbilityAndRecord(ctx->battlerAtk, ctx->abilities[ctx->battlerAtk], ABILITY_MAGIC_GUARD)) + && !(B_MAGIC_GUARD == GEN_4 && IsAbilityAndRecord(ctx->battlerAtk, ABILITY_MAGIC_GUARD)) && !RandomPercentage(RNG_PARALYSIS, 75)) { gProtectStructs[ctx->battlerAtk].nonVolatileStatusImmobility = TRUE; @@ -2412,7 +2543,7 @@ static enum MoveCanceler CancelerChoiceLock(struct BattleContext *ctx) if (gChosenMove != MOVE_STRUGGLE && (*choicedMoveAtk == MOVE_NONE || *choicedMoveAtk == MOVE_UNAVAILABLE) - && (IsHoldEffectChoice(holdEffect) || ctx->abilities[ctx->battlerAtk] == ABILITY_GORILLA_TACTICS)) + && (IsHoldEffectChoice(holdEffect) || BattlerHasTrait(ctx->battlerAtk, ABILITY_GORILLA_TACTICS))) *choicedMoveAtk = gChosenMove; u32 moveIndex; @@ -2551,12 +2682,12 @@ static enum MoveCanceler CancelerPPDeduction(struct BattleContext *ctx) for (u32 i = 0; i < gBattlersCount; i++) { if (!IsBattlerAlly(i, ctx->battlerAtk) && IsBattlerAlive(i)) - ppToDeduct += (GetBattlerAbility(i) == ABILITY_PRESSURE); + ppToDeduct += (BattlerHasTrait(i, ABILITY_PRESSURE) != 0); } } else if (moveTarget != MOVE_TARGET_OPPONENTS_FIELD) { - if (ctx->battlerAtk != ctx->battlerDef && ctx->abilities[ctx->battlerDef] == ABILITY_PRESSURE) + if (ctx->battlerAtk != ctx->battlerDef && BattlerHasTrait(ctx->battlerDef, ABILITY_PRESSURE)) ppToDeduct++; } @@ -2703,22 +2834,41 @@ static enum MoveCanceler CancelerMoveFailure(struct BattleContext *ctx) case EFFECT_POLTERGEIST: if (gBattleMons[ctx->battlerDef].item == ITEM_NONE || gFieldStatuses & STATUS_FIELD_MAGIC_ROOM - || ctx->abilities[ctx->battlerDef] == ABILITY_KLUTZ) + || BattlerHasTrait(ctx->battlerDef, ABILITY_KLUTZ)) battleScript = BattleScript_ButItFailed; break; case EFFECT_PROTECT: // TODO break; case EFFECT_REST: - if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_SLEEP - || ctx->abilities[ctx->battlerAtk] == ABILITY_COMATOSE) + if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_SLEEP) battleScript = BattleScript_RestIsAlreadyAsleep; else if (gBattleMons[ctx->battlerAtk].hp == gBattleMons[ctx->battlerAtk].maxHP) battleScript = BattleScript_AlreadyAtFullHp; - else if (ctx->abilities[ctx->battlerAtk] == ABILITY_INSOMNIA - || ctx->abilities[ctx->battlerAtk] == ABILITY_VITAL_SPIRIT - || ctx->abilities[ctx->battlerAtk] == ABILITY_PURIFYING_SALT) + else if (BattlerHasTrait(ctx->battlerAtk, ABILITY_INSOMNIA)) + { + PushTraitStack(ctx->battlerAtk, ABILITY_INSOMNIA); + gLastUsedAbility = ABILITY_INSOMNIA; + battleScript = BattleScript_InsomniaProtects; + } + else if (BattlerHasTrait(ctx->battlerAtk, ABILITY_VITAL_SPIRIT)) + { + PushTraitStack(ctx->battlerAtk, ABILITY_VITAL_SPIRIT); + gLastUsedAbility = ABILITY_VITAL_SPIRIT; + battleScript = BattleScript_InsomniaProtects; + } + else if (BattlerHasTrait(ctx->battlerAtk, ABILITY_COMATOSE)) + { + PushTraitStack(ctx->battlerAtk, ABILITY_COMATOSE); + gLastUsedAbility = ABILITY_COMATOSE; + battleScript = BattleScript_RestIsAlreadyAsleep; + } + else if (BattlerHasTrait(ctx->battlerAtk, ABILITY_PURIFYING_SALT)) + { + PushTraitStack(ctx->battlerAtk, ABILITY_PURIFYING_SALT); + gLastUsedAbility = ABILITY_PURIFYING_SALT; battleScript = BattleScript_InsomniaProtects; + } break; case EFFECT_SUCKER_PUNCH: if (HasBattlerActedThisTurn(ctx->battlerDef) @@ -2727,7 +2877,7 @@ static enum MoveCanceler CancelerMoveFailure(struct BattleContext *ctx) break; case EFFECT_UPPER_HAND: { - u32 prio = GetChosenMovePriority(ctx->battlerDef, ctx->abilities[ctx->battlerDef]); + u32 prio = GetChosenMovePriority(ctx->battlerDef); if (prio < 1 || prio > 3 // Fails if priority is less than 1 or greater than 3, if target already moved, or if using a status || HasBattlerActedThisTurn(ctx->battlerDef) || gChosenMoveByBattler[ctx->battlerDef] == MOVE_NONE @@ -2737,7 +2887,7 @@ static enum MoveCanceler CancelerMoveFailure(struct BattleContext *ctx) } case EFFECT_SNORE: if (!(gBattleMons[ctx->battlerAtk].status1 & STATUS1_SLEEP) - && ctx->abilities[ctx->battlerAtk] != ABILITY_COMATOSE) + && !BattlerHasTrait(ctx->battlerAtk, ABILITY_COMATOSE)) battleScript = BattleScript_ButItFailed; break; case EFFECT_STEEL_ROLLER: @@ -2783,7 +2933,7 @@ static enum MoveCanceler CancelerPowderStatus(struct BattleContext *ctx) { if (TryActivatePowderStatus(ctx->currentMove)) { - if (!IsAbilityAndRecord(ctx->battlerAtk, ctx->abilities[ctx->battlerAtk], ABILITY_MAGIC_GUARD)) + if (!IsAbilityAndRecord(ctx->battlerAtk, ABILITY_MAGIC_GUARD)) SetPassiveDamageAmount(ctx->battlerAtk, GetNonDynamaxMaxHP(ctx->battlerAtk) / 4); // This might be incorrect @@ -2796,37 +2946,49 @@ static enum MoveCanceler CancelerPowderStatus(struct BattleContext *ctx) return MOVE_STEP_SUCCESS; } -bool32 IsDazzlingAbility(enum Ability ability) +bool32 HasDazzlingAbility(u32 battlerDef) { - switch (ability) + if (gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_DAZZLING) : BattlerHasTrait(battlerDef, ABILITY_DAZZLING)) + { + if (!gAiLogicData->aiCalcInProgress) + PushTraitStack(battlerDef, ABILITY_DAZZLING); + gLastUsedAbility = ABILITY_DAZZLING; + return TRUE; + } + if (gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_QUEENLY_MAJESTY) : BattlerHasTrait(battlerDef, ABILITY_QUEENLY_MAJESTY)) { - case ABILITY_DAZZLING: return TRUE; - case ABILITY_QUEENLY_MAJESTY: return TRUE; - case ABILITY_ARMOR_TAIL: return TRUE; - default: break; + if (!gAiLogicData->aiCalcInProgress) + PushTraitStack(battlerDef, ABILITY_QUEENLY_MAJESTY); + gLastUsedAbility = ABILITY_QUEENLY_MAJESTY; + return TRUE; + } + if (gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_ARMOR_TAIL) : BattlerHasTrait(battlerDef, ABILITY_ARMOR_TAIL)) + { + if (!gAiLogicData->aiCalcInProgress) + PushTraitStack(battlerDef, ABILITY_ARMOR_TAIL); + gLastUsedAbility = ABILITY_ARMOR_TAIL; + return TRUE; } + return FALSE; } static enum MoveCanceler CancelerPriorityBlock(struct BattleContext *ctx) { bool32 effect = FALSE; - s32 priority = GetChosenMovePriority(ctx->battlerAtk, ctx->abilities[ctx->battlerAtk]); - u32 blockAbility = ABILITY_NONE; // ability of battler who is blocking + s32 priority = GetChosenMovePriority(ctx->battlerAtk); u32 blockedByBattler = ctx->battlerDef; if (priority <= 0 || IsBattlerAlly(ctx->battlerAtk, ctx->battlerDef)) return MOVE_STEP_SUCCESS; - if (IsDazzlingAbility(ctx->abilities[ctx->battlerDef])) + if (HasDazzlingAbility(ctx->battlerDef)) { - blockAbility = ctx->abilities[ctx->battlerDef]; effect = TRUE; } else if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(ctx->battlerDef))) { - blockAbility = GetBattlerAbility(BATTLE_PARTNER(ctx->battlerDef)); - if (IsDazzlingAbility(blockAbility)) + if (HasDazzlingAbility(BATTLE_PARTNER(ctx->battlerDef))) { blockedByBattler = BATTLE_PARTNER(ctx->battlerDef); effect = TRUE; @@ -2836,8 +2998,7 @@ static enum MoveCanceler CancelerPriorityBlock(struct BattleContext *ctx) if (effect) { gMultiHitCounter = 0; // Prevent multi-hit moves from hitting more than once after move has been absorbed. - gLastUsedAbility = blockAbility; - RecordAbilityBattle(blockedByBattler, blockAbility); + RecordAbilityBattle(blockedByBattler, gLastUsedAbility); gBattleScripting.battler = gBattlerAbility = blockedByBattler; gBattlescriptCurrInstr = BattleScript_DazzlingProtected; gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; @@ -2850,7 +3011,7 @@ static enum MoveCanceler CancelerPriorityBlock(struct BattleContext *ctx) static enum MoveCanceler CancelerProtean(struct BattleContext *ctx) { enum Type moveType = GetBattleMoveType(ctx->currentMove); - if (ProteanTryChangeType(ctx->battlerAtk, ctx->abilities[ctx->battlerAtk], ctx->currentMove, moveType)) + if (ProteanTryChangeType(ctx->battlerAtk, ctx->currentMove, moveType)) { if (GetConfig(CONFIG_PROTEAN_LIBERO) >= GEN_9) gDisableStructs[ctx->battlerAtk].usedProteanLibero = TRUE; @@ -2869,7 +3030,8 @@ static enum MoveCanceler CancelerExplodingDamp(struct BattleContext *ctx) u32 dampBattler = IsAbilityOnField(ABILITY_DAMP); if (dampBattler && IsMoveDampBanned(ctx->currentMove)) { - gBattleScripting.battler = dampBattler - 1; + gBattleScripting.battler = dampBattler = dampBattler - 1; + PushTraitStack(dampBattler, ABILITY_DAMP); gBattlescriptCurrInstr = BattleScript_DampStopsExplosion; gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; return MOVE_STEP_FAILURE; @@ -2881,13 +3043,11 @@ static enum MoveCanceler CancelerMultihitMoves(struct BattleContext *ctx) { if (GetMoveEffect(ctx->currentMove) == EFFECT_MULTI_HIT) { - enum Ability ability = ctx->abilities[ctx->battlerAtk]; - - if (ability == ABILITY_SKILL_LINK) + if (BattlerHasTrait(gBattlerAttacker, ABILITY_SKILL_LINK)) { gMultiHitCounter = 5; } - else if (ability == ABILITY_BATTLE_BOND + else if (BattlerHasTrait(gBattlerAttacker, ABILITY_BATTLE_BOND) && ctx->currentMove == MOVE_WATER_SHURIKEN && gBattleMons[ctx->battlerAtk].species == SPECIES_GRENINJA_ASH) { @@ -2956,7 +3116,6 @@ static enum MoveCanceler CancelerMultihitMoves(struct BattleContext *ctx) static enum MoveCanceler CancelerMultiTargetMoves(struct BattleContext *ctx) { u32 moveTarget = GetBattlerMoveTargetType(ctx->battlerAtk, ctx->currentMove); - enum Ability abilityAtk = ctx->abilities[ctx->battlerAtk]; if (IsSpreadMove(moveTarget)) { @@ -2965,8 +3124,6 @@ static enum MoveCanceler CancelerMultiTargetMoves(struct BattleContext *ctx) if (gBattleStruct->bouncedMoveIsUsed && !IsOnPlayerSide(battlerDef)) continue; - enum Ability abilityDef = GetBattlerAbility(battlerDef); - if (ctx->battlerAtk == battlerDef || !IsBattlerAlive(battlerDef) || (GetMoveEffect(ctx->currentMove) == EFFECT_SYNCHRONOISE && !DoBattlersShareType(ctx->battlerAtk, battlerDef)) @@ -2976,19 +3133,19 @@ static enum MoveCanceler CancelerMultiTargetMoves(struct BattleContext *ctx) gBattleStruct->moveResultFlags[battlerDef] = MOVE_RESULT_NO_EFFECT; gBattleStruct->noResultString[battlerDef] = WILL_FAIL; } - else if (CanAbilityBlockMove(ctx->battlerAtk, battlerDef, abilityAtk, abilityDef, ctx->currentMove, CHECK_TRIGGER)) + else if (CanAbilityBlockMove(ctx->battlerAtk, battlerDef, ctx->currentMove, CHECK_TRIGGER)) { gBattleStruct->moveResultFlags[battlerDef] = 0; gBattleStruct->noResultString[battlerDef] = WILL_FAIL; } - else if (CanAbilityAbsorbMove(ctx->battlerAtk, battlerDef, abilityDef, ctx->currentMove, GetBattleMoveType(gCurrentMove), CHECK_TRIGGER)) + else if (CanAbilityAbsorbMove(ctx->battlerAtk, battlerDef, ctx->currentMove, GetBattleMoveType(gCurrentMove), CHECK_TRIGGER)) { gBattleStruct->moveResultFlags[battlerDef] = 0; gBattleStruct->noResultString[battlerDef] = CHECK_ACCURACY; } else { - CalcTypeEffectivenessMultiplierHelper(ctx->currentMove, GetBattleMoveType(ctx->currentMove), ctx->battlerAtk, battlerDef, abilityAtk, abilityDef, TRUE); // Sets moveResultFlags + CalcTypeEffectivenessMultiplierHelper(ctx->currentMove, GetBattleMoveType(ctx->currentMove), ctx->battlerAtk, battlerDef, TRUE); // Sets moveResultFlags gBattleStruct->noResultString[battlerDef] = CAN_DAMAGE; } } @@ -3219,25 +3376,29 @@ bool32 HasNoMonsToSwitch(u32 battler, u8 partyIdBattlerOn1, u8 partyIdBattlerOn2 } } -bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, u32 ability) +bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, bool32 viaAbility) { + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battler); + if (gBattleWeather & sBattleWeatherInfo[battleWeatherId].flag) { return FALSE; } else if (gBattleWeather & B_WEATHER_PRIMAL_ANY - && ability != ABILITY_DESOLATE_LAND - && ability != ABILITY_PRIMORDIAL_SEA - && ability != ABILITY_DELTA_STREAM) + && !SearchTraits(battlerTraits, ABILITY_DESOLATE_LAND) + && !SearchTraits(battlerTraits, ABILITY_PRIMORDIAL_SEA) + && !SearchTraits(battlerTraits, ABILITY_DELTA_STREAM)) { return FALSE; } - else if (GetConfig(CONFIG_ABILITY_WEATHER) < GEN_6 && ability != ABILITY_NONE) + else if (GetConfig(CONFIG_ABILITY_WEATHER) < GEN_6 && viaAbility) { gBattleWeather = sBattleWeatherInfo[battleWeatherId].flag; for (u32 i = 0; i < gBattlersCount; i++) { gDisableStructs[i].weatherAbilityDone = FALSE; + gDisableStructs[i].transformWeatherAbilityDone = FALSE; ResetParadoxWeatherStat(i); } return TRUE; @@ -3255,6 +3416,7 @@ bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, u32 ability) for (u32 i = 0; i < gBattlersCount; i++) { gDisableStructs[i].weatherAbilityDone = FALSE; + gDisableStructs[i].transformWeatherAbilityDone = FALSE; ResetParadoxWeatherStat(i); } return TRUE; @@ -3472,38 +3634,44 @@ void ChooseStatBoostAnimation(u32 battler) #undef ANIM_STAT_ACC #undef ANIM_STAT_EVASION -bool32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef, u32 move, enum FunctionCallOption option) +bool32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, u32 move, enum FunctionCallOption option) { const u8 *battleScriptBlocksMove = NULL; + enum Ability abilityDef = ABILITY_NONE; + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battlerDef); - switch (abilityDef) + if (SearchTraits(battlerTraits, ABILITY_SOUNDPROOF) + && IsSoundMove(move) && !(GetBattlerMoveTargetType(battlerAtk, move) & MOVE_TARGET_USER)) { - case ABILITY_SOUNDPROOF: - if (IsSoundMove(move) && !(GetBattlerMoveTargetType(battlerAtk, move) & MOVE_TARGET_USER)) - battleScriptBlocksMove = BattleScript_SoundproofProtected; - break; - case ABILITY_BULLETPROOF: - if (IsBallisticMove(move)) - battleScriptBlocksMove = BattleScript_SoundproofProtected; - break; - case ABILITY_GOOD_AS_GOLD: - if (IsBattleMoveStatus(move)) + abilityDef = ABILITY_SOUNDPROOF; + PushTraitStack(battlerDef, ABILITY_SOUNDPROOF); + battleScriptBlocksMove = BattleScript_SoundproofProtected; + } + else if (SearchTraits(battlerTraits, ABILITY_BULLETPROOF) + && IsBallisticMove(move)) + { + abilityDef = ABILITY_BULLETPROOF; + PushTraitStack(battlerDef, ABILITY_BULLETPROOF); + battleScriptBlocksMove = BattleScript_SoundproofProtected; + } + else if (SearchTraits(battlerTraits, ABILITY_GOOD_AS_GOLD) + && IsBattleMoveStatus(move)) + { + if (!(GetBattlerMoveTargetType(battlerAtk, move) & (MOVE_TARGET_OPPONENTS_FIELD | MOVE_TARGET_ALL_BATTLERS))) { - if (!(GetBattlerMoveTargetType(battlerAtk, move) & (MOVE_TARGET_OPPONENTS_FIELD | MOVE_TARGET_ALL_BATTLERS))) - battleScriptBlocksMove = BattleScript_GoodAsGoldActivates; + abilityDef = ABILITY_GOOD_AS_GOLD; + PushTraitStack(battlerDef, ABILITY_GOOD_AS_GOLD); + battleScriptBlocksMove = BattleScript_GoodAsGoldActivates; } - break; - default: - break; } - if (battleScriptBlocksMove == NULL) { s32 atkPriority = 0; if (option == AI_CHECK) - atkPriority = GetBattleMovePriority(battlerAtk, abilityAtk, move); + atkPriority = GetBattleMovePriority(battlerAtk, move); else - atkPriority = GetChosenMovePriority(battlerAtk, abilityAtk); + atkPriority = GetChosenMovePriority(battlerAtk); if (atkPriority <= 0) { @@ -3511,13 +3679,13 @@ bool32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, enum Ability abilityA } else if (IsBattleMoveStatus(move) && BlocksPrankster(move, battlerAtk, battlerDef, TRUE) - && !(IsBattleMoveStatus(move) && (abilityDef == ABILITY_MAGIC_BOUNCE || gProtectStructs[battlerDef].bounceMove))) + && !(IsBattleMoveStatus(move) && (SearchTraits(battlerTraits, ABILITY_MAGIC_BOUNCE) || gProtectStructs[battlerDef].bounceMove))) { if (option == RUN_SCRIPT && !IsSpreadMove(GetBattlerMoveTargetType(battlerAtk, move))) CancelMultiTurnMoves(battlerAtk, SKY_DROP_ATTACKCANCELER_CHECK); // Don't cancel moves that can hit two targets bc one target might not be protected battleScriptBlocksMove = BattleScript_DoesntAffectTargetAtkString; } - else if (IsBattlerTerrainAffected(battlerDef, abilityDef, GetBattlerHoldEffect(battlerDef), STATUS_FIELD_PSYCHIC_TERRAIN) // Not an ability but similar conditions + else if (IsBattlerTerrainAffected(battlerDef, GetBattlerHoldEffect(battlerDef), STATUS_FIELD_PSYCHIC_TERRAIN) // Not an ability but similar conditions && !IsBattlerAlly(battlerAtk, battlerDef) && GetMoveTarget(move) != MOVE_TARGET_ALL_BATTLERS && GetMoveTarget(move) != MOVE_TARGET_OPPONENTS_FIELD) @@ -3549,78 +3717,103 @@ bool32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, enum Ability abilityA return TRUE; } -bool32 CanAbilityAbsorbMove(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef, u32 move, enum Type moveType, enum FunctionCallOption option) +bool32 CanAbilityAbsorbMove(u32 battlerAtk, u32 battlerDef, u32 move, enum Type moveType, enum FunctionCallOption option) { + enum Ability abilityDef = ABILITY_NONE; + enum Ability battlerTraits[MAX_MON_TRAITS]; + enum Ability AIBattlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battlerDef); + AI_STORE_BATTLER_TRAITS(battlerDef); //includes AI ambiguity over opponent abilities, innates are always fixed and known + enum MoveAbsorbed effect = MOVE_ABSORBED_BY_NO_ABILITY; const u8 *battleScript = NULL; enum Stat statId = 0; u32 statAmount = 1; - switch (abilityDef) + // Uses an extra check for the received ability in case the AI is trying to give a status to itself and thus should know the ability already + if ((gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_VOLT_ABSORB) : SearchTraits(battlerTraits, ABILITY_VOLT_ABSORB) || abilityDef == ABILITY_VOLT_ABSORB) + && moveType == TYPE_ELECTRIC && GetBattlerMoveTargetType(battlerAtk, move) != MOVE_TARGET_ALL_BATTLERS) { - default: - effect = MOVE_ABSORBED_BY_NO_ABILITY; - break; - case ABILITY_VOLT_ABSORB: - if (moveType == TYPE_ELECTRIC && GetBattlerMoveTargetType(battlerAtk, move) != MOVE_TARGET_ALL_BATTLERS) - effect = MOVE_ABSORBED_BY_DRAIN_HP_ABILITY; - break; - case ABILITY_WATER_ABSORB: - case ABILITY_DRY_SKIN: - if (moveType == TYPE_WATER) - effect = MOVE_ABSORBED_BY_DRAIN_HP_ABILITY; - break; - case ABILITY_EARTH_EATER: - if (moveType == TYPE_GROUND) - effect = MOVE_ABSORBED_BY_DRAIN_HP_ABILITY; - break; - case ABILITY_MOTOR_DRIVE: - if (moveType == TYPE_ELECTRIC && GetBattlerMoveTargetType(battlerAtk, move) != MOVE_TARGET_ALL_BATTLERS) - { - effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; - statId = STAT_SPEED; - } - break; - case ABILITY_LIGHTNING_ROD: - if (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) >= GEN_5 && moveType == TYPE_ELECTRIC && GetBattlerMoveTargetType(battlerAtk, move) != MOVE_TARGET_ALL_BATTLERS) - { - effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; - statId = STAT_SPATK; - } - break; - case ABILITY_STORM_DRAIN: - if (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) >= GEN_5 && moveType == TYPE_WATER) - { - effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; - statId = STAT_SPATK; - } - break; - case ABILITY_SAP_SIPPER: - if (moveType == TYPE_GRASS) - { - effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; - statId = STAT_ATK; - } - break; - case ABILITY_WELL_BAKED_BODY: - if (moveType == TYPE_FIRE) - { - effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; - statAmount = 2; - statId = STAT_DEF; - } - break; - case ABILITY_WIND_RIDER: - if (IsWindMove(move) && !(GetBattlerMoveTargetType(battlerAtk, move) & MOVE_TARGET_USER)) - { - effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; - statId = STAT_ATK; - } - break; - case ABILITY_FLASH_FIRE: - if (moveType == TYPE_FIRE && (B_FLASH_FIRE_FROZEN >= GEN_5 || !(gBattleMons[battlerDef].status1 & STATUS1_FREEZE))) - effect = MOVE_ABSORBED_BY_BOOST_FLASH_FIRE; - break; + abilityDef = ABILITY_VOLT_ABSORB; + PushTraitStack(battlerDef, ABILITY_VOLT_ABSORB); + effect = MOVE_ABSORBED_BY_DRAIN_HP_ABILITY; + } + if ((gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_WATER_ABSORB) : SearchTraits(battlerTraits, ABILITY_WATER_ABSORB) || abilityDef == ABILITY_WATER_ABSORB) + && moveType == TYPE_WATER) + { + abilityDef = ABILITY_WATER_ABSORB; + PushTraitStack(battlerDef, ABILITY_WATER_ABSORB); + effect = MOVE_ABSORBED_BY_DRAIN_HP_ABILITY; + } + if ((gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_DRY_SKIN) : SearchTraits(battlerTraits, ABILITY_DRY_SKIN) || abilityDef == ABILITY_DRY_SKIN) + && moveType == TYPE_WATER) + { + abilityDef = ABILITY_DRY_SKIN; + PushTraitStack(battlerDef, ABILITY_DRY_SKIN); + effect = MOVE_ABSORBED_BY_DRAIN_HP_ABILITY; + } + if ((gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_EARTH_EATER) : SearchTraits(battlerTraits, ABILITY_EARTH_EATER) || abilityDef == ABILITY_EARTH_EATER) + && moveType == TYPE_GROUND) + { + abilityDef = ABILITY_EARTH_EATER; + PushTraitStack(battlerDef, ABILITY_EARTH_EATER); + effect = MOVE_ABSORBED_BY_DRAIN_HP_ABILITY; + } + if ((gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_MOTOR_DRIVE) : SearchTraits(battlerTraits, ABILITY_MOTOR_DRIVE) || abilityDef == ABILITY_MOTOR_DRIVE) + && moveType == TYPE_ELECTRIC && GetBattlerMoveTargetType(battlerAtk, move) != MOVE_TARGET_ALL_BATTLERS) + { + abilityDef = ABILITY_MOTOR_DRIVE; + PushTraitStack(battlerDef, ABILITY_MOTOR_DRIVE); + effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; + statId = STAT_SPEED; + } + if ((gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_LIGHTNING_ROD) : SearchTraits(battlerTraits, ABILITY_LIGHTNING_ROD) || abilityDef == ABILITY_LIGHTNING_ROD) + && GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) >= GEN_5 && moveType == TYPE_ELECTRIC && GetBattlerMoveTargetType(battlerAtk, move) != MOVE_TARGET_ALL_BATTLERS) + { + abilityDef = ABILITY_LIGHTNING_ROD; + PushTraitStack(battlerDef, ABILITY_LIGHTNING_ROD); + effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; + statId = STAT_SPATK; + } + if ((gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_STORM_DRAIN) : SearchTraits(battlerTraits, ABILITY_STORM_DRAIN) || abilityDef == ABILITY_STORM_DRAIN) + && GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) >= GEN_5 && moveType == TYPE_WATER) + { + abilityDef = ABILITY_STORM_DRAIN; + PushTraitStack(battlerDef, ABILITY_STORM_DRAIN); + effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; + statId = STAT_SPATK; + } + if ((gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_SAP_SIPPER) : SearchTraits(battlerTraits, ABILITY_SAP_SIPPER) || abilityDef == ABILITY_SAP_SIPPER) + && moveType == TYPE_GRASS) + { + abilityDef = ABILITY_SAP_SIPPER; + PushTraitStack(battlerDef, ABILITY_SAP_SIPPER); + effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; + statId = STAT_ATK; + } + if ((gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_WELL_BAKED_BODY) : SearchTraits(battlerTraits, ABILITY_WELL_BAKED_BODY) || abilityDef == ABILITY_WELL_BAKED_BODY) + && moveType == TYPE_FIRE) + { + abilityDef = ABILITY_WELL_BAKED_BODY; + PushTraitStack(battlerDef, ABILITY_WELL_BAKED_BODY); + effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; + statAmount = 2; + statId = STAT_DEF; + } + if ((gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_WIND_RIDER) : SearchTraits(battlerTraits, ABILITY_WIND_RIDER) || abilityDef == ABILITY_WIND_RIDER) + && IsWindMove(move) && !(GetBattlerMoveTargetType(battlerAtk, move) & MOVE_TARGET_USER)) + { + abilityDef = ABILITY_WIND_RIDER; + PushTraitStack(battlerDef, ABILITY_WIND_RIDER); + effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; + statId = STAT_ATK; + } + if ((gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_FLASH_FIRE) : SearchTraits(battlerTraits, ABILITY_FLASH_FIRE) || abilityDef == ABILITY_FLASH_FIRE) + && moveType == TYPE_FIRE && (B_FLASH_FIRE_FROZEN >= GEN_5 || !(gBattleMons[battlerDef].status1 & STATUS1_FREEZE))) + { + abilityDef = ABILITY_FLASH_FIRE; + PushTraitStack(battlerDef, ABILITY_FLASH_FIRE); + effect = MOVE_ABSORBED_BY_BOOST_FLASH_FIRE; } if (effect == MOVE_ABSORBED_BY_NO_ABILITY || option != RUN_SCRIPT) @@ -3644,7 +3837,7 @@ bool32 CanAbilityAbsorbMove(u32 battlerAtk, u32 battlerDef, enum Ability ability break; case MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY: gBattleStruct->pledgeMove = FALSE; - if (!CompareStat(battlerDef, statId, MAX_STAT_STAGE, CMP_LESS_THAN, abilityDef)) + if (!CompareStat(battlerDef, statId, MAX_STAT_STAGE, CMP_LESS_THAN)) { battleScript = BattleScript_MonMadeMoveUseless; } @@ -3954,14 +4147,18 @@ bool32 TryFieldEffects(enum FieldEffectCases caseId) return effect; } -u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ability, u32 special, u32 moveArg) +u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, u32 special, u32 moveArg) { u32 effect = 0; enum Type moveType = 0; u32 move = 0; u32 side = 0; u32 i = 0, j = 0; - u32 partner = 0; + u32 partner = BATTLE_PARTNER(battler); + struct Pokemon *mon; + + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battler); if (gBattleTypeFlags & BATTLE_TYPE_SAFARI) return 0; @@ -3971,8 +4168,6 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab if (special) gLastUsedAbility = special; - else if (ability) - gLastUsedAbility = ability; else gLastUsedAbility = GetBattlerAbility(battler); @@ -3987,16 +4182,21 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab { case ABILITYEFFECT_ON_SWITCHIN: gBattleScripting.battler = battler; - switch (gLastUsedAbility) + u8 traitCheck = ABILITY_NONE; + + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_TRACE)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] ) { - case ABILITY_TRACE: + if (traitCheck > 1) + { + // Trace replaces your main Ability, so it generally should not be an Innate. + DebugPrintf("Trace not set as main Ability"); + } + else { u32 chosenTarget; u32 target1; u32 target2; - if (gSpecialStatuses[battler].switchInAbilityDone) - break; if (GetBattlerHoldEffectIgnoreAbility(battler) == HOLD_EFFECT_ABILITY_SHIELD) break; @@ -4006,7 +4206,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab if (IsDoubleBattle()) { if (!gAbilitiesInfo[gBattleMons[target1].ability].cantBeTraced && gBattleMons[target1].hp != 0 - && !gAbilitiesInfo[gBattleMons[target2].ability].cantBeTraced && gBattleMons[target2].hp != 0) + && !gAbilitiesInfo[gBattleMons[target2].ability].cantBeTraced && gBattleMons[target2].hp != 0) chosenTarget = GetBattlerAtPosition((RandomPercentage(RNG_TRACE, 50) * 2) | side), effect++; else if (!gAbilitiesInfo[gBattleMons[target1].ability].cantBeTraced && gBattleMons[target1].hp != 0) chosenTarget = target1, effect++; @@ -4018,9 +4218,10 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab if (!gAbilitiesInfo[gBattleMons[target1].ability].cantBeTraced && gBattleMons[target1].hp != 0) chosenTarget = target1, effect++; } - if (effect != 0) { + //SwitchinTraitDone not set to True so that Traced switchin abilities like Intimidate can still activate + PushTraitStack(battler, ABILITY_TRACE); BattleScriptPushCursorAndCallback(BattleScript_TraceActivates); gBattleStruct->tracedAbility[battler] = gLastUsedAbility = gBattleMons[chosenTarget].ability; RecordAbilityBattle(chosenTarget, gLastUsedAbility); // Record the opposing battler has this ability @@ -4028,128 +4229,109 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab PREPARE_ABILITY_BUFFER(gBattleTextBuff2, gLastUsedAbility) } } - break; - case ABILITY_IMPOSTER: - { - u32 diagonalBattler = BATTLE_OPPOSITE(battler); - if (IsDoubleBattle()) - diagonalBattler = BATTLE_PARTNER(diagonalBattler); - - // Imposter only activates when the battler first switches in - if (gDisableStructs[battler].isFirstTurn == 2 - && !gDisableStructs[battler].overwrittenAbility - && IsBattlerAlive(diagonalBattler) - && !gBattleMons[diagonalBattler].volatiles.substitute - && !gBattleMons[diagonalBattler].volatiles.transformed - && !gBattleMons[battler].volatiles.transformed - && gBattleStruct->illusion[diagonalBattler].state != ILLUSION_ON - && !IsSemiInvulnerable(diagonalBattler, EXCLUDE_COMMANDER)) - { - SaveBattlerAttacker(gBattlerAttacker); - SaveBattlerTarget(gBattlerTarget); - gBattlerAttacker = battler; - gBattlerTarget = diagonalBattler; - BattleScriptPushCursorAndCallback(BattleScript_ImposterActivates); - effect++; - } - } - break; - case ABILITY_MOLD_BREAKER: - if (!gSpecialStatuses[battler].switchInAbilityDone) - { - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_MOLDBREAKER; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg); - effect++; - } - break; - case ABILITY_TERAVOLT: - if (!gSpecialStatuses[battler].switchInAbilityDone) - { - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_TERAVOLT; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg); - effect++; - } - break; - case ABILITY_TURBOBLAZE: - if (!gSpecialStatuses[battler].switchInAbilityDone) - { - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_TURBOBLAZE; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg); - effect++; - } - break; - case ABILITY_SLOW_START: - if (!gSpecialStatuses[battler].switchInAbilityDone) - { - gDisableStructs[battler].slowStartTimer = 5; - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_SLOWSTART; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg); - effect++; - } - break; - case ABILITY_UNNERVE: - if (!gSpecialStatuses[battler].switchInAbilityDone && !gDisableStructs[battler].unnerveActivated) - { - gEffectBattler = GetOppositeBattler(battler); - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_UNNERVE; - gDisableStructs[battler].unnerveActivated = TRUE; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg); - effect++; - } - break; - case ABILITY_AS_ONE_ICE_RIDER: - case ABILITY_AS_ONE_SHADOW_RIDER: - if (!gSpecialStatuses[battler].switchInAbilityDone && !gDisableStructs[battler].unnerveActivated) - { - gEffectBattler = GetOppositeBattler(battler); - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_ASONE; - gDisableStructs[battler].unnerveActivated = TRUE; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_ActivateAsOne); - effect++; - } - break; - case ABILITY_CURIOUS_MEDICINE: - if (!gSpecialStatuses[battler].switchInAbilityDone && IsDoubleBattle() - && IsBattlerAlive(BATTLE_PARTNER(battler)) && TryResetBattlerStatChanges(BATTLE_PARTNER(battler))) - { - gEffectBattler = BATTLE_PARTNER(battler); - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_CURIOUS_MEDICINE; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg); - effect++; - } - break; - case ABILITY_PASTEL_VEIL: - if (!gSpecialStatuses[battler].switchInAbilityDone) + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_IMPOSTER)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + u32 diagonalBattler = BATTLE_OPPOSITE(battler); + if (IsDoubleBattle()) + diagonalBattler = BATTLE_PARTNER(diagonalBattler); + + // Imposter only activates when the battler first switches in + if(gDisableStructs[battler].isFirstTurn == 2 + && !gDisableStructs[battler].overwrittenAbility + && IsBattlerAlive(diagonalBattler) + && !gBattleMons[diagonalBattler].volatiles.substitute + && !gBattleMons[diagonalBattler].volatiles.transformed + && !gBattleMons[battler].volatiles.transformed + && gBattleStruct->illusion[diagonalBattler].state != ILLUSION_ON + && !IsSemiInvulnerable(diagonalBattler, EXCLUDE_COMMANDER)) { + SaveBattlerAttacker(gBattlerAttacker); SaveBattlerTarget(gBattlerTarget); - gBattlerTarget = battler; - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_PASTEL_VEIL; - BattleScriptPushCursorAndCallback(BattleScript_PastelVeilActivates); - effect++; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; + gBattlerAttacker = battler; + gBattlerTarget = diagonalBattler; + effect += CommonSwitchInAbilities(battler, ABILITY_IMPOSTER, traitCheck, BattleScript_ImposterActivates); } - break; - case ABILITY_ANTICIPATION: - if (!gSpecialStatuses[battler].switchInAbilityDone) + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_INTIMIDATE)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] + && !IsOpposingSideEmpty(battler)) + { + PushTraitStack(battler, ABILITY_INTIMIDATE); + SaveBattlerAttacker(gBattlerAttacker); + gBattlerAttacker = battler; + effect += CommonSwitchInAbilities(battler, ABILITY_INTIMIDATE, traitCheck, BattleScript_IntimidateActivates); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_SUPERSWEET_SYRUP)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] + && !GetBattlerPartyState(battler)->supersweetSyrup + && !IsOpposingSideEmpty(battler)) + { + SaveBattlerAttacker(gBattlerAttacker); + gBattlerAttacker = battler; + GetBattlerPartyState(battler)->supersweetSyrup = TRUE; + effect += CommonSwitchInAbilities(battler, ABILITY_SUPERSWEET_SYRUP, traitCheck, BattleScript_SupersweetSyrupActivates); + } + // Only one Mold Breaker type move needs to activate + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_TERAVOLT)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + effect += CommonSwitchInAbilities(battler, ABILITY_TERAVOLT, traitCheck, BattleScript_TeravoltActivates); + else if ((traitCheck = SearchTraits(battlerTraits, ABILITY_TURBOBLAZE)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + effect += CommonSwitchInAbilities(battler, ABILITY_TURBOBLAZE, traitCheck, BattleScript_TurboblazeActivates); + else if ((traitCheck = SearchTraits(battlerTraits, ABILITY_MOLD_BREAKER)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + effect += CommonSwitchInAbilities(battler, ABILITY_MOLD_BREAKER, traitCheck, BattleScript_MoldBreakerActivates); + + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_SLOW_START)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + gDisableStructs[battler].slowStartTimer = 5; + effect += CommonSwitchInAbilities(battler, ABILITY_SLOW_START, traitCheck, BattleScript_SlowStartActivates); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_UNNERVE)) && !gDisableStructs[battler].unnerveActivated && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + gEffectBattler2 = GetOppositeBattler(battler); + gDisableStructs[battler].unnerveActivated = TRUE; + effect += CommonSwitchInAbilities(battler, ABILITY_UNNERVE, traitCheck, BattleScript_UnnerveActivates); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_AS_ONE_ICE_RIDER)) && !gDisableStructs[battler].unnerveActivated && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + gEffectBattler2 = GetOppositeBattler(battler); + gDisableStructs[battler].unnerveActivated = TRUE; + effect += CommonSwitchInAbilities(battler, ABILITY_AS_ONE_ICE_RIDER, traitCheck, BattleScript_ActivateAsOne); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_AS_ONE_SHADOW_RIDER)) && !gDisableStructs[battler].unnerveActivated && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + gEffectBattler2 = GetOppositeBattler(battler); + gDisableStructs[battler].unnerveActivated = TRUE; + effect += CommonSwitchInAbilities(battler, ABILITY_AS_ONE_SHADOW_RIDER, traitCheck, BattleScript_ActivateAsOne); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_CURIOUS_MEDICINE)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] && IsDoubleBattle() + && IsBattlerAlive(partner) && TryResetBattlerStatChanges(partner)) + { + gEffectBattler = partner; + effect += CommonSwitchInAbilities(battler, ABILITY_CURIOUS_MEDICINE, traitCheck, BattleScript_CuriousMedicineActivates); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_PASTEL_VEIL)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + SaveBattlerTarget(gBattlerTarget); + gBattlerTarget = battler; + gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] = TRUE; + if (gBattleMons[battler].status1 & (STATUS1_POISON | STATUS1_TOXIC_POISON) + || gBattleMons[partner].status1 & (STATUS1_POISON | STATUS1_TOXIC_POISON)) // Only adds to the popup stack if there is poison to cure. + PushTraitStack(battler, ABILITY_PASTEL_VEIL); + BattleScriptPushCursorAndCallback(BattleScript_PastelVeilActivates); + effect++; + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_ANTICIPATION)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + bool32 anticipationCheck = FALSE; + struct DamageContext ctx = {0}; + uq4_12_t modifier = UQ_4_12(1.0); + for (i = 0; i < MAX_BATTLERS_COUNT; i++) { - struct DamageContext ctx = {0}; - uq4_12_t modifier = UQ_4_12(1.0); - for (i = 0; i < MAX_BATTLERS_COUNT; i++) + if (IsBattlerAlive(i) && !IsBattlerAlly(i, battler)) { - if (IsBattlerAlive(i) && !IsBattlerAlly(i, battler)) + for (j = 0; j < MAX_MON_MOVES; j++) { - for (j = 0; j < MAX_MON_MOVES; j++) - { - move = gBattleMons[i].moves[j]; - enum BattleMoveEffects moveEffect = GetMoveEffect(move); - moveType = GetBattleMoveType(move); + move = gBattleMons[i].moves[j]; + enum BattleMoveEffects moveEffect = GetMoveEffect(move); + moveType = GetBattleMoveType(move); ctx.battlerAtk = i; ctx.battlerDef = battler; @@ -4161,481 +4343,332 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab if (modifier >= UQ_4_12(2.0) || moveEffect == EFFECT_OHKO || moveEffect == EFFECT_SHEER_COLD) - { - effect++; - break; - } + { + anticipationCheck = TRUE; + effect++; + break; } } } + } - if (effect != 0) + if (anticipationCheck) + CommonSwitchInAbilities(battler, ABILITY_ANTICIPATION, traitCheck, BattleScript_AnticipationActivates); // effect++ already called, so it is not incremented again here + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_FRISK)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + effect += CommonSwitchInAbilities(battler, ABILITY_FRISK, traitCheck, BattleScript_FriskActivates); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_FOREWARN)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] && !IsOpposingSideEmpty(battler)) + { + ForewarnChooseMove(battler); + effect += CommonSwitchInAbilities(battler, ABILITY_FOREWARN, traitCheck, BattleScript_ForewarnActivates); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_DOWNLOAD)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + enum Stat statId; + u32 opposingBattler; + u32 opposingDef = 0, opposingSpDef = 0; + + opposingBattler = BATTLE_OPPOSITE(battler); + for (i = 0; i < 2; opposingBattler ^= BIT_FLANK, i++) + { + if (IsBattlerAlive(opposingBattler)) { - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_ANTICIPATION; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg); + opposingDef += gBattleMons[opposingBattler].defense + * gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_DEF]][0] + / gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_DEF]][1]; + opposingSpDef += gBattleMons[opposingBattler].spDefense + * gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_SPDEF]][0] + / gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_SPDEF]][1]; } } - break; - case ABILITY_FRISK: - if (!gSpecialStatuses[battler].switchInAbilityDone) - { - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_FriskActivates); // Try activate - effect++; - } - return effect; // Note: It returns effect as to not record the ability if Frisk does not activate. - case ABILITY_FOREWARN: - if (!gSpecialStatuses[battler].switchInAbilityDone && !IsOpposingSideEmpty(battler)) + + if (opposingDef < opposingSpDef) + statId = STAT_ATK; + else + statId = STAT_SPATK; + + gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] = TRUE; + + if (CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN)) { - ForewarnChooseMove(battler); - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_FOREWARN; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg); + SET_STATCHANGER(statId, 1, FALSE); + SaveBattlerAttacker(gBattlerAttacker); + gBattlerAttacker = battler; + PREPARE_STAT_BUFFER(gBattleTextBuff1, statId); + PushTraitStack(battler, ABILITY_DOWNLOAD); + BattleScriptPushCursorAndCallback(BattleScript_AttackerDownloadStatRaise); effect++; } - break; - case ABILITY_DOWNLOAD: - if (!gSpecialStatuses[battler].switchInAbilityDone) - { - enum Stat statId; - u32 opposingBattler; - u32 opposingDef = 0, opposingSpDef = 0; + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_PRESSURE)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + effect += CommonSwitchInAbilities(battler, ABILITY_PRESSURE, traitCheck, BattleScript_PressureActivates); - opposingBattler = BATTLE_OPPOSITE(battler); - for (i = 0; i < 2; opposingBattler ^= BIT_FLANK, i++) - { - if (IsBattlerAlive(opposingBattler)) - { - opposingDef += gBattleMons[opposingBattler].defense - * gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_DEF]][0] - / gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_DEF]][1]; - opposingSpDef += gBattleMons[opposingBattler].spDefense - * gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_SPDEF]][0] - / gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_SPDEF]][1]; - } - } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_DARK_AURA)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + effect += CommonSwitchInAbilities(battler, ABILITY_DARK_AURA, traitCheck, BattleScript_DarkAuraActivates); - if (opposingDef < opposingSpDef) - statId = STAT_ATK; - else - statId = STAT_SPATK; + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_FAIRY_AURA)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + effect += CommonSwitchInAbilities(battler, ABILITY_FAIRY_AURA, traitCheck, BattleScript_FairyAuraActivates); - gSpecialStatuses[battler].switchInAbilityDone = TRUE; + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_AURA_BREAK)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + effect += CommonSwitchInAbilities(battler, ABILITY_AURA_BREAK, traitCheck, BattleScript_AuraBreakActivates); - if (CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) - { - SET_STATCHANGER(statId, 1, FALSE); - SaveBattlerAttacker(gBattlerAttacker); - gBattlerAttacker = battler; - PREPARE_STAT_BUFFER(gBattleTextBuff1, statId); - BattleScriptPushCursorAndCallback(BattleScript_AttackerAbilityStatRaiseEnd3); - effect++; - } - } - break; - case ABILITY_PRESSURE: - if (!gSpecialStatuses[battler].switchInAbilityDone) - { - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_PRESSURE; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg); - effect++; - } - break; - case ABILITY_DARK_AURA: - if (!gSpecialStatuses[battler].switchInAbilityDone) - { - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_DARKAURA; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg); - effect++; - } - break; - case ABILITY_FAIRY_AURA: - if (!gSpecialStatuses[battler].switchInAbilityDone) - { - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_FAIRYAURA; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg); - effect++; - } - break; - case ABILITY_AURA_BREAK: - if (!gSpecialStatuses[battler].switchInAbilityDone) - { - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_AURABREAK; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg); - effect++; - } - break; - case ABILITY_COMATOSE: - if (!gSpecialStatuses[battler].switchInAbilityDone) - { - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_COMATOSE; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg); - effect++; - } - break; - case ABILITY_SCREEN_CLEANER: - if (!gSpecialStatuses[battler].switchInAbilityDone && TryRemoveScreens(battler)) - { - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_SCREENCLEANER; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg); - effect++; - } - break; - case ABILITY_DRIZZLE: - if (TryChangeBattleWeather(battler, BATTLE_WEATHER_RAIN, gLastUsedAbility)) - { - BattleScriptPushCursorAndCallback(BattleScript_DrizzleActivates); - effect++; - } - else if (gBattleWeather & B_WEATHER_PRIMAL_ANY && HasWeatherEffect() && !gSpecialStatuses[battler].switchInAbilityDone) - { - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_BlockedByPrimalWeatherEnd3); - effect++; - } - break; - case ABILITY_SAND_STREAM: - if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SANDSTORM, gLastUsedAbility)) - { - BattleScriptPushCursorAndCallback(BattleScript_SandstreamActivates); - effect++; - } - else if (gBattleWeather & B_WEATHER_PRIMAL_ANY && HasWeatherEffect() && !gSpecialStatuses[battler].switchInAbilityDone) - { - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_BlockedByPrimalWeatherEnd3); - effect++; - } - break; - case ABILITY_ORICHALCUM_PULSE: - case ABILITY_DROUGHT: - if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SUN, gLastUsedAbility)) - { - BattleScriptPushCursorAndCallback(BattleScript_DroughtActivates); - effect++; - } - else if (gBattleWeather & B_WEATHER_PRIMAL_ANY && HasWeatherEffect() && !gSpecialStatuses[battler].switchInAbilityDone) - { - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_BlockedByPrimalWeatherEnd3); - effect++; - } - break; - case ABILITY_SNOW_WARNING: - if (GetConfig(CONFIG_SNOW_WARNING) >= GEN_9 && TryChangeBattleWeather(battler, BATTLE_WEATHER_SNOW, gLastUsedAbility)) - { - BattleScriptPushCursorAndCallback(BattleScript_SnowWarningActivatesSnow); - effect++; - } - else if (GetConfig(CONFIG_SNOW_WARNING) < GEN_9 && TryChangeBattleWeather(battler, BATTLE_WEATHER_HAIL, gLastUsedAbility)) - { - BattleScriptPushCursorAndCallback(BattleScript_SnowWarningActivatesHail); - effect++; - } - else if (gBattleWeather & B_WEATHER_PRIMAL_ANY && HasWeatherEffect() && !gSpecialStatuses[battler].switchInAbilityDone) - { - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_BlockedByPrimalWeatherEnd3); - effect++; - } - break; - case ABILITY_ELECTRIC_SURGE: - case ABILITY_HADRON_ENGINE: - if (TryChangeBattleTerrain(battler, STATUS_FIELD_ELECTRIC_TERRAIN)) - { - BattleScriptPushCursorAndCallback(BattleScript_ElectricSurgeActivates); - effect++; - } - break; - case ABILITY_GRASSY_SURGE: - if (TryChangeBattleTerrain(battler, STATUS_FIELD_GRASSY_TERRAIN)) - { - BattleScriptPushCursorAndCallback(BattleScript_GrassySurgeActivates); - effect++; - } - break; - case ABILITY_MISTY_SURGE: - if (TryChangeBattleTerrain(battler, STATUS_FIELD_MISTY_TERRAIN)) - { - BattleScriptPushCursorAndCallback(BattleScript_MistySurgeActivates); - effect++; - } - break; - case ABILITY_PSYCHIC_SURGE: - if (TryChangeBattleTerrain(battler, STATUS_FIELD_PSYCHIC_TERRAIN)) - { - BattleScriptPushCursorAndCallback(BattleScript_PsychicSurgeActivates); - effect++; - } - break; - case ABILITY_INTIMIDATE: - if (!gSpecialStatuses[battler].switchInAbilityDone && !IsOpposingSideEmpty(battler)) - { - SaveBattlerAttacker(gBattlerAttacker); - gBattlerAttacker = battler; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - SET_STATCHANGER(STAT_ATK, 1, TRUE); - BattleScriptPushCursorAndCallback(BattleScript_IntimidateActivates); - effect++; - } - break; - case ABILITY_SUPERSWEET_SYRUP: - if (!gSpecialStatuses[battler].switchInAbilityDone - && !GetBattlerPartyState(battler)->supersweetSyrup - && !IsOpposingSideEmpty(battler)) - { - SaveBattlerAttacker(gBattlerAttacker); - gBattlerAttacker = battler; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - GetBattlerPartyState(battler)->supersweetSyrup = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_SupersweetSyrupActivates); - effect++; - } - break; - case ABILITY_CLOUD_NINE: - case ABILITY_AIR_LOCK: - if (!gSpecialStatuses[battler].switchInAbilityDone) - { - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_AnnounceAirLockCloudNine); - effect++; - } - break; - case ABILITY_TERAFORM_ZERO: - if (!gSpecialStatuses[battler].switchInAbilityDone - && gBattleMons[battler].species == SPECIES_TERAPAGOS_STELLAR) - { - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_ActivateTeraformZero); - effect++; - } - break; - case ABILITY_SCHOOLING: - if (gBattleMons[battler].level < 20) - break; - // Fallthrough - case ABILITY_ZEN_MODE: - case ABILITY_SHIELDS_DOWN: - if (TryBattleFormChange(battler, FORM_CHANGE_BATTLE_HP_PERCENT)) - { - BattleScriptPushCursorAndCallback(BattleScript_BattlerFormChangeEnd3); - effect++; - } - break; - case ABILITY_INTREPID_SWORD: - if (!gSpecialStatuses[battler].switchInAbilityDone - && !GetBattlerPartyState(battler)->intrepidSwordBoost) - { - if (GetConfig(CONFIG_INTREPID_SWORD) == GEN_9) - GetBattlerPartyState(battler)->intrepidSwordBoost = TRUE; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - if (CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) - { - SET_STATCHANGER(STAT_ATK, 1, FALSE); - BattleScriptPushCursorAndCallback(BattleScript_BattlerAbilityStatRaiseOnSwitchIn); - effect++; - } - } - break; - case ABILITY_DAUNTLESS_SHIELD: - if (!gSpecialStatuses[battler].switchInAbilityDone - && !GetBattlerPartyState(battler)->dauntlessShieldBoost) - { - if (GetConfig(CONFIG_DAUNTLESS_SHIELD) == GEN_9) - GetBattlerPartyState(battler)->dauntlessShieldBoost = TRUE; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - if (CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) - { - SET_STATCHANGER(STAT_DEF, 1, FALSE); - BattleScriptPushCursorAndCallback(BattleScript_BattlerAbilityStatRaiseOnSwitchIn); - effect++; - } - } - break; - case ABILITY_WIND_RIDER: - if (!gSpecialStatuses[battler].switchInAbilityDone - && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility) - && gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_TAILWIND) - { - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - SET_STATCHANGER(STAT_ATK, 1, FALSE); - BattleScriptPushCursorAndCallback(BattleScript_BattlerAbilityStatRaiseOnSwitchIn); - effect++; - } - break; - case ABILITY_DESOLATE_LAND: - if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SUN_PRIMAL, gLastUsedAbility)) - { - BattleScriptPushCursorAndCallback(BattleScript_DesolateLandActivates); - effect++; - } - break; - case ABILITY_PRIMORDIAL_SEA: - if (TryChangeBattleWeather(battler, BATTLE_WEATHER_RAIN_PRIMAL, gLastUsedAbility)) - { - BattleScriptPushCursorAndCallback(BattleScript_PrimordialSeaActivates); - effect++; - } - break; - case ABILITY_DELTA_STREAM: - if (TryChangeBattleWeather(battler, BATTLE_WEATHER_STRONG_WINDS, gLastUsedAbility)) - { - BattleScriptPushCursorAndCallback(BattleScript_DeltaStreamActivates); - effect++; - } - break; - case ABILITY_VESSEL_OF_RUIN: - if (!gSpecialStatuses[battler].switchInAbilityDone) - { - gBattleMons[battler].volatiles.vesselOfRuin = TRUE; - PREPARE_STAT_BUFFER(gBattleTextBuff1, STAT_SPATK); - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_RuinAbilityActivates); - effect++; - } - break; - case ABILITY_SWORD_OF_RUIN: - if (!gSpecialStatuses[battler].switchInAbilityDone) - { - gBattleMons[battler].volatiles.swordOfRuin = TRUE; - PREPARE_STAT_BUFFER(gBattleTextBuff1, STAT_DEF); - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_RuinAbilityActivates); - effect++; - } - break; - case ABILITY_TABLETS_OF_RUIN: - if (!gSpecialStatuses[battler].switchInAbilityDone) - { - gBattleMons[battler].volatiles.tabletsOfRuin = TRUE; - PREPARE_STAT_BUFFER(gBattleTextBuff1, STAT_ATK); - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_RuinAbilityActivates); - effect++; - } - break; - case ABILITY_BEADS_OF_RUIN: - if (!gSpecialStatuses[battler].switchInAbilityDone) - { - gBattleMons[battler].volatiles.beadsOfRuin = TRUE; - PREPARE_STAT_BUFFER(gBattleTextBuff1, STAT_SPDEF); - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_RuinAbilityActivates); - effect++; - } - break; - case ABILITY_SUPREME_OVERLORD: - if (!gSpecialStatuses[battler].switchInAbilityDone) + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_COMATOSE)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + effect += CommonSwitchInAbilities(battler, ABILITY_COMATOSE, traitCheck, BattleScript_ComatoseActivates); + + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_SCREEN_CLEANER)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] + && TryRemoveScreens(battler)) + effect += CommonSwitchInAbilities(battler, ABILITY_SCREEN_CLEANER, traitCheck, BattleScript_ScreenCleanerActivates); + + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_DRIZZLE)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + if (TryChangeBattleWeather(battler, BATTLE_WEATHER_RAIN, TRUE)) + effect += CommonSwitchInAbilities(battler, ABILITY_DRIZZLE, traitCheck, BattleScript_DrizzleActivates); + else if (gBattleWeather & B_WEATHER_PRIMAL_ANY && HasWeatherEffect()) + effect += CommonSwitchInAbilities(battler, ABILITY_DRIZZLE, traitCheck, BattleScript_BlockedByPrimalWeatherEnd3); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_SAND_STREAM)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SANDSTORM, TRUE)) + effect += CommonSwitchInAbilities(battler, ABILITY_SAND_STREAM, traitCheck, BattleScript_SandstreamActivates); + else if (gBattleWeather & B_WEATHER_PRIMAL_ANY && HasWeatherEffect()) + effect += CommonSwitchInAbilities(battler, ABILITY_SAND_STREAM, traitCheck, BattleScript_BlockedByPrimalWeatherEnd3); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_DROUGHT)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SUN, TRUE)) + effect += CommonSwitchInAbilities(battler, ABILITY_DROUGHT, traitCheck, BattleScript_DroughtActivates); + else if (gBattleWeather & B_WEATHER_PRIMAL_ANY && HasWeatherEffect()) + effect += CommonSwitchInAbilities(battler, ABILITY_DROUGHT, traitCheck, BattleScript_BlockedByPrimalWeatherEnd3); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_ORICHALCUM_PULSE)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] = TRUE; + if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SUN, TRUE)) + effect += CommonSwitchInAbilities(battler, ABILITY_ORICHALCUM_PULSE, traitCheck, BattleScript_DroughtActivates); + else if (gBattleWeather & B_WEATHER_PRIMAL_ANY && HasWeatherEffect() && !gSpecialStatuses[battler].switchInAbilityDone) + effect += CommonSwitchInAbilities(battler, ABILITY_ORICHALCUM_PULSE, traitCheck, BattleScript_BlockedByPrimalWeatherEnd3); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_SNOW_WARNING)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + if (GetConfig(CONFIG_SNOW_WARNING) >= GEN_9 && TryChangeBattleWeather(battler, BATTLE_WEATHER_SNOW, TRUE)) + effect += CommonSwitchInAbilities(battler, ABILITY_SNOW_WARNING, traitCheck, BattleScript_SnowWarningActivatesSnow); + else if (GetConfig(CONFIG_SNOW_WARNING) < GEN_9 && TryChangeBattleWeather(battler, BATTLE_WEATHER_HAIL, TRUE)) + effect += CommonSwitchInAbilities(battler, ABILITY_SNOW_WARNING, traitCheck, BattleScript_SnowWarningActivatesHail); + else if (gBattleWeather & B_WEATHER_PRIMAL_ANY && HasWeatherEffect()) + effect += CommonSwitchInAbilities(battler, ABILITY_SNOW_WARNING, traitCheck, BattleScript_BlockedByPrimalWeatherEnd3); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_ELECTRIC_SURGE)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] = TRUE; + if(TryChangeBattleTerrain(battler, STATUS_FIELD_ELECTRIC_TERRAIN)) + effect += CommonSwitchInAbilities(battler, ABILITY_ELECTRIC_SURGE, traitCheck, BattleScript_ElectricSurgeActivates); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_HADRON_ENGINE)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] = TRUE; + if(TryChangeBattleTerrain(battler, STATUS_FIELD_ELECTRIC_TERRAIN)) + effect += CommonSwitchInAbilities(battler, ABILITY_HADRON_ENGINE, traitCheck, BattleScript_ElectricSurgeActivates); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_GRASSY_SURGE)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] = TRUE; + if(TryChangeBattleTerrain(battler, STATUS_FIELD_GRASSY_TERRAIN)) + effect += CommonSwitchInAbilities(battler, ABILITY_GRASSY_SURGE, traitCheck, BattleScript_GrassySurgeActivates); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_MISTY_SURGE)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] = TRUE; + if(TryChangeBattleTerrain(battler, STATUS_FIELD_MISTY_TERRAIN)) + effect += CommonSwitchInAbilities(battler, ABILITY_MISTY_SURGE, traitCheck, BattleScript_MistySurgeActivates); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_PSYCHIC_SURGE)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] = TRUE; + if(TryChangeBattleTerrain(battler, STATUS_FIELD_PSYCHIC_TERRAIN)) + effect += CommonSwitchInAbilities(battler, ABILITY_PSYCHIC_SURGE, traitCheck, BattleScript_PsychicSurgeActivates); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_CLOUD_NINE)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + effect += CommonSwitchInAbilities(battler, ABILITY_CLOUD_NINE, traitCheck, BattleScript_AnnounceAirLockCloudNine); + + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_AIR_LOCK)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + effect += CommonSwitchInAbilities(battler, ABILITY_AIR_LOCK, traitCheck, BattleScript_AnnounceAirLockCloudNine); + + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_TERAFORM_ZERO)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] + && gBattleMons[battler].species == SPECIES_TERAPAGOS_STELLAR) + effect += CommonSwitchInAbilities(battler, ABILITY_TERAFORM_ZERO, traitCheck, BattleScript_ActivateTeraformZero); + + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_SCHOOLING)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] + && gBattleMons[battler].level >= 20 + && TryBattleFormChange(battler, FORM_CHANGE_BATTLE_HP_PERCENT)) + { + effect += CommonSwitchInAbilities(battler, ABILITY_SCHOOLING, traitCheck, BattleScript_BattlerFormChangeEnd3); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_ZEN_MODE)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] + && TryBattleFormChange(battler, FORM_CHANGE_BATTLE_HP_PERCENT)) + { + effect += CommonSwitchInAbilities(battler, ABILITY_ZEN_MODE, traitCheck, BattleScript_BattlerFormChangeEnd3); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_SHIELDS_DOWN)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] + && TryBattleFormChange(battler, FORM_CHANGE_BATTLE_HP_PERCENT)) + { + effect += CommonSwitchInAbilities(battler, ABILITY_SHIELDS_DOWN, traitCheck, BattleScript_BattlerFormChangeEnd3); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_INTREPID_SWORD)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN) + && !(gBattleStruct->partyState[GetBattlerSide(battler)][gBattlerPartyIndexes[battler]].intrepidSwordBoost)) + { + if (GetConfig(CONFIG_INTREPID_SWORD) == GEN_9) + gBattleStruct->partyState[GetBattlerSide(battler)][gBattlerPartyIndexes[battler]].intrepidSwordBoost = TRUE; + effect += CommonSwitchInAbilities(battler, ABILITY_INTREPID_SWORD, traitCheck, BattleScript_BattlerAbilityStatRaiseOnSwitchInIntrepid); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_DAUNTLESS_SHIELD)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] && CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN) + && !(gBattleStruct->partyState[GetBattlerSide(battler)][gBattlerPartyIndexes[battler]].dauntlessShieldBoost)) + { + if (GetConfig(CONFIG_DAUNTLESS_SHIELD) == GEN_9) + gBattleStruct->partyState[GetBattlerSide(battler)][gBattlerPartyIndexes[battler]].dauntlessShieldBoost = TRUE; + effect += CommonSwitchInAbilities(battler, ABILITY_DAUNTLESS_SHIELD, traitCheck, BattleScript_BattlerAbilityStatRaiseOnSwitchInDauntless); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_WIND_RIDER)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] + && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN) + && gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_TAILWIND) + { + effect += CommonSwitchInAbilities(battler, ABILITY_WIND_RIDER, traitCheck, BattleScript_BattlerAbilityStatRaiseOnSwitchInWindRider); + } + if (SearchTraits(battlerTraits, ABILITY_DESOLATE_LAND) && TryChangeBattleWeather(battler, BATTLE_WEATHER_SUN_PRIMAL, TRUE)) + { + PushTraitStack(battler, ABILITY_DESOLATE_LAND); + BattleScriptPushCursorAndCallback(BattleScript_DesolateLandActivates); + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_PRIMORDIAL_SEA) && TryChangeBattleWeather(battler, BATTLE_WEATHER_RAIN_PRIMAL, TRUE)) + { + PushTraitStack(battler, ABILITY_PRIMORDIAL_SEA); + BattleScriptPushCursorAndCallback(BattleScript_PrimordialSeaActivates); + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_DELTA_STREAM) && TryChangeBattleWeather(battler, BATTLE_WEATHER_STRONG_WINDS, TRUE)) + { + PushTraitStack(battler, ABILITY_DELTA_STREAM); + BattleScriptPushCursorAndCallback(BattleScript_DeltaStreamActivates); + effect++; + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_VESSEL_OF_RUIN)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + gBattleMons[battler].volatiles.vesselOfRuin = TRUE; + effect += CommonSwitchInAbilities(battler, ABILITY_VESSEL_OF_RUIN, traitCheck, BattleScript_RuinAbilityActivatesVessel); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_SWORD_OF_RUIN)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + gBattleMons[battler].volatiles.swordOfRuin = TRUE; + effect += CommonSwitchInAbilities(battler, ABILITY_SWORD_OF_RUIN, traitCheck, BattleScript_RuinAbilityActivatesSword); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_TABLETS_OF_RUIN)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + gBattleMons[battler].volatiles.tabletsOfRuin = TRUE; + effect += CommonSwitchInAbilities(battler, ABILITY_TABLETS_OF_RUIN, traitCheck, BattleScript_RuinAbilityActivatesTablets); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_BEADS_OF_RUIN)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + gBattleMons[battler].volatiles.beadsOfRuin = TRUE; + effect += CommonSwitchInAbilities(battler, ABILITY_BEADS_OF_RUIN, traitCheck, BattleScript_RuinAbilityActivatesBeads); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_SUPREME_OVERLORD)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] = TRUE; + gBattleStruct->supremeOverlordCounter[battler] = min(5, GetBattlerSideFaintCounter(battler)); + if (gBattleStruct->supremeOverlordCounter[battler] > 0) + effect += CommonSwitchInAbilities(battler, ABILITY_SUPREME_OVERLORD, traitCheck, BattleScript_SupremeOverlordActivates); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_COSTAR)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] + && IsDoubleBattle() + && IsBattlerAlive(partner) + && BattlerHasCopyableChanges(partner) + && !SearchTraits(battlerTraits, ABILITY_CURIOUS_MEDICINE)) // Curious Medicine would negate Costar so it's one or the other + { + for (i = 0; i < NUM_BATTLE_STATS; i++) + gBattleMons[battler].statStages[i] = gBattleMons[partner].statStages[i]; + gBattleMons[battler].volatiles.focusEnergy = gBattleMons[partner].volatiles.focusEnergy; + gBattleMons[battler].volatiles.dragonCheer = gBattleMons[partner].volatiles.dragonCheer; + gBattleMons[battler].volatiles.bonusCritStages = gBattleMons[partner].volatiles.bonusCritStages; + gEffectBattler = partner; + effect += CommonSwitchInAbilities(battler, ABILITY_COSTAR, traitCheck, BattleScript_CostarActivates); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_ZERO_TO_HERO))){ + side = GetBattlerSide(battler); + mon = GetBattlerMon(battler); + + if(!gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] + && GetMonData(mon, MON_DATA_SPECIES) == SPECIES_PALAFIN_HERO + && !gBattleStruct->partyState[side][gBattlerPartyIndexes[battler]].transformZeroToHero) { - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - gBattleStruct->supremeOverlordCounter[battler] = min(5, GetBattlerSideFaintCounter(battler)); - if (gBattleStruct->supremeOverlordCounter[battler] > 0) - { - BattleScriptPushCursorAndCallback(BattleScript_SupremeOverlordActivates); - effect++; - } + GetBattlerPartyState(battler)->transformZeroToHero = TRUE; + effect += CommonSwitchInAbilities(battler, ABILITY_ZERO_TO_HERO, traitCheck, BattleScript_ZeroToHeroActivates); } - break; - case ABILITY_COSTAR: - partner = BATTLE_PARTNER(battler); - if (!gSpecialStatuses[battler].switchInAbilityDone - && IsDoubleBattle() - && IsBattlerAlive(partner) - && BattlerHasCopyableChanges(partner)) + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_HOSPITALITY))) + { + if (!gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] + && IsDoubleBattle() + && !gBattleMons[partner].volatiles.healBlock + && gBattleMons[partner].hp < gBattleMons[partner].maxHP + && IsBattlerAlive(partner)) { - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - for (i = 0; i < NUM_BATTLE_STATS; i++) - gBattleMons[battler].statStages[i] = gBattleMons[partner].statStages[i]; - // Copy crit boosts (Focus Energy, Dragon Cheer, G-Max Chi Strike) - gBattleMons[battler].volatiles.focusEnergy = gBattleMons[partner].volatiles.focusEnergy; - gBattleMons[battler].volatiles.dragonCheer = gBattleMons[partner].volatiles.dragonCheer; - gBattleMons[battler].volatiles.bonusCritStages = gBattleMons[partner].volatiles.bonusCritStages; gEffectBattler = partner; - BattleScriptPushCursorAndCallback(BattleScript_CostarActivates); - effect++; + SetHealAmount(partner, GetNonDynamaxMaxHP(partner) / 4); + effect += CommonSwitchInAbilities(battler, ABILITY_HOSPITALITY, traitCheck, BattleScript_HospitalityActivates); } - break; - case ABILITY_ZERO_TO_HERO: - if (!gSpecialStatuses[battler].switchInAbilityDone - && GetMonData(GetBattlerMon(battler), MON_DATA_SPECIES) == SPECIES_PALAFIN_HERO - && !GetBattlerPartyState(battler)->transformZeroToHero) + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_EMBODY_ASPECT_TEAL_MASK)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + enum Stat stat = STAT_SPEED; + + if (!CompareStat(battler, stat, MAX_STAT_STAGE, CMP_EQUAL)) { - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - GetBattlerPartyState(battler)->transformZeroToHero = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_ZeroToHeroActivates); - effect++; + effect += CommonSwitchInAbilities(battler, ABILITY_EMBODY_ASPECT_TEAL_MASK, traitCheck, BattleScript_BattlerAbilityStatRaiseOnSwitchInEmbodyAspectTeal); } - break; - case ABILITY_HOSPITALITY: - partner = BATTLE_PARTNER(battler); - - if (!gSpecialStatuses[battler].switchInAbilityDone - && IsDoubleBattle() - && !gBattleMons[partner].volatiles.healBlock - && gBattleMons[partner].hp < gBattleMons[partner].maxHP - && IsBattlerAlive(partner)) + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_EMBODY_ASPECT_HEARTHFLAME_MASK)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + enum Stat stat = STAT_ATK; + + if (!CompareStat(battler, stat, MAX_STAT_STAGE, CMP_EQUAL)) { - gEffectBattler = partner; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - SetHealAmount(partner, GetNonDynamaxMaxHP(partner) / 4); - BattleScriptPushCursorAndCallback(BattleScript_HospitalityActivates); - effect++; + effect += CommonSwitchInAbilities(battler, ABILITY_EMBODY_ASPECT_HEARTHFLAME_MASK, traitCheck, BattleScript_BattlerAbilityStatRaiseOnSwitchInEmbodyAspectHearthFlame); } - break; - case ABILITY_EMBODY_ASPECT_TEAL_MASK: - case ABILITY_EMBODY_ASPECT_HEARTHFLAME_MASK: - case ABILITY_EMBODY_ASPECT_WELLSPRING_MASK: - case ABILITY_EMBODY_ASPECT_CORNERSTONE_MASK: - if (!gSpecialStatuses[battler].switchInAbilityDone) - { - enum Stat stat; - - if (gLastUsedAbility == ABILITY_EMBODY_ASPECT_HEARTHFLAME_MASK) - stat = STAT_ATK; - else if (gLastUsedAbility == ABILITY_EMBODY_ASPECT_WELLSPRING_MASK) - stat = STAT_SPDEF; - else if (gLastUsedAbility == ABILITY_EMBODY_ASPECT_CORNERSTONE_MASK) - stat = STAT_DEF; - else //ABILITY_EMBODY_ASPECT_TEAL_MASK - stat = STAT_SPEED; - - if (CompareStat(battler, stat, MAX_STAT_STAGE, CMP_EQUAL, gLastUsedAbility)) - break; + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_EMBODY_ASPECT_WELLSPRING_MASK)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + enum Stat stat = STAT_SPDEF; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - SET_STATCHANGER(stat, 1, FALSE); - BattleScriptPushCursorAndCallback(BattleScript_BattlerAbilityStatRaiseOnSwitchIn); - effect++; + if (!CompareStat(battler, stat, MAX_STAT_STAGE, CMP_EQUAL)) + { + effect += CommonSwitchInAbilities(battler, ABILITY_EMBODY_ASPECT_WELLSPRING_MASK, traitCheck, BattleScript_BattlerAbilityStatRaiseOnSwitchInEmbodyAspectWellSpring); } - break; - case ABILITY_TERA_SHIFT: - if (!gSpecialStatuses[battler].switchInAbilityDone - && gBattleMons[battler].species == SPECIES_TERAPAGOS_NORMAL - && TryBattleFormChange(battler, FORM_CHANGE_BATTLE_SWITCH)) + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_EMBODY_ASPECT_CORNERSTONE_MASK)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1]) + { + enum Stat stat = STAT_DEF; + + if (!CompareStat(battler, stat, MAX_STAT_STAGE, CMP_EQUAL)) { - gBattleScripting.abilityPopupOverwrite = gLastUsedAbility = ABILITY_TERA_SHIFT; - gSpecialStatuses[battler].switchInAbilityDone = TRUE; - BattleScriptPushCursorAndCallback(BattleScript_BattlerFormChangeWithStringEnd3); - effect++; + effect += CommonSwitchInAbilities(battler, ABILITY_EMBODY_ASPECT_CORNERSTONE_MASK, traitCheck, BattleScript_BattlerAbilityStatRaiseOnSwitchInEmbodyAspectCornerStone); } - break; - case ABILITY_COMMANDER: - partner = BATTLE_PARTNER(battler); - if (!gSpecialStatuses[battler].switchInAbilityDone + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_TERA_SHIFT)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] + && gBattleMons[battler].species == SPECIES_TERAPAGOS_NORMAL + && TryBattleFormChange(battler, FORM_CHANGE_BATTLE_SWITCH)) + { + effect += CommonSwitchInAbilities(battler, ABILITY_TERA_SHIFT, traitCheck, BattleScript_BattlerFormChangeWithStringEnd3); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_COMMANDER)) && !gSpecialStatuses[battler].switchInTraitDone[traitCheck - 1] && IsBattlerAlive(partner) && IsBattlerAlive(battler) && gBattleStruct->battlerState[partner].commanderSpecies == SPECIES_NONE && gBattleMons[partner].species == SPECIES_DONDOZO && (gChosenActionByBattler[partner] != B_ACTION_SWITCH || HasBattlerActedThisTurn(partner)) && GET_BASE_SPECIES_ID(GetMonData(GetBattlerMon(battler), MON_DATA_SPECIES)) == SPECIES_TATSUGIRI) - { + { SaveBattlerAttacker(gBattlerAttacker); gSpecialStatuses[battler].switchInAbilityDone = TRUE; gBattlerAttacker = partner; @@ -4646,80 +4679,167 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab gBattleMons[battler].volatiles.confusionTurns--; BtlController_EmitSpriteInvisibility(battler, B_COMM_TO_CONTROLLER, TRUE); MarkBattlerForControllerExec(battler); - BattleScriptPushCursorAndCallback(BattleScript_CommanderActivates); - effect++; - } - break; - default: - break; + effect += CommonSwitchInAbilities(battler, ABILITY_COMMANDER, traitCheck, BattleScript_CommanderActivates); } break; case ABILITYEFFECT_ENDTURN: if (IsBattlerAlive(battler)) { + u8 itemTraitType = 0; gBattlerAttacker = battler; - switch (gLastUsedAbility) + + // Item abilities have additional conitions which might conflict with following Else statements + // Harvest, Pickup, and Ball Fetch do their item availability checks first independantly + // then just their activations are included in the If Else statements of the rest of the abilities. + // If affecting the same slot at the same time, priority is Harvest, Pickup, Ball Fetch (Ball Fetch is last because it seeks an empty slot) + + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_HARVEST)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] + && (IsBattlerWeatherAffected(battler, B_WEATHER_SUN) || RandomPercentage(RNG_HARVEST, 50)) + && gBattleMons[battler].item == ITEM_NONE + && gBattleStruct->changedItems[battler] == ITEM_NONE // Will not inherit an item + && GetItemPocket(GetBattlerPartyState(battler)->usedHeldItem) == POCKET_BERRIES + && itemTraitType == 0) + { + itemTraitType = TRIGGER_HARVEST; + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_PICKUP)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] + && itemTraitType == 0) { - case ABILITY_PICKUP: if (gBattleMons[battler].item == ITEM_NONE && gBattleStruct->changedItems[battler] == ITEM_NONE // Will not inherit an item && PickupHasValidTarget(battler)) { + itemTraitType = TRIGGER_PICKUP; + } + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_BALL_FETCH)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] + && gBattleMons[battler].item == ITEM_NONE + && gBattleResults.catchAttempts[ItemIdToBallId(gLastUsedBall)] >= 1 + && !gHasFetchedBall + && itemTraitType == 0) + { + itemTraitType = TRIGGER_BALL_FETCH; + } + + if (itemTraitType != 0) + { + if (itemTraitType == TRIGGER_HARVEST) + { + gSpecialStatuses[battler].endTurnTraitDone[SearchTraits(battlerTraits, ABILITY_HARVEST) - 1] = TRUE; + gLastUsedItem = GetBattlerPartyState(battler)->usedHeldItem; + PushTraitStack(battler, ABILITY_HARVEST); + BattleScriptExecute(BattleScript_HarvestActivates); + effect++; + break; + } + else if (itemTraitType == TRIGGER_PICKUP) + { + gSpecialStatuses[battler].endTurnTraitDone[SearchTraits(battlerTraits, ABILITY_PICKUP) - 1] = TRUE; gBattlerTarget = RandomUniformExcept(RNG_PICKUP, 0, gBattlersCount - 1, CantPickupItem); gLastUsedItem = GetBattlerPartyState(gBattlerTarget)->usedHeldItem; + PushTraitStack(battler, ABILITY_PICKUP); BattleScriptExecute(BattleScript_PickupActivates); effect++; + break; } - break; - case ABILITY_HARVEST: - if ((IsBattlerWeatherAffected(battler, B_WEATHER_SUN) || RandomPercentage(RNG_HARVEST, 50)) - && gBattleMons[battler].item == ITEM_NONE - && gBattleStruct->changedItems[battler] == ITEM_NONE // Will not inherit an item - && GetItemPocket(GetBattlerPartyState(battler)->usedHeldItem) == POCKET_BERRIES) + else if (itemTraitType == TRIGGER_BALL_FETCH) { - gLastUsedItem = GetBattlerPartyState(battler)->usedHeldItem; - BattleScriptExecute(BattleScript_HarvestActivates); + gSpecialStatuses[battler].endTurnTraitDone[SearchTraits(battlerTraits, ABILITY_BALL_FETCH) - 1] = TRUE; + gLastUsedItem = gLastUsedBall; + gBattleScripting.battler = battler; + gBattleMons[battler].item = gLastUsedItem; + BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, 2, &gLastUsedItem); + MarkBattlerForControllerExec(battler); + gHasFetchedBall = TRUE; + PushTraitStack(battler, ABILITY_BALL_FETCH); + BattleScriptExecute(BattleScript_BallFetch); effect++; + break; } + } +else if ((traitCheck = SearchTraits(battlerTraits, ABILITY_ICE_BODY)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] + && IsBattlerWeatherAffected(battler, B_WEATHER_HAIL | B_WEATHER_SNOW) + && !IsBattlerAtMaxHp(battler) + && gBattleMons[battler].volatiles.semiInvulnerable != STATE_UNDERGROUND + && gBattleMons[battler].volatiles.semiInvulnerable != STATE_UNDERWATER + && !gBattleMons[battler].volatiles.healBlock) + { + gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] = TRUE; + PushTraitStack(battler, ABILITY_ICE_BODY); + BattleScriptExecute(BattleScript_IceBodyHeal); + SetHealAmount(battler, GetNonDynamaxMaxHP(battler) / 16); + effect++; break; - case ABILITY_ICE_BODY: - if (IsBattlerWeatherAffected(battler, B_WEATHER_HAIL | B_WEATHER_SNOW) - && !IsBattlerAtMaxHp(battler) - && gBattleMons[battler].volatiles.semiInvulnerable != STATE_UNDERGROUND - && gBattleMons[battler].volatiles.semiInvulnerable != STATE_UNDERWATER - && !gBattleMons[battler].volatiles.healBlock) - { - BattleScriptExecute(BattleScript_IceBodyHeal); + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_RAIN_DISH)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] + && IsBattlerWeatherAffected(battler, B_WEATHER_RAIN) + && !IsBattlerAtMaxHp(battler) + && !gBattleMons[battler].volatiles.healBlock) + { + gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] = TRUE; + PushTraitStack(battler, ABILITY_RAIN_DISH); + if (!SearchTraits(battlerTraits, ABILITY_DRY_SKIN)) SetHealAmount(battler, GetNonDynamaxMaxHP(battler) / 16); - effect++; + else + { + SetHealAmount(battler, GetNonDynamaxMaxHP(battler) * (0.187)); // Rain Dish + Dry Skin + gSpecialStatuses[battler].endTurnTraitDone[SearchTraits(battlerTraits, ABILITY_DRY_SKIN) - 1] = TRUE; } + BattleScriptExecute(BattleScript_RainDishActivates); + effect++; break; - case ABILITY_DRY_SKIN: - if (IsBattlerWeatherAffected(battler, B_WEATHER_SUN)) - goto SOLAR_POWER_HP_DROP; - // Dry Skin works similarly to Rain Dish in Rain - case ABILITY_RAIN_DISH: - if (IsBattlerWeatherAffected(battler, B_WEATHER_RAIN) - && !IsBattlerAtMaxHp(battler) - && !gBattleMons[battler].volatiles.healBlock) + } + else if ((traitCheck = SearchTraits(battlerTraits, ABILITY_SOLAR_POWER)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] + && IsBattlerWeatherAffected(battler, B_WEATHER_SUN)) + { + gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] = TRUE; + PushTraitStack(battler, ABILITY_SOLAR_POWER); + if (!SearchTraits(battlerTraits, ABILITY_DRY_SKIN)) + SetPassiveDamageAmount(battler, GetNonDynamaxMaxHP(battler) / 8); + else { - s32 healAmount = gLastUsedAbility == ABILITY_RAIN_DISH ? 16 : 8; - SetHealAmount(battler, GetNonDynamaxMaxHP(battler) / healAmount); - BattleScriptExecute(BattleScript_RainDishActivates); - effect++; + SetPassiveDamageAmount(battler, GetNonDynamaxMaxHP(battler) / 4); // Solar Power + Dry Skin + gSpecialStatuses[battler].endTurnTraitDone[SearchTraits(battlerTraits, ABILITY_DRY_SKIN) - 1] = TRUE; } + BattleScriptExecute(BattleScript_SolarPowerActivates); + effect++; break; - case ABILITY_HYDRATION: - if (IsBattlerWeatherAffected(battler, B_WEATHER_RAIN) - && gBattleMons[battler].status1 & STATUS1_ANY) + } + else if ((traitCheck = SearchTraits(battlerTraits, ABILITY_DRY_SKIN)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1]) + { + if (IsBattlerWeatherAffected(battler, B_WEATHER_SUN) && !SearchTraits(battlerTraits, ABILITY_SOLAR_POWER)) // Damage stacking handled in Solar Power { - goto ABILITY_HEAL_MON_STATUS; + gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] = TRUE; + PushTraitStack(battler, ABILITY_DRY_SKIN); + SetPassiveDamageAmount(battler, GetNonDynamaxMaxHP(battler) / 8); + BattleScriptExecute(BattleScript_SolarPowerActivates); + effect++; + break; } - break; - case ABILITY_SHED_SKIN: - if ((gBattleMons[battler].status1 & STATUS1_ANY) - && (GetConfig(CONFIG_ABILITY_TRIGGER_CHANCE) == GEN_4 ? RandomPercentage(RNG_SHED_SKIN, 30) : RandomChance(RNG_SHED_SKIN, 1, 3))) + else if (IsBattlerWeatherAffected(battler, B_WEATHER_RAIN) && !SearchTraits(battlerTraits, ABILITY_RAIN_DISH)) // Healing stacking handled in Rain Dish { + gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] = TRUE; + PushTraitStack(battler, ABILITY_DRY_SKIN); + SetHealAmount(battler, GetNonDynamaxMaxHP(battler) / 8); + BattleScriptExecute(BattleScript_RainDishActivates); + effect++; + break; + } + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_HYDRATION)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] + && IsBattlerWeatherAffected(battler, B_WEATHER_RAIN) + && gBattleMons[battler].status1 & STATUS1_ANY) + { + gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] = TRUE; + PushTraitStack(battler, ABILITY_HYDRATION); + goto ABILITY_HEAL_MON_STATUS; + } + else if ((traitCheck = SearchTraits(battlerTraits, ABILITY_SHED_SKIN)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] + && gBattleMons[battler].status1 & STATUS1_ANY + && (GetConfig(CONFIG_ABILITY_TRIGGER_CHANCE) == GEN_4 ? RandomPercentage(RNG_SHED_SKIN, 30) : RandomChance(RNG_SHED_SKIN, 1, 3))) + { + gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] = TRUE; + PushTraitStack(battler, ABILITY_SHED_SKIN); ABILITY_HEAL_MON_STATUS: if (gBattleMons[battler].status1 & (STATUS1_POISON | STATUS1_TOXIC_POISON)) StringCopy(gBattleTextBuff1, gStatusConditionString_PoisonJpn); @@ -4743,402 +4863,472 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, 4, &gBattleMons[battler].status1); MarkBattlerForControllerExec(battler); effect++; - } + break; + } + else if ((traitCheck = SearchTraits(battlerTraits, ABILITY_SPEED_BOOST)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] + && CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN) && gDisableStructs[battler].isFirstTurn != 2) + { + gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] = TRUE; + SaveBattlerAttacker(gBattlerAttacker); + SET_STATCHANGER(STAT_SPEED, 1, FALSE); + PushTraitStack(battler, ABILITY_SPEED_BOOST); + BattleScriptExecute(BattleScript_AttackerAbilityStatRaiseEnd2); + gBattleScripting.battler = battler; + effect++; break; - case ABILITY_SPEED_BOOST: - if (CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility) && gDisableStructs[battler].isFirstTurn != 2) + } + else if ((traitCheck = SearchTraits(battlerTraits, ABILITY_MOODY)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] + && gDisableStructs[battler].isFirstTurn != 2) + { + u32 validToRaise = 0, validToLower = 0; + u32 statsNum = GetConfig(CONFIG_MOODY_ACC_EVASION) >= GEN_8 ? NUM_STATS : NUM_BATTLE_STATS; + + for (i = STAT_ATK; i < statsNum; i++) { - SaveBattlerAttacker(gBattlerAttacker); - SET_STATCHANGER(STAT_SPEED, 1, FALSE); - BattleScriptExecute(BattleScript_AttackerAbilityStatRaiseEnd2); - gBattleScripting.battler = battler; - effect++; + if (CompareStat(battler, i, MIN_STAT_STAGE, CMP_GREATER_THAN)) + validToLower |= 1u << i; + if (CompareStat(battler, i, MAX_STAT_STAGE, CMP_LESS_THAN)) + validToRaise |= 1u << i; } - break; - case ABILITY_MOODY: - if (gDisableStructs[battler].isFirstTurn != 2) - { - u32 validToRaise = 0, validToLower = 0; - u32 statsNum = GetConfig(CONFIG_MOODY_ACC_EVASION) >= GEN_8 ? NUM_STATS : NUM_BATTLE_STATS; - - for (i = STAT_ATK; i < statsNum; i++) - { - if (CompareStat(battler, i, MIN_STAT_STAGE, CMP_GREATER_THAN, gLastUsedAbility)) - validToLower |= 1u << i; - if (CompareStat(battler, i, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) - validToRaise |= 1u << i; - } - gBattleScripting.statChanger = gBattleScripting.savedStatChanger = 0; // for raising and lowering stat respectively - if (validToRaise) // Find stat to raise - { - i = RandomUniformExcept(RNG_MOODY_INCREASE, STAT_ATK, statsNum - 1, MoodyCantRaiseStat); - SET_STATCHANGER(i, 2, FALSE); - validToLower &= ~(1u << i); // Can't lower the same stat as raising. - } - if (validToLower) // Find stat to lower - { - // MoodyCantLowerStat already checks that both stats are different - i = RandomUniformExcept(RNG_MOODY_DECREASE, STAT_ATK, statsNum - 1, MoodyCantLowerStat); - SET_STATCHANGER2(gBattleScripting.savedStatChanger, i, 1, TRUE); - } - BattleScriptExecute(BattleScript_MoodyActivates); - effect++; + gBattleScripting.statChanger = gBattleScripting.savedStatChanger = 0; // for raising and lowering stat respectively + if (validToRaise) // Find stat to raise + { + i = RandomUniformExcept(RNG_MOODY_INCREASE, STAT_ATK, statsNum - 1, MoodyCantRaiseStat); + SET_STATCHANGER(i, 2, FALSE); + validToLower &= ~(1u << i); // Can't lower the same stat as raising. + } + if (validToLower) // Find stat to lower + { + // MoodyCantLowerStat already checks that both stats are different + i = RandomUniformExcept(RNG_MOODY_DECREASE, STAT_ATK, statsNum - 1, MoodyCantLowerStat); + SET_STATCHANGER2(gBattleScripting.savedStatChanger, i, 1, TRUE); } + gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] = TRUE; + PushTraitStack(battler, ABILITY_MOODY); + BattleScriptExecute(BattleScript_MoodyActivates); + effect++; break; - case ABILITY_TRUANT: + } + else if ((traitCheck = SearchTraits(battlerTraits, ABILITY_TRUANT)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1]) + { + gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] = TRUE; gDisableStructs[gBattlerAttacker].truantCounter ^= 1; break; - case ABILITY_SLOW_START: + } + else if ((traitCheck = SearchTraits(battlerTraits, ABILITY_SLOW_START)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1]) + { if (gDisableStructs[battler].slowStartTimer > 0 && --gDisableStructs[battler].slowStartTimer == 0) { + gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] = TRUE; + PushTraitStack(battler, ABILITY_SLOW_START); BattleScriptExecute(BattleScript_SlowStartEnds); effect++; + break; } - break; - case ABILITY_BAD_DREAMS: + } + else if ((traitCheck = SearchTraits(battlerTraits, ABILITY_BAD_DREAMS)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1]) + { + gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] = TRUE; + PushTraitStack(battler, ABILITY_BAD_DREAMS); BattleScriptExecute(BattleScript_BadDreamsActivates); effect++; break; - case ABILITY_SOLAR_POWER: - if (IsBattlerWeatherAffected(battler, B_WEATHER_SUN)) - { - SOLAR_POWER_HP_DROP: - SetPassiveDamageAmount(battler, GetNonDynamaxMaxHP(battler) / 8); - BattleScriptExecute(BattleScript_SolarPowerActivates); - effect++; - } - break; - case ABILITY_HEALER: - gBattleScripting.battler = BATTLE_PARTNER(battler); + } + else if ((traitCheck = SearchTraits(battlerTraits, ABILITY_HEALER)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1]) + { + gBattleScripting.battler = partner; if (IsBattlerAlive(gBattleScripting.battler) - && gBattleMons[gBattleScripting.battler].status1 & STATUS1_ANY - && RandomPercentage(RNG_HEALER, 30)) + && gBattleMons[gBattleScripting.battler].status1 & STATUS1_ANY + && RandomPercentage(RNG_HEALER, 30)) { + gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] = TRUE; + PushTraitStack(battler, ABILITY_HEALER); BattleScriptExecute(BattleScript_HealerActivates); effect++; - } - break; - case ABILITY_SCHOOLING: - if (gBattleMons[battler].level < 20) break; - // Fallthrough - case ABILITY_ZEN_MODE: - case ABILITY_SHIELDS_DOWN: - if (TryBattleFormChange(battler, FORM_CHANGE_BATTLE_HP_PERCENT)) - { - gBattleScripting.battler = battler; - BattleScriptExecute(BattleScript_BattlerFormChangeEnd2); - effect++; } + } + if ((traitCheck = SearchTraits(battlerTraits, ABILITY_SCHOOLING)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] + && gBattleMons[battler].level >= 20 + && TryBattleFormChange(battler, FORM_CHANGE_BATTLE_HP_PERCENT)) + { + gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] = TRUE; + gBattleScripting.battler = battler; + PushTraitStack(battler, ABILITY_SCHOOLING); + BattleScriptExecute(BattleScript_BattlerFormChangeEnd2); + effect++; break; - case ABILITY_POWER_CONSTRUCT: - if (TryBattleFormChange(battler, FORM_CHANGE_BATTLE_HP_PERCENT)) - { - gBattleScripting.battler = battler; - BattleScriptExecute(BattleScript_PowerConstruct); - effect++; - } + } + else if ((traitCheck = SearchTraits(battlerTraits, ABILITY_ZEN_MODE)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] + && TryBattleFormChange(battler, FORM_CHANGE_BATTLE_HP_PERCENT)) + { + gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] = TRUE; + gBattleScripting.battler = battler; + PushTraitStack(battler, ABILITY_ZEN_MODE); + BattleScriptExecute(BattleScript_BattlerFormChangeEnd2); + effect++; break; - case ABILITY_BALL_FETCH: - if (!(gBattleTypeFlags & BATTLE_TYPE_RAID) - && gBattleMons[battler].item == ITEM_NONE - && gBattleResults.catchAttempts[ItemIdToBallId(gLastUsedBall)] >= 1 - && !gHasFetchedBall) - { - gLastUsedItem = gLastUsedBall; - gBattleScripting.battler = battler; - gBattleMons[battler].item = gLastUsedItem; - BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, 2, &gLastUsedItem); - MarkBattlerForControllerExec(battler); - gHasFetchedBall = TRUE; - BattleScriptExecute(BattleScript_BallFetch); - effect++; - } + } + else if ((traitCheck = SearchTraits(battlerTraits, ABILITY_SHIELDS_DOWN)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] + && TryBattleFormChange(battler, FORM_CHANGE_BATTLE_HP_PERCENT)) + { + gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] = TRUE; + gBattleScripting.battler = battler; + PushTraitStack(battler, ABILITY_SHIELDS_DOWN); + BattleScriptExecute(BattleScript_BattlerFormChangeEnd2); + effect++; break; - case ABILITY_HUNGER_SWITCH: - if (!gBattleMons[battler].volatiles.transformed - && GetActiveGimmick(battler) != GIMMICK_TERA - && TryBattleFormChange(battler, FORM_CHANGE_BATTLE_TURN_END)) - { - gBattleScripting.battler = battler; - BattleScriptExecute(BattleScript_BattlerFormChangeEnd3NoPopup); - effect++; - } + } + else if ((traitCheck = SearchTraits(battlerTraits, ABILITY_POWER_CONSTRUCT)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] + && TryBattleFormChange(battler, FORM_CHANGE_BATTLE_HP_PERCENT)) + { + gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] = TRUE; + gBattleScripting.battler = battler; + PushTraitStack(battler, ABILITY_POWER_CONSTRUCT); + BattleScriptExecute(BattleScript_PowerConstruct); + effect++; + break; + } + else if ((traitCheck = SearchTraits(battlerTraits, ABILITY_HUNGER_SWITCH)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] + && !gBattleMons[battler].volatiles.transformed + && GetActiveGimmick(battler) != GIMMICK_TERA + && TryBattleFormChange(battler, FORM_CHANGE_BATTLE_TURN_END)) + { + gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] = TRUE; + gBattleScripting.battler = battler; + PushTraitStack(battler, ABILITY_HUNGER_SWITCH); + BattleScriptExecute(BattleScript_BattlerFormChangeEnd3NoPopup); + effect++; break; - case ABILITY_CUD_CHEW: + } + // Cud Chew moved to the bottom because it has additional IF conditions so it could block the Else on abilities coming after (Multi) + else if ((traitCheck = SearchTraits(battlerTraits, ABILITY_CUD_CHEW)) && !gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1]) + { if (gDisableStructs[battler].cudChew == TRUE) { + gSpecialStatuses[battler].endTurnTraitDone[traitCheck - 1] = TRUE; gBattleScripting.battler = battler; gDisableStructs[battler].cudChew = FALSE; gLastUsedItem = GetBattlerPartyState(battler)->usedHeldItem; GetBattlerPartyState(battler)->usedHeldItem = ITEM_NONE; + PushTraitStack(battler, ABILITY_CUD_CHEW); BattleScriptExecute(BattleScript_CudChewActivates); effect++; + break; } - else if (!gDisableStructs[battler].cudChew && GetItemPocket(GetBattlerPartyState(battler)->usedHeldItem) == POCKET_BERRIES) + else if (!gDisableStructs[battler].cudChew && GetItemPocket(GetBattlerPartyState(battler)->usedHeldItem) == POCKET_BERRIES) { gDisableStructs[battler].cudChew = TRUE; } - break; - default: - break; - } + } } break; case ABILITYEFFECT_COLOR_CHANGE: - switch (gLastUsedAbility) + if (SearchTraits(battlerTraits, ABILITY_BERSERK) + && IsBattlerTurnDamaged(battler) + && IsBattlerAlive(battler) + && HadMoreThanHalfHpNowDoesnt(battler) + && CompareStat(battler, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + { + gEffectBattler = battler; + PushTraitStack(battler, ABILITY_BERSERK); + BattleScriptCall(BattleScript_TargetAbilityStatRaiseRetBerserk); + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_ANGER_SHELL) + && IsBattlerTurnDamaged(battler) + && IsBattlerAlive(battler) + && HadMoreThanHalfHpNowDoesnt(battler)) { - case ABILITY_COLOR_CHANGE: - if (IsBattlerTurnDamaged(battler) - && IsBattlerAlive(battler) - && !IS_BATTLER_OF_TYPE(battler, moveType) - && move != MOVE_STRUGGLE - && moveType != TYPE_STELLAR - && moveType != TYPE_MYSTERY) - { - gEffectBattler = gBattlerAbility = battler; - SET_BATTLER_TYPE(battler, moveType); - PREPARE_TYPE_BUFFER(gBattleTextBuff1, moveType); - BattleScriptCall(BattleScript_ColorChangeActivates); - effect++; - } - break; - case ABILITY_BERSERK: - if (IsBattlerTurnDamaged(battler) - && IsBattlerAlive(battler) - && HadMoreThanHalfHpNowDoesnt(battler) - && CompareStat(battler, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) - { - gEffectBattler = gBattlerAbility = battler; - SET_STATCHANGER(STAT_SPATK, 1, FALSE); - BattleScriptCall(BattleScript_BerserkActivates); - effect++; - } - break; - case ABILITY_ANGER_SHELL: - if (IsBattlerTurnDamaged(battler) - && IsBattlerAlive(battler) - && HadMoreThanHalfHpNowDoesnt(battler)) - { - gEffectBattler = gBattlerAbility = battler; - BattleScriptCall(BattleScript_AngerShellActivates); - effect++; - } - break; - default: - break; + PushTraitStack(gBattlerTarget, ABILITY_ANGER_SHELL); + BattleScriptCall(BattleScript_AngerShellActivates); + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_COLOR_CHANGE) // Last so the other abilities don't overwrite the gBattleTextBuff1 + && IsBattlerTurnDamaged(battler) + && IsBattlerAlive(battler) + && !IS_BATTLER_OF_TYPE(battler, moveType) + && move != MOVE_STRUGGLE + && moveType != TYPE_STELLAR + && moveType != TYPE_MYSTERY) + { + gEffectBattler = gBattlerAbility = battler; + SET_BATTLER_TYPE(battler, moveType); + PREPARE_TYPE_BUFFER(gBattleTextBuff1, moveType); + PushTraitStack(battler, ABILITY_COLOR_CHANGE); + BattleScriptCall(BattleScript_ColorChangeActivates); + effect++; } break; case ABILITYEFFECT_MOVE_END: // Think contact abilities. - switch (gLastUsedAbility) + if (SearchTraits(battlerTraits, ABILITY_JUSTIFIED) + && IsBattlerTurnDamaged(battler) + && IsBattlerAlive(battler) + && moveType == TYPE_DARK + && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + { + gEffectBattler = gBattlerAbility = battler; + PushTraitStack(battler, ABILITY_JUSTIFIED); + BattleScriptCall(BattleScript_TargetAbilityStatRaiseRetJustified); + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_RATTLED) + && IsBattlerTurnDamaged(battler) + && IsBattlerAlive(battler) + && (moveType == TYPE_DARK || moveType == TYPE_BUG || moveType == TYPE_GHOST) + && CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN)) { - case ABILITY_JUSTIFIED: - if (IsBattlerTurnDamaged(battler) - && IsBattlerAlive(battler) - && moveType == TYPE_DARK - && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) - { - gEffectBattler = gBattlerAbility = battler; - SET_STATCHANGER(STAT_ATK, 1, FALSE); - BattleScriptCall(BattleScript_TargetAbilityStatRaiseRet); - effect++; - } - break; - case ABILITY_RATTLED: - if (IsBattlerTurnDamaged(battler) - && IsBattlerAlive(battler) - && (moveType == TYPE_DARK || moveType == TYPE_BUG || moveType == TYPE_GHOST) - && CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) - { - gEffectBattler = gBattlerAbility = battler; - SET_STATCHANGER(STAT_SPEED, 1, FALSE); - BattleScriptCall(BattleScript_TargetAbilityStatRaiseRet); - effect++; - } - break; - case ABILITY_WATER_COMPACTION: - if (IsBattlerTurnDamaged(battler) - && IsBattlerAlive(battler) - && moveType == TYPE_WATER - && CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) - { - gEffectBattler = gBattlerAbility = battler; - SET_STATCHANGER(STAT_DEF, 2, FALSE); - BattleScriptCall(BattleScript_TargetAbilityStatRaiseRet); - effect++; - } - break; - case ABILITY_STAMINA: - if (gBattlerAttacker != gBattlerTarget - && IsBattlerTurnDamaged(gBattlerTarget) - && IsBattlerAlive(battler) - && CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) - { - gEffectBattler = gBattlerAbility = battler; - SET_STATCHANGER(STAT_DEF, 1, FALSE); - BattleScriptCall(BattleScript_TargetAbilityStatRaiseRet); - effect++; - } - break; - case ABILITY_WEAK_ARMOR: - if (IsBattlerTurnDamaged(battler) - && IsBattlerAlive(battler) - && IsBattleMovePhysical(gCurrentMove) - && (CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility) // Don't activate if both Speed and Defense cannot be raised. - || CompareStat(battler, STAT_DEF, MIN_STAT_STAGE, CMP_GREATER_THAN, gLastUsedAbility))) - { - if (GetMoveEffect(gCurrentMove) == EFFECT_HIT_ESCAPE && CanBattlerSwitch(gBattlerAttacker)) - gProtectStructs[battler].disableEjectPack = TRUE; // Set flag for target - - BattleScriptCall(BattleScript_WeakArmorActivates); - effect++; - } - break; - case ABILITY_CURSED_BODY: - if (IsBattlerTurnDamaged(gBattlerTarget) - && gDisableStructs[gBattlerAttacker].disabledMove == MOVE_NONE - && IsBattlerAlive(gBattlerAttacker) - && !IsAbilityOnSide(gBattlerAttacker, ABILITY_AROMA_VEIL) - && gChosenMove != MOVE_STRUGGLE - && RandomPercentage(RNG_CURSED_BODY, 30)) + gEffectBattler = gBattlerAbility = battler; + PushTraitStack(battler, ABILITY_RATTLED); + BattleScriptCall(BattleScript_TargetAbilityStatRaiseRetRattled); + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_WATER_COMPACTION) + && IsBattlerTurnDamaged(battler) + && IsBattlerAlive(battler) + && moveType == TYPE_WATER + && CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN)) + { + gEffectBattler = gBattlerAbility = battler; + PushTraitStack(battler, ABILITY_WATER_COMPACTION); + BattleScriptCall(BattleScript_TargetAbilityStatRaiseRetWaterCompaction); + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_STAMINA) + && gBattlerAttacker != gBattlerTarget + && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerAlive(battler) + && CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN)) + { + gEffectBattler = gBattlerAbility = battler; + PushTraitStack(battler, ABILITY_STAMINA); + BattleScriptCall(BattleScript_TargetAbilityStatRaiseRetStamina); + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_WEAK_ARMOR) + && IsBattlerTurnDamaged(battler) + && IsBattlerAlive(battler) + && IsBattleMovePhysical(gCurrentMove) + && (CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN) // Don't activate if both Speed and Defense cannot be raised. + || CompareStat(battler, STAT_DEF, MIN_STAT_STAGE, CMP_GREATER_THAN))) + { + if (GetMoveEffect(gCurrentMove) == EFFECT_HIT_ESCAPE && CanBattlerSwitch(gBattlerAttacker)) + gProtectStructs[battler].disableEjectPack = TRUE; // Set flag for target + PushTraitStack(battler, ABILITY_WEAK_ARMOR); + BattleScriptCall(BattleScript_WeakArmorActivates); + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_CURSED_BODY) + && IsBattlerTurnDamaged(gBattlerTarget) + && gDisableStructs[gBattlerAttacker].disabledMove == MOVE_NONE + && IsBattlerAlive(gBattlerAttacker) + && !IsAbilityOnSide(gBattlerAttacker, ABILITY_AROMA_VEIL) + && gChosenMove != MOVE_STRUGGLE + && RandomPercentage(RNG_CURSED_BODY, 30)) + { + gDisableStructs[gBattlerAttacker].disabledMove = gChosenMove; + gDisableStructs[gBattlerAttacker].disableTimer = 4; + PREPARE_MOVE_BUFFER(gBattleTextBuff3, gChosenMove); + PushTraitStack(battler, ABILITY_CURSED_BODY); + BattleScriptCall(BattleScript_CursedBodyActivates); + effect++; + } + // Mummy and Lingering Aroma use the same battlescript since they can't both activate at the same time (Trait) + if (SearchTraits(battlerTraits, ABILITY_MUMMY) + && IsBattlerAlive(gBattlerAttacker) + && IsBattlerTurnDamaged(gBattlerTarget) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerHoldEffect(gBattlerAttacker), move) + && gDisableStructs[gBattlerAttacker].overwrittenAbility != GetBattlerAbility(gBattlerTarget) + && !BattlerHasTrait(gBattlerAttacker, ABILITY_MUMMY) + && !BattlerHasTrait(gBattlerAttacker, ABILITY_LINGERING_AROMA) + && !gAbilitiesInfo[gBattleMons[gBattlerAttacker].ability].cantBeSuppressed) + { + if (GetBattlerHoldEffectIgnoreAbility(gBattlerAttacker) == HOLD_EFFECT_ABILITY_SHIELD) { - gDisableStructs[gBattlerAttacker].disabledMove = gChosenMove; - gDisableStructs[gBattlerAttacker].disableTimer = 4; - PREPARE_MOVE_BUFFER(gBattleTextBuff1, gChosenMove); - BattleScriptCall(BattleScript_CursedBodyActivates); - effect++; + RecordItemEffectBattle(gBattlerAttacker, HOLD_EFFECT_ABILITY_SHIELD); } - break; - case ABILITY_LINGERING_AROMA: - case ABILITY_MUMMY: - if (IsBattlerAlive(gBattlerAttacker) - && IsBattlerTurnDamaged(gBattlerTarget) - && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move) - && gDisableStructs[gBattlerAttacker].overwrittenAbility != GetBattlerAbility(gBattlerTarget) - && gBattleMons[gBattlerAttacker].ability != ABILITY_MUMMY - && gBattleMons[gBattlerAttacker].ability != ABILITY_LINGERING_AROMA - && !gAbilitiesInfo[gBattleMons[gBattlerAttacker].ability].cantBeSuppressed) + else { - if (GetBattlerHoldEffectIgnoreAbility(gBattlerAttacker) == HOLD_EFFECT_ABILITY_SHIELD) - { - RecordItemEffectBattle(gBattlerAttacker, HOLD_EFFECT_ABILITY_SHIELD); - break; - } - RemoveAbilityFlags(gBattlerAttacker); gLastUsedAbility = gBattleMons[gBattlerAttacker].ability; - gBattleMons[gBattlerAttacker].ability = gDisableStructs[gBattlerAttacker].overwrittenAbility = gBattleMons[gBattlerTarget].ability; + gBattleMons[gBattlerAttacker].ability = gDisableStructs[gBattlerAttacker].overwrittenAbility = ABILITY_MUMMY; + PushTraitStack(gBattlerAttacker, gLastUsedAbility); + PushTraitStack(battler, ABILITY_MUMMY); BattleScriptCall(BattleScript_MummyActivates); effect++; - break; } - break; - case ABILITY_WANDERING_SPIRIT: - if (IsBattlerAlive(gBattlerAttacker) - && IsBattlerTurnDamaged(gBattlerTarget) - && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move) - && !(GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX) - && !gAbilitiesInfo[gBattleMons[gBattlerAttacker].ability].cantBeSwapped) + } + else if (SearchTraits(battlerTraits, ABILITY_LINGERING_AROMA) + && IsBattlerAlive(gBattlerAttacker) + && IsBattlerTurnDamaged(gBattlerTarget) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerHoldEffect(gBattlerAttacker), move) + && gDisableStructs[gBattlerAttacker].overwrittenAbility != GetBattlerAbility(gBattlerTarget) + && !BattlerHasTrait(gBattlerAttacker, ABILITY_MUMMY) + && !BattlerHasTrait(gBattlerAttacker, ABILITY_LINGERING_AROMA) + && !gAbilitiesInfo[gBattleMons[gBattlerAttacker].ability].cantBeSuppressed) + { + if (GetBattlerHoldEffectIgnoreAbility(gBattlerAttacker) == HOLD_EFFECT_ABILITY_SHIELD) { - if (GetBattlerHoldEffectIgnoreAbility(gBattlerAttacker) == HOLD_EFFECT_ABILITY_SHIELD) - { - RecordItemEffectBattle(gBattlerAttacker, HOLD_EFFECT_ABILITY_SHIELD); - break; - } - if (GetBattlerHoldEffectIgnoreAbility(gBattlerTarget) == HOLD_EFFECT_ABILITY_SHIELD) - { - RecordItemEffectBattle(gBattlerTarget, HOLD_EFFECT_ABILITY_SHIELD); - break; - } - - RemoveAbilityFlags(gBattlerAttacker); - gLastUsedAbility = gBattleMons[gBattlerAttacker].ability; - gBattleMons[gBattlerAttacker].ability = gDisableStructs[gBattlerAttacker].overwrittenAbility = gBattleMons[gBattlerTarget].ability; - gBattleMons[gBattlerTarget].ability = gDisableStructs[gBattlerTarget].overwrittenAbility = gLastUsedAbility; - BattleScriptCall(BattleScript_WanderingSpiritActivates); - effect++; + RecordItemEffectBattle(gBattlerAttacker, HOLD_EFFECT_ABILITY_SHIELD); break; } + RemoveAbilityFlags(gBattlerAttacker); + gLastUsedAbility = gBattleMons[gBattlerAttacker].ability; + gBattleMons[gBattlerAttacker].ability = gDisableStructs[gBattlerAttacker].overwrittenAbility = ABILITY_LINGERING_AROMA; + PushTraitStack(gBattlerAttacker, gLastUsedAbility); + PushTraitStack(battler, ABILITY_LINGERING_AROMA); + BattleScriptCall(BattleScript_MummyActivates); + effect++; break; - case ABILITY_ANGER_POINT: - if (gSpecialStatuses[battler].criticalHit - && IsBattlerTurnDamaged(battler) - && IsBattlerAlive(battler) - && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) + } + if (SearchTraits(battlerTraits, ABILITY_WANDERING_SPIRIT) + && IsBattlerAlive(gBattlerAttacker) + && IsBattlerTurnDamaged(gBattlerTarget) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerHoldEffect(gBattlerAttacker), move) + && !(GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX) + && !gAbilitiesInfo[gBattleMons[gBattlerAttacker].ability].cantBeSwapped) + { + if (SearchTraits(battlerTraits, ABILITY_WANDERING_SPIRIT) > 1) { - SET_STATCHANGER(STAT_ATK, MAX_STAT_STAGE - gBattleMons[battler].statStages[STAT_ATK], FALSE); - BattleScriptCall(BattleScript_TargetsStatWasMaxedOut); - effect++; + // Wandering Spirit replaces your main Ability, so it generally should not be an Innate. + DebugPrintf("Wandering Spirit not set as main Ability"); } - break; - case ABILITY_GOOEY: - case ABILITY_TANGLING_HAIR: - if (IsBattlerAlive(gBattlerAttacker) - && (CompareStat(gBattlerAttacker, STAT_SPEED, MIN_STAT_STAGE, CMP_GREATER_THAN, gLastUsedAbility) || GetBattlerAbility(gBattlerAttacker) == ABILITY_MIRROR_ARMOR) - && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && IsBattlerTurnDamaged(gBattlerTarget) - && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move)) + if (GetBattlerHoldEffectIgnoreAbility(gBattlerAttacker) == HOLD_EFFECT_ABILITY_SHIELD) { - SET_STATCHANGER(STAT_SPEED, 1, TRUE); - PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); - BattleScriptCall(BattleScript_GooeyActivates); - effect++; + RecordItemEffectBattle(gBattlerAttacker, HOLD_EFFECT_ABILITY_SHIELD); } - break; - case ABILITY_ROUGH_SKIN: - case ABILITY_IRON_BARBS: - if (IsBattlerAlive(gBattlerAttacker) - && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && IsBattlerTurnDamaged(gBattlerTarget) - && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move)) + if (GetBattlerHoldEffectIgnoreAbility(gBattlerTarget) == HOLD_EFFECT_ABILITY_SHIELD) { - SetPassiveDamageAmount(gBattlerAttacker, GetNonDynamaxMaxHP(gBattlerAttacker) / (B_ROUGH_SKIN_DMG >= GEN_4 ? 8 : 16)); - PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); - BattleScriptCall(BattleScript_RoughSkinActivates); - effect++; + RecordItemEffectBattle(gBattlerTarget, HOLD_EFFECT_ABILITY_SHIELD); } - break; - case ABILITY_AFTERMATH: - if (!(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) - && !IsBattlerAlive(gBattlerTarget) - && IsBattlerAlive(gBattlerAttacker) - && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move)) - { - if ((battler = IsAbilityOnField(ABILITY_DAMP))) - { - gBattleScripting.battler = battler - 1; - BattleScriptCall(BattleScript_DampPreventsAftermath); - } - else - { - gBattleScripting.battler = gBattlerTarget; - SetPassiveDamageAmount(gBattlerAttacker, GetNonDynamaxMaxHP(gBattlerAttacker) / 4); - BattleScriptCall(BattleScript_AftermathDmg); - } + + RemoveAbilityFlags(gBattlerAttacker); + gLastUsedAbility = gBattleMons[gBattlerAttacker].ability; + gBattleMons[gBattlerAttacker].ability = gDisableStructs[gBattlerAttacker].overwrittenAbility = gBattleMons[gBattlerTarget].ability; + gBattleMons[gBattlerTarget].ability = gDisableStructs[gBattlerTarget].overwrittenAbility = gLastUsedAbility; + BattleScriptCall(BattleScript_WanderingSpiritActivates); + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_ANGER_POINT) + && gSpecialStatuses[battler].criticalHit + && IsBattlerTurnDamaged(battler) + && IsBattlerAlive(battler) + && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + { + SET_STATCHANGER(STAT_ATK, MAX_STAT_STAGE - gBattleMons[battler].statStages[STAT_ATK], FALSE); + PushTraitStack(battler, ABILITY_ANGER_POINT); + BattleScriptCall(BattleScript_TargetsStatWasMaxedOut); + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_GOOEY) + && IsBattlerAlive(gBattlerAttacker) + && (CompareStat(gBattlerAttacker, STAT_SPEED, MIN_STAT_STAGE, CMP_GREATER_THAN) || BattlerHasTrait(gBattlerAttacker, ABILITY_MIRROR_ARMOR)) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && IsBattlerTurnDamaged(gBattlerTarget) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerHoldEffect(gBattlerAttacker), move)) + { + gLastUsedAbility = ABILITY_GOOEY; + SET_STATCHANGER(STAT_SPEED, 1, TRUE); + PushTraitStack(battler, ABILITY_GOOEY); + BattleScriptCall(BattleScript_GooeyActivates); + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_TANGLING_HAIR) + && IsBattlerAlive(gBattlerAttacker) + && (CompareStat(gBattlerAttacker, STAT_SPEED, MIN_STAT_STAGE, CMP_GREATER_THAN) || BattlerHasTrait(gBattlerAttacker, ABILITY_MIRROR_ARMOR)) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && IsBattlerTurnDamaged(gBattlerTarget) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerHoldEffect(gBattlerAttacker), move)) + { + gLastUsedAbility = ABILITY_TANGLING_HAIR; + SET_STATCHANGER(STAT_SPEED, 1, TRUE); + PushTraitStack(battler, ABILITY_TANGLING_HAIR); + BattleScriptCall(BattleScript_TanglingHairActivates); + effect++; + } + s32 returnDamage = 0; + if (SearchTraits(battlerTraits, ABILITY_INNARDS_OUT) + && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) + && !IsBattlerAlive(gBattlerTarget) + && IsBattlerAlive(gBattlerAttacker)) + { + // Prevent Innards Out effect if Future Sight user is currently not on field + if (!IsFutureSightAttackerInParty(gBattlerAttacker, gBattlerTarget, gCurrentMove)) + { + returnDamage += (gBattleStruct->moveDamage[gBattlerTarget]); + PushTraitStack(battler, ABILITY_INNARDS_OUT); + BattleScriptCall(BattleScript_InnardsOutDmg); effect++; } - break; - case ABILITY_INNARDS_OUT: - if (!(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) - && !IsBattlerAlive(gBattlerTarget) - && IsBattlerAlive(gBattlerAttacker)) + } + if (SearchTraits(battlerTraits, ABILITY_AFTERMATH) + && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) + && !IsBattlerAlive(gBattlerTarget) + && IsBattlerAlive(gBattlerAttacker) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerHoldEffect(gBattlerAttacker), move)) + { + u32 dampBattler; + if ((dampBattler = IsAbilityOnField(ABILITY_DAMP))) + { + gBattleScripting.battler = dampBattler = dampBattler - 1; + PushTraitStack(dampBattler, ABILITY_DAMP); + PushTraitStack(battler, ABILITY_AFTERMATH); + BattleScriptCall(BattleScript_DampPreventsAftermath); + } + else { - // Prevent Innards Out effect if Future Sight user is currently not on field - if (IsFutureSightAttackerInParty(gBattlerAttacker, gBattlerTarget, gCurrentMove)) - break; - - gBattleScripting.battler = gBattlerTarget; - SetPassiveDamageAmount(gBattlerAttacker, gBattleStruct->moveDamage[gBattlerTarget]); + returnDamage += (GetNonDynamaxMaxHP(gBattlerAttacker) / 4); + PushTraitStack(battler, ABILITY_AFTERMATH); BattleScriptCall(BattleScript_AftermathDmg); - effect++; } - break; - case ABILITY_EFFECT_SPORE: + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_IRON_BARBS) + && IsBattlerAlive(gBattlerAttacker) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && IsBattlerTurnDamaged(gBattlerTarget) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerHoldEffect(gBattlerAttacker), move)) + { + returnDamage += (GetNonDynamaxMaxHP(gBattlerAttacker) / (B_ROUGH_SKIN_DMG >= GEN_4 ? 8 : 16)); + gLastUsedAbility = ABILITY_IRON_BARBS; + PushTraitStack(battler, ABILITY_IRON_BARBS); + BattleScriptCall(BattleScript_IronBarbsActivates); + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_ROUGH_SKIN) + && IsBattlerAlive(gBattlerAttacker) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && IsBattlerTurnDamaged(gBattlerTarget) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerHoldEffect(gBattlerAttacker), move)) + { + returnDamage += (GetNonDynamaxMaxHP(gBattlerAttacker) / (B_ROUGH_SKIN_DMG >= GEN_4 ? 8 : 16)); + gLastUsedAbility = ABILITY_ROUGH_SKIN; + PushTraitStack(battler, ABILITY_ROUGH_SKIN); + BattleScriptCall(BattleScript_RoughSkinActivates); + effect++; + } + if (returnDamage > 0) + { + SetPassiveDamageAmount(gBattlerAttacker, returnDamage); // Set combined passive damage of damage returning abilities + } + + // Only one non-volitile ailment can trigger at a time, + // so each ability is checked for viability then one ailment is activated from tht list. + // Ailment priority is: Sleep > Paralysis > Burn > Poison + bool32 trySleep = FALSE; + bool32 tryParalysis = FALSE; + bool32 tryBurn = FALSE; + bool32 tryPoison = FALSE; + enum Ability sleepAbility = ABILITY_NONE; + enum Ability paralysisAbility = ABILITY_NONE; + enum Ability burnAbility = ABILITY_NONE; + enum Ability poisonAbility = ABILITY_NONE; + + if (SearchTraits(battlerTraits, ABILITY_EFFECT_SPORE)) { - enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker); enum HoldEffect holdEffectAtk = GetBattlerHoldEffect(gBattlerAttacker); - if (IsAffectedByPowderMove(gBattlerAttacker, abilityAtk, holdEffectAtk)) + if (IsAffectedByPowderMove(gBattlerAttacker, holdEffectAtk)) { u32 poison, paralysis, sleep; @@ -5154,380 +5344,389 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab } sleep = 30; + gLastUsedAbility= ABILITY_EFFECT_SPORE; + i = RandomUniform(RNG_EFFECT_SPORE, 0, GetConfig(CONFIG_ABILITY_TRIGGER_CHANCE) >= GEN_4 ? 99 : 299); if (i < poison) - goto POISON_POINT; - if (i < paralysis) - goto STATIC; - // Sleep - if (i < sleep - && IsBattlerAlive(gBattlerAttacker) - && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && IsBattlerTurnDamaged(gBattlerTarget) - && CanBeSlept(gBattlerTarget, gBattlerAttacker, abilityAtk, NOT_BLOCKED_BY_SLEEP_CLAUSE) - && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, abilityAtk, holdEffectAtk, move)) { - if (IsSleepClauseEnabled()) - gBattleStruct->battlerState[gBattlerAttacker].sleepClauseEffectExempt = TRUE; - gEffectBattler = gBattlerAttacker; - gBattleScripting.battler = gBattlerTarget; - gBattleScripting.moveEffect = MOVE_EFFECT_SLEEP; - PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); - BattleScriptCall(BattleScript_AbilityStatusEffect); - effect++; + poisonAbility = ABILITY_EFFECT_SPORE; + tryPoison = TRUE; } - } - } - break; - case ABILITY_POISON_POINT: - if (GetConfig(CONFIG_ABILITY_TRIGGER_CHANCE) >= GEN_4 ? RandomPercentage(RNG_POISON_POINT, 30) : RandomChance(RNG_POISON_POINT, 1, 3)) - { - POISON_POINT: - { - enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker); - if (IsBattlerAlive(gBattlerAttacker) - && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && IsBattlerTurnDamaged(gBattlerTarget) - && CanBePoisoned(gBattlerTarget, gBattlerAttacker, gLastUsedAbility, abilityAtk) - && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, abilityAtk, GetBattlerHoldEffect(gBattlerAttacker), move)) + else if (i < paralysis) { - gEffectBattler = gBattlerAttacker; - gBattleScripting.battler = gBattlerTarget; - gBattleScripting.moveEffect = MOVE_EFFECT_POISON; - PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); - BattleScriptCall(BattleScript_AbilityStatusEffect); - effect++; + paralysisAbility = ABILITY_EFFECT_SPORE; + tryParalysis = TRUE; } - } - } - break; - case ABILITY_STATIC: - if (GetConfig(CONFIG_ABILITY_TRIGGER_CHANCE) >= GEN_4 ? RandomPercentage(RNG_STATIC, 30) : RandomChance(RNG_STATIC, 1, 3)) - { - STATIC: - { - enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker); - if (IsBattlerAlive(gBattlerAttacker) - && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && IsBattlerTurnDamaged(gBattlerTarget) - && CanBeParalyzed(gBattlerTarget, gBattlerAttacker, abilityAtk) - && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, abilityAtk, GetBattlerHoldEffect(gBattlerAttacker), move)) + else if (i < sleep) { - gEffectBattler = gBattlerAttacker; - gBattleScripting.battler = gBattlerTarget; - gBattleScripting.moveEffect = MOVE_EFFECT_PARALYSIS; - PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); - BattleScriptCall(BattleScript_AbilityStatusEffect); - effect++; + sleepAbility = ABILITY_EFFECT_SPORE; + trySleep = TRUE; } } - } - break; - case ABILITY_FLAME_BODY: - if (IsBattlerAlive(gBattlerAttacker) - && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move) - && IsBattlerTurnDamaged(gBattlerTarget) - && CanBeBurned(gBattlerTarget, gBattlerAttacker, GetBattlerAbility(gBattlerAttacker)) - && (GetConfig(CONFIG_ABILITY_TRIGGER_CHANCE) >= GEN_4 ? RandomPercentage(RNG_FLAME_BODY, 30) : RandomChance(RNG_FLAME_BODY, 1, 3))) - { - gEffectBattler = gBattlerAttacker; - gBattleScripting.battler = gBattlerTarget; - gBattleScripting.moveEffect = MOVE_EFFECT_BURN; - PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); - BattleScriptCall(BattleScript_AbilityStatusEffect); - effect++; - } - break; - case ABILITY_CUTE_CHARM: - if (IsBattlerAlive(gBattlerAttacker) - && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && IsBattlerTurnDamaged(gBattlerTarget) - && IsBattlerAlive(gBattlerTarget) - && (GetConfig(CONFIG_ABILITY_TRIGGER_CHANCE) >= GEN_4 ? RandomPercentage(RNG_CUTE_CHARM, 30) : RandomChance(RNG_CUTE_CHARM, 1, 3)) - && !(gBattleMons[gBattlerAttacker].volatiles.infatuation) - && AreBattlersOfOppositeGender(gBattlerAttacker, gBattlerTarget) - && !IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_OBLIVIOUS) - && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move) - && !IsAbilityOnSide(gBattlerAttacker, ABILITY_AROMA_VEIL)) - { - gBattleMons[gBattlerAttacker].volatiles.infatuation = INFATUATED_WITH(gBattlerTarget); - BattleScriptCall(BattleScript_CuteCharmActivates); - effect++; - } - break; - case ABILITY_ILLUSION: - if (gBattleStruct->illusion[gBattlerTarget].state == ILLUSION_ON && IsBattlerTurnDamaged(gBattlerTarget)) + } + + if (SearchTraits(battlerTraits, ABILITY_STATIC) + && (GetConfig(CONFIG_ABILITY_TRIGGER_CHANCE) >= GEN_4 ? RandomPercentage(RNG_STATIC, 30) : RandomChance(RNG_STATIC, 1, 3))) + { + paralysisAbility = ABILITY_STATIC; + tryParalysis = TRUE; + } + if (SearchTraits(battlerTraits, ABILITY_FLAME_BODY) + && (GetConfig(CONFIG_ABILITY_TRIGGER_CHANCE) >= GEN_4 ? RandomPercentage(RNG_FLAME_BODY, 30) : RandomChance(RNG_FLAME_BODY, 1, 3))) + { + burnAbility = ABILITY_FLAME_BODY; + tryBurn = TRUE; + } + if (SearchTraits(battlerTraits, ABILITY_POISON_POINT) + && (GetConfig(CONFIG_ABILITY_TRIGGER_CHANCE) >= GEN_4 ? RandomPercentage(RNG_POISON_POINT, 30) : RandomChance(RNG_POISON_POINT, 1, 3))) + { + poisonAbility = ABILITY_POISON_POINT; + tryPoison = TRUE; + } + + // Ailment priority is: Sleep > Paralysis > Burn > Poison + if (trySleep + && IsBattlerAlive(gBattlerAttacker) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && IsBattlerTurnDamaged(gBattlerTarget) + && CanBeSlept(gBattlerTarget, gBattlerAttacker, NOT_BLOCKED_BY_SLEEP_CLAUSE) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerHoldEffect(gBattlerAttacker), move)) + { + if (IsSleepClauseEnabled()) + gBattleStruct->battlerState[gBattlerAttacker].sleepClauseEffectExempt = TRUE; + gEffectBattler = gBattlerAttacker; + gBattleScripting.battler = gBattlerTarget; + gBattleScripting.moveEffect = MOVE_EFFECT_SLEEP; + gLastUsedAbility = sleepAbility; + PushTraitStack(battler, gLastUsedAbility); + BattleScriptCall(BattleScript_AbilityStatusEffectDef); + effect++; + } + else if (tryParalysis + && IsBattlerAlive(gBattlerAttacker) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && IsBattlerTurnDamaged(gBattlerTarget) + && CanBeParalyzed(gBattlerTarget, gBattlerAttacker) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerHoldEffect(gBattlerAttacker), move)) + { + gEffectBattler = gBattlerAttacker; + gBattleScripting.battler = gBattlerTarget; + gBattleScripting.moveEffect = MOVE_EFFECT_PARALYSIS; + gLastUsedAbility = paralysisAbility; + PushTraitStack(battler, gLastUsedAbility); + BattleScriptCall(BattleScript_AbilityStatusEffectDef); + effect++; + } + else if (tryBurn + && IsBattlerAlive(gBattlerAttacker) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && IsBattlerTurnDamaged(gBattlerTarget) + && CanBeBurned(gBattlerTarget, gBattlerAttacker) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerHoldEffect(gBattlerAttacker), move)) + { + gEffectBattler = gBattlerAttacker; + gBattleScripting.battler = gBattlerTarget; + gBattleScripting.moveEffect = MOVE_EFFECT_BURN; + gLastUsedAbility = burnAbility; + PushTraitStack(battler, gLastUsedAbility); + BattleScriptCall(BattleScript_AbilityStatusEffectDef); + effect++; + } + else if (tryPoison + && IsBattlerAlive(gBattlerAttacker) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && IsBattlerTurnDamaged(gBattlerTarget) + && CanBePoisoned(gBattlerTarget, gBattlerAttacker) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerHoldEffect(gBattlerAttacker), move)) + { + gEffectBattler = gBattlerAttacker; + gBattleScripting.battler = gBattlerTarget; + gBattleScripting.moveEffect = MOVE_EFFECT_POISON; + gLastUsedAbility = poisonAbility; + PushTraitStack(battler, gLastUsedAbility); + BattleScriptCall(BattleScript_AbilityStatusEffectDef); + effect++; + } + + if (SearchTraits(battlerTraits, ABILITY_CUTE_CHARM) + && IsBattlerAlive(gBattlerAttacker) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerAlive(gBattlerTarget) + && (GetConfig(CONFIG_ABILITY_TRIGGER_CHANCE) >= GEN_4 ? RandomPercentage(RNG_CUTE_CHARM, 30) : RandomChance(RNG_CUTE_CHARM, 1, 3)) + && !(gBattleMons[gBattlerAttacker].volatiles.infatuation) + && AreBattlersOfOppositeGender(gBattlerAttacker, gBattlerTarget) + && !IsAbilityAndRecord(gBattlerAttacker, ABILITY_OBLIVIOUS) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerHoldEffect(gBattlerAttacker), move) + && !IsAbilityOnSide(gBattlerAttacker, ABILITY_AROMA_VEIL)) + { + gBattleMons[gBattlerAttacker].volatiles.infatuation = INFATUATED_WITH(gBattlerTarget); + PushTraitStack(battler, ABILITY_CUTE_CHARM); + BattleScriptCall(BattleScript_CuteCharmActivates); + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_ILLUSION) + && gBattleStruct->illusion[gBattlerTarget].state == ILLUSION_ON && IsBattlerTurnDamaged(gBattlerTarget)) + { + gBattleScripting.battler = gBattlerTarget; + BattleScriptCall(BattleScript_IllusionOff); + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_COTTON_DOWN) + && IsBattlerAlive(gBattlerAttacker) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && IsBattlerTurnDamaged(gBattlerTarget)) + { + gEffectBattler = gBattlerTarget; + PushTraitStack(battler, ABILITY_COTTON_DOWN); + BattleScriptCall(BattleScript_CottonDownActivates); + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_STEAM_ENGINE) + && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerAlive(battler) + && CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN) + && (moveType == TYPE_FIRE || moveType == TYPE_WATER)) + { + gEffectBattler = gBattlerAbility = battler; + PushTraitStack(battler, ABILITY_STEAM_ENGINE); + BattleScriptCall(BattleScript_TargetAbilityStatRaiseRetSteam); + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_SAND_SPIT) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && IsBattlerTurnDamaged(gBattlerTarget) + && !(gBattleWeather & B_WEATHER_SANDSTORM && HasWeatherEffect())) + { + if (gBattleWeather & B_WEATHER_PRIMAL_ANY && HasWeatherEffect()) { - gBattleScripting.battler = gBattlerTarget; - BattleScriptCall(BattleScript_IllusionOff); + PushTraitStack(battler, ABILITY_SAND_SPIT); + BattleScriptCall(BattleScript_BlockedByPrimalWeatherRet); effect++; } - break; - case ABILITY_COTTON_DOWN: - if (IsBattlerAlive(gBattlerAttacker) - && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && IsBattlerTurnDamaged(gBattlerTarget)) + else if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SANDSTORM, TRUE)) { - gEffectBattler = gBattlerTarget; - BattleScriptCall(BattleScript_CottonDownActivates); + PushTraitStack(battler, ABILITY_SAND_SPIT); + gBattleScripting.battler = battler; + BattleScriptCall(BattleScript_SandSpitActivates); effect++; } - break; - case ABILITY_STEAM_ENGINE: - if (IsBattlerTurnDamaged(gBattlerTarget) - && IsBattlerAlive(battler) - && CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility) - && (moveType == TYPE_FIRE || moveType == TYPE_WATER)) + } + if (SearchTraits(battlerTraits, ABILITY_PERISH_BODY) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerAlive(battler) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerHoldEffect(gBattlerAttacker), move) + && !gBattleMons[gBattlerAttacker].volatiles.perishSong) + { + if (!gBattleMons[battler].volatiles.perishSong) { - gEffectBattler = gBattlerAbility = battler; - SET_STATCHANGER(STAT_SPEED, 6, FALSE); - BattleScriptCall(BattleScript_TargetAbilityStatRaiseRet); - effect++; + gBattleMons[battler].volatiles.perishSong = TRUE; + gDisableStructs[battler].perishSongTimer = 3; } - break; - case ABILITY_SAND_SPIT: - if (!gProtectStructs[gBattlerAttacker].confusionSelfDmg - && IsBattlerTurnDamaged(gBattlerTarget) - && !(gBattleWeather & B_WEATHER_SANDSTORM && HasWeatherEffect())) + gBattleMons[gBattlerAttacker].volatiles.perishSong = TRUE; + gDisableStructs[gBattlerAttacker].perishSongTimer = 3; + PushTraitStack(battler, ABILITY_PERISH_BODY); + BattleScriptCall(BattleScript_PerishBodyActivates); + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_GULP_MISSILE) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerAlive(gBattlerAttacker) + && gBattleMons[gBattlerTarget].species != SPECIES_CRAMORANT) + { + PushTraitStack(battler, ABILITY_GULP_MISSILE); + if (!IsAbilityAndRecord(gBattlerAttacker, ABILITY_MAGIC_GUARD)) + SetPassiveDamageAmount(gBattlerAttacker, GetNonDynamaxMaxHP(gBattlerAttacker) / 4); + + switch (gBattleMons[gBattlerTarget].species) { - if (gBattleWeather & B_WEATHER_PRIMAL_ANY && HasWeatherEffect()) - { - BattleScriptCall(BattleScript_BlockedByPrimalWeatherRet); + case SPECIES_CRAMORANT_GORGING: + TryBattleFormChange(battler, FORM_CHANGE_HIT_BY_MOVE); + BattleScriptCall(BattleScript_GulpMissileGorging); effect++; - } - else if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SANDSTORM, gLastUsedAbility)) - { - gBattleScripting.battler = battler; - BattleScriptCall(BattleScript_SandSpitActivates); + break; + case SPECIES_CRAMORANT_GULPING: + TryBattleFormChange(battler, FORM_CHANGE_HIT_BY_MOVE); + BattleScriptCall(BattleScript_GulpMissileGulping); effect++; - } - } - break; - case ABILITY_PERISH_BODY: - if (!gProtectStructs[gBattlerAttacker].confusionSelfDmg - && IsBattlerTurnDamaged(gBattlerTarget) - && IsBattlerAlive(battler) - && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move) - && !gBattleMons[gBattlerAttacker].volatiles.perishSong) - { - if (!gBattleMons[battler].volatiles.perishSong) - { - gBattleMons[battler].volatiles.perishSong = TRUE; - gDisableStructs[battler].perishSongTimer = 3; - } - gBattleMons[gBattlerAttacker].volatiles.perishSong = TRUE; - gDisableStructs[gBattlerAttacker].perishSongTimer = 3; - BattleScriptCall(BattleScript_PerishBodyActivates); - effect++; + break; } - break; - case ABILITY_GULP_MISSILE: - if (!gProtectStructs[gBattlerAttacker].confusionSelfDmg - && IsBattlerTurnDamaged(gBattlerTarget) - && IsBattlerAlive(gBattlerAttacker) - && gBattleMons[gBattlerTarget].species != SPECIES_CRAMORANT) - { - if (!IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_MAGIC_GUARD)) - SetPassiveDamageAmount(gBattlerAttacker, GetNonDynamaxMaxHP(gBattlerAttacker) / 4); + } + if (SearchTraits(battlerTraits, ABILITY_SEED_SOWER) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerAlive(gBattlerTarget) + && TryChangeBattleTerrain(gBattlerTarget, STATUS_FIELD_GRASSY_TERRAIN)) + { + PushTraitStack(gBattlerTarget, ABILITY_SEED_SOWER); + BattleScriptCall(BattleScript_SeedSowerActivates); + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_THERMAL_EXCHANGE) + && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerAlive(gBattlerTarget) + && CompareStat(gBattlerTarget, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN) + && moveType == TYPE_FIRE) + { + gEffectBattler = gBattlerAbility = gBattlerTarget; + PushTraitStack(gBattlerTarget, ABILITY_THERMAL_EXCHANGE); + BattleScriptCall(BattleScript_TargetAbilityStatRaiseRetThermal); + effect++; + } + // Only one of Wind Power or Electromorphosis can activate at a time + if (SearchTraits(battlerTraits, ABILITY_WIND_POWER) + && IsWindMove(gCurrentMove) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerAlive(gBattlerTarget)) + { + PushTraitStack(battler, ABILITY_WIND_POWER); + BattleScriptCall(BattleScript_WindPowerActivates); + effect++; + } + else if (SearchTraits(battlerTraits, ABILITY_ELECTROMORPHOSIS) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerAlive(gBattlerTarget)) + { + PushTraitStack(battler, ABILITY_ELECTROMORPHOSIS); + BattleScriptCall(BattleScript_ElectromorphosisActivates); + effect++; + } - switch (gBattleMons[gBattlerTarget].species) - { - case SPECIES_CRAMORANT_GORGING: - TryBattleFormChange(battler, FORM_CHANGE_HIT_BY_MOVE); - BattleScriptCall(BattleScript_GulpMissileGorging); - effect++; - break; - case SPECIES_CRAMORANT_GULPING: - TryBattleFormChange(battler, FORM_CHANGE_HIT_BY_MOVE); - BattleScriptCall(BattleScript_GulpMissileGulping); - effect++; - break; - } - } - break; - case ABILITY_SEED_SOWER: - if (!gProtectStructs[gBattlerAttacker].confusionSelfDmg - && IsBattlerTurnDamaged(gBattlerTarget) - && IsBattlerAlive(gBattlerTarget) - && TryChangeBattleTerrain(gBattlerTarget, STATUS_FIELD_GRASSY_TERRAIN)) - { - BattleScriptCall(BattleScript_SeedSowerActivates); - effect++; - } - break; - case ABILITY_THERMAL_EXCHANGE: - if (IsBattlerTurnDamaged(gBattlerTarget) - && IsBattlerAlive(gBattlerTarget) - && CompareStat(gBattlerTarget, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility) - && moveType == TYPE_FIRE) - { - gEffectBattler = gBattlerAbility = gBattlerTarget; - SET_STATCHANGER(STAT_ATK, 1, FALSE); - BattleScriptCall(BattleScript_TargetAbilityStatRaiseRet); - effect++; - } - break; - case ABILITY_WIND_POWER: - if (!IsWindMove(gCurrentMove)) - break; - // fall through - case ABILITY_ELECTROMORPHOSIS: - if (!gProtectStructs[gBattlerAttacker].confusionSelfDmg - && IsBattlerTurnDamaged(gBattlerTarget) - && IsBattlerAlive(gBattlerTarget)) - { - BattleScriptCall(BattleScript_WindPowerActivates); - effect++; - } - break; - case ABILITY_TOXIC_DEBRIS: - if (!gBattleStruct->isSkyBattle - && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && IsBattleMovePhysical(gCurrentMove) - && IsBattlerTurnDamaged(gBattlerTarget) - && (gSideTimers[GetBattlerSide(gBattlerAttacker)].toxicSpikesAmount != 2)) - { - SaveBattlerTarget(gBattlerTarget); - SaveBattlerAttacker(gBattlerAttacker); - gBattlerAttacker = gBattlerTarget; - gBattlerTarget = BATTLE_OPPOSITE(gBattlerAttacker); - BattleScriptCall(BattleScript_ToxicDebrisActivates); - effect++; - } - break; - default: - break; + if (SearchTraits(battlerTraits, ABILITY_TOXIC_DEBRIS) + && !gBattleStruct->isSkyBattle + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && IsBattleMovePhysical(gCurrentMove) + && IsBattlerTurnDamaged(gBattlerTarget) + && (gSideTimers[GetBattlerSide(gBattlerAttacker)].toxicSpikesAmount != 2)) + { + SaveBattlerTarget(gBattlerTarget); + SaveBattlerAttacker(gBattlerAttacker); + gBattlerAttacker = gBattlerTarget; + gBattlerTarget = BATTLE_OPPOSITE(gBattlerAttacker); + PushTraitStack(battler, ABILITY_TOXIC_DEBRIS); + BattleScriptCall(BattleScript_ToxicDebrisActivates); + effect++; } - break; + break; case ABILITYEFFECT_MOVE_END_ATTACKER: // Same as above, but for attacker - switch (gLastUsedAbility) - { - case ABILITY_POISON_TOUCH: - if (IsBattlerAlive(gBattlerTarget) - && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && CanBePoisoned(gBattlerAttacker, gBattlerTarget, gLastUsedAbility, GetBattlerAbility(gBattlerTarget)) - && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move) - && IsBattlerTurnDamaged(gBattlerTarget) // Need to actually hit the target - && RandomPercentage(RNG_POISON_TOUCH, 30)) - { - gEffectBattler = gBattlerTarget; - gBattleScripting.battler = gBattlerAttacker; - gBattleScripting.moveEffect = MOVE_EFFECT_POISON; - PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); - BattleScriptCall(BattleScript_AbilityStatusEffect); - effect++; - } - break; - case ABILITY_TOXIC_CHAIN: - if (gBattleStruct->toxicChainPriority) - { - gBattleStruct->toxicChainPriority = FALSE; - gEffectBattler = gBattlerTarget; - gBattleScripting.battler = gBattlerAttacker; - gBattleScripting.moveEffect = MOVE_EFFECT_TOXIC; - PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); - BattleScriptCall(BattleScript_AbilityStatusEffect); - effect++; - } - break; - case ABILITY_STENCH: - if (IsBattlerAlive(gBattlerTarget) - && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && RandomChance(RNG_STENCH, 1, 10) - && IsBattlerTurnDamaged(gBattlerTarget) - && !MoveHasAdditionalEffect(gCurrentMove, MOVE_EFFECT_FLINCH)) - { - SetMoveEffect(gBattlerAttacker, gBattlerTarget, MOVE_EFFECT_FLINCH, gBattlescriptCurrInstr, EFFECT_PRIMARY); - effect++; - } - break; - case ABILITY_GULP_MISSILE: - if ((gBattleMons[gBattlerAttacker].species == SPECIES_CRAMORANT) - && ((gCurrentMove == MOVE_SURF && IsBattlerTurnDamaged(gBattlerTarget)) || gBattleMons[gBattlerAttacker].volatiles.semiInvulnerable == STATE_UNDERWATER) - && TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_HP_PERCENT)) - { - gBattleScripting.battler = gBattlerAttacker; - BattleScriptCall(BattleScript_BattlerFormChange); - effect++; - } - break; - case ABILITY_POISON_PUPPETEER: - if (gBattleMons[gBattlerAttacker].species == SPECIES_PECHARUNT - && gBattleStruct->poisonPuppeteerConfusion == TRUE - && CanBeConfused(gBattlerTarget)) - { - gBattleStruct->poisonPuppeteerConfusion = FALSE; - gBattleScripting.moveEffect = MOVE_EFFECT_CONFUSION; - PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); - BattleScriptCall(BattleScript_AbilityStatusEffect); - effect++; - } - break; - default: - break; + STORE_BATTLER_TRAITS(gBattlerAttacker); + + if (SearchTraits(battlerTraits, ABILITY_GULP_MISSILE) + && (gBattleMons[gBattlerAttacker].species == SPECIES_CRAMORANT) + && ((gCurrentMove == MOVE_SURF && IsBattlerTurnDamaged(gBattlerTarget)) || gBattleMons[gBattlerAttacker].volatiles.semiInvulnerable == STATE_UNDERWATER) + && TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_HP_PERCENT)) + { + gLastUsedAbility = ABILITY_GULP_MISSILE; + gBattleScripting.battler = gBattlerAttacker; + PushTraitStack(gBattlerAttacker, ABILITY_GULP_MISSILE); + BattleScriptCall(BattleScript_BattlerFormChange); + effect++; } - break; - case ABILITYEFFECT_MOVE_END_OTHER: // Abilities that activate on *another* battler's moveend: Dancer, Soul-Heart, Receiver, Symbiosis - switch (GetBattlerAbility(battler)) + else if (SearchTraits(battlerTraits, ABILITY_POISON_PUPPETEER) + && gBattleMons[gBattlerAttacker].species == SPECIES_PECHARUNT + && gBattleStruct->poisonPuppeteerConfusion == TRUE + && CanBeConfused(gBattlerTarget)) { - case ABILITY_DANCER: - if (IsBattlerAlive(battler) - && IsDanceMove(move) - && !gSpecialStatuses[battler].dancerUsedMove - && gBattlerAttacker != battler) - { - // Set bit and save Dancer mon's original target - gSpecialStatuses[battler].dancerUsedMove = TRUE; - gSpecialStatuses[battler].dancerOriginalTarget = gBattleStruct->moveTarget[battler] | 0x4; - gBattlerAttacker = gBattlerAbility = battler; - gCalledMove = move; + gBattleStruct->poisonPuppeteerConfusion = FALSE; + gBattleScripting.moveEffect = MOVE_EFFECT_CONFUSION; + gLastUsedAbility = ABILITY_POISON_PUPPETEER; + PushTraitStack(gBattlerAttacker, ABILITY_POISON_PUPPETEER); + BattleScriptCall(BattleScript_AbilityStatusEffectAtk); + effect++; + } + else if (SearchTraits(battlerTraits, ABILITY_TOXIC_CHAIN) + && gBattleStruct->toxicChainPriority) + { + gBattleStruct->toxicChainPriority = FALSE; + gEffectBattler = gBattlerTarget; + gBattleScripting.battler = gBattlerAttacker; + gBattleScripting.moveEffect = MOVE_EFFECT_TOXIC; + gLastUsedAbility = ABILITY_TOXIC_CHAIN; + PushTraitStack(gBattlerAttacker, ABILITY_TOXIC_CHAIN); + BattleScriptCall(BattleScript_AbilityStatusEffectAtk); + effect++; + } + else if (SearchTraits(battlerTraits, ABILITY_POISON_TOUCH) + && IsBattlerAlive(gBattlerTarget) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && CanBePoisoned(gBattlerAttacker, gBattlerTarget) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerHoldEffect(gBattlerAttacker), move) + && IsBattlerTurnDamaged(gBattlerTarget) // Need to actually hit the target + && RandomPercentage(RNG_POISON_TOUCH, 30)) + { + gEffectBattler = gBattlerTarget; + gBattleScripting.battler = gBattlerAttacker; + gBattleScripting.moveEffect = MOVE_EFFECT_POISON; + gLastUsedAbility = ABILITY_POISON_TOUCH; + PushTraitStack(gBattlerAttacker, ABILITY_POISON_TOUCH); + BattleScriptCall(BattleScript_AbilityStatusEffectAtk); + effect++; + } + else if (SearchTraits(battlerTraits, ABILITY_STENCH) + && IsBattlerAlive(gBattlerTarget) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && RandomChance(RNG_STENCH, 1, 10) + && IsBattlerTurnDamaged(gBattlerTarget) + && !MoveHasAdditionalEffect(gCurrentMove, MOVE_EFFECT_FLINCH)) + { + SetMoveEffect(gBattlerAttacker, gBattlerTarget, MOVE_EFFECT_FLINCH, gBattlescriptCurrInstr, EFFECT_PRIMARY); + effect++; + } + break; + case ABILITYEFFECT_MOVE_END_OTHER: // Abilities that activate on *another* battler's moveend: Dancer, Soul-Heart, Receiver, Symbiosis + if (SearchTraits(battlerTraits, ABILITY_DANCER) + && IsBattlerAlive(battler) + && IsDanceMove(move) + && !gSpecialStatuses[battler].dancerUsedMove + && gBattlerAttacker != battler) + { + // Set bit and save Dancer mon's original target + gSpecialStatuses[battler].dancerUsedMove = TRUE; + gSpecialStatuses[battler].dancerOriginalTarget = *(gBattleStruct->moveTarget + battler) | 0x4; + gBattlerAttacker = gBattlerAbility = battler; + gCalledMove = move; // Set the target to the original target of the mon that first used a Dance move gBattlerTarget = gBattleScripting.savedBattler & 0x3; - // Make sure that the target isn't an ally - if it is, target the original user - if (IsBattlerAlly(gBattlerTarget, gBattlerAttacker)) - gBattlerTarget = (gBattleScripting.savedBattler & 0xF0) >> 4; - BattleScriptExecute(BattleScript_DancerActivates); - effect++; - } - break; - default: - break; + // Make sure that the target isn't an ally - if it is, target the original user + if (IsBattlerAlly(gBattlerTarget, gBattlerAttacker)) + gBattlerTarget = (gBattleScripting.savedBattler & 0xF0) >> 4; + PushTraitStack(battler, ABILITY_DANCER); + BattleScriptExecute(BattleScript_DancerActivates); + effect++; } break; case ABILITYEFFECT_OPPORTUNIST: case ABILITYEFFECT_OPPORTUNIST_FIRST_TURN: - switch (ability) - { - case ABILITY_OPPORTUNIST: - if (gProtectStructs[battler].activateOpportunist == 2) + if (BattlerHasTrait(battler, ABILITY_OPPORTUNIST) + && gProtectStructs[battler].activateOpportunist == 2) { gBattleScripting.battler = battler; gProtectStructs[battler].activateOpportunist--; ChooseStatBoostAnimation(battler); + PushTraitStack(battler, ABILITY_OPPORTUNIST); if (caseID == ABILITYEFFECT_OPPORTUNIST_FIRST_TURN) BattleScriptPushCursorAndCallback(BattleScript_OpportunistCopyStatChangeEnd3); else BattleScriptCall(BattleScript_OpportunistCopyStatChange); effect = 1; } - break; - default: - break; - } break; - case ABILITYEFFECT_IMMUNITY: + case ABILITYEFFECT_IMMUNITY: effect = TryImmunityAbilityHealStatus(battler, caseID); if (effect) - return effect; + return effect; break; case ABILITYEFFECT_ON_SWITCHIN_IMMUNITIES: effect = TryImmunityAbilityHealStatus(battler, caseID); break; case ABILITYEFFECT_SYNCHRONIZE: - if (gLastUsedAbility == ABILITY_SYNCHRONIZE && gBattleStruct->synchronizeMoveEffect != MOVE_EFFECT_NONE) + if (SearchTraits(battlerTraits, ABILITY_SYNCHRONIZE) && gBattleStruct->synchronizeMoveEffect != MOVE_EFFECT_NONE) { gBattleScripting.battler = gBattlerAbility = gBattlerTarget; RecordAbilityBattle(gBattlerTarget, ABILITY_SYNCHRONIZE); @@ -5538,26 +5737,27 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab if (CanSetNonVolatileStatus( gBattlerTarget, gBattlerAttacker, - gLastUsedAbility, - GetBattlerAbility(gBattlerAttacker), + gBattleStruct->synchronizeMoveEffect, CHECK_TRIGGER)) { gEffectBattler = gBattlerAttacker; gBattleScripting.moveEffect = gBattleStruct->synchronizeMoveEffect; PREPARE_ABILITY_BUFFER(gBattleTextBuff1, ABILITY_SYNCHRONIZE); + PushTraitStack(battler, ABILITY_SYNCHRONIZE); BattleScriptCall(BattleScript_SynchronizeActivates); effect++; } else // Synchronize ability pop up still shows up even if status fails { + PushTraitStack(battler, ABILITY_SYNCHRONIZE); BattleScriptCall(BattleScript_AbilityPopUp); } gBattleStruct->synchronizeMoveEffect = MOVE_EFFECT_NONE; } break; case ABILITYEFFECT_ATK_SYNCHRONIZE: - if (gLastUsedAbility == ABILITY_SYNCHRONIZE && gBattleStruct->synchronizeMoveEffect != MOVE_EFFECT_NONE) + if (SearchTraits(battlerTraits, ABILITY_SYNCHRONIZE) && gBattleStruct->synchronizeMoveEffect != MOVE_EFFECT_NONE) { gBattleScripting.battler = gBattlerAbility = gBattlerAttacker; RecordAbilityBattle(gBattlerAttacker, ABILITY_SYNCHRONIZE); @@ -5568,8 +5768,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab if (CanSetNonVolatileStatus( gBattlerAttacker, gBattlerTarget, - gLastUsedAbility, - GetBattlerAbility(gBattlerAttacker), + gBattleStruct->synchronizeMoveEffect, CHECK_TRIGGER)) { @@ -5579,11 +5778,13 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab gEffectBattler = gBattlerTarget; gBattleScripting.moveEffect = gBattleStruct->synchronizeMoveEffect; PREPARE_ABILITY_BUFFER(gBattleTextBuff1, ABILITY_SYNCHRONIZE); + PushTraitStack(battler, ABILITY_SYNCHRONIZE); BattleScriptCall(BattleScript_SynchronizeActivates); effect++; } else // Synchronize ability pop up still shows up even if status fails { + PushTraitStack(battler, ABILITY_SYNCHRONIZE); BattleScriptCall(BattleScript_AbilityPopUp); } gBattleStruct->synchronizeMoveEffect = MOVE_EFFECT_NONE; @@ -5593,97 +5794,146 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab case ABILITYEFFECT_NEUTRALIZINGGAS: case ABILITYEFFECT_NEUTRALIZINGGAS_FIRST_TURN: // Prints message only. separate from ABILITYEFFECT_ON_SWITCHIN bc activates before entry hazards - if (gLastUsedAbility == ABILITY_NEUTRALIZING_GAS && !gDisableStructs[battler].neutralizingGas) + if (SearchTraits(battlerTraits, ABILITY_NEUTRALIZING_GAS) && !gDisableStructs[battler].neutralizingGas) { - gDisableStructs[battler].neutralizingGas = TRUE; - gBattlerAbility = battler; - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_NEUTRALIZING_GAS; - if (caseID == ABILITYEFFECT_NEUTRALIZINGGAS_FIRST_TURN) - BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg); + if (SearchTraits(battlerTraits, ABILITY_NEUTRALIZING_GAS) > 1) + { + // Neutralizing Gas negates all Main Abilities and should not be an Innate. + DebugPrintf("Neutralizing Gas not set as main Ability"); + } else - BattleScriptCall(BattleScript_SwitchInAbilityMsgRet); - effect++; + { + gDisableStructs[battler].neutralizingGas = TRUE; + gBattlerAbility = battler; + PushTraitStack(battler, ABILITY_NEUTRALIZING_GAS); + if (caseID == ABILITYEFFECT_NEUTRALIZINGGAS_FIRST_TURN) + BattleScriptPushCursorAndCallback(BattleScript_NeutralizingGasActivates); + else + BattleScriptCall(BattleScript_NeutralizingGasActivatesRet); + effect++; + } } break; case ABILITYEFFECT_ON_WEATHER: // For ability effects that activate when the battle weather changes. if (!IsBattlerAlive(battler)) return effect; - gLastUsedAbility = GetBattlerAbility(battler); - switch (gLastUsedAbility) - { - case ABILITY_FORECAST: - case ABILITY_FLOWER_GIFT: - case ABILITY_ICE_FACE: + + bool32 transformReturnCheck = FALSE; + if (SearchTraits(battlerTraits, ABILITY_FORECAST)) { u32 battlerWeatherAffected = IsBattlerWeatherAffected(battler, gBattleWeather); if (battlerWeatherAffected && !CanBattlerFormChange(battler, FORM_CHANGE_BATTLE_WEATHER)) { - // If Hail/Snow activates when in Eiscue is in base, prevent reversion when Eiscue Noice gets broken - gDisableStructs[battler].weatherAbilityDone = TRUE; + transformReturnCheck = TRUE; } - if (((!gDisableStructs[battler].weatherAbilityDone && battlerWeatherAffected) + if (((!gDisableStructs[battler].transformWeatherAbilityDone && battlerWeatherAffected) || gBattleWeather == B_WEATHER_NONE || !HasWeatherEffect()) // Air Lock active && TryBattleFormChange(battler, FORM_CHANGE_BATTLE_WEATHER)) { + gLastUsedAbility = ABILITY_FORECAST; gBattleScripting.battler = battler; - gDisableStructs[battler].weatherAbilityDone = TRUE; + gDisableStructs[battler].transformWeatherAbilityDone = TRUE; + PushTraitStack(battler, ABILITY_FORECAST); BattleScriptPushCursorAndCallback(BattleScript_BattlerFormChangeWithStringEnd3); effect++; } - break; } - case ABILITY_PROTOSYNTHESIS: - if (!gDisableStructs[battler].weatherAbilityDone - && (gBattleWeather & B_WEATHER_SUN) && HasWeatherEffect() - && !gBattleMons[battler].volatiles.transformed - && !gDisableStructs[battler].boosterEnergyActivated) + else if (SearchTraits(battlerTraits, ABILITY_FLOWER_GIFT)) + { + u32 battlerWeatherAffected = IsBattlerWeatherAffected(battler, gBattleWeather); + if (battlerWeatherAffected && !CanBattlerFormChange(battler, FORM_CHANGE_BATTLE_WEATHER)) + { + transformReturnCheck = TRUE; + } + + if (((!gDisableStructs[battler].transformWeatherAbilityDone && battlerWeatherAffected) + || gBattleWeather == B_WEATHER_NONE + || !HasWeatherEffect()) // Air Lock active + && TryBattleFormChange(battler, FORM_CHANGE_BATTLE_WEATHER)) { - gDisableStructs[battler].weatherAbilityDone = TRUE; - gDisableStructs[battler].paradoxBoostedStat = GetParadoxHighestStatId(battler); - PREPARE_STAT_BUFFER(gBattleTextBuff1, gDisableStructs[battler].paradoxBoostedStat); + gLastUsedAbility = ABILITY_FLOWER_GIFT; gBattleScripting.battler = battler; - BattleScriptPushCursorAndCallback(BattleScript_ProtosynthesisActivates); + gDisableStructs[battler].transformWeatherAbilityDone = TRUE; + PushTraitStack(battler, ABILITY_FLOWER_GIFT); + BattleScriptPushCursorAndCallback(BattleScript_BattlerFormChangeWithStringEnd3); effect++; } - break; - default: - break; } - break; - case ABILITYEFFECT_ON_TERRAIN: // For ability effects that activate when the field terrain changes. - if (!IsBattlerAlive(battler)) - return effect; - gLastUsedAbility = GetBattlerAbility(battler); - switch (gLastUsedAbility) + else if (SearchTraits(battlerTraits, ABILITY_ICE_FACE)) { - case ABILITY_MIMICRY: - if (!gDisableStructs[battler].terrainAbilityDone && ChangeTypeBasedOnTerrain(battler)) + u32 battlerWeatherAffected = IsBattlerWeatherAffected(battler, gBattleWeather); + if (battlerWeatherAffected && !CanBattlerFormChange(battler, FORM_CHANGE_BATTLE_WEATHER)) { - gDisableStructs[battler].terrainAbilityDone = TRUE; - ChangeTypeBasedOnTerrain(battler); - gBattlerAbility = gBattleScripting.battler = battler; - BattleScriptPushCursorAndCallback(BattleScript_MimicryActivates); - effect++; + transformReturnCheck = TRUE; } - break; - case ABILITY_QUARK_DRIVE: - if (!gDisableStructs[battler].terrainAbilityDone - && gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN - && !gBattleMons[battler].volatiles.transformed - && !gDisableStructs[battler].boosterEnergyActivated) + + if (((!gDisableStructs[battler].transformWeatherAbilityDone && battlerWeatherAffected) + || gBattleWeather == B_WEATHER_NONE + || !HasWeatherEffect()) // Air Lock active + && TryBattleFormChange(battler, FORM_CHANGE_BATTLE_WEATHER)) { - gDisableStructs[battler].terrainAbilityDone = TRUE; - gDisableStructs[battler].paradoxBoostedStat = GetParadoxHighestStatId(battler); - PREPARE_STAT_BUFFER(gBattleTextBuff1, gDisableStructs[battler].paradoxBoostedStat); - gBattlerAbility = gBattleScripting.battler = battler; - BattleScriptPushCursorAndCallback(BattleScript_QuarkDriveActivates); + gLastUsedAbility = ABILITY_ICE_FACE; + gBattleScripting.battler = battler; + gDisableStructs[battler].transformWeatherAbilityDone = TRUE; + PushTraitStack(battler, ABILITY_ICE_FACE); + BattleScriptPushCursorAndCallback(BattleScript_BattlerFormChangeWithStringEnd3); effect++; } - break; - default: - break; + } + // Disables weather return transformation at the end of the transform block to reduce conflicts + if (transformReturnCheck) + { + // If Hail/Snow activates when in Eiscue is in base, prevent reversion when Eiscue Noice gets broken + gDisableStructs[battler].transformWeatherAbilityDone = TRUE; + } + + if (SearchTraits(battlerTraits, ABILITY_PROTOSYNTHESIS) + && !gDisableStructs[battler].weatherAbilityDone + && (gBattleWeather & B_WEATHER_SUN) && HasWeatherEffect() + && !gBattleMons[battler].volatiles.transformed + && !gDisableStructs[battler].boosterEnergyActivated) + { + gLastUsedAbility = ABILITY_PROTOSYNTHESIS; + gDisableStructs[battler].weatherAbilityDone = TRUE; + gDisableStructs[battler].paradoxBoostedStat = GetParadoxHighestStatId(battler); + PREPARE_STAT_BUFFER(gBattleTextBuff1, gDisableStructs[battler].paradoxBoostedStat); + gBattleScripting.battler = battler; + PushTraitStack(battler, ABILITY_PROTOSYNTHESIS); + BattleScriptPushCursorAndCallback(BattleScript_ProtosynthesisActivates); + effect++; + } + break; + case ABILITYEFFECT_ON_TERRAIN: // For ability effects that activate when the field terrain changes. + if (!IsBattlerAlive(battler)) + return effect; + + if (SearchTraits(battlerTraits, ABILITY_MIMICRY) + && !gDisableStructs[battler].terrainAbilityDone && ChangeTypeBasedOnTerrain(battler)) + { + gLastUsedAbility = ABILITY_MIMICRY; + gDisableStructs[battler].terrainAbilityDone = TRUE; + ChangeTypeBasedOnTerrain(battler); + gBattlerAbility = gBattleScripting.battler = battler; + PushTraitStack(battler, ABILITY_MIMICRY); + BattleScriptPushCursorAndCallback(BattleScript_MimicryActivates); + effect++; + } + if (SearchTraits(battlerTraits, ABILITY_QUARK_DRIVE) + && !gDisableStructs[battler].terrainAbilityDone + && gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN + && !gBattleMons[battler].volatiles.transformed + && !(gDisableStructs[battler].boosterEnergyActivated & (1u << battler))) + { + gLastUsedAbility = ABILITY_QUARK_DRIVE; + gDisableStructs[battler].terrainAbilityDone = TRUE; + gDisableStructs[battler].paradoxBoostedStat = GetParadoxHighestStatId(battler); + PREPARE_STAT_BUFFER(gBattleTextBuff1, GetHighestStatId(battler)); + gBattlerAbility = gBattleScripting.battler = battler; + PushTraitStack(battler, ABILITY_QUARK_DRIVE); + BattleScriptPushCursorAndCallback(BattleScript_QuarkDriveActivates); + effect++; } break; } @@ -5692,7 +5942,6 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab RecordAbilityBattle(battler, gLastUsedAbility); if (effect && caseID <= ABILITYEFFECT_MOVE_END) gBattlerAbility = battler; - return effect; } @@ -5721,28 +5970,45 @@ bool32 IsNeutralizingGasOnField(void) return FALSE; } -bool32 IsMoldBreakerTypeAbility(u32 battler, enum Ability ability) +bool32 HasMoldBreakerTypeAbility(u32 battler) { + enum Ability foundAbility = ABILITY_NONE; + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battler); + + if (gAiLogicData->aiCalcInProgress) + battlerTraits[0] = gAiLogicData->abilities[battler]; + if (gBattleMons[battler].volatiles.gastroAcid) return FALSE; - if (ability == ABILITY_MOLD_BREAKER - || ability == ABILITY_TERAVOLT - || ability == ABILITY_TURBOBLAZE - || (ability == ABILITY_MYCELIUM_MIGHT && IsBattleMoveStatus(gCurrentMove))) - { - RecordAbilityBattle(battler, ability); + if (SearchTraits(battlerTraits, ABILITY_TERAVOLT)) + foundAbility = ABILITY_TERAVOLT; + else if (SearchTraits(battlerTraits, ABILITY_TURBOBLAZE)) + foundAbility = ABILITY_TURBOBLAZE; + else if (SearchTraits(battlerTraits, ABILITY_MOLD_BREAKER)) + foundAbility = ABILITY_MOLD_BREAKER; + else if ((SearchTraits(battlerTraits, ABILITY_MYCELIUM_MIGHT)) && IsBattleMoveStatus(gCurrentMove)) + foundAbility = ABILITY_MYCELIUM_MIGHT; + + if (foundAbility != ABILITY_NONE) + { + RecordAbilityBattle(battler, foundAbility); return TRUE; } return FALSE; } -static inline bool32 CanBreakThroughAbility(u32 battlerAtk, u32 battlerDef, u32 hasAbilityShield, u32 ignoreMoldBreaker) +static inline bool32 CanBreakThroughAbility(u32 battlerAtk, u32 battlerDef, enum Ability ability, u32 hasAbilityShield, u32 ignoreMoldBreaker) { + if (ability == ABILITY_NONE) + ability = gBattleMons[battlerDef].ability; + if (hasAbilityShield || ignoreMoldBreaker || battlerDef == battlerAtk) return FALSE; - return gBattleStruct->moldBreakerActive && gAbilitiesInfo[gBattleMons[battlerDef].ability].breakable; + + return gBattleStruct->moldBreakerActive && gAbilitiesInfo[ability].breakable; } u32 GetBattlerAbilityNoAbilityShield(u32 battler) @@ -5769,11 +6035,11 @@ u32 GetBattlerAbilityInternal(u32 battler, u32 ignoreMoldBreaker, u32 noAbilityS { // Edge case: pokemon under the effect of gastro acid transforms into a pokemon with Comatose (Todo: verify how other unsuppressable abilities behave) if (gBattleMons[battler].volatiles.transformed - && gBattleMons[battler].volatiles.gastroAcid - && gBattleMons[battler].ability == ABILITY_COMATOSE) - return ABILITY_NONE; + && gBattleMons[battler].volatiles.gastroAcid + && gBattleMons[battler].ability == ABILITY_COMATOSE) + return ABILITY_NONE; - if (CanBreakThroughAbility(gBattlerAttacker, battler, hasAbilityShield, ignoreMoldBreaker)) + if (CanBreakThroughAbility(gBattlerAttacker, battler, ABILITY_NONE, hasAbilityShield, ignoreMoldBreaker)) return ABILITY_NONE; return gBattleMons[battler].ability; @@ -5784,10 +6050,10 @@ u32 GetBattlerAbilityInternal(u32 battler, u32 ignoreMoldBreaker, u32 noAbilityS if (!hasAbilityShield && IsNeutralizingGasOnField() - && (gBattleMons[battler].ability != ABILITY_NEUTRALIZING_GAS || gBattleMons[battler].volatiles.gastroAcid)) + && (gBattleMons[battler].ability != ABILITY_NEUTRALIZING_GAS || gBattleMons[battler].volatiles.gastroAcid)) // Neutralizing Gas should be a Main Ability return ABILITY_NONE; - if (CanBreakThroughAbility(gBattlerAttacker, battler, hasAbilityShield, ignoreMoldBreaker)) + if (CanBreakThroughAbility(gBattlerAttacker, battler, ABILITY_NONE, hasAbilityShield, ignoreMoldBreaker)) return ABILITY_NONE; return gBattleMons[battler].ability; @@ -5795,9 +6061,9 @@ u32 GetBattlerAbilityInternal(u32 battler, u32 ignoreMoldBreaker, u32 noAbilityS u32 IsAbilityOnSide(u32 battler, enum Ability ability) { - if (IsBattlerAlive(battler) && GetBattlerAbility(battler) == ability) + if (IsBattlerAlive(battler) && BattlerHasTrait(battler, ability)) return battler + 1; - else if (IsBattlerAlive(BATTLE_PARTNER(battler)) && GetBattlerAbility(BATTLE_PARTNER(battler)) == ability) + else if (IsBattlerAlive(BATTLE_PARTNER(battler)) && BattlerHasTrait(BATTLE_PARTNER(battler), ability)) return BATTLE_PARTNER(battler) + 1; else return 0; @@ -5814,7 +6080,7 @@ u32 IsAbilityOnField(enum Ability ability) for (i = 0; i < gBattlersCount; i++) { - if (IsBattlerAlive(i) && GetBattlerAbility(i) == ability) + if (IsBattlerAlive(i) && BattlerHasTrait(i, ability)) return i + 1; } @@ -5827,7 +6093,7 @@ u32 IsAbilityOnFieldExcept(u32 battler, enum Ability ability) for (i = 0; i < gBattlersCount; i++) { - if (i != battler && IsBattlerAlive(i) && GetBattlerAbility(i) == ability) + if (i != battler && IsBattlerAlive(i) && BattlerHasTrait(i, ability)) return i + 1; } @@ -5839,22 +6105,30 @@ u32 IsAbilityPreventingEscape(u32 battler) if (GetConfig(CONFIG_GHOSTS_ESCAPE) >= GEN_6 && IS_BATTLER_OF_TYPE(battler, TYPE_GHOST)) return 0; - bool32 isBattlerGrounded = IsBattlerGrounded(battler, GetBattlerAbility(battler), GetBattlerHoldEffect(battler)); + bool32 isBattlerGrounded = IsBattlerGrounded(battler, GetBattlerHoldEffect(battler)); for (u32 battlerDef = 0; battlerDef < gBattlersCount; battlerDef++) { if (battler == battlerDef || IsBattlerAlly(battler, battlerDef)) continue; - enum Ability ability = GetBattlerAbility(battlerDef); - if (ability == ABILITY_SHADOW_TAG && (B_SHADOW_TAG_ESCAPE <= GEN_3 || GetBattlerAbility(battler) != ABILITY_SHADOW_TAG)) - return battlerDef + 1; + if (BattlerHasTrait(battlerDef, ABILITY_SHADOW_TAG) && (B_SHADOW_TAG_ESCAPE <= GEN_3 || !BattlerHasTrait(battler, ABILITY_SHADOW_TAG))) + { + gDisplayAbility = ABILITY_SHADOW_TAG; + return battlerDef + 1; - if (ability == ABILITY_ARENA_TRAP && isBattlerGrounded) + } + if (BattlerHasTrait(battlerDef, ABILITY_ARENA_TRAP) && isBattlerGrounded) + { + gDisplayAbility = ABILITY_ARENA_TRAP; return battlerDef + 1; - if (ability == ABILITY_MAGNET_PULL && IS_BATTLER_OF_TYPE(battler, TYPE_STEEL)) + } + if (BattlerHasTrait(battlerDef, ABILITY_MAGNET_PULL) && IS_BATTLER_OF_TYPE(battler, TYPE_STEEL)) + { + gDisplayAbility = ABILITY_MAGNET_PULL; return battlerDef + 1; + } } return 0; @@ -5895,14 +6169,14 @@ void BattleScriptPushCursorAndCallback(const u8 *BS_ptr) gBattleMainFunc = RunBattleScriptCommands; } -bool32 IsBattlerTerrainAffected(u32 battler, enum Ability ability, enum HoldEffect holdEffect, u32 terrainFlag) +bool32 IsBattlerTerrainAffected(u32 battler, enum HoldEffect holdEffect, u32 terrainFlag) { if (!(gFieldStatuses & terrainFlag)) return FALSE; if (IsSemiInvulnerable(battler, CHECK_ALL)) return FALSE; - return IsBattlerGrounded(battler, ability, holdEffect); + return IsBattlerGrounded(battler, holdEffect); } u32 GetHighestStatId(u32 battler) @@ -6006,14 +6280,14 @@ u32 GetParadoxHighestStatId(u32 battler) static void ResetParadoxWeatherStat(u32 battler) { - if (gBattleMons[battler].ability == ABILITY_PROTOSYNTHESIS + if (BattlerHasTrait(battler, ABILITY_PROTOSYNTHESIS) && !gDisableStructs[battler].boosterEnergyActivated) gDisableStructs[battler].paradoxBoostedStat = 0; } static void ResetParadoxTerrainStat(u32 battler) { - if (gBattleMons[battler].ability == ABILITY_QUARK_DRIVE + if (BattlerHasTrait(battler, ABILITY_QUARK_DRIVE) && !gDisableStructs[battler].boosterEnergyActivated) gDisableStructs[battler].paradoxBoostedStat = 0; } @@ -6026,7 +6300,7 @@ u32 GetParadoxBoostedStatId(u32 battler) return gDisableStructs[battler].paradoxBoostedStat; } -bool32 CanBeSlept(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef, enum SleepClauseBlock isBlockedBySleepClause) +bool32 CanBeSlept(u32 battlerAtk, u32 battlerDef, enum SleepClauseBlock isBlockedBySleepClause) { if (IsSleepClauseActiveForSide(GetBattlerSide(battlerDef)) && isBlockedBySleepClause != NOT_BLOCKED_BY_SLEEP_CLAUSE) return FALSE; @@ -6038,8 +6312,6 @@ bool32 CanBeSlept(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef, enum if (CanSetNonVolatileStatus( battlerAtk, battlerDef, - ABILITY_NONE, // attacker ability does not matter - abilityDef, MOVE_EFFECT_SLEEP, // also covers yawn CHECK_TRIGGER)) effect = TRUE; @@ -6048,13 +6320,11 @@ bool32 CanBeSlept(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef, enum return effect; } -bool32 CanBePoisoned(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef) +bool32 CanBePoisoned(u32 battlerAtk, u32 battlerDef) { if (CanSetNonVolatileStatus( battlerAtk, battlerDef, - abilityAtk, - abilityDef, MOVE_EFFECT_TOXIC, // also covers poison CHECK_TRIGGER)) return TRUE; @@ -6062,99 +6332,100 @@ bool32 CanBePoisoned(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, en } // TODO: check order of battlerAtk and battlerDef -bool32 CanBeBurned(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef) +bool32 CanBeBurned(u32 battlerAtk, u32 battlerDef) { if (CanSetNonVolatileStatus( battlerAtk, battlerDef, - ABILITY_NONE, // attacker ability does not matter - abilityDef, MOVE_EFFECT_BURN, CHECK_TRIGGER)) return TRUE; return FALSE; } -bool32 CanBeParalyzed(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef) +bool32 CanBeParalyzed(u32 battlerAtk, u32 battlerDef) { if (CanSetNonVolatileStatus( battlerAtk, battlerDef, - ABILITY_NONE, // attacker ability does not matter - abilityDef, MOVE_EFFECT_PARALYSIS, CHECK_TRIGGER)) return TRUE; return FALSE; } -bool32 CanBeFrozen(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef) +bool32 CanBeFrozen(u32 battlerAtk, u32 battlerDef) { if (CanSetNonVolatileStatus( battlerAtk, battlerDef, - ABILITY_NONE, // attacker ability does not matter - abilityDef, MOVE_EFFECT_FREEZE, CHECK_TRIGGER)) return TRUE; return FALSE; } // Unused, technically also redundant because it is just a copy of CanBeFrozen -bool32 CanGetFrostbite(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef) +bool32 CanGetFrostbite(u32 battlerAtk, u32 battlerDef) { if (CanSetNonVolatileStatus( battlerAtk, battlerDef, - ABILITY_NONE, // attacker ability does not matter - abilityDef, MOVE_EFFECT_FREEZE_OR_FROSTBITE, // also covers frostbite CHECK_TRIGGER)) return TRUE; return FALSE; } -bool32 IsSafeguardProtected(u32 battlerAtk, u32 battlerDef, u32 abilityAtk) +bool32 IsSafeguardProtected(u32 battlerAtk, u32 battlerDef) { if (!(gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_SAFEGUARD)) return FALSE; if (IsBattlerAlly(battlerAtk, battlerDef)) return TRUE; - if (abilityAtk == ABILITY_INFILTRATOR) + if (BattlerHasTrait(battlerAtk, ABILITY_INFILTRATOR)) return FALSE; return TRUE; } -bool32 CanSetNonVolatileStatus(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef, enum MoveEffect effect, enum FunctionCallOption option) +bool32 CanSetNonVolatileStatus(u32 battlerAtk, u32 battlerDef, enum MoveEffect effect, enum FunctionCallOption option) { const u8 *battleScript = NULL; u32 sideBattler = ABILITY_NONE; bool32 abilityAffected = FALSE; + enum Ability abilityDef = ABILITY_NONE; + enum Ability battlerTraits[MAX_MON_TRAITS]; + enum Ability AIBattlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battlerDef); + AI_STORE_BATTLER_TRAITS(battlerDef); // Move specific checks switch (effect) { case MOVE_EFFECT_POISON: case MOVE_EFFECT_TOXIC: + if (gBattleMons[battlerDef].status1 & (STATUS1_POISON | STATUS1_TOXIC_POISON)) { battleScript = BattleScript_AlreadyPoisoned; } - else if (abilityAtk != ABILITY_CORROSION && IS_BATTLER_ANY_TYPE(battlerDef, TYPE_POISON, TYPE_STEEL)) + else if (gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_IMMUNITY) : SearchTraits(battlerTraits, ABILITY_IMMUNITY)) { - battleScript = BattleScript_NotAffected; + gLastUsedAbility = abilityDef = ABILITY_IMMUNITY; + PushTraitStack(battlerDef, ABILITY_IMMUNITY); + abilityAffected = TRUE; + battleScript = BattleScript_ImmunityProtected; } else if ((sideBattler = IsAbilityOnSide(battlerDef, ABILITY_PASTEL_VEIL))) { + gLastUsedAbility = abilityDef = ABILITY_PASTEL_VEIL; abilityAffected = TRUE; battlerDef = sideBattler - 1; - abilityDef = ABILITY_PASTEL_VEIL; + PushTraitStack(battlerDef, ABILITY_PASTEL_VEIL); battleScript = BattleScript_ImmunityProtected; } - else if (abilityDef == ABILITY_IMMUNITY) + else if (!BattlerHasTrait(battlerAtk, ABILITY_CORROSION) && IS_BATTLER_ANY_TYPE(battlerDef, TYPE_POISON, TYPE_STEEL)) { - abilityAffected = TRUE; - battleScript = BattleScript_ImmunityProtected; + battleScript = BattleScript_NotAffected; } break; case MOVE_EFFECT_PARALYSIS: @@ -6167,13 +6438,15 @@ bool32 CanSetNonVolatileStatus(u32 battlerAtk, u32 battlerDef, enum Ability abil battleScript = BattleScript_NotAffected; } else if (option == RUN_SCRIPT // Check only important during battle execution for moves - && CalcTypeEffectivenessMultiplierHelper(gCurrentMove, GetBattleMoveType(gCurrentMove), battlerAtk, battlerDef, abilityAtk, abilityDef, TRUE) + && CalcTypeEffectivenessMultiplierHelper(gCurrentMove, GetBattleMoveType(gCurrentMove), battlerAtk, battlerDef, TRUE) && gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_NO_EFFECT) { battleScript = BattleScript_ButItFailed; } - else if (abilityDef == ABILITY_LIMBER) + else if (gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_LIMBER) : SearchTraits(battlerTraits, ABILITY_LIMBER)) { + gLastUsedAbility = abilityDef = ABILITY_LIMBER; + PushTraitStack(battlerDef, ABILITY_LIMBER); abilityAffected = TRUE; battleScript = BattleScript_ImmunityProtected; } @@ -6187,13 +6460,24 @@ bool32 CanSetNonVolatileStatus(u32 battlerAtk, u32 battlerDef, enum Ability abil { battleScript = BattleScript_NotAffected; } - else if (abilityDef == ABILITY_WATER_VEIL || abilityDef == ABILITY_WATER_BUBBLE) + else if (gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_WATER_VEIL) : SearchTraits(battlerTraits, ABILITY_WATER_VEIL)) { + gLastUsedAbility = abilityDef = ABILITY_WATER_VEIL; + PushTraitStack(battlerDef, ABILITY_WATER_VEIL); abilityAffected = TRUE; battleScript = BattleScript_ImmunityProtected; } - else if (abilityDef == ABILITY_THERMAL_EXCHANGE) + else if (gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_WATER_BUBBLE) : SearchTraits(battlerTraits, ABILITY_WATER_BUBBLE)) { + gLastUsedAbility = abilityDef = ABILITY_WATER_BUBBLE; + PushTraitStack(battlerDef, ABILITY_WATER_BUBBLE); + abilityAffected = TRUE; + battleScript = BattleScript_ImmunityProtected; + } + else if (gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_THERMAL_EXCHANGE) : SearchTraits(battlerTraits, ABILITY_THERMAL_EXCHANGE)) + { + gLastUsedAbility = abilityDef = ABILITY_THERMAL_EXCHANGE; + PushTraitStack(battlerDef, ABILITY_THERMAL_EXCHANGE); abilityAffected = TRUE; battleScript = BattleScript_AbilityProtectsDoesntAffect; } @@ -6211,22 +6495,32 @@ bool32 CanSetNonVolatileStatus(u32 battlerAtk, u32 battlerDef, enum Ability abil { battleScript = BattleScript_SleepClauseBlocked; } - else if (IsBattlerTerrainAffected(battlerDef, abilityDef, GetBattlerHoldEffect(battlerDef), STATUS_FIELD_ELECTRIC_TERRAIN)) + else if (IsBattlerTerrainAffected(battlerDef, GetBattlerHoldEffect(battlerDef), STATUS_FIELD_ELECTRIC_TERRAIN)) { battleScript = BattleScript_ElectricTerrainPrevents; } - else if ((sideBattler = IsAbilityOnSide(battlerDef, ABILITY_SWEET_VEIL))) + else if (gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_INSOMNIA) : SearchTraits(battlerTraits, ABILITY_INSOMNIA)) { + gLastUsedAbility = abilityDef = ABILITY_INSOMNIA; + PushTraitStack(battlerDef, ABILITY_INSOMNIA); abilityAffected = TRUE; - battlerDef = sideBattler - 1; - abilityDef = ABILITY_SWEET_VEIL; - battleScript = BattleScript_ImmunityProtected; + battleScript = BattleScript_PrintAbilityMadeIneffective; } - else if (abilityDef == ABILITY_VITAL_SPIRIT || abilityDef == ABILITY_INSOMNIA) + else if (gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_VITAL_SPIRIT) : SearchTraits(battlerTraits, ABILITY_VITAL_SPIRIT)) { + gLastUsedAbility = abilityDef = ABILITY_VITAL_SPIRIT; + PushTraitStack(battlerDef, ABILITY_VITAL_SPIRIT); abilityAffected = TRUE; battleScript = BattleScript_PrintAbilityMadeIneffective; } + else if ((sideBattler = IsAbilityOnSide(battlerDef, ABILITY_SWEET_VEIL))) + { + gLastUsedAbility = abilityDef = ABILITY_SWEET_VEIL; + abilityAffected = TRUE; + battlerDef = sideBattler - 1; + PushTraitStack(battlerDef, ABILITY_SWEET_VEIL); + battleScript = BattleScript_ImmunityProtected; + } break; case MOVE_EFFECT_FREEZE: case MOVE_EFFECT_FROSTBITE: @@ -6238,8 +6532,10 @@ bool32 CanSetNonVolatileStatus(u32 battlerAtk, u32 battlerDef, enum Ability abil { battleScript = BattleScript_NotAffected; } - else if (abilityDef == ABILITY_MAGMA_ARMOR) + else if (gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_MAGMA_ARMOR) : SearchTraits(battlerTraits, ABILITY_MAGMA_ARMOR)) { + gLastUsedAbility = abilityDef = ABILITY_MAGMA_ARMOR; + PushTraitStack(battlerDef, ABILITY_MAGMA_ARMOR); abilityAffected = TRUE; battleScript = BattleScript_NotAffected; } @@ -6252,22 +6548,30 @@ bool32 CanSetNonVolatileStatus(u32 battlerAtk, u32 battlerDef, enum Ability abil return FALSE; // Checks that apply to all non volatile statuses - if (abilityDef == ABILITY_COMATOSE - || abilityDef == ABILITY_PURIFYING_SALT) + if (gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_COMATOSE) : SearchTraits(battlerTraits, ABILITY_COMATOSE)) + { + gLastUsedAbility = abilityDef = ABILITY_COMATOSE; + PushTraitStack(battlerDef, ABILITY_COMATOSE); + abilityAffected = TRUE; + battleScript = BattleScript_AbilityProtectsDoesntAffect; + } + else if (gAiLogicData->aiCalcInProgress ? AISearchTraits(AIBattlerTraits, ABILITY_PURIFYING_SALT) : SearchTraits(battlerTraits, ABILITY_PURIFYING_SALT)) { + gLastUsedAbility = abilityDef = ABILITY_PURIFYING_SALT; + PushTraitStack(battlerDef, ABILITY_PURIFYING_SALT); abilityAffected = TRUE; battleScript = BattleScript_AbilityProtectsDoesntAffect; } - else if (IsBattlerTerrainAffected(battlerDef, abilityDef, GetBattlerHoldEffect(battlerDef), STATUS_FIELD_MISTY_TERRAIN)) + else if (IsBattlerTerrainAffected(battlerDef, GetBattlerHoldEffect(battlerDef), STATUS_FIELD_MISTY_TERRAIN)) { battleScript = BattleScript_MistyTerrainPrevents; } - else if (IsLeafGuardProtected(battlerDef, abilityDef)) + else if (IsLeafGuardProtected(battlerDef)) { abilityAffected = TRUE; battleScript = BattleScript_AbilityProtectsDoesntAffect; } - else if (IsShieldsDownProtected(battlerDef, abilityDef)) + else if (IsShieldsDownProtected(battlerDef)) { abilityAffected = TRUE; battleScript = BattleScript_AbilityProtectsDoesntAffect; @@ -6279,7 +6583,7 @@ bool32 CanSetNonVolatileStatus(u32 battlerAtk, u32 battlerDef, enum Ability abil abilityDef = ABILITY_FLOWER_VEIL; battleScript = BattleScript_FlowerVeilProtects; } - else if (IsSafeguardProtected(battlerAtk, battlerDef, abilityAtk)) + else if (IsSafeguardProtected(battlerAtk, battlerDef)) { battleScript = BattleScript_SafeguardProtected; } @@ -6340,16 +6644,16 @@ static bool32 CanSleepDueToSleepClause(u32 battlerAtk, u32 battlerDef, enum Func bool32 CanBeConfused(u32 battler) { - enum Ability ability = GetBattlerAbility(battler); + // Uses an extra check for the received ability in case the AI is trying to give a status to itself and thus should know the ability already if (gBattleMons[battler].volatiles.confusionTurns > 0 - || IsBattlerTerrainAffected(battler, ability, GetBattlerHoldEffect(battler), STATUS_FIELD_MISTY_TERRAIN) - || IsAbilityAndRecord(battler, ability, ABILITY_OWN_TEMPO)) + || IsBattlerTerrainAffected(battler, GetBattlerHoldEffect(battler), STATUS_FIELD_MISTY_TERRAIN) + || (gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battler, ABILITY_OWN_TEMPO) : IsAbilityAndRecord(battler, ABILITY_OWN_TEMPO))) return FALSE; return TRUE; } // second argument is 1/X of current hp compared to max hp -bool32 HasEnoughHpToEatBerry(u32 battler, enum Ability ability, u32 hpFraction, u32 itemId) +bool32 HasEnoughHpToEatBerry(u32 battler, u32 hpFraction, u32 itemId) { if (!IsBattlerAlive(battler)) return FALSE; @@ -6360,8 +6664,10 @@ bool32 HasEnoughHpToEatBerry(u32 battler, enum Ability ability, u32 hpFraction, if (hpFraction <= 4 && GetItemPocket(itemId) == POCKET_BERRIES && gBattleMons[battler].hp <= gBattleMons[battler].maxHP / 2 - && IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_GLUTTONY)) + && IsAbilityAndRecord(battler, ABILITY_GLUTTONY)) + { return TRUE; + } return FALSE; } @@ -6428,7 +6734,7 @@ u32 GetBattleMoveTarget(u16 move, u8 setTarget) enum Ability battlerAbilityOnField = 0; targetBattler = SetRandomTarget(gBattlerAttacker); - if (moveType == TYPE_ELECTRIC && GetBattlerAbility(targetBattler) != ABILITY_LIGHTNING_ROD) + if (moveType == TYPE_ELECTRIC && !BattlerHasTrait(targetBattler, ABILITY_LIGHTNING_ROD)) { if (B_REDIRECT_ABILITY_ALLIES >= GEN_4) battlerAbilityOnField = IsAbilityOnField(ABILITY_LIGHTNING_ROD); @@ -6442,7 +6748,7 @@ u32 GetBattleMoveTarget(u16 move, u8 setTarget) gSpecialStatuses[targetBattler].abilityRedirected = TRUE; } } - else if (moveType == TYPE_WATER && GetBattlerAbility(targetBattler) != ABILITY_STORM_DRAIN) + else if (moveType == TYPE_WATER && !BattlerHasTrait(targetBattler, ABILITY_STORM_DRAIN)) { if (B_REDIRECT_ABILITY_ALLIES >= GEN_4) battlerAbilityOnField = IsAbilityOnField(ABILITY_STORM_DRAIN); @@ -6579,7 +6885,7 @@ u8 GetAttackerObedienceForAction() obedienceLevel = levelReferenced - obedienceLevel; calc = ((rnd >> 16) & 255); - if (calc < obedienceLevel && CanBeSlept(gBattlerAttacker, gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), NOT_BLOCKED_BY_SLEEP_CLAUSE)) + if (calc < obedienceLevel && CanBeSlept(gBattlerAttacker, gBattlerAttacker, NOT_BLOCKED_BY_SLEEP_CLAUSE)) { // try putting asleep int i; @@ -6599,21 +6905,21 @@ u8 GetAttackerObedienceForAction() enum HoldEffect GetBattlerHoldEffect(u32 battler) { - return GetBattlerHoldEffectInternal(battler, GetBattlerAbility(battler)); + return GetBattlerHoldEffectInternal(battler, FALSE); } enum HoldEffect GetBattlerHoldEffectIgnoreAbility(u32 battler) { - return GetBattlerHoldEffectInternal(battler, ABILITY_NONE); + return GetBattlerHoldEffectInternal(battler, TRUE); } -enum HoldEffect GetBattlerHoldEffectInternal(u32 battler, u32 ability) +enum HoldEffect GetBattlerHoldEffectInternal(u32 battler, bool32 ignoreAbility) { if (gBattleMons[battler].volatiles.embargo) return HOLD_EFFECT_NONE; if (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) return HOLD_EFFECT_NONE; - if (ability == ABILITY_KLUTZ && !gBattleMons[battler].volatiles.gastroAcid) + if (!ignoreAbility && BattlerHasTrait(battler, ABILITY_KLUTZ) && !gBattleMons[battler].volatiles.gastroAcid) return HOLD_EFFECT_NONE; gPotentialItemEffectBattler = battler; @@ -6641,7 +6947,7 @@ u32 GetBattlerHoldEffectParam(u32 battler) return GetItemHoldEffectParam(gBattleMons[battler].item); } -bool32 CanBattlerAvoidContactEffects(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum HoldEffect holdEffectAtk, u32 move) +bool32 CanBattlerAvoidContactEffects(u32 battlerAtk, u32 battlerDef, enum HoldEffect holdEffectAtk, u32 move) { if (holdEffectAtk == HOLD_EFFECT_PROTECTIVE_PADS) { @@ -6649,27 +6955,24 @@ bool32 CanBattlerAvoidContactEffects(u32 battlerAtk, u32 battlerDef, enum Abilit return TRUE; } - return !IsMoveMakingContact(battlerAtk, battlerDef, abilityAtk, holdEffectAtk, move); + return !IsMoveMakingContact(battlerAtk, battlerDef, holdEffectAtk, move); } -bool32 IsMoveMakingContact(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum HoldEffect holdEffectAtk, u32 move) +bool32 IsMoveMakingContact(u32 battlerAtk, u32 battlerDef, enum HoldEffect holdEffectAtk, u32 move) { if (!(MoveMakesContact(move) || (GetMoveEffect(move) == EFFECT_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] == DAMAGE_CATEGORY_PHYSICAL))) { return FALSE; } - else if (holdEffectAtk == HOLD_EFFECT_PUNCHING_GLOVE && IsPunchingMove(move)) + else if ((holdEffectAtk == HOLD_EFFECT_PUNCHING_GLOVE && IsPunchingMove(move)) + || BattlerHasTrait(battlerAtk, ABILITY_LONG_REACH)) { RecordItemEffectBattle(battlerAtk, HOLD_EFFECT_PUNCHING_GLOVE); return FALSE; } - else if (abilityAtk == ABILITY_LONG_REACH) - { - RecordAbilityBattle(battlerAtk, ABILITY_LONG_REACH); - return FALSE; - } - return TRUE; + else + return TRUE; } static inline bool32 IsSideProtected(u32 battler, enum ProtectMethod method) @@ -6708,8 +7011,8 @@ bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move) { if (IsZMove(move) || IsMaxMove(move)) return FALSE; // Z-Moves and Max Moves bypass protection (except Max Guard). - if (GetBattlerAbility(battlerAtk) == ABILITY_UNSEEN_FIST - && IsMoveMakingContact(battlerAtk, battlerDef, ABILITY_UNSEEN_FIST, GetBattlerHoldEffect(battlerAtk), move)) + if (BattlerHasTrait(battlerAtk, ABILITY_UNSEEN_FIST) + && IsMoveMakingContact(battlerAtk, battlerDef, GetBattlerHoldEffect(battlerAtk), move)) return FALSE; } @@ -6737,7 +7040,7 @@ bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move) isProtected = TRUE; else if (gProtectStructs[battlerDef].protected == PROTECT_KINGS_SHIELD && !IsBattleMoveStatus(move)) isProtected = TRUE; - else if (IsSideProtected(battlerDef, PROTECT_QUICK_GUARD) && GetChosenMovePriority(battlerAtk, GetBattlerAbility(battlerAtk)) > 0) + else if (IsSideProtected(battlerDef, PROTECT_QUICK_GUARD) && GetChosenMovePriority(battlerAtk) > 0) isProtected = TRUE; else if (IsSideProtected(battlerDef, PROTECT_MAT_BLOCK) && !IsBattleMoveStatus(move)) isProtected = TRUE; @@ -6788,7 +7091,7 @@ enum IronBallCheck }; // Only called directly when calculating damage type effectiveness, and Iron Ball's type effectiveness mechanics -static bool32 IsBattlerGroundedInverseCheck(u32 battler, enum Ability ability, enum HoldEffect holdEffect, enum InverseBattleCheck checkInverse, bool32 isAnticipation) +static bool32 IsBattlerGroundedInverseCheck(u32 battler, enum HoldEffect holdEffect, enum InverseBattleCheck checkInverse, bool32 isAnticipation, bool32 hasLevitate) { if (holdEffect == HOLD_EFFECT_IRON_BALL) return TRUE; @@ -6804,16 +7107,20 @@ static bool32 IsBattlerGroundedInverseCheck(u32 battler, enum Ability ability, e return FALSE; if (holdEffect == HOLD_EFFECT_AIR_BALLOON) return FALSE; - if (ability == ABILITY_LEVITATE) + if (hasLevitate) return FALSE; if (IS_BATTLER_OF_TYPE(battler, TYPE_FLYING) && (checkInverse != INVERSE_BATTLE || !FlagGet(B_FLAG_INVERSE_BATTLE))) return FALSE; return TRUE; } -bool32 IsBattlerGrounded(u32 battler, enum Ability ability, enum HoldEffect holdEffect) -{ - return IsBattlerGroundedInverseCheck(battler, ability, holdEffect, NOT_INVERSE_BATTLE, FALSE); +bool32 IsBattlerGrounded(u32 battler, enum HoldEffect holdEffect) +{ bool32 hasLevitate; + + // Regular ability check split out here as the AI switching logic uses battle context to figure out the Ability instead. (Multi) + hasLevitate = gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battler, ABILITY_LEVITATE) : BattlerHasTrait(battler, ABILITY_LEVITATE); + + return IsBattlerGroundedInverseCheck(battler, holdEffect, NOT_INVERSE_BATTLE, FALSE, hasLevitate); } u32 GetMoveSlot(u16 *moves, u32 move) @@ -6832,12 +7139,11 @@ u32 GetBattlerWeight(u32 battler) { u32 i; u32 weight = GetSpeciesWeight(gBattleMons[battler].species); - enum Ability ability = GetBattlerAbility(battler); enum HoldEffect holdEffect = GetBattlerHoldEffect(battler); - if (ability == ABILITY_HEAVY_METAL) + if (BattlerHasTrait(battler, ABILITY_HEAVY_METAL)) weight *= 2; - else if (ability == ABILITY_LIGHT_METAL) + if (BattlerHasTrait(battler, ABILITY_LIGHT_METAL)) weight /= 2; if (holdEffect == HOLD_EFFECT_FLOAT_STONE) @@ -7158,7 +7464,7 @@ static inline u32 CalcMoveBasePower(struct DamageContext *ctx) break; case EFFECT_DOUBLE_POWER_ON_ARG_STATUS: // Comatose targets treated as if asleep - if ((gBattleMons[battlerDef].status1 | (STATUS1_SLEEP * (ctx->abilityDef == ABILITY_COMATOSE))) & GetMoveEffectArg_Status(move) + if ((gBattleMons[battlerDef].status1 | (STATUS1_SLEEP * (BattlerHasTrait(battlerDef, ABILITY_COMATOSE)))) & GetMoveEffectArg_Status(move) && !((GetMoveAdditionalEffectById(move, 0)->moveEffect == MOVE_EFFECT_REMOVE_STATUS) && DoesSubstituteBlockMove(battlerAtk, battlerDef, move))) basePower *= 2; break; @@ -7213,21 +7519,21 @@ static inline u32 CalcMoveBasePower(struct DamageContext *ctx) basePower += (CountBattlerStatIncreases(battlerAtk, TRUE) * 20); break; case EFFECT_ELECTRO_BALL: - speed = GetBattlerTotalSpeedStat(battlerAtk, ctx->abilityAtk, ctx->holdEffectAtk) / GetBattlerTotalSpeedStat(battlerDef, ctx->abilityDef, ctx->holdEffectDef); + speed = GetBattlerTotalSpeedStat(battlerAtk, ctx->holdEffectAtk) / GetBattlerTotalSpeedStat(battlerDef, ctx->holdEffectDef); if (speed >= ARRAY_COUNT(sSpeedDiffPowerTable)) speed = ARRAY_COUNT(sSpeedDiffPowerTable) - 1; basePower = sSpeedDiffPowerTable[speed]; break; case EFFECT_GYRO_BALL: { - u32 attackerSpeed = GetBattlerTotalSpeedStat(battlerAtk, ctx->abilityAtk, ctx->holdEffectAtk); + u32 attackerSpeed = GetBattlerTotalSpeedStat(battlerAtk, ctx->holdEffectAtk); if (attackerSpeed == 0) { basePower = 1; } else { - basePower = ((25 * GetBattlerTotalSpeedStat(battlerDef, ctx->abilityDef, ctx->holdEffectDef)) / attackerSpeed) + 1; + basePower = ((25 * GetBattlerTotalSpeedStat(battlerDef, ctx->holdEffectDef)) / attackerSpeed) + 1; if (basePower > 150) basePower = 150; } @@ -7268,7 +7574,7 @@ static inline u32 CalcMoveBasePower(struct DamageContext *ctx) basePower *= 2; break; case EFFECT_MISTY_EXPLOSION: - if (IsBattlerTerrainAffected(battlerAtk, ctx->abilityAtk, ctx->holdEffectAtk, STATUS_FIELD_MISTY_TERRAIN)) + if (IsBattlerTerrainAffected(battlerAtk, ctx->holdEffectAtk, STATUS_FIELD_MISTY_TERRAIN)) basePower = uq4_12_multiply(basePower, UQ_4_12(1.5)); break; case EFFECT_DYNAMAX_DOUBLE_DMG: @@ -7295,15 +7601,15 @@ static inline u32 CalcMoveBasePower(struct DamageContext *ctx) basePower = uq4_12_multiply(basePower, UQ_4_12(1.5)); break; case EFFECT_TERRAIN_PULSE: - if (IsBattlerTerrainAffected(battlerAtk, ctx->abilityAtk, ctx->holdEffectAtk, STATUS_FIELD_TERRAIN_ANY)) + if (IsBattlerTerrainAffected(battlerAtk, ctx->holdEffectAtk, STATUS_FIELD_TERRAIN_ANY)) basePower *= 2; break; case EFFECT_EXPANDING_FORCE: - if (IsBattlerTerrainAffected(battlerAtk, ctx->abilityAtk, ctx->holdEffectAtk, STATUS_FIELD_PSYCHIC_TERRAIN)) + if (IsBattlerTerrainAffected(battlerAtk, ctx->holdEffectAtk, STATUS_FIELD_PSYCHIC_TERRAIN)) basePower = uq4_12_multiply(basePower, UQ_4_12(1.5)); break; case EFFECT_RISING_VOLTAGE: - if (IsBattlerTerrainAffected(battlerDef, ctx->abilityDef, ctx->holdEffectDef, STATUS_FIELD_ELECTRIC_TERRAIN)) + if (IsBattlerTerrainAffected(battlerDef, ctx->holdEffectDef, STATUS_FIELD_ELECTRIC_TERRAIN)) basePower *= 2; break; case EFFECT_BEAT_UP: @@ -7359,6 +7665,7 @@ static inline u32 CalcMoveBasePowerAfterModifiers(struct DamageContext *ctx) u32 move = ctx->move; enum Type moveType = ctx->moveType; enum BattleMoveEffects moveEffect = GetMoveEffect(move); + enum Ability battlerTraits[MAX_MON_TRAITS]; uq4_12_t holdEffectModifier; uq4_12_t modifier = UQ_4_12(1.0); @@ -7412,13 +7719,13 @@ static inline u32 CalcMoveBasePowerAfterModifiers(struct DamageContext *ctx) modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); if (GetMoveEffect(ctx->chosenMove) == EFFECT_ME_FIRST) modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); - if (IsBattlerTerrainAffected(battlerAtk, ctx->abilityAtk, ctx->holdEffectAtk, STATUS_FIELD_GRASSY_TERRAIN) && moveType == TYPE_GRASS) + if (IsBattlerTerrainAffected(battlerAtk, ctx->holdEffectAtk, STATUS_FIELD_GRASSY_TERRAIN) && moveType == TYPE_GRASS) modifier = uq4_12_multiply(modifier, (B_TERRAIN_TYPE_BOOST >= GEN_8 ? UQ_4_12(1.3) : UQ_4_12(1.5))); - if (IsBattlerTerrainAffected(battlerDef, ctx->abilityDef, ctx->holdEffectDef, STATUS_FIELD_MISTY_TERRAIN) && moveType == TYPE_DRAGON) + if (IsBattlerTerrainAffected(battlerDef, ctx->holdEffectDef, STATUS_FIELD_MISTY_TERRAIN) && moveType == TYPE_DRAGON) modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); - if (IsBattlerTerrainAffected(battlerAtk, ctx->abilityAtk, ctx->holdEffectAtk, STATUS_FIELD_ELECTRIC_TERRAIN) && moveType == TYPE_ELECTRIC) + if (IsBattlerTerrainAffected(battlerAtk, ctx->holdEffectAtk, STATUS_FIELD_ELECTRIC_TERRAIN) && moveType == TYPE_ELECTRIC) modifier = uq4_12_multiply(modifier, (B_TERRAIN_TYPE_BOOST >= GEN_8 ? UQ_4_12(1.3) : UQ_4_12(1.5))); - if (IsBattlerTerrainAffected(battlerAtk, ctx->abilityAtk, ctx->holdEffectAtk, STATUS_FIELD_PSYCHIC_TERRAIN) && moveType == TYPE_PSYCHIC) + if (IsBattlerTerrainAffected(battlerAtk, ctx->holdEffectAtk, STATUS_FIELD_PSYCHIC_TERRAIN) && moveType == TYPE_PSYCHIC) modifier = uq4_12_multiply(modifier, (B_TERRAIN_TYPE_BOOST >= GEN_8 ? UQ_4_12(1.3) : UQ_4_12(1.5))); if (IsFieldMudSportAffected(ctx->moveType)) modifier = uq4_12_multiply(modifier, UQ_4_12(GetConfig(CONFIG_SPORT_DMG_REDUCTION) >= GEN_5 ? 0.33 : 0.5)); @@ -7426,105 +7733,60 @@ static inline u32 CalcMoveBasePowerAfterModifiers(struct DamageContext *ctx) modifier = uq4_12_multiply(modifier, UQ_4_12(GetConfig(CONFIG_SPORT_DMG_REDUCTION) >= GEN_5 ? 0.33 : 0.5)); // attacker's abilities - switch (ctx->abilityAtk) + STORE_BATTLER_TRAITS(battlerAtk); + + if (SearchTraits(battlerTraits, ABILITY_TECHNICIAN) && basePower <= 60) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + if (SearchTraits(battlerTraits, ABILITY_FLARE_BOOST) && gBattleMons[battlerAtk].status1 & STATUS1_BURN && IsBattleMoveSpecial(move)) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + if (SearchTraits(battlerTraits, ABILITY_TOXIC_BOOST) && gBattleMons[battlerAtk].status1 & STATUS1_PSN_ANY && IsBattleMovePhysical(move)) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + if (SearchTraits(battlerTraits, ABILITY_RECKLESS) && (moveEffect == EFFECT_RECOIL || moveEffect == EFFECT_RECOIL_IF_MISS)) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.2)); + if (SearchTraits(battlerTraits, ABILITY_IRON_FIST) && IsPunchingMove(move)) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.2)); + if (SearchTraits(battlerTraits, ABILITY_SHEER_FORCE) && MoveIsAffectedBySheerForce(move)) + {modifier = uq4_12_multiply(modifier, UQ_4_12(1.3));} + if (SearchTraits(battlerTraits, ABILITY_SAND_FORCE) && (moveType == TYPE_STEEL || moveType == TYPE_ROCK || moveType == TYPE_GROUND) + && ctx->weather & B_WEATHER_SANDSTORM) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); + if (SearchTraits(battlerTraits, ABILITY_RIVALRY)) { - case ABILITY_TECHNICIAN: - if (basePower <= 60) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); - break; - case ABILITY_FLARE_BOOST: - if (gBattleMons[battlerAtk].status1 & STATUS1_BURN && IsBattleMoveSpecial(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); - break; - case ABILITY_TOXIC_BOOST: - if (gBattleMons[battlerAtk].status1 & STATUS1_PSN_ANY && IsBattleMovePhysical(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); - break; - case ABILITY_RECKLESS: - if (moveEffect == EFFECT_RECOIL || moveEffect == EFFECT_RECOIL_IF_MISS) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.2)); - break; - case ABILITY_IRON_FIST: - if (IsPunchingMove(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.2)); - break; - case ABILITY_SHEER_FORCE: - if (MoveIsAffectedBySheerForce(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); - break; - case ABILITY_SAND_FORCE: - if ((moveType == TYPE_STEEL || moveType == TYPE_ROCK || moveType == TYPE_GROUND) - && ctx->weather & B_WEATHER_SANDSTORM) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); - break; - case ABILITY_RIVALRY: if (AreBattlersOfSameGender(battlerAtk, battlerDef)) modifier = uq4_12_multiply(modifier, UQ_4_12(1.25)); else if (AreBattlersOfOppositeGender(battlerAtk, battlerDef)) modifier = uq4_12_multiply(modifier, UQ_4_12(0.75)); - break; - case ABILITY_ANALYTIC: - if (IsLastMonToMove(battlerAtk) && move != MOVE_FUTURE_SIGHT && move != MOVE_DOOM_DESIRE) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); - break; - case ABILITY_TOUGH_CLAWS: - if (IsMoveMakingContact(battlerAtk, battlerDef, ctx->abilityAtk, ctx->holdEffectAtk, ctx->move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); - break; - case ABILITY_STRONG_JAW: - if (IsBitingMove(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); - break; - case ABILITY_MEGA_LAUNCHER: - if (IsPulseMove(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); - break; - case ABILITY_WATER_BUBBLE: - if (moveType == TYPE_WATER) - modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); - break; - case ABILITY_STEELWORKER: - if (moveType == TYPE_STEEL) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); - break; - case ABILITY_PIXILATE: - if (moveType == TYPE_FAIRY && gBattleStruct->battlerState[battlerAtk].ateBoost) - modifier = uq4_12_multiply(modifier, UQ_4_12(GetConfig(CONFIG_ATE_MULTIPLIER) >= GEN_7 ? 1.2 : 1.3)); - break; - case ABILITY_GALVANIZE: - if (moveType == TYPE_ELECTRIC && gBattleStruct->battlerState[battlerAtk].ateBoost) - modifier = uq4_12_multiply(modifier, UQ_4_12(GetConfig(CONFIG_ATE_MULTIPLIER) >= GEN_7 ? 1.2 : 1.3)); - break; - case ABILITY_REFRIGERATE: - if (moveType == TYPE_ICE && gBattleStruct->battlerState[battlerAtk].ateBoost) - modifier = uq4_12_multiply(modifier, UQ_4_12(GetConfig(CONFIG_ATE_MULTIPLIER) >= GEN_7 ? 1.2 : 1.3)); - break; - case ABILITY_AERILATE: - if (moveType == TYPE_FLYING && gBattleStruct->battlerState[battlerAtk].ateBoost) - modifier = uq4_12_multiply(modifier, UQ_4_12(GetConfig(CONFIG_ATE_MULTIPLIER) >= GEN_7 ? 1.2 : 1.3)); - break; - case ABILITY_NORMALIZE: - if (moveType == TYPE_NORMAL && gBattleStruct->battlerState[battlerAtk].ateBoost && GetConfig(CONFIG_ATE_MULTIPLIER) >= GEN_7) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.2)); - break; - case ABILITY_PUNK_ROCK: - if (IsSoundMove(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); - break; - case ABILITY_STEELY_SPIRIT: - if (moveType == TYPE_STEEL) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); - break; - case ABILITY_SHARPNESS: - if (IsSlicingMove(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); - break; - case ABILITY_SUPREME_OVERLORD: - modifier = uq4_12_multiply(modifier, GetSupremeOverlordModifier(battlerAtk)); - break; - default: - break; } + if (SearchTraits(battlerTraits, ABILITY_ANALYTIC) && IsLastMonToMove(battlerAtk) && move != MOVE_FUTURE_SIGHT && move != MOVE_DOOM_DESIRE) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); + if (SearchTraits(battlerTraits, ABILITY_TOUGH_CLAWS) && IsMoveMakingContact(battlerAtk, battlerDef, ctx->holdEffectAtk, ctx->move)) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); + if (SearchTraits(battlerTraits, ABILITY_STRONG_JAW) && IsBitingMove(move)) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + if (SearchTraits(battlerTraits, ABILITY_MEGA_LAUNCHER) && IsPulseMove(move)) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + if (SearchTraits(battlerTraits, ABILITY_WATER_BUBBLE) && moveType == TYPE_WATER) + modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); + if (SearchTraits(battlerTraits, ABILITY_STEELWORKER) && moveType == TYPE_STEEL) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + if (SearchTraits(battlerTraits, ABILITY_PIXILATE) && moveType == TYPE_FAIRY && gBattleStruct->battlerState[battlerAtk].ateBoost) + modifier = uq4_12_multiply(modifier, UQ_4_12(GetConfig(CONFIG_ATE_MULTIPLIER) >= GEN_7 ? 1.2 : 1.3)); + if (SearchTraits(battlerTraits, ABILITY_GALVANIZE) && moveType == TYPE_ELECTRIC && gBattleStruct->battlerState[battlerAtk].ateBoost) + modifier = uq4_12_multiply(modifier, UQ_4_12(GetConfig(CONFIG_ATE_MULTIPLIER) >= GEN_7 ? 1.2 : 1.3)); + if (SearchTraits(battlerTraits, ABILITY_REFRIGERATE) && moveType == TYPE_ICE && gBattleStruct->battlerState[battlerAtk].ateBoost) + modifier = uq4_12_multiply(modifier, UQ_4_12(GetConfig(CONFIG_ATE_MULTIPLIER) >= GEN_7 ? 1.2 : 1.3)); + if (SearchTraits(battlerTraits, ABILITY_AERILATE) && moveType == TYPE_FLYING && gBattleStruct->battlerState[battlerAtk].ateBoost) + modifier = uq4_12_multiply(modifier, UQ_4_12(GetConfig(CONFIG_ATE_MULTIPLIER) >= GEN_7 ? 1.2 : 1.3)); + if (SearchTraits(battlerTraits, ABILITY_NORMALIZE) && moveType == TYPE_NORMAL && gBattleStruct->battlerState[battlerAtk].ateBoost && GetConfig(CONFIG_ATE_MULTIPLIER) >= GEN_7) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.2)); + if (SearchTraits(battlerTraits, ABILITY_PUNK_ROCK) && IsSoundMove(move)) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); + if (SearchTraits(battlerTraits, ABILITY_STEELY_SPIRIT) && moveType == TYPE_STEEL) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + if (SearchTraits(battlerTraits, ABILITY_SHARPNESS) && IsSlicingMove(move)) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + if (SearchTraits(battlerTraits, ABILITY_SUPREME_OVERLORD)) + modifier = uq4_12_multiply(modifier, GetSupremeOverlordModifier(battlerAtk)); // field abilities if ((IsAbilityOnField(ABILITY_DARK_AURA) && moveType == TYPE_DARK) @@ -7539,42 +7801,41 @@ static inline u32 CalcMoveBasePowerAfterModifiers(struct DamageContext *ctx) // attacker partner's abilities if (IsBattlerAlive(BATTLE_PARTNER(battlerAtk))) { - switch (GetBattlerAbility(BATTLE_PARTNER(battlerAtk))) - { - case ABILITY_BATTERY: - if (IsBattleMoveSpecial(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); - break; - case ABILITY_POWER_SPOT: + STORE_BATTLER_TRAITS(BATTLE_PARTNER(battlerAtk)); + + if (SearchTraits(battlerTraits, ABILITY_BATTERY) + && IsBattleMoveSpecial(move)) modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); - break; - case ABILITY_STEELY_SPIRIT: - if (moveType == TYPE_STEEL) + + if (SearchTraits(battlerTraits, ABILITY_POWER_SPOT)) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); + + if (SearchTraits(battlerTraits, ABILITY_STEELY_SPIRIT) + && moveType == TYPE_STEEL) modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); - break; - default: - break; - } } // target's abilities - switch (ctx->abilityDef) + STORE_BATTLER_TRAITS(ctx->battlerDef); + + if(SearchTraits(battlerTraits, ABILITY_HEATPROOF) + && moveType == TYPE_FIRE) { - case ABILITY_HEATPROOF: - case ABILITY_WATER_BUBBLE: - if (moveType == TYPE_FIRE) - { - modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); - if (ctx->updateFlags) - RecordAbilityBattle(battlerDef, ctx->abilityDef); - } - break; - case ABILITY_DRY_SKIN: - if (moveType == TYPE_FIRE) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.25)); - break; - default: - break; + modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); + if (ctx->updateFlags) + RecordAbilityBattle(battlerDef, ABILITY_HEATPROOF); + } + if(SearchTraits(battlerTraits, ABILITY_WATER_BUBBLE) + && moveType == TYPE_FIRE) + { + modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); + if (ctx->updateFlags) + RecordAbilityBattle(battlerDef, ABILITY_WATER_BUBBLE); + } + if(SearchTraits(battlerTraits, ABILITY_DRY_SKIN) + && moveType == TYPE_FIRE) + { + modifier = uq4_12_multiply(modifier, UQ_4_12(1.25)); } holdEffectParamAtk = GetBattlerHoldEffectParam(battlerAtk); @@ -7696,6 +7957,8 @@ static inline u32 CalcAttackStat(struct DamageContext *ctx) u32 move = ctx->move; enum Type moveType = ctx->moveType; enum BattleMoveEffects moveEffect = GetMoveEffect(move); + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battlerAtk); atkBaseSpeciesId = GET_BASE_SPECIES_ID(gBattleMons[battlerAtk].species); @@ -7747,7 +8010,7 @@ static inline u32 CalcAttackStat(struct DamageContext *ctx) if (ctx->isCrit && atkStage < DEFAULT_STAT_STAGE) atkStage = DEFAULT_STAT_STAGE; // pokemon with unaware ignore attack stat changes while taking damage - if (ctx->abilityDef == ABILITY_UNAWARE) + if (BattlerHasTrait(battlerDef, ABILITY_UNAWARE)) atkStage = DEFAULT_STAT_STAGE; atkStat *= gStatStageRatios[atkStage][0]; @@ -7760,113 +8023,87 @@ static inline u32 CalcAttackStat(struct DamageContext *ctx) return uq4_12_multiply_by_int_half_down(ApplyOffensiveBadgeBoost(modifier, battlerAtk, move), atkStat); // attacker's abilities - switch (ctx->abilityAtk) - { - case ABILITY_HUGE_POWER: - case ABILITY_PURE_POWER: - if (IsBattleMovePhysical(move)) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0)); - break; - case ABILITY_SLOW_START: - if (gDisableStructs[battlerAtk].slowStartTimer > 0) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.5)); - break; - case ABILITY_SOLAR_POWER: - if (IsBattleMoveSpecial(move) && IsBattlerWeatherAffected(battlerAtk, B_WEATHER_SUN)) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); - break; - case ABILITY_DEFEATIST: - if (gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 2)) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.5)); - break; - case ABILITY_FLASH_FIRE: - if (moveType == TYPE_FIRE && gDisableStructs[battlerAtk].flashFireBoosted) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); - break; - case ABILITY_SWARM: - if (moveType == TYPE_BUG && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3)) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); - break; - case ABILITY_TORRENT: - if (moveType == TYPE_WATER && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3)) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); - break; - case ABILITY_BLAZE: - if (moveType == TYPE_FIRE && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3)) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); - break; - case ABILITY_OVERGROW: - if (moveType == TYPE_GRASS && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3)) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); - break; - case ABILITY_PLUS: - if (IsBattleMoveSpecial(move) && IsBattlerAlive(BATTLE_PARTNER(battlerAtk))) - { - enum Ability partnerAbility = GetBattlerAbility(BATTLE_PARTNER(battlerAtk)); - if (partnerAbility == ABILITY_MINUS - || (B_PLUS_MINUS_INTERACTION >= GEN_5 && partnerAbility == ABILITY_PLUS)) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); - } - break; - case ABILITY_MINUS: - if (IsBattleMoveSpecial(move) && IsBattlerAlive(BATTLE_PARTNER(battlerAtk))) - { - enum Ability partnerAbility = GetBattlerAbility(BATTLE_PARTNER(battlerAtk)); - if (partnerAbility == ABILITY_PLUS - || (B_PLUS_MINUS_INTERACTION >= GEN_5 && partnerAbility == ABILITY_MINUS)) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); - } - break; - case ABILITY_FLOWER_GIFT: - if (gBattleMons[battlerAtk].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(battlerAtk, B_WEATHER_SUN) && IsBattleMovePhysical(move)) + if (SearchTraits(battlerTraits, ABILITY_HUGE_POWER) + && IsBattleMovePhysical(move)) + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0)); + if (SearchTraits(battlerTraits, ABILITY_PURE_POWER) + && IsBattleMovePhysical(move)) + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0)); + if (SearchTraits(battlerTraits, ABILITY_SLOW_START) + && gDisableStructs[battlerAtk].slowStartTimer > 0) + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.5)); + if (SearchTraits(battlerTraits, ABILITY_SOLAR_POWER) + && IsBattleMoveSpecial(move) && IsBattlerWeatherAffected(battlerAtk, B_WEATHER_SUN)) + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); + if (SearchTraits(battlerTraits, ABILITY_DEFEATIST) + && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 2)) + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.5)); + if (SearchTraits(battlerTraits, ABILITY_FLASH_FIRE) + && moveType == TYPE_FIRE && gDisableStructs[battlerAtk].flashFireBoosted) + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); + if (SearchTraits(battlerTraits, ABILITY_SWARM) + && moveType == TYPE_BUG && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3)) + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); + if (SearchTraits(battlerTraits, ABILITY_TORRENT) + && moveType == TYPE_WATER && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3)) + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); + if (SearchTraits(battlerTraits, ABILITY_BLAZE) + && moveType == TYPE_FIRE && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3)) + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); + if (SearchTraits(battlerTraits, ABILITY_OVERGROW) + && moveType == TYPE_GRASS && gBattleMons[battlerAtk].hp <= (gBattleMons[battlerAtk].maxHP / 3)) + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); + if (SearchTraits(battlerTraits, ABILITY_PLUS) + && IsBattleMoveSpecial(move) && IsBattlerAlive(BATTLE_PARTNER(battlerAtk)) + && (BattlerHasTrait(BATTLE_PARTNER(battlerAtk), ABILITY_MINUS) + || (B_PLUS_MINUS_INTERACTION >= GEN_5 && BattlerHasTrait(BATTLE_PARTNER(battlerAtk), ABILITY_PLUS)))) + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); + if (SearchTraits(battlerTraits, ABILITY_MINUS) + && IsBattleMoveSpecial(move) && IsBattlerAlive(BATTLE_PARTNER(battlerAtk)) + && (BattlerHasTrait(BATTLE_PARTNER(battlerAtk), ABILITY_PLUS) + || (B_PLUS_MINUS_INTERACTION >= GEN_5 && BattlerHasTrait(BATTLE_PARTNER(battlerAtk), ABILITY_MINUS)))) + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); + if (SearchTraits(battlerTraits, ABILITY_FLOWER_GIFT) + && gBattleMons[battlerAtk].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(battlerAtk, B_WEATHER_SUN) && IsBattleMovePhysical(move)) modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); - break; - case ABILITY_HUSTLE: - if (IsBattleMovePhysical(move)) + if (SearchTraits(battlerTraits, ABILITY_HUSTLE) + && IsBattleMovePhysical(move)) modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); - break; - case ABILITY_STAKEOUT: - if (gDisableStructs[battlerDef].isFirstTurn == 2) // just switched in + if (SearchTraits(battlerTraits, ABILITY_STAKEOUT) + && gDisableStructs[battlerDef].isFirstTurn == 2) // just switched in modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0)); - break; - case ABILITY_GUTS: - if (gBattleMons[battlerAtk].status1 & STATUS1_ANY && IsBattleMovePhysical(move)) + if (SearchTraits(battlerTraits, ABILITY_GUTS) + && gBattleMons[battlerAtk].status1 & STATUS1_ANY && IsBattleMovePhysical(move)) modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); - break; - case ABILITY_TRANSISTOR: - if (moveType == TYPE_ELECTRIC) + if (SearchTraits(battlerTraits, ABILITY_TRANSISTOR) + && moveType == TYPE_ELECTRIC) { if (GetConfig(CONFIG_TRANSISTOR_BOOST) >= GEN_9) modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); else modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); } - break; - case ABILITY_DRAGONS_MAW: - if (moveType == TYPE_DRAGON) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); - break; - case ABILITY_GORILLA_TACTICS: - if (IsBattleMovePhysical(move)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); - break; - case ABILITY_ROCKY_PAYLOAD: - if (moveType == TYPE_ROCK) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); - break; - case ABILITY_PROTOSYNTHESIS: - if (!(gBattleMons[battlerAtk].volatiles.transformed)) + if (SearchTraits(battlerTraits, ABILITY_DRAGONS_MAW) + && moveType == TYPE_DRAGON) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + if (SearchTraits(battlerTraits, ABILITY_GORILLA_TACTICS) + && IsBattleMovePhysical(move)) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + if (SearchTraits(battlerTraits, ABILITY_ROCKY_PAYLOAD) + && moveType == TYPE_ROCK) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); + if (SearchTraits(battlerTraits, ABILITY_PROTOSYNTHESIS) + && !(gBattleMons[battlerAtk].volatiles.transformed)) + { + enum Stat atkHighestStat = GetParadoxBoostedStatId(battlerAtk); + if (((ctx->weather & B_WEATHER_SUN) && HasWeatherEffect()) || gDisableStructs[battlerAtk].boosterEnergyActivated) { - enum Stat atkHighestStat = GetParadoxBoostedStatId(battlerAtk); - if (((ctx->weather & B_WEATHER_SUN) && HasWeatherEffect()) || gDisableStructs[battlerAtk].boosterEnergyActivated) - { - if ((IsBattleMovePhysical(move) && atkHighestStat == STAT_ATK) || (IsBattleMoveSpecial(move) && atkHighestStat == STAT_SPATK)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); - } + if ((IsBattleMovePhysical(move) && atkHighestStat == STAT_ATK) || (IsBattleMoveSpecial(move) && atkHighestStat == STAT_SPATK)) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); } - break; - case ABILITY_QUARK_DRIVE: - if (!(gBattleMons[battlerAtk].volatiles.transformed)) + } + if (SearchTraits(battlerTraits, ABILITY_QUARK_DRIVE) + && !(gBattleMons[battlerAtk].volatiles.transformed)) { enum Stat atkHighestStat = GetParadoxBoostedStatId(battlerAtk); if (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gDisableStructs[battlerAtk].boosterEnergyActivated) @@ -7875,54 +8112,37 @@ static inline u32 CalcAttackStat(struct DamageContext *ctx) modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); } } - break; - case ABILITY_ORICHALCUM_PULSE: - if ((ctx->weather & B_WEATHER_SUN) && HasWeatherEffect() && IsBattleMovePhysical(move) + if (SearchTraits(battlerTraits, ABILITY_ORICHALCUM_PULSE) + && (ctx->weather & B_WEATHER_SUN) && HasWeatherEffect() && IsBattleMovePhysical(move) && ctx->holdEffectAtk != HOLD_EFFECT_UTILITY_UMBRELLA) modifier = uq4_12_multiply(modifier, UQ_4_12(1.3333)); - break; - case ABILITY_HADRON_ENGINE: - if (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN && IsBattleMoveSpecial(move)) + if (SearchTraits(battlerTraits, ABILITY_HADRON_ENGINE) + && (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN && IsBattleMoveSpecial(move))) modifier = uq4_12_multiply(modifier, UQ_4_12(1.3333)); - break; - default: - break; - } // target's abilities - switch (ctx->abilityDef) + if (BattlerHasTrait(battlerDef, ABILITY_THICK_FAT) + && (moveType == TYPE_FIRE || moveType == TYPE_ICE)) { - case ABILITY_THICK_FAT: - if (moveType == TYPE_FIRE || moveType == TYPE_ICE) - { - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.5)); - if (ctx->updateFlags) - RecordAbilityBattle(battlerDef, ABILITY_THICK_FAT); - } - break; - case ABILITY_PURIFYING_SALT: - if (moveType == TYPE_GHOST) - { - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.5)); - if (ctx->updateFlags) - RecordAbilityBattle(battlerDef, ABILITY_PURIFYING_SALT); - } - break; - default: - break; + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.5)); + if (ctx->updateFlags) + RecordAbilityBattle(battlerDef, ABILITY_THICK_FAT); + } + if (BattlerHasTrait(battlerDef, ABILITY_PURIFYING_SALT) + && moveType == TYPE_GHOST) + { + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.5)); + if (ctx->updateFlags) + RecordAbilityBattle(battlerDef, ABILITY_PURIFYING_SALT); } // ally's abilities if (IsBattlerAlive(BATTLE_PARTNER(battlerAtk))) { - switch (GetBattlerAbility(BATTLE_PARTNER(battlerAtk))) + if (BattlerHasTrait(BATTLE_PARTNER(battlerAtk), ABILITY_FLOWER_GIFT) + && gBattleMons[BATTLE_PARTNER(battlerAtk)].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(BATTLE_PARTNER(battlerAtk), B_WEATHER_SUN) && IsBattleMovePhysical(move)) { - case ABILITY_FLOWER_GIFT: - if (gBattleMons[BATTLE_PARTNER(battlerAtk)].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(BATTLE_PARTNER(battlerAtk), B_WEATHER_SUN) && IsBattleMovePhysical(move)) modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); - break; - default: - break; } } @@ -7991,6 +8211,7 @@ static inline u32 CalcDefenseStat(struct DamageContext *ctx) u32 battlerDef = ctx->battlerDef; u32 move = ctx->move; enum BattleMoveEffects moveEffect = GetMoveEffect(move); + enum Ability battlerTraits[MAX_MON_TRAITS]; def = gBattleMons[battlerDef].defense; spDef = gBattleMons[battlerDef].spDefense; @@ -8033,7 +8254,7 @@ static inline u32 CalcDefenseStat(struct DamageContext *ctx) if (ctx->isCrit && defStage > DEFAULT_STAT_STAGE) defStage = DEFAULT_STAT_STAGE; // pokemon with unaware ignore defense stat changes while dealing damage - if (ctx->abilityAtk == ABILITY_UNAWARE) + if (BattlerHasTrait(ctx->battlerAtk, ABILITY_UNAWARE)) defStage = DEFAULT_STAT_STAGE; // certain moves also ignore stat changes if (MoveIgnoresDefenseEvasionStages(move)) @@ -8049,70 +8270,56 @@ static inline u32 CalcDefenseStat(struct DamageContext *ctx) return uq4_12_multiply_by_int_half_down(ApplyDefensiveBadgeBoost(modifier, battlerDef, move), defStat); // target's abilities - switch (ctx->abilityDef) + STORE_BATTLER_TRAITS(battlerDef); + + if (SearchTraits(battlerTraits, ABILITY_MARVEL_SCALE) + && gBattleMons[battlerDef].status1 & STATUS1_ANY && usesDefStat) { - case ABILITY_MARVEL_SCALE: - if (gBattleMons[battlerDef].status1 & STATUS1_ANY && usesDefStat) - { - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); - if (ctx->updateFlags) - RecordAbilityBattle(battlerDef, ABILITY_MARVEL_SCALE); - } - break; - case ABILITY_FUR_COAT: - if (usesDefStat) - { - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0)); - if (ctx->updateFlags) - RecordAbilityBattle(battlerDef, ABILITY_FUR_COAT); - } - break; - case ABILITY_GRASS_PELT: - if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN && usesDefStat) - { - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); - if (ctx->updateFlags) - RecordAbilityBattle(battlerDef, ABILITY_GRASS_PELT); - } - break; - case ABILITY_FLOWER_GIFT: - if (gBattleMons[battlerDef].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(battlerDef, B_WEATHER_SUN) && !usesDefStat) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); - break; - case ABILITY_PROTOSYNTHESIS: - { - enum Stat defHighestStat = GetParadoxBoostedStatId(battlerDef); - if (((ctx->weather & B_WEATHER_SUN && HasWeatherEffect()) || gDisableStructs[battlerDef].boosterEnergyActivated) - && ((IsBattleMovePhysical(move) && defHighestStat == STAT_DEF) || (IsBattleMoveSpecial(move) && defHighestStat == STAT_SPDEF)) - && !(gBattleMons[battlerDef].volatiles.transformed)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); - } - break; - case ABILITY_QUARK_DRIVE: - { - u32 defHighestStat = GetParadoxBoostedStatId(battlerDef); - if ((gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gDisableStructs[battlerDef].boosterEnergyActivated) - && ((IsBattleMovePhysical(move) && defHighestStat == STAT_DEF) || (IsBattleMoveSpecial(move) && defHighestStat == STAT_SPDEF)) - && !(gBattleMons[battlerDef].volatiles.transformed)) - modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); - } - break; - default: - break; + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); + if (ctx->updateFlags) + RecordAbilityBattle(battlerDef, ABILITY_MARVEL_SCALE); } - - // ally's abilities - if (IsBattlerAlive(BATTLE_PARTNER(battlerDef))) + if (SearchTraits(battlerTraits, ABILITY_FUR_COAT) + && usesDefStat) { - switch (GetBattlerAbility(BATTLE_PARTNER(battlerDef))) - { - case ABILITY_FLOWER_GIFT: - if (gBattleMons[BATTLE_PARTNER(battlerDef)].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(BATTLE_PARTNER(battlerDef), B_WEATHER_SUN) && !usesDefStat) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); - break; - default: - break; - } + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0)); + if (ctx->updateFlags) + RecordAbilityBattle(battlerDef, ABILITY_FUR_COAT); + } + if (SearchTraits(battlerTraits, ABILITY_GRASS_PELT) + && gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN && usesDefStat) + { + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); + if (ctx->updateFlags) + RecordAbilityBattle(battlerDef, ABILITY_GRASS_PELT); + } + if (SearchTraits(battlerTraits, ABILITY_FLOWER_GIFT) + && gBattleMons[battlerDef].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(battlerDef, B_WEATHER_SUN) && !usesDefStat) + { + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); + } + if(SearchTraits(battlerTraits, ABILITY_PROTOSYNTHESIS)) + { + enum Stat defHighestStat = GetParadoxBoostedStatId(battlerDef); + if (((ctx->weather & B_WEATHER_SUN && HasWeatherEffect()) || gDisableStructs[battlerDef].boosterEnergyActivated) + && ((IsBattleMovePhysical(move) && defHighestStat == STAT_DEF) || (IsBattleMoveSpecial(move) && defHighestStat == STAT_SPDEF)) + && !(gBattleMons[battlerDef].volatiles.transformed)) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); + } + if(SearchTraits(battlerTraits, ABILITY_QUARK_DRIVE)) + { + u32 defHighestStat = GetParadoxBoostedStatId(battlerDef); + if ((gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gDisableStructs[battlerDef].boosterEnergyActivated) + && ((IsBattleMovePhysical(move) && defHighestStat == STAT_DEF) || (IsBattleMoveSpecial(move) && defHighestStat == STAT_SPDEF)) + && !(gBattleMons[battlerDef].volatiles.transformed)) + modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); + } + // ally's abilities + if (IsBattlerAlive(BATTLE_PARTNER(battlerDef)) + && BattlerHasTrait(BATTLE_PARTNER(battlerDef), ABILITY_FLOWER_GIFT)) + { + if (gBattleMons[BATTLE_PARTNER(battlerDef)].species == SPECIES_CHERRIM_SUNSHINE && IsBattlerWeatherAffected(BATTLE_PARTNER(battlerDef), B_WEATHER_SUN) && !usesDefStat) + modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); } // Ruin field effects @@ -8194,10 +8401,10 @@ static inline uq4_12_t GetSameTypeAttackBonusModifier(struct DamageContext *ctx) if (ctx->moveType == TYPE_MYSTERY) return UQ_4_12(1.0); else if (gBattleStruct->pledgeMove && IS_BATTLER_OF_TYPE(BATTLE_PARTNER(ctx->battlerAtk), ctx->moveType)) - return (ctx->abilityAtk == ABILITY_ADAPTABILITY) ? UQ_4_12(2.0) : UQ_4_12(1.5); + return (BattlerHasTrait(ctx->battlerAtk, ABILITY_ADAPTABILITY)) ? UQ_4_12(2.0) : UQ_4_12(1.5); else if (!IS_BATTLER_OF_TYPE(ctx->battlerAtk, ctx->moveType) || ctx->move == MOVE_STRUGGLE || ctx->move == MOVE_NONE) return UQ_4_12(1.0); - return (ctx->abilityAtk == ABILITY_ADAPTABILITY) ? UQ_4_12(2.0) : UQ_4_12(1.5); + return (BattlerHasTrait(ctx->battlerAtk, ABILITY_ADAPTABILITY)) ? UQ_4_12(2.0) : UQ_4_12(1.5); } // Utility Umbrella holders take normal damage from what would be rain- and sun-weakened attacks. @@ -8232,7 +8439,7 @@ static inline uq4_12_t GetBurnOrFrostBiteModifier(struct DamageContext *ctx) if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_BURN && IsBattleMovePhysical(ctx->move) && (GetConfig(CONFIG_BURN_FACADE_DMG) < GEN_6 || moveEffect != EFFECT_FACADE) - && ctx->abilityAtk != ABILITY_GUTS) + && !BattlerHasTrait(ctx->battlerAtk, ABILITY_GUTS)) return UQ_4_12(0.5); if (gBattleMons[ctx->battlerAtk].status1 & STATUS1_FROSTBITE && IsBattleMoveSpecial(ctx->move) @@ -8301,14 +8508,14 @@ static inline uq4_12_t GetScreensModifier(struct DamageContext *ctx) bool32 reflect = (sideStatus & SIDE_STATUS_REFLECT) && IsBattleMovePhysical(ctx->move); bool32 auroraVeil = sideStatus & SIDE_STATUS_AURORA_VEIL; - if (ctx->isCrit || gProtectStructs[ctx->battlerAtk].confusionSelfDmg) + if (ctx->isCrit || BattlerHasTrait(ctx->battlerAtk, ABILITY_INFILTRATOR) || gProtectStructs[ctx->battlerAtk].confusionSelfDmg) { return UQ_4_12(1.0); } - if (ctx->abilityAtk == ABILITY_INFILTRATOR && !IsBattlerAlly(ctx->battlerAtk, ctx->battlerDef)) + if (BattlerHasTrait(ctx->battlerAtk, ABILITY_INFILTRATOR && !IsBattlerAlly(ctx->battlerAtk, ctx->battlerDef))) { if (ctx->updateFlags) - RecordAbilityBattle(ctx->battlerAtk, ctx->abilityDef); + RecordAbilityBattle(ctx->battlerAtk, ABILITY_INFILTRATOR); return UQ_4_12(1.0); } if (reflect || lightScreen || auroraVeil) @@ -8327,82 +8534,81 @@ static inline uq4_12_t GetCollisionCourseElectroDriftModifier(u32 move, uq4_12_t static inline uq4_12_t GetAttackerAbilitiesModifier(u32 battlerAtk, uq4_12_t typeEffectivenessModifier, bool32 isCrit, enum Ability abilityAtk) { - switch (abilityAtk) - { - case ABILITY_NEUROFORCE: - if (typeEffectivenessModifier >= UQ_4_12(2.0)) - return UQ_4_12(1.25); - break; - case ABILITY_SNIPER: - if (isCrit) - return UQ_4_12(1.5); - break; - case ABILITY_TINTED_LENS: - if (typeEffectivenessModifier <= UQ_4_12(0.5)) - return UQ_4_12(2.0); - break; - default: - break; - } + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battlerAtk); + + if (SearchTraits(battlerTraits, ABILITY_NEUROFORCE) + && typeEffectivenessModifier >= UQ_4_12(2.0)) + return UQ_4_12(1.25); + + if (SearchTraits(battlerTraits, ABILITY_SNIPER) + && isCrit) + return UQ_4_12(1.5); + + if (SearchTraits(battlerTraits, ABILITY_TINTED_LENS) + && typeEffectivenessModifier <= UQ_4_12(0.5)) + return UQ_4_12(2.0); + return UQ_4_12(1.0); } static inline uq4_12_t GetDefenderAbilitiesModifier(struct DamageContext *ctx) { - bool32 recordAbility = FALSE; uq4_12_t modifier = UQ_4_12(1.0); + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(ctx->battlerDef); - switch (ctx->abilityDef) + if (SearchTraits(battlerTraits, ABILITY_MULTISCALE) && IsBattlerAtMaxHp(ctx->battlerDef)) { - case ABILITY_MULTISCALE: - case ABILITY_SHADOW_SHIELD: - if (IsBattlerAtMaxHp(ctx->battlerDef)) - { - modifier = UQ_4_12(0.5); - recordAbility = TRUE; - } - break; - case ABILITY_FILTER: - case ABILITY_SOLID_ROCK: - case ABILITY_PRISM_ARMOR: - if (ctx->typeEffectivenessModifier >= UQ_4_12(2.0)) - { - modifier = UQ_4_12(0.75); - recordAbility = TRUE; - } - break; - case ABILITY_FLUFFY: - if (ctx->moveType == TYPE_FIRE && !IsMoveMakingContact(ctx->battlerAtk, ctx->battlerDef, ABILITY_NONE, ctx->holdEffectAtk, ctx->move)) - { - modifier = UQ_4_12(2.0); - recordAbility = TRUE; - } - if (ctx->moveType != TYPE_FIRE && IsMoveMakingContact(ctx->battlerAtk, ctx->battlerDef, ABILITY_NONE, ctx->holdEffectAtk, ctx->move)) - { - modifier = UQ_4_12(0.5); - recordAbility = TRUE; - } - break; - case ABILITY_PUNK_ROCK: - if (IsSoundMove(ctx->move)) + RecordAbilityBattle(ctx->battlerAtk, ABILITY_MULTISCALE); + modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); + } + if (SearchTraits(battlerTraits, ABILITY_SHADOW_SHIELD) && IsBattlerAtMaxHp(ctx->battlerDef)) + { + RecordAbilityBattle(ctx->battlerAtk, ABILITY_SHADOW_SHIELD); + modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); + } + if (SearchTraits(battlerTraits, ABILITY_FILTER) && ctx->typeEffectivenessModifier >= UQ_4_12(2.0)) + { + RecordAbilityBattle(ctx->battlerAtk, ABILITY_FILTER); + modifier = uq4_12_multiply(modifier, UQ_4_12(0.75)); + } + if (SearchTraits(battlerTraits, ABILITY_SOLID_ROCK) && ctx->typeEffectivenessModifier >= UQ_4_12(2.0)) + { + RecordAbilityBattle(ctx->battlerAtk, ABILITY_SOLID_ROCK); + modifier = uq4_12_multiply(modifier, UQ_4_12(0.75)); + } + if (SearchTraits(battlerTraits, ABILITY_PRISM_ARMOR) && ctx->typeEffectivenessModifier >= UQ_4_12(2.0)) + { + RecordAbilityBattle(ctx->battlerAtk, ABILITY_PRISM_ARMOR); + modifier = uq4_12_multiply(modifier, UQ_4_12(0.75)); + } + if (SearchTraits(battlerTraits, ABILITY_FLUFFY)) + { + if (ctx->moveType == TYPE_FIRE && !IsMoveMakingContact(ctx->battlerAtk, ctx->battlerDef, ctx->holdEffectAtk, ctx->move)) { - modifier = UQ_4_12(0.5); - recordAbility = TRUE; + RecordAbilityBattle(ctx->battlerAtk, ABILITY_FLUFFY); + modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); } - break; - case ABILITY_ICE_SCALES: - if (IsBattleMoveSpecial(ctx->move)) + if (ctx->moveType != TYPE_FIRE && IsMoveMakingContact(ctx->battlerAtk, ctx->battlerDef, ctx->holdEffectAtk, ctx->move)) { - modifier = UQ_4_12(0.5); - recordAbility = TRUE; + RecordAbilityBattle(ctx->battlerAtk, ABILITY_FLUFFY); + modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); } - break; - default: - break; + } + if (SearchTraits(battlerTraits, ABILITY_PUNK_ROCK) + && IsSoundMove(ctx->move)) + { + RecordAbilityBattle(ctx->battlerAtk, ABILITY_PUNK_ROCK); + modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); } - if (recordAbility && ctx->updateFlags) - RecordAbilityBattle(ctx->battlerAtk, ctx->abilityDef); + if (SearchTraits(battlerTraits, ABILITY_ICE_SCALES) + && IsBattleMoveSpecial(ctx->move)) + { + RecordAbilityBattle(ctx->battlerAtk, ABILITY_ICE_SCALES); + modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); + } return modifier; } @@ -8412,14 +8618,9 @@ static inline uq4_12_t GetDefenderPartnerAbilitiesModifier(u32 battlerPartnerDef if (!IsBattlerAlive(battlerPartnerDef)) return UQ_4_12(1.0); - switch (GetBattlerAbility(battlerPartnerDef)) - { - case ABILITY_FRIEND_GUARD: + if (BattlerHasTrait(battlerPartnerDef, ABILITY_FRIEND_GUARD)) return UQ_4_12(0.75); - break; - default: - break; - } + return UQ_4_12(1.0); } @@ -8460,7 +8661,7 @@ static inline uq4_12_t GetDefenderItemsModifier(struct DamageContext *ctx) { if (ctx->updateFlags) gSpecialStatuses[ctx->battlerDef].berryReduced = TRUE; - return (ctx->abilityDef == ABILITY_RIPEN) ? UQ_4_12(0.25) : UQ_4_12(0.5); + return (BattlerHasTrait(ctx->battlerDef, ABILITY_RIPEN)) ? UQ_4_12(0.25) : UQ_4_12(0.5); } break; default: @@ -8750,6 +8951,8 @@ s32 CalculateMoveDamageVars(struct DamageContext *ctx) static inline void MulByTypeEffectiveness(struct DamageContext *ctx, uq4_12_t *modifier, enum Type defType) { uq4_12_t mod = GetTypeModifier(ctx->moveType, defType); + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(ctx->battlerAtk); if (mod == UQ_4_12(0.0) && ctx->holdEffectDef == HOLD_EFFECT_RING_TARGET) { @@ -8762,19 +8965,21 @@ static inline void MulByTypeEffectiveness(struct DamageContext *ctx, uq4_12_t *m mod = UQ_4_12(1.0); } else if ((ctx->moveType == TYPE_FIGHTING || ctx->moveType == TYPE_NORMAL) && defType == TYPE_GHOST - && (ctx->abilityAtk == ABILITY_SCRAPPY || ctx->abilityAtk == ABILITY_MINDS_EYE) - && mod == UQ_4_12(0.0)) + && (SearchTraits(battlerTraits, ABILITY_SCRAPPY) || SearchTraits(battlerTraits, ABILITY_MINDS_EYE)) + && mod == UQ_4_12(0.0) && !ctx->isAnticipation) // Anticipation ignores Scrappy and Minds Eye { mod = UQ_4_12(1.0); - if (ctx->updateFlags) - RecordAbilityBattle(ctx->battlerAtk, ctx->abilityAtk); + if (SearchTraits(battlerTraits, ABILITY_SCRAPPY)) + RecordAbilityBattle(ctx->battlerAtk, ABILITY_SCRAPPY); + else if (SearchTraits(battlerTraits, ABILITY_MINDS_EYE)) + RecordAbilityBattle(ctx->battlerAtk, ABILITY_MINDS_EYE); } if (ctx->moveType == TYPE_PSYCHIC && defType == TYPE_DARK && gBattleMons[ctx->battlerDef].volatiles.miracleEye && mod == UQ_4_12(0.0)) mod = UQ_4_12(1.0); if (GetMoveEffect(ctx->move) == EFFECT_SUPER_EFFECTIVE_ON_ARG && defType == GetMoveArgType(ctx->move) && !ctx->isAnticipation) mod = UQ_4_12(2.0); - if (ctx->moveType == TYPE_GROUND && defType == TYPE_FLYING && IsBattlerGrounded(ctx->battlerDef, ctx->abilityDef, ctx->holdEffectDef) && mod == UQ_4_12(0.0)) + if (ctx->moveType == TYPE_GROUND && defType == TYPE_FLYING && IsBattlerGrounded(ctx->battlerDef, ctx->holdEffectDef) && mod == UQ_4_12(0.0)) mod = UQ_4_12(1.0); if (ctx->moveType == TYPE_STELLAR && GetActiveGimmick(ctx->battlerDef) == GIMMICK_TERA) mod = UQ_4_12(2.0); @@ -8788,10 +8993,11 @@ static inline void MulByTypeEffectiveness(struct DamageContext *ctx, uq4_12_t *m if (gSpecialStatuses[ctx->battlerDef].distortedTypeMatchups || (mod > UQ_4_12(0.0) && ShouldTeraShellDistortTypeMatchups(ctx->move, ctx->battlerDef, ctx->abilityDef))) { + PushTraitStack(gBattlerTarget, ABILITY_TERA_SHELL); mod = UQ_4_12(0.5); if (ctx->updateFlags) { - RecordAbilityBattle(ctx->battlerDef, ctx->abilityDef); + RecordAbilityBattle(ctx->battlerDef, ABILITY_TERA_SHELL); gSpecialStatuses[ctx->battlerDef].distortedTypeMatchups = TRUE; gSpecialStatuses[ctx->battlerDef].teraShellAbilityDone = TRUE; } @@ -8811,8 +9017,6 @@ static inline void TryNoticeIllusionInTypeEffectiveness(u32 move, enum Type move ctx.move = ctx.chosenMove = move; ctx.moveType = moveType; ctx.updateFlags = FALSE; - ctx.abilityAtk = GetBattlerAbility(battlerAtk); - ctx.abilityDef = ABILITY_ILLUSION; ctx.holdEffectAtk = GetBattlerHoldEffect(battlerAtk); ctx.holdEffectDef = GetBattlerHoldEffect(battlerDef); @@ -8853,6 +9057,8 @@ static inline uq4_12_t CalcTypeEffectivenessMultiplierInternal(struct DamageCont u32 illusionSpecies; enum Type types[3]; GetBattlerTypes(ctx->battlerDef, FALSE, types); + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(ctx->battlerDef); MulByTypeEffectiveness(ctx, &modifier, types[0]); if (types[1] != types[0]) @@ -8871,15 +9077,18 @@ static inline uq4_12_t CalcTypeEffectivenessMultiplierInternal(struct DamageCont if (B_GLARE_GHOST < GEN_4 && ctx->move == MOVE_GLARE && IS_BATTLER_OF_TYPE(ctx->battlerDef, TYPE_GHOST)) modifier = UQ_4_12(0.0); } - else if (ctx->moveType == TYPE_GROUND && !IsBattlerGroundedInverseCheck(ctx->battlerDef, ctx->abilityDef, ctx->holdEffectDef, INVERSE_BATTLE, ctx->isAnticipation) && !(MoveIgnoresTypeIfFlyingAndUngrounded(ctx->move))) + else if (ctx->moveType == TYPE_GROUND && !IsBattlerGroundedInverseCheck(ctx->battlerDef, ctx->holdEffectDef, INVERSE_BATTLE, ctx->isAnticipation, SearchTraits(battlerTraits, ABILITY_LEVITATE) + && !MoveIgnoresTypeIfFlyingAndUngrounded(ctx->move) + && !MoveIgnoresTargetAbility(ctx->move) && (ctx->holdEffectDef == HOLD_EFFECT_ABILITY_SHIELD || !HasMoldBreakerTypeAbility(ctx->battlerAtk)))) { modifier = UQ_4_12(0.0); - if (ctx->updateFlags && ctx->abilityDef == ABILITY_LEVITATE) + if (ctx->updateFlags && SearchTraits(battlerTraits, ABILITY_LEVITATE)) { gBattleStruct->moveResultFlags[ctx->battlerDef] |= (MOVE_RESULT_MISSED | MOVE_RESULT_DOESNT_AFFECT_FOE); gLastUsedAbility = ABILITY_LEVITATE; gLastLandedMoves[ctx->battlerDef] = 0; gBattleStruct->missStringId[ctx->battlerDef] = B_MSG_GROUND_MISS; + PushTraitStack(ctx->battlerDef, ABILITY_LEVITATE); RecordAbilityBattle(ctx->battlerDef, ABILITY_LEVITATE); } } @@ -8890,7 +9099,7 @@ static inline uq4_12_t CalcTypeEffectivenessMultiplierInternal(struct DamageCont // Thousand Arrows ignores type modifiers for flying mons if (MoveIgnoresTypeIfFlyingAndUngrounded(ctx->move) - && !IsBattlerGrounded(ctx->battlerDef, ctx->abilityDef, ctx->holdEffectDef) + && !IsBattlerGrounded(ctx->battlerDef, ctx->holdEffectDef) && IS_BATTLER_OF_TYPE(ctx->battlerDef, TYPE_FLYING)) { modifier = UQ_4_12(1.0); @@ -8901,24 +9110,28 @@ static inline uq4_12_t CalcTypeEffectivenessMultiplierInternal(struct DamageCont && ctx->moveType == TYPE_GROUND && ctx->holdEffectDef == HOLD_EFFECT_IRON_BALL && IS_BATTLER_OF_TYPE(ctx->battlerDef, TYPE_FLYING) - && !IsBattlerGrounded(ctx->battlerDef, ctx->abilityDef, HOLD_EFFECT_NONE) // We want to ignore Iron Ball so skip item check + && !IsBattlerGrounded(ctx->battlerDef, HOLD_EFFECT_NONE) // We want to ignore Iron Ball so skip item check && !FlagGet(B_FLAG_INVERSE_BATTLE)) { modifier = UQ_4_12(1.0); } - if (((ctx->abilityDef == ABILITY_WONDER_GUARD && modifier <= UQ_4_12(1.0)) - || (ctx->abilityDef == ABILITY_TELEPATHY && ctx->battlerDef == BATTLE_PARTNER(ctx->battlerAtk))) + if (((SearchTraits(battlerTraits, ABILITY_WONDER_GUARD) && modifier <= UQ_4_12(1.0)) + || (SearchTraits(battlerTraits, ABILITY_TELEPATHY) && ctx->battlerDef == BATTLE_PARTNER(ctx->battlerAtk))) && GetMovePower(ctx->move) != 0) { modifier = UQ_4_12(0.0); if (ctx->updateFlags) { - gLastUsedAbility = gBattleMons[ctx->battlerDef].ability; + if(SearchTraits(battlerTraits, ABILITY_WONDER_GUARD)) + gLastUsedAbility = ABILITY_WONDER_GUARD; + else if(SearchTraits(battlerTraits, ABILITY_TELEPATHY)) + gLastUsedAbility = ABILITY_TELEPATHY; gBattleStruct->moveResultFlags[ctx->battlerDef] |= MOVE_RESULT_MISSED; gLastLandedMoves[ctx->battlerDef] = 0; gBattleStruct->missStringId[ctx->battlerDef] = B_MSG_AVOIDED_DMG; - RecordAbilityBattle(ctx->battlerDef, gBattleMons[ctx->battlerDef].ability); + PushTraitStack(ctx->battlerDef, gLastUsedAbility); + RecordAbilityBattle(ctx->battlerDef, gLastUsedAbility); } } @@ -8947,7 +9160,7 @@ uq4_12_t CalcTypeEffectivenessMultiplier(struct DamageContext *ctx) return modifier; } -uq4_12_t CalcPartyMonTypeEffectivenessMultiplier(u16 move, u16 speciesDef, enum Ability abilityDef) +uq4_12_t CalcPartyMonTypeEffectivenessMultiplier(u16 move, u16 speciesDef, struct Pokemon *mon) { uq4_12_t modifier = UQ_4_12(1.0); enum Type moveType = GetBattleMoveType(move); @@ -8958,16 +9171,25 @@ uq4_12_t CalcPartyMonTypeEffectivenessMultiplier(u16 move, u16 speciesDef, enum ctx.move = ctx.chosenMove = move; ctx.moveType = moveType; ctx.updateFlags = FALSE; - ctx.abilityDef = abilityDef; MulByTypeEffectiveness(&ctx, &modifier, GetSpeciesType(speciesDef, 0)); if (GetSpeciesType(speciesDef, 1) != GetSpeciesType(speciesDef, 0)) MulByTypeEffectiveness(&ctx, &modifier, GetSpeciesType(speciesDef, 1)); - if (ctx.moveType == TYPE_GROUND && abilityDef == ABILITY_LEVITATE && !(gFieldStatuses & STATUS_FIELD_GRAVITY)) - modifier = UQ_4_12(0.0); - if (abilityDef == ABILITY_WONDER_GUARD && modifier <= UQ_4_12(1.0) && GetMovePower(move) != 0) - modifier = UQ_4_12(0.0); + if (mon == 0) //catch for non Pokemon struct entries, only checks Ability + { + if (ctx.moveType == TYPE_GROUND && MonHasTrait(mon, ABILITY_LEVITATE) && !(gFieldStatuses & STATUS_FIELD_GRAVITY)) + modifier = UQ_4_12(0.0); + if (MonHasTrait(mon, ABILITY_WONDER_GUARD) && modifier <= UQ_4_12(1.0) && GetMovePower(move) != 0) + modifier = UQ_4_12(0.0); + } + else + { + if (ctx.moveType == TYPE_GROUND && MonHasTrait(mon, ABILITY_LEVITATE) && !(gFieldStatuses & STATUS_FIELD_GRAVITY)) + modifier = UQ_4_12(0.0); + if (MonHasTrait(mon, ABILITY_WONDER_GUARD) && modifier <= UQ_4_12(1.0) && GetMovePower(move) != 0) + modifier = UQ_4_12(0.0); + } } return modifier; @@ -8991,7 +9213,6 @@ static uq4_12_t GetInverseTypeMultiplier(uq4_12_t multiplier) uq4_12_t GetOverworldTypeEffectiveness(struct Pokemon *mon, enum Type moveType) { uq4_12_t modifier = UQ_4_12(1.0); - enum Ability abilityDef = GetMonAbility(mon); u16 speciesDef = GetMonData(mon, MON_DATA_SPECIES); enum Type type1 = GetSpeciesType(speciesDef, 0); enum Type type2 = GetSpeciesType(speciesDef, 1); @@ -9008,8 +9229,8 @@ uq4_12_t GetOverworldTypeEffectiveness(struct Pokemon *mon, enum Type moveType) if (type2 != type1) MulByTypeEffectiveness(&ctx, &modifier, type2); - if ((modifier <= UQ_4_12(1.0) && abilityDef == ABILITY_WONDER_GUARD) - || CanAbilityAbsorbMove(0, 0, abilityDef, MOVE_NONE, moveType, CHECK_TRIGGER)) + if ((modifier <= UQ_4_12(1.0) && MonHasTrait(mon, ABILITY_WONDER_GUARD)) + || CanAbilityAbsorbMove(0, ctx.battlerDef, MOVE_NONE, moveType, CHECK_TRIGGER)) modifier = UQ_4_12(0.0); return modifier; @@ -9258,11 +9479,11 @@ u16 GetBattleFormChangeTargetSpecies(u32 battler, enum FormChanges method) targetSpecies = formChanges[i].targetSpecies; break; case FORM_CHANGE_BATTLE_SWITCH: - if (formChanges[i].param1 == GetBattlerAbility(battler) || formChanges[i].param1 == ABILITY_NONE) + if (BattlerHasTrait(battler, formChanges[i].param1) || formChanges[i].param1 == ABILITY_NONE) targetSpecies = formChanges[i].targetSpecies; break; case FORM_CHANGE_BATTLE_HP_PERCENT: - if (formChanges[i].param1 == GetBattlerAbility(battler)) + if (BattlerHasTrait(battler, formChanges[i].param1)) { // We multiply by 100 to make sure that integer division doesn't mess with the health check. u32 hpCheck = gBattleMons[battler].hp * 100 * 100 / gBattleMons[battler].maxHP; @@ -9287,7 +9508,7 @@ u16 GetBattleFormChangeTargetSpecies(u32 battler, enum FormChanges method) // Check if there is a required ability and if the battler's ability does not match it // or is suppressed. If so, revert to the no weather form. if (formChanges[i].param2 - && GetBattlerAbility(battler) != formChanges[i].param2 + && !BattlerHasTrait(battler, formChanges[i].param2) && formChanges[i].param1 == B_WEATHER_NONE) { targetSpecies = formChanges[i].targetSpecies; @@ -9307,7 +9528,7 @@ u16 GetBattleFormChangeTargetSpecies(u32 battler, enum FormChanges method) break; case FORM_CHANGE_BATTLE_TURN_END: case FORM_CHANGE_HIT_BY_MOVE: - if (formChanges[i].param1 == GetBattlerAbility(battler)) + if (BattlerHasTrait(battler, formChanges[i].param1)) targetSpecies = formChanges[i].targetSpecies; break; case FORM_CHANGE_STATUS: @@ -9321,12 +9542,12 @@ u16 GetBattleFormChangeTargetSpecies(u32 battler, enum FormChanges method) case FORM_CHANGE_BATTLE_BEFORE_MOVE: case FORM_CHANGE_BATTLE_AFTER_MOVE: if (formChanges[i].param1 == gCurrentMove - && (formChanges[i].param2 == ABILITY_NONE || formChanges[i].param2 == GetBattlerAbility(battler))) + && (formChanges[i].param2 == ABILITY_NONE || BattlerHasTrait(battler, formChanges[i].param2))) targetSpecies = formChanges[i].targetSpecies; break; case FORM_CHANGE_BATTLE_BEFORE_MOVE_CATEGORY: if (formChanges[i].param1 == GetBattleMoveCategory(gCurrentMove) - && (formChanges[i].param2 == ABILITY_NONE || formChanges[i].param2 == GetBattlerAbility(battler))) + && (formChanges[i].param2 == ABILITY_NONE || BattlerHasTrait(battler, formChanges[i].param2))) targetSpecies = formChanges[i].targetSpecies; break; default: @@ -9358,7 +9579,7 @@ static bool32 CanBattlerFormChange(u32 battler, enum FormChanges method) case FORM_CHANGE_BATTLE_SWITCH: if (IsGigantamaxed(battler)) return TRUE; - else if (GetActiveGimmick(battler) == GIMMICK_TERA && GetBattlerAbility(battler) == ABILITY_HUNGER_SWITCH) + else if (GetActiveGimmick(battler) == GIMMICK_TERA && BattlerHasTrait(battler, ABILITY_HUNGER_SWITCH)) return FALSE; break; default: @@ -9394,6 +9615,9 @@ bool32 TryBattleFormChange(u32 battler, enum FormChanges method) if (!CanBattlerFormChange(battler, method)) return FALSE; + if(BattlerHasTrait(battler, ABILITY_STANCE_CHANGE)) + PushTraitStack(battler, ABILITY_STANCE_CHANGE); + targetSpecies = GetBattleFormChangeTargetSpecies(battler, method); if (targetSpecies == currentSpecies) targetSpecies = GetFormChangeTargetSpecies(&party[monId], method, 0); @@ -9506,7 +9730,7 @@ bool32 TryClearIllusion(u32 battler, enum AbilityEffect caseID) { if (gBattleStruct->illusion[battler].state != ILLUSION_ON) return FALSE; - if (GetBattlerAbility(battler) == ABILITY_ILLUSION && IsBattlerAlive(battler)) + if (BattlerHasTrait(battler, ABILITY_ILLUSION) && IsBattlerAlive(battler)) return FALSE; gBattleScripting.battler = battler; @@ -9586,7 +9810,7 @@ bool32 SetIllusionMon(struct Pokemon *mon, u32 battler) u32 id; gBattleStruct->illusion[battler].state = ILLUSION_OFF; - if (GetMonAbility(mon) != ABILITY_ILLUSION) + if (!MonHasTrait(mon, ABILITY_ILLUSION)) return FALSE; party = GetBattlerParty(battler); @@ -9610,62 +9834,92 @@ bool32 SetIllusionMon(struct Pokemon *mon, u32 battler) u32 TryImmunityAbilityHealStatus(u32 battler, enum AbilityEffect caseID) { u32 effect = 0; - switch (GetBattlerAbilityIgnoreMoldBreaker(battler)) + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS_IGNORE_MOLDBREAKER(battler); + + // Cures non-volatile status conditions before volitile and can only cure one at a time + if (SearchTraits(battlerTraits, ABILITY_IMMUNITY) + && gBattleMons[battler].status1 & (STATUS1_POISON | STATUS1_TOXIC_POISON | STATUS1_TOXIC_COUNTER)) { - case ABILITY_IMMUNITY: - case ABILITY_PASTEL_VEIL: - if (gBattleMons[battler].status1 & (STATUS1_POISON | STATUS1_TOXIC_POISON | STATUS1_TOXIC_COUNTER)) - { - StringCopy(gBattleTextBuff1, gStatusConditionString_PoisonJpn); - effect = 1; - } - break; - case ABILITY_OWN_TEMPO: - if (gBattleMons[battler].volatiles.confusionTurns > 0) - { - StringCopy(gBattleTextBuff1, gStatusConditionString_ConfusionJpn); - effect = 2; - } - break; - case ABILITY_LIMBER: - if (gBattleMons[battler].status1 & STATUS1_PARALYSIS) - { - StringCopy(gBattleTextBuff1, gStatusConditionString_ParalysisJpn); - effect = 1; - } - break; - case ABILITY_INSOMNIA: - case ABILITY_VITAL_SPIRIT: - if (gBattleMons[battler].status1 & STATUS1_SLEEP) - { - TryDeactivateSleepClause(GetBattlerSide(battler), gBattlerPartyIndexes[battler]); - gBattleMons[battler].volatiles.nightmare = FALSE; - StringCopy(gBattleTextBuff1, gStatusConditionString_SleepJpn); - effect = 1; - } - break; - case ABILITY_WATER_VEIL: - case ABILITY_WATER_BUBBLE: - case ABILITY_THERMAL_EXCHANGE: - if (gBattleMons[battler].status1 & STATUS1_BURN) - { - StringCopy(gBattleTextBuff1, gStatusConditionString_BurnJpn); - effect = 1; - } - break; - case ABILITY_MAGMA_ARMOR: - if (gBattleMons[battler].status1 & (STATUS1_FREEZE | STATUS1_FROSTBITE)) - { - StringCopy(gBattleTextBuff1, gStatusConditionString_IceJpn); - effect = 1; - } - break; - case ABILITY_OBLIVIOUS: + PushTraitStack(battler, ABILITY_IMMUNITY); + StringCopy(gBattleTextBuff1, gStatusConditionString_PoisonJpn); + effect = 1; + } + else if (SearchTraits(battlerTraits, ABILITY_PASTEL_VEIL) + && gBattleMons[battler].status1 & (STATUS1_POISON | STATUS1_TOXIC_POISON | STATUS1_TOXIC_COUNTER)) + { + PushTraitStack(battler, ABILITY_PASTEL_VEIL); + StringCopy(gBattleTextBuff1, gStatusConditionString_PoisonJpn); + effect = 1; + } + else if (SearchTraits(battlerTraits, ABILITY_LIMBER) + && gBattleMons[battler].status1 & STATUS1_PARALYSIS) + { + PushTraitStack(battler, ABILITY_LIMBER); + StringCopy(gBattleTextBuff1, gStatusConditionString_ParalysisJpn); + effect = 1; + } + else if (SearchTraits(battlerTraits, ABILITY_INSOMNIA) + && gBattleMons[battler].status1 & STATUS1_SLEEP) + { + TryDeactivateSleepClause(GetBattlerSide(battler), gBattlerPartyIndexes[battler]); + gBattleMons[battler].volatiles.nightmare = FALSE; + PushTraitStack(battler, ABILITY_INSOMNIA); + StringCopy(gBattleTextBuff1, gStatusConditionString_SleepJpn); + effect = 1; + } + else if (SearchTraits(battlerTraits, ABILITY_VITAL_SPIRIT) + && gBattleMons[battler].status1 & STATUS1_SLEEP) + { + TryDeactivateSleepClause(GetBattlerSide(battler), gBattlerPartyIndexes[battler]); + gBattleMons[battler].volatiles.nightmare = FALSE; + PushTraitStack(battler, ABILITY_VITAL_SPIRIT); + StringCopy(gBattleTextBuff1, gStatusConditionString_SleepJpn); + effect = 1; + } + else if (SearchTraits(battlerTraits, ABILITY_WATER_VEIL) + && gBattleMons[battler].status1 & STATUS1_BURN) + { + PushTraitStack(battler, ABILITY_WATER_VEIL); + StringCopy(gBattleTextBuff1, gStatusConditionString_BurnJpn); + effect = 1; + } + else if (SearchTraits(battlerTraits, ABILITY_WATER_BUBBLE) + && gBattleMons[battler].status1 & STATUS1_BURN) + { + PushTraitStack(battler, ABILITY_WATER_BUBBLE); + StringCopy(gBattleTextBuff1, gStatusConditionString_BurnJpn); + effect = 1; + } + else if (SearchTraits(battlerTraits, ABILITY_THERMAL_EXCHANGE) + && gBattleMons[battler].status1 & STATUS1_BURN) + { + PushTraitStack(battler, ABILITY_THERMAL_EXCHANGE); + StringCopy(gBattleTextBuff1, gStatusConditionString_BurnJpn); + effect = 1; + } + else if (SearchTraits(battlerTraits, ABILITY_MAGMA_ARMOR) + && gBattleMons[battler].status1 & (STATUS1_FREEZE | STATUS1_FROSTBITE)) + { + PushTraitStack(battler, ABILITY_MAGMA_ARMOR); + StringCopy(gBattleTextBuff1, gStatusConditionString_IceJpn); + effect = 1; + } + else if (SearchTraits(battlerTraits, ABILITY_OWN_TEMPO) + && gBattleMons[battler].volatiles.confusionTurns > 0) + { + PushTraitStack(battler, ABILITY_OWN_TEMPO); + StringCopy(gBattleTextBuff1, gStatusConditionString_ConfusionJpn); + effect = 2; + } + else if (SearchTraits(battlerTraits, ABILITY_OBLIVIOUS) + && gBattleMons[battler].status1 & (STATUS1_FREEZE | STATUS1_FROSTBITE)) + { + PushTraitStack(battler, ABILITY_OBLIVIOUS); if (gBattleMons[battler].volatiles.infatuation) effect = 3; else if (GetConfig(CONFIG_OBLIVIOUS_TAUNT) >= GEN_6 && gDisableStructs[battler].tauntTimer != 0) effect = 4; - break; } if (effect != 0) @@ -9849,7 +10103,7 @@ bool32 CanFling(u32 battler) u16 item = gBattleMons[battler].item; if (item == ITEM_NONE - || (GetConfig(CONFIG_KLUTZ_FLING_INTERACTION) >= GEN_5 && GetBattlerAbility(battler) == ABILITY_KLUTZ) + || (GetConfig(CONFIG_KLUTZ_FLING_INTERACTION) >= GEN_5 && BattlerHasTrait(battler, ABILITY_KLUTZ)) || gFieldStatuses & STATUS_FIELD_MAGIC_ROOM || gBattleMons[battler].volatiles.embargo || (GetItemTMHMIndex(item) != 0 && GetItemImportance(item) == 1) // don't fling reusable TMs @@ -9870,7 +10124,7 @@ void SortBattlersBySpeed(u8 *battlers, bool32 slowToFast) for (i = 0; i < gBattlersCount; i++) { u32 battler = battlers[i]; - speeds[i] = GetBattlerTotalSpeedStat(battler, GetBattlerAbility(battler), GetBattlerHoldEffect(battler)); + speeds[i] = GetBattlerTotalSpeedStat(battler, GetBattlerHoldEffect(battler)); } for (i = 1; i < gBattlersCount; i++) @@ -9999,20 +10253,20 @@ bool32 IsBattlerAffectedByHazards(u32 battler, bool32 toxicSpikes) return ret; } -bool32 IsSheerForceAffected(u16 move, enum Ability ability) +bool32 IsSheerForceAffected(u16 move, u32 battler) { - return ability == ABILITY_SHEER_FORCE && MoveIsAffectedBySheerForce(move); + return (gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battler, ABILITY_SHEER_FORCE) : BattlerHasTrait(battler, ABILITY_SHEER_FORCE)) && MoveIsAffectedBySheerForce(move); } // This function is the body of "jumpifstat", but can be used dynamically in a function -bool32 CompareStat(u32 battler, enum Stat statId, u8 cmpTo, u8 cmpKind, enum Ability ability) +bool32 CompareStat(u32 battler, enum Stat statId, u8 cmpTo, u8 cmpKind) { bool32 ret = FALSE; u8 statValue = gBattleMons[battler].statStages[statId]; // Because this command is used as a way of checking if a stat can be lowered/raised, // we need to do some modification at run-time. - if (ability == ABILITY_CONTRARY) + if (BattlerHasTrait(battler, ABILITY_CONTRARY)) { if (cmpKind == CMP_GREATER_THAN) cmpKind = CMP_LESS_THAN; @@ -10056,6 +10310,42 @@ bool32 CompareStat(u32 battler, enum Stat statId, u8 cmpTo, u8 cmpKind, enum Abi return ret; } +bool32 CompareStatIgnoreContrary(u32 battler, enum Stat statId, u8 cmpTo, u8 cmpKind) +{ + bool32 ret = FALSE; + u8 statValue = gBattleMons[battler].statStages[statId]; + + switch (cmpKind) + { + case CMP_EQUAL: + if (statValue == cmpTo) + ret = TRUE; + break; + case CMP_NOT_EQUAL: + if (statValue != cmpTo) + ret = TRUE; + break; + case CMP_GREATER_THAN: + if (statValue > cmpTo) + ret = TRUE; + break; + case CMP_LESS_THAN: + if (statValue < cmpTo) + ret = TRUE; + break; + case CMP_COMMON_BITS: + if (statValue & cmpTo) + ret = TRUE; + break; + case CMP_NO_COMMON_BITS: + if (!(statValue & cmpTo)) + ret = TRUE; + break; + } + + return ret; +} + bool32 BlocksPrankster(u16 move, u32 battlerPrankster, u32 battlerDef, bool32 checkTarget) { if (GetConfig(CONFIG_PRANKSTER_DARK_TYPES) < GEN_7) @@ -10114,7 +10404,7 @@ u32 GetBattlerMoveTargetType(u32 battler, u32 move) enum BattleMoveEffects effect = GetMoveEffect(move); if (effect == EFFECT_CURSE && !IS_BATTLER_OF_TYPE(battler, TYPE_GHOST)) return MOVE_TARGET_USER; - if (effect == EFFECT_EXPANDING_FORCE && IsBattlerTerrainAffected(battler, GetBattlerAbility(battler), GetBattlerHoldEffect(battler), STATUS_FIELD_PSYCHIC_TERRAIN)) + if (effect == EFFECT_EXPANDING_FORCE && IsBattlerTerrainAffected(battler, GetBattlerHoldEffect(battler), STATUS_FIELD_PSYCHIC_TERRAIN)) return MOVE_TARGET_BOTH; if (effect == EFFECT_TERA_STARSTORM && gBattleMons[battler].species == SPECIES_TERAPAGOS_STELLAR) return MOVE_TARGET_BOTH; @@ -10219,9 +10509,9 @@ bool32 AreBattlersOfSameGender(u32 battler1, u32 battler2) return (gender1 != MON_GENDERLESS && gender2 != MON_GENDERLESS && gender1 == gender2); } -u32 CalcSecondaryEffectChance(u32 battler, enum Ability battlerAbility, const struct AdditionalEffect *additionalEffect) +u32 CalcSecondaryEffectChance(u32 battler, const struct AdditionalEffect *additionalEffect) { - bool8 hasSereneGrace = (battlerAbility == ABILITY_SERENE_GRACE); + bool8 hasSereneGrace = (BattlerHasTrait(battler, ABILITY_SERENE_GRACE)); bool8 hasRainbow = (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_RAINBOW) != 0; u16 secondaryEffectChance = additionalEffect->chance; @@ -10236,9 +10526,9 @@ u32 CalcSecondaryEffectChance(u32 battler, enum Ability battlerAbility, const st return secondaryEffectChance; } -bool32 MoveEffectIsGuaranteed(u32 battler, enum Ability battlerAbility, const struct AdditionalEffect *additionalEffect) +bool32 MoveEffectIsGuaranteed(u32 battler, const struct AdditionalEffect *additionalEffect) { - return additionalEffect->chance == 0 || CalcSecondaryEffectChance(battler, battlerAbility, additionalEffect) >= 100; + return additionalEffect->chance == 0 || CalcSecondaryEffectChance(battler, additionalEffect) >= 100; } bool32 IsGen6ExpShareEnabled(void) @@ -10322,9 +10612,8 @@ bool32 MoveIsAffectedBySheerForce(u32 move) bool8 CanMonParticipateInSkyBattle(struct Pokemon *mon) { u16 species = GetMonData(mon, MON_DATA_SPECIES); - u16 monAbilityNum = GetMonData(mon, MON_DATA_ABILITY_NUM, NULL); - bool8 hasLevitateAbility = GetSpeciesAbility(species, monAbilityNum) == ABILITY_LEVITATE; + bool8 hasLevitateAbility = (MonHasTrait(mon, ABILITY_LEVITATE)); bool8 isFlyingType = GetSpeciesType(species, 0) == TYPE_FLYING || GetSpeciesType(species, 1) == TYPE_FLYING; bool8 monIsValidAndNotEgg = GetMonData(mon, MON_DATA_SANITY_HAS_SPECIES) && !GetMonData(mon, MON_DATA_IS_EGG); @@ -10487,16 +10776,14 @@ bool32 CanTargetPartner(u32 battlerAtk, u32 battlerDef) static inline bool32 DoesBattlerHaveAbilityImmunity(u32 battlerAtk, u32 battlerDef, enum Type moveType) { - enum Ability abilityDef = GetBattlerAbility(battlerDef); - - return CanAbilityBlockMove(battlerAtk, battlerDef, GetBattlerAbility(battlerAtk), abilityDef, gCurrentMove, CHECK_TRIGGER) - || CanAbilityAbsorbMove(battlerAtk, battlerDef, abilityDef, gCurrentMove, moveType, CHECK_TRIGGER); + return CanAbilityBlockMove(battlerAtk, battlerDef, gCurrentMove, CHECK_TRIGGER) + || CanAbilityAbsorbMove(battlerAtk, battlerDef, gCurrentMove, moveType, CHECK_TRIGGER); } bool32 TargetFullyImmuneToCurrMove(u32 battlerAtk, u32 battlerDef) { enum Type moveType = GetBattleMoveType(gCurrentMove); - return ((CalcTypeEffectivenessMultiplierHelper(gCurrentMove, moveType, battlerAtk, battlerDef, GetBattlerAbility(battlerAtk), GetBattlerAbility(battlerDef), FALSE) == UQ_4_12(0.0)) + return ((CalcTypeEffectivenessMultiplierHelper(gCurrentMove, moveType, battlerAtk, battlerDef, FALSE) == UQ_4_12(0.0)) || IsBattlerProtected(battlerAtk, battlerDef, gCurrentMove) || !BreaksThroughSemiInvulnerablity(battlerDef, gCurrentMove) || DoesBattlerHaveAbilityImmunity(battlerAtk, battlerDef, moveType)); @@ -10570,7 +10857,7 @@ void ClearDamageCalcResults(void) gBattleStruct->numSpreadTargets = 0; gBattleScripting.savedDmg = 0; if (gCurrentMove != MOVE_NONE) - gBattleStruct->moldBreakerActive = IsMoldBreakerTypeAbility(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker)) || MoveIgnoresTargetAbility(gCurrentMove); + gBattleStruct->moldBreakerActive = HasMoldBreakerTypeAbility(gBattlerAttacker) || MoveIgnoresTargetAbility(gCurrentMove); else gBattleStruct->moldBreakerActive = FALSE; } @@ -10581,11 +10868,11 @@ bool32 DoesDestinyBondFail(u32 battler) } // This check has always to be the last in a condtion statement because of the recording of AI data. -bool32 IsMoveEffectBlockedByTarget(enum Ability ability) +bool32 IsMoveEffectBlockedByTarget(void) { - if (ability == ABILITY_SHIELD_DUST) + if (BattlerHasTrait(gBattlerTarget, ABILITY_SHIELD_DUST)) { - RecordAbilityBattle(gBattlerTarget, ability); + RecordAbilityBattle(gBattlerTarget, ABILITY_SHIELD_DUST); return TRUE; } else if (GetBattlerHoldEffect(gBattlerTarget) == HOLD_EFFECT_COVERT_CLOAK) @@ -10627,15 +10914,9 @@ bool32 HasWeatherEffect(void) if (!IsBattlerAlive(battler)) continue; - enum Ability ability = GetBattlerAbility(battler); - switch (ability) - { - case ABILITY_CLOUD_NINE: - case ABILITY_AIR_LOCK: + if (BattlerHasTrait(battler, ABILITY_CLOUD_NINE) + || BattlerHasTrait(battler, ABILITY_AIR_LOCK)) return FALSE; - default: - break; - } } return TRUE; @@ -10648,13 +10929,12 @@ void UpdateStallMons(void) if (!IsDoubleBattle() || GetMoveTarget(gCurrentMove) == MOVE_TARGET_SELECTED) { enum Type moveType = GetBattleMoveType(gCurrentMove); // Probably doesn't handle dynamic move types right now - enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker); - enum Ability abilityDef = GetBattlerAbility(gBattlerTarget); - if (CanAbilityAbsorbMove(gBattlerAttacker, gBattlerTarget, abilityDef, gCurrentMove, moveType, CHECK_TRIGGER)) + + if (CanAbilityAbsorbMove(gBattlerAttacker, gBattlerTarget, gCurrentMove, moveType, CHECK_TRIGGER)) { gAiBattleData->playerStallMons[gBattlerPartyIndexes[gBattlerTarget]]++; } - else if (CanAbilityBlockMove(gBattlerAttacker, gBattlerTarget, abilityAtk, abilityDef, gCurrentMove, CHECK_TRIGGER)) + else if (CanAbilityBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove, CHECK_TRIGGER)) { gAiBattleData->playerStallMons[gBattlerPartyIndexes[gBattlerTarget]]++; } @@ -10855,10 +11135,9 @@ void RemoveHazardFromField(u32 side, enum Hazards hazardType) } } -bool32 CanMoveSkipAccuracyCalc(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef, u32 move, enum FunctionCallOption option) +bool32 CanMoveSkipAccuracyCalc(u32 battlerAtk, u32 battlerDef, u32 move, enum FunctionCallOption option) { bool32 effect = FALSE; - enum Ability ability = ABILITY_NONE; enum BattleMoveEffects moveEffect = GetMoveEffect(move); u32 nonVolatileStatus = GetMoveNonVolatileStatus(move); @@ -10869,19 +11148,17 @@ bool32 CanMoveSkipAccuracyCalc(u32 battlerAtk, u32 battlerDef, enum Ability abil effect = TRUE; } // If the attacker has the ability No Guard and they aren't targeting a Pokemon involved in a Sky Drop with the move Sky Drop, move hits. - else if (abilityAtk == ABILITY_NO_GUARD + else if (BattlerHasTrait(battlerAtk, ABILITY_NO_GUARD) && gBattleMons[battlerDef].volatiles.semiInvulnerable != STATE_COMMANDER && (moveEffect != EFFECT_SKY_DROP || gBattleStruct->skyDropTargets[battlerDef] == SKY_DROP_NO_TARGET)) { effect = TRUE; - ability = ABILITY_NO_GUARD; } // If the target has the ability No Guard and they aren't involved in a Sky Drop or the current move isn't Sky Drop, move hits. - else if (abilityDef == ABILITY_NO_GUARD + else if (BattlerHasTrait(battlerDef, ABILITY_NO_GUARD) && (moveEffect != EFFECT_SKY_DROP || gBattleStruct->skyDropTargets[battlerDef] == SKY_DROP_NO_TARGET)) { effect = TRUE; - ability = ABILITY_NO_GUARD; } // If the target is under the effects of Telekinesis, and the move isn't a OH-KO move, move hits. else if (gBattleMons[battlerDef].volatiles.telekinesis @@ -10933,28 +11210,34 @@ bool32 CanMoveSkipAccuracyCalc(u32 battlerAtk, u32 battlerDef, enum Ability abil return effect; } - if (ability != ABILITY_NONE && option == RUN_SCRIPT) + if (BattlerHasTrait(battlerAtk, ABILITY_NO_GUARD) && option == RUN_SCRIPT) RecordAbilityBattle(battlerAtk, ABILITY_NO_GUARD); return effect; } -u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, enum Ability atkAbility, enum Ability defAbility, enum HoldEffect atkHoldEffect, enum HoldEffect defHoldEffect) +u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, enum HoldEffect atkHoldEffect, enum HoldEffect defHoldEffect) { u32 calc, moveAcc; s8 buff, accStage, evasionStage; u32 atkParam = GetBattlerHoldEffectParam(battlerAtk); u32 defParam = GetBattlerHoldEffectParam(battlerDef); + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battlerAtk); + + // Use AI Ability knowledge if this is an AI check + if(gAiLogicData->aiCalcInProgress) + battlerTraits[0] = gAiLogicData->abilities[battlerAtk]; gPotentialItemEffectBattler = battlerDef; accStage = gBattleMons[battlerAtk].statStages[STAT_ACC]; evasionStage = gBattleMons[battlerDef].statStages[STAT_EVASION]; - if (atkAbility == ABILITY_UNAWARE || atkAbility == ABILITY_KEEN_EYE || atkAbility == ABILITY_MINDS_EYE - || (GetConfig(CONFIG_ILLUMINATE_EFFECT) >= GEN_9 && atkAbility == ABILITY_ILLUMINATE)) + if (SearchTraits(battlerTraits, ABILITY_UNAWARE) || SearchTraits(battlerTraits, ABILITY_KEEN_EYE) || SearchTraits(battlerTraits, ABILITY_MINDS_EYE) + || (GetConfig(CONFIG_ILLUMINATE_EFFECT) >= GEN_9 && SearchTraits(battlerTraits, ABILITY_ILLUMINATE))) evasionStage = DEFAULT_STAT_STAGE; if (MoveIgnoresDefenseEvasionStages(move)) evasionStage = DEFAULT_STAT_STAGE; - if (defAbility == ABILITY_UNAWARE) + if (gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_UNAWARE) : BattlerHasTrait(battlerDef, ABILITY_UNAWARE)) accStage = DEFAULT_STAT_STAGE; if (gBattleMons[battlerDef].volatiles.foresight || gBattleMons[battlerDef].volatiles.miracleEye) @@ -10972,59 +11255,39 @@ u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, enum Ability atkA if (IsBattlerWeatherAffected(battlerDef, B_WEATHER_SUN) && MoveHas50AccuracyInSun(move)) moveAcc = 50; // Check Wonder Skin. - if (defAbility == ABILITY_WONDER_SKIN && IsBattleMoveStatus(move) && moveAcc > 50) + if ((gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battlerDef, ABILITY_WONDER_SKIN) : BattlerHasTrait(battlerDef, ABILITY_WONDER_SKIN)) && IsBattleMoveStatus(move) && moveAcc > 50) moveAcc = 50; calc = gAccuracyStageRatios[buff].dividend * moveAcc; calc /= gAccuracyStageRatios[buff].divisor; // Attacker's ability - switch (atkAbility) - { - case ABILITY_COMPOUND_EYES: + if (SearchTraits(battlerTraits, ABILITY_COMPOUND_EYES)) calc = (calc * 130) / 100; // 1.3 compound eyes boost - break; - case ABILITY_VICTORY_STAR: + if (SearchTraits(battlerTraits, ABILITY_VICTORY_STAR)) calc = (calc * 110) / 100; // 1.1 victory star boost - break; - case ABILITY_HUSTLE: + if (SearchTraits(battlerTraits, ABILITY_HUSTLE)) if (IsBattleMovePhysical(move)) calc = (calc * 80) / 100; // 1.2 hustle loss - break; - default: - break; - } // Target's ability - switch (defAbility) - { - case ABILITY_SAND_VEIL: + STORE_BATTLER_TRAITS(battlerDef); + + if (SearchTraits(battlerTraits, ABILITY_SAND_VEIL)) if (gBattleWeather & B_WEATHER_SANDSTORM && HasWeatherEffect()) calc = (calc * 80) / 100; // 1.2 sand veil loss - break; - case ABILITY_SNOW_CLOAK: + if (SearchTraits(battlerTraits, ABILITY_SNOW_CLOAK)) if ((gBattleWeather & (B_WEATHER_HAIL | B_WEATHER_SNOW)) && HasWeatherEffect()) calc = (calc * 80) / 100; // 1.2 snow cloak loss - break; - case ABILITY_TANGLED_FEET: + if (SearchTraits(battlerTraits, ABILITY_TANGLED_FEET)) if (gBattleMons[battlerDef].volatiles.confusionTurns) calc = (calc * 50) / 100; // 1.5 tangled feet loss - break; - default: - break; - } // Attacker's ally's ability u32 atkAlly = BATTLE_PARTNER(battlerAtk); - switch (GetBattlerAbility(atkAlly)) - { - case ABILITY_VICTORY_STAR: + if (BattlerHasTrait(atkAlly, ABILITY_VICTORY_STAR)) if (IsBattlerAlive(atkAlly)) calc = (calc * 110) / 100; // 1.1 ally's victory star boost - break; - default: - break; - } // Attacker's hold effect switch (atkHoldEffect) @@ -11053,7 +11316,7 @@ u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, enum Ability atkA if (gBattleStruct->battlerState[battlerAtk].usedMicleBerry) { // TODO: Is this true? - if (atkAbility == ABILITY_RIPEN) + if ((gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battlerAtk, ABILITY_RIPEN) : BattlerHasTrait(battlerAtk, ABILITY_RIPEN))) calc = (calc * 140) / 100; // ripen gives 40% acc boost else calc = (calc * 120) / 100; // 20% acc boost @@ -11124,9 +11387,11 @@ static bool32 IsOpposingSideEmpty(u32 battler) return TRUE; } -bool32 IsAffectedByPowderMove(u32 battler, u32 ability, enum HoldEffect holdEffect) +bool32 IsAffectedByPowderMove(u32 battler, enum HoldEffect holdEffect) { - if ((GetConfig(CONFIG_POWDER_OVERCOAT) >= GEN_6 && ability == ABILITY_OVERCOAT) + bool32 hasCoat = (gAiLogicData->aiCalcInProgress ? AI_BATTLER_HAS_TRAIT(battler, ABILITY_OVERCOAT) : BattlerHasTrait(battler, ABILITY_OVERCOAT)); + + if ((GetConfig(CONFIG_POWDER_OVERCOAT) >= GEN_6 && hasCoat) || (GetConfig(CONFIG_POWDER_GRASS) >= GEN_6 && IS_BATTLER_OF_TYPE(battler, TYPE_GRASS)) || holdEffect == HOLD_EFFECT_SAFETY_GOGGLES) return FALSE; @@ -11259,7 +11524,7 @@ static u32 GetSleepTalkMove(void) u32 i, unusableMovesBits = 0, movePosition; - if (GetBattlerAbility(gBattlerAttacker) != ABILITY_COMATOSE + if (!BattlerHasTrait(gBattlerAttacker, ABILITY_COMATOSE) && !(gBattleMons[gBattlerAttacker].status1 & STATUS1_SLEEP)) return move; @@ -11312,26 +11577,19 @@ static u32 GetMeFirstMove(void) void RemoveAbilityFlags(u32 battler) { - switch (GetBattlerAbility(battler)) - { - case ABILITY_FLASH_FIRE: + enum Ability battlerTraits[MAX_MON_TRAITS]; + STORE_BATTLER_TRAITS(battler); + + if (SearchTraits(battlerTraits, ABILITY_FLASH_FIRE)) gDisableStructs[battler].flashFireBoosted = FALSE; - break; - case ABILITY_VESSEL_OF_RUIN: + if (SearchTraits(battlerTraits, ABILITY_VESSEL_OF_RUIN)) gBattleMons[battler].volatiles.vesselOfRuin = FALSE; - break; - case ABILITY_TABLETS_OF_RUIN: + if (SearchTraits(battlerTraits, ABILITY_TABLETS_OF_RUIN)) gBattleMons[battler].volatiles.tabletsOfRuin = FALSE; - break; - case ABILITY_SWORD_OF_RUIN: + if (SearchTraits(battlerTraits, ABILITY_SWORD_OF_RUIN)) gBattleMons[battler].volatiles.swordOfRuin = FALSE; - break; - case ABILITY_BEADS_OF_RUIN: + if (SearchTraits(battlerTraits, ABILITY_BEADS_OF_RUIN)) gBattleMons[battler].volatiles.beadsOfRuin = FALSE; - break; - default: - break; - } } bool32 IsAnyTargetTurnDamaged(u32 battlerAtk) @@ -11366,3 +11624,167 @@ bool32 IsMimikyuDisguised(u32 battler) return gBattleMons[battler].species == SPECIES_MIMIKYU_DISGUISED || gBattleMons[battler].species == SPECIES_MIMIKYU_TOTEM_DISGUISED; } + +//Returns the Ability or Innate of the battler at the given trait number, used to build out trait arrays +enum Ability GetBattlerTrait(u8 battlerId, u8 traitNum, u32 ignoreMoldBreaker) +{ + bool32 hasAbilityShield = GetBattlerHoldEffectIgnoreAbility(battlerId) == HOLD_EFFECT_ABILITY_SHIELD; + + enum Ability ability = ABILITIES_COUNT; + + #if TESTING + if (gTestRunnerEnabled) + { + u32 array = (!IsPartnerMonFromSameTrainer(battlerId)) ? battlerId : GetBattlerSide(battlerId); + u32 partyIndex = gBattlerPartyIndexes[battlerId]; + + if (T_SHOULD_TEST_DEFAULT_INNATES) // If TRUE, tests will use a pokemon's default Innates whenever one is not specified in the test. + { + if (traitNum > 0 && TestRunner_Battle_GetForcedInnates(array, partyIndex, traitNum - 1) != ABILITY_NONE) + { + ability = TestRunner_Battle_GetForcedInnates(array, partyIndex, traitNum - 1); + } + } + else if (traitNum > 0) + { + ability = TestRunner_Battle_GetForcedInnates(array, partyIndex, traitNum - 1); + } + } + #endif + + if (traitNum == 0){ + { + //DebugPrintf("ABILITY: %S", gAbilitiesInfo[GetBattlerAbility(battlerId)].name); + if (!ignoreMoldBreaker) + return GetBattlerAbility(battlerId); + else + return GetBattlerAbilityIgnoreMoldBreaker(battlerId); + } + } + else + { + // Load natural Innate if not a Test + if (ability == ABILITIES_COUNT) + ability = GetSpeciesInnate(gBattleMons[battlerId].species, traitNum); + + //DebugPrintf("Trait %d: %S", traitNum, gAbilitiesInfo[ability].name); + + // Check if ability is nullified + if (battlerId != gBattlerAttacker + && !ignoreMoldBreaker + && CanBreakThroughAbility(gBattlerAttacker, battlerId, ability, hasAbilityShield, FALSE)) + return ABILITY_NONE; + + return ability; + } +} + +//Returns the slot the Innate is found in accouting for randomization and ability disabling. Assumes the Ability is already slot 1. Returns 0 if not found. +u8 BattlerHasInnate(u8 battlerId, enum Ability ability) +{ + /*if (BattlerIgnoresAbility(gBattlerAttacker, battlerId, ability) && B_MOLD_BREAKER_WORKS_ON_INNATES == TRUE) + return 0; + else if (BattlerAbilityWasRemoved(battlerId, ability) && B_NEUTRALIZING_GAS_WORKS_ON_INNATES == TRUE) + return 0; + else*/ + + //Check for Mold Breaker type negation + if (battlerId != gBattlerAttacker + && CanBreakThroughAbility(gBattlerAttacker, battlerId, ability, FALSE, FALSE)) + return 0; + + for (u8 i = 0; i < MAX_MON_INNATES; i++) + { + if (gBattleMons[battlerId].innates[i] == ability) + return i + 2; + } + + return SpeciesHasInnate(gBattleMons[battlerId].species, ability); +} + +//Returns the trait slot number of the given ability. Starts at 1 for the primary Ability and returns 0 if the ability is not found. Use for individual checks. +u8 BattlerHasTrait(u8 battlerId, enum Ability ability) +{ + u8 traitNum = 0; + + if (GetBattlerAbility(battlerId) == ability) + traitNum = 1; + else + traitNum = BattlerHasInnate(battlerId, ability); + + return traitNum; +} + +//Used to search abilities for functions already under GetBattlerAbility to avoid infinite loops. +u8 BattlerHasTraitPlain(u8 battlerId, enum Ability ability) +{ + if (gBattleMons[battlerId].ability == ability) + return 1; + else + return BattlerHasInnate(battlerId, ability); +} + +void PushTraitStack(u8 battlerId, enum Ability ability) +{ + + for (int i = 0; i < (MAX_BATTLERS_COUNT * MAX_MON_TRAITS); i++) + { + if (gTraitStack[i][1] == ABILITY_NONE) + { + gTraitStack[i][0] = battlerId; + gTraitStack[i][1] = ability; + //DebugPrintf("TRAIT STACK: Stack Slot: %d, Battler: %d, Ability: %S", i, gTraitStack[i][0], gAbilitiesInfo[gTraitStack[i][1]].name); + break; + } + } +} + +u8 PullTraitStackBattler() +{ + u8 battlerId = MAX_BATTLERS_COUNT; + + for (int i = 0; i < (MAX_BATTLERS_COUNT * MAX_MON_TRAITS); i++) + { + if (gTraitStack[i][1] == ABILITY_NONE) + { + if (i == 0) + break; //Do nothing if first slot is already empty + battlerId = gTraitStack[i-1][0]; + break; + } + } + return battlerId; +} + +enum Ability PullTraitStackAbility() +{ + enum Ability ability = ABILITY_NONE; + + for (int i = 0; i < (MAX_BATTLERS_COUNT * MAX_MON_TRAITS); i++) + { + if (gTraitStack[i][1] == ABILITY_NONE) + { + if (i == 0) + break; //Do nothing if first slot is already empty + ability = gTraitStack[i-1][1]; //Return the ability in the slot before the most recent empty slot + break; + } + // else + // DebugPrintf("TRAIT STACK: [%d] - %S", i, gAbilitiesInfo[gTraitStack[i][1]].name); + } + return ability; +} +// Clears the latest ability popup slot. Searches from the bottom to the top since the stack should generally be small. +void PopTraitStack() +{ + for (int i =0; i < (MAX_BATTLERS_COUNT * MAX_MON_TRAITS); i++) + { + if (gTraitStack[i][1] == ABILITY_NONE) + { + if (i == 0) + break; //Do nothing if first slot is already empty + gTraitStack[i-1][0] = gTraitStack[i-1][1] = 0; //Clear the slot + break; + } + } +} diff --git a/src/battle_util2.c b/src/battle_util2.c index 3f252713009a..7d8527ea2185 100644 --- a/src/battle_util2.c +++ b/src/battle_util2.c @@ -1,5 +1,6 @@ #include "global.h" #include "battle.h" +#include "battle_ai_main.h" #include "battle_anim.h" #include "battle_controllers.h" #include "malloc.h" @@ -55,6 +56,7 @@ void FreeBattleResources(void) gFieldStatuses = 0; if (gBattleResources != NULL) { + memset(&gBattleStruct->illusion, 0, sizeof(gBattleStruct->illusion)); FREE_AND_SET_NULL(gBattleStruct); FREE_AND_SET_NULL(gAiBattleData); FREE_AND_SET_NULL(gAiThinkingStruct); @@ -143,7 +145,7 @@ u32 BattlePalace_TryEscapeStatus(u8 battler) { u32 toSub; - if (GetBattlerAbility(battler) == ABILITY_EARLY_BIRD) + if (BattlerHasTrait(battler, ABILITY_EARLY_BIRD)) toSub = 2; else toSub = 1; diff --git a/src/data/pokemon/species_info/gen_1_families.h b/src/data/pokemon/species_info/gen_1_families.h index 0301a485b302..91657b427013 100644 --- a/src/data/pokemon/species_info/gen_1_families.h +++ b/src/data/pokemon/species_info/gen_1_families.h @@ -22,7 +22,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_GRASS), .abilities = { ABILITY_OVERGROW, ABILITY_NONE, ABILITY_CHLOROPHYLL }, - .bodyColor = BODY_COLOR_GREEN, + //.innates = { ABILITY_SUPERSWEET_SYRUP }, + .bodyColor = BODY_COLOR_GREEN, .noFlip = TRUE, .speciesName = _("Bulbasaur"), .cryId = CRY_BULBASAUR, @@ -93,7 +94,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_GRASS), .abilities = { ABILITY_OVERGROW, ABILITY_NONE, ABILITY_CHLOROPHYLL }, - .bodyColor = BODY_COLOR_GREEN, + //.innates = { ABILITY_SUPERSWEET_SYRUP, ABILITY_FLOWER_VEIL }, + .bodyColor = BODY_COLOR_GREEN, .noFlip = TRUE, .speciesName = _("Ivysaur"), .cryId = CRY_IVYSAUR, @@ -171,7 +173,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_GRASS), .abilities = { ABILITY_OVERGROW, ABILITY_NONE, ABILITY_CHLOROPHYLL }, - .bodyColor = BODY_COLOR_GREEN, + //.innates = { ABILITY_SUPERSWEET_SYRUP, ABILITY_FLOWER_VEIL, ABILITY_PROTOSYNTHESIS }, + .bodyColor = BODY_COLOR_GREEN, .speciesName = _("Venusaur"), .cryId = CRY_VENUSAUR, .natDexNum = NATIONAL_DEX_VENUSAUR, @@ -255,7 +258,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_GRASS), .abilities = { ABILITY_THICK_FAT, ABILITY_THICK_FAT, ABILITY_THICK_FAT }, - .bodyColor = BODY_COLOR_GREEN, + //.innates = { ABILITY_SUPERSWEET_SYRUP, ABILITY_FLOWER_VEIL, ABILITY_PROTOSYNTHESIS }, + .bodyColor = BODY_COLOR_GREEN, .speciesName = _("Venusaur"), .cryId = CRY_VENUSAUR_MEGA, .natDexNum = NATIONAL_DEX_VENUSAUR, @@ -325,7 +329,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_GRASS), .abilities = { ABILITY_OVERGROW, ABILITY_NONE, ABILITY_CHLOROPHYLL }, - .bodyColor = BODY_COLOR_GREEN, + //.innates = { ABILITY_EFFECT_SPORE, ABILITY_FLOWER_VEIL, ABILITY_PROTOSYNTHESIS }, + .bodyColor = BODY_COLOR_GREEN, .speciesName = _("Venusaur"), .cryId = CRY_VENUSAUR, .natDexNum = NATIONAL_DEX_VENUSAUR, @@ -385,7 +390,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_DRAGON), .abilities = { ABILITY_BLAZE, ABILITY_NONE, ABILITY_SOLAR_POWER }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_FLAME_BODY }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Charmander"), .cryId = CRY_CHARMANDER, .natDexNum = NATIONAL_DEX_CHARMANDER, @@ -455,7 +461,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_DRAGON), .abilities = { ABILITY_BLAZE, ABILITY_NONE, ABILITY_SOLAR_POWER }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_FLAME_BODY, ABILITY_BERSERK }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Charmeleon"), .cryId = CRY_CHARMELEON, .natDexNum = NATIONAL_DEX_CHARMELEON, @@ -533,7 +540,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_DRAGON), .abilities = { ABILITY_BLAZE, ABILITY_NONE, ABILITY_SOLAR_POWER }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_FLAME_BODY, ABILITY_BERSERK, ABILITY_RECKLESS }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Charizard"), .cryId = CRY_CHARIZARD, .natDexNum = NATIONAL_DEX_CHARIZARD, @@ -602,7 +610,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_DRAGON), .abilities = { ABILITY_TOUGH_CLAWS, ABILITY_TOUGH_CLAWS, ABILITY_TOUGH_CLAWS }, - .bodyColor = BODY_COLOR_BLACK, + //.innates = { ABILITY_FLAME_BODY, ABILITY_BERSERK, ABILITY_RECKLESS }, + .bodyColor = BODY_COLOR_BLACK, .speciesName = _("Charizard"), .cryId = CRY_CHARIZARD_MEGA_X, .natDexNum = NATIONAL_DEX_CHARIZARD, @@ -669,7 +678,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_DRAGON), .abilities = { ABILITY_DROUGHT, ABILITY_DROUGHT, ABILITY_DROUGHT }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_SOLAR_POWER, ABILITY_BERSERK, ABILITY_SPEED_BOOST }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Charizard"), .cryId = CRY_CHARIZARD_MEGA_Y, .natDexNum = NATIONAL_DEX_CHARIZARD, @@ -739,7 +749,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_DRAGON), .abilities = { ABILITY_BLAZE, ABILITY_NONE, ABILITY_SOLAR_POWER }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_TURBOBLAZE, ABILITY_BERSERK, ABILITY_RECKLESS }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Charizard"), .cryId = CRY_CHARIZARD, .natDexNum = NATIONAL_DEX_CHARIZARD, @@ -799,7 +810,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_WATER_1), .abilities = { ABILITY_TORRENT, ABILITY_NONE, ABILITY_RAIN_DISH }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_SHELL_ARMOR }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Squirtle"), .cryId = CRY_SQUIRTLE, .natDexNum = NATIONAL_DEX_SQUIRTLE, @@ -870,7 +882,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_WATER_1), .abilities = { ABILITY_TORRENT, ABILITY_NONE, ABILITY_RAIN_DISH }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_SHELL_ARMOR, ABILITY_SWIFT_SWIM }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Wartortle"), .cryId = CRY_WARTORTLE, .natDexNum = NATIONAL_DEX_WARTORTLE, @@ -948,7 +961,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_WATER_1), .abilities = { ABILITY_TORRENT, ABILITY_NONE, ABILITY_RAIN_DISH }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_SHELL_ARMOR, ABILITY_BULLETPROOF, ABILITY_ROCK_HEAD }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Blastoise"), .cryId = CRY_BLASTOISE, .natDexNum = NATIONAL_DEX_BLASTOISE, @@ -1020,7 +1034,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_WATER_1), .abilities = { ABILITY_MEGA_LAUNCHER, ABILITY_MEGA_LAUNCHER, ABILITY_MEGA_LAUNCHER }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_SHELL_ARMOR, ABILITY_BULLETPROOF, ABILITY_SHEER_FORCE }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Blastoise"), .cryId = CRY_BLASTOISE_MEGA, .natDexNum = NATIONAL_DEX_BLASTOISE, @@ -1090,7 +1105,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_WATER_1), .abilities = { ABILITY_TORRENT, ABILITY_NONE, ABILITY_RAIN_DISH }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_SHELL_ARMOR, ABILITY_DRIZZLE, ABILITY_STAMINA }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Blastoise"), .cryId = CRY_BLASTOISE, .natDexNum = NATIONAL_DEX_BLASTOISE, @@ -1150,7 +1166,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_BUG), .abilities = { ABILITY_SHIELD_DUST, ABILITY_NONE, ABILITY_RUN_AWAY }, - .bodyColor = BODY_COLOR_GREEN, + //.innates = { ABILITY_STENCH, ABILITY_SUCTION_CUPS }, + .bodyColor = BODY_COLOR_GREEN, .speciesName = _("Caterpie"), .cryId = CRY_CATERPIE, .natDexNum = NATIONAL_DEX_CATERPIE, @@ -1224,7 +1241,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_BUG), .abilities = { ABILITY_SHED_SKIN, ABILITY_NONE, ABILITY_NONE }, - .bodyColor = BODY_COLOR_GREEN, + //.innates = { ABILITY_SHELL_ARMOR }, + .bodyColor = BODY_COLOR_GREEN, .speciesName = _("Metapod"), .cryId = CRY_METAPOD, .natDexNum = NATIONAL_DEX_METAPOD, @@ -1304,7 +1322,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_BUG), .abilities = { ABILITY_COMPOUND_EYES, ABILITY_NONE, ABILITY_TINTED_LENS }, - .bodyColor = BODY_COLOR_WHITE, + //.innates = { ABILITY_SHIELD_DUST, ABILITY_EFFECT_SPORE, ABILITY_HARVEST }, + .bodyColor = BODY_COLOR_WHITE, .speciesName = _("Butterfree"), .cryId = CRY_BUTTERFREE, .natDexNum = NATIONAL_DEX_BUTTERFREE, @@ -1400,7 +1419,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_BUG), .abilities = { ABILITY_COMPOUND_EYES, ABILITY_NONE, ABILITY_TINTED_LENS }, - .bodyColor = BODY_COLOR_WHITE, + //.innates = { ABILITY_SHIELD_DUST, ABILITY_EFFECT_SPORE, ABILITY_DAZZLING }, + .bodyColor = BODY_COLOR_WHITE, .speciesName = _("Butterfree"), .cryId = CRY_BUTTERFREE, .natDexNum = NATIONAL_DEX_BUTTERFREE, @@ -1461,7 +1481,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_BUG), .abilities = { ABILITY_SHIELD_DUST, ABILITY_NONE, ABILITY_RUN_AWAY }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_POISON_POINT }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Weedle"), .cryId = CRY_WEEDLE, .natDexNum = NATIONAL_DEX_WEEDLE, @@ -1535,7 +1556,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_BUG), .abilities = { ABILITY_SHED_SKIN, ABILITY_NONE, ABILITY_NONE }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_POISON_POINT, ABILITY_SHELL_ARMOR }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Kakuna"), .cryId = CRY_KAKUNA, .natDexNum = NATIONAL_DEX_KAKUNA, @@ -1623,7 +1645,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_BUG), .abilities = { ABILITY_SWARM, ABILITY_NONE, ABILITY_SNIPER }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_POISON_POINT, ABILITY_MERCILESS }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Beedrill"), .cryId = CRY_BEEDRILL, .natDexNum = NATIONAL_DEX_BEEDRILL, @@ -1700,7 +1723,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_BUG), .abilities = { ABILITY_ADAPTABILITY, ABILITY_ADAPTABILITY, ABILITY_ADAPTABILITY }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_POISON_POINT, ABILITY_MERCILESS, ABILITY_SKILL_LINK }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Beedrill"), .cryId = CRY_BEEDRILL_MEGA, .natDexNum = NATIONAL_DEX_BEEDRILL, @@ -1776,7 +1800,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_KEEN_EYE, ABILITY_NONE, ABILITY_BIG_PECKS }, #endif - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_RUN_AWAY }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Pidgey"), .cryId = CRY_PIDGEY, .natDexNum = NATIONAL_DEX_PIDGEY, @@ -1850,7 +1875,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_KEEN_EYE, ABILITY_NONE, ABILITY_BIG_PECKS }, #endif - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_VITAL_SPIRIT, ABILITY_TOUGH_CLAWS }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Pidgeotto"), .cryId = CRY_PIDGEOTTO, .natDexNum = NATIONAL_DEX_PIDGEOTTO, @@ -1933,7 +1959,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_KEEN_EYE, ABILITY_NONE, ABILITY_BIG_PECKS }, #endif - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_VITAL_SPIRIT, ABILITY_TOUGH_CLAWS, ABILITY_QUICK_DRAW }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Pidgeot"), .cryId = CRY_PIDGEOT, .natDexNum = NATIONAL_DEX_PIDGEOT, @@ -2003,7 +2030,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FLYING), .abilities = { ABILITY_NO_GUARD, ABILITY_NO_GUARD, ABILITY_NO_GUARD }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_VITAL_SPIRIT, ABILITY_TOUGH_CLAWS, ABILITY_GALE_WINGS }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Pidgeot"), .cryId = CRY_PIDGEOT_MEGA, .natDexNum = NATIONAL_DEX_PIDGEOT, @@ -2074,7 +2102,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_RUN_AWAY, ABILITY_GUTS, ABILITY_HUSTLE }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_HYPER_CUTTER }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Rattata"), .cryId = CRY_RATTATA, .natDexNum = NATIONAL_DEX_RATTATA, @@ -2158,7 +2187,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_RUN_AWAY, ABILITY_GUTS, ABILITY_HUSTLE }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_HYPER_CUTTER, ABILITY_STRONG_JAW, ABILITY_SWIFT_SWIM }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Raticate"), .cryId = CRY_RATICATE, .natDexNum = NATIONAL_DEX_RATICATE, @@ -2250,7 +2280,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_GLUTTONY, ABILITY_HUSTLE, ABILITY_THICK_FAT }, - .bodyColor = BODY_COLOR_BLACK, + //.innates = { ABILITY_CHEEK_POUCH }, + .bodyColor = BODY_COLOR_BLACK, .speciesName = _("Rattata"), .cryId = CRY_RATTATA, .natDexNum = NATIONAL_DEX_RATTATA, @@ -2319,7 +2350,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_GLUTTONY, ABILITY_HUSTLE, ABILITY_THICK_FAT }, - .bodyColor = BODY_COLOR_BLACK, + //.innates = { ABILITY_CHEEK_POUCH, ABILITY_HARVEST }, + .bodyColor = BODY_COLOR_BLACK, .speciesName = _("Raticate"), .cryId = CRY_RATICATE, .natDexNum = NATIONAL_DEX_RATICATE, @@ -2381,7 +2413,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_THICK_FAT, ABILITY_NONE, ABILITY_NONE }, - .bodyColor = BODY_COLOR_BLACK, + //.innates = { ABILITY_CHEEK_POUCH, ABILITY_HARVEST }, + .bodyColor = BODY_COLOR_BLACK, .speciesName = _("Raticate"), .cryId = CRY_RATICATE, .natDexNum = NATIONAL_DEX_RATICATE, @@ -2448,7 +2481,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FLYING), .abilities = { ABILITY_KEEN_EYE, ABILITY_NONE, ABILITY_SNIPER }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_HUSTLE }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Spearow"), .cryId = CRY_SPEAROW, .natDexNum = NATIONAL_DEX_SPEAROW, @@ -2519,7 +2553,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FLYING), .abilities = { ABILITY_KEEN_EYE, ABILITY_NONE, ABILITY_SNIPER }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_WIND_RIDER, ABILITY_MOXIE, ABILITY_EARLY_BIRD }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Fearow"), .cryId = CRY_FEAROW, .natDexNum = NATIONAL_DEX_FEAROW, @@ -2595,7 +2630,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD, EGG_GROUP_DRAGON), .abilities = { ABILITY_INTIMIDATE, ABILITY_SHED_SKIN, ABILITY_UNNERVE }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_LIMBER }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Ekans"), .cryId = CRY_EKANS, .natDexNum = NATIONAL_DEX_EKANS, @@ -2674,7 +2710,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD, EGG_GROUP_DRAGON), .abilities = { ABILITY_INTIMIDATE, ABILITY_SHED_SKIN, ABILITY_UNNERVE }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_LIMBER, ABILITY_MERCILESS }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Arbok"), .cryId = CRY_ARBOK, .natDexNum = NATIONAL_DEX_ARBOK, @@ -2744,7 +2781,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_RECKLESS }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Pichu"), .cryId = CRY_PICHU, .natDexNum = NATIONAL_DEX_PICHU, @@ -2812,7 +2850,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_NONE }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_RECKLESS, ABILITY_COMPETITIVE }, + .bodyColor = BODY_COLOR_YELLOW, .noFlip = TRUE, .speciesName = _("Pichu"), .cryId = CRY_PICHU, @@ -2895,7 +2934,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD, EGG_GROUP_FAIRY), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_BATTERY }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Pikachu"), .cryId = CRY_PIKACHU, .natDexNum = NATIONAL_DEX_PIKACHU, @@ -2983,7 +3023,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_BATTERY }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Pikachu"), .cryId = CRY_PIKACHU, .natDexNum = NATIONAL_DEX_PIKACHU, @@ -3035,7 +3076,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_BATTERY }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Pikachu"), .cryId = CRY_PIKACHU, .natDexNum = NATIONAL_DEX_PIKACHU, @@ -3087,7 +3129,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_BATTERY }, + .bodyColor = BODY_COLOR_YELLOW, .noFlip = TRUE, .speciesName = _("Pikachu"), .cryId = CRY_PIKACHU, @@ -3140,7 +3183,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_BATTERY }, + .bodyColor = BODY_COLOR_YELLOW, .noFlip = TRUE, .speciesName = _("Pikachu"), .cryId = CRY_PIKACHU, @@ -3193,7 +3237,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_BATTERY }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Pikachu"), .cryId = CRY_PIKACHU, .natDexNum = NATIONAL_DEX_PIKACHU, @@ -3245,7 +3290,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_BATTERY }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Pikachu"), .cryId = CRY_PIKACHU, .natDexNum = NATIONAL_DEX_PIKACHU, @@ -3300,7 +3346,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_BATTERY, ABILITY_BATTLE_BOND }, + .bodyColor = BODY_COLOR_YELLOW, .noFlip = TRUE, .speciesName = _("Pikachu"), .cryId = CRY_PIKACHU, @@ -3357,7 +3404,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_BATTERY, ABILITY_BATTLE_BOND }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Pikachu"), .cryId = CRY_PIKACHU, .natDexNum = NATIONAL_DEX_PIKACHU, @@ -3413,7 +3461,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_BATTERY, ABILITY_BATTLE_BOND }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Pikachu"), .cryId = CRY_PIKACHU, .natDexNum = NATIONAL_DEX_PIKACHU, @@ -3469,7 +3518,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_BATTERY, ABILITY_BATTLE_BOND }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Pikachu"), .cryId = CRY_PIKACHU, .natDexNum = NATIONAL_DEX_PIKACHU, @@ -3525,7 +3575,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_BATTERY, ABILITY_BATTLE_BOND }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Pikachu"), .cryId = CRY_PIKACHU, .natDexNum = NATIONAL_DEX_PIKACHU, @@ -3581,7 +3632,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_BATTERY, ABILITY_BATTLE_BOND }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Pikachu"), .cryId = CRY_PIKACHU, .natDexNum = NATIONAL_DEX_PIKACHU, @@ -3637,7 +3689,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_BATTERY, ABILITY_BATTLE_BOND }, + .bodyColor = BODY_COLOR_YELLOW, .noFlip = TRUE, .speciesName = _("Pikachu"), .cryId = CRY_PIKACHU, @@ -3694,7 +3747,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_BATTERY, ABILITY_BATTLE_BOND }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Pikachu"), .cryId = CRY_PIKACHU, .natDexNum = NATIONAL_DEX_PIKACHU, @@ -3753,7 +3807,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD, EGG_GROUP_FAIRY), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_BATTERY, ABILITY_TRANSISTOR }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Pikachu"), .cryId = CRY_PIKACHU, .natDexNum = NATIONAL_DEX_PIKACHU, @@ -3812,7 +3867,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_BATTERY }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Pikachu"), .cryId = CRY_PIKACHU, .natDexNum = NATIONAL_DEX_PIKACHU, @@ -3900,7 +3956,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD, EGG_GROUP_FAIRY), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_TERAVOLT, ABILITY_TRANSISTOR }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Raichu"), .cryId = CRY_RAICHU, .natDexNum = NATIONAL_DEX_RAICHU, @@ -3982,7 +4039,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD, EGG_GROUP_FAIRY), .abilities = { ABILITY_SURGE_SURFER, ABILITY_NONE, ABILITY_NONE }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_PLUS, ABILITY_LEVITATE, ABILITY_ELECTRIC_SURGE }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Raichu"), .cryId = CRY_RAICHU, .natDexNum = NATIONAL_DEX_RAICHU, @@ -4052,7 +4110,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_SAND_VEIL, ABILITY_NONE, ABILITY_SAND_RUSH }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_SHELL_ARMOR }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Sandshrew"), .cryId = CRY_SANDSHREW, .natDexNum = NATIONAL_DEX_SANDSHREW, @@ -4125,7 +4184,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_SAND_VEIL, ABILITY_NONE, ABILITY_SAND_RUSH }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_SHELL_ARMOR, ABILITY_ROUGH_SKIN, ABILITY_SHARPNESS }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Sandslash"), .cryId = CRY_SANDSLASH, .natDexNum = NATIONAL_DEX_SANDSLASH, @@ -4197,7 +4257,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_SNOW_CLOAK, ABILITY_NONE, ABILITY_SLUSH_RUSH }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_SHELL_ARMOR }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Sandshrew"), .cryId = CRY_SANDSHREW, .natDexNum = NATIONAL_DEX_SANDSHREW, @@ -4265,7 +4326,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_SNOW_CLOAK, ABILITY_NONE, ABILITY_SLUSH_RUSH }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_SHELL_ARMOR, ABILITY_ROUGH_SKIN, ABILITY_REFRIGERATE }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Sandslash"), .cryId = CRY_SANDSLASH, .natDexNum = NATIONAL_DEX_SANDSLASH, @@ -4337,7 +4399,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_POISON_POINT, ABILITY_NONE, ABILITY_HUSTLE }, #endif - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_ANTICIPATION }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Nidoran♀"), .cryId = CRY_NIDORAN_F, .natDexNum = NATIONAL_DEX_NIDORAN_F, @@ -4417,7 +4480,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_POISON_POINT, ABILITY_NONE, ABILITY_HUSTLE }, #endif - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_ANTICIPATION, ABILITY_HEALER }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Nidorina"), .cryId = CRY_NIDORINA, .natDexNum = NATIONAL_DEX_NIDORINA, @@ -4496,7 +4560,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_POISON_POINT, ABILITY_NONE, ABILITY_SHEER_FORCE }, #endif - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_BULLETPROOF, ABILITY_HEALER, ABILITY_ROUGH_SKIN }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Nidoqueen"), .cryId = CRY_NIDOQUEEN, .natDexNum = NATIONAL_DEX_NIDOQUEEN, @@ -4566,7 +4631,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_POISON_POINT, ABILITY_NONE, ABILITY_HUSTLE }, #endif - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_ANTICIPATION }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Nidoran♂"), .cryId = CRY_NIDORAN_M, .natDexNum = NATIONAL_DEX_NIDORAN_M, @@ -4639,7 +4705,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_POISON_POINT, ABILITY_NONE, ABILITY_HUSTLE }, #endif - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_ANTICIPATION, ABILITY_FRIEND_GUARD }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Nidorino"), .cryId = CRY_NIDORINO, .natDexNum = NATIONAL_DEX_NIDORINO, @@ -4719,7 +4786,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_POISON_POINT, ABILITY_NONE, ABILITY_SHEER_FORCE }, #endif - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_TOUGH_CLAWS, ABILITY_FRIEND_GUARD, ABILITY_MOLD_BREAKER }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Nidoking"), .cryId = CRY_NIDOKING, .natDexNum = NATIONAL_DEX_NIDOKING, @@ -4802,7 +4870,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_CUTE_CHARM, ABILITY_NONE, ABILITY_FRIEND_GUARD }, #endif - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_DANCER }, + .bodyColor = BODY_COLOR_PINK, .noFlip = TRUE, .speciesName = _("Cleffa"), .cryId = CRY_CLEFFA, @@ -4879,7 +4948,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_CUTE_CHARM, ABILITY_NONE, ABILITY_FRIEND_GUARD }, #endif - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_DANCER, ABILITY_FAIRY_AURA }, + .bodyColor = BODY_COLOR_PINK, .noFlip = TRUE, .speciesName = _("Clefairy"), .cryId = CRY_CLEFAIRY, @@ -4961,7 +5031,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_CUTE_CHARM, ABILITY_NONE, ABILITY_UNAWARE }, #endif - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_DANCER, ABILITY_FAIRY_AURA, ABILITY_PIXILATE }, + .bodyColor = BODY_COLOR_PINK, .noFlip = TRUE, .speciesName = _("Clefable"), .cryId = CRY_CLEFABLE, @@ -5089,7 +5160,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_FLASH_FIRE, ABILITY_NONE, ABILITY_DROUGHT }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_SOLAR_POWER }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Vulpix"), .cryId = CRY_VULPIX, .natDexNum = NATIONAL_DEX_VULPIX, @@ -5163,7 +5235,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_FLASH_FIRE, ABILITY_NONE, ABILITY_DROUGHT }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_SOLAR_POWER, ABILITY_MAGIC_BOUNCE, ABILITY_CURSED_BODY }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Ninetales"), .cryId = CRY_NINETALES, .natDexNum = NATIONAL_DEX_NINETALES, @@ -5235,7 +5308,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_SNOW_CLOAK, ABILITY_NONE, ABILITY_SNOW_WARNING }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_CUTE_CHARM }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Vulpix"), .cryId = CRY_VULPIX, .natDexNum = NATIONAL_DEX_VULPIX, @@ -5304,7 +5378,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_SNOW_CLOAK, ABILITY_NONE, ABILITY_SNOW_WARNING }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_CUTE_CHARM, ABILITY_ICE_SCALES, ABILITY_ICE_BODY }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Ninetales"), .cryId = CRY_NINETALES, .natDexNum = NATIONAL_DEX_NINETALES, @@ -5383,7 +5458,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_CUTE_CHARM, ABILITY_NONE, ABILITY_FRIEND_GUARD }, #endif - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_TANGLED_FEET }, + .bodyColor = BODY_COLOR_PINK, .noFlip = TRUE, .speciesName = _("Igglybuff"), .cryId = CRY_IGGLYBUFF, @@ -5461,7 +5537,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_CUTE_CHARM, ABILITY_NONE, ABILITY_FRIEND_GUARD }, #endif - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_LIMBER, ABILITY_KEEN_EYE }, + .bodyColor = BODY_COLOR_PINK, .noFlip = TRUE, .speciesName = _("Jigglypuff"), .cryId = CRY_JIGGLYPUFF, @@ -5553,7 +5630,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_CUTE_CHARM, ABILITY_NONE, ABILITY_FRISK }, #endif - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_LIMBER, ABILITY_KEEN_EYE, ABILITY_INTIMIDATE }, + .bodyColor = BODY_COLOR_PINK, .noFlip = TRUE, .speciesName = _("Wigglytuff"), .cryId = CRY_WIGGLYTUFF, @@ -5625,7 +5703,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FLYING), .abilities = { ABILITY_INNER_FOCUS, ABILITY_NONE, ABILITY_INFILTRATOR }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_MINDS_EYE }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Zubat"), .cryId = CRY_ZUBAT, .natDexNum = NATIONAL_DEX_ZUBAT, @@ -5718,7 +5797,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FLYING), .abilities = { ABILITY_INNER_FOCUS, ABILITY_NONE, ABILITY_INFILTRATOR }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_MINDS_EYE, ABILITY_STRONG_JAW }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Golbat"), .cryId = CRY_GOLBAT, .natDexNum = NATIONAL_DEX_GOLBAT, @@ -5825,7 +5905,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FLYING), .abilities = { ABILITY_INNER_FOCUS, ABILITY_NONE, ABILITY_INFILTRATOR }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_MINDS_EYE, ABILITY_STRONG_JAW, ABILITY_QUICK_DRAW }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Crobat"), .cryId = CRY_CROBAT, .natDexNum = NATIONAL_DEX_CROBAT, @@ -5911,7 +5992,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_GRASS), .abilities = { ABILITY_CHLOROPHYLL, ABILITY_NONE, ABILITY_RUN_AWAY }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_TANGLED_FEET }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Oddish"), .cryId = CRY_ODDISH, .natDexNum = NATIONAL_DEX_ODDISH, @@ -5985,7 +6067,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_GRASS), .abilities = { ABILITY_CHLOROPHYLL, ABILITY_NONE, ABILITY_STENCH }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_SUPERSWEET_SYRUP, ABILITY_LINGERING_AROMA }, + .bodyColor = BODY_COLOR_BLUE, .noFlip = TRUE, .speciesName = _("Gloom"), .cryId = CRY_GLOOM, @@ -6081,7 +6164,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_GRASS), .abilities = { ABILITY_CHLOROPHYLL, ABILITY_NONE, ABILITY_EFFECT_SPORE }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_SUPERSWEET_SYRUP, ABILITY_LINGERING_AROMA, ABILITY_OVERGROW }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Vileplume"), .cryId = CRY_VILEPLUME, .natDexNum = NATIONAL_DEX_VILEPLUME, @@ -6170,7 +6254,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_GRASS), .abilities = { ABILITY_CHLOROPHYLL, ABILITY_NONE, ABILITY_HEALER }, - .bodyColor = BODY_COLOR_GREEN, + //.innates = { ABILITY_DANCER, ABILITY_DROUGHT, ABILITY_FLOWER_VEIL }, + .bodyColor = BODY_COLOR_GREEN, .speciesName = _("Bellossom"), .cryId = CRY_BELLOSSOM, .natDexNum = NATIONAL_DEX_BELLOSSOM, @@ -6259,7 +6344,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_EFFECT_SPORE, ABILITY_NONE, ABILITY_DAMP }, #endif - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_OWN_TEMPO }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Paras"), .cryId = CRY_PARAS, .natDexNum = NATIONAL_DEX_PARAS, @@ -6349,7 +6435,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_EFFECT_SPORE, ABILITY_NONE, ABILITY_DAMP }, #endif - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_OWN_TEMPO, ABILITY_OBLIVIOUS, ABILITY_VITAL_SPIRIT }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Parasect"), .cryId = CRY_PARASECT, .natDexNum = NATIONAL_DEX_PARASECT, @@ -6426,7 +6513,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_COMPOUND_EYES, ABILITY_NONE, ABILITY_RUN_AWAY }, #endif - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_POISON_TOUCH }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Venonat"), .cryId = CRY_VENONAT, .natDexNum = NATIONAL_DEX_VENONAT, @@ -6505,7 +6593,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_SHIELD_DUST, ABILITY_NONE, ABILITY_WONDER_SKIN }, #endif - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_POISON_TOUCH, ABILITY_LEVITATE, ABILITY_TOXIC_DEBRIS }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Venomoth"), .cryId = CRY_VENOMOTH, .natDexNum = NATIONAL_DEX_VENOMOTH, @@ -6595,7 +6684,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_SAND_VEIL, ABILITY_ARENA_TRAP, ABILITY_SAND_FORCE }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_INFILTRATOR, ABILITY_RUN_AWAY }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Diglett"), .cryId = CRY_DIGLETT, .natDexNum = NATIONAL_DEX_DIGLETT, @@ -6673,7 +6763,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_SAND_VEIL, ABILITY_ARENA_TRAP, ABILITY_SAND_FORCE }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_INFILTRATOR, ABILITY_RUN_AWAY, ABILITY_QUICK_DRAW }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Dugtrio"), .cryId = CRY_DUGTRIO, .natDexNum = NATIONAL_DEX_DUGTRIO, @@ -6751,7 +6842,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_SAND_VEIL, ABILITY_TANGLING_HAIR, ABILITY_SAND_FORCE }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_ROCK_HEAD }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Diglett"), .cryId = CRY_DIGLETT, .natDexNum = NATIONAL_DEX_DIGLETT, @@ -6819,7 +6911,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_SAND_VEIL, ABILITY_TANGLING_HAIR, ABILITY_SAND_FORCE }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_ROCK_HEAD, ABILITY_SAND_SPIT }, + .bodyColor = BODY_COLOR_BROWN, .noFlip = TRUE, .speciesName = _("Dugtrio"), .cryId = CRY_DUGTRIO, @@ -6895,7 +6988,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = MEOWTH_ABILITIES, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_SUPER_LUCK }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Meowth"), .cryId = CRY_MEOWTH, .natDexNum = NATIONAL_DEX_MEOWTH, @@ -6975,7 +7069,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_LIMBER, ABILITY_NONE, ABILITY_UNNERVE }, #endif - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_SUPER_LUCK, ABILITY_MOODY }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Persian"), .cryId = CRY_PERSIAN, .natDexNum = NATIONAL_DEX_PERSIAN, @@ -7045,7 +7140,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_PICKUP, ABILITY_TECHNICIAN, ABILITY_RATTLED }, - .bodyColor = BODY_COLOR_GRAY, + //.innates = { ABILITY_MOODY }, + .bodyColor = BODY_COLOR_GRAY, .speciesName = _("Meowth"), .cryId = CRY_MEOWTH, .natDexNum = NATIONAL_DEX_MEOWTH, @@ -7113,7 +7209,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_FUR_COAT, ABILITY_TECHNICIAN, ABILITY_RATTLED }, - .bodyColor = BODY_COLOR_GRAY, + //.innates = { ABILITY_MOODY, ABILITY_WONDER_SKIN }, + .bodyColor = BODY_COLOR_GRAY, .speciesName = _("Persian"), .cryId = CRY_PERSIAN, .natDexNum = NATIONAL_DEX_PERSIAN, @@ -7180,7 +7277,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_PICKUP, ABILITY_TOUGH_CLAWS, ABILITY_UNNERVE }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_HUSTLE }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Meowth"), .cryId = CRY_MEOWTH, .natDexNum = NATIONAL_DEX_MEOWTH, @@ -7247,7 +7345,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_BATTLE_ARMOR, ABILITY_TOUGH_CLAWS, ABILITY_STEELY_SPIRIT }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_HUSTLE, ABILITY_SHEER_FORCE }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Perrserker"), .cryId = CRY_PERRSERKER, .natDexNum = NATIONAL_DEX_PERRSERKER, @@ -7312,7 +7411,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = MEOWTH_ABILITIES, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_SUPER_LUCK, ABILITY_SNIPER }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Meowth"), .cryId = CRY_MEOWTH, .natDexNum = NATIONAL_DEX_MEOWTH, @@ -7373,7 +7473,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_1, EGG_GROUP_FIELD), .abilities = { ABILITY_DAMP, ABILITY_CLOUD_NINE, ABILITY_SWIFT_SWIM }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_BERSERK }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Psyduck"), .cryId = CRY_PSYDUCK, .natDexNum = NATIONAL_DEX_PSYDUCK, @@ -7444,7 +7545,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_1, EGG_GROUP_FIELD), .abilities = { ABILITY_DAMP, ABILITY_CLOUD_NINE, ABILITY_SWIFT_SWIM }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_ILLUMINATE, ABILITY_STORM_DRAIN, ABILITY_RAIN_DISH }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Golduck"), .cryId = CRY_GOLDUCK, .natDexNum = NATIONAL_DEX_GOLDUCK, @@ -7519,7 +7621,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_VITAL_SPIRIT, ABILITY_NONE, ABILITY_DEFIANT }, #endif - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_GUTS }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Mankey"), .cryId = CRY_MANKEY, .natDexNum = NATIONAL_DEX_MANKEY, @@ -7596,7 +7699,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_VITAL_SPIRIT, ABILITY_NONE, ABILITY_DEFIANT }, #endif - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_GUTS, ABILITY_RECKLESS }, + .bodyColor = BODY_COLOR_BROWN, .noFlip = TRUE, .speciesName = _("Primeape"), .cryId = CRY_PRIMEAPE, @@ -7672,7 +7776,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_VITAL_SPIRIT, ABILITY_INNER_FOCUS, ABILITY_DEFIANT }, - .bodyColor = BODY_COLOR_GRAY, + //.innates = { ABILITY_GUTS, ABILITY_RECKLESS, ABILITY_ANGER_POINT }, + .bodyColor = BODY_COLOR_GRAY, .noFlip = TRUE, .speciesName = _("Annihilape"), .cryId = CRY_ANNIHILAPE, @@ -7739,7 +7844,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_INTIMIDATE, ABILITY_FLASH_FIRE, ABILITY_JUSTIFIED }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_GUTS }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Growlithe"), .cryId = CRY_GROWLITHE, .natDexNum = NATIONAL_DEX_GROWLITHE, @@ -7813,7 +7919,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_INTIMIDATE, ABILITY_FLASH_FIRE, ABILITY_JUSTIFIED }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_GUTS, ABILITY_BLAZE, ABILITY_BATTLE_BOND }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Arcanine"), .cryId = CRY_ARCANINE, .natDexNum = NATIONAL_DEX_ARCANINE, @@ -7882,7 +7989,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_INTIMIDATE, ABILITY_FLASH_FIRE, ABILITY_ROCK_HEAD }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_STEADFAST }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Growlithe"), .cryId = CRY_GROWLITHE, .natDexNum = NATIONAL_DEX_GROWLITHE, @@ -7948,7 +8056,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_INTIMIDATE, ABILITY_FLASH_FIRE, ABILITY_ROCK_HEAD }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_STEADFAST, ABILITY_QUICK_FEET, ABILITY_STRONG_JAW }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Arcanine"), .cryId = CRY_ARCANINE, .natDexNum = NATIONAL_DEX_ARCANINE, @@ -8016,7 +8125,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_1), .abilities = { ABILITY_WATER_ABSORB, ABILITY_DAMP, ABILITY_SWIFT_SWIM }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_TANGLED_FEET }, + .bodyColor = BODY_COLOR_BLUE, .noFlip = TRUE, .speciesName = _("Poliwag"), .cryId = CRY_POLIWAG, @@ -8087,7 +8197,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_1), .abilities = { ABILITY_WATER_ABSORB, ABILITY_DAMP, ABILITY_SWIFT_SWIM }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_GUTS, ABILITY_WATER_VEIL }, + .bodyColor = BODY_COLOR_BLUE, .noFlip = TRUE, .speciesName = _("Poliwhirl"), .cryId = CRY_POLIWHIRL, @@ -8173,7 +8284,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_1), .abilities = { ABILITY_WATER_ABSORB, ABILITY_DAMP, ABILITY_SWIFT_SWIM }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_GUTS, ABILITY_WATER_VEIL, ABILITY_STAMINA }, + .bodyColor = BODY_COLOR_BLUE, .noFlip = TRUE, .speciesName = _("Poliwrath"), .cryId = CRY_POLIWRATH, @@ -8248,7 +8360,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_1), .abilities = { ABILITY_WATER_ABSORB, ABILITY_DAMP, ABILITY_DRIZZLE }, - .bodyColor = BODY_COLOR_GREEN, + //.innates = { ABILITY_SWIFT_SWIM, ABILITY_WATER_VEIL, ABILITY_LIQUID_VOICE }, + .bodyColor = BODY_COLOR_GREEN, .noFlip = TRUE, .speciesName = _("Politoed"), .cryId = CRY_POLITOED, @@ -8343,7 +8456,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_HUMAN_LIKE), .abilities = { ABILITY_SYNCHRONIZE, ABILITY_INNER_FOCUS, ABILITY_MAGIC_GUARD }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_ANTICIPATION }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Abra"), .cryId = CRY_ABRA, .natDexNum = NATIONAL_DEX_ABRA, @@ -8415,7 +8529,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_HUMAN_LIKE), .abilities = { ABILITY_SYNCHRONIZE, ABILITY_INNER_FOCUS, ABILITY_MAGIC_GUARD }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_FOREWARN, ABILITY_TELEPATHY }, + .bodyColor = BODY_COLOR_BROWN, .noFlip = TRUE, .speciesName = _("Kadabra"), .cryId = CRY_KADABRA, @@ -8515,7 +8630,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_HUMAN_LIKE), .abilities = { ABILITY_SYNCHRONIZE, ABILITY_INNER_FOCUS, ABILITY_MAGIC_GUARD }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_FOREWARN, ABILITY_TELEPATHY, ABILITY_PSYCHIC_SURGE }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Alakazam"), .cryId = CRY_ALAKAZAM, .natDexNum = NATIONAL_DEX_ALAKAZAM, @@ -8605,7 +8721,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_HUMAN_LIKE), .abilities = { ABILITY_TRACE, ABILITY_TRACE, ABILITY_TRACE }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_SYNCHRONIZE, ABILITY_LEVITATE, ABILITY_MAGIC_BOUNCE }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Alakazam"), .cryId = CRY_ALAKAZAM_MEGA, .natDexNum = NATIONAL_DEX_ALAKAZAM, @@ -8687,7 +8804,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_GUTS, ABILITY_NONE, ABILITY_STEADFAST }, #endif - .bodyColor = BODY_COLOR_GRAY, + //.innates = { ABILITY_SHEER_FORCE }, + .bodyColor = BODY_COLOR_GRAY, .speciesName = _("Machop"), .cryId = CRY_MACHOP, .natDexNum = NATIONAL_DEX_MACHOP, @@ -8763,7 +8881,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_GUTS, ABILITY_NONE, ABILITY_STEADFAST }, #endif - .bodyColor = BODY_COLOR_GRAY, + //.innates = { ABILITY_SHEER_FORCE, ABILITY_DEFIANT }, + .bodyColor = BODY_COLOR_GRAY, .noFlip = TRUE, .speciesName = _("Machoke"), .cryId = CRY_MACHOKE, @@ -8846,7 +8965,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_GUTS, ABILITY_NONE, ABILITY_STEADFAST }, #endif - .bodyColor = BODY_COLOR_GRAY, + //.innates = { ABILITY_SHEER_FORCE, ABILITY_DEFIANT, ABILITY_STAMINA }, + .bodyColor = BODY_COLOR_GRAY, .speciesName = _("Machamp"), .cryId = CRY_MACHAMP, .natDexNum = NATIONAL_DEX_MACHAMP, @@ -8925,7 +9045,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_GUTS, ABILITY_NONE, ABILITY_STEADFAST }, #endif - .bodyColor = BODY_COLOR_GRAY, + //.innates = { ABILITY_SHEER_FORCE, ABILITY_DEFIANT, ABILITY_MOLD_BREAKER }, + .bodyColor = BODY_COLOR_GRAY, .speciesName = _("Machamp"), .cryId = CRY_MACHAMP, .natDexNum = NATIONAL_DEX_MACHAMP, @@ -8985,7 +9106,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_GRASS), .abilities = { ABILITY_CHLOROPHYLL, ABILITY_NONE, ABILITY_GLUTTONY }, - .bodyColor = BODY_COLOR_GREEN, + //.innates = { ABILITY_TANGLED_FEET }, + .bodyColor = BODY_COLOR_GREEN, .speciesName = _("Bellsprout"), .cryId = CRY_BELLSPROUT, .natDexNum = NATIONAL_DEX_BELLSPROUT, @@ -9054,7 +9176,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_GRASS), .abilities = { ABILITY_CHLOROPHYLL, ABILITY_NONE, ABILITY_GLUTTONY }, - .bodyColor = BODY_COLOR_GREEN, + //.innates = { ABILITY_CORROSION, ABILITY_LIQUID_OOZE }, + .bodyColor = BODY_COLOR_GREEN, .noFlip = TRUE, .speciesName = _("Weepinbell"), .cryId = CRY_WEEPINBELL, @@ -9144,7 +9267,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_GRASS), .abilities = { ABILITY_CHLOROPHYLL, ABILITY_NONE, ABILITY_GLUTTONY }, - .bodyColor = BODY_COLOR_GREEN, + //.innates = { ABILITY_CORROSION, ABILITY_LIQUID_OOZE, ABILITY_SUPERSWEET_SYRUP }, + .bodyColor = BODY_COLOR_GREEN, .noFlip = TRUE, .speciesName = _("Victreebel"), .cryId = CRY_VICTREEBEL, @@ -9277,7 +9401,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_3), .abilities = { ABILITY_CLEAR_BODY, ABILITY_LIQUID_OOZE, ABILITY_RAIN_DISH }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_DRY_SKIN, ABILITY_ILLUMINATE }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Tentacool"), .cryId = CRY_TENTACOOL, .natDexNum = NATIONAL_DEX_TENTACOOL, @@ -9349,7 +9474,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_3), .abilities = { ABILITY_CLEAR_BODY, ABILITY_LIQUID_OOZE, ABILITY_RAIN_DISH }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_DRY_SKIN, ABILITY_ILLUMINATE, ABILITY_TANGLING_HAIR }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Tentacruel"), .cryId = CRY_TENTACRUEL, .natDexNum = NATIONAL_DEX_TENTACRUEL, @@ -9427,7 +9553,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MINERAL), .abilities = { ABILITY_ROCK_HEAD, ABILITY_STURDY, ABILITY_SAND_VEIL }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_ROCKY_PAYLOAD }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Geodude"), .cryId = CRY_GEODUDE, .natDexNum = NATIONAL_DEX_GEODUDE, @@ -9499,7 +9626,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MINERAL), .abilities = { ABILITY_ROCK_HEAD, ABILITY_STURDY, ABILITY_SAND_VEIL }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_ROCKY_PAYLOAD, ABILITY_EARTH_EATER }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Graveler"), .cryId = CRY_GRAVELER, .natDexNum = NATIONAL_DEX_GRAVELER, @@ -9580,7 +9708,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MINERAL), .abilities = { ABILITY_ROCK_HEAD, ABILITY_STURDY, ABILITY_SAND_VEIL }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_ROCKY_PAYLOAD, ABILITY_EARTH_EATER, ABILITY_BULLETPROOF }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Golem"), .cryId = CRY_GOLEM, .natDexNum = NATIONAL_DEX_GOLEM, @@ -9652,7 +9781,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MINERAL), .abilities = { ABILITY_MAGNET_PULL, ABILITY_STURDY, ABILITY_GALVANIZE }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_MINUS }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Geodude"), .cryId = CRY_GEODUDE, .natDexNum = NATIONAL_DEX_GEODUDE, @@ -9721,7 +9851,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MINERAL), .abilities = { ABILITY_MAGNET_PULL, ABILITY_STURDY, ABILITY_GALVANIZE }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_MINUS, ABILITY_STATIC, ABILITY_ILLUMINATE }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Graveler"), .cryId = CRY_GRAVELER, .natDexNum = NATIONAL_DEX_GRAVELER, @@ -9789,7 +9920,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MINERAL), .abilities = { ABILITY_MAGNET_PULL, ABILITY_STURDY, ABILITY_GALVANIZE }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_MINUS, ABILITY_STATIC, ABILITY_ROCKY_PAYLOAD }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Golem"), .cryId = CRY_GOLEM, .natDexNum = NATIONAL_DEX_GOLEM, @@ -9857,7 +9989,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_RUN_AWAY, ABILITY_FLASH_FIRE, ABILITY_FLAME_BODY }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_QUICK_FEET, ABILITY_TOUGH_CLAWS }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Ponyta"), .cryId = CRY_PONYTA, .natDexNum = NATIONAL_DEX_PONYTA, @@ -9927,7 +10060,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_RUN_AWAY, ABILITY_FLASH_FIRE, ABILITY_FLAME_BODY }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_SPEED_BOOST, ABILITY_TOUGH_CLAWS, ABILITY_RIVALRY }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Rapidash"), .cryId = CRY_RAPIDASH, .natDexNum = NATIONAL_DEX_RAPIDASH, @@ -9996,7 +10130,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_RUN_AWAY, ABILITY_PASTEL_VEIL, ABILITY_ANTICIPATION }, - .bodyColor = BODY_COLOR_WHITE, + //.innates = { ABILITY_HEALER, ABILITY_WONDER_SKIN }, + .bodyColor = BODY_COLOR_WHITE, .speciesName = _("Ponyta"), .cryId = CRY_PONYTA, .natDexNum = NATIONAL_DEX_PONYTA, @@ -10063,7 +10198,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_RUN_AWAY, ABILITY_PASTEL_VEIL, ABILITY_ANTICIPATION }, - .bodyColor = BODY_COLOR_WHITE, + //.innates = { ABILITY_HEALER, ABILITY_MAGIC_BOUNCE, ABILITY_DAZZLING }, + .bodyColor = BODY_COLOR_WHITE, .speciesName = _("Rapidash"), .cryId = CRY_RAPIDASH, .natDexNum = NATIONAL_DEX_RAPIDASH, @@ -10132,7 +10268,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_WATER_1), .abilities = { ABILITY_OBLIVIOUS, ABILITY_OWN_TEMPO, ABILITY_REGENERATOR }, - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_SIMPLE, ABILITY_UNAWARE, ABILITY_STALL }, + .bodyColor = BODY_COLOR_PINK, .speciesName = _("Slowpoke"), .cryId = CRY_SLOWPOKE, .natDexNum = NATIONAL_DEX_SLOWPOKE, @@ -10208,7 +10345,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_WATER_1), .abilities = { ABILITY_OBLIVIOUS, ABILITY_OWN_TEMPO, ABILITY_REGENERATOR }, - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_SIMPLE, ABILITY_UNAWARE, ABILITY_THICK_FAT }, + .bodyColor = BODY_COLOR_PINK, .speciesName = _("Slowbro"), .cryId = CRY_SLOWBRO, .natDexNum = NATIONAL_DEX_SLOWBRO, @@ -10278,7 +10416,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_WATER_1), .abilities = { ABILITY_OBLIVIOUS, ABILITY_OWN_TEMPO, ABILITY_REGENERATOR }, - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_ANALYTIC, ABILITY_INNER_FOCUS, ABILITY_MAGIC_BOUNCE }, + .bodyColor = BODY_COLOR_PINK, .speciesName = _("Slowking"), .cryId = CRY_SLOWKING, .natDexNum = NATIONAL_DEX_SLOWKING, @@ -10349,7 +10488,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_WATER_1), .abilities = { ABILITY_SHELL_ARMOR, ABILITY_SHELL_ARMOR, ABILITY_SHELL_ARMOR }, - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_SIMPLE, ABILITY_STAMINA, ABILITY_THICK_FAT }, + .bodyColor = BODY_COLOR_PINK, .speciesName = _("Slowbro"), .cryId = CRY_SLOWBRO, .natDexNum = NATIONAL_DEX_SLOWBRO, @@ -10419,7 +10559,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_WATER_1), .abilities = { ABILITY_GLUTTONY, ABILITY_OWN_TEMPO, ABILITY_REGENERATOR }, - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_MOODY, ABILITY_UNAWARE, ABILITY_STALL }, + .bodyColor = BODY_COLOR_PINK, .speciesName = _("Slowpoke"), .cryId = CRY_SLOWPOKE_GALAR, .natDexNum = NATIONAL_DEX_SLOWPOKE, @@ -10491,7 +10632,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_WATER_1), .abilities = { ABILITY_QUICK_DRAW, ABILITY_OWN_TEMPO, ABILITY_REGENERATOR }, - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_POISON_POINT, ABILITY_TOXIC_BOOST, ABILITY_POISON_HEAL }, + .bodyColor = BODY_COLOR_PINK, .noFlip = TRUE, .speciesName = _("Slowbro"), .cryId = CRY_SLOWBRO, @@ -10558,7 +10700,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_WATER_1), .abilities = { ABILITY_CURIOUS_MEDICINE, ABILITY_OWN_TEMPO, ABILITY_REGENERATOR }, - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_SYNCHRONIZE, ABILITY_TOXIC_BOOST, ABILITY_POISON_HEAL }, + .bodyColor = BODY_COLOR_PINK, .speciesName = _("Slowking"), .cryId = CRY_SLOWKING, .natDexNum = NATIONAL_DEX_SLOWKING, @@ -10630,7 +10773,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MINERAL), .abilities = { ABILITY_MAGNET_PULL, ABILITY_STURDY, ABILITY_ANALYTIC }, - .bodyColor = BODY_COLOR_GRAY, + //.innates = { ABILITY_PLUS, ABILITY_LEVITATE }, + .bodyColor = BODY_COLOR_GRAY, .speciesName = _("Magnemite"), .cryId = CRY_MAGNEMITE, .natDexNum = NATIONAL_DEX_MAGNEMITE, @@ -10702,7 +10846,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MINERAL), .abilities = { ABILITY_MAGNET_PULL, ABILITY_STURDY, ABILITY_ANALYTIC }, - .bodyColor = BODY_COLOR_GRAY, + //.innates = { ABILITY_PLUS, ABILITY_LEVITATE, ABILITY_ELECTRIC_SURGE }, + .bodyColor = BODY_COLOR_GRAY, .speciesName = _("Magneton"), .cryId = CRY_MAGNETON, .natDexNum = NATIONAL_DEX_MAGNETON, @@ -10786,7 +10931,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MINERAL), .abilities = { ABILITY_MAGNET_PULL, ABILITY_STURDY, ABILITY_ANALYTIC }, - .bodyColor = BODY_COLOR_GRAY, + //.innates = { ABILITY_PLUS, ABILITY_LEVITATE, ABILITY_ELECTRIC_SURGE }, + .bodyColor = BODY_COLOR_GRAY, .speciesName = _("Magnezone"), .cryId = CRY_MAGNEZONE, .natDexNum = NATIONAL_DEX_MAGNEZONE, @@ -10866,7 +11012,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FLYING, EGG_GROUP_FIELD), .abilities = { ABILITY_KEEN_EYE, ABILITY_INNER_FOCUS, ABILITY_DEFIANT }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_SHARPNESS }, + .bodyColor = BODY_COLOR_BROWN, .noFlip = TRUE, .speciesName = _("Farfetch'd"), .cryId = CRY_FARFETCHD, @@ -10947,7 +11094,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FLYING, EGG_GROUP_FIELD), .abilities = { ABILITY_STEADFAST, ABILITY_NONE, ABILITY_SCRAPPY }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_NO_GUARD, ABILITY_MOLD_BREAKER }, + .bodyColor = BODY_COLOR_BROWN, .noFlip = TRUE, .speciesName = _("Farfetch'd"), .cryId = CRY_FARFETCHD, @@ -11016,7 +11164,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FLYING, EGG_GROUP_FIELD), .abilities = { ABILITY_STEADFAST, ABILITY_NONE, ABILITY_SCRAPPY }, - .bodyColor = BODY_COLOR_WHITE, + //.innates = { ABILITY_SUPER_LUCK, ABILITY_SNIPER, ABILITY_LEAF_GUARD }, + .bodyColor = BODY_COLOR_WHITE, .noFlip = TRUE, .speciesName = _("Sirfetch'd"), .cryId = CRY_SIRFETCHD, @@ -11084,7 +11233,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FLYING), .abilities = { ABILITY_RUN_AWAY, ABILITY_EARLY_BIRD, ABILITY_TANGLED_FEET }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_QUICK_FEET, ABILITY_INNER_FOCUS }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Doduo"), .cryId = CRY_DODUO, .natDexNum = NATIONAL_DEX_DODUO, @@ -11175,7 +11325,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FLYING), .abilities = { ABILITY_RUN_AWAY, ABILITY_EARLY_BIRD, ABILITY_TANGLED_FEET }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_QUICK_FEET, ABILITY_MOODY, ABILITY_STAKEOUT }, + .bodyColor = BODY_COLOR_BROWN, .noFlip = TRUE, .speciesName = _("Dodrio"), .cryId = CRY_DODRIO, @@ -11266,7 +11417,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_THICK_FAT, ABILITY_NONE, ABILITY_ICE_BODY }, #endif - .bodyColor = BODY_COLOR_WHITE, + //.innates = { ABILITY_GUTS }, + .bodyColor = BODY_COLOR_WHITE, .speciesName = _("Seel"), .cryId = CRY_SEEL, .natDexNum = NATIONAL_DEX_SEEL, @@ -11340,7 +11492,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_THICK_FAT, ABILITY_NONE, ABILITY_ICE_BODY }, #endif - .bodyColor = BODY_COLOR_WHITE, + //.innates = { ABILITY_GUTS, ABILITY_SLUSH_RUSH, ABILITY_SNOW_CLOAK }, + .bodyColor = BODY_COLOR_WHITE, .speciesName = _("Dewgong"), .cryId = CRY_DEWGONG, .natDexNum = NATIONAL_DEX_DEWGONG, @@ -11410,7 +11563,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_AMORPHOUS), .abilities = { ABILITY_STENCH, ABILITY_STICKY_HOLD, ABILITY_POISON_TOUCH }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_LIQUID_OOZE, ABILITY_LIMBER }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Grimer"), .cryId = CRY_GRIMER, .natDexNum = NATIONAL_DEX_GRIMER, @@ -11484,7 +11638,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_AMORPHOUS), .abilities = { ABILITY_STENCH, ABILITY_STICKY_HOLD, ABILITY_POISON_TOUCH }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_LIQUID_OOZE, ABILITY_LIMBER, ABILITY_UNNERVE }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Muk"), .cryId = CRY_MUK, .natDexNum = NATIONAL_DEX_MUK, @@ -11557,7 +11712,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_AMORPHOUS), .abilities = { ABILITY_POISON_TOUCH, ABILITY_GLUTTONY, ABILITY_POWER_OF_ALCHEMY }, - .bodyColor = BODY_COLOR_GREEN, + //.innates = { ABILITY_LIQUID_OOZE, ABILITY_TOXIC_DEBRIS }, + .bodyColor = BODY_COLOR_GREEN, .speciesName = _("Grimer"), .cryId = CRY_GRIMER, .natDexNum = NATIONAL_DEX_GRIMER, @@ -11626,7 +11782,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_AMORPHOUS), .abilities = { ABILITY_POISON_TOUCH, ABILITY_GLUTTONY, ABILITY_POWER_OF_ALCHEMY }, - .bodyColor = BODY_COLOR_GREEN, + //.innates = { ABILITY_LIQUID_OOZE, ABILITY_TOXIC_DEBRIS, ABILITY_POISON_POINT }, + .bodyColor = BODY_COLOR_GREEN, .noFlip = TRUE, .speciesName = _("Muk"), .cryId = CRY_MUK, @@ -11701,7 +11858,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_SHELL_ARMOR, ABILITY_NONE, ABILITY_OVERCOAT }, #endif - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_BULLETPROOF }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Shellder"), .cryId = CRY_SHELLDER, .natDexNum = NATIONAL_DEX_SHELLDER, @@ -11778,7 +11936,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_SHELL_ARMOR, ABILITY_NONE, ABILITY_OVERCOAT }, #endif - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_BULLETPROOF, ABILITY_ROUGH_SKIN }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Cloyster"), .cryId = CRY_CLOYSTER, .natDexNum = NATIONAL_DEX_CLOYSTER, @@ -11848,7 +12007,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_AMORPHOUS), .abilities = { ABILITY_LEVITATE, ABILITY_NONE, ABILITY_NONE }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_POISON_TOUCH, ABILITY_PRESSURE }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Gastly"), .cryId = CRY_GASTLY, .natDexNum = NATIONAL_DEX_GASTLY, @@ -11922,7 +12082,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_AMORPHOUS), .abilities = { ABILITY_LEVITATE, ABILITY_NONE, ABILITY_NONE }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_POISON_TOUCH, ABILITY_CURSED_BODY }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Haunter"), .cryId = CRY_HAUNTER, .natDexNum = NATIONAL_DEX_HAUNTER, @@ -12011,7 +12172,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_AMORPHOUS), .abilities = GENGAR_ABILITIES, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_MOXIE, ABILITY_PRANKSTER, ABILITY_INFILTRATOR }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Gengar"), .cryId = CRY_GENGAR, .natDexNum = NATIONAL_DEX_GENGAR, @@ -12081,7 +12243,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_AMORPHOUS), .abilities = { ABILITY_SHADOW_TAG, ABILITY_SHADOW_TAG, ABILITY_SHADOW_TAG }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_MOXIE, ABILITY_MERCILESS, ABILITY_CURSED_BODY }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Gengar"), .cryId = CRY_GENGAR_MEGA, .natDexNum = NATIONAL_DEX_GENGAR, @@ -12151,7 +12314,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_AMORPHOUS), .abilities = GENGAR_ABILITIES, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_MOXIE, ABILITY_GLUTTONY, ABILITY_CURSED_BODY }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Gengar"), .cryId = CRY_GENGAR, .natDexNum = NATIONAL_DEX_GENGAR, @@ -12211,7 +12375,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MINERAL), .abilities = { ABILITY_ROCK_HEAD, ABILITY_STURDY, ABILITY_WEAK_ARMOR }, - .bodyColor = BODY_COLOR_GRAY, + //.innates = { ABILITY_EARTH_EATER }, + .bodyColor = BODY_COLOR_GRAY, .speciesName = _("Onix"), .cryId = CRY_ONIX, .natDexNum = NATIONAL_DEX_ONIX, @@ -12284,7 +12449,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MINERAL), .abilities = { ABILITY_ROCK_HEAD, ABILITY_STURDY, ABILITY_SHEER_FORCE }, - .bodyColor = BODY_COLOR_GRAY, + //.innates = { ABILITY_THERMAL_EXCHANGE, ABILITY_SOLID_ROCK }, + .bodyColor = BODY_COLOR_GRAY, .speciesName = _("Steelix"), .cryId = CRY_STEELIX, .natDexNum = NATIONAL_DEX_STEELIX, @@ -12371,7 +12537,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MINERAL), .abilities = { ABILITY_SAND_FORCE, ABILITY_SAND_FORCE, ABILITY_SAND_FORCE }, - .bodyColor = BODY_COLOR_GRAY, + //.innates = { ABILITY_ROCK_HEAD, ABILITY_SOLID_ROCK, ABILITY_RECKLESS }, + .bodyColor = BODY_COLOR_GRAY, .speciesName = _("Steelix"), .cryId = CRY_STEELIX_MEGA, .natDexNum = NATIONAL_DEX_STEELIX, @@ -12447,7 +12614,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_INSOMNIA, ABILITY_NONE, ABILITY_INNER_FOCUS }, #endif - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_GLUTTONY }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Drowzee"), .cryId = CRY_DROWZEE, .natDexNum = NATIONAL_DEX_DROWZEE, @@ -12524,7 +12692,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_INSOMNIA, ABILITY_NONE, ABILITY_INNER_FOCUS }, #endif - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_OWN_TEMPO, ABILITY_SYNCHRONIZE, ABILITY_PRESSURE }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Hypno"), .cryId = CRY_HYPNO, .natDexNum = NATIONAL_DEX_HYPNO, @@ -12611,7 +12780,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_3), .abilities = { ABILITY_HYPER_CUTTER, ABILITY_SHELL_ARMOR, ABILITY_SHEER_FORCE }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_REGENERATOR }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Krabby"), .cryId = CRY_KRABBY, .natDexNum = NATIONAL_DEX_KRABBY, @@ -12683,7 +12853,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_3), .abilities = { ABILITY_HYPER_CUTTER, ABILITY_SHELL_ARMOR, ABILITY_SHEER_FORCE }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_REGENERATOR, ABILITY_HUSTLE, ABILITY_MOLD_BREAKER }, + .bodyColor = BODY_COLOR_RED, .noFlip = TRUE, .speciesName = _("Kingler"), .cryId = CRY_KINGLER, @@ -12754,7 +12925,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_3), .abilities = { ABILITY_HYPER_CUTTER, ABILITY_SHELL_ARMOR, ABILITY_SHEER_FORCE }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_SHEER_FORCE, ABILITY_HUSTLE, ABILITY_MOLD_BREAKER }, + .bodyColor = BODY_COLOR_RED, .noFlip = TRUE, .speciesName = _("Kingler"), .cryId = CRY_KINGLER, @@ -12815,7 +12987,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MINERAL), .abilities = { ABILITY_SOUNDPROOF, ABILITY_STATIC, ABILITY_AFTERMATH }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_PLUS }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Voltorb"), .cryId = CRY_VOLTORB, .natDexNum = NATIONAL_DEX_VOLTORB, @@ -12893,7 +13066,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MINERAL), .abilities = { ABILITY_SOUNDPROOF, ABILITY_STATIC, ABILITY_AFTERMATH }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_PLUS, ABILITY_LIGHTNING_ROD, ABILITY_ELECTROMORPHOSIS }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Electrode"), .cryId = CRY_ELECTRODE, .natDexNum = NATIONAL_DEX_ELECTRODE, @@ -12964,7 +13138,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MINERAL), .abilities = { ABILITY_SOUNDPROOF, ABILITY_STATIC, ABILITY_AFTERMATH }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_MINUS }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Voltorb"), .cryId = CRY_VOLTORB, .natDexNum = NATIONAL_DEX_VOLTORB, @@ -13030,7 +13205,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MINERAL), .abilities = { ABILITY_SOUNDPROOF, ABILITY_STATIC, ABILITY_AFTERMATH }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_MINUS, ABILITY_BERSERK }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Electrode"), .cryId = CRY_ELECTRODE, .natDexNum = NATIONAL_DEX_ELECTRODE, @@ -13099,7 +13275,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_GRASS), .abilities = { ABILITY_CHLOROPHYLL, ABILITY_NONE, ABILITY_HARVEST }, - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_TELEPATHY }, + .bodyColor = BODY_COLOR_PINK, .noFlip = TRUE, .speciesName = _("Exeggcute"), .cryId = CRY_EXEGGCUTE, @@ -13188,7 +13365,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_GRASS), .abilities = { ABILITY_CHLOROPHYLL, ABILITY_NONE, ABILITY_HARVEST }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_TELEPATHY, ABILITY_LEAF_GUARD, ABILITY_SKILL_LINK }, + .bodyColor = BODY_COLOR_YELLOW, .noFlip = TRUE, .speciesName = _("Exeggutor"), .cryId = CRY_EXEGGUTOR, @@ -13257,7 +13435,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_GRASS), .abilities = { ABILITY_FRISK, ABILITY_NONE, ABILITY_HARVEST }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_TELEPATHY, ABILITY_KEEN_EYE, ABILITY_SKILL_LINK }, + .bodyColor = BODY_COLOR_YELLOW, .noFlip = TRUE, .speciesName = _("Exeggutor"), .cryId = CRY_EXEGGUTOR, @@ -13327,7 +13506,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER), .abilities = { ABILITY_ROCK_HEAD, ABILITY_LIGHTNING_ROD, ABILITY_BATTLE_ARMOR }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_LONG_REACH, ABILITY_MOODY }, + .bodyColor = BODY_COLOR_BROWN, .noFlip = TRUE, .speciesName = _("Cubone"), .cryId = CRY_CUBONE, @@ -13404,7 +13584,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER), .abilities = { ABILITY_ROCK_HEAD, ABILITY_LIGHTNING_ROD, ABILITY_BATTLE_ARMOR }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_LONG_REACH, ABILITY_RECKLESS, ABILITY_GUTS }, + .bodyColor = BODY_COLOR_BROWN, .noFlip = TRUE, .speciesName = _("Marowak"), .cryId = CRY_MAROWAK, @@ -13478,7 +13659,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER), .abilities = { ABILITY_CURSED_BODY, ABILITY_LIGHTNING_ROD, ABILITY_ROCK_HEAD }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_LONG_REACH, ABILITY_RECKLESS }, + .bodyColor = BODY_COLOR_PURPLE, .noFlip = TRUE, .speciesName = _("Marowak"), .cryId = CRY_MAROWAK, @@ -13541,7 +13723,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER), .abilities = { ABILITY_ROCK_HEAD, ABILITY_NONE, ABILITY_NONE }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_LONG_REACH, ABILITY_RECKLESS }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Marowak"), .cryId = CRY_MAROWAK, .natDexNum = NATIONAL_DEX_MAROWAK, @@ -13612,7 +13795,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_GUTS, ABILITY_NONE, ABILITY_VITAL_SPIRIT }, #endif - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_SCRAPPY }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Tyrogue"), .cryId = CRY_TYROGUE, .natDexNum = NATIONAL_DEX_TYROGUE, @@ -13687,7 +13871,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_LIMBER, ABILITY_NONE, ABILITY_UNBURDEN }, #endif - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_LONG_REACH, ABILITY_QUICK_FEET }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Hitmonlee"), .cryId = CRY_HITMONLEE, .natDexNum = NATIONAL_DEX_HITMONLEE, @@ -13758,7 +13943,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_KEEN_EYE, ABILITY_NONE, ABILITY_INNER_FOCUS }, #endif - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_TECHNICIAN, ABILITY_GUTS }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Hitmonchan"), .cryId = CRY_HITMONCHAN, .natDexNum = NATIONAL_DEX_HITMONCHAN, @@ -13838,7 +14024,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_INTIMIDATE, ABILITY_NONE, ABILITY_STEADFAST }, #endif - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_SKILL_LINK, ABILITY_OWN_TEMPO, ABILITY_INNER_FOCUS }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Hitmontop"), .cryId = CRY_HITMONTOP, .natDexNum = NATIONAL_DEX_HITMONTOP, @@ -13911,7 +14098,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER), .abilities = { ABILITY_OWN_TEMPO, ABILITY_OBLIVIOUS, ABILITY_CLOUD_NINE }, - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_STICKY_HOLD, ABILITY_PICKPOCKET }, + .bodyColor = BODY_COLOR_PINK, .speciesName = _("Lickitung"), .cryId = CRY_LICKITUNG, .natDexNum = NATIONAL_DEX_LICKITUNG, @@ -13983,7 +14171,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER), .abilities = { ABILITY_OWN_TEMPO, ABILITY_OBLIVIOUS, ABILITY_CLOUD_NINE }, - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_STICKY_HOLD, ABILITY_PICKPOCKET, ABILITY_LONG_REACH }, + .bodyColor = BODY_COLOR_PINK, .speciesName = _("Lickilicky"), .cryId = CRY_LICKILICKY, .natDexNum = NATIONAL_DEX_LICKILICKY, @@ -14064,7 +14253,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_LEVITATE, ABILITY_NONE, ABILITY_NONE }, #endif - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_AFTERMATH }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Koffing"), .cryId = CRY_KOFFING, .natDexNum = NATIONAL_DEX_KOFFING, @@ -14142,7 +14332,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_LEVITATE, ABILITY_NONE, ABILITY_NONE }, #endif - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_AFTERMATH, ABILITY_POISON_TOUCH }, + .bodyColor = BODY_COLOR_PURPLE, .noFlip = TRUE, .speciesName = _("Weezing"), .cryId = CRY_WEEZING, @@ -14216,7 +14407,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_AMORPHOUS), .abilities = { ABILITY_LEVITATE, ABILITY_NEUTRALIZING_GAS, ABILITY_MISTY_SURGE }, - .bodyColor = BODY_COLOR_GRAY, + //.innates = { ABILITY_NATURAL_CURE, ABILITY_HEALER, ABILITY_CURIOUS_MEDICINE }, + .bodyColor = BODY_COLOR_GRAY, .noFlip = TRUE, .speciesName = _("Weezing"), .cryId = CRY_WEEZING, @@ -14286,7 +14478,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_FIELD), .abilities = { ABILITY_LIGHTNING_ROD, ABILITY_ROCK_HEAD, ABILITY_RECKLESS }, - .bodyColor = BODY_COLOR_GRAY, + //.innates = { ABILITY_HUSTLE }, + .bodyColor = BODY_COLOR_GRAY, .speciesName = _("Rhyhorn"), .cryId = CRY_RHYHORN, .natDexNum = NATIONAL_DEX_RHYHORN, @@ -14370,7 +14563,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_FIELD), .abilities = { ABILITY_LIGHTNING_ROD, ABILITY_ROCK_HEAD, ABILITY_RECKLESS }, - .bodyColor = BODY_COLOR_GRAY, + //.innates = { ABILITY_TOUGH_CLAWS, ABILITY_BULLETPROOF }, + .bodyColor = BODY_COLOR_GRAY, .speciesName = _("Rhydon"), .cryId = CRY_RHYDON, .natDexNum = NATIONAL_DEX_RHYDON, @@ -14460,7 +14654,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_FIELD), .abilities = { ABILITY_LIGHTNING_ROD, ABILITY_SOLID_ROCK, ABILITY_RECKLESS }, - .bodyColor = BODY_COLOR_GRAY, + //.innates = { ABILITY_TOUGH_CLAWS, ABILITY_BULLETPROOF, ABILITY_ROCKY_PAYLOAD }, + .bodyColor = BODY_COLOR_GRAY, .speciesName = _("Rhyperior"), .cryId = CRY_RHYPERIOR, .natDexNum = NATIONAL_DEX_RHYPERIOR, @@ -14547,7 +14742,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_NATURAL_CURE, ABILITY_SERENE_GRACE, ABILITY_FRIEND_GUARD }, - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_PICKUP }, + .bodyColor = BODY_COLOR_PINK, .speciesName = _("Happiny"), .cryId = CRY_HAPPINY, .natDexNum = NATIONAL_DEX_HAPPINY, @@ -14619,7 +14815,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FAIRY), .abilities = { ABILITY_NATURAL_CURE, ABILITY_SERENE_GRACE, ABILITY_HEALER }, - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_HOSPITALITY, ABILITY_SYMBIOSIS }, + .bodyColor = BODY_COLOR_PINK, .speciesName = _("Chansey"), .cryId = CRY_CHANSEY, .natDexNum = NATIONAL_DEX_CHANSEY, @@ -14698,7 +14895,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FAIRY), .abilities = { ABILITY_NATURAL_CURE, ABILITY_SERENE_GRACE, ABILITY_HEALER }, - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_HOSPITALITY, ABILITY_SYMBIOSIS, ABILITY_TRIAGE }, + .bodyColor = BODY_COLOR_PINK, .speciesName = _("Blissey"), .cryId = CRY_BLISSEY, .natDexNum = NATIONAL_DEX_BLISSEY, @@ -14772,7 +14970,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_CHLOROPHYLL, ABILITY_NONE, ABILITY_REGENERATOR }, #endif - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_TANGLED_FEET, ABILITY_UNNERVE }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Tangela"), .cryId = CRY_TANGELA, .natDexNum = NATIONAL_DEX_TANGELA, @@ -14844,7 +15043,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_GRASS), .abilities = { ABILITY_CHLOROPHYLL, ABILITY_LEAF_GUARD, ABILITY_REGENERATOR }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_TANGLING_HAIR, ABILITY_UNNERVE, ABILITY_LONG_REACH }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Tangrowth"), .cryId = CRY_TANGROWTH, .natDexNum = NATIONAL_DEX_TANGROWTH, @@ -14929,7 +15129,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER), .abilities = { ABILITY_EARLY_BIRD, ABILITY_SCRAPPY, ABILITY_INNER_FOCUS }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_DAUNTLESS_SHIELD, ABILITY_ANGER_POINT }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Kangaskhan"), .cryId = CRY_KANGASKHAN, .natDexNum = NATIONAL_DEX_KANGASKHAN, @@ -15001,7 +15202,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER), .abilities = { ABILITY_PARENTAL_BOND, ABILITY_PARENTAL_BOND, ABILITY_PARENTAL_BOND }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_SCRAPPY, ABILITY_ANGER_POINT, ABILITY_INNER_FOCUS }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Kangaskhan"), .cryId = CRY_KANGASKHAN_MEGA, .natDexNum = NATIONAL_DEX_KANGASKHAN, @@ -15078,7 +15280,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_SWIFT_SWIM, ABILITY_NONE, ABILITY_DAMP }, #endif - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_KEEN_EYE, ABILITY_RUN_AWAY }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Horsea"), .cryId = CRY_HORSEA, .natDexNum = NATIONAL_DEX_HORSEA, @@ -15155,7 +15358,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_POISON_POINT, ABILITY_NONE, ABILITY_DAMP }, #endif - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_KEEN_EYE, ABILITY_SWIFT_SWIM }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Seadra"), .cryId = CRY_SEADRA, .natDexNum = NATIONAL_DEX_SEADRA, @@ -15242,7 +15446,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_SWIFT_SWIM, ABILITY_NONE, ABILITY_DAMP }, #endif - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_KEEN_EYE, ABILITY_MARVEL_SCALE, ABILITY_MULTISCALE }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Kingdra"), .cryId = CRY_KINGDRA, .natDexNum = NATIONAL_DEX_KINGDRA, @@ -15315,7 +15520,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_2), .abilities = { ABILITY_SWIFT_SWIM, ABILITY_WATER_VEIL, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_MARVEL_SCALE }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Goldeen"), .cryId = CRY_GOLDEEN, .natDexNum = NATIONAL_DEX_GOLDEEN, @@ -15404,7 +15610,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_2), .abilities = { ABILITY_SWIFT_SWIM, ABILITY_WATER_VEIL, ABILITY_LIGHTNING_ROD }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_MARVEL_SCALE, ABILITY_STORM_DRAIN, ABILITY_DAMP }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Seaking"), .cryId = CRY_SEAKING, .natDexNum = NATIONAL_DEX_SEAKING, @@ -15490,7 +15697,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_3), .abilities = { ABILITY_ILLUMINATE, ABILITY_NATURAL_CURE, ABILITY_ANALYTIC }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_REGENERATOR }, + .bodyColor = BODY_COLOR_BROWN, .noFlip = TRUE, .speciesName = _("Staryu"), .cryId = CRY_STARYU, @@ -15563,7 +15771,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_3), .abilities = { ABILITY_ILLUMINATE, ABILITY_NATURAL_CURE, ABILITY_ANALYTIC }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_REGENERATOR, ABILITY_TINTED_LENS, ABILITY_WONDER_SKIN }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Starmie"), .cryId = CRY_STARMIE, .natDexNum = NATIONAL_DEX_STARMIE, @@ -15702,7 +15911,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_SOUNDPROOF, ABILITY_NONE, ABILITY_TECHNICIAN }, #endif - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_MIMICRY, ABILITY_SYNCHRONIZE, ABILITY_RUN_AWAY }, + .bodyColor = BODY_COLOR_PINK, .speciesName = _("Mime Jr."), .cryId = CRY_MIME_JR, .natDexNum = NATIONAL_DEX_MIME_JR, @@ -15780,7 +15990,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_HUMAN_LIKE), .abilities = { ABILITY_SOUNDPROOF, ABILITY_FILTER, ABILITY_TECHNICIAN }, - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_MIMICRY, ABILITY_SYNCHRONIZE, ABILITY_MAGIC_GUARD }, + .bodyColor = BODY_COLOR_PINK, .speciesName = _("Mr. Mime"), .cryId = CRY_MR_MIME, .natDexNum = NATIONAL_DEX_MR_MIME, @@ -15853,7 +16064,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_HUMAN_LIKE), .abilities = { ABILITY_VITAL_SPIRIT, ABILITY_SCREEN_CLEANER, ABILITY_ICE_BODY }, - .bodyColor = BODY_COLOR_WHITE, + //.innates = { ABILITY_FILTER, ABILITY_SNOW_CLOAK }, + .bodyColor = BODY_COLOR_WHITE, .speciesName = _("Mr. Mime"), .cryId = CRY_MR_MIME, .natDexNum = NATIONAL_DEX_MR_MIME, @@ -15920,7 +16132,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_HUMAN_LIKE), .abilities = { ABILITY_TANGLED_FEET, ABILITY_SCREEN_CLEANER, ABILITY_ICE_BODY }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_FILTER, ABILITY_SLUSH_RUSH, ABILITY_QUICK_FEET }, + .bodyColor = BODY_COLOR_PURPLE, .noFlip = TRUE, .speciesName = _("Mr. Rime"), .cryId = CRY_MR_RIME, @@ -15990,7 +16203,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_SWARM, ABILITY_NONE, ABILITY_STEADFAST }, #endif - .bodyColor = BODY_COLOR_GREEN, + //.innates = { ABILITY_HYPER_CUTTER, ABILITY_SHARPNESS, ABILITY_QUICK_DRAW }, + .bodyColor = BODY_COLOR_GREEN, .speciesName = _("Scyther"), .cryId = CRY_SCYTHER, .natDexNum = NATIONAL_DEX_SCYTHER, @@ -16094,7 +16308,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_SWARM, ABILITY_NONE, ABILITY_LIGHT_METAL }, #endif - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_HYPER_CUTTER, ABILITY_IRON_FIST, ABILITY_INTIMIDATE }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Scizor"), .cryId = CRY_SCIZOR, .natDexNum = NATIONAL_DEX_SCIZOR, @@ -16175,7 +16390,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_BUG), .abilities = { ABILITY_TECHNICIAN, ABILITY_TECHNICIAN, ABILITY_TECHNICIAN }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_DRY_SKIN, ABILITY_IRON_FIST, ABILITY_INTIMIDATE }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Scizor"), .cryId = CRY_SCIZOR_MEGA, .natDexNum = NATIONAL_DEX_SCIZOR, @@ -16246,7 +16462,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_BUG), .abilities = { ABILITY_SWARM, ABILITY_SHEER_FORCE, ABILITY_SHARPNESS }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_HYPER_CUTTER, ABILITY_HUSTLE, ABILITY_NO_GUARD }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Kleavor"), .cryId = CRY_KLEAVOR, .natDexNum = NATIONAL_DEX_KLEAVOR, @@ -16317,7 +16534,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_OBLIVIOUS, ABILITY_NONE, ABILITY_HYDRATION }, #endif - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_CUTE_CHARM }, + .bodyColor = BODY_COLOR_PINK, .speciesName = _("Smoochum"), .cryId = CRY_SMOOCHUM, .natDexNum = NATIONAL_DEX_SMOOCHUM, @@ -16391,7 +16609,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_OBLIVIOUS, ABILITY_NONE, ABILITY_DRY_SKIN }, #endif - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_CUTE_CHARM, ABILITY_DANCER, ABILITY_OWN_TEMPO }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Jynx"), .cryId = CRY_JYNX, .natDexNum = NATIONAL_DEX_JYNX, @@ -16464,7 +16683,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_VITAL_SPIRIT }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_MOTOR_DRIVE }, + .bodyColor = BODY_COLOR_YELLOW, .noFlip = TRUE, .speciesName = _("Elekid"), .cryId = CRY_ELEKID, @@ -16536,7 +16756,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_HUMAN_LIKE), .abilities = { ABILITY_STATIC, ABILITY_NONE, ABILITY_VITAL_SPIRIT }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_LIGHTNING_ROD, ABILITY_COMPETITIVE }, + .bodyColor = BODY_COLOR_YELLOW, .noFlip = TRUE, .speciesName = _("Electabuzz"), .cryId = CRY_ELECTABUZZ, @@ -16618,7 +16839,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_HUMAN_LIKE), .abilities = { ABILITY_MOTOR_DRIVE, ABILITY_NONE, ABILITY_VITAL_SPIRIT }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_PLUS, ABILITY_NO_GUARD, ABILITY_BERSERK }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Electivire"), .cryId = CRY_ELECTIVIRE, .natDexNum = NATIONAL_DEX_ELECTIVIRE, @@ -16690,7 +16912,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_FLAME_BODY, ABILITY_NONE, ABILITY_VITAL_SPIRIT }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_MAGMA_ARMOR }, + .bodyColor = BODY_COLOR_RED, .noFlip = TRUE, .speciesName = _("Magby"), .cryId = CRY_MAGBY, @@ -16763,7 +16986,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_HUMAN_LIKE), .abilities = { ABILITY_FLAME_BODY, ABILITY_NONE, ABILITY_VITAL_SPIRIT }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_MAGMA_ARMOR, ABILITY_BLAZE }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Magmar"), .cryId = CRY_MAGMAR, .natDexNum = NATIONAL_DEX_MAGMAR, @@ -16841,7 +17065,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_HUMAN_LIKE), .abilities = { ABILITY_FLAME_BODY, ABILITY_NONE, ABILITY_VITAL_SPIRIT }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_MAGMA_ARMOR, ABILITY_BLAZE, ABILITY_SOLAR_POWER }, + .bodyColor = BODY_COLOR_RED, .noFlip = TRUE, .speciesName = _("Magmortar"), .cryId = CRY_MAGMORTAR, @@ -16915,7 +17140,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_HYPER_CUTTER, ABILITY_NONE, ABILITY_MOXIE }, #endif - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_RIVALRY, ABILITY_GUTS }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Pinsir"), .cryId = CRY_PINSIR, .natDexNum = NATIONAL_DEX_PINSIR, @@ -16988,7 +17214,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_BUG), .abilities = { ABILITY_AERILATE, ABILITY_AERILATE, ABILITY_AERILATE }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_HUSTLE, ABILITY_NO_GUARD, ABILITY_SWARM }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Pinsir"), .cryId = CRY_PINSIR_MEGA, .natDexNum = NATIONAL_DEX_PINSIR, @@ -17066,7 +17293,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_INTIMIDATE, ABILITY_NONE, ABILITY_SHEER_FORCE }, #endif - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_HUSTLE, ABILITY_VITAL_SPIRIT }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Tauros"), .cryId = CRY_TAUROS, .natDexNum = NATIONAL_DEX_TAUROS, @@ -17137,7 +17365,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_INTIMIDATE, ABILITY_ANGER_POINT, ABILITY_CUD_CHEW }, - .bodyColor = BODY_COLOR_BLACK, + //.innates = { ABILITY_SHEER_FORCE, ABILITY_VITAL_SPIRIT }, + .bodyColor = BODY_COLOR_BLACK, .speciesName = _("Tauros"), .cryId = CRY_TAUROS, .natDexNum = NATIONAL_DEX_TAUROS, @@ -17203,7 +17432,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_INTIMIDATE, ABILITY_ANGER_POINT, ABILITY_CUD_CHEW }, - .bodyColor = BODY_COLOR_BLACK, + //.innates = { ABILITY_FLAME_BODY, ABILITY_VITAL_SPIRIT }, + .bodyColor = BODY_COLOR_BLACK, .speciesName = _("Tauros"), .cryId = CRY_TAUROS, .natDexNum = NATIONAL_DEX_TAUROS, @@ -17269,7 +17499,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_INTIMIDATE, ABILITY_ANGER_POINT, ABILITY_CUD_CHEW }, - .bodyColor = BODY_COLOR_BLACK, + //.innates = { ABILITY_THICK_FAT, ABILITY_VITAL_SPIRIT }, + .bodyColor = BODY_COLOR_BLACK, .speciesName = _("Tauros"), .cryId = CRY_TAUROS, .natDexNum = NATIONAL_DEX_TAUROS, @@ -17338,7 +17569,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_2, EGG_GROUP_DRAGON), .abilities = { ABILITY_SWIFT_SWIM, ABILITY_NONE, ABILITY_RATTLED }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_IMMUNITY }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Magikarp"), .cryId = CRY_MAGIKARP, .natDexNum = NATIONAL_DEX_MAGIKARP, @@ -17426,7 +17658,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_2, EGG_GROUP_DRAGON), .abilities = { ABILITY_INTIMIDATE, ABILITY_NONE, ABILITY_MOXIE }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_ANGER_POINT, ABILITY_STRONG_JAW }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Gyarados"), .cryId = CRY_GYARADOS, .natDexNum = NATIONAL_DEX_GYARADOS, @@ -17509,7 +17742,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_2, EGG_GROUP_DRAGON), .abilities = { ABILITY_MOLD_BREAKER, ABILITY_MOLD_BREAKER, ABILITY_MOLD_BREAKER }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_INTIMIDATE, ABILITY_LEVITATE, ABILITY_SHEER_FORCE }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Gyarados"), .cryId = CRY_GYARADOS_MEGA, .natDexNum = NATIONAL_DEX_GYARADOS, @@ -17583,7 +17817,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_WATER_1), .abilities = { ABILITY_WATER_ABSORB, ABILITY_SHELL_ARMOR, ABILITY_HYDRATION }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_WATER_VEIL, ABILITY_LIQUID_VOICE, ABILITY_HOSPITALITY }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Lapras"), .cryId = CRY_LAPRAS, .natDexNum = NATIONAL_DEX_LAPRAS, @@ -17655,7 +17890,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER, EGG_GROUP_WATER_1), .abilities = { ABILITY_WATER_ABSORB, ABILITY_SHELL_ARMOR, ABILITY_HYDRATION }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_ICE_SCALES, ABILITY_LIQUID_VOICE, ABILITY_HOSPITALITY }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Lapras"), .cryId = CRY_LAPRAS, .natDexNum = NATIONAL_DEX_LAPRAS, @@ -17718,7 +17954,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_DITTO), .abilities = { ABILITY_LIMBER, ABILITY_NONE, ABILITY_IMPOSTER }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_STICKY_HOLD }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Ditto"), .cryId = CRY_DITTO, .natDexNum = NATIONAL_DEX_DITTO, @@ -17797,7 +18034,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = EEVEE_ABILITIES, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_SCRAPPY }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Eevee"), .cryId = CRY_EEVEE, .natDexNum = NATIONAL_DEX_EEVEE, @@ -17893,7 +18131,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = EEVEE_ABILITIES, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_SCRAPPY, ABILITY_FLUFFY, ABILITY_NORMALIZE }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Eevee"), .cryId = CRY_EEVEE, .natDexNum = NATIONAL_DEX_EEVEE, @@ -17952,7 +18191,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_RUN_AWAY, ABILITY_ADAPTABILITY, ABILITY_ANTICIPATION }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_SCRAPPY }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Eevee"), .cryId = CRY_EEVEE, .natDexNum = NATIONAL_DEX_EEVEE, @@ -18028,7 +18268,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_WATER_ABSORB, ABILITY_WATER_ABSORB, ABILITY_HYDRATION }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_ADAPTABILITY, ABILITY_SWIFT_SWIM, ABILITY_RAIN_DISH }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Vaporeon"), .cryId = CRY_VAPOREON, .natDexNum = NATIONAL_DEX_VAPOREON, @@ -18094,7 +18335,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_VOLT_ABSORB, ABILITY_VOLT_ABSORB, ABILITY_QUICK_FEET }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_ADAPTABILITY, ABILITY_TECHNICIAN, ABILITY_ROUGH_SKIN }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Jolteon"), .cryId = CRY_JOLTEON, .natDexNum = NATIONAL_DEX_JOLTEON, @@ -18160,7 +18402,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_FLASH_FIRE, ABILITY_FLASH_FIRE, ABILITY_GUTS }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_ADAPTABILITY, ABILITY_FLAME_BODY, ABILITY_GUTS }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Flareon"), .cryId = CRY_FLAREON, .natDexNum = NATIONAL_DEX_FLAREON, @@ -18227,7 +18470,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_SYNCHRONIZE, ABILITY_SYNCHRONIZE, ABILITY_MAGIC_BOUNCE }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_ADAPTABILITY, ABILITY_WONDER_SKIN, ABILITY_ANTICIPATION }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Espeon"), .cryId = CRY_ESPEON, .natDexNum = NATIONAL_DEX_ESPEON, @@ -18294,7 +18538,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_SYNCHRONIZE, ABILITY_SYNCHRONIZE, ABILITY_INNER_FOCUS }, - .bodyColor = BODY_COLOR_BLACK, + //.innates = { ABILITY_ADAPTABILITY, ABILITY_POISON_TOUCH, ABILITY_INFILTRATOR }, + .bodyColor = BODY_COLOR_BLACK, .speciesName = _("Umbreon"), .cryId = CRY_UMBREON, .natDexNum = NATIONAL_DEX_UMBREON, @@ -18362,7 +18607,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_LEAF_GUARD, ABILITY_LEAF_GUARD, ABILITY_CHLOROPHYLL }, - .bodyColor = BODY_COLOR_GREEN, + //.innates = { ABILITY_ADAPTABILITY, ABILITY_PROTOSYNTHESIS, ABILITY_AROMA_VEIL }, + .bodyColor = BODY_COLOR_GREEN, .speciesName = _("Leafeon"), .cryId = CRY_LEAFEON, .natDexNum = NATIONAL_DEX_LEAFEON, @@ -18429,7 +18675,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_SNOW_CLOAK, ABILITY_SNOW_CLOAK, ABILITY_ICE_BODY }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_ADAPTABILITY, ABILITY_ROUGH_SKIN, ABILITY_DAZZLING }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Glaceon"), .cryId = CRY_GLACEON, .natDexNum = NATIONAL_DEX_GLACEON, @@ -18498,7 +18745,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FIELD), .abilities = { ABILITY_CUTE_CHARM, ABILITY_CUTE_CHARM, ABILITY_PIXILATE }, - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_ADAPTABILITY, ABILITY_TANGLING_HAIR, ABILITY_PASTEL_VEIL }, + .bodyColor = BODY_COLOR_PINK, .noFlip = TRUE, .speciesName = _("Sylveon"), .cryId = CRY_SYLVEON, @@ -18573,7 +18821,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_TRACE, ABILITY_NONE, ABILITY_ANALYTIC }, #endif - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_IMMUNITY, ABILITY_CLEAR_BODY }, + .bodyColor = BODY_COLOR_PINK, .speciesName = _("Porygon"), .cryId = CRY_PORYGON, .natDexNum = NATIONAL_DEX_PORYGON, @@ -18650,7 +18899,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_TRACE, ABILITY_NONE, ABILITY_ANALYTIC }, #endif - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_IMMUNITY, ABILITY_CLEAR_BODY, ABILITY_ADAPTABILITY }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Porygon2"), .cryId = CRY_PORYGON2, .natDexNum = NATIONAL_DEX_PORYGON2, @@ -18731,7 +18981,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MINERAL), .abilities = { ABILITY_ADAPTABILITY, ABILITY_DOWNLOAD, ABILITY_ANALYTIC }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_LEVITATE, ABILITY_MOODY, ABILITY_TANGLED_FEET }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Porygon-Z"), .cryId = CRY_PORYGON_Z, .natDexNum = NATIONAL_DEX_PORYGON_Z, @@ -18810,7 +19061,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_1, EGG_GROUP_WATER_3), .abilities = { ABILITY_SWIFT_SWIM, ABILITY_SHELL_ARMOR, ABILITY_WEAK_ARMOR }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_LIMBER }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Omanyte"), .cryId = CRY_OMANYTE, .natDexNum = NATIONAL_DEX_OMANYTE, @@ -18884,7 +19136,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_1, EGG_GROUP_WATER_3), .abilities = { ABILITY_SWIFT_SWIM, ABILITY_SHELL_ARMOR, ABILITY_WEAK_ARMOR }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_LIMBER, ABILITY_TANGLING_HAIR, ABILITY_ROUGH_SKIN }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Omastar"), .cryId = CRY_OMASTAR, .natDexNum = NATIONAL_DEX_OMASTAR, @@ -18960,7 +19213,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_1, EGG_GROUP_WATER_3), .abilities = { ABILITY_SWIFT_SWIM, ABILITY_BATTLE_ARMOR, ABILITY_WEAK_ARMOR }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_STURDY }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Kabuto"), .cryId = CRY_KABUTO, .natDexNum = NATIONAL_DEX_KABUTO, @@ -19040,7 +19294,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_1, EGG_GROUP_WATER_3), .abilities = { ABILITY_SWIFT_SWIM, ABILITY_BATTLE_ARMOR, ABILITY_WEAK_ARMOR }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_HYPER_CUTTER, ABILITY_SHARPNESS, ABILITY_MOXIE }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Kabutops"), .cryId = CRY_KABUTOPS, .natDexNum = NATIONAL_DEX_KABUTOPS, @@ -19110,7 +19365,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FLYING), .abilities = { ABILITY_ROCK_HEAD, ABILITY_PRESSURE, ABILITY_UNNERVE }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_STRONG_JAW, ABILITY_GUTS }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Aerodactyl"), .cryId = CRY_AERODACTYL, .natDexNum = NATIONAL_DEX_AERODACTYL, @@ -19183,7 +19439,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_FLYING), .abilities = { ABILITY_TOUGH_CLAWS, ABILITY_TOUGH_CLAWS, ABILITY_TOUGH_CLAWS }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_ROCK_HEAD, ABILITY_ROUGH_SKIN, ABILITY_RECKLESS }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Aerodactyl"), .cryId = CRY_AERODACTYL_MEGA, .natDexNum = NATIONAL_DEX_AERODACTYL, @@ -19258,7 +19515,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_PICKUP, ABILITY_THICK_FAT, ABILITY_GLUTTONY }, - .bodyColor = BODY_COLOR_BLACK, + //.innates = { ABILITY_HARVEST }, + .bodyColor = BODY_COLOR_BLACK, .speciesName = _("Munchlax"), .cryId = CRY_MUNCHLAX, .natDexNum = NATIONAL_DEX_MUNCHLAX, @@ -19330,7 +19588,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER), .abilities = { ABILITY_IMMUNITY, ABILITY_THICK_FAT, ABILITY_GLUTTONY }, - .bodyColor = BODY_COLOR_BLACK, + //.innates = { ABILITY_OBLIVIOUS, ABILITY_GUTS, ABILITY_OWN_TEMPO }, + .bodyColor = BODY_COLOR_BLACK, .speciesName = _("Snorlax"), .cryId = CRY_SNORLAX, .natDexNum = NATIONAL_DEX_SNORLAX, @@ -19403,7 +19662,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_MONSTER), .abilities = { ABILITY_IMMUNITY, ABILITY_THICK_FAT, ABILITY_GLUTTONY }, - .bodyColor = BODY_COLOR_BLACK, + //.innates = { ABILITY_OBLIVIOUS, ABILITY_HARVEST, ABILITY_RIPEN }, + .bodyColor = BODY_COLOR_BLACK, .speciesName = _("Snorlax"), .cryId = CRY_SNORLAX, .natDexNum = NATIONAL_DEX_SNORLAX, @@ -19470,7 +19730,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_PRESSURE, ABILITY_NONE, ABILITY_SNOW_CLOAK }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_SNOW_WARNING, ABILITY_ICE_BODY, ABILITY_HOSPITALITY }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Articuno"), .cryId = CRY_ARTICUNO, .natDexNum = NATIONAL_DEX_ARTICUNO, @@ -19543,7 +19804,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_COMPETITIVE, ABILITY_NONE, ABILITY_NONE }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_CURSED_BODY, ABILITY_SHARPNESS, ABILITY_INNER_FOCUS }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Articuno"), .cryId = CRY_ARTICUNO, .natDexNum = NATIONAL_DEX_ARTICUNO, @@ -19624,7 +19886,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #else .abilities = { ABILITY_PRESSURE, ABILITY_NONE, ABILITY_LIGHTNING_ROD }, #endif - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_DRIZZLE, ABILITY_LIGHTNING_ROD, ABILITY_PLUS }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Zapdos"), .cryId = CRY_ZAPDOS, .natDexNum = NATIONAL_DEX_ZAPDOS, @@ -19697,7 +19960,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_DEFIANT, ABILITY_NONE, ABILITY_NONE }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_SPEED_BOOST, ABILITY_TOUGH_CLAWS, ABILITY_GUTS }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Zapdos"), .cryId = CRY_ZAPDOS, .natDexNum = NATIONAL_DEX_ZAPDOS, @@ -19773,7 +20037,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_PRESSURE, ABILITY_NONE, ABILITY_FLAME_BODY }, - .bodyColor = BODY_COLOR_YELLOW, + //.innates = { ABILITY_DROUGHT, ABILITY_FLASH_FIRE, ABILITY_POWER_SPOT }, + .bodyColor = BODY_COLOR_YELLOW, .speciesName = _("Moltres"), .cryId = CRY_MOLTRES, .natDexNum = NATIONAL_DEX_MOLTRES, @@ -19874,7 +20139,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_BERSERK, ABILITY_NONE, ABILITY_NONE }, - .bodyColor = BODY_COLOR_RED, + //.innates = { ABILITY_SHADOW_SHIELD, ABILITY_DARK_AURA, ABILITY_VITAL_SPIRIT }, + .bodyColor = BODY_COLOR_RED, .speciesName = _("Moltres"), .cryId = CRY_MOLTRES, .natDexNum = NATIONAL_DEX_MOLTRES, @@ -19945,7 +20211,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_1, EGG_GROUP_DRAGON), .abilities = { ABILITY_SHED_SKIN, ABILITY_NONE, ABILITY_MARVEL_SCALE }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_VITAL_SPIRIT }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Dratini"), .cryId = CRY_DRATINI, .natDexNum = NATIONAL_DEX_DRATINI, @@ -20015,7 +20282,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_1, EGG_GROUP_DRAGON), .abilities = { ABILITY_SHED_SKIN, ABILITY_NONE, ABILITY_MARVEL_SCALE }, - .bodyColor = BODY_COLOR_BLUE, + //.innates = { ABILITY_SERENE_GRACE, ABILITY_CLOUD_NINE }, + .bodyColor = BODY_COLOR_BLUE, .speciesName = _("Dragonair"), .cryId = CRY_DRAGONAIR, .natDexNum = NATIONAL_DEX_DRAGONAIR, @@ -20091,7 +20359,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_WATER_1, EGG_GROUP_DRAGON), .abilities = { ABILITY_INNER_FOCUS, ABILITY_NONE, ABILITY_MULTISCALE }, - .bodyColor = BODY_COLOR_BROWN, + //.innates = { ABILITY_MARVEL_SCALE, ABILITY_VITAL_SPIRIT, ABILITY_HOSPITALITY }, + .bodyColor = BODY_COLOR_BROWN, .speciesName = _("Dragonite"), .cryId = CRY_DRAGONITE, .natDexNum = NATIONAL_DEX_DRAGONITE, @@ -20225,7 +20494,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_PRESSURE, ABILITY_NONE, ABILITY_UNNERVE }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_FILTER, ABILITY_BERSERK, ABILITY_MAGIC_BOUNCE }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Mewtwo"), .cryId = CRY_MEWTWO, .natDexNum = NATIONAL_DEX_MEWTWO, @@ -20297,7 +20567,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_STEADFAST, ABILITY_STEADFAST, ABILITY_STEADFAST }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_MOXIE, ABILITY_STAMINA, ABILITY_LONG_REACH }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Mewtwo"), .cryId = CRY_MEWTWO_MEGA_X, .natDexNum = NATIONAL_DEX_MEWTWO, @@ -20368,7 +20639,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_INSOMNIA, ABILITY_INSOMNIA, ABILITY_INSOMNIA }, - .bodyColor = BODY_COLOR_PURPLE, + //.innates = { ABILITY_SOUL_HEART, ABILITY_BERSERK, ABILITY_LEVITATE }, + .bodyColor = BODY_COLOR_PURPLE, .speciesName = _("Mewtwo"), .cryId = CRY_MEWTWO_MEGA_Y, .natDexNum = NATIONAL_DEX_MEWTWO, @@ -20451,7 +20723,8 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED), .abilities = { ABILITY_SYNCHRONIZE, ABILITY_NONE, ABILITY_NONE }, - .bodyColor = BODY_COLOR_PINK, + //.innates = { ABILITY_FILTER, ABILITY_COLOR_CHANGE, ABILITY_SERENE_GRACE }, + .bodyColor = BODY_COLOR_PINK, .speciesName = _("Mew"), .cryId = CRY_MEW, .natDexNum = NATIONAL_DEX_MEW, @@ -20507,4 +20780,4 @@ const struct SpeciesInfo gSpeciesInfoGen1[] = #ifdef __INTELLISENSE__ }; -#endif +#endif \ No newline at end of file diff --git a/src/egg_hatch.c b/src/egg_hatch.c index 2c632ee5781b..8be61221d7d1 100644 --- a/src/egg_hatch.c +++ b/src/egg_hatch.c @@ -931,10 +931,9 @@ u8 GetEggCyclesToSubtract(void) { if (!GetMonData(&gPlayerParty[i], MON_DATA_SANITY_IS_EGG)) { - enum Ability ability = GetMonAbility(&gPlayerParty[i]); - if (ability == ABILITY_MAGMA_ARMOR - || ability == ABILITY_FLAME_BODY - || ability == ABILITY_STEAM_ENGINE) + if (MonHasTrait(&gPlayerParty[i], ABILITY_MAGMA_ARMOR) + || MonHasTrait(&gPlayerParty[i], ABILITY_FLAME_BODY) + || MonHasTrait(&gPlayerParty[i], ABILITY_STEAM_ENGINE)) return 2; } } diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 188f4dc74260..0e139e0b02ee 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -2545,7 +2545,7 @@ void GetFollowerAction(struct ScriptContext *ctx) // Essentially a big switch fo } emotion = RandomWeightedIndex(emotion_weight, FOLLOWER_EMOTION_LENGTH); - if ((mon->status & STATUS1_PSN_ANY) && GetMonAbility(mon) != ABILITY_POISON_HEAL) + if ((mon->status & STATUS1_PSN_ANY) && !MonHasTrait(mon, ABILITY_POISON_HEAL)) emotion = FOLLOWER_EMOTION_POISONED; // end special conditions @@ -5506,7 +5506,6 @@ static bool32 TryStartFollowerTransformEffect(struct ObjectEvent *objectEvent, s { u32 multi; struct Pokemon *mon; - enum Ability ability; if (DoesSpeciesHaveFormChangeMethod(OW_SPECIES(objectEvent), FORM_CHANGE_OVERWORLD_WEATHER) && OW_SPECIES(objectEvent) != (multi = GetOverworldWeatherSpecies(OW_SPECIES(objectEvent)))) { @@ -5516,9 +5515,9 @@ static bool32 TryStartFollowerTransformEffect(struct ObjectEvent *objectEvent, s } if (OW_FOLLOWERS_COPY_WILD_PKMN - && (MonKnowsMove(mon = GetFirstLiveMon(), MOVE_TRANSFORM) - || (ability = GetMonAbility(mon)) == ABILITY_IMPOSTER || ability == ABILITY_ILLUSION) - && (Random() & 0xFFFF) < 18 && GetLocalWildMon(FALSE)) + && (MonKnowsMove(mon = GetFirstLiveMon(), MOVE_TRANSFORM) + || MonHasTrait(mon, ABILITY_IMPOSTER) || MonHasTrait(mon, ABILITY_ILLUSION)) + && (Random() & 0xFFFF) < 18 && GetLocalWildMon(FALSE)) { sprite->data[7] = TRANSFORM_TYPE_RANDOM_WILD << 8; PlaySE(SE_M_MINIMIZE); diff --git a/src/fishing.c b/src/fishing.c index 7730351abbcb..59a49195a529 100644 --- a/src/fishing.c +++ b/src/fishing.c @@ -479,14 +479,10 @@ static bool32 DoesFishingMinigameAllowCancel(void) static bool32 Fishing_DoesFirstMonInPartyHaveSuctionCupsOrStickyHold(void) { - enum Ability ability; - if (GetMonData(&gPlayerParty[0], MON_DATA_SANITY_IS_EGG)) return FALSE; - ability = GetMonAbility(&gPlayerParty[0]); - - return (ability == ABILITY_SUCTION_CUPS || ability == ABILITY_STICKY_HOLD); + return (MonHasTrait(&gPlayerParty[0], ABILITY_SUCTION_CUPS) || MonHasTrait(&gPlayerParty[0], ABILITY_STICKY_HOLD)); } static bool32 Fishing_RollForBite(u32 rod, bool32 isStickyHold) diff --git a/src/fldeff_cut.c b/src/fldeff_cut.c index a5f45ab29e55..12303f538442 100644 --- a/src/fldeff_cut.c +++ b/src/fldeff_cut.c @@ -140,7 +140,6 @@ bool32 SetUpFieldMove_Cut(void) s16 x, y; u8 i, j; u8 tileBehavior; - enum Ability userAbility; bool8 cutTiles[CUT_NORMAL_AREA]; bool8 ret; @@ -154,8 +153,8 @@ bool32 SetUpFieldMove_Cut(void) else { PlayerGetDestCoords(&gPlayerFacingPosition.x, &gPlayerFacingPosition.y); - userAbility = GetMonAbility(&gPlayerParty[GetCursorSelectionMonId()]); - if (userAbility == ABILITY_HYPER_CUTTER) + //userAbility = GetMonAbility(&gPlayerParty[GetCursorSelectionMonId()]); + if (MonHasTrait(&gPlayerParty[GetCursorSelectionMonId()], ABILITY_HYPER_CUTTER)) { sCutSquareSide = CUT_HYPER_SIDE; sTileCountFromPlayer_X = 2; @@ -214,7 +213,7 @@ bool32 SetUpFieldMove_Cut(void) } } - if (userAbility != ABILITY_HYPER_CUTTER) + if (!MonHasTrait(&gPlayerParty[GetCursorSelectionMonId()], ABILITY_HYPER_CUTTER)) { if (ret == TRUE) { diff --git a/src/graphics.c b/src/graphics.c index 013c2d2b85be..19d68c217b7e 100644 --- a/src/graphics.c +++ b/src/graphics.c @@ -2119,3 +2119,5 @@ const u32 gBattleIcons_Gfx2[] = INCBIN_U32("graphics/types/battle_icons2.4bpp.sm const u16 gBattleIcons_Pal1[] = INCBIN_U16("graphics/types/battle_icons1.gbapal"); const u16 gBattleIcons_Pal2[] = INCBIN_U16("graphics/types/battle_icons2.gbapal"); +//New Summary Pages +const u32 gSummaryPage_Traits_Tilemap[] = INCBIN_U32("graphics/summary_screen/page_traits.bin.lz"); \ No newline at end of file diff --git a/src/item_use.c b/src/item_use.c index ad08d016b035..5810e6c7c19e 100644 --- a/src/item_use.c +++ b/src/item_use.c @@ -1269,7 +1269,7 @@ bool32 CannotUseItemsInBattle(u16 itemId, struct Pokemon *mon) switch (battleUsage) { case EFFECT_ITEM_INCREASE_STAT: - if (CompareStat(gBattlerInMenuId, GetItemEffect(itemId)[1], MAX_STAT_STAGE, CMP_EQUAL, GetBattlerAbility(gBattlerInMenuId))) + if (CompareStat(gBattlerInMenuId, GetItemEffect(itemId)[1], MAX_STAT_STAGE, CMP_EQUAL)) cannotUse = TRUE; break; case EFFECT_ITEM_SET_FOCUS_ENERGY: @@ -1307,11 +1307,10 @@ bool32 CannotUseItemsInBattle(u16 itemId, struct Pokemon *mon) break; case EFFECT_ITEM_INCREASE_ALL_STATS: { - u32 ability = GetBattlerAbility(gBattlerInMenuId); cannotUse = TRUE; for (i = STAT_ATK; i < NUM_STATS; i++) { - if (!CompareStat(gBattlerInMenuId, i, MAX_STAT_STAGE, CMP_EQUAL, ability)) + if (!CompareStat(gBattlerInMenuId, i, MAX_STAT_STAGE, CMP_EQUAL)) { cannotUse = FALSE; break; diff --git a/src/match_call.c b/src/match_call.c index e8782cf60e2a..1bc05cd41275 100644 --- a/src/match_call.c +++ b/src/match_call.c @@ -1057,7 +1057,7 @@ static bool32 UpdateMatchCallMinutesCounter(void) static bool32 CheckMatchCallChance(void) { int callChance = 1; - if (!GetMonData(&gPlayerParty[0], MON_DATA_SANITY_IS_EGG) && GetMonAbility(&gPlayerParty[0]) == ABILITY_LIGHTNING_ROD) + if (!GetMonData(&gPlayerParty[0], MON_DATA_SANITY_IS_EGG) && MonHasTrait(&gPlayerParty[0], ABILITY_LIGHTNING_ROD)) callChance = 2; if (Random() % 10 < callChance * 3) diff --git a/src/overworld.c b/src/overworld.c index 3c0430a16884..b108353dd7b4 100644 --- a/src/overworld.c +++ b/src/overworld.c @@ -1380,7 +1380,7 @@ void UpdateAmbientCry(s16 *state, u16 *delayCounter) for (i = 0; i < monsCount; i++) { if (!GetMonData(&gPlayerParty[i], MON_DATA_SANITY_IS_EGG) - && GetMonAbility(&gPlayerParty[0]) == ABILITY_SWARM) + && MonHasTrait(&gPlayerParty[0], ABILITY_SWARM)) { divBy = 2; break; diff --git a/src/pokemon.c b/src/pokemon.c index 90273d17097e..7976eaf182f4 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -3663,6 +3663,11 @@ void PokemonToBattleMon(struct Pokemon *src, struct BattlePokemon *dst) for (i = 0; i < NUM_BATTLE_STATS; i++) dst->statStages[i] = DEFAULT_STAT_STAGE; + for (i = 0; i < MAX_MON_INNATES; i++) + { + dst->innates[i] = GetSpeciesInnate(dst->species, i + 1); + } + memset(&dst->volatiles, 0, sizeof(struct Volatiles)); } @@ -6337,14 +6342,13 @@ static s32 GetWildMonTableIdInAlteringCave(u16 species) static inline bool32 CanFirstMonBoostHeldItemRarity(void) { - enum Ability ability; if (GetMonData(&gPlayerParty[0], MON_DATA_SANITY_IS_EGG)) return FALSE; - ability = GetMonAbility(&gPlayerParty[0]); - if (ability == ABILITY_COMPOUND_EYES) + //ability = GetMonAbility(&gPlayerParty[0]); + if (MonHasTrait(&gPlayerParty[0], ABILITY_COMPOUND_EYES)) return TRUE; - else if ((OW_SUPER_LUCK >= GEN_8) && ability == ABILITY_SUPER_LUCK) + else if ((OW_SUPER_LUCK >= GEN_8) && MonHasTrait(&gPlayerParty[0], ABILITY_SUPER_LUCK)) return TRUE; return FALSE; } @@ -7495,3 +7499,43 @@ bool32 IsSpeciesOfType(u32 species, enum Type type) return TRUE; return FALSE; } + +//Returns the slot the Innate is found in, assuming the Ability is already slot 1. Returns 0 if not found. +u8 SpeciesHasInnate(u16 species, u16 ability) { + u8 i; + u8 innateNum = 0; + + for (i = 0; i < MAX_MON_INNATES; i++) + { + if (gSpeciesInfo[species].innates[i] == ability) + { + innateNum = i + 2; + //DebugPrintf("INNATE FOUND: %d", innateNum - 1); + } + } + + return innateNum; +} + +bool8 BoxMonHasInnate(struct BoxPokemon *boxmon, u16 ability) +{ + u16 species = GetBoxMonData(boxmon, MON_DATA_SPECIES, NULL); + + return SpeciesHasInnate(species, ability); +} + +bool8 MonHasTrait(struct Pokemon *mon, u16 ability) +{ + u16 species = GetMonData(mon, MON_DATA_SPECIES, NULL); + + return (GetMonAbility(mon) == ability || SpeciesHasInnate(species, ability)); +} + +enum Ability GetSpeciesInnate(u16 species, u8 traitNum) +{ + if (MAX_MON_INNATES > 0) + return gSpeciesInfo[species].innates[traitNum - 1]; + else + return 0; +} + diff --git a/src/pokemon_summary_screen.c b/src/pokemon_summary_screen.c index c9306265464c..c26e7cebc0cd 100644 --- a/src/pokemon_summary_screen.c +++ b/src/pokemon_summary_screen.c @@ -26,6 +26,8 @@ #include "mon_markings.h" #include "move_relearner.h" #include "naming_screen.h" +#include "move_relearner.h" +#include "naming_screen.h" #include "party_menu.h" #include "palette.h" #include "pokeball.h" @@ -53,36 +55,37 @@ // Screen titles (upper left) #define PSS_LABEL_WINDOW_POKEMON_INFO_TITLE 0 -#define PSS_LABEL_WINDOW_POKEMON_SKILLS_TITLE 1 -#define PSS_LABEL_WINDOW_BATTLE_MOVES_TITLE 2 -#define PSS_LABEL_WINDOW_CONTEST_MOVES_TITLE 3 +#define PSS_LABEL_WINDOW_POKEMON_TRAITS_TITLE 1 +#define PSS_LABEL_WINDOW_POKEMON_SKILLS_TITLE 2 +#define PSS_LABEL_WINDOW_BATTLE_MOVES_TITLE 3 +#define PSS_LABEL_WINDOW_CONTEST_MOVES_TITLE 4 // Button control text (upper right) -#define PSS_LABEL_WINDOW_PROMPT_UTILITY 4 // Handles "Switch", "Info", and "Cancel" prompts. Also handles the "Rename" and "IVs"/"EVs"/"STATS" prompts if P_SUMMARY_SCREEN_RENAME and P_SUMMARY_SCREEN_IV_EV_INFO are true, respectively -#define PSS_LABEL_WINDOW_PROMPT_INFO 5 // unused -#define PSS_LABEL_WINDOW_PROMPT_SWITCH 6 // unused -#define PSS_LABEL_WINDOW_UNUSED1 7 +#define PSS_LABEL_WINDOW_PROMPT_UTILITY 5 // Also handles the "rename" prompt if P_SUMMARY_SCREEN_RENAME is true +#define PSS_LABEL_WINDOW_PROMPT_INFO 6 +#define PSS_LABEL_WINDOW_PROMPT_SWITCH 7 +#define PSS_LABEL_WINDOW_UNUSED1 8 // Info screen -#define PSS_LABEL_WINDOW_POKEMON_INFO_RENTAL 8 -#define PSS_LABEL_WINDOW_POKEMON_INFO_TYPE 9 +#define PSS_LABEL_WINDOW_POKEMON_INFO_RENTAL 9 +#define PSS_LABEL_WINDOW_POKEMON_INFO_TYPE 10 // Skills screen -#define PSS_LABEL_WINDOW_POKEMON_SKILLS_STATS_LEFT 10 // HP, Attack, Defense -#define PSS_LABEL_WINDOW_POKEMON_SKILLS_STATS_RIGHT 11 // Sp. Attack, Sp. Defense, Speed -#define PSS_LABEL_WINDOW_POKEMON_SKILLS_EXP 12 // EXP, Next Level -#define PSS_LABEL_WINDOW_POKEMON_SKILLS_STATUS 13 +#define PSS_LABEL_WINDOW_POKEMON_SKILLS_STATS_LEFT 11 // HP, Attack, Defense +#define PSS_LABEL_WINDOW_POKEMON_SKILLS_STATS_RIGHT 12 // Sp. Attack, Sp. Defense, Speed +#define PSS_LABEL_WINDOW_POKEMON_SKILLS_EXP 13 // EXP, Next Level +#define PSS_LABEL_WINDOW_POKEMON_SKILLS_STATUS 14 // Moves screen -#define PSS_LABEL_WINDOW_MOVES_POWER_ACC 14 // Also contains the power and accuracy values -#define PSS_LABEL_WINDOW_MOVES_APPEAL_JAM 15 -#define PSS_LABEL_WINDOW_PROMPT_RELEARN 16 +#define PSS_LABEL_WINDOW_MOVES_POWER_ACC 15 // Also contains the power and accuracy values +#define PSS_LABEL_WINDOW_MOVES_APPEAL_JAM 16 +#define PSS_LABEL_WINDOW_PROMPT_RELEARN 17 // Above/below the pokemon's portrait (left) -#define PSS_LABEL_WINDOW_PORTRAIT_DEX_NUMBER 17 -#define PSS_LABEL_WINDOW_PORTRAIT_NICKNAME 18 // The upper name -#define PSS_LABEL_WINDOW_PORTRAIT_SPECIES 19 // The lower name -#define PSS_LABEL_WINDOW_END 20 +#define PSS_LABEL_WINDOW_PORTRAIT_DEX_NUMBER 18 +#define PSS_LABEL_WINDOW_PORTRAIT_NICKNAME 19 // The upper name +#define PSS_LABEL_WINDOW_PORTRAIT_SPECIES 20 // The lower name +#define PSS_LABEL_WINDOW_END 21 // Dynamic fields for the Pokémon Info page #define PSS_DATA_WINDOW_INFO_ORIGINAL_TRAINER 0 @@ -90,6 +93,12 @@ #define PSS_DATA_WINDOW_INFO_ABILITY 2 #define PSS_DATA_WINDOW_INFO_MEMO 3 +//Dynamic fields for the Pokemon Traits page +#define PSS_DATA_WINDOW_TRAITS1 0 +#define PSS_DATA_WINDOW_TRAITS2 1 +#define PSS_DATA_WINDOW_TRAITS3 2 +#define PSS_DATA_WINDOW_TRAITS4 3 + // Dynamic fields for the Pokémon Skills page #define PSS_DATA_WINDOW_SKILLS_HELD_ITEM 0 #define PSS_DATA_WINDOW_SKILLS_RIBBON_COUNT 1 @@ -165,6 +174,7 @@ static EWRAM_DATA struct PokemonSummaryScreenData u32 OTID; // 0x48 enum Type teraType; u8 mintNature; + u8 innates[MAX_MON_INNATES]; } summary; u16 bgTilemapBuffers[PSS_PAGE_COUNT][2][0x400]; u8 mode; @@ -260,6 +270,7 @@ static void PrintMonOTName(void); static void PrintMonOTID(void); static void PrintMonAbilityName(void); static void PrintMonAbilityDescription(void); +static void PrintMonTraits(u8); static void BufferMonTrainerMemo(void); static void PrintMonTrainerMemo(void); static void BufferNatureString(void); @@ -334,6 +345,9 @@ static u8 AddWindowFromTemplateList(const struct WindowTemplate *template, u8 te static u8 IncrementSkillsStatsMode(u8 mode); static void ClearStatLabel(u32 length, u32 statsCoordX, u32 statsCoordY); u32 GetAdjustedIvData(struct Pokemon *mon, u32 stat); +static void PrintTraits(void); +static void Task_PrintTraits(u8); + static const struct BgTemplate sBgTemplates[] = { @@ -423,6 +437,7 @@ static const struct SlidingWindow sAppealJamSlidingWindow = .top = 45 }; static const s8 sMultiBattleOrder[] = {0, 2, 3, 1, 4, 5}; +static const int offset = 22; //offest to make template calculations easier static const struct WindowTemplate sSummaryTemplate[] = { [PSS_LABEL_WINDOW_POKEMON_INFO_TITLE] = { @@ -434,7 +449,7 @@ static const struct WindowTemplate sSummaryTemplate[] = .paletteNum = 6, .baseBlock = 1, }, - [PSS_LABEL_WINDOW_POKEMON_SKILLS_TITLE] = { + [PSS_LABEL_WINDOW_POKEMON_TRAITS_TITLE] = { .bg = 0, .tilemapLeft = 0, .tilemapTop = 0, @@ -443,7 +458,7 @@ static const struct WindowTemplate sSummaryTemplate[] = .paletteNum = 6, .baseBlock = 23, }, - [PSS_LABEL_WINDOW_BATTLE_MOVES_TITLE] = { + [PSS_LABEL_WINDOW_POKEMON_SKILLS_TITLE] = { .bg = 0, .tilemapLeft = 0, .tilemapTop = 0, @@ -452,7 +467,7 @@ static const struct WindowTemplate sSummaryTemplate[] = .paletteNum = 6, .baseBlock = 45, }, - [PSS_LABEL_WINDOW_CONTEST_MOVES_TITLE] = { + [PSS_LABEL_WINDOW_BATTLE_MOVES_TITLE] = { .bg = 0, .tilemapLeft = 0, .tilemapTop = 0, @@ -461,6 +476,15 @@ static const struct WindowTemplate sSummaryTemplate[] = .paletteNum = 6, .baseBlock = 67, }, + [PSS_LABEL_WINDOW_CONTEST_MOVES_TITLE] = { + .bg = 0, + .tilemapLeft = 0, + .tilemapTop = 0, + .width = 11, + .height = 2, + .paletteNum = 6, + .baseBlock = 67 + offset, + }, [PSS_LABEL_WINDOW_PROMPT_UTILITY] = { .bg = 0, .tilemapLeft = 22, @@ -468,7 +492,7 @@ static const struct WindowTemplate sSummaryTemplate[] = .width = 8, .height = 2, .paletteNum = 7, - .baseBlock = 89, + .baseBlock = 89 + offset, }, [PSS_LABEL_WINDOW_PROMPT_INFO] = { .bg = 0, @@ -477,7 +501,7 @@ static const struct WindowTemplate sSummaryTemplate[] = .width = 8, .height = 2, .paletteNum = 7, - .baseBlock = 105, + .baseBlock = 105 + offset, }, [PSS_LABEL_WINDOW_PROMPT_SWITCH] = { .bg = 0, @@ -486,7 +510,7 @@ static const struct WindowTemplate sSummaryTemplate[] = .width = 8, .height = 2, .paletteNum = 7, - .baseBlock = 121, + .baseBlock = 121 + offset, }, [PSS_LABEL_WINDOW_UNUSED1] = { .bg = 0, @@ -495,7 +519,7 @@ static const struct WindowTemplate sSummaryTemplate[] = .width = 0, .height = 2, .paletteNum = 6, - .baseBlock = 137, + .baseBlock = 137 + offset, }, [PSS_LABEL_WINDOW_POKEMON_INFO_RENTAL] = { .bg = 0, @@ -504,7 +528,7 @@ static const struct WindowTemplate sSummaryTemplate[] = .width = 18, .height = 2, .paletteNum = 6, - .baseBlock = 137, + .baseBlock = 137 + offset, }, [PSS_LABEL_WINDOW_POKEMON_INFO_TYPE] = { .bg = 0, @@ -513,7 +537,7 @@ static const struct WindowTemplate sSummaryTemplate[] = .width = 18, .height = 2, .paletteNum = 6, - .baseBlock = 173, + .baseBlock = 173 + offset, }, [PSS_LABEL_WINDOW_POKEMON_SKILLS_STATS_LEFT] = { .bg = 0, @@ -522,7 +546,7 @@ static const struct WindowTemplate sSummaryTemplate[] = .width = 6, .height = 6, .paletteNum = 6, - .baseBlock = 209, + .baseBlock = 209 + offset, }, [PSS_LABEL_WINDOW_POKEMON_SKILLS_STATS_RIGHT] = { .bg = 0, @@ -531,7 +555,7 @@ static const struct WindowTemplate sSummaryTemplate[] = .width = 5, .height = 6, .paletteNum = 6, - .baseBlock = 245, + .baseBlock = 245 + offset, }, [PSS_LABEL_WINDOW_POKEMON_SKILLS_EXP] = { .bg = 0, @@ -540,7 +564,7 @@ static const struct WindowTemplate sSummaryTemplate[] = .width = 11, .height = 4, .paletteNum = 6, - .baseBlock = 275, + .baseBlock = 275 + offset, }, [PSS_LABEL_WINDOW_POKEMON_SKILLS_STATUS] = { .bg = 0, @@ -549,7 +573,7 @@ static const struct WindowTemplate sSummaryTemplate[] = .width = 6, .height = 2, .paletteNum = 6, - .baseBlock = 319, + .baseBlock = 319 + offset, }, [PSS_LABEL_WINDOW_MOVES_POWER_ACC] = { .bg = 0, @@ -558,7 +582,7 @@ static const struct WindowTemplate sSummaryTemplate[] = .width = 9, .height = 4, .paletteNum = 6, - .baseBlock = 331, + .baseBlock = 331 + offset, }, [PSS_LABEL_WINDOW_MOVES_APPEAL_JAM] = { .bg = 0, @@ -567,7 +591,7 @@ static const struct WindowTemplate sSummaryTemplate[] = .width = 5, .height = 4, .paletteNum = 6, - .baseBlock = 367, + .baseBlock = 367 + offset, }, [PSS_LABEL_WINDOW_PROMPT_RELEARN] = { .bg = 0, @@ -576,7 +600,7 @@ static const struct WindowTemplate sSummaryTemplate[] = .width = 11, .height = 2, .paletteNum = 15, - .baseBlock = 800, + .baseBlock = 800 + offset, }, [PSS_LABEL_WINDOW_PORTRAIT_DEX_NUMBER] = { .bg = 0, @@ -585,7 +609,7 @@ static const struct WindowTemplate sSummaryTemplate[] = .width = 5, .height = 2, .paletteNum = 7, - .baseBlock = 403, + .baseBlock = 403 + offset, }, [PSS_LABEL_WINDOW_PORTRAIT_NICKNAME] = { .bg = 0, @@ -594,7 +618,7 @@ static const struct WindowTemplate sSummaryTemplate[] = .width = 9, .height = 2, .paletteNum = 6, - .baseBlock = 413, + .baseBlock = 413 + offset, }, [PSS_LABEL_WINDOW_PORTRAIT_SPECIES] = { .bg = 0, @@ -603,7 +627,7 @@ static const struct WindowTemplate sSummaryTemplate[] = .width = 9, .height = 4, .paletteNum = 6, - .baseBlock = 431, + .baseBlock = 431 + offset, }, [PSS_LABEL_WINDOW_END] = DUMMY_WIN_TEMPLATE }; @@ -616,7 +640,7 @@ static const struct WindowTemplate sPageInfoTemplate[] = .width = 11, .height = 2, .paletteNum = 6, - .baseBlock = 467, + .baseBlock = 467 + offset, }, [PSS_DATA_WINDOW_INFO_ID] = { .bg = 0, @@ -625,7 +649,7 @@ static const struct WindowTemplate sPageInfoTemplate[] = .width = 7, .height = 2, .paletteNum = 6, - .baseBlock = 489, + .baseBlock = 489 + offset, }, [PSS_DATA_WINDOW_INFO_ABILITY] = { .bg = 0, @@ -634,7 +658,7 @@ static const struct WindowTemplate sPageInfoTemplate[] = .width = 18, .height = 4, .paletteNum = 6, - .baseBlock = 503, + .baseBlock = 503 + offset, }, [PSS_DATA_WINDOW_INFO_MEMO] = { .bg = 0, @@ -643,9 +667,48 @@ static const struct WindowTemplate sPageInfoTemplate[] = .width = 18, .height = 6, .paletteNum = 6, - .baseBlock = 575, + .baseBlock = 575 + offset, }, }; +static const struct WindowTemplate sPageTraitsTemplate[] = +{ + [PSS_DATA_WINDOW_TRAITS1] = { + .bg = 0, + .tilemapLeft = 11, + .tilemapTop = 4, + .width = 18, + .height = 4, + .paletteNum = 6, + .baseBlock = 467 + offset, + }, + [PSS_DATA_WINDOW_TRAITS2] = { + .bg = 0, + .tilemapLeft = 11, + .tilemapTop = 8, + .width = 18, + .height = 4, + .paletteNum = 6, + .baseBlock = 539 + offset, + }, + [PSS_DATA_WINDOW_TRAITS3] = { + .bg = 0, + .tilemapLeft = 11, + .tilemapTop = 12, + .width = 18, + .height = 4, + .paletteNum = 6, + .baseBlock = 611 + offset, + }, + [PSS_DATA_WINDOW_TRAITS4] = { + .bg = 0, + .tilemapLeft = 11, + .tilemapTop = 16, + .width = 18, + .height = 4, + .paletteNum = 6, + .baseBlock = 683 + offset, + }, +}; static const struct WindowTemplate sPageSkillsTemplate[] = { [PSS_DATA_WINDOW_SKILLS_HELD_ITEM] = { @@ -655,7 +718,7 @@ static const struct WindowTemplate sPageSkillsTemplate[] = .width = 10, .height = 2, .paletteNum = 6, - .baseBlock = 467, + .baseBlock = 467 + offset, }, [PSS_DATA_WINDOW_SKILLS_RIBBON_COUNT] = { .bg = 0, @@ -664,7 +727,7 @@ static const struct WindowTemplate sPageSkillsTemplate[] = .width = 10, .height = 2, .paletteNum = 6, - .baseBlock = 487, + .baseBlock = 487 + offset, }, [PSS_DATA_WINDOW_SKILLS_STATS_LEFT] = { .bg = 0, @@ -673,7 +736,7 @@ static const struct WindowTemplate sPageSkillsTemplate[] = .width = 6, .height = 6, .paletteNum = 6, - .baseBlock = 507, + .baseBlock = 507 + offset, }, [PSS_DATA_WINDOW_SKILLS_STATS_RIGHT] = { .bg = 0, @@ -682,7 +745,7 @@ static const struct WindowTemplate sPageSkillsTemplate[] = .width = 3, .height = 6, .paletteNum = 6, - .baseBlock = 543, + .baseBlock = 543 + offset, }, [PSS_DATA_WINDOW_EXP] = { .bg = 0, @@ -691,7 +754,7 @@ static const struct WindowTemplate sPageSkillsTemplate[] = .width = 6, .height = 4, .paletteNum = 6, - .baseBlock = 561, + .baseBlock = 561 + offset, }, }; static const struct WindowTemplate sPageMovesTemplate[] = // This is used for both battle and contest moves @@ -703,7 +766,7 @@ static const struct WindowTemplate sPageMovesTemplate[] = // This is used for bo .width = 9, .height = 10, .paletteNum = 6, - .baseBlock = 467, + .baseBlock = 467 + offset, }, [PSS_DATA_WINDOW_MOVE_PP] = { .bg = 0, @@ -712,7 +775,7 @@ static const struct WindowTemplate sPageMovesTemplate[] = // This is used for bo .width = 6, .height = 10, .paletteNum = 8, - .baseBlock = 557, + .baseBlock = 557 + offset, }, [PSS_DATA_WINDOW_MOVE_DESCRIPTION] = { .bg = 0, @@ -721,7 +784,7 @@ static const struct WindowTemplate sPageMovesTemplate[] = // This is used for bo .width = 20, .height = 4, .paletteNum = 6, - .baseBlock = 617, + .baseBlock = 617 + offset, }, }; static const u8 sTextColors[][3] = @@ -749,6 +812,7 @@ static const u8 sButtons_Gfx[][4 * TILE_SIZE_4BPP] = { static void (*const sTextPrinterFunctions[])(void) = { [PSS_PAGE_INFO] = PrintInfoPageText, + [PSS_PAGE_TRAITS] = PrintTraits, [PSS_PAGE_SKILLS] = PrintSkillsPageText, [PSS_PAGE_BATTLE_MOVES] = PrintBattleMoves, [PSS_PAGE_CONTEST_MOVES] = PrintContestMoves @@ -757,6 +821,7 @@ static void (*const sTextPrinterFunctions[])(void) = static const TaskFunc sTextPrinterTasks[] = { [PSS_PAGE_INFO] = Task_PrintInfoPage, + [PSS_PAGE_TRAITS] = Task_PrintTraits, [PSS_PAGE_SKILLS] = Task_PrintSkillsPage, [PSS_PAGE_BATTLE_MOVES] = Task_PrintBattleMoves, [PSS_PAGE_CONTEST_MOVES] = Task_PrintContestMoves @@ -1450,43 +1515,47 @@ static bool8 DecompressGraphics(void) sMonSummaryScreen->switchCounter++; break; case 3: - DecompressDataWithHeaderWram(gSummaryPage_Skills_Tilemap, sMonSummaryScreen->bgTilemapBuffers[PSS_PAGE_SKILLS][1]); + DecompressDataWithHeaderWram(gSummaryPage_Traits_Tilemap, sMonSummaryScreen->bgTilemapBuffers[PSS_PAGE_TRAITS][1]); sMonSummaryScreen->switchCounter++; break; case 4: - DecompressDataWithHeaderWram(gSummaryPage_BattleMoves_Tilemap, sMonSummaryScreen->bgTilemapBuffers[PSS_PAGE_BATTLE_MOVES][1]); + DecompressDataWithHeaderWram(gSummaryPage_Skills_Tilemap, sMonSummaryScreen->bgTilemapBuffers[PSS_PAGE_SKILLS][1]); sMonSummaryScreen->switchCounter++; break; case 5: - DecompressDataWithHeaderWram(gSummaryPage_ContestMoves_Tilemap, sMonSummaryScreen->bgTilemapBuffers[PSS_PAGE_CONTEST_MOVES][1]); + DecompressDataWithHeaderWram(gSummaryPage_BattleMoves_Tilemap, sMonSummaryScreen->bgTilemapBuffers[PSS_PAGE_BATTLE_MOVES][1]); sMonSummaryScreen->switchCounter++; break; case 6: + DecompressDataWithHeaderWram(gSummaryPage_ContestMoves_Tilemap, sMonSummaryScreen->bgTilemapBuffers[PSS_PAGE_CONTEST_MOVES][1]); + sMonSummaryScreen->switchCounter++; + break; + case 7: LoadPalette(gSummaryScreen_Pal, BG_PLTT_ID(0), 8 * PLTT_SIZE_4BPP); LoadPalette(&gPPTextPalette, BG_PLTT_ID(8) + 1, PLTT_SIZEOF(16 - 1)); sMonSummaryScreen->switchCounter++; break; - case 7: + case 8: LoadCompressedSpriteSheet(&gSpriteSheet_MoveTypes); sMonSummaryScreen->switchCounter++; break; - case 8: + case 9: LoadCompressedSpriteSheet(&sMoveSelectorSpriteSheet); sMonSummaryScreen->switchCounter++; break; - case 9: + case 10: LoadCompressedSpriteSheet(&sStatusIconsSpriteSheet); sMonSummaryScreen->switchCounter++; break; - case 10: + case 11: LoadSpritePalette(&sStatusIconsSpritePalette); sMonSummaryScreen->switchCounter++; break; - case 11: + case 12: LoadSpritePalette(&sMoveSelectorSpritePal); sMonSummaryScreen->switchCounter++; break; - case 12: + case 13: LoadPalette(gMoveTypes_Pal, OBJ_PLTT_ID(13), 3 * PLTT_SIZE_4BPP); LoadCompressedSpriteSheet(&gSpriteSheet_CategoryIcons); LoadSpritePalette(&gSpritePal_CategoryIcons); @@ -1555,6 +1624,12 @@ static bool8 ExtractMonDataToSummaryStruct(struct Pokemon *mon) sum->metGame = GetMonData(mon, MON_DATA_MET_GAME); sum->friendship = GetMonData(mon, MON_DATA_FRIENDSHIP); break; + case 4: + for (i = 0; i < MAX_MON_INNATES; i++) + { + sum->innates[i] = GetMonData(mon, MON_DATA_INNATE1 + i); + } + break; default: sum->ribbonCount = GetMonData(mon, MON_DATA_RIBBON_COUNT); sum->teraType = GetMonData(mon, MON_DATA_TERA_TYPE); @@ -1728,7 +1803,8 @@ static void Task_HandleInput(u8 taskId) } else if (JOY_NEW(A_BUTTON)) { - if (sMonSummaryScreen->currPageIndex != PSS_PAGE_SKILLS) + if (sMonSummaryScreen->currPageIndex != PSS_PAGE_SKILLS + && sMonSummaryScreen->currPageIndex != PSS_PAGE_TRAITS) { if (sMonSummaryScreen->currPageIndex == PSS_PAGE_INFO) { @@ -3381,6 +3457,7 @@ static void PrintPageNamesAndStats(void) int statsXPos; PrintTextOnWindow(PSS_LABEL_WINDOW_POKEMON_INFO_TITLE, gText_PkmnInfo, 2, 1, 0, 1); + PrintTextOnWindow(PSS_LABEL_WINDOW_POKEMON_TRAITS_TITLE, gText_PkmnTraits, 2, 1, 0, 1); PrintTextOnWindow(PSS_LABEL_WINDOW_POKEMON_SKILLS_TITLE, gText_PkmnSkills, 2, 1, 0, 1); PrintTextOnWindow(PSS_LABEL_WINDOW_BATTLE_MOVES_TITLE, gText_BattleMoves, 2, 1, 0, 1); PrintTextOnWindow(PSS_LABEL_WINDOW_CONTEST_MOVES_TITLE, gText_ContestMoves, 2, 1, 0, 1); @@ -3422,6 +3499,7 @@ static void PutPageWindowTilemaps(u8 page) u8 i; ClearWindowTilemap(PSS_LABEL_WINDOW_POKEMON_INFO_TITLE); + ClearWindowTilemap(PSS_LABEL_WINDOW_POKEMON_TRAITS_TITLE); ClearWindowTilemap(PSS_LABEL_WINDOW_POKEMON_SKILLS_TITLE); ClearWindowTilemap(PSS_LABEL_WINDOW_BATTLE_MOVES_TITLE); ClearWindowTilemap(PSS_LABEL_WINDOW_CONTEST_MOVES_TITLE); @@ -3435,6 +3513,9 @@ static void PutPageWindowTilemaps(u8 page) PutWindowTilemap(PSS_LABEL_WINDOW_POKEMON_INFO_RENTAL); PutWindowTilemap(PSS_LABEL_WINDOW_POKEMON_INFO_TYPE); break; + case PSS_PAGE_TRAITS: + PutWindowTilemap(PSS_LABEL_WINDOW_POKEMON_TRAITS_TITLE); + break; case PSS_PAGE_SKILLS: PutWindowTilemap(PSS_LABEL_WINDOW_POKEMON_SKILLS_TITLE); PutWindowTilemap(PSS_LABEL_WINDOW_POKEMON_SKILLS_STATS_LEFT); @@ -3492,6 +3573,8 @@ static void ClearPageWindowTilemaps(u8 page) ClearWindowTilemap(PSS_LABEL_WINDOW_POKEMON_INFO_TYPE); ClearWindowTilemap(PSS_LABEL_WINDOW_PROMPT_RELEARN); break; + case PSS_PAGE_TRAITS: + break; case PSS_PAGE_SKILLS: ClearWindowTilemap(PSS_LABEL_WINDOW_POKEMON_SKILLS_STATS_LEFT); ClearWindowTilemap(PSS_LABEL_WINDOW_POKEMON_SKILLS_STATS_RIGHT); @@ -3840,6 +3923,64 @@ static void PrintEggMemo(void) PrintTextOnWindow(AddWindowFromTemplateList(sPageInfoTemplate, PSS_DATA_WINDOW_INFO_MEMO), text, 0, 1, 0, 0); } +static void PrintTraits(void) +{ + PrintMonTraits(0); + PrintMonTraits(1); + PrintMonTraits(2); + PrintMonTraits(3); +} + +static void Task_PrintTraits(u8 taskId) +{ + s16* data = gTasks[taskId].data; + + switch (data[0]) + { + case 1: + PrintMonTraits(0); + break; + case 2: + PrintMonTraits(1); + break; + case 3: + PrintMonTraits(2); + break; + case 4: + PrintMonTraits(3); + break; + case 5: + DestroyTask(taskId); + return; + } + data[0]++; +} + +static void PrintMonTraits(u8 innateIndex) +{ + u16 trait = 0; + struct PokeSummary* sum = &sMonSummaryScreen->summary; + + if (innateIndex == 0) + trait = GetAbilityBySpecies(sMonSummaryScreen->summary.species, sMonSummaryScreen->summary.abilityNum); + else if (innateIndex <= MAX_MON_INNATES) + trait = gSpeciesInfo[sum->species].innates[innateIndex-1]; + + int x = GetStringRightAlignXOffset(FONT_NORMAL, gAbilitiesInfo[trait].name, 18*8); + + if (trait == 0) + { + StringCopy(gStringVar1, gText_Blank); + PrintTextOnWindow(AddWindowFromTemplateList(sPageTraitsTemplate, innateIndex), gStringVar1, x, 1, 0, 1); + PrintTextOnWindow(AddWindowFromTemplateList(sPageTraitsTemplate, innateIndex), gStringVar1, 0, 17, 0, 0); + } + else + { + PrintTextOnWindow(AddWindowFromTemplateList(sPageTraitsTemplate, innateIndex), gAbilitiesInfo[trait].name, x, 1, 0, 1); + PrintTextOnWindow(AddWindowFromTemplateList(sPageTraitsTemplate, innateIndex), gAbilitiesInfo[trait].description, 0, 17, 0, 0); + } +} + static void PrintSkillsPageText(void) { PrintHeldItemName(); diff --git a/src/strings.c b/src/strings.c index d63266c03825..f09a6d4e4b81 100644 --- a/src/strings.c +++ b/src/strings.c @@ -1287,3 +1287,6 @@ const u8 gText_Rename[] = _("RENAME"); const u8 gText_CannotSendMonToBoxHM[] = _("Cannot send that mon to the box,\nbecause it knows a HM move.{PAUSE_UNTIL_PRESS}"); const u8 gText_CannotSendMonToBoxActive[] = _("Cannot send an active battler\nto the box.{PAUSE_UNTIL_PRESS}"); const u8 gText_CannotSendMonToBoxPartner[] = _("Cannot send a mon that doesn't,\nbelong to you to the box.{PAUSE_UNTIL_PRESS}"); + +//New Summary Screen Pages +const u8 gText_PkmnTraits[] = _("TRAITS"); \ No newline at end of file diff --git a/src/wild_encounter.c b/src/wild_encounter.c index 4c81fa371c6d..393355f27982 100644 --- a/src/wild_encounter.c +++ b/src/wild_encounter.c @@ -353,8 +353,7 @@ static u8 ChooseWildMonLevel(const struct WildPokemon *wildPokemon, u8 wildMonIn // check ability for max level mon if (!GetMonData(&gPlayerParty[0], MON_DATA_SANITY_IS_EGG)) { - enum Ability ability = GetMonAbility(&gPlayerParty[0]); - if (ability == ABILITY_HUSTLE || ability == ABILITY_VITAL_SPIRIT || ability == ABILITY_PRESSURE) + if (MonHasTrait(&gPlayerParty[0], ABILITY_HUSTLE) || MonHasTrait(&gPlayerParty[0], ABILITY_VITAL_SPIRIT) || MonHasTrait(&gPlayerParty[0], ABILITY_PRESSURE)) { if (Random() % 2 == 0) return max; @@ -466,7 +465,7 @@ u8 PickWildMonNature(void) } // check synchronize for a Pokémon with the same ability if (!GetMonData(&gPlayerParty[0], MON_DATA_SANITY_IS_EGG) - && GetMonAbility(&gPlayerParty[0]) == ABILITY_SYNCHRONIZE + && MonHasTrait(&gPlayerParty[0], ABILITY_SYNCHRONIZE) && (OW_SYNCHRONIZE_NATURE >= GEN_8 || Random() % 2 == 0)) { return GetMonData(&gPlayerParty[0], MON_DATA_PERSONALITY) % NUM_NATURES; @@ -493,7 +492,7 @@ void CreateWildMon(u16 species, u8 level) if (checkCuteCharm && !GetMonData(&gPlayerParty[0], MON_DATA_SANITY_IS_EGG) - && GetMonAbility(&gPlayerParty[0]) == ABILITY_CUTE_CHARM + && MonHasTrait(&gPlayerParty[0], ABILITY_CUTE_CHARM) && Random() % 3 != 0) { u16 leadingMonSpecies = GetMonData(&gPlayerParty[0], MON_DATA_SPECIES); @@ -633,27 +632,25 @@ static bool8 WildEncounterCheck(u32 encounterRate, bool8 ignoreAbility) encounterRate *= 2; if (!ignoreAbility && !GetMonData(&gPlayerParty[0], MON_DATA_SANITY_IS_EGG)) { - enum Ability ability = GetMonAbility(&gPlayerParty[0]); - - if (ability == ABILITY_STENCH && gMapHeader.mapLayoutId == LAYOUT_BATTLE_FRONTIER_BATTLE_PYRAMID_FLOOR) + if (MonHasTrait(&gPlayerParty[0], ABILITY_STENCH) && gMapHeader.mapLayoutId == LAYOUT_BATTLE_FRONTIER_BATTLE_PYRAMID_FLOOR) encounterRate = encounterRate * 3 / 4; - else if (ability == ABILITY_STENCH) + else if (MonHasTrait(&gPlayerParty[0], ABILITY_STENCH)) encounterRate /= 2; - else if (ability == ABILITY_ILLUMINATE) + else if (MonHasTrait(&gPlayerParty[0], ABILITY_ILLUMINATE)) encounterRate *= 2; - else if (ability == ABILITY_WHITE_SMOKE) + else if (MonHasTrait(&gPlayerParty[0], ABILITY_WHITE_SMOKE)) encounterRate /= 2; - else if (ability == ABILITY_ARENA_TRAP) + else if (MonHasTrait(&gPlayerParty[0], ABILITY_ARENA_TRAP)) encounterRate *= 2; - else if (ability == ABILITY_SAND_VEIL && gSaveBlock1Ptr->weather == WEATHER_SANDSTORM) + else if (MonHasTrait(&gPlayerParty[0], ABILITY_SAND_VEIL) && gSaveBlock1Ptr->weather == WEATHER_SANDSTORM) encounterRate /= 2; - else if (ability == ABILITY_SNOW_CLOAK && gSaveBlock1Ptr->weather == WEATHER_SNOW) + else if (MonHasTrait(&gPlayerParty[0], ABILITY_SNOW_CLOAK) && gSaveBlock1Ptr->weather == WEATHER_SNOW) encounterRate /= 2; - else if (ability == ABILITY_QUICK_FEET) + else if (MonHasTrait(&gPlayerParty[0], ABILITY_QUICK_FEET)) encounterRate /= 2; - else if (ability == ABILITY_INFILTRATOR && OW_INFILTRATOR >= GEN_8) + else if (MonHasTrait(&gPlayerParty[0], ABILITY_INFILTRATOR) && OW_INFILTRATOR >= GEN_8) encounterRate /= 2; - else if (ability == ABILITY_NO_GUARD) + else if (MonHasTrait(&gPlayerParty[0], ABILITY_NO_GUARD)) encounterRate *= 2; } if (encounterRate > MAX_ENCOUNTER_RATE) @@ -1106,13 +1103,11 @@ static bool8 IsWildLevelAllowedByRepel(u8 wildLevel) static bool8 IsAbilityAllowingEncounter(u8 level) { - enum Ability ability; - if (GetMonData(&gPlayerParty[0], MON_DATA_SANITY_IS_EGG)) return TRUE; - ability = GetMonAbility(&gPlayerParty[0]); - if (ability == ABILITY_KEEN_EYE || ability == ABILITY_INTIMIDATE) + //ability = GetMonAbility(&gPlayerParty[0]); + if (MonHasTrait(&gPlayerParty[0], ABILITY_KEEN_EYE) || MonHasTrait(&gPlayerParty[0], ABILITY_INTIMIDATE)) { u8 playerMonLevel = GetMonData(&gPlayerParty[0], MON_DATA_LEVEL); if (playerMonLevel > 5 && level <= playerMonLevel - 5 && !(Random() % 2)) diff --git a/test/battle/ability/adaptability.c b/test/battle/ability/adaptability.c index 876dac212c9f..d4d3a5f0ac28 100644 --- a/test/battle/ability/adaptability.c +++ b/test/battle/ability/adaptability.c @@ -62,3 +62,67 @@ SINGLE_BATTLE_TEST("(TERA) Terastallizing into the same type with Adaptability g } TO_DO_BATTLE_TEST("Adaptability does not affect Stellar-type moves"); + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Adaptability increases same-type attack bonus from x1.5 to x2 (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_HYPER_CUTTER; } + PARAMETRIZE { ability = ABILITY_ADAPTABILITY; } + GIVEN { + PLAYER(SPECIES_CRAWDAUNT) { Ability(ABILITY_SHELL_ARMOR); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_WATER_GUN); } + } SCENE { + MESSAGE("Crawdaunt used Water Gun!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + // The jump from 1.5x STAB to 2.0x STAB is a 1.33x boost. + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.33), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("(TERA) Terastallizing into a different type with Adaptability gives 2.0x STAB (Traits)", s16 damage) +{ + bool32 tera; + PARAMETRIZE { tera = GIMMICK_NONE; } + PARAMETRIZE { tera = GIMMICK_TERA; } + GIVEN { + PLAYER(SPECIES_CRAWDAUNT) { Ability(ABILITY_SHELL_ARMOR); Innates(ABILITY_ADAPTABILITY); TeraType(TYPE_NORMAL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HEADBUTT, gimmick: tera); } + } SCENE { + MESSAGE("Crawdaunt used Headbutt!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEADBUTT, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + // The jump from no STAB to 2.0x STAB is a 2.0x boost. + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("(TERA) Terastallizing into the same type with Adaptability gives 2.25x STAB (Traits)", s16 damage) +{ + bool32 tera; + PARAMETRIZE { tera = GIMMICK_NONE; } + PARAMETRIZE { tera = GIMMICK_TERA; } + GIVEN { + PLAYER(SPECIES_CRAWDAUNT) { Ability(ABILITY_SHELL_ARMOR); Innates(ABILITY_ADAPTABILITY); TeraType(TYPE_WATER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_WATER_PULSE, gimmick: tera); } + } SCENE { + MESSAGE("Crawdaunt used Water Pulse!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PULSE, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + // The jump from 2x STAB to 2.25x STAB is a 1.125x boost. + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.125), results[1].damage); + } +} + +TO_DO_BATTLE_TEST("Adaptability does not affect Stellar-type moves (Traits)"); +#endif diff --git a/test/battle/ability/aerilate.c b/test/battle/ability/aerilate.c index 5158a58de937..825bdde4c4f3 100644 --- a/test/battle/ability/aerilate.c +++ b/test/battle/ability/aerilate.c @@ -281,3 +281,237 @@ SINGLE_BATTLE_TEST("Aerilate doesn't affect damaging Z-Move types") TO_DO_BATTLE_TEST("Aerilate doesn't affect Max Strike's type"); TO_DO_BATTLE_TEST("(DYNAMAX) Aerilate turns Max Strike into Max Airstream"); // All other -ate abilities do this, so interpolating this as no Aerilate mon is available in a Dynamax game + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Aerilate boosts power of affected moves by 20% (Gen7+) or 30% (Gen1-6) (Traits)", s16 damage) +{ + u32 move, genConfig; + PARAMETRIZE { move = MOVE_CELEBRATE; genConfig = GEN_7; } + PARAMETRIZE { move = MOVE_CELEBRATE; genConfig = GEN_6; } + PARAMETRIZE { move = MOVE_SKILL_SWAP; genConfig = GEN_7; } + PARAMETRIZE { move = MOVE_SKILL_SWAP; genConfig = GEN_6; } + + GIVEN { + WITH_CONFIG(CONFIG_ATE_MULTIPLIER, genConfig); + ASSUME(GetMoveType(MOVE_TACKLE) == TYPE_NORMAL); + ASSUME(GetMoveEffect(MOVE_SKILL_SWAP) == EFFECT_SKILL_SWAP); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SALAMENCE) { Ability(ABILITY_INTIMIDATE); Innates(ABILITY_MOXIE); Item(ITEM_SALAMENCITE); } + } WHEN { + TURN { MOVE(opponent, move, gimmick: GIMMICK_MEGA); MOVE(player, MOVE_TACKLE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + if (genConfig >= GEN_7) + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.2), results[2].damage); // No STAB + else + EXPECT_MUL_EQ(results[1].damage, Q_4_12(1.3), results[3].damage); // No STAB + } +} + +SINGLE_BATTLE_TEST("Aerilate doesn't affect Weather Ball's type (Traits)", s16 damage) +{ + u32 move1, move2; + PARAMETRIZE { move1 = MOVE_CELEBRATE; move2 = MOVE_CELEBRATE; } + PARAMETRIZE { move1 = MOVE_SUNNY_DAY; move2 = MOVE_CELEBRATE; } + PARAMETRIZE { move1 = MOVE_CELEBRATE; move2 = MOVE_SKILL_SWAP; } + PARAMETRIZE { move1 = MOVE_SUNNY_DAY; move2 = MOVE_SKILL_SWAP; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_WEATHER_BALL) == EFFECT_WEATHER_BALL); + ASSUME(GetMoveType(MOVE_WEATHER_BALL) == TYPE_NORMAL); + ASSUME(GetMoveEffect(MOVE_SKILL_SWAP) == EFFECT_SKILL_SWAP); + ASSUME(GetSpeciesType(SPECIES_PINSIR, 0) == TYPE_BUG); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PINSIR) { Ability(ABILITY_MOXIE); Innates(ABILITY_HYPER_CUTTER); Item(ITEM_PINSIRITE); } + } WHEN { + TURN { MOVE(opponent, move2, gimmick: GIMMICK_MEGA); MOVE(player, move1); } + TURN { MOVE(player, MOVE_WEATHER_BALL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move2, opponent); + ANIMATION(ANIM_TYPE_MOVE, move1, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + if (move1 == MOVE_SUNNY_DAY) + MESSAGE("It's super effective!"); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(6.0), results[1].damage); // double base power + type effectiveness + sun 50% boost + EXPECT_MUL_EQ(results[2].damage, Q_4_12(6.0), results[3].damage); + EXPECT_EQ(results[0].damage, results[2].damage); + EXPECT_EQ(results[1].damage, results[3].damage); + } +} + +SINGLE_BATTLE_TEST("Aerilate doesn't affect Hidden Power's type (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_HIDDEN_POWER) == EFFECT_HIDDEN_POWER); + ASSUME(gTypesInfo[TYPE_ELECTRIC].isHiddenPowerType == TRUE); + ASSUME(GetSpeciesType(SPECIES_DIGLETT, 0) == TYPE_GROUND); + PLAYER(SPECIES_PINSIR) { Ability(ABILITY_HYPER_CUTTER); Innates(ABILITY_AERILATE); HPIV(31); AttackIV(31); DefenseIV(31); SpAttackIV(30); SpDefenseIV(31); SpeedIV(31); } // HP Electric + OPPONENT(SPECIES_DIGLETT); + } WHEN { + TURN { MOVE(player, MOVE_HIDDEN_POWER); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_HIDDEN_POWER, player); } + MESSAGE("It doesn't affect the opposing Diglett…"); + } +} + +TO_DO_BATTLE_TEST("Aerilate doesn't override Electrify (Gen7+) (Traits)"); // No mon with Aerilate exists in Gen8+, but probably behaves similar to Pixilate, which does. +TO_DO_BATTLE_TEST("Aerilate doesn't override Ion Deluge (Gen7+) (Traits)"); // Ion Deluge doesn't exist in Gen 8+, but we probably could assume it behaves similar to under Electrify. TODO: Test by hacking SV. +TO_DO_BATTLE_TEST("Aerilate overrides Electrify (Gen6) (Traits)") +TO_DO_BATTLE_TEST("Aerilate overrides Ion Deluge (Gen6) (Traits)") +TO_DO_BATTLE_TEST("Aerilate doesn't affect Tera Starstorm's type (Traits)"); +TO_DO_BATTLE_TEST("Aerilate doesn't affect Max Strike's type (Traits)"); +TO_DO_BATTLE_TEST("Aerilate doesn't affect Terrain Pulse's type (Traits)"); +TO_DO_BATTLE_TEST("Aerilate doesn't affect damaging Z-Move types (Traits)"); +TO_DO_BATTLE_TEST("(DYNAMAX) Aerilate turns Max Strike into Max Airstream (Traits)"); // All other -ate abilities do this, so interpolating this as no Aerilate mon is available in a Dynamax game +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Aerilate turns a Normal-type move into Flying-type move (Multi)") +{ + GIVEN { + PLAYER(SPECIES_MEGANIUM); + OPPONENT(SPECIES_SALAMENCE) { Items(ITEM_ORAN_BERRY, ITEM_SALAMENCITE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH, gimmick: GIMMICK_MEGA); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Aerilate can not turn certain moves into Flying type moves (Multi)") +{ + u32 move; + PARAMETRIZE { move = MOVE_WEATHER_BALL; } + // PARAMETRIZE { move = MOVE_NATURAL_GIFT; } TODO: handle this case via Skill Swap + PARAMETRIZE { move = MOVE_JUDGMENT; } + PARAMETRIZE { move = MOVE_TECHNO_BLAST; } + PARAMETRIZE { move = MOVE_REVELATION_DANCE; } + PARAMETRIZE { move = MOVE_MULTI_ATTACK; } + PARAMETRIZE { move = MOVE_TERRAIN_PULSE; } + GIVEN { + PLAYER(SPECIES_MEGANIUM); + OPPONENT(SPECIES_SALAMENCE) { Items(ITEM_ORAN_BERRY, ITEM_SALAMENCITE); } + } WHEN { + TURN { MOVE(opponent, move, gimmick: GIMMICK_MEGA); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponent); + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + NONE_OF { + MESSAGE("It's super effective!"); + } + } +} + +SINGLE_BATTLE_TEST("Aerilate boosts power of affected moves by 20% (Gen7+) or 30% (Gen1-6) (Multi)", s16 damage) +{ + u32 move, genConfig; + PARAMETRIZE { move = MOVE_CELEBRATE; genConfig = GEN_7; } + PARAMETRIZE { move = MOVE_CELEBRATE; genConfig = GEN_6; } + PARAMETRIZE { move = MOVE_SKILL_SWAP; genConfig = GEN_7; } + PARAMETRIZE { move = MOVE_SKILL_SWAP; genConfig = GEN_6; } + + GIVEN { + WITH_CONFIG(CONFIG_ATE_MULTIPLIER, genConfig); + ASSUME(GetMoveType(MOVE_TACKLE) == TYPE_NORMAL); + ASSUME(GetMoveEffect(MOVE_SKILL_SWAP) == EFFECT_SKILL_SWAP); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SALAMENCE) { Ability(ABILITY_MOXIE); Items(ITEM_ORAN_BERRY, ITEM_SALAMENCITE); } + } WHEN { + TURN { MOVE(opponent, move, gimmick: GIMMICK_MEGA); MOVE(player, MOVE_TACKLE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + if (genConfig >= GEN_7) + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.2), results[2].damage); // No STAB + else + EXPECT_MUL_EQ(results[1].damage, Q_4_12(1.3), results[3].damage); // No STAB + } +} + +SINGLE_BATTLE_TEST("Aerilate doesn't affect Weather Ball's type (Multi)", s16 damage) +{ + u32 move1, move2; + PARAMETRIZE { move1 = MOVE_CELEBRATE; move2 = MOVE_CELEBRATE; } + PARAMETRIZE { move1 = MOVE_SUNNY_DAY; move2 = MOVE_CELEBRATE; } + PARAMETRIZE { move1 = MOVE_CELEBRATE; move2 = MOVE_SKILL_SWAP; } + PARAMETRIZE { move1 = MOVE_SUNNY_DAY; move2 = MOVE_SKILL_SWAP; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_WEATHER_BALL) == EFFECT_WEATHER_BALL); + ASSUME(GetMoveType(MOVE_WEATHER_BALL) == TYPE_NORMAL); + ASSUME(GetMoveEffect(MOVE_SKILL_SWAP) == EFFECT_SKILL_SWAP); + ASSUME(GetSpeciesType(SPECIES_PINSIR, 0) == TYPE_BUG); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PINSIR) { Ability(ABILITY_HYPER_CUTTER); Items(ITEM_ORAN_BERRY, ITEM_PINSIRITE); } + } WHEN { + TURN { MOVE(opponent, move2, gimmick: GIMMICK_MEGA); MOVE(player, move1); } + TURN { MOVE(player, MOVE_WEATHER_BALL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move2, opponent); + ANIMATION(ANIM_TYPE_MOVE, move1, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + if (move1 == MOVE_SUNNY_DAY) + MESSAGE("It's super effective!"); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(6.0), results[1].damage); // double base power + type effectiveness + sun 50% boost + EXPECT_MUL_EQ(results[2].damage, Q_4_12(6.0), results[3].damage); + EXPECT_EQ(results[0].damage, results[2].damage); + EXPECT_EQ(results[1].damage, results[3].damage); + } +} + +SINGLE_BATTLE_TEST("Aerilate doesn't affect Natural Gift's type (Multi)") +{ + u16 move; + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SKILL_SWAP; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + ASSUME(GetMoveEffect(MOVE_SKILL_SWAP) == EFFECT_SKILL_SWAP); + ASSUME(gNaturalGiftTable[ITEM_TO_BERRY(ITEM_PERSIM_BERRY)].type == TYPE_GROUND); + ASSUME(GetSpeciesType(SPECIES_SALAMENCE_MEGA, 0) == TYPE_FLYING || GetSpeciesType(SPECIES_SALAMENCE_MEGA, 1) == TYPE_FLYING); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_PERSIM_BERRY); } + OPPONENT(SPECIES_SALAMENCE) { Items(ITEM_ORAN_BERRY, ITEM_SALAMENCITE); } + } WHEN { + TURN { MOVE(opponent, move, gimmick: GIMMICK_MEGA); MOVE(player, MOVE_NATURAL_GIFT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player); } + MESSAGE("It doesn't affect the opposing Salamence…"); + } +} + +SINGLE_BATTLE_TEST("Aerilate doesn't affect Judgment / Techno Blast / Multi-Attack's type (Multi)") +{ + u16 move, item; + PARAMETRIZE { move = MOVE_JUDGMENT; item = ITEM_ZAP_PLATE; } + PARAMETRIZE { move = MOVE_TECHNO_BLAST; item = ITEM_SHOCK_DRIVE; } + PARAMETRIZE { move = MOVE_MULTI_ATTACK; item = ITEM_ELECTRIC_MEMORY; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_JUDGMENT) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetMoveEffect(MOVE_TECHNO_BLAST) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetMoveEffect(MOVE_MULTI_ATTACK) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetMoveEffect(MOVE_SKILL_SWAP) == EFFECT_SKILL_SWAP); + ASSUME(gItemsInfo[ITEM_ZAP_PLATE].holdEffect == HOLD_EFFECT_PLATE); + ASSUME(gItemsInfo[ITEM_ZAP_PLATE].secondaryId == TYPE_ELECTRIC); + ASSUME(gItemsInfo[ITEM_SHOCK_DRIVE].holdEffect == HOLD_EFFECT_DRIVE); + ASSUME(gItemsInfo[ITEM_SHOCK_DRIVE].secondaryId == TYPE_ELECTRIC); + ASSUME(gItemsInfo[ITEM_ELECTRIC_MEMORY].holdEffect == HOLD_EFFECT_MEMORY); + ASSUME(gItemsInfo[ITEM_ELECTRIC_MEMORY].secondaryId == TYPE_ELECTRIC); + ASSUME(GetSpeciesType(SPECIES_DIGLETT, 0) == TYPE_GROUND); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, item); } + OPPONENT(SPECIES_SALAMENCE) { Items(ITEM_ORAN_BERRY, ITEM_SALAMENCITE); } + OPPONENT(SPECIES_DIGLETT); + } WHEN { + TURN { MOVE(opponent, MOVE_SKILL_SWAP, gimmick: GIMMICK_MEGA); } + TURN { SWITCH(opponent, 1); MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); + NOT { ANIMATION(ANIM_TYPE_MOVE, move, player); } + MESSAGE("It doesn't affect the opposing Diglett…"); + } +} +#endif diff --git a/test/battle/ability/aftermath.c b/test/battle/ability/aftermath.c index 88c50a5562a1..944af3cb3ae5 100644 --- a/test/battle/ability/aftermath.c +++ b/test/battle/ability/aftermath.c @@ -48,3 +48,53 @@ SINGLE_BATTLE_TEST("Aftermath ability pop-up will be displayed correctly: oppone ABILITY_POPUP(player, ABILITY_AFTERMATH); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Aftermath damages the attacker by 1/4th of its max HP if fainted by a contact move (Traits)") +{ + s16 aftermathDamage; + + GIVEN { + PLAYER(SPECIES_VOLTORB) { HP(1); Ability(ABILITY_SOUNDPROOF); Innates(ABILITY_AFTERMATH); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN {MOVE(opponent, MOVE_SCRATCH);} + } SCENE { + MESSAGE("The opposing Wobbuffet used Scratch!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + MESSAGE("Voltorb fainted!"); + ABILITY_POPUP(player, ABILITY_AFTERMATH); + HP_BAR(opponent, captureDamage: &aftermathDamage); + } THEN { + EXPECT_EQ(aftermathDamage, opponent->maxHP / 4); + } +} + +SINGLE_BATTLE_TEST("Aftermath ability pop-up will be displayed correctly: player point of view (Traits)") +{ + GIVEN { + PLAYER(SPECIES_SHROOMISH) { Ability(ABILITY_QUICK_FEET); Innates(ABILITY_POISON_HEAL); }; + OPPONENT(SPECIES_VOLTORB) { HP(1); Ability(ABILITY_SOUNDPROOF); Innates(ABILITY_AFTERMATH); }; + } WHEN { + TURN {MOVE(player, MOVE_HEADBUTT);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEADBUTT, player); + MESSAGE("The opposing Voltorb fainted!"); + ABILITY_POPUP(opponent, ABILITY_AFTERMATH); + } +} + +SINGLE_BATTLE_TEST("Aftermath ability pop-up will be displayed correctly: opponent point of view (Traits)") +{ + GIVEN { + PLAYER(SPECIES_VOLTORB) { HP(1); Ability(ABILITY_SOUNDPROOF); Innates(ABILITY_AFTERMATH); }; + OPPONENT(SPECIES_SHROOMISH) { Ability(ABILITY_QUICK_FEET); Innates(ABILITY_POISON_HEAL); }; + } WHEN { + TURN {MOVE(opponent, MOVE_HEADBUTT);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEADBUTT, opponent); + MESSAGE("Voltorb fainted!"); + ABILITY_POPUP(player, ABILITY_AFTERMATH); + } +} +#endif diff --git a/test/battle/ability/analytic.c b/test/battle/ability/analytic.c index da2b8e28a682..050c1adeece9 100644 --- a/test/battle/ability/analytic.c +++ b/test/battle/ability/analytic.c @@ -77,3 +77,32 @@ TO_DO_BATTLE_TEST("Analytic does not take into account modifications to speeed a // Triple Battles needed to test //TO_DO_BATTLE_TEST("If the Pokémon with Analytic is targeting a Pokémon in a flank position that chooses to switch with its ally in the middle, its move's power will always be normal when it attacks the Pokémon that is shifted into the flank position"); //TO_DO_BATTLE_TEST("If the Pokémon with Analytic targets a Pokémon in the middle whose ally on a flank chooses to shift into the middle position, its move's power still depends on whether the Pokémon that was in the middle (and is now on a flank) has acted when the Pokémon with Analytic uses its move"); + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Analytic increases the power of moves by 30% if it's the last one that uses its move (Traits)", s16 damage) +{ + u32 speed; + + PARAMETRIZE { speed = 3; } + PARAMETRIZE { speed = 1; } + + GIVEN { + PLAYER(SPECIES_MAGNEMITE) { Ability(ABILITY_STURDY); Innates(ABILITY_ANALYTIC); Speed(speed); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(2); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } 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) (Traits)"); //Eg. Paralysis, Power Weight, Stall +TO_DO_BATTLE_TEST("Analytic does not take into account modifications to speeed an priority (Gen 8) (Traits)"); //Eg. Paralysis, Power Weight, Stall +TO_DO_BATTLE_TEST("Analytic takes into account the turn order of what fainted Pokémon would've moved (Traits)"); + +// Triple Battles needed to test +//TO_DO_BATTLE_TEST("If the Pokémon with Analytic is targeting a Pokémon in a flank position that chooses to switch with its ally in the middle, its move's power will always be normal when it attacks the Pokémon that is shifted into the flank position (Traits)"); +//TO_DO_BATTLE_TEST("If the Pokémon with Analytic targets a Pokémon in the middle whose ally on a flank chooses to shift into the middle position, its move's power still depends on whether the Pokémon that was in the middle (and is now on a flank) has acted when the Pokémon with Analytic uses its move (Traits)"); +#endif diff --git a/test/battle/ability/anger_point.c b/test/battle/ability/anger_point.c index 7e8be8cb3378..fc6e383096e4 100644 --- a/test/battle/ability/anger_point.c +++ b/test/battle/ability/anger_point.c @@ -70,3 +70,76 @@ SINGLE_BATTLE_TEST("Anger Point does not trigger when a substitute takes the hit EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Anger Point raises Attack stage to maximum after receiving a critical hit (Traits)") +{ + GIVEN { + ASSUME(MoveAlwaysCrits(MOVE_FROST_BREATH)); + PLAYER(SPECIES_PRIMEAPE) { Ability(ABILITY_VITAL_SPIRIT); Innates(ABILITY_ANGER_POINT); } + OPPONENT(SPECIES_SNORUNT); + } WHEN { + TURN { MOVE(opponent, MOVE_FROST_BREATH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FROST_BREATH, opponent); + MESSAGE("A critical hit!"); + ABILITY_POPUP(player, ABILITY_ANGER_POINT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Primeape's Anger Point maxed its Attack!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], MAX_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Anger Point does not trigger when already at maximum Attack stage (Traits)") +{ + GIVEN { + ASSUME(MoveAlwaysCrits(MOVE_FROST_BREATH)); + ASSUME(GetMoveEffect(MOVE_BELLY_DRUM) == EFFECT_BELLY_DRUM); + PLAYER(SPECIES_PRIMEAPE) { Ability(ABILITY_VITAL_SPIRIT); Innates(ABILITY_ANGER_POINT); Speed(2); } + OPPONENT(SPECIES_SNORUNT) { Speed(1); } + } WHEN { + TURN { MOVE(player, MOVE_BELLY_DRUM); MOVE(opponent, MOVE_FROST_BREATH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BELLY_DRUM, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Primeape cut its own HP and maximized its Attack!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FROST_BREATH, opponent); + MESSAGE("A critical hit!"); + NONE_OF { + ABILITY_POPUP(player, ABILITY_ANGER_POINT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Primeape's Anger Point maxed its Attack!"); + } + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], MAX_STAT_STAGE); + } +} + +TO_DO_BATTLE_TEST("Anger Point triggers when a substitute takes the hit (Gen4) (Traits)"); + +SINGLE_BATTLE_TEST("Anger Point does not trigger when a substitute takes the hit (Gen5+) (Traits)") +{ + GIVEN { + ASSUME(MoveAlwaysCrits(MOVE_FROST_BREATH)); + ASSUME(GetMoveEffect(MOVE_SUBSTITUTE) == EFFECT_SUBSTITUTE); + PLAYER(SPECIES_PRIMEAPE) { Ability(ABILITY_VITAL_SPIRIT); Innates(ABILITY_ANGER_POINT); Speed(2); } + OPPONENT(SPECIES_SNORUNT) { Speed(1); } + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_FROST_BREATH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, player); + MESSAGE("Primeape put in a substitute!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FROST_BREATH, opponent); + MESSAGE("A critical hit!"); + NONE_OF { + ABILITY_POPUP(player, ABILITY_ANGER_POINT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Primeape's Anger Point maxed its Attack!"); + } + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} + +#endif diff --git a/test/battle/ability/anger_shell.c b/test/battle/ability/anger_shell.c index 6c9064ee0d7b..f5fb5a9c434d 100644 --- a/test/battle/ability/anger_shell.c +++ b/test/battle/ability/anger_shell.c @@ -113,3 +113,118 @@ SINGLE_BATTLE_TEST("Anger Shell does not activate if move is boosted by Sheer Fo EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Anger Shell activates only if the target had more than 50% of its HP (Traits)") +{ + bool32 activates = FALSE; + u16 maxHp = 500, hp = 0; + + PARAMETRIZE { hp = 250; activates = FALSE; } + PARAMETRIZE { hp = 249; activates = FALSE; } + PARAMETRIZE { hp = 100; activates = FALSE; } + PARAMETRIZE { hp = 50; activates = FALSE; } + PARAMETRIZE { hp = 251; activates = TRUE; } + PARAMETRIZE { hp = 254; activates = TRUE; } + + GIVEN { + ASSUME(!IsBattleMoveStatus(MOVE_SCRATCH)); + PLAYER(SPECIES_KLAWF) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_ANGER_SHELL); MaxHP(maxHp); HP(hp); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + if (activates) { + ABILITY_POPUP(player, ABILITY_ANGER_SHELL); + } else { + NOT ABILITY_POPUP(player, ABILITY_ANGER_SHELL); + } + } THEN { + if (activates) { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 1); + } + } +} + +SINGLE_BATTLE_TEST("Anger Shell lowers Def/Sp.Def by 1 and raises Atk/Sp.Atk/Spd by 1 (Traits)") +{ + u16 maxHp = 500; + GIVEN { + ASSUME(!IsBattleMoveStatus(MOVE_SCRATCH)); + PLAYER(SPECIES_KLAWF) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_ANGER_SHELL); MaxHP(maxHp); HP(maxHp / 2 + 1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(player, ABILITY_ANGER_SHELL); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Klawf's Defense fell!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Klawf's Sp. Def fell!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Klawf's Attack rose!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Klawf's Sp. Atk rose!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Klawf's Speed rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Anger Shell activates after all hits from a multi-hit move (Traits)") +{ + u32 j; + u16 maxHp = 500; + GIVEN { + ASSUME(GetMoveEffect(MOVE_DOUBLE_SLAP) == EFFECT_MULTI_HIT); + PLAYER(SPECIES_KLAWF) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_ANGER_SHELL); MaxHP(maxHp); HP(maxHp / 2 + 1); } + OPPONENT(SPECIES_SHELLDER) { Ability(ABILITY_SHELL_ARMOR); Innates(ABILITY_SKILL_LINK); } // Always hits 5 times. + } WHEN { + TURN { MOVE(opponent, MOVE_DOUBLE_SLAP); } + } SCENE { + for (j = 0; j < 4; j++) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_SLAP, opponent); + NOT ABILITY_POPUP(player, ABILITY_ANGER_SHELL); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_SLAP, opponent); + ABILITY_POPUP(player, ABILITY_ANGER_SHELL); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Anger Shell does not activate if move is boosted by Sheer Force (Traits)") +{ + u16 maxHp = 500; + GIVEN { + PLAYER(SPECIES_KLAWF) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_ANGER_SHELL); MaxHP(maxHp); HP(maxHp / 2 + 1); } + OPPONENT(SPECIES_NIDOKING) { Ability(ABILITY_POISON_POINT); Innates(ABILITY_SHEER_FORCE); } + } WHEN { + TURN { MOVE(opponent, MOVE_EMBER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); + NOT ABILITY_POPUP(player, ABILITY_ANGER_SHELL); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} +#endif diff --git a/test/battle/ability/anticipation.c b/test/battle/ability/anticipation.c index c20153b1eaf3..06cf19afca0c 100644 --- a/test/battle/ability/anticipation.c +++ b/test/battle/ability/anticipation.c @@ -358,3 +358,433 @@ TO_DO_BATTLE_TEST("Anticipation considers Scrappy and Normalize into their effec TO_DO_BATTLE_TEST("Anticipation considers Gravity into their effectiveness (Gen4)"); TO_DO_BATTLE_TEST("Anticipation doesn't trigger from Counter, Metal Burst or Mirror Coat (Gen4)"); TO_DO_BATTLE_TEST("Anticipation treats Hidden Power as Normal Type (Gen4-5)"); + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Anticipation causes notifies if an opponent has a super-effective move (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_CLOSE_COMBAT) == TYPE_FIGHTING); + ASSUME(GetSpeciesType(SPECIES_EEVEE, 0) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_EEVEE, 1) == TYPE_NORMAL); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ADAPTABILITY); Innates(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CLOSE_COMBAT, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation does not trigger even when a move is super effective on only 1 type (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WHISCASH) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_PIKACHU) { Moves(MOVE_CELEBRATE, MOVE_THUNDERBOLT); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation causes notifies if an opponent has a One-hit KO move (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FISSURE) == EFFECT_OHKO); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ADAPTABILITY); Innates(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_FISSURE, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation treats Self-Destruct and Explosion like all other Normal types (Gen5+) (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ADAPTABILITY); Innates(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_EXPLOSION, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation doesn't consider Normalize into their effectiveness (Gen5+) (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_CLOSE_COMBAT) == TYPE_FIGHTING); + ASSUME(GetSpeciesType(SPECIES_EEVEE, 0) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_EEVEE, 1) == TYPE_NORMAL); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ADAPTABILITY); Innates(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_DELCATTY) { Ability(ABILITY_CUTE_CHARM); Innates(ABILITY_NORMALIZE); Moves(MOVE_CLOSE_COMBAT, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation doesn't consider Scrappy into their effectiveness (Gen5+) (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_CLOSE_COMBAT) == TYPE_FIGHTING); + ASSUME(GetSpeciesType(SPECIES_DOUBLADE, 0) == TYPE_STEEL); + ASSUME(GetSpeciesType(SPECIES_DOUBLADE, 1) == TYPE_GHOST); + PLAYER(SPECIES_DOUBLADE) { Ability(ABILITY_NO_GUARD); Innates(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_KANGASKHAN) { Ability(ABILITY_EARLY_BIRD); Innates(ABILITY_SCRAPPY); Moves(MOVE_CLOSE_COMBAT, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation doesn't consider Gravity into their effectiveness (Gen5+) (Traits)") +{ + GIVEN { + PLAYER(SPECIES_SKARMORY) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_EEVEE) { Ability(ABILITY_ADAPTABILITY); Moves(MOVE_EARTHQUAKE, MOVE_GRAVITY, MOVE_SCRATCH, MOVE_POUND); } + } WHEN { + TURN { MOVE(opponent, MOVE_GRAVITY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRAVITY, opponent); + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation counts Counter, Metal Burst or Mirror Coat as attacking moves of their types (Gen5+) (Traits)") +{ + u32 move, species; + enum Type typeAtk, typeDef; + PARAMETRIZE { move = MOVE_COUNTER; species = SPECIES_RATICATE; typeAtk = TYPE_FIGHTING; typeDef = TYPE_NORMAL; } + PARAMETRIZE { move = MOVE_METAL_BURST; species = SPECIES_ROGGENROLA; typeAtk = TYPE_STEEL; typeDef = TYPE_ROCK; } + PARAMETRIZE { move = MOVE_MIRROR_COAT; species = SPECIES_NIDORINO; typeAtk = TYPE_PSYCHIC; typeDef = TYPE_POISON; } + GIVEN { + ASSUME(GetMoveType(move) == typeAtk); + ASSUME(GetSpeciesType(species, 0) == typeDef); + ASSUME(GetSpeciesType(species, 1) == typeDef); + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_EEVEE) { Ability(ABILITY_ADAPTABILITY); Moves(move, MOVE_SKILL_SWAP, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation considers Synchronoise as an ordinary Psychic-type move (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SYNCHRONOISE) == TYPE_PSYCHIC); + ASSUME(GetSpeciesType(SPECIES_NIDORINO, 0) == TYPE_POISON); + ASSUME(GetSpeciesType(SPECIES_NIDORINO, 1) == TYPE_POISON); + ASSUME(GetSpeciesType(SPECIES_EEVEE, 0) != TYPE_POISON); + ASSUME(GetSpeciesType(SPECIES_EEVEE, 1) != TYPE_POISON); + PLAYER(SPECIES_NIDORINO) { Ability(ABILITY_POISON_POINT); Innates(ABILITY_ANTICIPATION); Moves(MOVE_SYNCHRONOISE, MOVE_SKILL_SWAP, MOVE_POUND, MOVE_CELEBRATE); } + OPPONENT(SPECIES_EEVEE) { Ability(ABILITY_ADAPTABILITY); Moves(MOVE_SYNCHRONOISE, MOVE_SKILL_SWAP, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation considers Freeze-Dry as an ordinary Ice-type move (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_FREEZE_DRY) == TYPE_ICE); + ASSUME(GetSpeciesType(SPECIES_SQUIRTLE, 0) == TYPE_WATER); + ASSUME(GetSpeciesType(SPECIES_SQUIRTLE, 1) == TYPE_WATER); + PLAYER(SPECIES_SQUIRTLE) { Ability(ABILITY_TORRENT); Innates(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_EEVEE) { Ability(ABILITY_ADAPTABILITY); Moves(MOVE_FREEZE_DRY, MOVE_SKILL_SWAP, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation considers Flying Press as an ordinary Fighting-type move (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_FLYING_PRESS) == TYPE_FIGHTING); + ASSUME(GetSpeciesType(SPECIES_TANGELA, 0) == TYPE_GRASS); + ASSUME(GetSpeciesType(SPECIES_TANGELA, 1) == TYPE_GRASS); + PLAYER(SPECIES_TANGELA) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_EEVEE) { Ability(ABILITY_ADAPTABILITY); Moves(MOVE_FLYING_PRESS, MOVE_SKILL_SWAP, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation considers Aura Wheel as an ordinary Electric-type move (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_AURA_WHEEL) == TYPE_ELECTRIC); + ASSUME(GetSpeciesType(SPECIES_PONYTA_GALAR, 0) == TYPE_PSYCHIC); + ASSUME(GetSpeciesType(SPECIES_PONYTA_GALAR, 1) == TYPE_PSYCHIC); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_PONYTA_GALAR) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_MORPEKO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_HUNGER_SWITCH); Moves(MOVE_AURA_WHEEL, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation treats dynamic move types as their base type (Normal), Judgment (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_JUDGMENT) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetSpeciesType(SPECIES_EEVEE, 0) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_EEVEE, 1) == TYPE_NORMAL); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ADAPTABILITY); Innates(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_ARCEUS) { Item(ITEM_FIST_PLATE); Moves(MOVE_JUDGMENT, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation treats dynamic move types as their base type (Normal), Weather Ball (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_WEATHER_BALL) == EFFECT_WEATHER_BALL); + ASSUME(GetSpeciesType(SPECIES_FERROTHORN, 0) == TYPE_GRASS); + ASSUME(GetSpeciesType(SPECIES_FERROTHORN, 1) == TYPE_STEEL); + PLAYER(SPECIES_FERROTHORN) { Ability(ABILITY_IRON_BARBS); Innates(ABILITY_ANTICIPATION); Speed(2); } + OPPONENT(SPECIES_NINETALES) { Ability(ABILITY_FLASH_FIRE); Innates(ABILITY_DROUGHT); Moves(MOVE_WEATHER_BALL, MOVE_SKILL_SWAP, MOVE_POUND, MOVE_CELEBRATE); Speed(4); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation treats dynamic move types as their base type (Normal), Natural Gift (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + ASSUME(GetSpeciesType(SPECIES_EEVEE, 0) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_EEVEE, 1) == TYPE_NORMAL); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ADAPTABILITY); Innates(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LEPPA_BERRY); Moves(MOVE_NATURAL_GIFT, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation treats dynamic move types as their base type (Normal), Techno Blast (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TECHNO_BLAST) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetSpeciesType(SPECIES_FERROTHORN, 0) == TYPE_GRASS); + ASSUME(GetSpeciesType(SPECIES_FERROTHORN, 1) == TYPE_STEEL); + PLAYER(SPECIES_FERROTHORN) { Ability(ABILITY_IRON_BARBS); Innates(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_GENESECT) { Item(ITEM_BURN_DRIVE); Moves(MOVE_TECHNO_BLAST, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation treats dynamic move types as their base type (Normal), Revelation Dance (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_REVELATION_DANCE) == EFFECT_REVELATION_DANCE); + ASSUME(GetSpeciesType(SPECIES_FERROTHORN, 0) == TYPE_GRASS); + ASSUME(GetSpeciesType(SPECIES_FERROTHORN, 1) == TYPE_STEEL); + ASSUME(GetSpeciesType(SPECIES_ORICORIO_BAILE, 0) == TYPE_FIRE); + PLAYER(SPECIES_FERROTHORN) { Ability(ABILITY_IRON_BARBS); Innates(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_ORICORIO_BAILE) { Moves(MOVE_REVELATION_DANCE, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation treats dynamic move types as their base type (Normal), Multi-Attack (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_MULTI_ATTACK) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetSpeciesType(SPECIES_EEVEE, 0) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_EEVEE, 1) == TYPE_NORMAL); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ADAPTABILITY); Innates(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_SILVALLY) { Item(ITEM_FIGHTING_MEMORY); Moves(MOVE_MULTI_ATTACK, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation does not consider Strong Winds on type matchups (Traits)") +{ + GIVEN { + ASSUME(GetSpeciesType(SPECIES_RAYQUAZA_MEGA, 0) == TYPE_DRAGON); + ASSUME(GetSpeciesType(SPECIES_RAYQUAZA_MEGA, 1) == TYPE_FLYING); + PLAYER(SPECIES_RAYQUAZA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ANTICIPATION); Moves(MOVE_DRAGON_ASCENT, MOVE_CELEBRATE); } + OPPONENT(SPECIES_EEVEE) { Ability(ABILITY_ADAPTABILITY); Moves(MOVE_ROCK_SLIDE, MOVE_SKILL_SWAP, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ANTICIPATION); + ABILITY_POPUP(player, ABILITY_DELTA_STREAM); + ABILITY_POPUP(player, ABILITY_ANTICIPATION); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } +} + +SINGLE_BATTLE_TEST("Anticipation does not consider ate-abilities (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_WORMADAM_PLANT, 0) == TYPE_BUG); + ASSUME(GetSpeciesType(SPECIES_WORMADAM_PLANT, 1) == TYPE_GRASS); + PLAYER(SPECIES_WORMADAM_PLANT) { Ability(ABILITY_OVERCOAT); Innates(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_AURORUS) { Ability(ABILITY_SNOW_WARNING); Innates(ABILITY_REFRIGERATE); Moves(MOVE_GROWL, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation treats Hidden Power as its dynamic type (Gen6+) (Traits)") +{ + KNOWN_FAILING; + GIVEN { + ASSUME(GetSpeciesType(SPECIES_EEVEE, 0) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_EEVEE, 1) == TYPE_NORMAL); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ADAPTABILITY); Innates(ABILITY_ANTICIPATION); Item(ITEM_CHOPLE_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_HIDDEN_POWER, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); HPIV(30); AttackIV(2); DefenseIV(31); SpAttackIV(30); SpDefenseIV(30); SpeedIV(30); } + } WHEN { + TURN { MOVE(opponent, MOVE_HIDDEN_POWER); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ANTICIPATION); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); // Check that the item is triggered + ANIMATION(ANIM_TYPE_MOVE, MOVE_HIDDEN_POWER, opponent); + HP_BAR(opponent); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Anticipation considers Inverse Battle types (Traits)") +{ + GIVEN { + FLAG_SET(B_FLAG_INVERSE_BATTLE); + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_FERROTHORN, 0) == TYPE_GRASS); + ASSUME(GetSpeciesType(SPECIES_FERROTHORN, 1) == TYPE_STEEL); + PLAYER(SPECIES_FERROTHORN) { Ability(ABILITY_ADAPTABILITY); Innates(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_GROWL, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +TO_DO_BATTLE_TEST("Anticipation causes notifies if an opponent has a Self-Destruct or Explosion (Gen4) (Traits)"); +TO_DO_BATTLE_TEST("Anticipation considers Scrappy and Normalize into their effectiveness (Gen4) (Traits)"); +TO_DO_BATTLE_TEST("Anticipation considers Gravity into their effectiveness (Gen4) (Traits)"); +TO_DO_BATTLE_TEST("Anticipation doesn't trigger from Counter, Metal Burst or Mirror Coat (Gen4) (Traits)"); +TO_DO_BATTLE_TEST("Anticipation treats Hidden Power as Normal Type (Gen4-5) (Traits)"); +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Anticipation treats dynamic move types as their base type (Normal), Judgment (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_JUDGMENT) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetSpeciesType(SPECIES_EEVEE, 0) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_EEVEE, 1) == TYPE_NORMAL); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_ARCEUS) { Items(ITEM_ORAN_BERRY, ITEM_FIST_PLATE); Moves(MOVE_JUDGMENT, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation treats dynamic move types as their base type (Normal), Natural Gift (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + ASSUME(GetSpeciesType(SPECIES_EEVEE, 0) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_EEVEE, 1) == TYPE_NORMAL); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, ITEM_LEPPA_BERRY); Moves(MOVE_NATURAL_GIFT, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation treats dynamic move types as their base type (Normal), Techno Blast (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TECHNO_BLAST) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetSpeciesType(SPECIES_FERROTHORN, 0) == TYPE_GRASS); + ASSUME(GetSpeciesType(SPECIES_FERROTHORN, 1) == TYPE_STEEL); + PLAYER(SPECIES_FERROTHORN) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_GENESECT) { Items(ITEM_ORAN_BERRY, ITEM_BURN_DRIVE); Moves(MOVE_TECHNO_BLAST, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation treats dynamic move types as their base type (Normal), Multi-Attack (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_MULTI_ATTACK) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetSpeciesType(SPECIES_EEVEE, 0) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_EEVEE, 1) == TYPE_NORMAL); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_SILVALLY) { Items(ITEM_ORAN_BERRY, ITEM_FIGHTING_MEMORY); Moves(MOVE_MULTI_ATTACK, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + +SINGLE_BATTLE_TEST("Anticipation treats Hidden Power as its dynamic type (Gen6+) (Multi)") +{ + KNOWN_FAILING; + GIVEN { + ASSUME(GetSpeciesType(SPECIES_EEVEE, 0) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_EEVEE, 1) == TYPE_NORMAL); + PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); Items(ITEM_ORAN_BERRY, ITEM_CHOPLE_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_HIDDEN_POWER, MOVE_SCRATCH, MOVE_POUND, MOVE_CELEBRATE); HPIV(30); AttackIV(2); DefenseIV(31); SpAttackIV(30); SpDefenseIV(30); SpeedIV(30); } + } WHEN { + TURN { MOVE(opponent, MOVE_HIDDEN_POWER); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ANTICIPATION); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); // Check that the item is triggered + ANIMATION(ANIM_TYPE_MOVE, MOVE_HIDDEN_POWER, opponent); + HP_BAR(opponent); + MESSAGE("It's super effective!"); + } +} +#endif diff --git a/test/battle/ability/aroma_veil.c b/test/battle/ability/aroma_veil.c index 9a911f5d2129..a142f9361156 100644 --- a/test/battle/ability/aroma_veil.c +++ b/test/battle/ability/aroma_veil.c @@ -196,3 +196,201 @@ DOUBLE_BATTLE_TEST("Aroma Veil prevents Psychic Noise's effect") // Marked in Bulbapedia as need of research //TO_DO_BATTLE_TEST("Aroma Veil prevents G-Max Meltdown's effect"); + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Aroma Veil protects the Pokémon's side from Taunt (Traits)") +{ + struct BattlePokemon *moveTarget = NULL; + PARAMETRIZE { moveTarget = playerLeft; } + PARAMETRIZE { moveTarget = playerRight; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_TAUNT) == EFFECT_TAUNT); + ASSUME(GetMoveCategory(MOVE_HARDEN) == DAMAGE_CATEGORY_STATUS); + PLAYER(SPECIES_AROMATISSE) { Ability(ABILITY_HEALER); Innates(ABILITY_AROMA_VEIL); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_TAUNT, target: moveTarget); MOVE(moveTarget, MOVE_HARDEN); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TAUNT, opponentLeft); + ABILITY_POPUP(playerLeft, ABILITY_AROMA_VEIL); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HARDEN, moveTarget); + } +} + +DOUBLE_BATTLE_TEST("Aroma Veil protects the Pokémon's side from Torment (Traits)") +{ + struct BattlePokemon *moveTarget = NULL; + PARAMETRIZE { moveTarget = playerLeft; } + PARAMETRIZE { moveTarget = playerRight; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_TORMENT) == EFFECT_TORMENT); + ASSUME(GetMoveCategory(MOVE_HARDEN) == DAMAGE_CATEGORY_STATUS); + PLAYER(SPECIES_AROMATISSE) { Ability(ABILITY_HEALER); Innates(ABILITY_AROMA_VEIL); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_TORMENT, target: moveTarget); MOVE(moveTarget, MOVE_HARDEN); } + TURN { MOVE(moveTarget, MOVE_HARDEN); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TORMENT, opponentLeft); + ABILITY_POPUP(playerLeft, ABILITY_AROMA_VEIL); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HARDEN, moveTarget); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HARDEN, moveTarget); + } +} + +DOUBLE_BATTLE_TEST("Aroma Veil protects the Pokémon's side from Encore (Traits)") +{ + struct BattlePokemon *moveTarget = NULL; + PARAMETRIZE { moveTarget = playerLeft; } + PARAMETRIZE { moveTarget = playerRight; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_ENCORE) == EFFECT_ENCORE); + ASSUME(GetMoveCategory(MOVE_HARDEN) == DAMAGE_CATEGORY_STATUS); + PLAYER(SPECIES_AROMATISSE) { Ability(ABILITY_HEALER); Innates(ABILITY_AROMA_VEIL); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_ENCORE, target: moveTarget); MOVE(moveTarget, MOVE_HARDEN); } + TURN { MOVE(moveTarget, MOVE_CELEBRATE); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_ENCORE, opponentLeft); + ABILITY_POPUP(playerLeft, ABILITY_AROMA_VEIL); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HARDEN, moveTarget); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, moveTarget); + } +} + +DOUBLE_BATTLE_TEST("Aroma Veil protects the Pokémon's side from Disable (Traits)") +{ + struct BattlePokemon *moveTarget = NULL; + PARAMETRIZE { moveTarget = playerLeft; } + PARAMETRIZE { moveTarget = playerRight; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_DISABLE) == EFFECT_DISABLE); + ASSUME(GetMoveCategory(MOVE_HARDEN) == DAMAGE_CATEGORY_STATUS); + PLAYER(SPECIES_AROMATISSE) { Ability(ABILITY_HEALER); Innates(ABILITY_AROMA_VEIL); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_DISABLE, target: moveTarget); MOVE(moveTarget, MOVE_HARDEN); } + TURN { MOVE(moveTarget, MOVE_HARDEN); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_DISABLE, opponentLeft); + ABILITY_POPUP(playerLeft, ABILITY_AROMA_VEIL); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HARDEN, moveTarget); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HARDEN, moveTarget); + } +} + +DOUBLE_BATTLE_TEST("Aroma Veil protects the Pokémon's side from Cursed Body (Traits)") +{ + GIVEN { + ASSUME(MoveMakesContact(MOVE_PECK)); + PLAYER(SPECIES_AROMATISSE) { Ability(ABILITY_HEALER); Innates(ABILITY_AROMA_VEIL); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_JELLICENT) { Ability(ABILITY_WATER_ABSORB); Innates(ABILITY_CURSED_BODY); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_PECK, target: opponentLeft); MOVE(playerRight, MOVE_PECK, target: opponentLeft); } + TURN { MOVE(playerLeft, MOVE_PECK, target: opponentLeft); MOVE(playerRight, MOVE_PECK, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PECK, playerLeft); + NOT ABILITY_POPUP(opponentLeft, ABILITY_CURSED_BODY); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PECK, playerRight); + NOT ABILITY_POPUP(opponentLeft, ABILITY_CURSED_BODY); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PECK, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PECK, playerRight); + } +} + +DOUBLE_BATTLE_TEST("Aroma Veil protects the Pokémon's side from Heal Block (Traits)") +{ + struct BattlePokemon *moveTarget = NULL; + PARAMETRIZE { moveTarget = playerLeft; } + PARAMETRIZE { moveTarget = playerRight; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_HEAL_BLOCK) == EFFECT_HEAL_BLOCK); + ASSUME(GetMoveEffect(MOVE_RECOVER) == EFFECT_RESTORE_HP); + PLAYER(SPECIES_AROMATISSE) { Ability(ABILITY_HEALER); Innates(ABILITY_AROMA_VEIL); HP(1); } + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_HEAL_BLOCK, target: moveTarget); MOVE(moveTarget, MOVE_RECOVER); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_HEAL_BLOCK, opponentLeft); + ABILITY_POPUP(playerLeft, ABILITY_AROMA_VEIL); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RECOVER, moveTarget); + } +} + +DOUBLE_BATTLE_TEST("Aroma Veil protects the Pokémon's side from Infatuation (Traits)") +{ + struct BattlePokemon *moveTarget = NULL; + PARAMETRIZE { moveTarget = playerLeft; } + PARAMETRIZE { moveTarget = playerRight; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_ATTRACT) == EFFECT_ATTRACT); + PLAYER(SPECIES_AROMATISSE) { Ability(ABILITY_HEALER); Innates(ABILITY_AROMA_VEIL); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_ATTRACT, target: moveTarget); MOVE(moveTarget, MOVE_CELEBRATE); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_ATTRACT, opponentLeft); + ABILITY_POPUP(playerLeft, ABILITY_AROMA_VEIL); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, moveTarget); + } +} + +DOUBLE_BATTLE_TEST("Aroma Veil does not protect the Pokémon's side from Imprison (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_IMPRISON) == EFFECT_IMPRISON); + PLAYER(SPECIES_AROMATISSE) { Ability(ABILITY_HEALER); Innates(ABILITY_AROMA_VEIL); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) {Moves(MOVE_IMPRISON, MOVE_CELEBRATE); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_IMPRISON); MOVE(playerLeft, MOVE_CELEBRATE); MOVE(playerRight, MOVE_CELEBRATE); MOVE(opponentRight, MOVE_SPLASH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_IMPRISON, opponentLeft); + NONE_OF { + ABILITY_POPUP(playerLeft, ABILITY_AROMA_VEIL); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Aroma Veil prevents Psychic Noise's effect (Traits)") +{ + struct BattlePokemon *moveTarget = NULL; + PARAMETRIZE { moveTarget = playerLeft; } + PARAMETRIZE { moveTarget = playerRight; } + GIVEN { + ASSUME(GetMoveAdditionalEffectById(MOVE_PSYCHIC_NOISE, 0)->moveEffect == MOVE_EFFECT_PSYCHIC_NOISE); + PLAYER(SPECIES_AROMATISSE) { Ability(ABILITY_HEALER); Innates(ABILITY_AROMA_VEIL); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_PSYCHIC_NOISE, target: moveTarget); MOVE(moveTarget, MOVE_RECOVER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCHIC_NOISE, opponentLeft); + ABILITY_POPUP(playerLeft, ABILITY_AROMA_VEIL); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RECOVER, moveTarget); + } +} + +// Marked in Bulbapedia as need of research +//TO_DO_BATTLE_TEST("Aroma Veil prevents G-Max Meltdown's effect (Traits)"); +#endif diff --git a/test/battle/ability/aura_break.c b/test/battle/ability/aura_break.c index c023869c7895..445535710177 100644 --- a/test/battle/ability/aura_break.c +++ b/test/battle/ability/aura_break.c @@ -62,3 +62,67 @@ DOUBLE_BATTLE_TEST("Aura Break inverts Dark Aura's effect") EXPECT_MUL_EQ(damage[0], UQ_4_12(0.75), damage[2]); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Aura Break inverts Fairy Aura's effect (Traits)") +{ + s16 damage[3]; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_XERNEAS) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FAIRY_AURA); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZYGARDE_50) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_AURA_BREAK); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_PLAY_ROUGH, target:opponentLeft); } + TURN { MOVE(playerLeft, MOVE_PLAY_ROUGH, target:opponentLeft); SWITCH(playerRight, 2); } + TURN { MOVE(playerLeft, MOVE_PLAY_ROUGH, target:opponentLeft); SWITCH(opponentRight, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLAY_ROUGH, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damage[0]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLAY_ROUGH, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damage[1]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLAY_ROUGH, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damage[2]); + + } THEN { + EXPECT_MUL_EQ(damage[0], UQ_4_12(1.33), damage[1]); + EXPECT_MUL_EQ(damage[0], UQ_4_12(0.75), damage[2]); + } +} + +DOUBLE_BATTLE_TEST("Aura Break inverts Dark Aura's effect (Traits)") +{ + s16 damage[3]; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_YVELTAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DARK_AURA); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZYGARDE_50) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_AURA_BREAK); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_BITE, target:opponentLeft); } + TURN { MOVE(playerLeft, MOVE_BITE, target:opponentLeft); SWITCH(playerRight, 2); } + TURN { MOVE(playerLeft, MOVE_BITE, target:opponentLeft); SWITCH(opponentRight, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damage[0]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damage[1]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damage[2]); + + } THEN { + EXPECT_MUL_EQ(damage[0], UQ_4_12(1.33), damage[1]); + EXPECT_MUL_EQ(damage[0], UQ_4_12(0.75), damage[2]); + } +} +#endif diff --git a/test/battle/ability/bad_dreams.c b/test/battle/ability/bad_dreams.c index 0fa9f6ef61fd..be03e2fe78cd 100644 --- a/test/battle/ability/bad_dreams.c +++ b/test/battle/ability/bad_dreams.c @@ -139,3 +139,21 @@ DOUBLE_BATTLE_TEST("Bad Dreams faints both sleeping Pokemon on opponent side") MESSAGE("The opposing Wobbuffet fainted!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Bad Dreams causes Pokémon with Comatose to lose 1/8 of HP (Traits)") +{ + GIVEN { + PLAYER(SPECIES_DARKRAI); + OPPONENT(SPECIES_KOMALA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMATOSE); } + } WHEN { + TURN {;} + } SCENE { + ABILITY_POPUP(player, ABILITY_BAD_DREAMS); + MESSAGE("The opposing Komala is tormented!"); + HP_BAR(opponent); + } THEN { + EXPECT_EQ(opponent->hp, opponent->maxHP - opponent->maxHP / 8); + } +} +#endif diff --git a/test/battle/ability/ball_fetch.c b/test/battle/ability/ball_fetch.c index 5a8b4d9530e5..c83c8bb3b57f 100644 --- a/test/battle/ability/ball_fetch.c +++ b/test/battle/ability/ball_fetch.c @@ -118,3 +118,208 @@ SINGLE_BATTLE_TEST("Ball Fetch doesn't trigger in Trainer Battles") } TO_DO_BATTLE_TEST("Ball Fetch doesn't trigger in Max Raid Battles"); + +#if MAX_MON_TRAITS > 1 +WILD_BATTLE_TEST("Ball Fetch causes the Pokémon to pick up the last failed Ball at the end of the turn (Traits)") +{ + u32 item = 0; + + PARAMETRIZE { item = ITEM_POKE_BALL; } + PARAMETRIZE { item = ITEM_GREAT_BALL; } + PARAMETRIZE { item = ITEM_ULTRA_BALL; } + PARAMETRIZE { item = ITEM_STRANGE_BALL; } + PARAMETRIZE { item = ITEM_X_ACCURACY; } + + GIVEN { + PLAYER(SPECIES_YAMPER) { Ability(ABILITY_RATTLED); Innates(ABILITY_BALL_FETCH); } + OPPONENT(SPECIES_METAGROSS); + } WHEN { + TURN { USE_ITEM(player, item, WITH_RNG(RNG_BALLTHROW_SHAKE, MAX_u16) );} + TURN {} + } SCENE { + if (item != ITEM_X_ACCURACY) + ABILITY_POPUP(player, ABILITY_BALL_FETCH); + else + NOT ABILITY_POPUP(player, ABILITY_BALL_FETCH); + } THEN { + if (item != ITEM_X_ACCURACY) + EXPECT_EQ(player->item, item); + else + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +WILD_BATTLE_TEST("Ball Fetch doesn't trigger if the Pokémon is already holding an item (Traits)") +{ + u32 item = 0; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_NUGGET; } + + GIVEN { + PLAYER(SPECIES_YAMPER) { Ability(ABILITY_RATTLED); Innates(ABILITY_BALL_FETCH); Item(item); } + OPPONENT(SPECIES_METAGROSS); + } WHEN { + TURN { USE_ITEM(player, ITEM_GREAT_BALL, WITH_RNG(RNG_BALLTHROW_SHAKE, MAX_u16)); } + } SCENE { + if (item == ITEM_NONE) + { + MESSAGE("You used Great Ball!"); + ABILITY_POPUP(player, ABILITY_BALL_FETCH); + MESSAGE("Yamper found a Great Ball!"); + } + else + { + NONE_OF + { + ABILITY_POPUP(player, ABILITY_BALL_FETCH); + MESSAGE("Yamper found a Great Ball!"); + } + } + } THEN { + if (item == ITEM_NONE) + EXPECT_EQ(player->item, ITEM_GREAT_BALL); + else + EXPECT_EQ(player->item, item); + } +} + +WILD_BATTLE_TEST("Ball Fetch only picks up the first failed ball, once per battle (Traits)") +{ + u32 item = 0; + u32 item2 = 0; + + PARAMETRIZE { item = ITEM_GREAT_BALL; item2 = ITEM_X_ACCURACY; } + PARAMETRIZE { item = ITEM_GREAT_BALL; item2 = ITEM_ULTRA_BALL; } + PARAMETRIZE { item = ITEM_GREAT_BALL; item2 = ITEM_FAST_BALL; } + PARAMETRIZE { item = ITEM_GREAT_BALL; item2 = ITEM_STRANGE_BALL; } + + + GIVEN { + PLAYER(SPECIES_YAMPER) { Ability(ABILITY_RATTLED); Innates(ABILITY_BALL_FETCH); } + OPPONENT(SPECIES_METAGROSS); + } WHEN { + TURN { USE_ITEM(player, item, WITH_RNG(RNG_BALLTHROW_SHAKE, MAX_u16)); } + TURN { MOVE(player, MOVE_BESTOW); } + TURN { USE_ITEM(player, item2, WITH_RNG(RNG_BALLTHROW_SHAKE, MAX_u16)); } + } SCENE { + MESSAGE("You used Great Ball!"); + ABILITY_POPUP(player, ABILITY_BALL_FETCH); + MESSAGE("Yamper found a Great Ball!"); + MESSAGE("Yamper used Bestow!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BESTOW, player); + MESSAGE("The wild Metagross received Great Ball from Yamper!"); + NOT ABILITY_POPUP(player, ABILITY_BALL_FETCH); + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Ball Fetch doesn't trigger in Trainer Battles (Traits)") +{ + u32 item = 0; + + PARAMETRIZE { item = ITEM_POKE_BALL; } + PARAMETRIZE { item = ITEM_GREAT_BALL; } + PARAMETRIZE { item = ITEM_ULTRA_BALL; } + PARAMETRIZE { item = ITEM_STRANGE_BALL; } + PARAMETRIZE { item = ITEM_X_ACCURACY; } + + GIVEN { + PLAYER(SPECIES_YAMPER) { Ability(ABILITY_RATTLED); Innates(ABILITY_BALL_FETCH); } + OPPONENT(SPECIES_METAGROSS); + } WHEN { + TURN { USE_ITEM(player, item, WITH_RNG(RNG_BALLTHROW_SHAKE, 0)); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_BALL_FETCH); + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +TO_DO_BATTLE_TEST("Ball Fetch doesn't trigger in Max Raid Battles (Traits)"); +#endif + +#if MAX_MON_ITEMS > 1 +WILD_BATTLE_TEST("Ball Fetch causes the Pokémon to pick up the last failed Ball at the end of the turn (Multi)") +{ + u32 item = 0; + + PARAMETRIZE { item = ITEM_POKE_BALL; } + PARAMETRIZE { item = ITEM_GREAT_BALL; } + PARAMETRIZE { item = ITEM_ULTRA_BALL; } + PARAMETRIZE { item = ITEM_STRANGE_BALL; } + PARAMETRIZE { item = ITEM_X_ACCURACY; } + + GIVEN { + PLAYER(SPECIES_YAMPER) { Ability(ABILITY_BALL_FETCH); Items(ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_METAGROSS); + } WHEN { + TURN { USE_ITEM(player, item, WITH_RNG(RNG_BALLTHROW_SHAKE, MAX_u16) );} + TURN {} + } SCENE { + if (item != ITEM_X_ACCURACY) + ABILITY_POPUP(player, ABILITY_BALL_FETCH); + else + NOT ABILITY_POPUP(player, ABILITY_BALL_FETCH); + } THEN { + if (item != ITEM_X_ACCURACY) + EXPECT_EQ(player->item, item); + else + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +WILD_BATTLE_TEST("Ball Fetch only picks up the first failed ball, once per battle (Multi)") +{ + u32 item = 0; + u32 item2 = 0; + + PARAMETRIZE { item = ITEM_GREAT_BALL; item2 = ITEM_X_ACCURACY; } + PARAMETRIZE { item = ITEM_GREAT_BALL; item2 = ITEM_ULTRA_BALL; } + PARAMETRIZE { item = ITEM_GREAT_BALL; item2 = ITEM_FAST_BALL; } + PARAMETRIZE { item = ITEM_GREAT_BALL; item2 = ITEM_STRANGE_BALL; } + + + GIVEN { + PLAYER(SPECIES_YAMPER) { Ability(ABILITY_BALL_FETCH); Items(ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_METAGROSS); + } WHEN { + TURN { USE_ITEM(player, item, WITH_RNG(RNG_BALLTHROW_SHAKE, MAX_u16)); } + TURN { MOVE(player, MOVE_BESTOW); } + TURN { USE_ITEM(player, item2, WITH_RNG(RNG_BALLTHROW_SHAKE, MAX_u16)); } + } SCENE { + MESSAGE("You used Great Ball!"); + ABILITY_POPUP(player, ABILITY_BALL_FETCH); + MESSAGE("Yamper found a Great Ball!"); + MESSAGE("Yamper used Bestow!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BESTOW, player); + MESSAGE("The wild Metagross received Great Ball from Yamper!"); + NOT ABILITY_POPUP(player, ABILITY_BALL_FETCH); + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Ball Fetch doesn't trigger in Trainer Battles (Multi)") +{ + u32 item = 0; + + PARAMETRIZE { item = ITEM_POKE_BALL; } + PARAMETRIZE { item = ITEM_GREAT_BALL; } + PARAMETRIZE { item = ITEM_ULTRA_BALL; } + PARAMETRIZE { item = ITEM_STRANGE_BALL; } + PARAMETRIZE { item = ITEM_X_ACCURACY; } + + GIVEN { + PLAYER(SPECIES_YAMPER) { Ability(ABILITY_BALL_FETCH); Items(ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_METAGROSS); + } WHEN { + TURN { USE_ITEM(player, item, WITH_RNG(RNG_BALLTHROW_SHAKE, 0)); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_BALL_FETCH); + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} +#endif diff --git a/test/battle/ability/battery.c b/test/battle/ability/battery.c index 7ddc0b837ceb..95f6bd29eb4a 100644 --- a/test/battle/ability/battery.c +++ b/test/battle/ability/battery.c @@ -49,3 +49,53 @@ DOUBLE_BATTLE_TEST("Battery does not increase its own Sp. Attack damage") EXPECT_EQ(damage[0], damage[1]); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Battery increases Sp. Attack damage of allies by ~30% (Traits)") +{ + s16 damage[2]; + + GIVEN { + PLAYER(SPECIES_CHARJABUG) { Speed(1); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_BATTERY); } + PLAYER(SPECIES_WOBBUFFET) { Speed(2); Moves(MOVE_SHOCK_WAVE); } + PLAYER(SPECIES_CHARJABUG) { Speed(1); Ability(ABILITY_LIGHT_METAL); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(4); Moves(MOVE_CELEBRATE, MOVE_GASTRO_ACID); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(3); } + } WHEN { + TURN { MOVE(playerRight, MOVE_SHOCK_WAVE, target: opponentLeft); } + TURN { SWITCH(playerLeft, 2); MOVE(playerRight, MOVE_SHOCK_WAVE, target: opponentLeft);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHOCK_WAVE, playerRight); + HP_BAR(opponentLeft, captureDamage: &damage[0]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHOCK_WAVE, playerRight); + HP_BAR(opponentLeft, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[1], UQ_4_12(1.3), damage[0]); + } +} + +DOUBLE_BATTLE_TEST("Battery does not increase its own Sp. Attack damage (Traits)") +{ + s16 damage[2]; + + GIVEN { + PLAYER(SPECIES_CHARJABUG) { Speed(1); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_BATTERY); } + PLAYER(SPECIES_WOBBUFFET) { Speed(2); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(4); Moves(MOVE_CELEBRATE, MOVE_GASTRO_ACID); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(3); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SHOCK_WAVE, target: opponentLeft); } + TURN { MOVE(opponentLeft, MOVE_GASTRO_ACID, target: playerLeft); MOVE(playerLeft, MOVE_SHOCK_WAVE, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHOCK_WAVE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damage[0]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_GASTRO_ACID, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHOCK_WAVE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} +#endif diff --git a/test/battle/ability/battle_armor.c b/test/battle/ability/battle_armor.c index abf7396f6092..cdf554f1bc80 100644 --- a/test/battle/ability/battle_armor.c +++ b/test/battle/ability/battle_armor.c @@ -47,3 +47,52 @@ SINGLE_BATTLE_TEST("Mold Breaker, Teravolt and Turboblaze ignore Battle Armor an MESSAGE("A critical hit!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Battle Armor and Shell Armor block critical hits (Traits)") +{ + u32 species; + enum Ability ability, ability2; + + PARAMETRIZE { species = SPECIES_KINGLER; ability = ABILITY_SHELL_ARMOR; ability2 = ABILITY_HYPER_CUTTER; } + PARAMETRIZE { species = SPECIES_ARMALDO; ability = ABILITY_BATTLE_ARMOR; ability2 = ABILITY_HYPER_CUTTER; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ability2); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH, criticalHit: TRUE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + NOT MESSAGE("A critical hit!"); + } +} + +SINGLE_BATTLE_TEST("Mold Breaker, Teravolt and Turboblaze ignore Battle Armor and Shell Armor (Traits)") +{ + u32 j; + u32 species1, species2, ability1, ability2, ability3; + static const u32 breakerData[][2] = + { + {SPECIES_PINSIR, ABILITY_MOLD_BREAKER}, + {SPECIES_ZEKROM, ABILITY_TERAVOLT}, + {SPECIES_RESHIRAM, ABILITY_TURBOBLAZE}, + }; + + for (j = 0; j < ARRAY_COUNT(breakerData); j++) + { + PARAMETRIZE { species1 = breakerData[j][0]; ability1 = breakerData[j][1]; species2 = SPECIES_KINGLER; ability2 = ABILITY_SHELL_ARMOR; ability3 = ABILITY_HYPER_CUTTER; } + PARAMETRIZE { species1 = breakerData[j][0]; ability1 = breakerData[j][1]; species2 = SPECIES_ARMALDO; ability2 = ABILITY_BATTLE_ARMOR; ability3 = ABILITY_HYPER_CUTTER; } + } + + GIVEN { + PLAYER(species1) { Ability(ability3); Innates(ability1); } + OPPONENT(species2) { Ability(ability3); Innates(ability2); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH, criticalHit: TRUE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("A critical hit!"); + } +} +#endif diff --git a/test/battle/ability/battle_bond.c b/test/battle/ability/battle_bond.c index 9d8bb1b3cd11..c8d04fa652ed 100644 --- a/test/battle/ability/battle_bond.c +++ b/test/battle/ability/battle_bond.c @@ -189,3 +189,54 @@ SINGLE_BATTLE_TEST("Battle Bond increases a Stat even if only one can be increas EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 6); } } + + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Battle Bond increases Atk, SpAtk and Speed by 1 stage (Gen9+) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_BATTLE_BOND, GEN_9); + PLAYER(SPECIES_GRENINJA_BATTLE_BOND) { Ability(ABILITY_TORRENT); Innates(ABILITY_BATTLE_BOND); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_WATER_GUN); SEND_OUT(opponent, 1); } + } SCENE { + HP_BAR(opponent); + MESSAGE("The opposing Wobbuffet fainted!"); + ABILITY_POPUP(player, ABILITY_BATTLE_BOND); + } THEN { + EXPECT(player->species != SPECIES_GRENINJA_ASH); + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Battle Bond increases a Stat even if only one can be increased (Gen9+) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_BATTLE_BOND, GEN_9); + PLAYER(SPECIES_GRENINJA_BATTLE_BOND) { Ability(ABILITY_TORRENT); Innates(ABILITY_BATTLE_BOND); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_DANCE); } + TURN { MOVE(player, MOVE_DRAGON_DANCE); } + TURN { MOVE(player, MOVE_DRAGON_DANCE); } + TURN { MOVE(player, MOVE_DRAGON_DANCE); } + TURN { MOVE(player, MOVE_DRAGON_DANCE); } + TURN { MOVE(player, MOVE_DRAGON_DANCE); } + TURN { MOVE(player, MOVE_WATER_GUN); SEND_OUT(opponent, 1); } + } SCENE { + HP_BAR(opponent); + MESSAGE("The opposing Wobbuffet fainted!"); + ABILITY_POPUP(player, ABILITY_BATTLE_BOND); + } THEN { + EXPECT(player->species != SPECIES_GRENINJA_ASH); + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 6); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 6); + } +} +#endif diff --git a/test/battle/ability/beads_of_ruin.c b/test/battle/ability/beads_of_ruin.c index 037a56386c4b..dbc08c1d4cc8 100644 --- a/test/battle/ability/beads_of_ruin.c +++ b/test/battle/ability/beads_of_ruin.c @@ -189,3 +189,144 @@ DOUBLE_BATTLE_TEST("Beads of Ruin's Sp. Def reduction is ignored by Gastro Acid" EXPECT_LT(results[0].damage, results[1].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Beads of Ruin reduces Sp. Def if opposing mon's ability doesn't match (Traits)") +{ + s16 damage[2]; + + GIVEN { + PLAYER(SPECIES_CHI_YU) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_BEADS_OF_RUIN); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_BEADS_OF_RUIN); } + } WHEN { + TURN { MOVE(player, MOVE_WATER_GUN); } + TURN { MOVE(player, MOVE_WATER_GUN); SWITCH(opponent, 1);} + TURN { MOVE(player, MOVE_WATER_GUN); } + } SCENE { + ABILITY_POPUP(player, ABILITY_BEADS_OF_RUIN); + MESSAGE("Chi-Yu's Beads of Ruin weakened the Sp. Def of all surrounding Pokémon!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[1], Q_4_12(1.33), damage[0]); + } +} + +DOUBLE_BATTLE_TEST("Beads of Ruin increases damage taken by physical moves in Wonder Room (Traits)", s16 damage) +{ + bool32 useWonderRoom; + u32 move; + + PARAMETRIZE { useWonderRoom = FALSE; move = MOVE_SCRATCH; } + PARAMETRIZE { useWonderRoom = FALSE; move = MOVE_ROUND; } + PARAMETRIZE { useWonderRoom = TRUE; move = MOVE_SCRATCH; } + PARAMETRIZE { useWonderRoom = TRUE; move = MOVE_ROUND; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_WONDER_ROOM) == EFFECT_WONDER_ROOM); + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveEffect(MOVE_ROUND) != EFFECT_PSYSHOCK); + PLAYER(SPECIES_CHI_YU) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_BEADS_OF_RUIN); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (useWonderRoom) + TURN { MOVE(opponentLeft, MOVE_WONDER_ROOM); MOVE(playerRight, move, target: opponentLeft); } + else + TURN { MOVE(playerRight, move, target: opponentLeft); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_BEADS_OF_RUIN); + MESSAGE("Chi-Yu's Beads of Ruin weakened the Sp. Def of all surrounding Pokémon!"); + ANIMATION(ANIM_TYPE_MOVE, move, playerRight); + HP_BAR(opponentLeft, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_GT(results[2].damage, results[0].damage); // In Wonder Room, physical move deals more damage + EXPECT_LT(results[3].damage, results[1].damage); // In Wonder Room, special move deals less damage + } +} + +SINGLE_BATTLE_TEST("Beads of Ruin doesn't activate when dragged out by Mold Breaker attacker (Traits)") +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_MOLD_BREAKER; } + PARAMETRIZE { ability = ABILITY_SAND_RUSH; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_DRAGON_TAIL) == EFFECT_HIT_SWITCH_TARGET); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_CHI_YU) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_BEADS_OF_RUIN); } + OPPONENT(SPECIES_EXCADRILL) { Ability(ABILITY_SAND_FORCE); Innates(ability); } + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, opponent); + if (ability == ABILITY_MOLD_BREAKER) + { + NONE_OF { + ABILITY_POPUP(player, ABILITY_BEADS_OF_RUIN); + MESSAGE("Chi-Yu's Beads of Ruin weakened the Sp. Def of all surrounding Pokémon!"); + } + } + else + { + ABILITY_POPUP(player, ABILITY_BEADS_OF_RUIN); + MESSAGE("Chi-Yu's Beads of Ruin weakened the Sp. Def of all surrounding Pokémon!"); + } + } +} + +DOUBLE_BATTLE_TEST("Beads of Ruin's Sp. Def reduction is not ignored by Mold Breaker (Traits)", s16 damage) +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_MOLD_BREAKER; } + PARAMETRIZE { ability = ABILITY_SAND_RUSH; } + + GIVEN { + PLAYER(SPECIES_CHI_YU) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_BEADS_OF_RUIN); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_EXCADRILL) { Ability(ABILITY_SAND_FORCE); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_ROUND, target: playerRight); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_BEADS_OF_RUIN); + MESSAGE("Chi-Yu's Beads of Ruin weakened the Sp. Def of all surrounding Pokémon!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponentLeft); + HP_BAR(playerRight, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} + +DOUBLE_BATTLE_TEST("Beads of Ruin's Sp. Def reduction is ignored by Gastro Acid (Traits)", s16 damage) +{ + u32 move; + + PARAMETRIZE { move = MOVE_GASTRO_ACID; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_GASTRO_ACID) == EFFECT_GASTRO_ACID); + PLAYER(SPECIES_CHI_YU) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_BEADS_OF_RUIN); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentRight, move, target: playerLeft); MOVE(opponentLeft, MOVE_ROUND, target: playerRight); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_BEADS_OF_RUIN); + MESSAGE("Chi-Yu's Beads of Ruin weakened the Sp. Def of all surrounding Pokémon!"); + ANIMATION(ANIM_TYPE_MOVE, move, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponentLeft); + HP_BAR(playerRight, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_LT(results[0].damage, results[1].damage); + } +} +#endif diff --git a/test/battle/ability/beast_boost.c b/test/battle/ability/beast_boost.c index ce7df9135b8f..ca5e24884afb 100644 --- a/test/battle/ability/beast_boost.c +++ b/test/battle/ability/beast_boost.c @@ -253,3 +253,273 @@ SINGLE_BATTLE_TEST("Beast Boost doesn't consider status condition reductions") EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); } } + + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Beast Boost boosts the most proficient stat when knocking out a target (Traits)") +{ + u8 stats[] = {1, 1, 1, 1, 1}; + PARAMETRIZE { stats[0] = 255; } + PARAMETRIZE { stats[1] = 255; } + PARAMETRIZE { stats[2] = 255; } + PARAMETRIZE { stats[3] = 255; } + PARAMETRIZE { stats[4] = 255; } + GIVEN { + PLAYER(SPECIES_NIHILEGO) { Ability(ABILITY_BEAST_BOOST); Attack(stats[0]); Defense(stats[1]); SpAttack(stats[2]); SpDefense(stats[3]); Speed(stats[4]); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(1); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); SEND_OUT(opponent, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_BEAST_BOOST); + switch(i) { + case 0: + MESSAGE("Nihilego's Attack rose!"); + break; + case 1: + MESSAGE("Nihilego's Defense rose!"); + break; + case 2: + MESSAGE("Nihilego's Sp. Atk rose!"); + break; + case 3: + MESSAGE("Nihilego's Sp. Def rose!"); + break; + case 4: + MESSAGE("Nihilego's Speed rose!"); + break; + } + } +} + +SINGLE_BATTLE_TEST("Beast Boost doesn't trigger if user is fainted (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_DESTINY_BOND) == EFFECT_DESTINY_BOND); + ASSUME(GetMovePower(MOVE_SCRATCH) > 0); + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_KARTANA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_BEAST_BOOST); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_DESTINY_BOND); MOVE(opponent, MOVE_SCRATCH); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DESTINY_BOND, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_BEAST_BOOST); + SEND_IN_MESSAGE("Wynaut"); + MESSAGE("2 sent out Wobbuffet!"); + } +} + +SINGLE_BATTLE_TEST("Beast Boost prioritizes stats in the case of a tie in the following order: Atk, Def, Sp.Atk, Sp.Def, Speed (Traits)") +{ + u8 stats[] = {1, 1, 1, 1, 1}; + + PARAMETRIZE { stats[4] = 255; stats[3] = 255; stats[2] = 255; stats[1] = 255; stats[0] = 255; } + PARAMETRIZE { stats[4] = 255; stats[3] = 255; stats[2] = 255; stats[1] = 255; } + PARAMETRIZE { stats[4] = 255; stats[3] = 255; stats[2] = 255; } + PARAMETRIZE { stats[4] = 255; stats[3] = 255; } + GIVEN { + PLAYER(SPECIES_NIHILEGO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_BEAST_BOOST); Attack(stats[0]); Defense(stats[1]); SpAttack(stats[2]); SpDefense(stats[3]); Speed(stats[4]); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(1); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); SEND_OUT(opponent, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_BEAST_BOOST); + switch(i) { + case 0: + MESSAGE("Nihilego's Attack rose!"); + break; + case 1: + MESSAGE("Nihilego's Defense rose!"); + break; + case 2: + MESSAGE("Nihilego's Sp. Atk rose!"); + break; + case 3: + MESSAGE("Nihilego's Sp. Def rose!"); + break; + } + } +} + +SINGLE_BATTLE_TEST("Beast Boost considers Power Split (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_POWER_SPLIT) == EFFECT_POWER_SPLIT); + PLAYER(SPECIES_NIHILEGO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_BEAST_BOOST); Attack(200); Defense(30); SpAttack(50); SpDefense(30); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Attack(10); SpAttack(250); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_POWER_SPLIT); } + TURN { MOVE(player, MOVE_SCRATCH); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POWER_SPLIT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ABILITY_POPUP(player, ABILITY_BEAST_BOOST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Beast Boost considers Guard Split (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_GUARD_SPLIT) == EFFECT_GUARD_SPLIT); + PLAYER(SPECIES_NIHILEGO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_BEAST_BOOST); Attack(80); Defense(20); SpAttack(70); SpDefense(10); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Defense(200); SpDefense(30); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_GUARD_SPLIT); } + TURN { MOVE(player, MOVE_SCRATCH); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GUARD_SPLIT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ABILITY_POPUP(player, ABILITY_BEAST_BOOST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Beast Boost considers Power Trick (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_POWER_TRICK) == EFFECT_POWER_TRICK); + PLAYER(SPECIES_NIHILEGO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_BEAST_BOOST); Attack(40); Defense(200); SpAttack(60); SpDefense(50); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_POWER_TRICK); } + TURN { MOVE(player, MOVE_SCRATCH); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POWER_TRICK, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ABILITY_POPUP(player, ABILITY_BEAST_BOOST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Beast Boost considers Wonder Room (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_WONDER_ROOM) == EFFECT_WONDER_ROOM); + ASSUME(GetMovePower(MOVE_SCRATCH) > 0); + PLAYER(SPECIES_NIHILEGO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_BEAST_BOOST); Attack(100); Defense(50); SpAttack(70); SpDefense(200); Speed(120); Moves(MOVE_SPLASH, MOVE_EXTREME_SPEED); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(200); Moves(MOVE_WONDER_ROOM, MOVE_SPLASH); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(30); } + } WHEN { + TURN { MOVE(opponent, MOVE_WONDER_ROOM); MOVE(player, MOVE_SPLASH); } + TURN { MOVE(player, MOVE_EXTREME_SPEED); MOVE(opponent, MOVE_SPLASH); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WONDER_ROOM, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXTREME_SPEED, player); + ABILITY_POPUP(player, ABILITY_BEAST_BOOST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Beast Boost considers Speed Swap (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SPEED_SWAP) == EFFECT_SPEED_SWAP); + PLAYER(SPECIES_NIHILEGO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_BEAST_BOOST); Attack(60); Defense(60); SpAttack(70); SpDefense(60); Speed(30); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(200); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SPEED_SWAP); } + TURN { MOVE(player, MOVE_SCRATCH); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPEED_SWAP, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ABILITY_POPUP(player, ABILITY_BEAST_BOOST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Beast Boost doesn't consider stat stages (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SWORDS_DANCE) == EFFECT_ATTACK_UP_2); + PLAYER(SPECIES_NIHILEGO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_BEAST_BOOST); Attack(100); Defense(60); SpAttack(150); SpDefense(60); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SWORDS_DANCE); } + TURN { MOVE(player, MOVE_SCRATCH); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ABILITY_POPUP(player, ABILITY_BEAST_BOOST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Beast Boost doesn't consider held items (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_CHOICE_BAND].holdEffect == HOLD_EFFECT_CHOICE_BAND); + PLAYER(SPECIES_NIHILEGO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_BEAST_BOOST); Item(ITEM_CHOICE_BAND); Attack(120); Defense(60); SpAttack(150); SpDefense(60); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ABILITY_POPUP(player, ABILITY_BEAST_BOOST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Beast Boost doesn't consider status condition reductions (Traits)") +{ + GIVEN { + PLAYER(SPECIES_NIHILEGO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_BEAST_BOOST); Status1(STATUS1_BURN); Attack(150); Defense(60); SpAttack(100); SpDefense(60); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ABILITY_POPUP(player, ABILITY_BEAST_BOOST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Beast Boost doesn't consider held items (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_CHOICE_BAND].holdEffect == HOLD_EFFECT_CHOICE_BAND); + PLAYER(SPECIES_NIHILEGO) { Ability(ABILITY_BEAST_BOOST); Items(ITEM_ORAN_BERRY, ITEM_CHOICE_BAND); Attack(120); Defense(60); SpAttack(150); SpDefense(60); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ABILITY_POPUP(player, ABILITY_BEAST_BOOST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + } +} +#endif diff --git a/test/battle/ability/berserk.c b/test/battle/ability/berserk.c index fe356bc6711d..9165ab0e948f 100644 --- a/test/battle/ability/berserk.c +++ b/test/battle/ability/berserk.c @@ -134,3 +134,182 @@ SINGLE_BATTLE_TEST("Berserk activates before the hp can be restored on non multi EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Berserk activates only if the target had more than 50% of its HP (Traits)") +{ + bool32 activates = FALSE; + u16 maxHp = 500, hp = 0; + + PARAMETRIZE { hp = 250; activates = FALSE; } + PARAMETRIZE { hp = 249; activates = FALSE; } + PARAMETRIZE { hp = 100; activates = FALSE; } + PARAMETRIZE { hp = 50; activates = FALSE; } + PARAMETRIZE { hp = 251; activates = TRUE; } + PARAMETRIZE { hp = 254; activates = TRUE; } + + GIVEN { + ASSUME(!IsBattleMoveStatus(MOVE_SCRATCH)); + PLAYER(SPECIES_DRAMPA) { Ability(ABILITY_SAP_SIPPER); Innates(ABILITY_BERSERK); MaxHP(maxHp); HP(hp); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + if (activates) { + ABILITY_POPUP(player, ABILITY_BERSERK); + } else { + NOT ABILITY_POPUP(player, ABILITY_BERSERK); + } + } THEN { + if (activates) { + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + } + } +} + +SINGLE_BATTLE_TEST("Berserk raises Sp.Atk by 1 (Traits)") +{ + u16 maxHp = 500; + GIVEN { + ASSUME(!IsBattleMoveStatus(MOVE_SCRATCH)); + PLAYER(SPECIES_DRAMPA) { Ability(ABILITY_SAP_SIPPER); Innates(ABILITY_BERSERK); MaxHP(maxHp); HP(maxHp / 2 + 1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(player, ABILITY_BERSERK); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Drampa's Sp. Atk rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Berserk activates after all hits from a multi-hit move (Traits)") +{ + u32 j; + u16 maxHp = 500; + GIVEN { + ASSUME(GetMoveEffect(MOVE_DOUBLE_SLAP) == EFFECT_MULTI_HIT); + PLAYER(SPECIES_DRAMPA) { Ability(ABILITY_SAP_SIPPER); Innates(ABILITY_BERSERK); MaxHP(maxHp); HP(maxHp / 2 + 1); } + OPPONENT(SPECIES_SHELLDER) { Ability(ABILITY_SHELL_ARMOR); Innates(ABILITY_SKILL_LINK); } // Always hits 5 times. + } WHEN { + TURN { MOVE(opponent, MOVE_DOUBLE_SLAP); } + } SCENE { + for (j = 0; j < 4; j++) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_SLAP, opponent); + NOT ABILITY_POPUP(player, ABILITY_BERSERK); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_SLAP, opponent); + ABILITY_POPUP(player, ABILITY_BERSERK); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Berserk does not activate if move is boosted by Sheer Force (Traits)") +{ + u16 maxHp = 500; + GIVEN { + PLAYER(SPECIES_DRAMPA) { Ability(ABILITY_SAP_SIPPER); Innates(ABILITY_BERSERK); MaxHP(maxHp); HP(maxHp / 2 + 1); } + OPPONENT(SPECIES_NIDOKING) { Ability(ABILITY_POISON_POINT); Innates(ABILITY_SHEER_FORCE); } + } WHEN { + TURN { MOVE(opponent, MOVE_EMBER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); + NOT ABILITY_POPUP(player, ABILITY_BERSERK); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Berserk will not activate if the last multi hit move activates a restore berry (Traits)") +{ + u32 j; + GIVEN { + ASSUME(GetMoveEffect(MOVE_DOUBLE_SLAP) == EFFECT_MULTI_HIT); + PLAYER(SPECIES_DRAMPA) { Ability(ABILITY_SAP_SIPPER); Innates(ABILITY_BERSERK); Item(ITEM_SITRUS_BERRY); MaxHP(100); HP(90); } + OPPONENT(SPECIES_SHELLDER) { Ability(ABILITY_SHELL_ARMOR); Innates(ABILITY_SKILL_LINK); } // Always hits 5 times. + } WHEN { + TURN { MOVE(opponent, MOVE_DOUBLE_SLAP); } + } SCENE { + for (j = 0; j < 4; j++) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_SLAP, opponent); + NOT ABILITY_POPUP(player, ABILITY_BERSERK); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_SLAP, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + NOT ABILITY_POPUP(player, ABILITY_BERSERK); + + } THEN { + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Berserk activates before the hp can be restored on non multi hit moves (Traits)") +{ + u16 maxHp = 500; + GIVEN { + ASSUME(!IsBattleMoveStatus(MOVE_SCRATCH)); + PLAYER(SPECIES_DRAMPA) { Ability(ABILITY_SAP_SIPPER); Innates(ABILITY_BERSERK); Item(ITEM_SITRUS_BERRY); MaxHP(maxHp); HP(maxHp / 2 + 1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(player, ABILITY_BERSERK); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Berserk will not activate if the last multi hit move activates a restore berry (Multi)") +{ + u32 j; + GIVEN { + ASSUME(GetMoveEffect(MOVE_DOUBLE_SLAP) == EFFECT_MULTI_HIT); + PLAYER(SPECIES_DRAMPA) { Ability(ABILITY_BERSERK); Items(ITEM_PECHA_BERRY, ITEM_SITRUS_BERRY); MaxHP(100); HP(90); } + OPPONENT(SPECIES_SHELLDER) { Ability(ABILITY_SKILL_LINK); } // Always hits 5 times. + } WHEN { + TURN { MOVE(opponent, MOVE_DOUBLE_SLAP); } + } SCENE { + for (j = 0; j < 4; j++) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_SLAP, opponent); + NOT ABILITY_POPUP(player, ABILITY_BERSERK); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_SLAP, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + NOT ABILITY_POPUP(player, ABILITY_BERSERK); + + } THEN { + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Berserk activates before the hp can be restored on non multi hit moves (Multi)") +{ + u16 maxHp = 500; + GIVEN { + ASSUME(!IsBattleMoveStatus(MOVE_SCRATCH)); + PLAYER(SPECIES_DRAMPA) { Ability(ABILITY_BERSERK); Items(ITEM_PECHA_BERRY, ITEM_SITRUS_BERRY); MaxHP(maxHp); HP(maxHp / 2 + 1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(player, ABILITY_BERSERK); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + } +} +#endif diff --git a/test/battle/ability/big_pecks.c b/test/battle/ability/big_pecks.c index 44bb008a1d2f..2ded07cf3169 100644 --- a/test/battle/ability/big_pecks.c +++ b/test/battle/ability/big_pecks.c @@ -114,3 +114,119 @@ SINGLE_BATTLE_TEST("Big Pecks doesn't prevent receiving negative Defense stage c EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Big Pecks prevents Defense stage reduction from moves (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_LEER) == EFFECT_DEFENSE_DOWN); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_BIG_PECKS); } + } WHEN { + TURN { MOVE(player, MOVE_LEER); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_BIG_PECKS); + MESSAGE("The opposing Pidgey's Big Pecks prevents Defense loss!"); + } +} + +SINGLE_BATTLE_TEST("Big Pecks is ignored by Mold Breaker (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_LEER) == EFFECT_DEFENSE_DOWN); + PLAYER(SPECIES_PINSIR) { Ability(ABILITY_HYPER_CUTTER); Innates(ABILITY_MOLD_BREAKER); } + OPPONENT(SPECIES_PIDGEY) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_BIG_PECKS); } + } WHEN { + TURN { MOVE(player, MOVE_LEER); } + } SCENE { + ABILITY_POPUP(player, ABILITY_MOLD_BREAKER); + MESSAGE("Pinsir breaks the mold!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_LEER, player); + MESSAGE("The opposing Pidgey's Defense fell!"); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_BIG_PECKS); + MESSAGE("The opposing Pidgey's Big Pecks prevents Defense loss!"); + } + } +} + +SINGLE_BATTLE_TEST("Big Pecks doesn't prevent Defense stage reduction from moves used by the user (Traits)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffectSelf(MOVE_SUPERPOWER, MOVE_EFFECT_ATK_DEF_DOWN) == TRUE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_BIG_PECKS); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUPERPOWER); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPERPOWER, opponent); + MESSAGE("The opposing Pidgey's Attack fell!"); + MESSAGE("The opposing Pidgey's Defense fell!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Big Pecks doesn't prevent Topsy-Turvy (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_HARDEN) == EFFECT_DEFENSE_UP); + ASSUME(GetMoveEffect(MOVE_TOPSY_TURVY) == EFFECT_TOPSY_TURVY); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_BIG_PECKS); } + } WHEN { + TURN { MOVE(opponent, MOVE_HARDEN); MOVE(player, MOVE_TOPSY_TURVY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HARDEN, opponent); + MESSAGE("The opposing Pidgey's Defense rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOPSY_TURVY, player); + MESSAGE("All stat changes on the opposing Pidgey were inverted!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Big Pecks doesn't prevent Spectral Thief from resetting positive Defense stage changes (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_HARDEN) == EFFECT_DEFENSE_UP); + ASSUME(GetMoveEffect(MOVE_SPECTRAL_THIEF) == EFFECT_SPECTRAL_THIEF); + ASSUME(GetMoveEffect(MOVE_SOAK) == EFFECT_SOAK); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_BIG_PECKS); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player,MOVE_SOAK); } + TURN { MOVE(opponent, MOVE_HARDEN); MOVE(player, MOVE_SPECTRAL_THIEF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HARDEN, opponent); + MESSAGE("The opposing Pidgey's Defense rose!"); + MESSAGE("Wobbuffet stole the target's boosted stats!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPECTRAL_THIEF, player); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Big Pecks doesn't prevent receiving negative Defense stage changes from Baton Pass (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_LEER) == EFFECT_DEFENSE_DOWN); + ASSUME(GetMoveEffect(MOVE_BATON_PASS) == EFFECT_BATON_PASS); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_BIG_PECKS); } + } WHEN { + TURN { MOVE(player, MOVE_LEER); + MOVE(opponent, MOVE_BATON_PASS); + SEND_OUT(opponent, 1); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LEER, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BATON_PASS, opponent); + MESSAGE("2 sent out Pidgey!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); + } +} +#endif diff --git a/test/battle/ability/blaze.c b/test/battle/ability/blaze.c index c68c7466a165..d7624ceb8caa 100644 --- a/test/battle/ability/blaze.c +++ b/test/battle/ability/blaze.c @@ -18,3 +18,23 @@ SINGLE_BATTLE_TEST("Blaze boosts Fire-type moves in a pinch", s16 damage) EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Blaze boosts Fire-type moves in a pinch (Traits)", s16 damage) +{ + u16 hp; + PARAMETRIZE { hp = 99; } + PARAMETRIZE { hp = 33; } + GIVEN { + ASSUME(GetMoveType(MOVE_EMBER) == TYPE_FIRE); + PLAYER(SPECIES_CHARMANDER) { Ability(ABILITY_SOLAR_POWER); Innates(ABILITY_BLAZE); MaxHP(99); HP(hp); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_EMBER); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} +#endif diff --git a/test/battle/ability/bulletproof.c b/test/battle/ability/bulletproof.c index 37d59b468e55..c380346916fb 100644 --- a/test/battle/ability/bulletproof.c +++ b/test/battle/ability/bulletproof.c @@ -18,3 +18,23 @@ SINGLE_BATTLE_TEST("Bulletproof makes ballistic moves fail against the ability u } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Bulletproof makes ballistic moves fail against the ability user (Traits)") +{ + GIVEN { + ASSUME(IsBallisticMove(MOVE_ELECTRO_BALL)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CHESPIN) { Ability(ABILITY_OVERGROW); Innates(ABILITY_BULLETPROOF); } + } WHEN { + TURN { MOVE(player, MOVE_ELECTRO_BALL); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_BULLETPROOF); + MESSAGE("The opposing Chespin's Bulletproof blocks Electro Ball!"); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRO_BALL, player); + HP_BAR(opponent); + } + } +} +#endif diff --git a/test/battle/ability/cheek_pouch.c b/test/battle/ability/cheek_pouch.c index 8109dbe17a87..86aa7b6bbcf1 100644 --- a/test/battle/ability/cheek_pouch.c +++ b/test/battle/ability/cheek_pouch.c @@ -187,3 +187,367 @@ SINGLE_BATTLE_TEST("Cheek Pouch activation doesn't mutate damage when restoring EXPECT_GT(damage, 0); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Cheek Pouch restores 33% max HP (Traits)") +{ + s16 berryHeal, cheekPouchHeal; + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SUPER_FANG) == EFFECT_FIXED_PERCENT_DAMAGE); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffect == HOLD_EFFECT_RESTORE_HP); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffectParam == 10); + PLAYER(SPECIES_GREEDENT) { Ability(ABILITY_GLUTTONY); Innates(ABILITY_CHEEK_POUCH); MaxHP(60); HP(31); Item(ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUPER_FANG); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player, captureDamage: &berryHeal); + ABILITY_POPUP(player, ABILITY_CHEEK_POUCH); + HP_BAR(player, captureDamage: &cheekPouchHeal); + } THEN { + EXPECT_LT(berryHeal, 0); + EXPECT_EQ(cheekPouchHeal, -(player->maxHP / 3)); + } +} + +SINGLE_BATTLE_TEST("Cheek Pouch restores HP after the berry's effect (Traits)") +{ + u16 hpAfterBerry, hpAfterCheekPouch; + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SUPER_FANG) == EFFECT_FIXED_PERCENT_DAMAGE); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffect == HOLD_EFFECT_RESTORE_HP); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffectParam == 10); + PLAYER(SPECIES_GREEDENT) { Ability(ABILITY_GLUTTONY); Innates(ABILITY_CHEEK_POUCH); MaxHP(60); HP(31); Item(ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUPER_FANG); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player, captureHP: &hpAfterBerry); + ABILITY_POPUP(player, ABILITY_CHEEK_POUCH); + HP_BAR(player, captureHP: &hpAfterCheekPouch); + } THEN { + EXPECT_GT(hpAfterCheekPouch, hpAfterBerry); + } +} + +SINGLE_BATTLE_TEST("Cheek Pouch activates via Bug Bite/Pluck if it would trigger an effect (Traits)") +{ + u16 move; + s16 berryHeal, cheekPouchHeal; + + PARAMETRIZE { move = MOVE_BUG_BITE; } + PARAMETRIZE { move = MOVE_PLUCK; } + + GIVEN { + ASSUME(MoveHasAdditionalEffect(move, MOVE_EFFECT_BUG_BITE)); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffect == HOLD_EFFECT_RESTORE_HP); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffectParam == 10); + PLAYER(SPECIES_GREEDENT) { Ability(ABILITY_GLUTTONY); Innates(ABILITY_CHEEK_POUCH); MaxHP(60); HP(30); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_ORAN_BERRY); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + HP_BAR(player, captureDamage: &berryHeal); + ABILITY_POPUP(player, ABILITY_CHEEK_POUCH); + HP_BAR(player, captureDamage: &cheekPouchHeal); + } THEN { + EXPECT_LT(berryHeal, 0); + EXPECT_EQ(cheekPouchHeal, -(player->maxHP / 3)); + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Cheek Pouch activates when receiving from Fling if it would trigger an effect (Traits)") +{ + s16 berryHeal, cheekPouchHeal; + + GIVEN { + ASSUME(GetMoveEffect(MOVE_FLING) == EFFECT_FLING); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffect == HOLD_EFFECT_RESTORE_HP); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffectParam == 10); + PLAYER(SPECIES_WOBBUFFET) { Attack(1); Item(ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_GREEDENT) { Ability(ABILITY_GLUTTONY); Innates(ABILITY_CHEEK_POUCH); MaxHP(60); HP(30); } + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + HP_BAR(opponent, captureDamage: &berryHeal); + ABILITY_POPUP(opponent, ABILITY_CHEEK_POUCH); + HP_BAR(opponent, captureDamage: &cheekPouchHeal); + } THEN { + EXPECT_LT(berryHeal, 0); + EXPECT_EQ(cheekPouchHeal, -(opponent->maxHP / 3)); + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Cheek Pouch doesn't activate when using Natural Gift (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + PLAYER(SPECIES_GREEDENT) { Ability(ABILITY_GLUTTONY); Innates(ABILITY_CHEEK_POUCH); MaxHP(60); HP(40); Item(ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_NATURAL_GIFT); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_CHEEK_POUCH); + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Cheek Pouch doesn't activate when user uses Fling (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FLING) == EFFECT_FLING); + PLAYER(SPECIES_GREEDENT) { Ability(ABILITY_GLUTTONY); Innates(ABILITY_CHEEK_POUCH); MaxHP(60); HP(40); Item(ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_CHEEK_POUCH); + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Cheek Pouch doesn't activate when using a berry from the bag (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffect == HOLD_EFFECT_RESTORE_HP); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffectParam == 10); + PLAYER(SPECIES_GREEDENT) { Ability(ABILITY_GLUTTONY); Innates(ABILITY_CHEEK_POUCH); MaxHP(60); HP(20); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { USE_ITEM(player, ITEM_ORAN_BERRY, partyIndex: 0); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_CHEEK_POUCH); + } +} + +SINGLE_BATTLE_TEST("Cheek Pouch doesn't activate under Heal Block's effect (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_HEAL_BLOCK) == EFFECT_HEAL_BLOCK); + ASSUME(GetMoveEffect(MOVE_SUPER_FANG) == EFFECT_FIXED_PERCENT_DAMAGE); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffect == HOLD_EFFECT_RESTORE_HP); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffectParam == 10); + PLAYER(SPECIES_GREEDENT) { Ability(ABILITY_GLUTTONY); Innates(ABILITY_CHEEK_POUCH); MaxHP(60); HP(31); Item(ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_HEAL_BLOCK); } + TURN { MOVE(opponent, MOVE_SUPER_FANG); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEAL_BLOCK, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, opponent); + HP_BAR(player); + NOT ABILITY_POPUP(player, ABILITY_CHEEK_POUCH); + } THEN { + EXPECT_EQ(player->item, ITEM_ORAN_BERRY); + } +} + +SINGLE_BATTLE_TEST("Cheek Pouch activation doesn't mutate damage when restoring HP mid battle (Traits)") +{ + s16 damage, healing; + + GIVEN { + PLAYER(SPECIES_GREEDENT) { Ability(ABILITY_GLUTTONY); Innates(ABILITY_CHEEK_POUCH); Item(ITEM_CHOPLE_BERRY); HP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_KARATE_CHOP); } + ABILITY_POPUP(player, ABILITY_CHEEK_POUCH); + HP_BAR(player, captureDamage: &healing); + HP_BAR(player, captureDamage: &damage); + } THEN { + EXPECT_LT(healing, 0); + EXPECT_GT(damage, 0); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Cheek Pouch restores 33% max HP (Multi)") +{ + s16 berryHeal, cheekPouchHeal; + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SUPER_FANG) == EFFECT_FIXED_PERCENT_DAMAGE); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffect == HOLD_EFFECT_RESTORE_HP); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffectParam == 10); + PLAYER(SPECIES_GREEDENT) { Ability(ABILITY_CHEEK_POUCH); MaxHP(60); HP(31); Items(ITEM_GREAT_BALL, ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUPER_FANG); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player, captureDamage: &berryHeal); + ABILITY_POPUP(player, ABILITY_CHEEK_POUCH); + HP_BAR(player, captureDamage: &cheekPouchHeal); + } THEN { + EXPECT_LT(berryHeal, 0); + EXPECT_EQ(cheekPouchHeal, -(player->maxHP / 3)); + } +} + +SINGLE_BATTLE_TEST("Cheek Pouch restores HP after the berry's effect (Multi)") +{ + u16 hpAfterBerry, hpAfterCheekPouch; + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SUPER_FANG) == EFFECT_FIXED_PERCENT_DAMAGE); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffect == HOLD_EFFECT_RESTORE_HP); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffectParam == 10); + PLAYER(SPECIES_GREEDENT) { Ability(ABILITY_CHEEK_POUCH); MaxHP(60); HP(31); Items(ITEM_GREAT_BALL, ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUPER_FANG); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player, captureHP: &hpAfterBerry); + ABILITY_POPUP(player, ABILITY_CHEEK_POUCH); + HP_BAR(player, captureHP: &hpAfterCheekPouch); + } THEN { + EXPECT_GT(hpAfterCheekPouch, hpAfterBerry); + } +} + +SINGLE_BATTLE_TEST("Cheek Pouch activates via Bug Bite/Pluck if it would trigger an effect (Multi)") +{ + u16 move; + s16 berryHeal, cheekPouchHeal; + + PARAMETRIZE { move = MOVE_BUG_BITE; } + PARAMETRIZE { move = MOVE_PLUCK; } + + GIVEN { + ASSUME(MoveHasAdditionalEffect(move, MOVE_EFFECT_BUG_BITE)); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffect == HOLD_EFFECT_RESTORE_HP); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffectParam == 10); + PLAYER(SPECIES_GREEDENT) { Ability(ABILITY_CHEEK_POUCH); MaxHP(60); HP(30); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_ORAN_BERRY); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + HP_BAR(player, captureDamage: &berryHeal); + ABILITY_POPUP(player, ABILITY_CHEEK_POUCH); + HP_BAR(player, captureDamage: &cheekPouchHeal); + } THEN { + EXPECT_LT(berryHeal, 0); + EXPECT_EQ(cheekPouchHeal, -(player->maxHP / 3)); + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Cheek Pouch activates when receiving from Fling if it would trigger an effect (Multi)") +{ + s16 berryHeal, cheekPouchHeal; + + GIVEN { + ASSUME(GetMoveEffect(MOVE_FLING) == EFFECT_FLING); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffect == HOLD_EFFECT_RESTORE_HP); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffectParam == 10); + PLAYER(SPECIES_WOBBUFFET) { Attack(1); Items(ITEM_NONE, ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_GREEDENT) { Ability(ABILITY_CHEEK_POUCH); MaxHP(60); HP(30); } + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + HP_BAR(opponent, captureDamage: &berryHeal); + ABILITY_POPUP(opponent, ABILITY_CHEEK_POUCH); + HP_BAR(opponent, captureDamage: &cheekPouchHeal); + } THEN { + EXPECT_LT(berryHeal, 0); + EXPECT_EQ(cheekPouchHeal, -(opponent->maxHP / 3)); + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Cheek Pouch doesn't activate when using Natural Gift (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + PLAYER(SPECIES_GREEDENT) { Ability(ABILITY_CHEEK_POUCH); MaxHP(60); HP(40); Items(ITEM_GREAT_BALL, ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_NATURAL_GIFT); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_CHEEK_POUCH); + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Cheek Pouch doesn't activate when user uses Fling (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FLING) == EFFECT_FLING); + PLAYER(SPECIES_GREEDENT) { Ability(ABILITY_CHEEK_POUCH); MaxHP(60); HP(40); Items(ITEM_NONE, ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_CHEEK_POUCH); + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Cheek Pouch doesn't activate under Heal Block's effect (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_HEAL_BLOCK) == EFFECT_HEAL_BLOCK); + ASSUME(GetMoveEffect(MOVE_SUPER_FANG) == EFFECT_FIXED_PERCENT_DAMAGE); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffect == HOLD_EFFECT_RESTORE_HP); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffectParam == 10); + PLAYER(SPECIES_GREEDENT) { Ability(ABILITY_CHEEK_POUCH); MaxHP(60); HP(31); Items(ITEM_GREAT_BALL, ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_HEAL_BLOCK); } + TURN { MOVE(opponent, MOVE_SUPER_FANG); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEAL_BLOCK, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, opponent); + HP_BAR(player); + NOT ABILITY_POPUP(player, ABILITY_CHEEK_POUCH); + } THEN { + EXPECT_EQ(player->item, ITEM_ORAN_BERRY); + } +} + +SINGLE_BATTLE_TEST("Cheek Pouch activation doesn't mutate damage when restoring HP mid battle (Multi)") +{ + s16 damage, healing; + + GIVEN { + PLAYER(SPECIES_GREEDENT) { Ability(ABILITY_CHEEK_POUCH); Items(ITEM_GREAT_BALL, ITEM_CHOPLE_BERRY); HP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_KARATE_CHOP); } + ABILITY_POPUP(player, ABILITY_CHEEK_POUCH); + HP_BAR(player, captureDamage: &healing); + HP_BAR(player, captureDamage: &damage); + } THEN { + EXPECT_LT(healing, 0); + EXPECT_GT(damage, 0); + } +} +#endif diff --git a/test/battle/ability/chlorophyll.c b/test/battle/ability/chlorophyll.c index 66f7b9ccc9a3..62ded96cdd86 100644 --- a/test/battle/ability/chlorophyll.c +++ b/test/battle/ability/chlorophyll.c @@ -49,3 +49,71 @@ SINGLE_BATTLE_TEST("Chlorophyll doesn't double speed if they have an Utility Umb ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Chlorophyll doubles speed if it's sunny (Traits)") +{ + GIVEN { + PLAYER(SPECIES_VENUSAUR) { Ability(ABILITY_OVERGROW); Innates(ABILITY_CHLOROPHYLL); Speed(100); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(199); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SUNNY_DAY); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +SINGLE_BATTLE_TEST("Chlorophyll doesn't double speed if Cloud Nine/Air Lock is on the field (Traits)") +{ + GIVEN { + PLAYER(SPECIES_VENUSAUR) { Ability(ABILITY_OVERGROW); Innates(ABILITY_CHLOROPHYLL); Speed(100); } + OPPONENT(SPECIES_GOLDUCK) { Speed(199); Ability(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SUNNY_DAY); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } +} + +SINGLE_BATTLE_TEST("Chlorophyll doesn't double speed if they have an Utility Umbrella (Traits)") +{ + GIVEN { + PLAYER(SPECIES_VENUSAUR) { Ability(ABILITY_OVERGROW); Innates(ABILITY_CHLOROPHYLL); Speed(100); Item(ITEM_UTILITY_UMBRELLA); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(199); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SUNNY_DAY); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Chlorophyll doesn't double speed if they have an Utility Umbrella (Multi)") +{ + GIVEN { + PLAYER(SPECIES_VENUSAUR) { Ability(ABILITY_CHLOROPHYLL); Speed(100); Items(ITEM_PECHA_BERRY, ITEM_UTILITY_UMBRELLA); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(199); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SUNNY_DAY); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } +} +#endif diff --git a/test/battle/ability/clear_body.c b/test/battle/ability/clear_body.c index d1cd9f8d5e3b..df95918fa745 100644 --- a/test/battle/ability/clear_body.c +++ b/test/battle/ability/clear_body.c @@ -463,3 +463,513 @@ SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke protect from Pr } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke prevent intimidate (Traits)") +{ + s16 turnOneHit; + s16 turnTwoHit; + u32 species; + enum Ability ability; + + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } + GIVEN { + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_SHED_SKIN); }; + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); }; + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); }; + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_SCRATCH); } + + } SCENE { + HP_BAR(player, captureDamage: &turnOneHit); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } + ABILITY_POPUP(opponent, ability); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("The opposing Solgaleo's Full Metal Body prevents stat loss!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("The opposing Torkoal's White Smoke prevents stat loss!"); + else + MESSAGE("The opposing Metang's Clear Body prevents stat loss!"); + HP_BAR(player, captureDamage: &turnTwoHit); + } THEN { + EXPECT_EQ(turnOneHit, turnTwoHit); + } +} + +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke prevent stat stage reduction from moves (Traits)") +{ + u16 move = MOVE_NONE; + u32 j, species = SPECIES_NONE; + enum Ability ability = ABILITY_NONE; + static const u16 statReductionMoves[] = { + MOVE_GROWL, + MOVE_LEER, + MOVE_CONFIDE, + MOVE_FAKE_TEARS, + MOVE_SCARY_FACE, + MOVE_SWEET_SCENT, + MOVE_SAND_ATTACK, + }; + for (j = 0; j < ARRAY_COUNT(statReductionMoves); j++) + { + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; move = statReductionMoves[j]; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; move = statReductionMoves[j]; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; move = statReductionMoves[j]; } + } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_GROWL) == EFFECT_ATTACK_DOWN); + ASSUME(GetMoveEffect(MOVE_LEER) == EFFECT_DEFENSE_DOWN); + ASSUME(GetMoveEffect(MOVE_CONFIDE) == EFFECT_SPECIAL_ATTACK_DOWN); + ASSUME(GetMoveEffect(MOVE_FAKE_TEARS) == EFFECT_SPECIAL_DEFENSE_DOWN_2); + ASSUME(GetMoveEffect(MOVE_SCARY_FACE) == EFFECT_SPEED_DOWN_2); + ASSUME(GetMoveEffect(MOVE_SWEET_SCENT) == (B_UPDATED_MOVE_DATA >= GEN_6 ? EFFECT_EVASION_DOWN_2 : EFFECT_EVASION_DOWN)); + ASSUME(GetMoveEffect(MOVE_SAND_ATTACK) == EFFECT_ACCURACY_DOWN); + PLAYER(SPECIES_WOBBUFFET) + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, move, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } + ABILITY_POPUP(opponent, ability); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("The opposing Solgaleo's Full Metal Body prevents stat loss!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("The opposing Torkoal's White Smoke prevents stat loss!"); + else + MESSAGE("The opposing Metang's Clear Body prevents stat loss!"); + } +} + +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke prevent Sticky Web effect on switchin (Traits)") +{ + u32 species; + enum Ability ability; + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_STICKY_WEB) == EFFECT_STICKY_WEB); + PLAYER(SPECIES_WOBBUFFET) + OPPONENT(SPECIES_WOBBUFFET) + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_STICKY_WEB); } + TURN { SWITCH(opponent, 1); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } + ABILITY_POPUP(opponent, ability); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("The opposing Solgaleo's Full Metal Body prevents stat loss!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("The opposing Torkoal's White Smoke prevents stat loss!"); + else + MESSAGE("The opposing Metang's Clear Body prevents stat loss!"); + } +} + +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent stat stage reduction from moves used by the user (Traits)") +{ + u32 species; + enum Ability ability; + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } + GIVEN { + ASSUME(MoveHasAdditionalEffectSelf(MOVE_SUPERPOWER, MOVE_EFFECT_ATK_DEF_DOWN) == TRUE); + PLAYER(SPECIES_WOBBUFFET) + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUPERPOWER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPERPOWER, opponent); + NONE_OF { + ABILITY_POPUP(opponent, ability); + MESSAGE("The opposing Solgaleo's Full Metal Body prevents stat loss!"); + MESSAGE("The opposing Torkoal's White Smoke prevents stat loss!"); + MESSAGE("The opposing Metang's Clear Body prevents stat loss!"); + } + } +} + +SINGLE_BATTLE_TEST("Mold Breaker, Teravolt, and Turboblaze ignore Clear Body and White Smoke, but not Full Metal Body (Traits)") +{ + u32 j, k, species = SPECIES_NONE; + enum Ability ability = ABILITY_NONE; + enum Ability breakerAbility = ABILITY_NONE; + u16 move = ABILITY_NONE; + static const u16 breakerAbilities[] = { + ABILITY_MOLD_BREAKER, + ABILITY_TERAVOLT, + ABILITY_TURBOBLAZE, + }; + static const u16 statReductionMoves[] = { + MOVE_GROWL, + MOVE_LEER, + MOVE_CONFIDE, + MOVE_FAKE_TEARS, + MOVE_SCARY_FACE, + MOVE_SWEET_SCENT, + MOVE_SAND_ATTACK, + }; + + for (j = 0; j < ARRAY_COUNT(statReductionMoves); j++) + { + for (k = 0; k < ARRAY_COUNT(breakerAbilities); k++) + { + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; move = statReductionMoves[j]; breakerAbility = breakerAbilities[k]; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; move = statReductionMoves[j]; breakerAbility = breakerAbilities[k]; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; move = statReductionMoves[j]; breakerAbility = breakerAbilities[k]; } + } + } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_GROWL) == EFFECT_ATTACK_DOWN); + ASSUME(GetMoveEffect(MOVE_LEER) == EFFECT_DEFENSE_DOWN); + ASSUME(GetMoveEffect(MOVE_CONFIDE) == EFFECT_SPECIAL_ATTACK_DOWN); + ASSUME(GetMoveEffect(MOVE_FAKE_TEARS) == EFFECT_SPECIAL_DEFENSE_DOWN_2); + ASSUME(GetMoveEffect(MOVE_SCARY_FACE) == EFFECT_SPEED_DOWN_2); + ASSUME(GetMoveEffect(MOVE_SWEET_SCENT) == (B_UPDATED_MOVE_DATA >= GEN_6 ? EFFECT_EVASION_DOWN_2 : EFFECT_EVASION_DOWN)); + ASSUME(GetMoveEffect(MOVE_SAND_ATTACK) == EFFECT_ACCURACY_DOWN); + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(breakerAbility); } + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + if (ability == ABILITY_FULL_METAL_BODY){ // Full Metal Body can't be ignored by breaker abilities + NOT ANIMATION(ANIM_TYPE_MOVE, move, player); + ABILITY_POPUP(opponent, ability); + MESSAGE("The opposing Solgaleo's Full Metal Body prevents stat loss!"); + } + else{ + ANIMATION(ANIM_TYPE_MOVE, move, player); + NONE_OF { + ABILITY_POPUP(opponent, ability); + MESSAGE("The opposing Solgaleo's Full Metal Body prevents stat loss!"); + MESSAGE("The opposing Torkoal's White Smoke prevents stat loss!"); + MESSAGE("The opposing Metang's Clear Body prevents stat loss!"); + } + } + } +} + +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent Speed reduction from Iron Ball (Traits)") +{ + u32 j, species = SPECIES_NONE; + enum Ability ability = ABILITY_NONE; + u16 heldItem = ITEM_NONE; + static const u16 heldItems[] = { + ITEM_NONE, + ITEM_IRON_BALL, + }; + for (j = 0; j < ARRAY_COUNT(heldItems); j++) + { + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; heldItem = heldItems[j]; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; heldItem = heldItems[j]; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; heldItem = heldItems[j]; } + } + GIVEN { + ASSUME(gItemsInfo[ITEM_IRON_BALL].holdEffect == HOLD_EFFECT_IRON_BALL); + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + OPPONENT(species) { Speed(6); Ability(ABILITY_LIGHT_METAL); Innates(ability); Item(heldItem); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(opponent, ability); + if (heldItem == ITEM_IRON_BALL) { + MESSAGE("Wobbuffet used Celebrate!"); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("The opposing Solgaleo used Celebrate!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("The opposing Torkoal used Celebrate!"); + else + MESSAGE("The opposing Metang used Celebrate!"); + } else { + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("The opposing Solgaleo used Celebrate!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("The opposing Torkoal used Celebrate!"); + else + MESSAGE("The opposing Metang used Celebrate!"); + MESSAGE("Wobbuffet used Celebrate!"); + } + } +} + +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent Speed reduction from paralysis (Traits)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + OPPONENT(species) { Speed(6); Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_THUNDER_WAVE); } + TURN { MOVE(player, MOVE_THUNDER_WAVE); } + } SCENE { + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("The opposing Solgaleo used Celebrate!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("The opposing Torkoal used Celebrate!"); + else + MESSAGE("The opposing Metang used Celebrate!"); + MESSAGE("Wobbuffet used Thunder Wave!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_WAVE, player); + NOT ABILITY_POPUP(opponent, ability); + MESSAGE("Wobbuffet used Thunder Wave!"); + ONE_OF { + MESSAGE("The opposing Metang used Celebrate!"); + MESSAGE("The opposing Metang couldn't move because it's paralyzed!"); + MESSAGE("The opposing Solgaleo used Celebrate!"); + MESSAGE("The opposing Solgaleo couldn't move because it's paralyzed!"); + MESSAGE("The opposing Torkoal used Celebrate!"); + MESSAGE("The opposing Torkoal couldn't move because it's paralyzed!"); + } + } +} + +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent Attack reduction from burn (Traits)", s16 damage) +{ + bool32 burned = FALSE; + u32 species; + enum Ability ability; + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; burned = FALSE; } + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; burned = TRUE; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; burned = FALSE; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; burned = TRUE; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; burned = FALSE; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; burned = TRUE; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_WOBBUFFET) + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); if (burned) Status1(STATUS1_BURN); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + NOT ABILITY_POPUP(opponent, ability); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent receiving negative stat changes from Baton Pass (Traits)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SCARY_FACE) == EFFECT_SPEED_DOWN_2); + ASSUME(GetMoveEffect(MOVE_BATON_PASS) == EFFECT_BATON_PASS); + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(3); } + OPPONENT(species) { Speed(6); Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SCARY_FACE); MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_SCARY_FACE); } + } SCENE { + MESSAGE("Wobbuffet used Scary Face!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCARY_FACE, player); + ABILITY_POPUP(opponent, ability); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("The opposing Solgaleo used Celebrate!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("The opposing Torkoal used Celebrate!"); + else + MESSAGE("The opposing Metang used Celebrate!"); + } +} + +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent Topsy-Turvy (Traits)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOPSY_TURVY) == EFFECT_TOPSY_TURVY); + ASSUME(GetMoveEffect(MOVE_SCARY_FACE) == EFFECT_SPEED_DOWN_2); + ASSUME(GetMoveEffect(MOVE_BATON_PASS) == EFFECT_BATON_PASS); + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(3); } + OPPONENT(species) { Speed(6); Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SCARY_FACE); MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_TOPSY_TURVY); } + TURN { MOVE(player, MOVE_SCARY_FACE); } + } SCENE { + MESSAGE("Wobbuffet used Topsy-Turvy!"); + NOT ABILITY_POPUP(opponent, ability); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOPSY_TURVY, player); + if (ability == ABILITY_FULL_METAL_BODY) { + MESSAGE("The opposing Solgaleo used Celebrate!"); + MESSAGE("The opposing Solgaleo used Celebrate!"); + } + else if (ability == ABILITY_WHITE_SMOKE) { + MESSAGE("The opposing Torkoal used Celebrate!"); + MESSAGE("The opposing Torkoal used Celebrate!"); + } + else { + MESSAGE("The opposing Metang used Celebrate!"); + MESSAGE("The opposing Metang used Celebrate!"); + } + MESSAGE("Wobbuffet used Scary Face!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SCARY_FACE, player); + ABILITY_POPUP(opponent, ability); + } +} + +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent Spectral Thief from resetting positive stat changes (Traits)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SPECTRAL_THIEF) == EFFECT_SPECTRAL_THIEF); + ASSUME(GetMoveEffect(MOVE_AGILITY) == EFFECT_SPEED_UP_2); + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + OPPONENT(species) { Speed(5); Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN{ MOVE(opponent, MOVE_AGILITY); } + TURN{ MOVE(player, MOVE_SPECTRAL_THIEF); } + TURN{ } + } SCENE { + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("The opposing Solgaleo used Agility!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("The opposing Torkoal used Agility!"); + else + MESSAGE("The opposing Metang used Agility!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_AGILITY, opponent); + MESSAGE("Wobbuffet used Celebrate!"); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("The opposing Solgaleo used Celebrate!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("The opposing Torkoal used Celebrate!"); + else + MESSAGE("The opposing Metang used Celebrate!"); + MESSAGE("Wobbuffet used Spectral Thief!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPECTRAL_THIEF, player); + NOT ABILITY_POPUP(opponent, ability); + MESSAGE("Wobbuffet used Celebrate!"); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("The opposing Solgaleo used Celebrate!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("The opposing Torkoal used Celebrate!"); + else + MESSAGE("The opposing Metang used Celebrate!"); + } +} + +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke protect from Protect's secondary effects (Traits)") +{ + u32 move = MOVE_NONE; + u32 species = SPECIES_NONE; + enum Ability ability = ABILITY_NONE; + + static const u32 moves[] = { + MOVE_SPIKY_SHIELD, + MOVE_KINGS_SHIELD, + MOVE_SILK_TRAP, + MOVE_OBSTRUCT, + }; + + for (u32 j = 0; j < ARRAY_COUNT(moves); j++) + { + PARAMETRIZE{ move = moves[j]; species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ move = moves[j]; species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ move = moves[j]; species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } + } + + GIVEN { + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + if (move == MOVE_KINGS_SHIELD) { + MESSAGE("Wobbuffet's Attack fell!"); + } else if (move == MOVE_SILK_TRAP) { + MESSAGE("Wobbuffet's Speed fell!"); + } else if (move == MOVE_OBSTRUCT) { + MESSAGE("Wobbuffet's Defense harshly fell!"); + } + } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent Speed reduction from Iron Ball (Multi)") +{ + u32 j, species = SPECIES_NONE; + enum Ability ability = ABILITY_NONE; + u16 heldItem = ITEM_NONE; + static const u16 heldItems[] = { + ITEM_NONE, + ITEM_IRON_BALL, + }; + for (j = 0; j < ARRAY_COUNT(heldItems); j++) + { + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; heldItem = heldItems[j]; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; heldItem = heldItems[j]; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; heldItem = heldItems[j]; } + } + GIVEN { + ASSUME(gItemsInfo[ITEM_IRON_BALL].holdEffect == HOLD_EFFECT_IRON_BALL); + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + OPPONENT(species) { Speed(6); Ability(ability); Items(ITEM_PECHA_BERRY, heldItem); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(opponent, ability); + if (heldItem == ITEM_IRON_BALL) { + MESSAGE("Wobbuffet used Celebrate!"); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("The opposing Solgaleo used Celebrate!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("The opposing Torkoal used Celebrate!"); + else + MESSAGE("The opposing Metang used Celebrate!"); + } else { + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("The opposing Solgaleo used Celebrate!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("The opposing Torkoal used Celebrate!"); + else + MESSAGE("The opposing Metang used Celebrate!"); + MESSAGE("Wobbuffet used Celebrate!"); + } + } +} +#endif diff --git a/test/battle/ability/cloud_nine.c b/test/battle/ability/cloud_nine.c index a4eddd1e1c08..414fbcf0461f 100644 --- a/test/battle/ability/cloud_nine.c +++ b/test/battle/ability/cloud_nine.c @@ -40,3 +40,45 @@ TO_DO_BATTLE_TEST("Cloud Nine/Air Lock prevent basic weather effects, but withou TO_DO_BATTLE_TEST("Cloud Nine/Air Lock prevent basic weather effects, but without them disappearing - Strong Winds"); // Moves and abilities that are affected by weather should have new tests that check for Clould Nine/Air Lock, like Mold-Breaker Abilities + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Cloud Nine/Air Lock prevent basic weather effects, but without them disappearing - Sandstorm (Traits)") +{ + u32 species = 0; + enum Ability ability = 0; + PARAMETRIZE { species = SPECIES_PSYDUCK; ability = ABILITY_CLOUD_NINE; } + PARAMETRIZE { species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_SANDSTORM) == EFFECT_SANDSTORM); + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SANDSTORM); } + TURN {} + } SCENE { + ABILITY_POPUP(player, ability); + MESSAGE("The effects of the weather disappeared."); + MESSAGE("The opposing Wobbuffet used Sandstorm!"); + MESSAGE("The sandstorm is raging."); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SANDSTORM_CONTINUES); + NONE_OF { + HP_BAR(player); + HP_BAR(opponent); + MESSAGE("The opposing Wobbuffet is buffeted by the sandstorm!"); + } + MESSAGE("The sandstorm is raging."); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SANDSTORM_CONTINUES); + } +} + +TO_DO_BATTLE_TEST("Cloud Nine/Air Lock prevent basic weather effects, but without them disappearing - Sun (Traits)"); +TO_DO_BATTLE_TEST("Cloud Nine/Air Lock prevent basic weather effects, but without them disappearing - Rain (Traits)"); +TO_DO_BATTLE_TEST("Cloud Nine/Air Lock prevent basic weather effects, but without them disappearing - Hail (Traits)"); +TO_DO_BATTLE_TEST("Cloud Nine/Air Lock prevent basic weather effects, but without them disappearing - Snow (Traits)"); +TO_DO_BATTLE_TEST("Cloud Nine/Air Lock prevent basic weather effects, but without them disappearing - Fog (Traits)"); +TO_DO_BATTLE_TEST("Cloud Nine/Air Lock prevent basic weather effects, but without them disappearing - Primal Sun (Traits)"); +TO_DO_BATTLE_TEST("Cloud Nine/Air Lock prevent basic weather effects, but without them disappearing - Primal Rain (Traits)"); +TO_DO_BATTLE_TEST("Cloud Nine/Air Lock prevent basic weather effects, but without them disappearing - Strong Winds (Traits)"); + +// Moves and abilities that are affected by weather should have new tests that check for Clould Nine/Air Lock, like Mold-Breaker Abilities +#endif diff --git a/test/battle/ability/color_change.c b/test/battle/ability/color_change.c index 9c5176e3ac09..d280277ed787 100644 --- a/test/battle/ability/color_change.c +++ b/test/battle/ability/color_change.c @@ -186,3 +186,191 @@ SINGLE_BATTLE_TEST("Color Change does not activate if move is boosted by Sheer F NOT ABILITY_POPUP(player, ABILITY_COLOR_CHANGE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Color Change changes the type of a Pokemon being hit by a move if the type of the move and the Pokemon are different (Traits)") +{ + GIVEN { + ASSUME(GetSpeciesType(SPECIES_KECLEON, 0) != TYPE_PSYCHIC && GetSpeciesType(SPECIES_KECLEON, 1) != TYPE_PSYCHIC); + ASSUME(GetMoveType(MOVE_PSYWAVE) == TYPE_PSYCHIC); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KECLEON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COLOR_CHANGE); } + } WHEN { + TURN { MOVE(player, MOVE_PSYWAVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYWAVE, player); + ABILITY_POPUP(opponent, ABILITY_COLOR_CHANGE); + MESSAGE("The opposing Kecleon's Color Change made it the Psychic type!"); + } +} + +SINGLE_BATTLE_TEST("Color Change does not change the type when hit by a move that's the same type as itself (Traits)") +{ + GIVEN { + ASSUME(GetSpeciesType(SPECIES_KECLEON, 0) == TYPE_NORMAL || GetSpeciesType(SPECIES_KECLEON, 1) == TYPE_NORMAL); + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KECLEON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COLOR_CHANGE); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_COLOR_CHANGE); + MESSAGE("The opposing Kecleon's Color Change made it the Normal type!"); + } + } +} + +SINGLE_BATTLE_TEST("Color Change does not change the type of a dual-type Pokemon when hit by a move that shares its primary type (Traits)") +{ + GIVEN { + PLAYER(SPECIES_KECLEON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COLOR_CHANGE); } + OPPONENT(SPECIES_SLOWBRO); + } WHEN { + TURN { MOVE(opponent, MOVE_SKILL_SWAP); MOVE(player, MOVE_PSYCHO_CUT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCHO_CUT, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_COLOR_CHANGE); + MESSAGE("The opposing Slowbro's Color Change made it the Psychic type!"); + } + } +} + +SINGLE_BATTLE_TEST("Color Change does not change the type of a dual-type Pokemon when hit by a move that shares its secondary type (Traits)") +{ + GIVEN { + PLAYER(SPECIES_KECLEON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COLOR_CHANGE); } + OPPONENT(SPECIES_SLOWBRO); + } WHEN { + TURN { MOVE(opponent, MOVE_SKILL_SWAP); MOVE(player, MOVE_PSYCHO_CUT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCHO_CUT, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_COLOR_CHANGE); + MESSAGE("The opposing Slowbro's Color Change made it the Psychic type!"); + } + } +} + +SINGLE_BATTLE_TEST("Color Change changes the user to Electric type if hit by a move while the opponent is under the effect of Electrify (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KECLEON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COLOR_CHANGE); } + } WHEN { + TURN { MOVE(opponent, MOVE_ELECTRIFY); MOVE(player, MOVE_PSYCHO_CUT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCHO_CUT, player); + ABILITY_POPUP(opponent, ABILITY_COLOR_CHANGE); + MESSAGE("The opposing Kecleon's Color Change made it the Electric type!"); + } +} + +SINGLE_BATTLE_TEST("Color Change changes the type when a Pokemon is hit by Future Sight (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KECLEON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COLOR_CHANGE); } + } WHEN { + TURN { MOVE(player, MOVE_FUTURE_SIGHT); } + TURN { } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player); + MESSAGE("The opposing Kecleon took the Future Sight attack!"); + ABILITY_POPUP(opponent, ABILITY_COLOR_CHANGE); + MESSAGE("The opposing Kecleon's Color Change made it the Psychic type!"); + } +} + +SINGLE_BATTLE_TEST("Color Change changes the type when a Pokemon is hit by Doom Desire (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KECLEON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COLOR_CHANGE); } + } WHEN { + TURN { MOVE(player, MOVE_DOOM_DESIRE); } + TURN { } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOOM_DESIRE, player); + MESSAGE("The opposing Kecleon took the Doom Desire attack!"); + ABILITY_POPUP(opponent, ABILITY_COLOR_CHANGE); + MESSAGE("The opposing Kecleon's Color Change made it the Steel type!"); + } +} + +SINGLE_BATTLE_TEST("Color Change changes the type to Electric when a Pokemon is hit by a forseen attack under the effect of Electrify (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KECLEON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COLOR_CHANGE); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_FUTURE_SIGHT); } + TURN { } + TURN { MOVE(opponent, MOVE_ELECTRIFY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player); + MESSAGE("The opposing Kecleon took the Future Sight attack!"); + ABILITY_POPUP(opponent, ABILITY_COLOR_CHANGE); + MESSAGE("The opposing Kecleon's Color Change made it the Electric type!"); + } +} + +SINGLE_BATTLE_TEST("Color Change changes the type to Normal when a Pokemon is hit by a forseen attack under the effect of Normalize (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_NORMALIZE); } + OPPONENT(SPECIES_KECLEON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COLOR_CHANGE); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_FUTURE_SIGHT); } + TURN { MOVE(player, MOVE_SOAK); } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player); + MESSAGE("Wobbuffet used Soak!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SOAK, player); + MESSAGE("The opposing Kecleon transformed into the Water type!"); + MESSAGE("The opposing Kecleon took the Future Sight attack!"); + ABILITY_POPUP(opponent, ABILITY_COLOR_CHANGE); + MESSAGE("The opposing Kecleon's Color Change made it the Normal type!"); + } +} + +SINGLE_BATTLE_TEST("Color Change does not change the type to Normal when a Pokemon is hit by Struggle (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KECLEON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COLOR_CHANGE); } + } WHEN { + TURN { MOVE(player, MOVE_SOAK); } + TURN { MOVE(player, MOVE_STRUGGLE); } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SOAK, player); + MESSAGE("The opposing Kecleon transformed into the Water type!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_COLOR_CHANGE); + MESSAGE("The opposing Kecleon's Color Change made it the Normal type!"); + } + } +} + +SINGLE_BATTLE_TEST("Color Change does not activate if move is boosted by Sheer Force (Traits)") +{ + GIVEN { + PLAYER(SPECIES_KECLEON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COLOR_CHANGE); } + OPPONENT(SPECIES_NIDOKING) { Ability(ABILITY_RIVALRY); Innates(ABILITY_SHEER_FORCE); } + } WHEN { + TURN { MOVE(opponent, MOVE_EMBER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); + NOT ABILITY_POPUP(player, ABILITY_COLOR_CHANGE); + } +} +#endif diff --git a/test/battle/ability/comatose.c b/test/battle/ability/comatose.c index 3d20df3449ac..9677c01ab4cd 100644 --- a/test/battle/ability/comatose.c +++ b/test/battle/ability/comatose.c @@ -176,3 +176,239 @@ WILD_BATTLE_TEST("Comatose boosts Dream Ball's multiplier") EXPECT_EQ(gBattleResults.caughtMonSpecies, SPECIES_NONE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Comatose prevents status-inducing moves (Traits)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISONPOWDER; } + PARAMETRIZE { move = MOVE_SLEEP_POWDER; } + PARAMETRIZE { move = MOVE_THUNDER_WAVE; } + + GIVEN { + PLAYER(SPECIES_KOMALA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMATOSE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + MESSAGE("Komala is drowsing!"); + + NOT ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ABILITY_POPUP(player, ABILITY_COMATOSE); + MESSAGE("It doesn't affect Komala…"); + } +} + +SINGLE_BATTLE_TEST("Comatose Pokémon doesn't get poisoned by Toxic Spikes on switch-in (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_KOMALA) { Ability(ABILITY_LIGHT_METAL); Innates(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 Pokémon don't get poisoned by Toxic Spikes on switch-in if forced in by phazing with Mold Breaker (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_KOMALA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMATOSE); } + OPPONENT(SPECIES_PINSIR) { Ability(ABILITY_HYPER_CUTTER); Innates(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); + } +} + +SINGLE_BATTLE_TEST("Comatose makes Rest fail (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_REST) == EFFECT_REST); + PLAYER(SPECIES_KOMALA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMATOSE); HP(1); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_REST); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, player); + HP_BAR(player); + } + } THEN { + EXPECT_EQ(player->hp, 1); + EXPECT_EQ(player->status1, STATUS1_NONE); + } +} + +SINGLE_BATTLE_TEST("Comatose isn't affected by Mold Breaker, Turboblaze or Teravolt (Traits)") +{ + enum Ability ability; + u16 species; + + PARAMETRIZE { ability = ABILITY_MOLD_BREAKER; species = SPECIES_PINSIR; } + PARAMETRIZE { ability = ABILITY_TURBOBLAZE; species = SPECIES_RESHIRAM; } + PARAMETRIZE { ability = ABILITY_TERAVOLT; species = SPECIES_ZEKROM; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + PLAYER(SPECIES_KOMALA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMATOSE); } + OPPONENT(species) { Ability(ability); } + } WHEN { + TURN { MOVE(opponent, MOVE_TOXIC); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, opponent); + ABILITY_POPUP(player, ABILITY_COMATOSE); + MESSAGE("It doesn't affect Komala…"); + } THEN { + EXPECT_EQ(player->status1, STATUS1_NONE); + } +} + +SINGLE_BATTLE_TEST("Comatose isn't affected by Poison Touch + Sunsteel Strike (Traits)") +{ + GIVEN { + ASSUME(MoveIgnoresTargetAbility(MOVE_SUNSTEEL_STRIKE)); + ASSUME(MoveMakesContact(MOVE_SUNSTEEL_STRIKE)); + PLAYER(SPECIES_KOMALA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMATOSE); } + OPPONENT(SPECIES_CROAGUNK) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_POISON_TOUCH); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUNSTEEL_STRIKE, WITH_RNG(RNG_POISON_TOUCH, 1)); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNSTEEL_STRIKE, opponent); + HP_BAR(player); + NOT STATUS_ICON(player, poison: TRUE); + } THEN { + EXPECT_EQ(player->status1, STATUS1_NONE); + } +} + +WILD_BATTLE_TEST("Comatose boosts Dream Ball's multiplier (Traits)") +{ + enum Ability ability; + u16 species; + bool32 shouldCatch; + const u16 rng = 50000; + + PARAMETRIZE { species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; shouldCatch = TRUE; } + PARAMETRIZE { species = SPECIES_MIMIKYU; ability = ABILITY_DISGUISE; shouldCatch = FALSE; } + + GIVEN { + ASSUME(B_DREAM_BALL_MODIFIER >= GEN_8); + ASSUME(gSpeciesInfo[species].catchRate == 45); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); MaxHP(100); HP(1); } + } WHEN { + TURN { USE_ITEM(player, ITEM_DREAM_BALL, WITH_RNG(RNG_BALLTHROW_SHAKE, rng)); } + } SCENE { + ANIMATION(ANIM_TYPE_SPECIAL, B_ANIM_BALL_THROW, player); + } THEN { + if (shouldCatch) + EXPECT_EQ(gBattleResults.caughtMonSpecies, species); + else + EXPECT_EQ(gBattleResults.caughtMonSpecies, SPECIES_NONE); + } +} + +SINGLE_BATTLE_TEST("Comatose makes Rest fail (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_REST) == EFFECT_REST); + PLAYER(SPECIES_KOMALA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMATOSE); HP(1); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_REST); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, player); + HP_BAR(player); + } + } THEN { + EXPECT_EQ(player->hp, 1); + EXPECT_EQ(player->status1, STATUS1_NONE); + } +} + +SINGLE_BATTLE_TEST("Comatose isn't affected by Mold Breaker, Turboblaze or Teravolt (Traits)") +{ + enum Ability ability; + u16 species; + + PARAMETRIZE { ability = ABILITY_MOLD_BREAKER; species = SPECIES_PINSIR; } + PARAMETRIZE { ability = ABILITY_TURBOBLAZE; species = SPECIES_RESHIRAM; } + PARAMETRIZE { ability = ABILITY_TERAVOLT; species = SPECIES_ZEKROM; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + PLAYER(SPECIES_KOMALA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMATOSE); } + OPPONENT(species) { Ability(ability); } + } WHEN { + TURN { MOVE(opponent, MOVE_TOXIC); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, opponent); + ABILITY_POPUP(player, ABILITY_COMATOSE); + MESSAGE("It doesn't affect Komala…"); + } THEN { + EXPECT_EQ(player->status1, STATUS1_NONE); + } +} + +SINGLE_BATTLE_TEST("Comatose isn't affected by Poison Touch + Sunsteel Strike (Traits)") +{ + GIVEN { + ASSUME(MoveIgnoresTargetAbility(MOVE_SUNSTEEL_STRIKE)); + ASSUME(MoveMakesContact(MOVE_SUNSTEEL_STRIKE)); + PLAYER(SPECIES_KOMALA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMATOSE); } + OPPONENT(SPECIES_CROAGUNK) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_POISON_TOUCH); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUNSTEEL_STRIKE, WITH_RNG(RNG_POISON_TOUCH, 1)); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNSTEEL_STRIKE, opponent); + HP_BAR(player); + NOT STATUS_ICON(player, poison: TRUE); + } THEN { + EXPECT_EQ(player->status1, STATUS1_NONE); + } +} + +WILD_BATTLE_TEST("Comatose boosts Dream Ball's multiplier (Traits)") +{ + enum Ability ability; + u16 species; + bool32 shouldCatch; + const u16 rng = 50000; + + PARAMETRIZE { species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; shouldCatch = TRUE; } + PARAMETRIZE { species = SPECIES_MIMIKYU; ability = ABILITY_DISGUISE; shouldCatch = FALSE; } + + GIVEN { + ASSUME(B_DREAM_BALL_MODIFIER >= GEN_8); + ASSUME(gSpeciesInfo[species].catchRate == 45); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); MaxHP(100); HP(1); } + } WHEN { + TURN { USE_ITEM(player, ITEM_DREAM_BALL, WITH_RNG(RNG_BALLTHROW_SHAKE, rng)); } + } SCENE { + ANIMATION(ANIM_TYPE_SPECIAL, B_ANIM_BALL_THROW, player); + } THEN { + if (shouldCatch) + EXPECT_EQ(gBattleResults.caughtMonSpecies, species); + else + EXPECT_EQ(gBattleResults.caughtMonSpecies, SPECIES_NONE); + } +} + +#endif diff --git a/test/battle/ability/commander.c b/test/battle/ability/commander.c index e33c4e1521f7..e537bc718395 100644 --- a/test/battle/ability/commander.c +++ b/test/battle/ability/commander.c @@ -474,3 +474,521 @@ DOUBLE_BATTLE_TEST("Commander will not activate if partner Dondozo is about to s NOT ABILITY_POPUP(playerRight, ABILITY_COMMANDER); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Commander will activate once Dondozo switches in (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + PLAYER(SPECIES_DONDOZO); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(playerLeft, 2); } + } SCENE { + ABILITY_POPUP(playerRight, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + } +} + +DOUBLE_BATTLE_TEST("Commander increases all stats by 2 stages once it is triggered (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + PLAYER(SPECIES_DONDOZO); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Dondozo's Attack sharply rose!"); + MESSAGE("Dondozo's Defense sharply rose!"); + MESSAGE("Dondozo's Sp. Atk sharply rose!"); + MESSAGE("Dondozo's Sp. Def sharply rose!"); + MESSAGE("Dondozo's Speed sharply rose!"); + } +} + +DOUBLE_BATTLE_TEST("Commander Tatsugiri avoids moves targetted towards it (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + PLAYER(SPECIES_DONDOZO); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); MOVE(opponentRight, MOVE_POUND, target: playerRight); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); + MESSAGE("The opposing Wobbuffet's attack missed!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Commander Tatsugiri will still take residual damage from a field effect while inside Dondozo (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + PLAYER(SPECIES_DONDOZO); + OPPONENT(SPECIES_TYRANITAR) { Ability(ABILITY_MOXIE); Innates(ABILITY_SAND_STREAM); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + ABILITY_POPUP(opponentLeft, ABILITY_SAND_STREAM); + MESSAGE("Dondozo is buffeted by the sandstorm!"); + MESSAGE("Tatsugiri is buffeted by the sandstorm!"); + MESSAGE("The opposing Wobbuffet is buffeted by the sandstorm!"); + } +} + +DOUBLE_BATTLE_TEST("Commander Tatsugiri will still take poison damage if while inside Dondozo (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); Status1(STATUS1_POISON); } + PLAYER(SPECIES_DONDOZO); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + MESSAGE("Tatsugiri was hurt by its poisoning!"); + } +} + +DOUBLE_BATTLE_TEST("Commander Tatsugiri still avoids moves even when the attacker has No Guard (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + PLAYER(SPECIES_DONDOZO); + OPPONENT(SPECIES_MACHAMP) { Ability(ABILITY_INNER_FOCUS); Innates(ABILITY_NO_GUARD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); + MESSAGE("The opposing Machamp's attack missed!"); + } +} + +DOUBLE_BATTLE_TEST("Commander cannot affect a Dondozo that was previously affected by Commander until it faints and revived (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_DONDOZO); + PLAYER(SPECIES_TATSUGIRI) { HP(1); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); Status1(STATUS1_POISON); } + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, MOVE_CELEBRATE); SWITCH(playerLeft, 2); SEND_OUT(playerLeft, 3); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + MESSAGE("Tatsugiri was hurt by its poisoning!"); + NONE_OF { + ABILITY_POPUP(playerLeft, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + } + } +} + +DOUBLE_BATTLE_TEST("Commander prevents Whirlwind from working against Dondozo or Tatsugiri while it's active (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + PLAYER(SPECIES_DONDOZO); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_WHIRLWIND, target: playerLeft); } + TURN { MOVE(opponentRight, MOVE_WHIRLWIND, target: playerRight); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + MESSAGE("The opposing Wobbuffet used Whirlwind!"); + MESSAGE("But it failed!"); + MESSAGE("The opposing Wobbuffet used Whirlwind!"); + MESSAGE("But it failed!"); + } +} + +DOUBLE_BATTLE_TEST("Commander prevents Red Card from working while Commander is active (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + PLAYER(SPECIES_DONDOZO); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + } THEN { + EXPECT(opponentLeft->item == ITEM_NONE); + EXPECT(playerRight->species == SPECIES_DONDOZO); + } + +} + +DOUBLE_BATTLE_TEST("Commander Tatsugiri is not damaged by a double target move if Dondozo faints (Traits)") +{ + GIVEN { + ASSUME(GetMoveTarget(MOVE_SURF) == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(SPECIES_DONDOZO) { HP(1); }; + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SURF); SEND_OUT(playerLeft, 2); } + } SCENE { + ABILITY_POPUP(playerRight, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + HP_BAR(playerLeft); + HP_BAR(opponentRight); + NOT HP_BAR(playerRight); + MESSAGE("Dondozo fainted!"); + } +} + +DOUBLE_BATTLE_TEST("Commander Tatsugiri takes no damage from multi-target damaging moves (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + PLAYER(SPECIES_DONDOZO); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SURF); MOVE(opponentRight, MOVE_SURF); SWITCH(playerLeft, 2); } + } SCENE { + ABILITY_POPUP(playerRight, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURF, opponentLeft); + HP_BAR(playerLeft); + NOT HP_BAR(playerRight); + HP_BAR(opponentRight); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURF, opponentRight); + HP_BAR(playerLeft); + HP_BAR(opponentLeft); + NOT HP_BAR(playerRight); + } +} + +DOUBLE_BATTLE_TEST("Commander doesn't prevent Transform from working on a Commander Tatsugiri (Traits)") +{ + GIVEN { + PLAYER(SPECIES_DONDOZO); + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentRight, MOVE_TRANSFORM, target: playerRight); } + } SCENE { + ABILITY_POPUP(playerRight, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRANSFORM, opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Commander doesn't prevent Imposter from working on a Commander Tatsugiri (Traits)") +{ + GIVEN { + PLAYER(SPECIES_DONDOZO); + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_DITTO) { Ability(ABILITY_LIMBER); Innates(ABILITY_IMPOSTER); } + } WHEN { + TURN { } + TURN { SWITCH(opponentLeft, 2); } + } SCENE { + ABILITY_POPUP(playerRight, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + ABILITY_POPUP(opponentLeft, ABILITY_IMPOSTER); + MESSAGE("The opposing Ditto transformed into Tatsugiri using Imposter!"); + } +} + +DOUBLE_BATTLE_TEST("Commander Tatsugiri is still affected by Perish Song while controlling Dondozo (Traits)") +{ + GIVEN { + PLAYER(SPECIES_DONDOZO); + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_PERISH_SONG); } + TURN {} + TURN {} + TURN {} + } SCENE { + ABILITY_POPUP(playerRight, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PERISH_SONG, opponentLeft); + MESSAGE("All Pokémon that heard the song will faint in three turns!"); + MESSAGE("Dondozo's perish count fell to 0!"); + MESSAGE("Dondozo fainted!"); + MESSAGE("The opposing Wobbuffet's perish count fell to 0!"); + MESSAGE("The opposing Wobbuffet fainted!"); + NONE_OF { + MESSAGE("Tatsugiri's perish count fell to 0!"); + MESSAGE("Tatsugiri fainted!"); + } + MESSAGE("The opposing Wynaut's perish count fell to 0!"); + MESSAGE("The opposing Wynaut fainted!"); + } +} + +DOUBLE_BATTLE_TEST("Commander Tatsugiri is still affected by Haze while controlling Dondozo (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + PLAYER(SPECIES_DONDOZO); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, MOVE_SWORDS_DANCE); } + TURN { SWITCH(playerLeft, 2); MOVE(opponentRight, MOVE_HAZE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, playerRight); + ABILITY_POPUP(playerRight, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HAZE, opponentRight); + } THEN { + EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Commander Attacker is kept (Dondozo Left Slot) (Traits)") +{ + GIVEN { + ASSUME(GetMoveTarget(MOVE_SURF) == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + PLAYER(SPECIES_DONDOZO); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: opponentLeft); } + TURN { SWITCH(playerLeft, 2); MOVE(opponentLeft, MOVE_SURF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentRight); + ABILITY_POPUP(playerRight, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURF, opponentLeft); + HP_BAR(playerLeft); + HP_BAR(opponentRight); + MESSAGE("The opposing Wobbuffet's attack missed!"); + } +} + +DOUBLE_BATTLE_TEST("Commander Attacker is kept (Dondozo Right Slot) (Traits)") +{ + GIVEN { + ASSUME(GetMoveTarget(MOVE_SURF) == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_DONDOZO); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: opponentLeft); } + TURN { SWITCH(playerRight, 2); MOVE(opponentLeft, MOVE_SURF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentRight); + ABILITY_POPUP(playerLeft, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + MESSAGE("The opposing Wobbuffet's attack missed!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURF, opponentLeft); + HP_BAR(playerRight); + HP_BAR(opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Commander Tatsugiri does not attack if Dondozo faints the same turn it's switched in (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + PLAYER(SPECIES_DONDOZO) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + SWITCH(playerLeft, 2); + MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); + MOVE(opponentRight, MOVE_SCRATCH, target: playerRight); + MOVE(playerRight, MOVE_CELEBRATE); + SEND_OUT(playerLeft, 0); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); + HP_BAR(playerLeft); + MESSAGE("Dondozo fainted!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentRight); + HP_BAR(playerRight); + NOT MESSAGE("Tatsugiri used Celebrate!"); + } +} + +DOUBLE_BATTLE_TEST("Commander Tatsugiri does not get hit by Dragon Darts when a commanded Dondozo faints (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_DRAGON_DARTS) == EFFECT_DRAGON_DARTS); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_DONDOZO) { HP(1); } + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { SWITCH(playerLeft, 2); MOVE(opponentRight, MOVE_DRAGON_DARTS, target: playerRight); SEND_OUT(playerRight, 0); } + } SCENE { + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, opponentRight); + MESSAGE("Dondozo fainted!"); + NOT HP_BAR(playerLeft); + } +} + +DOUBLE_BATTLE_TEST("Commander Tatsugiri does not get hit by Dragon Darts when commanding Dondozo (Traits)") +{ + bool32 targetPlayerRight; + PARAMETRIZE { targetPlayerRight = TRUE; } + PARAMETRIZE { targetPlayerRight = FALSE; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_DRAGON_DARTS) == EFFECT_DRAGON_DARTS); + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + PLAYER(SPECIES_DONDOZO); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + if (targetPlayerRight == TRUE) + TURN { MOVE(opponentRight, MOVE_DRAGON_DARTS, target: playerRight); } + else + TURN { MOVE(opponentRight, MOVE_DRAGON_DARTS, target: playerLeft); } + } SCENE { + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, opponentRight); + HP_BAR(playerRight); + NOT HP_BAR(playerLeft); + HP_BAR(playerRight); + NOT HP_BAR(playerLeft); + } +} + +DOUBLE_BATTLE_TEST("Commander will not activate if Dondozo fainted right before Tatsugiri came in (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_DONDOZO) { HP(1); } + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: playerRight); MOVE(playerLeft, MOVE_SHED_TAIL); SEND_OUT(playerLeft, 2); SEND_OUT(playerRight, 3); } + } SCENE { + NOT ABILITY_POPUP(playerLeft, ABILITY_COMMANDER); + } +} + +DOUBLE_BATTLE_TEST("Commander prevent Dondozo from switch out by Dragon Tail (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_DRAGON_TAIL) == EFFECT_HIT_SWITCH_TARGET); + PLAYER(SPECIES_DONDOZO); + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_DRAGON_TAIL, target: playerLeft); } + } SCENE { + ABILITY_POPUP(playerRight, ABILITY_COMMANDER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, opponentLeft); + NOT MESSAGE("Wobbuffet was dragged out!"); + } +} +DOUBLE_BATTLE_TEST("Commander will not activate if partner Dondozo is about to switch out (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_DONDOZO); + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + SWITCH(playerLeft, 2); + SWITCH(playerRight, 3); + } + } SCENE { + NOT ABILITY_POPUP(playerRight, ABILITY_COMMANDER); + } +} + +DOUBLE_BATTLE_TEST("Commander will not activate if partner Dondozo is about to switch out (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_DONDOZO); + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + SWITCH(playerLeft, 2); + SWITCH(playerRight, 3); + } + } SCENE { + NOT ABILITY_POPUP(playerRight, ABILITY_COMMANDER); + } +} + +#endif + +#if MAX_MON_ITEMS > 1 +DOUBLE_BATTLE_TEST("Commander prevents Red Card from working while Commander is active (Multi)") +{ + GIVEN { + PLAYER(SPECIES_TATSUGIRI) { Ability(ABILITY_COMMANDER); } + PLAYER(SPECIES_DONDOZO); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + } THEN { + EXPECT(opponentLeft->item == ITEM_NONE); + EXPECT(playerRight->species == SPECIES_DONDOZO); + } +} + +#endif diff --git a/test/battle/ability/competitive.c b/test/battle/ability/competitive.c index b7d938ae8dd4..8eea2e0983fb 100644 --- a/test/battle/ability/competitive.c +++ b/test/battle/ability/competitive.c @@ -335,3 +335,381 @@ SINGLE_BATTLE_TEST("Competitive doesn't activate if the pokemon lowers it's own EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Competitive sharply raises player's Sp. Atk after Intimidate (Traits)") +{ + u32 abilityLeft, abilityRight; + + PARAMETRIZE { abilityLeft = ABILITY_FRIEND_GUARD; abilityRight = ABILITY_FRIEND_GUARD; } + PARAMETRIZE { abilityLeft = ABILITY_FRIEND_GUARD; abilityRight = ABILITY_COMPETITIVE; } + PARAMETRIZE { abilityLeft = ABILITY_COMPETITIVE; abilityRight = ABILITY_FRIEND_GUARD; } + PARAMETRIZE { abilityLeft = ABILITY_COMPETITIVE; abilityRight = ABILITY_COMPETITIVE; } + + GIVEN { + PLAYER(SPECIES_IGGLYBUFF) { Ability(ABILITY_CUTE_CHARM); Innates(abilityLeft); } + PLAYER(SPECIES_JIGGLYPUFF) { Ability(ABILITY_CUTE_CHARM); Innates(abilityRight); } + OPPONENT(SPECIES_GYARADOS) { Ability(ABILITY_MOXIE); Innates(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target:opponentLeft); MOVE(playerRight, MOVE_SCRATCH, target:opponentRight); } + } SCENE { + //1st mon Intimidate + ABILITY_POPUP(opponentLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("The opposing Gyarados's Intimidate cuts Igglybuff's Attack!"); + if (abilityLeft == ABILITY_COMPETITIVE) { + ABILITY_POPUP(playerLeft, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Igglybuff's Sp. Atk sharply rose!"); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("The opposing Gyarados's Intimidate cuts Jigglypuff's Attack!"); + if (abilityRight == ABILITY_COMPETITIVE) { + ABILITY_POPUP(playerRight, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Jigglypuff's Sp. Atk sharply rose!"); + } + + //2nd mon Intimidate + ABILITY_POPUP(opponentRight, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("The opposing Arbok's Intimidate cuts Igglybuff's Attack!"); + if (abilityLeft == ABILITY_COMPETITIVE) { + ABILITY_POPUP(playerLeft, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Igglybuff's Sp. Atk sharply rose!"); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("The opposing Arbok's Intimidate cuts Jigglypuff's Attack!"); + if (abilityRight == ABILITY_COMPETITIVE) { + ABILITY_POPUP(playerRight, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Jigglypuff's Sp. Atk sharply rose!"); + } + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + (abilityLeft == ABILITY_COMPETITIVE ? 4 : 0)); + EXPECT_EQ(playerRight->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + (abilityRight == ABILITY_COMPETITIVE ? 4 : 0)); + } +} + +// Same as above, but for opponent. +DOUBLE_BATTLE_TEST("Competitive sharply raises opponent's Sp. Atk after Intimidate (Traits)") +{ + u32 abilityLeft, abilityRight; + + PARAMETRIZE { abilityLeft = ABILITY_FRIEND_GUARD; abilityRight = ABILITY_FRIEND_GUARD; } + PARAMETRIZE { abilityLeft = ABILITY_FRIEND_GUARD; abilityRight = ABILITY_COMPETITIVE; } + PARAMETRIZE { abilityLeft = ABILITY_COMPETITIVE; abilityRight = ABILITY_FRIEND_GUARD; } + PARAMETRIZE { abilityLeft = ABILITY_COMPETITIVE; abilityRight = ABILITY_COMPETITIVE; } + + GIVEN { + OPPONENT(SPECIES_IGGLYBUFF) { Ability(ABILITY_CUTE_CHARM); Innates(abilityLeft); } + OPPONENT(SPECIES_JIGGLYPUFF) { Ability(ABILITY_CUTE_CHARM); Innates(abilityRight); } + PLAYER(SPECIES_GYARADOS) { Ability(ABILITY_MOXIE); Innates(ABILITY_INTIMIDATE); } + PLAYER(SPECIES_ARBOK) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target:playerLeft); MOVE(opponentRight, MOVE_SCRATCH, target:playerRight); } + } SCENE { + //1st mon Intimidate + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("Gyarados's Intimidate cuts the opposing Igglybuff's Attack!"); + if (abilityLeft == ABILITY_COMPETITIVE) { + ABILITY_POPUP(opponentLeft, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("The opposing Igglybuff's Sp. Atk sharply rose!"); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("Gyarados's Intimidate cuts the opposing Jigglypuff's Attack!"); + if (abilityRight == ABILITY_COMPETITIVE) { + ABILITY_POPUP(opponentRight, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("The opposing Jigglypuff's Sp. Atk sharply rose!"); + } + + //2nd mon Intimidate + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("Arbok's Intimidate cuts the opposing Igglybuff's Attack!"); + if (abilityLeft == ABILITY_COMPETITIVE) { + ABILITY_POPUP(opponentLeft, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("The opposing Igglybuff's Sp. Atk sharply rose!"); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("Arbok's Intimidate cuts the opposing Jigglypuff's Attack!"); + if (abilityRight == ABILITY_COMPETITIVE) { + ABILITY_POPUP(opponentRight, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("The opposing Jigglypuff's Sp. Atk sharply rose!"); + } + } THEN { + EXPECT_EQ(opponentLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + (abilityLeft == ABILITY_COMPETITIVE ? 4 : 0)); + EXPECT_EQ(opponentRight->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + (abilityRight == ABILITY_COMPETITIVE ? 4 : 0)); + } +} + +SINGLE_BATTLE_TEST("Competitive activates after Sticky Web lowers Speed (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_IGGLYBUFF) { Ability(ABILITY_COMPETITIVE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_STICKY_WEB); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponent); + // Switch-in - Sticky Web activates + SEND_IN_MESSAGE("Igglybuff"); + MESSAGE("Igglybuff was caught in a sticky web!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Igglybuff's Speed fell!"); + // Competitive activates + ABILITY_POPUP(player, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Igglybuff's Sp. Atk sharply rose!"); + } +} + +SINGLE_BATTLE_TEST("Competitive doesn't activate after Sticky Web lowers Speed if Court Changed (gen8) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_DEFIANT_STICKY_WEB, GEN_8); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_IGGLYBUFF) { Ability(ABILITY_CUTE_CHARM); Innates(ABILITY_COMPETITIVE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STICKY_WEB); MOVE(opponent, MOVE_COURT_CHANGE); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COURT_CHANGE, opponent); + // Switch-in - Sticky Web activates + SEND_IN_MESSAGE("Igglybuff"); + MESSAGE("Igglybuff was caught in a sticky web!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Igglybuff's Speed fell!"); + // Competitive doesn't activate + NONE_OF { + ABILITY_POPUP(player, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Igglybuff's Sp. Atk sharply rose!"); + } + } +} + +SINGLE_BATTLE_TEST("Competitive correctly activates after Sticky Web lowers Speed if Court Changed (Gen8) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_DEFIANT_STICKY_WEB, GEN_8); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_IGGLYBUFF) { Ability(ABILITY_CUTE_CHARM); Innates(ABILITY_COMPETITIVE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STICKY_WEB); MOVE(opponent, MOVE_COURT_CHANGE); } + TURN { SWITCH(player, 1); } + TURN { MOVE(opponent, MOVE_GROWL);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COURT_CHANGE, opponent); + // Switch-in - Sticky Web activates + SEND_IN_MESSAGE("Igglybuff"); + MESSAGE("Igglybuff was caught in a sticky web!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Igglybuff's Speed fell!"); + // Competitive doesn't activate + NONE_OF { + ABILITY_POPUP(player, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Igglybuff's Sp. Atk sharply rose!"); + } + // Competitive triggers correctly after Sticky Web + ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Igglybuff's Attack fell!"); + ABILITY_POPUP(player, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Igglybuff's Sp. Atk sharply rose!"); + } +} + +DOUBLE_BATTLE_TEST("Competitive is activated by Cotton Down for non-ally pokemon (Traits)") +{ + GIVEN { + PLAYER(SPECIES_IGGLYBUFF) { Ability(ABILITY_CUTE_CHARM); Innates(ABILITY_COMPETITIVE); } + PLAYER(SPECIES_IGGLYBUFF) { Ability(ABILITY_CUTE_CHARM); Innates(ABILITY_COMPETITIVE); } + OPPONENT(SPECIES_ELDEGOSS) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_COTTON_DOWN); } + OPPONENT(SPECIES_IGGLYBUFF) { Ability(ABILITY_CUTE_CHARM); Innates(ABILITY_COMPETITIVE); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_COTTON_DOWN); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Igglybuff's Speed fell!"); + ABILITY_POPUP(playerLeft, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Igglybuff's Sp. Atk sharply rose!"); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Igglybuff's Speed fell!"); + ABILITY_POPUP(playerRight, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Igglybuff's Sp. Atk sharply rose!"); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("The opposing Igglybuff's Speed fell!"); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(playerRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(playerLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 2); + EXPECT_EQ(playerRight->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Competitive activates before White Herb (Traits)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_LEER; } + PARAMETRIZE { move = MOVE_CONFIDE; } + + GIVEN { + PLAYER(SPECIES_IGGLYBUFF) { Ability(ABILITY_CUTE_CHARM); Innates(ABILITY_COMPETITIVE); Item(ITEM_WHITE_HERB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + + ABILITY_POPUP(player, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Igglybuff's Sp. Atk sharply rose!"); + + if (move == MOVE_LEER) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Igglybuff returned its stats to normal using its White Herb!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Igglybuff returned its stats to normal using its White Herb!"); + } + } + } THEN { + if (move == MOVE_LEER) { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 2); + } else { + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + } + } +} + +SINGLE_BATTLE_TEST("Competitive activates for each stat that is lowered (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TICKLE) == EFFECT_TICKLE); + PLAYER(SPECIES_IGGLYBUFF) { Ability(ABILITY_CUTE_CHARM); Innates(ABILITY_COMPETITIVE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TICKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TICKLE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + + MESSAGE("Igglybuff's Attack fell!"); + ABILITY_POPUP(player, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Igglybuff's Sp. Atk sharply rose!"); + + MESSAGE("Igglybuff's Defense fell!"); + ABILITY_POPUP(player, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Igglybuff's Sp. Atk sharply rose!"); + + } THEN { + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 4); + } +} + +SINGLE_BATTLE_TEST("Competitive doesn't activate if the pokemon lowers it's own stats (Traits)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SUPERPOWER; } + PARAMETRIZE { move = MOVE_CLOSE_COMBAT; } + PARAMETRIZE { move = MOVE_MAKE_IT_RAIN; } + PARAMETRIZE { move = MOVE_SPIN_OUT; } + + GIVEN { + ASSUME(MoveHasAdditionalEffectSelf(MOVE_SUPERPOWER, MOVE_EFFECT_ATK_DEF_DOWN)); + ASSUME(MoveHasAdditionalEffectSelf(MOVE_CLOSE_COMBAT, MOVE_EFFECT_DEF_SPDEF_DOWN)); + ASSUME(MoveHasAdditionalEffectSelf(MOVE_MAKE_IT_RAIN, MOVE_EFFECT_SP_ATK_MINUS_1)); + ASSUME(MoveHasAdditionalEffectSelf(MOVE_SPIN_OUT, MOVE_EFFECT_SPD_MINUS_2)); + PLAYER(SPECIES_IGGLYBUFF) { Ability(ABILITY_CUTE_CHARM); Innates(ABILITY_COMPETITIVE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + + NONE_OF { + ABILITY_POPUP(player, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Igglybuff's Sp. Atk sharply rose!"); + } + } THEN { + if (move == MOVE_SUPERPOWER) + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + else + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Competitive activates before White Herb (Multi)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_LEER; } + PARAMETRIZE { move = MOVE_CONFIDE; } + + GIVEN { + PLAYER(SPECIES_IGGLYBUFF) { Ability(ABILITY_COMPETITIVE); Items(ITEM_PECHA_BERRY, ITEM_WHITE_HERB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + + ABILITY_POPUP(player, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Igglybuff's Sp. Atk sharply rose!"); + + if (move == MOVE_LEER) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Igglybuff returned its stats to normal using its White Herb!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Igglybuff returned its stats to normal using its White Herb!"); + } + } + } THEN { + if (move == MOVE_LEER) { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 2); + } else { + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + } + } +} +#endif diff --git a/test/battle/ability/compound_eyes.c b/test/battle/ability/compound_eyes.c index 338da6bf21d9..29ea2ab326b3 100644 --- a/test/battle/ability/compound_eyes.c +++ b/test/battle/ability/compound_eyes.c @@ -31,3 +31,36 @@ SINGLE_BATTLE_TEST("Compound Eyes does not affect OHKO moves") HP_BAR(opponent, hp: 0); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Compound Eyes raises accuracy (Traits)") +{ + PASSES_RANDOMLY(91, 100, RNG_ACCURACY); + GIVEN { + ASSUME(GetMoveAccuracy(MOVE_THUNDER) == 70); + PLAYER(SPECIES_BUTTERFREE) { Ability(ABILITY_TINTED_LENS); Innates(ABILITY_COMPOUND_EYES); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_THUNDER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER, player); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Compound Eyes does not affect OHKO moves (Traits)") +{ + PASSES_RANDOMLY(30, 100, RNG_ACCURACY); + GIVEN { + ASSUME(GetMoveAccuracy(MOVE_FISSURE) == 30); + ASSUME(GetMoveEffect(MOVE_FISSURE) == EFFECT_OHKO); + PLAYER(SPECIES_BUTTERFREE) { Ability(ABILITY_TINTED_LENS); Innates(ABILITY_COMPOUND_EYES); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FISSURE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FISSURE, player); + HP_BAR(opponent, hp: 0); + } +} +#endif diff --git a/test/battle/ability/contrary.c b/test/battle/ability/contrary.c index 5cc0c2ad3fb1..3eccfbf9e751 100644 --- a/test/battle/ability/contrary.c +++ b/test/battle/ability/contrary.c @@ -288,3 +288,317 @@ SINGLE_BATTLE_TEST("Contrary does not invert stat changes that have been Baton-p EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Contrary raises Attack when Intimidated in a single battle (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_CONTRARY; } + PARAMETRIZE { ability = ABILITY_TANGLED_FEET; } + GIVEN { + PLAYER(SPECIES_MIGHTYENA) { Ability(ABILITY_QUICK_FEET); Innates(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_SPINDA) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + if (ability == ABILITY_CONTRARY) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Spinda's Attack rose!"); + } else { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Mightyena's Intimidate cuts the opposing Spinda's Attack!"); + } + HP_BAR(player, captureDamage: &results[i].damage); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], (ability == ABILITY_CONTRARY) ? DEFAULT_STAT_STAGE + 1 : DEFAULT_STAT_STAGE - 1); + } FINALLY { + EXPECT_MUL_EQ(results[1].damage, Q_4_12(2.25), results[0].damage); + } +} + +DOUBLE_BATTLE_TEST("Contrary raises Attack when Intimidated in a double battle (Traits)", s16 damageLeft, s16 damageRight) +{ + enum Ability abilityLeft, abilityRight; + + PARAMETRIZE { abilityLeft = ABILITY_CONTRARY; abilityRight = ABILITY_CONTRARY; } + PARAMETRIZE { abilityLeft = ABILITY_TANGLED_FEET; abilityRight = ABILITY_TANGLED_FEET; } + PARAMETRIZE { abilityLeft = ABILITY_CONTRARY; abilityRight = ABILITY_TANGLED_FEET; } + PARAMETRIZE { abilityLeft = ABILITY_TANGLED_FEET; abilityRight = ABILITY_CONTRARY; } + + GIVEN { + PLAYER(SPECIES_MIGHTYENA) { Ability(ABILITY_MOXIE); Innates(ABILITY_INTIMIDATE); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SPINDA) { Ability(ABILITY_LIGHT_METAL); Innates(abilityLeft); } + OPPONENT(SPECIES_SPINDA) { Ability(ABILITY_LIGHT_METAL); Innates(abilityRight); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); MOVE(opponentRight, MOVE_SCRATCH, target: playerRight); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + if (abilityLeft == ABILITY_CONTRARY) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("The opposing Spinda's Attack rose!"); + } else { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("Mightyena's Intimidate cuts the opposing Spinda's Attack!"); + } + if (abilityRight == ABILITY_CONTRARY) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("The opposing Spinda's Attack rose!"); + } else { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("Mightyena's Intimidate cuts the opposing Spinda's Attack!"); + } + HP_BAR(playerLeft, captureDamage: &results[i].damageLeft); + HP_BAR(playerRight, captureDamage: &results[i].damageRight); + } THEN { + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], (abilityLeft == ABILITY_CONTRARY) ? DEFAULT_STAT_STAGE+1 : DEFAULT_STAT_STAGE-1); + EXPECT_EQ(opponentRight->statStages[STAT_ATK], (abilityRight == ABILITY_CONTRARY) ? DEFAULT_STAT_STAGE+1 : DEFAULT_STAT_STAGE-1); + } FINALLY { + EXPECT_MUL_EQ(results[1].damageLeft, Q_4_12(2.25), results[0].damageLeft); + EXPECT_MUL_EQ(results[1].damageRight, Q_4_12(2.25), results[0].damageRight); + } +} + +SINGLE_BATTLE_TEST("Contrary raises stats after using a move which would normally lower them: Overheat (Traits)", s16 damageBefore, s16 damageAfter) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_CONTRARY; } + PARAMETRIZE { ability = ABILITY_TANGLED_FEET; } + GIVEN { + ASSUME(MoveHasAdditionalEffectSelf(MOVE_OVERHEAT, MOVE_EFFECT_SP_ATK_MINUS_2) == TRUE); + ASSUME(GetMoveCategory(MOVE_OVERHEAT) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SPINDA) { Ability(ABILITY_OWN_TEMPO); Innates(ability); } + } WHEN { + TURN { MOVE(opponent, MOVE_OVERHEAT); } + TURN { MOVE(opponent, MOVE_OVERHEAT); } + } SCENE { + MESSAGE("The opposing Spinda used Overheat!"); + HP_BAR(player, captureDamage: &results[i].damageBefore); + if (ability == ABILITY_CONTRARY) { + // ABILITY_POPUP(opponent, ABILITY_CONTRARY); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Spinda's Sp. Atk sharply rose!"); + } + else { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Spinda's Sp. Atk harshly fell!"); + } + + // MESSAGE("The opposing Spinda used Overheat!"); + HP_BAR(player, captureDamage: &results[i].damageAfter); + if (ability == ABILITY_CONTRARY) { + // ABILITY_POPUP(opponent, ABILITY_CONTRARY); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Spinda's Sp. Atk sharply rose!"); + } + else { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Spinda's Sp. Atk harshly fell!"); + } + } + FINALLY { + EXPECT_MUL_EQ(results[0].damageBefore, Q_4_12(2.0), results[0].damageAfter); + EXPECT_MUL_EQ(results[1].damageBefore, Q_4_12(0.5), results[1].damageAfter); + } +} + +SINGLE_BATTLE_TEST("Contrary lowers a stat after using a move which would normally raise it: Swords Dance (Traits)", s16 damageBefore, s16 damageAfter) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_CONTRARY; } + PARAMETRIZE { ability = ABILITY_TANGLED_FEET; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_SWORDS_DANCE) == EFFECT_ATTACK_UP_2); + PLAYER(SPECIES_WOBBUFFET) { Defense(102); } + OPPONENT(SPECIES_SPINDA) { Ability(ABILITY_OWN_TEMPO); Innates(ability); Attack(100); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(opponent, MOVE_SWORDS_DANCE); } + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + MESSAGE("The opposing Spinda used Scratch!"); + HP_BAR(player, captureDamage: &results[i].damageBefore); + + //MESSAGE("The opposing Spinda used Swords Dance!"); + if (ability == ABILITY_CONTRARY) { + // ABILITY_POPUP(opponent, ABILITY_CONTRARY); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Spinda's Attack harshly fell!"); + } + else { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Spinda's Attack sharply rose!"); + } + + // MESSAGE("The opposing Spinda used Scratch!"); + HP_BAR(player, captureDamage: &results[i].damageAfter); + } + FINALLY { + EXPECT_MUL_EQ(results[0].damageBefore, Q_4_12(0.5), results[0].damageAfter); + EXPECT_MUL_EQ(results[1].damageBefore, Q_4_12(2.0), results[1].damageAfter); + } +} + +SINGLE_BATTLE_TEST("Contrary raises a stat after using a move which would normally lower it: Growl (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_CONTRARY; } + PARAMETRIZE { ability = ABILITY_TANGLED_FEET; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_GROWL) == EFFECT_ATTACK_DOWN); + PLAYER(SPECIES_WOBBUFFET) { Speed(3); } + OPPONENT(SPECIES_SPINDA) { Ability(ABILITY_OWN_TEMPO); Innates(ability); Speed(2); } + } WHEN { + TURN { MOVE(player, MOVE_GROWL); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet used Growl!"); + if (ability == ABILITY_CONTRARY) { + // ABILITY_POPUP(opponent, ABILITY_CONTRARY); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Spinda's Attack rose!"); + } + else { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Spinda's Attack fell!"); + } + + MESSAGE("The opposing Spinda used Scratch!"); + HP_BAR(player, captureDamage: &results[i].damage); + } + FINALLY { + EXPECT_MUL_EQ(results[1].damage, Q_4_12(2.125), results[0].damage); + } +} + +SINGLE_BATTLE_TEST("Contrary lowers a stat after using a move which would normally raise it: Belly Drum (Traits)", s16 damageBefore, s16 damageAfter) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_CONTRARY; } + PARAMETRIZE { ability = ABILITY_TANGLED_FEET; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_BELLY_DRUM) == EFFECT_BELLY_DRUM); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SPINDA) { Ability(ABILITY_OWN_TEMPO); Innates(ability); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(opponent, MOVE_BELLY_DRUM); } + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + MESSAGE("The opposing Spinda used Scratch!"); + HP_BAR(player, captureDamage: &results[i].damageBefore); + + if (ability == ABILITY_CONTRARY) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Spinda cut its own HP and maximized its Attack!"); //Message stays the same + } + else { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Spinda cut its own HP and maximized its Attack!"); + } + + HP_BAR(player, captureDamage: &results[i].damageAfter); + } + FINALLY { + EXPECT_MUL_EQ(results[0].damageBefore, UQ_4_12(0.25), results[0].damageAfter); + EXPECT_MUL_EQ(results[1].damageBefore, UQ_4_12(4.0), results[1].damageAfter); + } +} + +SINGLE_BATTLE_TEST("Sticky Web raises Speed by 1 for Contrary mon on switch-in (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SNIVY) { Ability(ABILITY_OVERGROW); Innates(ABILITY_CONTRARY); } + } WHEN { + TURN { MOVE(player, MOVE_STICKY_WEB); } + TURN { SWITCH(opponent, 1); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, player); + MESSAGE("A sticky web has been laid out on the ground around the opposing team!"); + MESSAGE("2 sent out Snivy!"); + MESSAGE("The opposing Snivy was caught in a sticky web!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Snivy's Speed rose!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI sees Contrary-effected moves correctly in MoveEffectInPlus instead of as a neutral effect (Traits)") +{ + GIVEN{ + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_HERACROSS){ + Level(44); + HP(1); + Speed(5); + Nature(NATURE_ADAMANT); + Item(ITEM_LOADED_DICE); + Moves(MOVE_PIN_MISSILE); + } + OPPONENT(SPECIES_SERPERIOR){ + Level(44); + Speed(10); + Nature(NATURE_TIMID); + Ability(ABILITY_OVERGROW); Innates(ABILITY_CONTRARY); + Moves(MOVE_DRAGON_PULSE, MOVE_SPIN_OUT, MOVE_HIDDEN_POWER, MOVE_GLARE); + } + } WHEN { + TURN{ + MOVE(player, MOVE_PIN_MISSILE); + EXPECT_MOVE(opponent, MOVE_SPIN_OUT); // previously all 107, now sees speed can rise w/ Contrary + } + } +} + +SINGLE_BATTLE_TEST("Contrary does not invert stat changes that have been Baton-passed") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SWORDS_DANCE) == EFFECT_ATTACK_UP_2); + ASSUME(GetMoveEffect(MOVE_BATON_PASS) == EFFECT_BATON_PASS); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SNIVY) { Ability(ABILITY_OVERGROW); Innates(ABILITY_CONTRARY); } + } WHEN { + TURN { MOVE(opponent, MOVE_SWORDS_DANCE); } + TURN { MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BATON_PASS, opponent); + MESSAGE("2 sent out Snivy!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +AI_SINGLE_BATTLE_TEST("AI sees Contrary-effected moves correctly in MoveEffectInPlus instead of as a neutral effect (Multi)") +{ + GIVEN{ + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_HERACROSS){ + Level(44); + HP(1); + Speed(5); + Nature(NATURE_ADAMANT); + Items(ITEM_PECHA_BERRY, ITEM_LOADED_DICE); + Moves(MOVE_PIN_MISSILE); + } + OPPONENT(SPECIES_SERPERIOR){ + Level(44); + Speed(10); + Nature(NATURE_TIMID); + Ability(ABILITY_CONTRARY); + Moves(MOVE_DRAGON_PULSE, MOVE_SPIN_OUT, MOVE_HIDDEN_POWER, MOVE_GLARE); + } + } WHEN { + TURN{ + MOVE(player, MOVE_PIN_MISSILE); + EXPECT_MOVE(opponent, MOVE_SPIN_OUT); // previously all 107, now sees speed can rise w/ Contrary + } + } +} +#endif diff --git a/test/battle/ability/corrosion.c b/test/battle/ability/corrosion.c index c1e56ac59674..c55d76b3dabe 100644 --- a/test/battle/ability/corrosion.c +++ b/test/battle/ability/corrosion.c @@ -324,3 +324,446 @@ SINGLE_BATTLE_TEST("Corrosion does not affect Poison Spikes") } TO_DO_BATTLE_TEST("Dynamax: Corrosion can poison Poison/Steel types if the Pokémon uses G-Max Malodor") + +#if MAX_MON_TRAITS > 1 + +SINGLE_BATTLE_TEST("Corrosion can poison or badly poison a Pokemon regardless of its typing (Traits)") +{ + u16 species; + + PARAMETRIZE { species = SPECIES_ODDISH; } + PARAMETRIZE { species = SPECIES_BELDUM; } + + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_TWINEEDLE, MOVE_EFFECT_POISON) == TRUE); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_CORROSION); } + OPPONENT(species); + } WHEN { + TURN { MOVE(player, MOVE_TWINEEDLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TWINEEDLE, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Corrosion can poison or badly poison a Steel type with a status poison effect (Traits)") +{ + u16 move; + + PARAMETRIZE { move = MOVE_POISON_POWDER; } + PARAMETRIZE { move = MOVE_TOXIC; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_POISON_POWDER) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_POISON_POWDER) == MOVE_EFFECT_POISON); + ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_CORROSION); } + OPPONENT(SPECIES_BELDUM); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + if (move == MOVE_POISON_POWDER) + STATUS_ICON(opponent, poison: TRUE); + else + STATUS_ICON(opponent, badPoison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Corrosion does not effect poison type damaging moves if the target is immune to it (Traits)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_SLUDGE_BOMB, MOVE_EFFECT_POISON) == TRUE); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_CORROSION); } + OPPONENT(SPECIES_BELDUM); + } WHEN { + TURN { MOVE(player, MOVE_SLUDGE_BOMB); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SLUDGE_BOMB, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, poison: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Corrosion can poison Poison- and Steel-type targets if it uses Fling while holding a Toxic Orb or a Poison Barb (Traits)") +{ + u16 heldItem; + + PARAMETRIZE { heldItem = ITEM_POISON_BARB; } + PARAMETRIZE { heldItem = ITEM_TOXIC_ORB; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_FLING) == EFFECT_FLING); + ASSUME(gItemsInfo[ITEM_POISON_BARB].holdEffect == HOLD_EFFECT_TYPE_POWER); + ASSUME(gItemsInfo[ITEM_POISON_BARB].secondaryId == TYPE_POISON); + ASSUME(gItemsInfo[ITEM_TOXIC_ORB].holdEffect == HOLD_EFFECT_TOXIC_ORB); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_CORROSION); Item(heldItem); } + OPPONENT(SPECIES_ODDISH); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + if (heldItem == ITEM_POISON_BARB) + STATUS_ICON(opponent, poison: TRUE); + else + STATUS_ICON(opponent, badPoison: TRUE); + } +} + +SINGLE_BATTLE_TEST("If a Poison- or Steel-type Pokémon with Corrosion holds a Toxic Orb, it will badly poison itself (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_TOXIC_ORB].holdEffect == HOLD_EFFECT_TOXIC_ORB); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_CORROSION); Item(ITEM_TOXIC_ORB); } + OPPONENT(SPECIES_ODDISH); + } WHEN { + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + STATUS_ICON(player, badPoison: TRUE); + } +} + +SINGLE_BATTLE_TEST("If a Poison- or Steel-type Pokémon with Corrosion poisons a target with Synchronize, Synchronize will not poison Poison- or Steel-type Pokémon (Traits)") +{ + u16 move; + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISON_POWDER; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + ASSUME(GetMoveEffect(MOVE_POISON_POWDER) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_POISON_POWDER) == MOVE_EFFECT_POISON); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_CORROSION); } + OPPONENT(SPECIES_ABRA) { Ability(ABILITY_INNER_FOCUS); Innates(ABILITY_SYNCHRONIZE); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + if (move == MOVE_TOXIC) + STATUS_ICON(opponent, badPoison: TRUE); + else + STATUS_ICON(opponent, poison: TRUE); + NONE_OF { + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + STATUS_ICON(player, badPoison: TRUE); + STATUS_ICON(player, poison: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Corrosion cannot bypass moves that prevent poisoning such as Safeguard (Traits)") +{ + u16 move; + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISON_POWDER; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + ASSUME(GetMoveEffect(MOVE_POISON_POWDER) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_POISON_POWDER) == MOVE_EFFECT_POISON); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_CORROSION); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SAFEGUARD); MOVE(player, move); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, badPoison: TRUE); + STATUS_ICON(opponent, poison: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Corrosion cannot bypass abilities that prevent poisoning such as Immunity (Traits)") +{ + u16 move; + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISON_POWDER; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + ASSUME(GetMoveEffect(MOVE_POISON_POWDER) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_POISON_POWDER) == MOVE_EFFECT_POISON); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_CORROSION); } + OPPONENT(SPECIES_SNORLAX) { Ability(ABILITY_THICK_FAT); Innates(ABILITY_IMMUNITY); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, badPoison: TRUE); + STATUS_ICON(opponent, poison: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Corrosion allows the Pokémon with the ability to poison a Steel or Poison-type opponent by using Magic Coat (Traits)") +{ + u16 move; + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISON_POWDER; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + ASSUME(GetMoveEffect(MOVE_POISON_POWDER) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_POISON_POWDER) == MOVE_EFFECT_POISON); + ASSUME(GetMoveEffect(MOVE_MAGIC_COAT) == EFFECT_MAGIC_COAT); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_CORROSION); } + OPPONENT(SPECIES_BELDUM); + } WHEN { + TURN { MOVE(player, MOVE_MAGIC_COAT); MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MAGIC_COAT, player); + ANIMATION(ANIM_TYPE_MOVE, move, player); // Bounced by Magic Coat + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + if (move == MOVE_TOXIC) + STATUS_ICON(opponent, badPoison: TRUE); + else + STATUS_ICON(opponent, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Corrosion's effect is lost if the move used by the Pokémon with the ability is reflected by Magic Coat (Traits)") +{ + u16 move; + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISON_POWDER; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + ASSUME(GetMoveEffect(MOVE_POISON_POWDER) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_POISON_POWDER) == MOVE_EFFECT_POISON); + ASSUME(GetMoveEffect(MOVE_MAGIC_COAT) == EFFECT_MAGIC_COAT); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_CORROSION); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_MAGIC_COAT); MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MAGIC_COAT, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, move, player); + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + if (move == MOVE_TOXIC) + STATUS_ICON(opponent, badPoison: TRUE); + else + STATUS_ICON(opponent, poison: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Corrosion can poison Poison/Steel types if the Pokémon uses Baneful Bunker (Traits)") +{ + u16 species; + + PARAMETRIZE { species = SPECIES_ODDISH; } + PARAMETRIZE { species = SPECIES_BELDUM; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_BANEFUL_BUNKER) == EFFECT_PROTECT); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_CORROSION); } + OPPONENT(species); + } WHEN { + TURN { MOVE(player, MOVE_BANEFUL_BUNKER); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BANEFUL_BUNKER, player); + MESSAGE("Salandit protected itself!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + MESSAGE("Salandit protected itself!"); + STATUS_ICON(opponent, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Corrosion can poison Poison/Steel types if the Pokémon uses Psycho Shift while poisoned (Traits)") +{ + u16 species; + + PARAMETRIZE { species = SPECIES_ODDISH; } + PARAMETRIZE { species = SPECIES_BELDUM; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_PSYCHO_SHIFT) == EFFECT_PSYCHO_SHIFT); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_CORROSION); Status1(STATUS1_POISON); } + OPPONENT(species); + } WHEN { + TURN { MOVE(player, MOVE_PSYCHO_SHIFT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCHO_SHIFT, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, poison: TRUE); + STATUS_ICON(player, none: TRUE); + } +} + +SINGLE_BATTLE_TEST("Corrosion can poison Poison/Steel types if the Pokémon uses Fling while holding a Toxic Orb (Traits)") +{ + u16 species; + + PARAMETRIZE { species = SPECIES_ODDISH; } + PARAMETRIZE { species = SPECIES_BELDUM; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_FLING) == EFFECT_FLING); + ASSUME(gItemsInfo[ITEM_TOXIC_ORB].holdEffect == HOLD_EFFECT_TOXIC_ORB); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_CORROSION); Item(ITEM_TOXIC_ORB); } + OPPONENT(species); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, badPoison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Corrosion can poison Poison/Steel types if the Pokémon uses Fling while holding a Poison Barb (Traits)") +{ + u16 species; + + PARAMETRIZE { species = SPECIES_ODDISH; } + PARAMETRIZE { species = SPECIES_BELDUM; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_FLING) == EFFECT_FLING); + ASSUME(gItemsInfo[ITEM_POISON_BARB].holdEffect == HOLD_EFFECT_TYPE_POWER); + ASSUME(gItemsInfo[ITEM_POISON_BARB].secondaryId == TYPE_POISON); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_CORROSION); Item(ITEM_POISON_BARB); } + OPPONENT(species); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Corrosion does not affect Poison Spikes (Traits)") +{ + u16 species; + + PARAMETRIZE { species = SPECIES_ODDISH; } + PARAMETRIZE { species = SPECIES_BELDUM; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOXIC_SPIKES) == EFFECT_TOXIC_SPIKES); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_CORROSION); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(species); + } WHEN { + TURN { MOVE(player, MOVE_TOXIC_SPIKES); } + TURN { SWITCH(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, player); + } THEN { + EXPECT_EQ(opponent->status1, STATUS1_NONE); + } +} + +TO_DO_BATTLE_TEST("Dynamax: Corrosion can poison Poison/Steel types if the Pokémon uses G-Max Malodor (Traits)") +TO_DO_BATTLE_TEST("Corrosion does not affect Poison Spikes (Traits)") +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Corrosion can poison Poison- and Steel-type targets if it uses Fling while holding a Toxic Orb or a Poison Barb (Multi)") +{ + u16 heldItem; + + PARAMETRIZE { heldItem = ITEM_POISON_BARB; } + PARAMETRIZE { heldItem = ITEM_TOXIC_ORB; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_FLING) == EFFECT_FLING); + ASSUME(gItemsInfo[ITEM_POISON_BARB].holdEffect == HOLD_EFFECT_TYPE_POWER); + ASSUME(gItemsInfo[ITEM_POISON_BARB].secondaryId == TYPE_POISON); + ASSUME(gItemsInfo[ITEM_TOXIC_ORB].holdEffect == HOLD_EFFECT_TOXIC_ORB); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); Items(ITEM_PECHA_BERRY, heldItem); } + OPPONENT(SPECIES_ODDISH); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + if (heldItem == ITEM_POISON_BARB) + STATUS_ICON(opponent, poison: TRUE); + else + STATUS_ICON(opponent, badPoison: TRUE); + } +} + +SINGLE_BATTLE_TEST("If a Poison- or Steel-type Pokémon with Corrosion holds a Toxic Orb, it will badly poison itself (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_TOXIC_ORB].holdEffect == HOLD_EFFECT_TOXIC_ORB); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); Items(ITEM_PECHA_BERRY, ITEM_TOXIC_ORB); } + OPPONENT(SPECIES_ODDISH); + } WHEN { + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + STATUS_ICON(player, badPoison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Corrosion can poison Poison/Steel types if the Pokémon uses Fling while holding a Toxic Orb (Multi)") +{ + u16 species; + + PARAMETRIZE { species = SPECIES_ODDISH; } + PARAMETRIZE { species = SPECIES_BELDUM; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_FLING) == EFFECT_FLING); + ASSUME(gItemsInfo[ITEM_TOXIC_ORB].holdEffect == HOLD_EFFECT_TOXIC_ORB); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); Items(ITEM_PECHA_BERRY, ITEM_TOXIC_ORB); } + OPPONENT(species); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, badPoison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Corrosion can poison Poison/Steel types if the Pokémon uses Fling while holding a Poison Barb (Multi)") +{ + u16 species; + + PARAMETRIZE { species = SPECIES_ODDISH; } + PARAMETRIZE { species = SPECIES_BELDUM; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_FLING) == EFFECT_FLING); + ASSUME(gItemsInfo[ITEM_POISON_BARB].holdEffect == HOLD_EFFECT_TYPE_POWER); + ASSUME(gItemsInfo[ITEM_POISON_BARB].secondaryId == TYPE_POISON); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); Items(ITEM_PECHA_BERRY, ITEM_POISON_BARB); } + OPPONENT(species); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, poison: TRUE); + } +} +#endif diff --git a/test/battle/ability/costar.c b/test/battle/ability/costar.c index 09a6de4e9a6a..3dfcba0eecaf 100644 --- a/test/battle/ability/costar.c +++ b/test/battle/ability/costar.c @@ -176,3 +176,184 @@ DOUBLE_BATTLE_TEST("Costar's message displays correctly after all battlers faint MESSAGE("The opposing Flamigo copied the opposing Zacian's stat changes!"); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Costar copies an ally's stat stages upon entering battle (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_FLAMIGO) { Ability(ABILITY_SCRAPPY); Innates(ABILITY_COSTAR); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SWORDS_DANCE); } + TURN { SWITCH(opponentRight, 2); MOVE(playerLeft, MOVE_CELEBRATE); } + } SCENE { + // Turn 1 - buff up + MESSAGE("The opposing Wobbuffet used Swords Dance!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + // Turn 2 - Switch into Flamigo + MESSAGE("2 sent out Flamigo!"); + ABILITY_POPUP(opponentRight, ABILITY_COSTAR); + MESSAGE("The opposing Flamigo copied the opposing Wobbuffet's stat changes!"); + } THEN { + EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +DOUBLE_BATTLE_TEST("Costar copies an ally's Dragon Cheer critical hit boost (Traits)") +{ + PASSES_RANDOMLY(1, 8, RNG_CRITICAL_HIT); + GIVEN { + ASSUME(gMovesInfo[MOVE_DRAGON_CHEER].effect == EFFECT_DRAGON_CHEER); + ASSUME(gMovesInfo[MOVE_TACKLE].criticalHitStage == 0); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_FLAMIGO) { Ability(ABILITY_SCRAPPY); Innates(ABILITY_COSTAR); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, MOVE_DRAGON_CHEER, target: playerLeft); MOVE(playerLeft, MOVE_CELEBRATE); } + TURN { SWITCH(playerRight, 2); } + TURN { MOVE(playerRight, MOVE_TACKLE, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_CHEER, playerRight); + ABILITY_POPUP(playerRight, ABILITY_COSTAR); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight); + MESSAGE("A critical hit!"); + } +} + +DOUBLE_BATTLE_TEST("Costar copies an ally's lowered stat stages (Traits)") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_GROWL].effect == EFFECT_ATTACK_DOWN); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_FLAMIGO) { Ability(ABILITY_SCRAPPY); Innates(ABILITY_COSTAR); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_GROWL); MOVE(opponentRight, MOVE_CELEBRATE); } + TURN { SWITCH(playerRight, 2); MOVE(playerLeft, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, opponentLeft); + ABILITY_POPUP(playerRight, ABILITY_COSTAR); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + } +} + +DOUBLE_BATTLE_TEST("Costar copies an ally's Focus Energy critical hit boost (Traits)") +{ + PASSES_RANDOMLY(1, 2, RNG_CRITICAL_HIT); + GIVEN { + ASSUME(gMovesInfo[MOVE_FOCUS_ENERGY].effect == EFFECT_FOCUS_ENERGY); + ASSUME(gMovesInfo[MOVE_TACKLE].criticalHitStage == 0); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_FLAMIGO) { Ability(ABILITY_SCRAPPY); Innates(ABILITY_COSTAR); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SWORDS_DANCE); MOVE(playerRight, MOVE_CELEBRATE); } + TURN { MOVE(playerLeft, MOVE_FOCUS_ENERGY); MOVE(playerRight, MOVE_CELEBRATE); } + TURN { SWITCH(playerRight, 2); } + TURN { MOVE(playerRight, MOVE_TACKLE, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FOCUS_ENERGY, playerLeft); + ABILITY_POPUP(playerRight, ABILITY_COSTAR); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight); + MESSAGE("A critical hit!"); + } +} + +DOUBLE_BATTLE_TEST("Costar copies an ally's Dragon Cheer critical hit boost (Traits)") +{ + PASSES_RANDOMLY(1, 8, RNG_CRITICAL_HIT); + GIVEN { + ASSUME(gMovesInfo[MOVE_DRAGON_CHEER].effect == EFFECT_DRAGON_CHEER); + ASSUME(gMovesInfo[MOVE_TACKLE].criticalHitStage == 0); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_FLAMIGO) { Ability(ABILITY_SCRAPPY); Innates(ABILITY_COSTAR); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, MOVE_DRAGON_CHEER, target: playerLeft); MOVE(playerLeft, MOVE_SWORDS_DANCE); } + TURN { SWITCH(playerRight, 2); MOVE(playerLeft, MOVE_CELEBRATE); } + TURN { MOVE(playerRight, MOVE_TACKLE, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_CHEER, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, playerLeft); + ABILITY_POPUP(playerRight, ABILITY_COSTAR); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight); + MESSAGE("A critical hit!"); + } +} + +// Copy from Ruin ability tests +DOUBLE_BATTLE_TEST("Costar's message displays correctly after all battlers fainted - Player (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_ZACIAN) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_INTREPID_SWORD); } + PLAYER(SPECIES_FLAMIGO) { Ability(ABILITY_SCRAPPY); Innates(ABILITY_COSTAR); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_EXPLOSION); + SEND_OUT(playerLeft, 2); + SEND_OUT(opponentLeft, 2); + SEND_OUT(playerRight, 3); + SEND_OUT(opponentRight, 3); + } + } SCENE { + MESSAGE("The opposing Wobbuffet used Explosion!"); + ABILITY_POPUP(playerLeft, ABILITY_INTREPID_SWORD); + MESSAGE("Zacian's Intrepid Sword raised its Attack!"); + ABILITY_POPUP(playerRight, ABILITY_COSTAR); + MESSAGE("Flamigo copied Zacian's stat changes!"); + } +} + +DOUBLE_BATTLE_TEST("Costar's message displays correctly after all battlers fainted - Opponent (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_ZACIAN) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_INTREPID_SWORD); } + OPPONENT(SPECIES_FLAMIGO) { Ability(ABILITY_SCRAPPY); Innates(ABILITY_COSTAR); } + } WHEN { + TURN { + MOVE(playerLeft, MOVE_EXPLOSION); + SEND_OUT(playerLeft, 2); + SEND_OUT(opponentLeft, 2); + SEND_OUT(playerRight, 3); + SEND_OUT(opponentRight, 3); + } + } SCENE { + MESSAGE("Wobbuffet used Explosion!"); + ABILITY_POPUP(opponentLeft, ABILITY_INTREPID_SWORD); + MESSAGE("The opposing Zacian's Intrepid Sword raised its Attack!"); + ABILITY_POPUP(opponentRight, ABILITY_COSTAR); + MESSAGE("The opposing Flamigo copied the opposing Zacian's stat changes!"); + } +} + + +TO_DO_BATTLE_TEST("Costar can copy an ally's critical hit ratio (Traits)"); +#endif diff --git a/test/battle/ability/cotton_down.c b/test/battle/ability/cotton_down.c index 0dd02c42d9dd..f2e6c2a56204 100644 --- a/test/battle/ability/cotton_down.c +++ b/test/battle/ability/cotton_down.c @@ -96,3 +96,137 @@ DOUBLE_BATTLE_TEST("Cotton Down correctly gets blocked by stat reduction prevent EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Cotton Down drops speed by one of opposing battler if hit by a damaging move (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ELDEGOSS) { Ability(ABILITY_EFFECT_SPORE); Innates(ABILITY_COTTON_DOWN); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ABILITY_POPUP(opponent, ABILITY_COTTON_DOWN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Wobbuffet's Speed fell!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Cotton Down drops speed by one for each multi hit (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ELDEGOSS) { Ability(ABILITY_EFFECT_SPORE); Innates(ABILITY_COTTON_DOWN); } + } WHEN { + TURN { MOVE(player, MOVE_DOUBLE_KICK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_KICK, player); + ABILITY_POPUP(opponent, ABILITY_COTTON_DOWN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Wobbuffet's Speed fell!"); + ABILITY_POPUP(opponent, ABILITY_COTTON_DOWN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Wobbuffet's Speed fell!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 2); + } +} + +DOUBLE_BATTLE_TEST("Cotton Down drops speed by one of all other battlers on the field (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_ELDEGOSS) { Ability(ABILITY_EFFECT_SPORE); Innates(ABILITY_COTTON_DOWN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_COTTON_DOWN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Wobbuffet's Speed fell!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Wynaut's Speed fell!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("The opposing Wobbuffet's Speed fell!"); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(playerRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + } +} + +DOUBLE_BATTLE_TEST("Cotton Down correctly gets blocked by stat reduction preventing abilities (Traits)") +{ + GIVEN { + PLAYER(SPECIES_METAGROSS) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_CLEAR_BODY); } + PLAYER(SPECIES_WYNAUT) { Item(ITEM_CLEAR_AMULET); } + OPPONENT(SPECIES_ELDEGOSS) { Ability(ABILITY_EFFECT_SPORE); Innates(ABILITY_COTTON_DOWN); } + OPPONENT(SPECIES_CORVIKNIGHT) { Ability(ABILITY_UNNERVE); Innates(ABILITY_MIRROR_ARMOR); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_COTTON_DOWN); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Metagross's Speed fell!"); + } + ABILITY_POPUP(playerLeft, ABILITY_CLEAR_BODY); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Wynaut's Speed fell!"); + } + MESSAGE("The effects of the Clear Amulet held by Wynaut prevents its stats from being lowered!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("The opposing Corviknight's Speed fell!"); + } + ABILITY_POPUP(opponentRight, ABILITY_MIRROR_ARMOR); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(playerRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +DOUBLE_BATTLE_TEST("Cotton Down correctly gets blocked by stat reduction preventing abilities (Multi)") +{ + GIVEN { + PLAYER(SPECIES_METAGROSS) { Ability(ABILITY_CLEAR_BODY); } + PLAYER(SPECIES_WYNAUT) { Items(ITEM_PECHA_BERRY, ITEM_CLEAR_AMULET); } + OPPONENT(SPECIES_ELDEGOSS) { Ability(ABILITY_COTTON_DOWN); } + OPPONENT(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_COTTON_DOWN); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Metagross's Speed fell!"); + } + ABILITY_POPUP(playerLeft, ABILITY_CLEAR_BODY); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Wynaut's Speed fell!"); + } + MESSAGE("The effects of the Clear Amulet held by Wynaut prevents its stats from being lowered!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("The opposing Corviknight's Speed fell!"); + } + ABILITY_POPUP(opponentRight, ABILITY_MIRROR_ARMOR); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(playerRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} +#endif diff --git a/test/battle/ability/cud_chew.c b/test/battle/ability/cud_chew.c index 9f7c3902a91b..888170cc23c0 100644 --- a/test/battle/ability/cud_chew.c +++ b/test/battle/ability/cud_chew.c @@ -73,3 +73,127 @@ SINGLE_BATTLE_TEST("Cud Chew will activate Lum Berry effect again on the next tu STATUS_ICON(opponent, paralysis: FALSE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Cud Chew will activate Kee Berry effect again on the next turn (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_KEE_BERRY].holdEffect == HOLD_EFFECT_KEE_BERRY); + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TAUROS_PALDEA_COMBAT) { Ability(ABILITY_INTIMIDATE); Innates(ABILITY_CUD_CHEW); Item(ITEM_KEE_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_CELEBRATE);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ABILITY_POPUP(opponent, ABILITY_CUD_CHEW); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Cud Chew will activate Oran Berry effect again on the next turn (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffect == HOLD_EFFECT_RESTORE_HP); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffectParam == 10); + ASSUME(GetMoveEffect(MOVE_DRAGON_RAGE) == EFFECT_FIXED_HP_DAMAGE); + ASSUME(GetMoveFixedHPDamage(MOVE_DRAGON_RAGE) == 40); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TAUROS_PALDEA_COMBAT) { MaxHP(60); HP(60); Ability(ABILITY_INTIMIDATE); Innates(ABILITY_CUD_CHEW); Item(ITEM_ORAN_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_RAGE); } + TURN { MOVE(player, MOVE_CELEBRATE);} + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ABILITY_POPUP(opponent, ABILITY_CUD_CHEW); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT_EQ(opponent->hp, 40); + } +} + +SINGLE_BATTLE_TEST("Cud Chew will activate Lum Berry effect again on the next turn") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LUM_BERRY].holdEffect == HOLD_EFFECT_CURE_STATUS); + ASSUME(GetMoveEffect(MOVE_THUNDER_WAVE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_THUNDER_WAVE) == MOVE_EFFECT_PARALYSIS); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TAUROS_PALDEA_COMBAT) { Ability(ABILITY_INTIMIDATE); Innates(ABILITY_CUD_CHEW); Item(ITEM_LUM_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_THUNDER_WAVE); } + TURN { MOVE(player, MOVE_THUNDER_WAVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_WAVE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, opponent); + STATUS_ICON(opponent, paralysis: TRUE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + STATUS_ICON(opponent, paralysis: FALSE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_WAVE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, opponent); + STATUS_ICON(opponent, paralysis: TRUE); + ABILITY_POPUP(opponent, ABILITY_CUD_CHEW); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + STATUS_ICON(opponent, paralysis: FALSE); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Cud Chew will activate Kee Berry effect again on the next turn (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_KEE_BERRY].holdEffect == HOLD_EFFECT_KEE_BERRY); + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TAUROS_PALDEA_COMBAT) { Ability(ABILITY_CUD_CHEW); Items(ITEM_PECHA_BERRY, ITEM_KEE_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_CELEBRATE);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ABILITY_POPUP(opponent, ABILITY_CUD_CHEW); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Cud Chew will activate Oran Berry effect again on the next turn (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffect == HOLD_EFFECT_RESTORE_HP); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffectParam == 10); + ASSUME(GetMoveEffect(MOVE_DRAGON_RAGE) == EFFECT_FIXED_HP_DAMAGE); + ASSUME(GetMoveFixedHPDamage(MOVE_DRAGON_RAGE) == 40); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TAUROS_PALDEA_COMBAT) { MaxHP(60); HP(60); Ability(ABILITY_CUD_CHEW); Items(ITEM_PECHA_BERRY, ITEM_ORAN_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_RAGE); } + TURN { MOVE(player, MOVE_CELEBRATE);} + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ABILITY_POPUP(opponent, ABILITY_CUD_CHEW); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT_EQ(opponent->hp, 40); + } +} +#endif diff --git a/test/battle/ability/curious_medicine.c b/test/battle/ability/curious_medicine.c index 7c3e200c7a21..70c722fe608b 100644 --- a/test/battle/ability/curious_medicine.c +++ b/test/battle/ability/curious_medicine.c @@ -38,3 +38,43 @@ DOUBLE_BATTLE_TEST("Curious Medicine resets ally's stat stages upon entering bat EXPECT_EQ(opponentLeft->statStages[STAT_EVASION], DEFAULT_STAT_STAGE); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Curious Medicine resets ally's stat stages upon entering battle (Traits)") +{ + enum Ability ability; + + PARAMETRIZE { ability = ABILITY_CURIOUS_MEDICINE; } + PARAMETRIZE { ability = ABILITY_OWN_TEMPO; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SCOLIPEDE); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_SLOWKING_GALAR) { Ability(ABILITY_REGENERATOR); Innates(ability); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_QUIVER_DANCE); MOVE(playerLeft, MOVE_CHARM, target: opponentLeft); } + TURN { SWITCH(opponentRight, 2); MOVE(playerLeft, MOVE_CELEBRATE); } + } SCENE { + // Turn 1 - buff up + MESSAGE("The opposing Scolipede used Quiver Dance!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + // Turn 2 - Switch into Slowking + MESSAGE("2 sent out Slowking!"); + if (ability == ABILITY_CURIOUS_MEDICINE) + { + ABILITY_POPUP(opponentRight, ABILITY_CURIOUS_MEDICINE); + MESSAGE("The opposing Scolipede's stat changes were removed!"); + } + } THEN { + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], (ability == ABILITY_CURIOUS_MEDICINE) ? DEFAULT_STAT_STAGE : DEFAULT_STAT_STAGE - 2); + EXPECT_EQ(opponentLeft->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentLeft->statStages[STAT_SPEED], (ability == ABILITY_CURIOUS_MEDICINE) ? DEFAULT_STAT_STAGE : DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponentLeft->statStages[STAT_SPATK], (ability == ABILITY_CURIOUS_MEDICINE) ? DEFAULT_STAT_STAGE : DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponentLeft->statStages[STAT_SPDEF], (ability == ABILITY_CURIOUS_MEDICINE) ? DEFAULT_STAT_STAGE : DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponentLeft->statStages[STAT_ACC], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentLeft->statStages[STAT_EVASION], DEFAULT_STAT_STAGE); + } +} +#endif diff --git a/test/battle/ability/cursed_body.c b/test/battle/ability/cursed_body.c index 82d2606ef135..6ea6bcc0f840 100644 --- a/test/battle/ability/cursed_body.c +++ b/test/battle/ability/cursed_body.c @@ -130,3 +130,157 @@ SINGLE_BATTLE_TEST("Cursed Body disables the base move of a status Z-Move") } TO_DO_BATTLE_TEST("Cursed Body disables damaging Z-Moves, but not the base move") + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Cursed Body triggers 30% of the time (Traits)") +{ + PASSES_RANDOMLY(3, 10, RNG_CURSED_BODY); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_FRILLISH) { Ability(ABILITY_DAMP); Innates(ABILITY_CURSED_BODY); } + } WHEN { + TURN { MOVE(player, MOVE_AQUA_JET); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_JET, player); + ABILITY_POPUP(opponent, ABILITY_CURSED_BODY); + MESSAGE("Wobbuffet's Aqua Jet was disabled by the opposing Frillish's Cursed Body!"); + } +} + +SINGLE_BATTLE_TEST("Cursed Body cannot disable Struggle (Traits)") +{ + GIVEN { + ASSUME(GetItemHoldEffect(ITEM_CHOICE_SCARF) == HOLD_EFFECT_CHOICE_SCARF); + ASSUME(GetMoveEffect(MOVE_TAUNT) == EFFECT_TAUNT); + ASSUME(GetMoveCategory(MOVE_CELEBRATE) == DAMAGE_CATEGORY_STATUS); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_CHOICE_SCARF); Moves(MOVE_CELEBRATE); } + OPPONENT(SPECIES_FRILLISH) { Ability(ABILITY_DAMP); Innates(ABILITY_CURSED_BODY); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_TAUNT); } + TURN { FORCED_MOVE(player); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_CURSED_BODY); + MESSAGE("Wobbuffet's Struggle was disabled by the opposing Frillish's Cursed Body!"); + } + } +} + +SINGLE_BATTLE_TEST("Cursed Body can trigger if the attacker is behind a Substitute (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SUBSTITUTE) == EFFECT_SUBSTITUTE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_FRILLISH) { Ability(ABILITY_DAMP); Innates(ABILITY_CURSED_BODY); } + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); } + TURN { MOVE(player, MOVE_AQUA_JET); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_JET, player); + ABILITY_POPUP(opponent, ABILITY_CURSED_BODY); + MESSAGE("Wobbuffet's Aqua Jet was disabled by the opposing Frillish's Cursed Body!"); + } +} + +SINGLE_BATTLE_TEST("Cursed Body cannot trigger if the target is behind a Substitute (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SUBSTITUTE) == EFFECT_SUBSTITUTE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_FRILLISH) { Ability(ABILITY_DAMP); Innates(ABILITY_CURSED_BODY); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); } + TURN { MOVE(player, MOVE_AQUA_JET); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_JET, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_CURSED_BODY); + MESSAGE("Wobbuffet's Aqua Jet was disabled by the opposing Frillish's Cursed Body!"); + } + } +} + +SINGLE_BATTLE_TEST("Cursed Body does not stop a multistrike move mid-execution (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ROCK_BLAST) == EFFECT_MULTI_HIT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_FRILLISH) { Ability(ABILITY_DAMP); Innates(ABILITY_CURSED_BODY); } + } WHEN { + TURN { MOVE(player, MOVE_ROCK_BLAST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROCK_BLAST, player); + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_CURSED_BODY); + MESSAGE("Wobbuffet's Rock Blast was disabled by the opposing Frillish's Cursed Body!"); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Cursed Body disables the move that called another move instead of the called move (Traits)") +{ + PASSES_RANDOMLY(3, 10, RNG_CURSED_BODY); + GIVEN { + ASSUME(GetMoveEffect(MOVE_SLEEP_TALK) == EFFECT_SLEEP_TALK); + ASSUME(GetMoveType(MOVE_SHADOW_BALL) == TYPE_GHOST); + ASSUME(IsMoveSleepTalkBanned(MOVE_FLY)); + ASSUME(IsMoveSleepTalkBanned(MOVE_DIG)); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); Moves(MOVE_SLEEP_TALK, MOVE_SHADOW_BALL, MOVE_FLY, MOVE_DIG); } + OPPONENT(SPECIES_FRILLISH) { Ability(ABILITY_DAMP); Innates(ABILITY_CURSED_BODY); } + } WHEN { + TURN { MOVE(player, MOVE_SLEEP_TALK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SLEEP_TALK, player); + ABILITY_POPUP(opponent, ABILITY_CURSED_BODY); + MESSAGE("Wobbuffet's Sleep Talk was disabled by the opposing Frillish's Cursed Body!"); + } THEN { + EXPECT_EQ(gDisableStructs[B_POSITION_PLAYER_LEFT].disabledMove, MOVE_SLEEP_TALK); + } +} + +SINGLE_BATTLE_TEST("Cursed Body disables the base move of a status Z-Move (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURE_POWER) == EFFECT_NATURE_POWER); + ASSUME(GetMoveEffect(MOVE_ELECTRIC_TERRAIN) == EFFECT_ELECTRIC_TERRAIN); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_FRILLISH) { Ability(ABILITY_DAMP); Innates(ABILITY_CURSED_BODY); } + } WHEN { + TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_NATURE_POWER, gimmick: GIMMICK_Z_MOVE, WITH_RNG(RNG_CURSED_BODY, 1)); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_CURSED_BODY); + MESSAGE("Wobbuffet's Nature Power was disabled by the opposing Frillish's Cursed Body!"); + } THEN { + EXPECT_EQ(gDisableStructs[B_POSITION_PLAYER_LEFT].disabledMove, MOVE_NATURE_POWER); + } +} + +TO_DO_BATTLE_TEST("Cursed Body disables damaging Z-Moves, but not the base move (Traits)") +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Cursed Body cannot disable Struggle (Multi)") +{ + GIVEN { + ASSUME(GetItemHoldEffect(ITEM_CHOICE_SCARF) == HOLD_EFFECT_CHOICE_SCARF); + ASSUME(GetMoveEffect(MOVE_TAUNT) == EFFECT_TAUNT); + ASSUME(GetMoveCategory(MOVE_CELEBRATE) == DAMAGE_CATEGORY_STATUS); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_CHOICE_SCARF); Moves(MOVE_CELEBRATE); } + OPPONENT(SPECIES_FRILLISH) { Ability(ABILITY_CURSED_BODY); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_TAUNT); } + TURN { FORCED_MOVE(player); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_CURSED_BODY); + MESSAGE("Wobbuffet's Struggle was disabled by the opposing Frillish's Cursed Body!"); + } + } +} +#endif diff --git a/test/battle/ability/cute_charm.c b/test/battle/ability/cute_charm.c index d2395fda17f4..f5bb2206789d 100644 --- a/test/battle/ability/cute_charm.c +++ b/test/battle/ability/cute_charm.c @@ -69,3 +69,73 @@ SINGLE_BATTLE_TEST("Cute Charm triggers 1/3 times (Gen3) or 30% (Gen 4+) of the MESSAGE("Wobbuffet is in love with the opposing Clefairy!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Cute Charm inflicts infatuation on contact (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_SWIFT; } + GIVEN { + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + ASSUME(!MoveMakesContact(MOVE_SWIFT)); + PLAYER(SPECIES_WOBBUFFET) { Gender(MON_MALE); } + OPPONENT(SPECIES_CLEFAIRY) { Gender(MON_FEMALE); Ability(ABILITY_FRIEND_GUARD); Innates(ABILITY_CUTE_CHARM); } + } WHEN { + TURN { MOVE(player, move); } + TURN { MOVE(player, move); } + } SCENE { + if (MoveMakesContact(move)) { + ABILITY_POPUP(opponent, ABILITY_CUTE_CHARM); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_INFATUATION, player); + MESSAGE("The opposing Clefairy's Cute Charm infatuated Wobbuffet!"); + MESSAGE("Wobbuffet is in love with the opposing Clefairy!"); + } else { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_CUTE_CHARM); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_INFATUATION, player); + MESSAGE("The opposing Clefairy's Cute Charm infatuated Wobbuffet!"); + MESSAGE("Wobbuffet is in love with the opposing Clefairy!"); + } + } + } +} + +SINGLE_BATTLE_TEST("Cute Charm cannot infatuate same gender (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Gender(MON_MALE); } + OPPONENT(SPECIES_CLEFAIRY) { Gender(MON_MALE); Ability(ABILITY_FRIEND_GUARD); Innates(ABILITY_CUTE_CHARM); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + NOT ABILITY_POPUP(opponent, ABILITY_CUTE_CHARM); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + } +} + +TO_DO_BATTLE_TEST("Cute Charm cannot infatuate if either Pokémon are Gender-unknown (Traits)") + +TO_DO_BATTLE_TEST("Cute Charm triggers 1/3 of the time (Gen 3) (Traits)") + +SINGLE_BATTLE_TEST("Cute Charm triggers 30% of the time (Gen 4+) (Traits)") +{ + PASSES_RANDOMLY(3, 10, RNG_CUTE_CHARM); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_4); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_WOBBUFFET) { Gender(MON_MALE); } + OPPONENT(SPECIES_CLEFAIRY) { Gender(MON_FEMALE); Ability(ABILITY_FRIEND_GUARD); Innates(ABILITY_CUTE_CHARM); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_CUTE_CHARM); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_INFATUATION, player); + MESSAGE("The opposing Clefairy's Cute Charm infatuated Wobbuffet!"); + MESSAGE("Wobbuffet is in love with the opposing Clefairy!"); + } +} +#endif diff --git a/test/battle/ability/damp.c b/test/battle/ability/damp.c index 816588f4974e..2adbc1d05eb9 100644 --- a/test/battle/ability/damp.c +++ b/test/battle/ability/damp.c @@ -74,3 +74,77 @@ SINGLE_BATTLE_TEST("Damp prevents damage from Aftermath") } //TO_DO_BATTLE_TEST("Damp affects non-adjacent Pokémon (triples)") + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Damp prevents explosion-like moves from enemies (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_EXPLOSION; } + PARAMETRIZE { move = MOVE_SELF_DESTRUCT; } + PARAMETRIZE { move = MOVE_MIND_BLOWN; } + PARAMETRIZE { move = MOVE_MISTY_EXPLOSION; } + GIVEN { + PLAYER(SPECIES_PARAS) { Ability(ABILITY_DRY_SKIN); Innates(ABILITY_DAMP); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DAMP); + NONE_OF { HP_BAR(player); HP_BAR(opponent); } + } +} + +DOUBLE_BATTLE_TEST("Damp prevents explosion-like moves from enemies in a double battle (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_EXPLOSION; } + PARAMETRIZE { move = MOVE_SELF_DESTRUCT; } + PARAMETRIZE { move = MOVE_MIND_BLOWN; } + PARAMETRIZE { move = MOVE_MISTY_EXPLOSION; } + GIVEN { + PLAYER(SPECIES_PARAS) { Ability(ABILITY_DRY_SKIN); Innates(ABILITY_DAMP); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, move); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_DAMP); + NONE_OF { HP_BAR(playerLeft); HP_BAR(opponentLeft); HP_BAR(playerRight); HP_BAR(opponentRight); } + } +} + +SINGLE_BATTLE_TEST("Damp prevents explosion-like moves from self (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_EXPLOSION; } + PARAMETRIZE { move = MOVE_SELF_DESTRUCT; } + PARAMETRIZE { move = MOVE_MIND_BLOWN; } + PARAMETRIZE { move = MOVE_MISTY_EXPLOSION; } + GIVEN { + PLAYER(SPECIES_PARAS) { Ability(ABILITY_DRY_SKIN); Innates(ABILITY_DAMP); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DAMP); + NONE_OF { HP_BAR(player); HP_BAR(opponent); } + } +} + +SINGLE_BATTLE_TEST("Damp prevents damage from Aftermath (Traits)") +{ + GIVEN { + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_PARAS) { Ability(ABILITY_DRY_SKIN); Innates(ABILITY_DAMP); } + OPPONENT(SPECIES_VOLTORB) { Ability(ABILITY_SOUNDPROOF); Innates(ABILITY_AFTERMATH); HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); SEND_OUT(opponent, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_AFTERMATH); + ABILITY_POPUP(player, ABILITY_DAMP); + NONE_OF { HP_BAR(player); } + } +} +#endif diff --git a/test/battle/ability/dancer.c b/test/battle/ability/dancer.c index 075a3e46a597..d4130ab9f9bb 100644 --- a/test/battle/ability/dancer.c +++ b/test/battle/ability/dancer.c @@ -804,3 +804,630 @@ DOUBLE_BATTLE_TEST("Dancer doesn't activate Feather Dance if it was reflected by EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 2); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Dancer can copy a dance move immediately after it was used and allow the user of Dancer to still use its move (Traits)") +{ + GIVEN { + ASSUME(IsDanceMove(MOVE_QUIVER_DANCE)); + PLAYER(SPECIES_WOBBUFFET) + OPPONENT(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); } + } WHEN { + TURN { MOVE(player, MOVE_QUIVER_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUIVER_DANCE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ABILITY_POPUP(opponent, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUIVER_DANCE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); // Same turn + } +} + +SINGLE_BATTLE_TEST("Dancer can copy Teeter Dance (Traits)") +{ + GIVEN { + ASSUME(IsDanceMove(MOVE_TEETER_DANCE)); + PLAYER(SPECIES_WOBBUFFET) + OPPONENT(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); Item(ITEM_LUM_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TEETER_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEETER_DANCE, player); + ABILITY_POPUP(opponent, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEETER_DANCE, opponent); + } +} + +DOUBLE_BATTLE_TEST("Dancer can copy Teeter Dance and confuse both opposing targets (Traits)") +{ + GIVEN { + ASSUME(IsDanceMove(MOVE_TEETER_DANCE)); + ASSUME(gItemsInfo[ITEM_LUM_BERRY].holdEffect == HOLD_EFFECT_CURE_STATUS); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { Item(ITEM_LUM_BERRY); } + OPPONENT(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); Item(ITEM_LUM_BERRY); } + OPPONENT(SPECIES_SLOWPOKE) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_OWN_TEMPO); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_TEETER_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEETER_DANCE, playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEETER_DANCE, opponentLeft); + MESSAGE("Wobbuffet became confused!"); + MESSAGE("Wynaut became confused!"); + } +} + +DOUBLE_BATTLE_TEST("Dancer triggers from slowest to fastest (Traits)") +{ + GIVEN { + ASSUME(IsDanceMove(MOVE_DRAGON_DANCE)); + PLAYER(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); Speed(10); } + PLAYER(SPECIES_WYNAUT) { Speed(50); } + OPPONENT(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); Speed(20); } + OPPONENT(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); Speed(3); } + } WHEN { + TURN { MOVE(playerRight, MOVE_DRAGON_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, playerRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + ABILITY_POPUP(opponentRight, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, opponentRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + ABILITY_POPUP(playerLeft, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + } +} + +SINGLE_BATTLE_TEST("Dancer doesn't trigger if the original user flinches (Traits)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_FAKE_OUT, MOVE_EFFECT_FLINCH, 100)); + ASSUME(IsDanceMove(MOVE_DRAGON_DANCE)); + PLAYER(SPECIES_WOBBUFFET) + OPPONENT(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); } + } WHEN { + TURN { MOVE(opponent, MOVE_FAKE_OUT); MOVE(player, MOVE_DRAGON_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FAKE_OUT, opponent); + MESSAGE("Wobbuffet flinched and couldn't move!"); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, opponent); + } + } +} + +DOUBLE_BATTLE_TEST("Dancer still triggers if another dancer flinches (Traits)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_FAKE_OUT, MOVE_EFFECT_FLINCH, 100)); + ASSUME(IsDanceMove(MOVE_DRAGON_DANCE)); + PLAYER(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); Speed(10); } + PLAYER(SPECIES_WYNAUT) { Speed(5); } + OPPONENT(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); Speed(20); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(3); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_FAKE_OUT, target: playerLeft); MOVE(playerRight, MOVE_DRAGON_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FAKE_OUT, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, playerRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + ABILITY_POPUP(playerLeft, ABILITY_DANCER); + MESSAGE("Oricorio flinched and couldn't move!"); + NONE_OF { + MESSAGE("Oricorio used Dragon Dance!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + } + ABILITY_POPUP(opponentLeft, ABILITY_DANCER); + MESSAGE("The opposing Oricorio used Dragon Dance!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + } +} + +DOUBLE_BATTLE_TEST("Dancer still activates after Red Card (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) ; + PLAYER(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); } + PLAYER(SPECIES_CHANSEY); + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_SHUCKLE); + } WHEN { + TURN { MOVE(playerLeft, MOVE_FIERY_DANCE, target: opponentLeft); } + } SCENE { + MESSAGE("Wobbuffet used Fiery Dance!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIERY_DANCE, playerLeft); + HP_BAR(opponentLeft); + // Red card trigger + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + MESSAGE("The opposing Wobbuffet held up its Red Card against Wobbuffet!"); + MESSAGE("Chansey was dragged out!"); + // Dancer + ABILITY_POPUP(playerRight, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIERY_DANCE, playerRight); + HP_BAR(opponentLeft); + } +} + +DOUBLE_BATTLE_TEST("Dancer still activate after Red Card even if blocked by Suction Cups (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_SUCTION_CUPS); } + PLAYER(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); } + PLAYER(SPECIES_CHANSEY); + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_SHUCKLE); + } WHEN { + TURN { MOVE(playerLeft, MOVE_FIERY_DANCE, target: opponentLeft); } + } SCENE { + MESSAGE("Wobbuffet used Fiery Dance!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIERY_DANCE, playerLeft); + HP_BAR(opponentLeft); + // red card trigger + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + MESSAGE("The opposing Wobbuffet held up its Red Card against Wobbuffet!"); + MESSAGE("Wobbuffet anchors itself with Suction Cups!"); + NOT MESSAGE("Chansey was dragged out!"); + // Dancer + ABILITY_POPUP(playerRight, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIERY_DANCE, playerRight); + HP_BAR(opponentLeft); + } +} + +DOUBLE_BATTLE_TEST("Dancer-called damaging moves are considered for Counter/Mirror Coat/Metal Burst (Traits)") +{ + u32 danceMove, retaliateMove; + + PARAMETRIZE { danceMove = MOVE_AQUA_STEP; retaliateMove = MOVE_COUNTER; } + PARAMETRIZE { danceMove = MOVE_FIERY_DANCE; retaliateMove = MOVE_MIRROR_COAT; } + PARAMETRIZE { danceMove = MOVE_FIERY_DANCE; retaliateMove = MOVE_METAL_BURST; } + + GIVEN { + ASSUME(IsDanceMove(danceMove)); + if (retaliateMove == MOVE_COUNTER) + ASSUME(GetMoveCategory(danceMove) == DAMAGE_CATEGORY_PHYSICAL); + else if (retaliateMove == MOVE_MIRROR_COAT) + ASSUME(GetMoveCategory(danceMove) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_WOBBUFFET) { Speed(30); } + PLAYER(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); Speed(20); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } + } WHEN { + TURN { MOVE(playerLeft, danceMove, target: opponentLeft); MOVE(opponentLeft, retaliateMove); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, danceMove, playerLeft); + HP_BAR(opponentLeft); + ABILITY_POPUP(playerRight, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, danceMove, playerRight); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, retaliateMove, opponentLeft); + HP_BAR(playerRight); + NOT HP_BAR(playerLeft); + } +} + +SINGLE_BATTLE_TEST("Dancer copies a status Z-Move's base move without gaining an additional Z-Power effect (Traits)") +{ + GIVEN { + ASSUME(IsDanceMove(MOVE_SWORDS_DANCE)); + ASSUME(GetMoveEffect(MOVE_SCREECH) == EFFECT_DEFENSE_DOWN_2); + ASSUME(GetMoveZEffect(MOVE_SWORDS_DANCE) == Z_EFFECT_RESET_STATS); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); } + } WHEN { + TURN { MOVE(player, MOVE_SCREECH); } + TURN { MOVE(player, MOVE_SWORDS_DANCE, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCREECH, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ABILITY_POPUP(opponent, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 2); + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Dancer user may hit itself in confusion instead of copying a move if it's confused (Traits)") +{ + u32 genConfig, pctChance; + + PARAMETRIZE { genConfig = GEN_6; pctChance = 50; } + PARAMETRIZE { genConfig = GEN_7; pctChance = 33; } + PASSES_RANDOMLY(pctChance, 100, RNG_CONFUSION); + GIVEN { + WITH_CONFIG(CONFIG_CONFUSION_SELF_DMG_CHANCE, genConfig); + ASSUME(IsDanceMove(MOVE_DRAGON_DANCE)); + ASSUME(GetMoveEffect(MOVE_CONFUSE_RAY) == EFFECT_CONFUSE); + PLAYER(SPECIES_WOBBUFFET) { Speed(30); } + OPPONENT(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); Speed(10); } + } WHEN { + TURN { MOVE(player, MOVE_CONFUSE_RAY); } + TURN { MOVE(player, MOVE_DRAGON_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, player); + MESSAGE("The opposing Oricorio became confused!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ABILITY_POPUP(opponent, ABILITY_DANCER); + MESSAGE("The opposing Oricorio is confused!"); + MESSAGE("It hurt itself in its confusion!"); + HP_BAR(opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } + } +} + +SINGLE_BATTLE_TEST("Dancer can still copy a move even if it's being forced into a different move - Rampage move (Traits)") +{ + GIVEN { + ASSUME(IsDanceMove(MOVE_SWORDS_DANCE)); + ASSUME(IsDanceMove(MOVE_PETAL_DANCE)); + ASSUME(MoveHasAdditionalEffectSelf(MOVE_PETAL_DANCE, MOVE_EFFECT_THRASH)); + PLAYER(SPECIES_WOBBUFFET) { Speed(30); } + OPPONENT(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); Speed(10); } + } WHEN { + TURN { MOVE(opponent, MOVE_PETAL_DANCE); } + TURN { MOVE(player, MOVE_SWORDS_DANCE); FORCED_MOVE(opponent); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PETAL_DANCE, opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ABILITY_POPUP(opponent, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Dancer can still copy a move even if it's being forced into a different move - Rollout (Traits)") +{ + GIVEN { + ASSUME(IsDanceMove(MOVE_SWORDS_DANCE)); + ASSUME(GetMoveEffect(MOVE_ROLLOUT) == EFFECT_ROLLOUT); + PLAYER(SPECIES_WOBBUFFET) { Speed(30); } + OPPONENT(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); Speed(10); } + } WHEN { + TURN { MOVE(opponent, MOVE_ROLLOUT); } + TURN { MOVE(player, MOVE_SWORDS_DANCE); FORCED_MOVE(opponent); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROLLOUT, opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ABILITY_POPUP(opponent, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Dancer can still copy a move even if it's being forced into a different move - Choice items (Traits)") +{ + GIVEN { + ASSUME(IsDanceMove(MOVE_SWORDS_DANCE)); + ASSUME(GetItemHoldEffect(ITEM_CHOICE_BAND) == HOLD_EFFECT_CHOICE_BAND); + PLAYER(SPECIES_WOBBUFFET) { Speed(30); } + OPPONENT(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); Speed(10); Item(ITEM_CHOICE_BAND); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SWORDS_DANCE); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ABILITY_POPUP(opponent, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Dancer can still copy a move even if it's being forced into a different move - Encore (Traits)") +{ + GIVEN { + ASSUME(IsDanceMove(MOVE_SWORDS_DANCE)); + ASSUME(GetMoveEffect(MOVE_ENCORE) == EFFECT_ENCORE); + PLAYER(SPECIES_WOBBUFFET) { Speed(30); } + OPPONENT(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); Speed(10); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_ENCORE, target: opponent); MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SWORDS_DANCE); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ENCORE, player); + MESSAGE("The opposing Oricorio must do an encore!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ABILITY_POPUP(opponent, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Dancer tries to copy a status move but fails if it's under Taunt's effect (Traits)") +{ + GIVEN { + ASSUME(IsDanceMove(MOVE_SWORDS_DANCE)); + ASSUME(GetMoveEffect(MOVE_TAUNT) == EFFECT_TAUNT); + PLAYER(SPECIES_WOBBUFFET) { Speed(30); } + OPPONENT(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); Speed(10); } + } WHEN { + TURN { MOVE(player, MOVE_TAUNT); } + TURN { MOVE(player, MOVE_SWORDS_DANCE); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAUNT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ABILITY_POPUP(opponent, ABILITY_DANCER); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Dancer can still copy status moves if the user is holding an Assault Vest (Traits)") +{ + GIVEN { + ASSUME(IsDanceMove(MOVE_SWORDS_DANCE)); + ASSUME(GetItemHoldEffect(ITEM_ASSAULT_VEST) == HOLD_EFFECT_ASSAULT_VEST); + PLAYER(SPECIES_WOBBUFFET) { Speed(30); } + OPPONENT(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); Speed(10); Item(ITEM_ASSAULT_VEST); } + } WHEN { + TURN { MOVE(player, MOVE_SWORDS_DANCE); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ABILITY_POPUP(opponent, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +DOUBLE_BATTLE_TEST("Dancer copies Lunar Dance after the original user faints, but before the replacement is sent out (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_HEALING_WISH_SWITCH, GEN_7); + ASSUME(GetMoveEffect(MOVE_LUNAR_DANCE) == EFFECT_LUNAR_DANCE); + PLAYER(SPECIES_WOBBUFFET) { Speed(50); } + PLAYER(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); Speed(20); } + PLAYER(SPECIES_WYNAUT) { Speed(5); } + PLAYER(SPECIES_CHANSEY) { Speed(5); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_LUNAR_DANCE); SEND_OUT(playerLeft, 2); SEND_OUT(playerRight, 3); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LUNAR_DANCE, playerLeft); + HP_BAR(playerLeft, hp: 0); + MESSAGE("Wobbuffet fainted!"); + ABILITY_POPUP(playerRight, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_LUNAR_DANCE, playerRight); + HP_BAR(playerRight, hp: 0); + MESSAGE("Oricorio fainted!"); + SEND_IN_MESSAGE("Wynaut"); + SEND_IN_MESSAGE("Chansey"); + } +} + +DOUBLE_BATTLE_TEST("Dancer doesn't activate Feather Dance if it was reflected by Magic Bounce/Coat (Traits)") +{ + bool32 useMagicCoat; + + PARAMETRIZE { useMagicCoat = FALSE; } + PARAMETRIZE { useMagicCoat = TRUE; } + GIVEN { + ASSUME(IsDanceMove(MOVE_FEATHER_DANCE)); + ASSUME(GetMoveEffect(MOVE_MAGIC_COAT) == EFFECT_MAGIC_COAT); + PLAYER(SPECIES_WOBBUFFET) { Speed(20); } + PLAYER(SPECIES_WOBBUFFET) { Speed(10); } + if (useMagicCoat) + OPPONENT(SPECIES_WOBBUFFET) { Speed(30); } + else + OPPONENT(SPECIES_ESPEON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_MAGIC_BOUNCE); Speed(30); } + OPPONENT(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); Speed(5); } + } WHEN { + if (useMagicCoat) + TURN { MOVE(opponentLeft, MOVE_MAGIC_COAT); MOVE(playerLeft, MOVE_FEATHER_DANCE, target: opponentLeft); } + else + TURN { MOVE(playerLeft, MOVE_FEATHER_DANCE, target: opponentLeft); } + } SCENE { + if (useMagicCoat) + ANIMATION(ANIM_TYPE_MOVE, MOVE_MAGIC_COAT, opponentLeft); + else + ABILITY_POPUP(opponentLeft, ABILITY_MAGIC_BOUNCE); + NONE_OF { + ABILITY_POPUP(opponentRight, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FEATHER_DANCE, opponentRight); + } + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 2); + } +} + +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Dancer can copy Teeter Dance (Multi)") +{ + GIVEN { + ASSUME(IsDanceMove(MOVE_TEETER_DANCE)); + PLAYER(SPECIES_WOBBUFFET) + OPPONENT(SPECIES_ORICORIO) { Ability(ABILITY_DANCER); Items(ITEM_PECHA_BERRY, ITEM_LUM_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TEETER_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEETER_DANCE, player); + ABILITY_POPUP(opponent, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEETER_DANCE, opponent); + } +} + +DOUBLE_BATTLE_TEST("Dancer can copy Teeter Dance and confuse both opposing targets (Multi)") +{ + GIVEN { + ASSUME(IsDanceMove(MOVE_TEETER_DANCE)); + ASSUME(gItemsInfo[ITEM_LUM_BERRY].holdEffect == HOLD_EFFECT_CURE_STATUS); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { Items(ITEM_PECHA_BERRY, ITEM_LUM_BERRY); } + OPPONENT(SPECIES_ORICORIO) { Ability(ABILITY_DANCER); Items(ITEM_PECHA_BERRY, ITEM_LUM_BERRY); } + OPPONENT(SPECIES_SLOWPOKE) { Ability(ABILITY_OWN_TEMPO); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_TEETER_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEETER_DANCE, playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEETER_DANCE, opponentLeft); + MESSAGE("Wobbuffet became confused!"); + MESSAGE("Wynaut became confused!"); + } +} + +SINGLE_BATTLE_TEST("Dancer-called attacks do not trigger Life Orb if target is immune (Multi)") +{ + GIVEN { + ASSUME(IsDanceMove(MOVE_REVELATION_DANCE)); + ASSUME(GetMoveEffect(MOVE_REVELATION_DANCE) == EFFECT_REVELATION_DANCE); + ASSUME(GetMoveEffect(MOVE_ROOST) == EFFECT_ROOST); + ASSUME(GetItemHoldEffect(ITEM_LIFE_ORB) == HOLD_EFFECT_LIFE_ORB); + ASSUME(GetSpeciesType(SPECIES_ORICORIO_POM_POM, 0) == TYPE_ELECTRIC || GetSpeciesType(SPECIES_ORICORIO_POM_POM, 1) == TYPE_ELECTRIC); + PLAYER(SPECIES_RAICHU) { Ability(ABILITY_LIGHTNING_ROD); } + OPPONENT(SPECIES_ORICORIO_POM_POM) { Ability(ABILITY_DANCER); Items(ITEM_PECHA_BERRY, ITEM_LIFE_ORB); } + } WHEN { + TURN { MOVE(opponent, MOVE_ROOST); MOVE(player, MOVE_REVELATION_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_REVELATION_DANCE, player); + ABILITY_POPUP(opponent, ABILITY_DANCER); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_REVELATION_DANCE, opponent); + ABILITY_POPUP(player, ABILITY_LIGHTNING_ROD); + NOT HP_BAR(opponent); + } +} + +DOUBLE_BATTLE_TEST("Dancer still activates after Red Card (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) ; + PLAYER(SPECIES_ORICORIO) { Ability(ABILITY_DANCER); } + PLAYER(SPECIES_CHANSEY); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_SHUCKLE); + } WHEN { + TURN { MOVE(playerLeft, MOVE_FIERY_DANCE, target: opponentLeft); } + } SCENE { + MESSAGE("Wobbuffet used Fiery Dance!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIERY_DANCE, playerLeft); + HP_BAR(opponentLeft); + // Red card trigger + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + MESSAGE("The opposing Wobbuffet held up its Red Card against Wobbuffet!"); + MESSAGE("Chansey was dragged out!"); + // Dancer + ABILITY_POPUP(playerRight, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIERY_DANCE, playerRight); + HP_BAR(opponentLeft); + } +} + +DOUBLE_BATTLE_TEST("Dancer still activate after Red Card even if blocked by Suction Cups (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SUCTION_CUPS); } + PLAYER(SPECIES_ORICORIO) { Ability(ABILITY_DANCER); } + PLAYER(SPECIES_CHANSEY); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_SHUCKLE); + } WHEN { + TURN { MOVE(playerLeft, MOVE_FIERY_DANCE, target: opponentLeft); } + } SCENE { + MESSAGE("Wobbuffet used Fiery Dance!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIERY_DANCE, playerLeft); + HP_BAR(opponentLeft); + // red card trigger + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + MESSAGE("The opposing Wobbuffet held up its Red Card against Wobbuffet!"); + MESSAGE("Wobbuffet anchors itself with Suction Cups!"); + NOT MESSAGE("Chansey was dragged out!"); + // Dancer + ABILITY_POPUP(playerRight, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIERY_DANCE, playerRight); + HP_BAR(opponentLeft); + } +} + +SINGLE_BATTLE_TEST("Dancer can still copy a move even if it's being forced into a different move - Choice items (Multi)") +{ + GIVEN { + ASSUME(IsDanceMove(MOVE_SWORDS_DANCE)); + ASSUME(GetItemHoldEffect(ITEM_CHOICE_BAND) == HOLD_EFFECT_CHOICE_BAND); + PLAYER(SPECIES_WOBBUFFET) { Speed(30); } + OPPONENT(SPECIES_ORICORIO) { Ability(ABILITY_DANCER); Speed(10); Items(ITEM_NUGGET, ITEM_CHOICE_BAND); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SWORDS_DANCE); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ABILITY_POPUP(opponent, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Dancer can still copy status moves if the user is holding an Assault Vest (Multi)") +{ + GIVEN { + ASSUME(IsDanceMove(MOVE_SWORDS_DANCE)); + ASSUME(GetItemHoldEffect(ITEM_ASSAULT_VEST) == HOLD_EFFECT_ASSAULT_VEST); + PLAYER(SPECIES_WOBBUFFET) { Speed(30); } + OPPONENT(SPECIES_ORICORIO) { Ability(ABILITY_DANCER); Speed(10); Items(ITEM_NUGGET, ITEM_ASSAULT_VEST); } + } WHEN { + TURN { MOVE(player, MOVE_SWORDS_DANCE); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ABILITY_POPUP(opponent, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +#endif diff --git a/test/battle/ability/dark_aura.c b/test/battle/ability/dark_aura.c index 1371975a2884..537738e99966 100644 --- a/test/battle/ability/dark_aura.c +++ b/test/battle/ability/dark_aura.c @@ -106,3 +106,109 @@ DOUBLE_BATTLE_TEST("Dark Aura's effect doesn't stack multiple times") EXPECT_EQ(damage[5], damage[2]); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Dark Aura increases the power of all Dark-type attacks by 33% (Traits)") +{ + s16 damage[8]; + + GIVEN { + PLAYER(SPECIES_YVELTAL) { Ability(ABILITY_LIGHT_METAL); } + PLAYER(SPECIES_YVELTAL) { Ability(ABILITY_LIGHT_METAL); } + PLAYER(SPECIES_YVELTAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DARK_AURA);} + OPPONENT(SPECIES_YVELTAL) { Ability(ABILITY_LIGHT_METAL); } + OPPONENT(SPECIES_YVELTAL) { Ability(ABILITY_LIGHT_METAL); } + } WHEN { + TURN { + MOVE(playerLeft, MOVE_BITE, target:opponentLeft, secondaryEffect:FALSE); + MOVE(playerRight, MOVE_BITE, target:opponentRight, secondaryEffect:FALSE); + MOVE(opponentLeft, MOVE_BITE, target:playerLeft, secondaryEffect:FALSE); + MOVE(opponentRight, MOVE_BITE, target:playerRight, secondaryEffect:FALSE); + } + TURN { SWITCH(playerRight, 2); } + TURN { + MOVE(playerLeft, MOVE_BITE, target:opponentLeft, secondaryEffect:FALSE); + MOVE(playerRight, MOVE_BITE, target:opponentRight, secondaryEffect:FALSE); + MOVE(opponentLeft, MOVE_BITE, target:playerLeft, secondaryEffect:FALSE); + MOVE(opponentRight, MOVE_BITE, target:playerRight, secondaryEffect:FALSE); + } + } SCENE { + // Turn 1 + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, playerRight); + HP_BAR(opponentRight, captureDamage: &damage[1]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, opponentLeft); + HP_BAR(playerLeft, captureDamage: &damage[2]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, opponentRight); + HP_BAR(playerRight, captureDamage: &damage[3]); + + // Turn 2 + ABILITY_POPUP(playerRight, ABILITY_DARK_AURA); + + // Turn 3 + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damage[4]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, playerRight); + HP_BAR(opponentRight, captureDamage: &damage[5]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, opponentLeft); + HP_BAR(playerLeft, captureDamage: &damage[6]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, opponentRight); + HP_BAR(playerRight, captureDamage: &damage[7]); + } THEN { + EXPECT_MUL_EQ(damage[0], UQ_4_12(1.33), damage[4]); + EXPECT_MUL_EQ(damage[1], UQ_4_12(1.33), damage[5]); + EXPECT_MUL_EQ(damage[2], UQ_4_12(1.33), damage[6]); + EXPECT_MUL_EQ(damage[3], UQ_4_12(1.33), damage[7]); + } +} + +DOUBLE_BATTLE_TEST("Dark Aura's effect doesn't stack multiple times (Traits)") +{ + s16 damage[6]; + + GIVEN { + PLAYER(SPECIES_YVELTAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DARK_AURA); } + PLAYER(SPECIES_WOBBUFFET) { HP(9999); MaxHP(9999); } + PLAYER(SPECIES_YVELTAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DARK_AURA); } + OPPONENT(SPECIES_WOBBUFFET) { HP(9999); MaxHP(9999); } + OPPONENT(SPECIES_WOBBUFFET) { HP(9999); MaxHP(9999); } + } WHEN { + TURN { + MOVE(playerLeft, MOVE_BITE, target:opponentLeft, secondaryEffect:FALSE); + MOVE(opponentLeft, MOVE_BITE, target:playerLeft, secondaryEffect:FALSE); + MOVE(opponentRight, MOVE_BITE, target:playerLeft, secondaryEffect:FALSE); + } + TURN { SWITCH(playerRight, 2); } + TURN { + MOVE(playerLeft, MOVE_BITE, target:opponentLeft, secondaryEffect:FALSE); + MOVE(opponentLeft, MOVE_BITE, target:playerLeft, secondaryEffect:FALSE); + MOVE(opponentRight, MOVE_BITE, target:playerLeft, secondaryEffect:FALSE); + } + } SCENE { + // Turn 1 + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, opponentLeft); + HP_BAR(playerLeft, captureDamage: &damage[1]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, opponentRight); + HP_BAR(playerLeft, captureDamage: &damage[2]); + + // Turn 2 + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Yveltal"); + + // Turn 3 + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damage[3]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, opponentLeft); + HP_BAR(playerLeft, captureDamage: &damage[4]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, opponentRight); + HP_BAR(playerLeft, captureDamage: &damage[5]); + } THEN { + EXPECT_EQ(damage[3], damage[0]); + EXPECT_EQ(damage[4], damage[1]); + EXPECT_EQ(damage[5], damage[2]); + } +} +#endif diff --git a/test/battle/ability/dauntless_shield.c b/test/battle/ability/dauntless_shield.c index 9a8871d5d72a..729f187a3418 100644 --- a/test/battle/ability/dauntless_shield.c +++ b/test/battle/ability/dauntless_shield.c @@ -81,3 +81,67 @@ SINGLE_BATTLE_TEST("Dauntless Shield activates when it's no longer effected by N MESSAGE("The opposing Zamazenta's Dauntless Shield raised its Defense!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Dauntless Shield raises Defense by one stage (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZAMAZENTA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DAUNTLESS_SHIELD); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DAUNTLESS_SHIELD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Zamazenta's Dauntless Shield raised its Defense!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Dauntless Shield raises Defense by one stage every time it switches in (Gen8) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_DAUNTLESS_SHIELD, GEN_8); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZAMAZENTA) { Ability(ABILITY_LIGHT_METAL); Innates(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("The opposing Zamazenta's Dauntless Shield raised its Defense!"); + ABILITY_POPUP(opponent, ABILITY_DAUNTLESS_SHIELD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Zamazenta's Dauntless Shield raised its Defense!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Dauntless Shield raises Defense by one stage only once per battle (Gen 9+) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_DAUNTLESS_SHIELD, GEN_9); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZAMAZENTA) { Ability(ABILITY_LIGHT_METAL); Innates(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("The opposing 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("The opposing Zamazenta's Dauntless Shield raised its Defense!"); + } + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} +#endif diff --git a/test/battle/ability/dazzling.c b/test/battle/ability/dazzling.c index f54f024452d9..a5ea30dcdb53 100644 --- a/test/battle/ability/dazzling.c +++ b/test/battle/ability/dazzling.c @@ -276,3 +276,333 @@ SINGLE_BATTLE_TEST("Dazzling, Queenly Majesty and Armor Tail do not block high-p NOT ABILITY_POPUP(opponent, ability); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Dazzling, Queenly Majesty and Armor Tail protect the user from priority moves (Traits)") +{ + u32 species; + enum Ability ability, ability2; + + PARAMETRIZE { species = SPECIES_BRUXISH; ability = ABILITY_DAZZLING; ability2 = ABILITY_STRONG_JAW; } + PARAMETRIZE { species = SPECIES_FARIGIRAF; ability = ABILITY_ARMOR_TAIL; ability2 = ABILITY_CUD_CHEW; } + PARAMETRIZE { species = SPECIES_TSAREENA; ability = ABILITY_QUEENLY_MAJESTY; ability2 = ABILITY_LEAF_GUARD; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ability2); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_QUICK_ATTACK, target: opponentLeft); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, opponentRight); + ABILITY_POPUP(opponentLeft, ability); + MESSAGE("Wobbuffet cannot use Quick Attack!"); + } +} + +DOUBLE_BATTLE_TEST("Dazzling, Queenly Majesty and Armor Tail protect users partner from priority moves (Traits)") +{ + u32 species; + enum Ability ability, ability2; + + PARAMETRIZE { species = SPECIES_BRUXISH; ability = ABILITY_DAZZLING; ability2 = ABILITY_STRONG_JAW; } + PARAMETRIZE { species = SPECIES_FARIGIRAF; ability = ABILITY_ARMOR_TAIL; ability2 = ABILITY_CUD_CHEW; } + PARAMETRIZE { species = SPECIES_TSAREENA; ability = ABILITY_QUEENLY_MAJESTY; ability2 = ABILITY_LEAF_GUARD; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ability2); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_QUICK_ATTACK, target: opponentRight); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, opponentRight); + ABILITY_POPUP(opponentLeft, ability); + MESSAGE("Wobbuffet cannot use Quick Attack!"); + } +} + +DOUBLE_BATTLE_TEST("Dazzling, Queenly Majesty and Armor Tail don't protect the user from negative priority (Traits)") +{ + u32 species; + enum Ability ability, ability2; + + PARAMETRIZE { species = SPECIES_BRUXISH; ability = ABILITY_DAZZLING; ability2 = ABILITY_STRONG_JAW; } + PARAMETRIZE { species = SPECIES_FARIGIRAF; ability = ABILITY_ARMOR_TAIL; ability2 = ABILITY_CUD_CHEW; } + PARAMETRIZE { species = SPECIES_TSAREENA; ability = ABILITY_QUEENLY_MAJESTY; ability2 = ABILITY_LEAF_GUARD; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ability2); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_AVALANCHE, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AVALANCHE, playerLeft); + NOT ABILITY_POPUP(opponentLeft, ability); + } +} + +SINGLE_BATTLE_TEST("Dazzling, Queenly Majesty and Armor Tail protect from all multi hit hits with one activation (Traits)") +{ + u32 species; + enum Ability ability, ability2; + + PARAMETRIZE { species = SPECIES_BRUXISH; ability = ABILITY_DAZZLING; ability2 = ABILITY_STRONG_JAW; } + PARAMETRIZE { species = SPECIES_FARIGIRAF; ability = ABILITY_ARMOR_TAIL; ability2 = ABILITY_CUD_CHEW; } + PARAMETRIZE { species = SPECIES_TSAREENA; ability = ABILITY_QUEENLY_MAJESTY; ability2 = ABILITY_LEAF_GUARD; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ability2); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_WATER_SHURIKEN); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_SHURIKEN, opponent); + ABILITY_POPUP(opponent, ability); + NONE_OF { + ABILITY_POPUP(opponent, ability); + ABILITY_POPUP(opponent, ability); + ABILITY_POPUP(opponent, ability); + ABILITY_POPUP(opponent, ability); + } + } +} + +SINGLE_BATTLE_TEST("Dazzling, Queenly Majesty and Armor Tail prevent Protean activation (Traits)") +{ + u32 species, ability, ability2; + + PARAMETRIZE { species = SPECIES_BRUXISH; ability = ABILITY_DAZZLING; ability2 = ABILITY_STRONG_JAW; } + PARAMETRIZE { species = SPECIES_FARIGIRAF; ability = ABILITY_ARMOR_TAIL; ability2 = ABILITY_CUD_CHEW; } + PARAMETRIZE { species = SPECIES_TSAREENA; ability = ABILITY_QUEENLY_MAJESTY; ability2 = ABILITY_LEAF_GUARD; } + + GIVEN { + PLAYER(SPECIES_KECLEON) { Ability(ABILITY_COLOR_CHANGE); Innates(ABILITY_PROTEAN); } + OPPONENT(species) { Ability(ability2); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_WATER_SHURIKEN); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_SHURIKEN, player); + ABILITY_POPUP(player, ABILITY_PROTEAN); + } + ABILITY_POPUP(opponent, ability); + } +} + +// Listed on Bulbapedia as "Moves that target all Pokémon (except Perish Song, Flower Shield, and Rototiller)," +// Despite the fact that there's only 2 remaining moves from that list, being Haze and Teatime +SINGLE_BATTLE_TEST("Dazzling, Queenly Majesty and Armor Tail do not block Haze (Traits)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_BRUXISH; ability = ABILITY_DAZZLING; } + PARAMETRIZE { species = SPECIES_FARIGIRAF; ability = ABILITY_ARMOR_TAIL; } + PARAMETRIZE { species = SPECIES_TSAREENA; ability = ABILITY_QUEENLY_MAJESTY; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_HAZE) == EFFECT_HAZE); + PLAYER(SPECIES_MURKROW) { Ability(ABILITY_INSOMNIA); Innates(ABILITY_PRANKSTER); } + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SWORDS_DANCE); } + TURN { MOVE(player, MOVE_HAZE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HAZE, player); + NOT ABILITY_POPUP(opponent, ability); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Dazzling, Queenly Majesty and Armor Tail do not block Teatime (Traits)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_BRUXISH; ability = ABILITY_DAZZLING; } + PARAMETRIZE { species = SPECIES_FARIGIRAF; ability = ABILITY_ARMOR_TAIL; } + PARAMETRIZE { species = SPECIES_TSAREENA; ability = ABILITY_QUEENLY_MAJESTY; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_TEATIME) == EFFECT_TEATIME); + ASSUME(GetItemHoldEffect(ITEM_ORAN_BERRY) == HOLD_EFFECT_RESTORE_HP); + PLAYER(SPECIES_MURKROW) { Ability(ABILITY_INSOMNIA); Innates(ABILITY_PRANKSTER); Item(ITEM_ORAN_BERRY); HP(1); MaxHP(100); } + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); Item(ITEM_ORAN_BERRY); HP(1); MaxHP(100); } + } WHEN { + TURN { MOVE(player, MOVE_TEATIME); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEATIME, player); + NOT ABILITY_POPUP(opponent, ability); + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Dazzling, Queenly Majesty and Armor Tail do not block a move's Z-Status effect (Traits)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_BRUXISH; ability = ABILITY_DAZZLING; } + PARAMETRIZE { species = SPECIES_FARIGIRAF; ability = ABILITY_ARMOR_TAIL; } + PARAMETRIZE { species = SPECIES_TSAREENA; ability = ABILITY_QUEENLY_MAJESTY; } + + GIVEN { + ASSUME(GetMoveZEffect(MOVE_BABY_DOLL_EYES) == Z_EFFECT_DEF_UP_1); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_FAIRIUM_Z); } + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_BABY_DOLL_EYES, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ABILITY_POPUP(opponent, ability); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_BABY_DOLL_EYES, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Mold Breaker ignores Dazzling, Queenly Majesty and Armor Tail (Traits)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_BRUXISH; ability = ABILITY_DAZZLING; } + PARAMETRIZE { species = SPECIES_FARIGIRAF; ability = ABILITY_ARMOR_TAIL; } + PARAMETRIZE { species = SPECIES_TSAREENA; ability = ABILITY_QUEENLY_MAJESTY; } + + GIVEN { + PLAYER(SPECIES_PINSIR) { Ability(ABILITY_HYPER_CUTTER); Innates(ABILITY_MOLD_BREAKER); } + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_QUICK_ATTACK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player); + HP_BAR(opponent); + NOT ABILITY_POPUP(opponent, ability); + } +} + +DOUBLE_BATTLE_TEST("Instruct-called moves keep their priority, which is considered for Dazzling, Queenly Majesty and Armor Tail (Traits)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_BRUXISH; ability = ABILITY_DAZZLING; } + PARAMETRIZE { species = SPECIES_FARIGIRAF; ability = ABILITY_ARMOR_TAIL; } + PARAMETRIZE { species = SPECIES_TSAREENA; ability = ABILITY_QUEENLY_MAJESTY; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_INSTRUCT) == EFFECT_INSTRUCT); + ASSUME(GetItemHoldEffect(ITEM_EJECT_BUTTON) == HOLD_EFFECT_EJECT_BUTTON); + PLAYER(SPECIES_WOBBUFFET) { Speed(10); } + PLAYER(SPECIES_WOBBUFFET) { Speed(30); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_BUTTON); Speed(20); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); Speed(15); } + } WHEN { + TURN { MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); SEND_OUT(opponentLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, playerRight); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + MESSAGE("The opposing Wobbuffet is switched out with the Eject Button!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft); + ABILITY_POPUP(opponentLeft, ability); + MESSAGE("Wobbuffet cannot use Quick Attack!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, playerRight); + } +} + +SINGLE_BATTLE_TEST("Dazzling, Queenly Majesty and Armor Tail do not block high-priority moves called by other moves (Traits)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_BRUXISH; ability = ABILITY_DAZZLING; } + PARAMETRIZE { species = SPECIES_FARIGIRAF; ability = ABILITY_ARMOR_TAIL; } + PARAMETRIZE { species = SPECIES_TSAREENA; ability = ABILITY_QUEENLY_MAJESTY; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_METRONOME) == EFFECT_METRONOME); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_METRONOME, WITH_RNG(RNG_METRONOME, MOVE_QUICK_ATTACK)); } + } SCENE { + MESSAGE("Wobbuffet used Metronome!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_METRONOME, player); + MESSAGE("Waggling a finger let it use Quick Attack!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player); + HP_BAR(opponent); + NOT ABILITY_POPUP(opponent, ability); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Dazzling, Queenly Majesty and Armor Tail do not block Teatime (Multi)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_BRUXISH; ability = ABILITY_DAZZLING; } + PARAMETRIZE { species = SPECIES_FARIGIRAF; ability = ABILITY_ARMOR_TAIL; } + PARAMETRIZE { species = SPECIES_TSAREENA; ability = ABILITY_QUEENLY_MAJESTY; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_TEATIME) == EFFECT_TEATIME); + ASSUME(GetItemHoldEffect(ITEM_ORAN_BERRY) == HOLD_EFFECT_RESTORE_HP); + PLAYER(SPECIES_MURKROW) { Ability(ABILITY_PRANKSTER); Items(ITEM_NUGGET, ITEM_ORAN_BERRY); HP(1); MaxHP(100); } + OPPONENT(species) { Ability(ability); Items(ITEM_NUGGET, ITEM_ORAN_BERRY); HP(1); MaxHP(100); } + } WHEN { + TURN { MOVE(player, MOVE_TEATIME); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEATIME, player); + NOT ABILITY_POPUP(opponent, ability); + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Instruct-called moves keep their priority, which is considered for Dazzling, Queenly Majesty and Armor Tail (Multi)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_BRUXISH; ability = ABILITY_DAZZLING; } + PARAMETRIZE { species = SPECIES_FARIGIRAF; ability = ABILITY_ARMOR_TAIL; } + PARAMETRIZE { species = SPECIES_TSAREENA; ability = ABILITY_QUEENLY_MAJESTY; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_INSTRUCT) == EFFECT_INSTRUCT); + ASSUME(GetItemHoldEffect(ITEM_EJECT_BUTTON) == HOLD_EFFECT_EJECT_BUTTON); + PLAYER(SPECIES_WOBBUFFET) { Speed(10); } + PLAYER(SPECIES_WOBBUFFET) { Speed(30); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NUGGET, ITEM_EJECT_BUTTON); Speed(20); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); Speed(15); } + } WHEN { + TURN { MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); SEND_OUT(opponentLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, playerRight); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + MESSAGE("The opposing Wobbuffet is switched out with the Eject Button!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft); + ABILITY_POPUP(opponentLeft, ability); + MESSAGE("Wobbuffet cannot use Quick Attack!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, playerRight); + } +} +#endif diff --git a/test/battle/ability/defeatist.c b/test/battle/ability/defeatist.c index 93d84e8a9d0d..e6faecfb5efb 100644 --- a/test/battle/ability/defeatist.c +++ b/test/battle/ability/defeatist.c @@ -44,3 +44,43 @@ SINGLE_BATTLE_TEST("Defeatist halves Special Attack when HP <= 50%", s16 damage) EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.5), results[1].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Defeatist halves Attack when HP <= 50% (Traits)", s16 damage) +{ + u32 hp; + PARAMETRIZE { hp = 400; } + PARAMETRIZE { hp = 200; } + GIVEN { + PLAYER(SPECIES_ARCHEN) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DEFEATIST); HP(hp), MaxHP(400);} + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Defeatist halves Special Attack when HP <= 50% (Traits)", s16 damage) +{ + u32 hp; + PARAMETRIZE { hp = 400; } + PARAMETRIZE { hp = 200; } + GIVEN { + PLAYER(SPECIES_ARCHEN) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DEFEATIST); HP(hp), MaxHP(400);} + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ECHOED_VOICE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.5), results[1].damage); + } +} +#endif diff --git a/test/battle/ability/defiant.c b/test/battle/ability/defiant.c index 2c5c5297173e..d3adefb4d6b2 100644 --- a/test/battle/ability/defiant.c +++ b/test/battle/ability/defiant.c @@ -378,3 +378,424 @@ SINGLE_BATTLE_TEST("Defiant doesn't display ability popup when already at Maximu EXPECT_EQ(player->statStages[STAT_ATK], MAX_STAT_STAGE); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Defiant sharply raises player's Attack after Intimidate (Traits)") +{ + enum Ability abilityLeft, abilityRight; + + PARAMETRIZE { abilityLeft = ABILITY_VITAL_SPIRIT; abilityRight = ABILITY_VITAL_SPIRIT; } + PARAMETRIZE { abilityLeft = ABILITY_VITAL_SPIRIT; abilityRight = ABILITY_DEFIANT; } + PARAMETRIZE { abilityLeft = ABILITY_DEFIANT; abilityRight = ABILITY_VITAL_SPIRIT; } + PARAMETRIZE { abilityLeft = ABILITY_DEFIANT; abilityRight = ABILITY_DEFIANT; } + + GIVEN { + PLAYER(SPECIES_MANKEY) { Ability(ABILITY_ANGER_POINT); Innates(abilityLeft); } + PLAYER(SPECIES_PRIMEAPE) { Ability(ABILITY_ANGER_POINT); Innates(abilityRight); } + OPPONENT(SPECIES_GYARADOS) { Ability(ABILITY_MOXIE); Innates(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target:opponentLeft); MOVE(playerRight, MOVE_SCRATCH, target:opponentRight); } + } SCENE { + //1st mon Intimidate + ABILITY_POPUP(opponentLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("The opposing Gyarados's Intimidate cuts Mankey's Attack!"); + if (abilityLeft == ABILITY_DEFIANT) { + ABILITY_POPUP(playerLeft, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Mankey's Attack sharply rose!"); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("The opposing Gyarados's Intimidate cuts Primeape's Attack!"); + if (abilityRight == ABILITY_DEFIANT) { + ABILITY_POPUP(playerRight, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Primeape's Attack sharply rose!"); + } + + //2nd mon Intimidate + ABILITY_POPUP(opponentRight, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("The opposing Arbok's Intimidate cuts Mankey's Attack!"); + if (abilityLeft == ABILITY_DEFIANT) { + ABILITY_POPUP(playerLeft, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Mankey's Attack sharply rose!"); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("The opposing Arbok's Intimidate cuts Primeape's Attack!"); + if (abilityRight == ABILITY_DEFIANT) { + ABILITY_POPUP(playerRight, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Primeape's Attack sharply rose!"); + } + } THEN { + // -2 from Intimidates and +4 from Defiants gets +2 total + EXPECT_EQ(playerLeft->statStages[STAT_ATK], (abilityLeft == ABILITY_DEFIANT) ? DEFAULT_STAT_STAGE + 2 : DEFAULT_STAT_STAGE - 2); + EXPECT_EQ(playerRight->statStages[STAT_ATK], (abilityRight == ABILITY_DEFIANT) ? DEFAULT_STAT_STAGE + 2 : DEFAULT_STAT_STAGE - 2); + } +} + +// Same as above, but for opponent. +DOUBLE_BATTLE_TEST("Defiant sharply raises opponent's Attack after Intimidate (Traits)") +{ + enum Ability abilityLeft, abilityRight; + + PARAMETRIZE { abilityLeft = ABILITY_VITAL_SPIRIT; abilityRight = ABILITY_VITAL_SPIRIT; } + PARAMETRIZE { abilityLeft = ABILITY_VITAL_SPIRIT; abilityRight = ABILITY_DEFIANT; } + PARAMETRIZE { abilityLeft = ABILITY_DEFIANT; abilityRight = ABILITY_VITAL_SPIRIT; } + PARAMETRIZE { abilityLeft = ABILITY_DEFIANT; abilityRight = ABILITY_DEFIANT; } + + GIVEN { + OPPONENT(SPECIES_MANKEY) { Ability(ABILITY_ANGER_POINT); Innates(abilityLeft); } + OPPONENT(SPECIES_PRIMEAPE) { Ability(ABILITY_ANGER_POINT); Innates(abilityRight); } + PLAYER(SPECIES_GYARADOS) { Ability(ABILITY_MOXIE); Innates(ABILITY_INTIMIDATE); } + PLAYER(SPECIES_ARBOK) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target:playerLeft); MOVE(opponentRight, MOVE_SCRATCH, target:playerRight); } + } SCENE { + //1st mon Intimidate + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("Gyarados's Intimidate cuts the opposing Mankey's Attack!"); + if (abilityLeft == ABILITY_DEFIANT) { + ABILITY_POPUP(opponentLeft, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("The opposing Mankey's Attack sharply rose!"); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("Gyarados's Intimidate cuts the opposing Primeape's Attack!"); + if (abilityRight == ABILITY_DEFIANT) { + ABILITY_POPUP(opponentRight, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("The opposing Primeape's Attack sharply rose!"); + } + + //2nd mon Intimidate + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("Arbok's Intimidate cuts the opposing Mankey's Attack!"); + if (abilityLeft == ABILITY_DEFIANT) { + ABILITY_POPUP(opponentLeft, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("The opposing Mankey's Attack sharply rose!"); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("Arbok's Intimidate cuts the opposing Primeape's Attack!"); + if (abilityRight == ABILITY_DEFIANT) { + ABILITY_POPUP(opponentRight, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("The opposing Primeape's Attack sharply rose!"); + } + } THEN { + // -2 from Intimidates and +4 from Defiants gets +2 total + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], (abilityLeft == ABILITY_DEFIANT) ? DEFAULT_STAT_STAGE + 2 : DEFAULT_STAT_STAGE - 2); + EXPECT_EQ(opponentRight->statStages[STAT_ATK], (abilityRight == ABILITY_DEFIANT) ? DEFAULT_STAT_STAGE + 2 : DEFAULT_STAT_STAGE - 2); + } +} + +SINGLE_BATTLE_TEST("Defiant activates after Sticky Web lowers Speed (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_MANKEY) { Ability(ABILITY_ANGER_POINT); Innates(ABILITY_DEFIANT); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_STICKY_WEB); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponent); + // Switch-in - Sticky Web activates + SEND_IN_MESSAGE("Mankey"); + MESSAGE("Mankey was caught in a sticky web!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Speed fell!"); + // Defiant activates + ABILITY_POPUP(player, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Attack sharply rose!"); + } +} + +SINGLE_BATTLE_TEST("Defiant doesn't activate after Sticky Web lowers Speed if Court Changed (Gen8) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_DEFIANT_STICKY_WEB, GEN_8); + ASSUME(GetMoveEffect(MOVE_GROWL) == EFFECT_ATTACK_DOWN); + ASSUME(GetMoveEffect(MOVE_STICKY_WEB) == EFFECT_STICKY_WEB); + ASSUME(GetMoveEffect(MOVE_COURT_CHANGE) == EFFECT_COURT_CHANGE); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_MANKEY) { Ability(ABILITY_ANGER_POINT); Innates(ABILITY_DEFIANT); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STICKY_WEB); MOVE(opponent, MOVE_COURT_CHANGE); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_GROWL);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COURT_CHANGE, opponent); + // Switch-in - Sticky Web activates + SEND_IN_MESSAGE("Mankey"); + MESSAGE("Mankey was caught in a sticky web!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Speed fell!"); + // Defiant doesn't activate + NONE_OF { + ABILITY_POPUP(player, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Attack sharply rose!"); + } + // Defiant triggers correctly after Sticky Web + ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Attack fell!"); + ABILITY_POPUP(player, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Attack sharply rose!"); + } +} + +SINGLE_BATTLE_TEST("Defiant activates after Sticky Web lowers Speed if Court Changed (Gen9) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_DEFIANT_STICKY_WEB, GEN_9); + ASSUME(GetMoveEffect(MOVE_GROWL) == EFFECT_ATTACK_DOWN); + ASSUME(GetMoveEffect(MOVE_STICKY_WEB) == EFFECT_STICKY_WEB); + ASSUME(GetMoveEffect(MOVE_COURT_CHANGE) == EFFECT_COURT_CHANGE); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_MANKEY) { Ability(ABILITY_ANGER_POINT); Innates(ABILITY_DEFIANT); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STICKY_WEB); MOVE(opponent, MOVE_COURT_CHANGE); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_GROWL);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COURT_CHANGE, opponent); + // Switch-in - Sticky Web activates + SEND_IN_MESSAGE("Mankey"); + MESSAGE("Mankey was caught in a sticky web!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Speed fell!"); + ABILITY_POPUP(player, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Attack sharply rose!"); + // Defiant triggers correctly after Sticky Web + ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Attack fell!"); + ABILITY_POPUP(player, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Attack sharply rose!"); + } +} + +DOUBLE_BATTLE_TEST("Defiant is activated by Cotton Down for non-ally Pokémon (Traits)") +{ + GIVEN { + PLAYER(SPECIES_MANKEY) { Ability(ABILITY_ANGER_POINT); Innates(ABILITY_DEFIANT); } + PLAYER(SPECIES_MANKEY) { Ability(ABILITY_ANGER_POINT); Innates(ABILITY_DEFIANT); } + OPPONENT(SPECIES_ELDEGOSS) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_COTTON_DOWN); } + OPPONENT(SPECIES_MANKEY) { Ability(ABILITY_ANGER_POINT); Innates(ABILITY_DEFIANT); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_COTTON_DOWN); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Mankey's Speed fell!"); + ABILITY_POPUP(playerLeft, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Mankey's Attack sharply rose!"); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Mankey's Speed fell!"); + ABILITY_POPUP(playerRight, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Mankey's Attack sharply rose!"); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("The opposing Mankey's Speed fell!"); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(playerRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Defiant activates before White Herb (Traits)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_LEER; } + PARAMETRIZE { move = MOVE_GROWL; } + + GIVEN { + PLAYER(SPECIES_MANKEY) { Ability(ABILITY_ANGER_POINT); Innates(ABILITY_DEFIANT); Item(ITEM_WHITE_HERB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + + ABILITY_POPUP(player, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Attack sharply rose!"); + + if (move == MOVE_LEER) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Mankey returned its stats to normal using its White Herb!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Mankey returned its stats to normal using its White Herb!"); + } + } + } THEN { + if (move == MOVE_LEER) { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } else { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } + } +} + +SINGLE_BATTLE_TEST("Defiant activates for each stat that is lowered (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TICKLE) == EFFECT_TICKLE); + PLAYER(SPECIES_MANKEY) { Ability(ABILITY_ANGER_POINT); Innates(ABILITY_DEFIANT); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TICKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TICKLE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Attack fell!"); + ABILITY_POPUP(player, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Attack sharply rose!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Defense fell!"); + ABILITY_POPUP(player, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Attack sharply rose!"); + + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 3); + } +} + +SINGLE_BATTLE_TEST("Defiant doesn't activate if the Pokémon lowers it's own stats (Traits)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SUPERPOWER; } + PARAMETRIZE { move = MOVE_CLOSE_COMBAT; } + PARAMETRIZE { move = MOVE_MAKE_IT_RAIN; } + PARAMETRIZE { move = MOVE_SPIN_OUT; } + + GIVEN { + ASSUME(MoveHasAdditionalEffectSelf(MOVE_SUPERPOWER, MOVE_EFFECT_ATK_DEF_DOWN)); + ASSUME(MoveHasAdditionalEffectSelf(MOVE_CLOSE_COMBAT, MOVE_EFFECT_DEF_SPDEF_DOWN)); + ASSUME(MoveHasAdditionalEffectSelf(MOVE_MAKE_IT_RAIN, MOVE_EFFECT_SP_ATK_MINUS_1)); + ASSUME(MoveHasAdditionalEffectSelf(MOVE_SPIN_OUT, MOVE_EFFECT_SPD_MINUS_2)); + PLAYER(SPECIES_MANKEY) { Ability(ABILITY_ANGER_POINT); Innates(ABILITY_DEFIANT); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + + NONE_OF { + ABILITY_POPUP(player, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Attack sharply rose!"); + } + } THEN { + if (move == MOVE_SUPERPOWER) + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + else + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Defiant doesn't display ability popup when already at Maximum Attack (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TICKLE) == EFFECT_TICKLE); + ASSUME(GetMoveEffect(MOVE_BELLY_DRUM) == EFFECT_BELLY_DRUM); + PLAYER(SPECIES_MANKEY) { Ability(ABILITY_ANGER_POINT); Innates(ABILITY_DEFIANT); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BELLY_DRUM); MOVE(opponent, MOVE_TICKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BELLY_DRUM, player); + // Maxed Attack + ANIMATION(ANIM_TYPE_MOVE, MOVE_TICKLE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Attack fell!"); + ABILITY_POPUP(player, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Attack rose!"); + // Maxed Attack + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Defense fell!"); + NONE_OF { + ABILITY_POPUP(player, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Attack sharply rose!"); + } + MESSAGE("Mankey's Attack won't go any higher!"); + + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], MAX_STAT_STAGE); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Defiant activates before White Herb (Multi)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_LEER; } + PARAMETRIZE { move = MOVE_GROWL; } + + GIVEN { + PLAYER(SPECIES_MANKEY) { Ability(ABILITY_DEFIANT); Items(ITEM_PECHA_BERRY, ITEM_WHITE_HERB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + + ABILITY_POPUP(player, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mankey's Attack sharply rose!"); + + if (move == MOVE_LEER) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Mankey returned its stats to normal using its White Herb!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Mankey returned its stats to normal using its White Herb!"); + } + } + } THEN { + if (move == MOVE_LEER) { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } else { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } + } +} +#endif diff --git a/test/battle/ability/desolate_land.c b/test/battle/ability/desolate_land.c index 918ea720a2ad..10cbe504c66b 100644 --- a/test/battle/ability/desolate_land.c +++ b/test/battle/ability/desolate_land.c @@ -177,3 +177,199 @@ SINGLE_BATTLE_TEST("Desolate Land can be replaced by Primordial Sea") EXPECT(gBattleWeather & B_WEATHER_RAIN_PRIMAL); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Desolate Land prevents other weather abilities (Traits)") +{ + u16 ability, species; + PARAMETRIZE { ability = ABILITY_DROUGHT; species = SPECIES_NINETALES; } + PARAMETRIZE { ability = ABILITY_DRIZZLE; species = SPECIES_POLITOED; } + PARAMETRIZE { ability = ABILITY_SAND_STREAM; species = SPECIES_HIPPOWDON; } + PARAMETRIZE { ability = ABILITY_SNOW_WARNING; species = SPECIES_ABOMASNOW; } + + GIVEN { + PLAYER(SPECIES_GROUDON) { Item(ITEM_RED_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { SWITCH(opponent, 1); } + } SCENE { + ABILITY_POPUP(opponent, ability); + } THEN { + EXPECT(gBattleWeather & B_WEATHER_SUN_PRIMAL); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Desolate Land blocks damaging Water-type moves (Multi)") +{ + GIVEN { + PLAYER(SPECIES_GROUDON) {Items(ITEM_PECHA_BERRY, ITEM_RED_ORB);} + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_WATER_GUN); } + TURN { MOVE(opponent, MOVE_WATER_GUN); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Water Gun!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + MESSAGE("The Water-type attack evaporated in the extremely harsh sunlight!"); + NOT HP_BAR(player); + MESSAGE("The opposing Wobbuffet used Water Gun!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + MESSAGE("The Water-type attack evaporated in the extremely harsh sunlight!"); + NOT HP_BAR(player); + } THEN { + EXPECT_EQ(player->hp, player->maxHP); + } +} + +DOUBLE_BATTLE_TEST("Desolate Land blocks damaging Water-type moves and prints the message only once with moves hitting multiple targets (Multi)") +{ + GIVEN { + ASSUME(!IsBattleMoveStatus(MOVE_SURF)); + ASSUME(GetMoveType(MOVE_SURF) == TYPE_WATER); + ASSUME(GetMoveTarget(MOVE_SURF) == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(SPECIES_GROUDON) {Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); {Speed(5);}} + PLAYER(SPECIES_WOBBUFFET) {Speed(5);} + OPPONENT(SPECIES_WOBBUFFET) {Speed(10);} + OPPONENT(SPECIES_WOBBUFFET) {Speed(8);} + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SURF); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Surf!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SURF, opponentLeft); + MESSAGE("The Water-type attack evaporated in the extremely harsh sunlight!"); + NOT MESSAGE("The Water-type attack evaporated in the extremely harsh sunlight!"); + } THEN { + EXPECT_EQ(playerLeft->hp, playerLeft->maxHP); + EXPECT_EQ(playerRight->hp, playerRight->maxHP); + EXPECT_EQ(opponentRight->hp, opponentRight->maxHP); + } +} + +SINGLE_BATTLE_TEST("Desolate Land does not block a move if Pokémon is asleep and uses a Water-type move (Multi)") // Sleep/confusion/paralysis all happen before the check for primal weather +{ + GIVEN { + PLAYER(SPECIES_GROUDON) {Items(ITEM_PECHA_BERRY, ITEM_RED_ORB);} + OPPONENT(SPECIES_WOBBUFFET) {Status1(STATUS1_SLEEP);} + } WHEN { + TURN { MOVE(opponent, MOVE_WATER_GUN); } + } SCENE { + NOT MESSAGE("The Water-type attack evaporated in the extremely harsh sunlight!"); + MESSAGE("The opposing Wobbuffet is fast asleep."); + } +} + +SINGLE_BATTLE_TEST("Desolate Land will not create a softlock when move in semi invulnerable position is blocked (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); } + } WHEN { + TURN { MOVE(player, MOVE_DIVE); } + TURN { SWITCH(opponent, 1); SKIP_TURN(player); } + TURN { MOVE(player, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DIVE, player); + ABILITY_POPUP(opponent, ABILITY_DESOLATE_LAND); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_DIVE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } +} + +SINGLE_BATTLE_TEST("Desolate Land is removed immediately if user faints (Multi)") +{ + GIVEN { + PLAYER(SPECIES_GROUDON) { HP(1); Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_POUND); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, opponent); + NOT MESSAGE("The sunlight is strong."); + MESSAGE("The extremely harsh sunlight faded!"); + } +} + +SINGLE_BATTLE_TEST("Desolate Land blocks weather-setting moves (Multi)") +{ + u16 move; + PARAMETRIZE { move = MOVE_SUNNY_DAY; } + PARAMETRIZE { move = MOVE_RAIN_DANCE; } + PARAMETRIZE { move = MOVE_SANDSTORM; } + PARAMETRIZE { move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SUNNY_DAY) == EFFECT_SUNNY_DAY); + ASSUME(GetMoveEffect(MOVE_RAIN_DANCE) == EFFECT_RAIN_DANCE); + ASSUME(GetMoveEffect(MOVE_SANDSTORM) == EFFECT_SANDSTORM); + ASSUME(GetMoveEffect(MOVE_HAIL) == EFFECT_HAIL); + ASSUME(GetMoveEffect(MOVE_SNOWSCAPE) == EFFECT_SNOWSCAPE); + PLAYER(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, move, opponent); + } THEN { + EXPECT(gBattleWeather & B_WEATHER_SUN_PRIMAL); + } +} + +SINGLE_BATTLE_TEST("Desolate Land prevents other weather abilities (Multi)") +{ + u16 ability, species; + PARAMETRIZE { ability = ABILITY_DROUGHT; species = SPECIES_NINETALES; } + PARAMETRIZE { ability = ABILITY_DRIZZLE; species = SPECIES_POLITOED; } + PARAMETRIZE { ability = ABILITY_SAND_STREAM; species = SPECIES_HIPPOWDON; } + PARAMETRIZE { ability = ABILITY_SNOW_WARNING; species = SPECIES_ABOMASNOW; } + + GIVEN { + PLAYER(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ability); } + } WHEN { + TURN { SWITCH(opponent, 1); } + } SCENE { + ABILITY_POPUP(opponent, ability); + } THEN { + EXPECT(gBattleWeather & B_WEATHER_SUN_PRIMAL); + } +} + +SINGLE_BATTLE_TEST("Desolate Land can be replaced by Delta Stream (Multi)") +{ + GIVEN { + PLAYER(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_RAYQUAZA) { Ability(ABILITY_DELTA_STREAM); } + } WHEN { + TURN { SWITCH(opponent, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DELTA_STREAM); + MESSAGE("Mysterious strong winds are protecting Flying-type Pokémon!"); + } THEN { + EXPECT(gBattleWeather & B_WEATHER_STRONG_WINDS); + } +} + +SINGLE_BATTLE_TEST("Desolate Land can be replaced by Primordial Sea (Multi)") +{ + GIVEN { + PLAYER(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KYOGRE) { Items(ITEM_PECHA_BERRY, ITEM_BLUE_ORB); } + } WHEN { + TURN { SWITCH(opponent, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_PRIMORDIAL_SEA); + MESSAGE("A heavy rain began to fall!"); + } THEN { + EXPECT(gBattleWeather & B_WEATHER_RAIN_PRIMAL); + } +} +#endif diff --git a/test/battle/ability/disguise.c b/test/battle/ability/disguise.c index a874ec254c30..56338fc0f5b8 100644 --- a/test/battle/ability/disguise.c +++ b/test/battle/ability/disguise.c @@ -227,3 +227,248 @@ SINGLE_BATTLE_TEST("Disguise does not break from a teammate's Wish") NOT ABILITY_POPUP(player, ABILITY_DISGUISE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Disguised Mimikyu doesn't lose 1/8 of its max HP upon changing to its busted form (Gen7) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_DISGUISE_HP_LOSS, GEN_7); + PLAYER(SPECIES_MIMIKYU_DISGUISED) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DISGUISE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_AERIAL_ACE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AERIAL_ACE, opponent); + NOT HP_BAR(player); + ABILITY_POPUP(player, ABILITY_DISGUISE); + } THEN { + EXPECT_EQ(player->species, SPECIES_MIMIKYU_BUSTED); + EXPECT_EQ(player->hp, player->maxHP); + } +} + +SINGLE_BATTLE_TEST("Disguised Mimikyu will lose 1/8 of its max HP upon changing to its busted form (Gen8+) (Traits)") +{ + s16 disguiseDamage; + + GIVEN { + WITH_CONFIG(CONFIG_DISGUISE_HP_LOSS, GEN_8); + PLAYER(SPECIES_MIMIKYU_DISGUISED) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DISGUISE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_AERIAL_ACE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AERIAL_ACE, opponent); + ABILITY_POPUP(player, ABILITY_DISGUISE); + HP_BAR(player, captureDamage: &disguiseDamage); + } THEN { + EXPECT_EQ(player->species, SPECIES_MIMIKYU_BUSTED); + EXPECT_EQ(disguiseDamage, player->maxHP / 8); + } +} + +SINGLE_BATTLE_TEST("Disguised Mimikyu takes no damage from a confusion hit and changes to its busted form (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_CONFUSE_RAY) == EFFECT_CONFUSE); + PLAYER(SPECIES_MIMIKYU_DISGUISED) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DISGUISE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_CONFUSE_RAY); } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, player); + MESSAGE("Mimikyu became confused!"); + MESSAGE("Mimikyu is confused!"); + MESSAGE("It hurt itself in its confusion!"); + NOT HP_BAR(player); + ABILITY_POPUP(player, ABILITY_DISGUISE); + } THEN { + EXPECT_EQ(player->species, SPECIES_MIMIKYU_BUSTED); + } +} + +SINGLE_BATTLE_TEST("Disguised Mimikyu's Air Balloon will pop upon changing to its busted form (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_AIR_BALLOON].holdEffect == HOLD_EFFECT_AIR_BALLOON); + PLAYER(SPECIES_MIMIKYU_DISGUISED) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DISGUISE); Item(ITEM_AIR_BALLOON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_AERIAL_ACE); } + } SCENE { + MESSAGE("Mimikyu floats in the air with its Air Balloon!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_AERIAL_ACE, opponent); + NOT HP_BAR(player); + ABILITY_POPUP(player, ABILITY_DISGUISE); + MESSAGE("Mimikyu's Air Balloon popped!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_MIMIKYU_BUSTED); + } +} + +SINGLE_BATTLE_TEST("Disguised Mimikyu takes damage from secondary damage without breaking the disguise (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_STEALTH_ROCK) == EFFECT_STEALTH_ROCK); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_MIMIKYU_DISGUISED) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DISGUISE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_STEALTH_ROCK); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEALTH_ROCK, opponent); + HP_BAR(player); + MESSAGE("Pointed stones dug into Mimikyu!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_MIMIKYU_DISGUISED); + } +} + +SINGLE_BATTLE_TEST("Disguised Mimikyu takes damage from Rocky Helmet without breaking the disguise (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_ROCKY_HELMET].holdEffect == HOLD_EFFECT_ROCKY_HELMET); + PLAYER(SPECIES_MIMIKYU_DISGUISED) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DISGUISE); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_ROCKY_HELMET); } + } WHEN { + TURN { MOVE(player, MOVE_AERIAL_ACE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AERIAL_ACE, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + HP_BAR(player); + MESSAGE("Mimikyu was hurt by the opposing Wobbuffet's Rocky Helmet!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_MIMIKYU_DISGUISED); + } +} + +SINGLE_BATTLE_TEST("Disguised Mimikyu takes damage from Rough Skin without breaking the disguise (Traits)") +{ + GIVEN { + PLAYER(SPECIES_MIMIKYU_DISGUISED) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DISGUISE); } + OPPONENT(SPECIES_CARVANHA) { Ability(ABILITY_SPEED_BOOST); Innates(ABILITY_ROUGH_SKIN); } + } WHEN { + TURN { MOVE(player, MOVE_AERIAL_ACE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AERIAL_ACE, player); + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_ROUGH_SKIN); + HP_BAR(player); + MESSAGE("Mimikyu was hurt by the opposing Carvanha's Rough Skin!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_MIMIKYU_DISGUISED); + } +} + +SINGLE_BATTLE_TEST("Disguised Mimikyu is ignored by Mold Breaker (Traits)") +{ + GIVEN { + PLAYER(SPECIES_MIMIKYU_DISGUISED) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DISGUISE); } + OPPONENT(SPECIES_PINSIR) { Ability(ABILITY_HYPER_CUTTER); Innates(ABILITY_MOLD_BREAKER); } + } WHEN { + TURN { MOVE(opponent, MOVE_AERIAL_ACE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AERIAL_ACE, opponent); + NOT ABILITY_POPUP(player, ABILITY_DISGUISE); + } +} + +SINGLE_BATTLE_TEST("Disguised Mimikyu's types revert back to Ghost/Fairy when Disguise is broken (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SHADOW_CLAW) == TYPE_GHOST); + PLAYER(SPECIES_MIMIKYU_DISGUISED) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DISGUISE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SOAK); } + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(opponent, MOVE_SHADOW_CLAW); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Soak!"); + MESSAGE("Mimikyu transformed into the Water type!"); + MESSAGE("The opposing Wobbuffet used Scratch!"); + ABILITY_POPUP(player, ABILITY_DISGUISE); + MESSAGE("The opposing Wobbuffet used Shadow Claw!"); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Disguised Mimikyu blocks a move after getting Gastro Acid Batton Passed (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_BATON_PASS) == EFFECT_BATON_PASS); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_MIMIKYU_DISGUISED) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DISGUISE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_GASTRO_ACID); MOVE(player, MOVE_BATON_PASS); SEND_OUT(player, 1); } + TURN { MOVE(opponent, MOVE_SHADOW_CLAW); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GASTRO_ACID, opponent); + MESSAGE("Wobbuffet's Ability was suppressed!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BATON_PASS, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHADOW_CLAW, opponent); + ABILITY_POPUP(player, ABILITY_DISGUISE); + } +} + +SINGLE_BATTLE_TEST("Disguise does not break from a teammate's Wish (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_WISH) == EFFECT_WISH); + PLAYER(SPECIES_JIRACHI); + PLAYER(SPECIES_MIMIKYU_DISGUISED) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DISGUISE); HP(219); MaxHP(220); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_WISH); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WISH, player); + NOT ABILITY_POPUP(player, ABILITY_DISGUISE); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Disguised Mimikyu's Air Balloon will pop upon changing to its busted form (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_AIR_BALLOON].holdEffect == HOLD_EFFECT_AIR_BALLOON); + PLAYER(SPECIES_MIMIKYU_DISGUISED) { Ability(ABILITY_DISGUISE); Items(ITEM_PECHA_BERRY, ITEM_AIR_BALLOON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_AERIAL_ACE); } + } SCENE { + MESSAGE("Mimikyu floats in the air with its Air Balloon!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_AERIAL_ACE, opponent); + NOT HP_BAR(player); + ABILITY_POPUP(player, ABILITY_DISGUISE); + MESSAGE("Mimikyu's Air Balloon popped!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_MIMIKYU_BUSTED); + } +} + +SINGLE_BATTLE_TEST("Disguised Mimikyu takes damage from Rocky Helmet without breaking the disguise (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_ROCKY_HELMET].holdEffect == HOLD_EFFECT_ROCKY_HELMET); + PLAYER(SPECIES_MIMIKYU_DISGUISED) { Ability(ABILITY_DISGUISE); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_ROCKY_HELMET); } + } WHEN { + TURN { MOVE(player, MOVE_AERIAL_ACE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AERIAL_ACE, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + HP_BAR(player); + MESSAGE("Mimikyu was hurt by the opposing Wobbuffet's Rocky Helmet!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_MIMIKYU_DISGUISED); + } +} +#endif diff --git a/test/battle/ability/download.c b/test/battle/ability/download.c index f275b7c2628b..8b33f6cb3467 100644 --- a/test/battle/ability/download.c +++ b/test/battle/ability/download.c @@ -119,3 +119,118 @@ DOUBLE_BATTLE_TEST("Download raises Sp.Attack if enemies have lower total Sp. De EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Download raises Attack if player has lower Def than Sp. Def (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_LIGHT_METAL; } + PARAMETRIZE { ability = ABILITY_DOWNLOAD; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Defense(100); SpDefense(200); } + OPPONENT(SPECIES_PORYGON) { Ability(ABILITY_ANALYTIC); Innates(ability); Attack(100); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + if (ability == ABILITY_DOWNLOAD) + { + ABILITY_POPUP(opponent, ABILITY_DOWNLOAD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Porygon's Download raised its Attack!"); + } + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Download raises Sp.Attack if enemy has lower Sp. Def than Def (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_LIGHT_METAL; } + PARAMETRIZE { ability = ABILITY_DOWNLOAD; } + GIVEN { + PLAYER(SPECIES_PORYGON) { Ability(ABILITY_ANALYTIC); Innates(ability); SpAttack(100); } + OPPONENT(SPECIES_WOBBUFFET) { Defense(200); SpDefense(100); } + } WHEN { + TURN { MOVE(player, MOVE_TRI_ATTACK); } + } SCENE { + if (ability == ABILITY_DOWNLOAD) + { + ABILITY_POPUP(player, ABILITY_DOWNLOAD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Porygon's Download raised its Sp. Atk!"); + } + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Download doesn't activate if target hasn't been sent out yet (Traits)", s16 damagePhysical, s16 damageSpecial) +{ + enum Ability ability; + + PARAMETRIZE { ability = ABILITY_LIGHT_METAL; } + PARAMETRIZE { ability = ABILITY_DOWNLOAD; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + PLAYER(SPECIES_WOBBUFFET) { Speed(100); } + PLAYER(SPECIES_PORYGON) { Ability(ABILITY_ANALYTIC); Innates(ability); Defense(400); SpDefense(300); Speed(300); Attack(100); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(100); } + OPPONENT(SPECIES_PORYGON2) { Ability(ABILITY_ANALYTIC); Innates(ability); Defense(100); SpDefense(200); Speed(200); } + } WHEN { + TURN { MOVE(player, MOVE_EXPLOSION); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_TRI_ATTACK); } + } SCENE { + HP_BAR(player, hp: 0); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, player); + // Everyone faints. + + SEND_IN_MESSAGE("Porygon"); + NONE_OF { + ABILITY_POPUP(player, ABILITY_DOWNLOAD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Porygon's Download raised its Attack!"); + } + MESSAGE("2 sent out Porygon2!"); + + if (ability == ABILITY_DOWNLOAD) + { + ABILITY_POPUP(opponent, ABILITY_DOWNLOAD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Porygon2's Download raised its Sp. Atk!"); + } + + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRI_ATTACK, opponent); + HP_BAR(player, captureDamage: &results[i].damageSpecial); + } FINALLY { + EXPECT_MUL_EQ(results[0].damageSpecial, Q_4_12(1.5), results[1].damageSpecial); + } +} + +DOUBLE_BATTLE_TEST("Download raises Sp.Attack if enemies have lower total Sp. Def than Def (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_LIGHT_METAL; } + PARAMETRIZE { ability = ABILITY_DOWNLOAD; } + GIVEN { + PLAYER(SPECIES_PORYGON) { Ability(ABILITY_ANALYTIC); Innates(ability); SpAttack(100); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { Defense(200); SpDefense(100); } + OPPONENT(SPECIES_WOBBUFFET) { Defense(100); SpDefense(150); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_TRI_ATTACK, target: opponentLeft ); } + } SCENE { + if (ability == ABILITY_DOWNLOAD) + { + ABILITY_POPUP(playerLeft, ABILITY_DOWNLOAD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Porygon's Download raised its Sp. Atk!"); + } + HP_BAR(opponentLeft, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} +#endif diff --git a/test/battle/ability/dragons_maw.c b/test/battle/ability/dragons_maw.c index 950cb1db0fe8..91552d0bc082 100644 --- a/test/battle/ability/dragons_maw.c +++ b/test/battle/ability/dragons_maw.c @@ -31,3 +31,36 @@ SINGLE_BATTLE_TEST("Dragon's Maw increases Dragon-type move damage", s16 damage) EXPECT_MUL_EQ(results[4].damage, Q_4_12(1.5), results[5].damage); // Dragon Breath should be affected } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Dragon's Maw increases Dragon-type move damage (Traits)", s16 damage) +{ + u32 move; + enum Ability ability; + + PARAMETRIZE { move = MOVE_SCRATCH; ability = ABILITY_KLUTZ; } + PARAMETRIZE { move = MOVE_SCRATCH; ability = ABILITY_DRAGONS_MAW; } + PARAMETRIZE { move = MOVE_DRAGON_CLAW; ability = ABILITY_KLUTZ; } + PARAMETRIZE { move = MOVE_DRAGON_CLAW; ability = ABILITY_DRAGONS_MAW; } + PARAMETRIZE { move = MOVE_DRAGON_BREATH; ability = ABILITY_KLUTZ; } + PARAMETRIZE { move = MOVE_DRAGON_BREATH; ability = ABILITY_DRAGONS_MAW; } + + GIVEN { + ASSUME(GetMoveType(MOVE_SCRATCH) != TYPE_DRAGON); + ASSUME(GetMoveType(MOVE_DRAGON_CLAW) == TYPE_DRAGON); + ASSUME(GetMoveType(MOVE_DRAGON_BREATH) == TYPE_DRAGON); + ASSUME(GetMoveCategory(MOVE_DRAGON_CLAW) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_DRAGON_BREATH) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_REGIDRAGO) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); // Scratch should be unaffected + EXPECT_MUL_EQ(results[2].damage, Q_4_12(1.5), results[3].damage); // Dragon Claw should be affected + EXPECT_MUL_EQ(results[4].damage, Q_4_12(1.5), results[5].damage); // Dragon Breath should be affected + } +} +#endif diff --git a/test/battle/ability/drizzle.c b/test/battle/ability/drizzle.c index 8e6f7876c9eb..c598addc9954 100644 --- a/test/battle/ability/drizzle.c +++ b/test/battle/ability/drizzle.c @@ -103,3 +103,138 @@ SINGLE_BATTLE_TEST("Drizzle sets up permanent rain (Gen3-5)") NOT MESSAGE("The rain stopped."); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Drizzle summons rain (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_DRIZZLE; } + PARAMETRIZE { ability = ABILITY_DAMP; } + + GIVEN { + PLAYER(SPECIES_POLITOED) { Ability(ABILITY_WATER_ABSORB); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BUBBLE); } + } SCENE { + if (ability == ABILITY_DRIZZLE) { + ABILITY_POPUP(player, ABILITY_DRIZZLE); + MESSAGE("Politoed's Drizzle made it rain!"); + } + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[1].damage, Q_4_12(1.5), results[0].damage); + } +} + +SINGLE_BATTLE_TEST("Drizzle sets up rain for 5 turns (Gen6+) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_ABILITY_WEATHER, GEN_6); + PLAYER(SPECIES_POLITOED) { Moves(MOVE_CELEBRATE); Ability(ABILITY_WATER_ABSORB); Innates(ABILITY_DRIZZLE); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DRIZZLE); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("The rain stopped."); + } +} + +SINGLE_BATTLE_TEST("Drizzle sets up rain for 8 turns with Damp Rock (Gen6+) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_ABILITY_WEATHER, GEN_6); + PLAYER(SPECIES_POLITOED) { Moves(MOVE_CELEBRATE); Ability(ABILITY_WATER_ABSORB); Innates(ABILITY_DRIZZLE); Item(ITEM_DAMP_ROCK); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DRIZZLE); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("The rain stopped."); + } +} + +SINGLE_BATTLE_TEST("Drizzle sets up permanent rain (Gen3-5) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_ABILITY_WEATHER, GEN_3); + PLAYER(SPECIES_POLITOED) { Moves(MOVE_CELEBRATE); Ability(ABILITY_WATER_ABSORB); Innates(ABILITY_DRIZZLE); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DRIZZLE); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + NOT MESSAGE("The rain stopped."); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Drizzle sets up rain for 8 turns with Damp Rock (Gen6+) (Multi)") +{ + GIVEN { + WITH_CONFIG(CONFIG_ABILITY_WEATHER, GEN_6); + PLAYER(SPECIES_POLITOED) { Moves(MOVE_CELEBRATE); Ability(ABILITY_DRIZZLE); Items(ITEM_PECHA_BERRY, ITEM_DAMP_ROCK); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DRIZZLE); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("Rain continues to fall."); + MESSAGE("The rain stopped."); + } +} +#endif diff --git a/test/battle/ability/drought.c b/test/battle/ability/drought.c index ba78643b5043..175e4535392e 100644 --- a/test/battle/ability/drought.c +++ b/test/battle/ability/drought.c @@ -81,3 +81,116 @@ SINGLE_BATTLE_TEST("Drought sets up permanent sun (Gen3-5)") NOT MESSAGE("The sunlight faded."); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Drought sets up sun for 5 turns (Gen6+) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_ABILITY_WEATHER, GEN_6); + PLAYER(SPECIES_NINETALES) { Moves(MOVE_CELEBRATE); Ability(ABILITY_FLASH_FIRE); Innates(ABILITY_DROUGHT); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DROUGHT); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight faded."); + } +} + +SINGLE_BATTLE_TEST("Drought sets up sun for 8 turns with Heat Rock (Gen6+) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_ABILITY_WEATHER, GEN_6); + PLAYER(SPECIES_NINETALES) { Moves(MOVE_CELEBRATE); Ability(ABILITY_FLASH_FIRE); Innates(ABILITY_DROUGHT); Item(ITEM_HEAT_ROCK); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DROUGHT); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight faded."); + } +} + +SINGLE_BATTLE_TEST("Drought sets up permanent sun (Gen3-5) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_ABILITY_WEATHER, GEN_3); + PLAYER(SPECIES_NINETALES) { Moves(MOVE_CELEBRATE); Ability(ABILITY_FLASH_FIRE); Innates(ABILITY_DROUGHT); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DROUGHT); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + NOT MESSAGE("The sunlight faded."); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Drought sets up sun for 8 turns with Heat Rock (Gen6+) (Multi)") +{ + GIVEN { + WITH_CONFIG(CONFIG_ABILITY_WEATHER, GEN_6); + PLAYER(SPECIES_NINETALES) { Moves(MOVE_CELEBRATE); Ability(ABILITY_DROUGHT); Items(ITEM_PECHA_BERRY, ITEM_HEAT_ROCK); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DROUGHT); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight faded."); + } +} +#endif diff --git a/test/battle/ability/dry_skin.c b/test/battle/ability/dry_skin.c index 7de0f82acf51..ac85be25308e 100644 --- a/test/battle/ability/dry_skin.c +++ b/test/battle/ability/dry_skin.c @@ -168,3 +168,155 @@ SINGLE_BATTLE_TEST("Dry Skin prevents Absorb Bulb and Luminous Moss from activat } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Dry Skin causes 1/8th Max HP damage in Sun (Traits)") +{ + GIVEN { + PLAYER(SPECIES_PARASECT) { Ability(ABILITY_DAMP); Innates(ABILITY_DRY_SKIN); HP(100); MaxHP(200); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DRY_SKIN); + HP_BAR(player, damage: 200 / 8); + MESSAGE("Parasect's Dry Skin takes its toll!"); + } +} + +TO_DO_BATTLE_TEST("Dry Skin doesn't get damaged in Sun if Cloud Nine/Air Lock is on the field (Traits)"); + +SINGLE_BATTLE_TEST("Dry Skin heals 1/8th Max HP in Rain (Traits)") +{ + GIVEN { + PLAYER(SPECIES_PARASECT) { Ability(ABILITY_DAMP); Innates(ABILITY_DRY_SKIN); HP(100); MaxHP(200); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_RAIN_DANCE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DRY_SKIN); + MESSAGE("Parasect's Dry Skin restored its HP a little!"); + HP_BAR(player, damage: -(200 / 8)); + } +} + +TO_DO_BATTLE_TEST("Dry Skin doesn't heal in Rain if Cloud Nine/Air Lock is on the field (Traits)"); + +SINGLE_BATTLE_TEST("Dry Skin increases damage taken from Fire-type moves by 25% (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_EFFECT_SPORE; } + PARAMETRIZE { ability = ABILITY_DRY_SKIN; } + GIVEN { + ASSUME(GetMoveType(MOVE_EMBER) == TYPE_FIRE); + ASSUME(GetMovePower(MOVE_EMBER) == 40); + ASSUME(GetSpeciesType(SPECIES_PARASECT, 0) == TYPE_BUG); + ASSUME(GetSpeciesType(SPECIES_PARASECT, 1) == TYPE_GRASS); + ASSUME(GetSpeciesType(SPECIES_WOBBUFFET, 0) == TYPE_PSYCHIC); + ASSUME(GetSpeciesType(SPECIES_WOBBUFFET, 1) == TYPE_PSYCHIC); + PLAYER(SPECIES_WOBBUFFET) { SpAttack(71); } + OPPONENT(SPECIES_PARASECT) { Ability(ABILITY_DAMP); Innates(ability); SpDefense(165); } + } WHEN { + TURN { MOVE(player, MOVE_EMBER); } + } SCENE { + MESSAGE("Wobbuffet used Ember!"); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + // Due to numerics related to rounding on each applied multiplier, + // the ability effect doesn't manifest as a 25% damage increase, but as a ~31% damage increase in this case. + // Values obtained from https://calc.pokemonshowdown.com (Neutral nature and 0 IVs on both sides) + EXPECT_EQ(results[0].damage, 52); + EXPECT_EQ(results[1].damage, 68); + } +} + +SINGLE_BATTLE_TEST("Dry Skin heals 25% when hit by water type moves (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_BUBBLE) == TYPE_WATER); + PLAYER(SPECIES_PARASECT) { Ability(ABILITY_DAMP); Innates(ABILITY_DRY_SKIN); HP(100); MaxHP(200); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_BUBBLE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DRY_SKIN); + HP_BAR(player, damage: -50); + MESSAGE("Parasect restored HP using its Dry Skin!"); + } +} + +SINGLE_BATTLE_TEST("Dry Skin does not activate if protected (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_BUBBLE) == TYPE_WATER); + PLAYER(SPECIES_PARASECT) { Ability(ABILITY_DAMP); Innates(ABILITY_DRY_SKIN); HP(100); MaxHP(200); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PROTECT); MOVE(opponent, MOVE_BUBBLE); } + } SCENE { + NONE_OF { ABILITY_POPUP(player, ABILITY_DRY_SKIN); HP_BAR(player); MESSAGE("Parasect restored HP using its Dry Skin!"); } + } +} + +SINGLE_BATTLE_TEST("Dry Skin is only triggered once on multi strike moves (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_WATER_SHURIKEN) == TYPE_WATER); + ASSUME(GetMoveEffect(MOVE_WATER_SHURIKEN) == EFFECT_MULTI_HIT); + PLAYER(SPECIES_PARASECT) { Ability(ABILITY_DAMP); Innates(ABILITY_DRY_SKIN); HP(100); MaxHP(200); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_WATER_SHURIKEN); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DRY_SKIN); + HP_BAR(player, damage: -50); + MESSAGE("Parasect restored HP using its Dry Skin!"); + } +} + +SINGLE_BATTLE_TEST("Dry Skin prevents Absorb Bulb and Luminous Moss from activating (Traits)") +{ + u32 item; + PARAMETRIZE { item = ITEM_ABSORB_BULB; } + PARAMETRIZE { item = ITEM_LUMINOUS_MOSS; } + GIVEN { + ASSUME(GetMoveType(MOVE_BUBBLE) == TYPE_WATER); + PLAYER(SPECIES_PARASECT) { Ability(ABILITY_DAMP); Innates(ABILITY_DRY_SKIN); HP(100); MaxHP(200); Item(item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_BUBBLE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DRY_SKIN); + HP_BAR(player, damage: -50); + MESSAGE("Parasect restored HP using its Dry Skin!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Dry Skin prevents Absorb Bulb and Luminous Moss from activating (Multi)") +{ + u32 item; + PARAMETRIZE { item = ITEM_ABSORB_BULB; } + PARAMETRIZE { item = ITEM_LUMINOUS_MOSS; } + GIVEN { + ASSUME(GetMoveType(MOVE_BUBBLE) == TYPE_WATER); + PLAYER(SPECIES_PARASECT) { Ability(ABILITY_DRY_SKIN); HP(100); MaxHP(200); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_BUBBLE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_DRY_SKIN); + HP_BAR(player, damage: -50); + MESSAGE("Parasect restored HP using its Dry Skin!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } + } +} +#endif diff --git a/test/battle/ability/earth_eater.c b/test/battle/ability/earth_eater.c index c30b9674f50e..b0296ef83e34 100644 --- a/test/battle/ability/earth_eater.c +++ b/test/battle/ability/earth_eater.c @@ -47,3 +47,52 @@ SINGLE_BATTLE_TEST("Earth Eater activates on status moves") MESSAGE("Orthworm restored HP using its Earth Eater!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Earth Eater heals 25% when hit by ground type moves (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_MUD_SLAP) == TYPE_GROUND); + PLAYER(SPECIES_ORTHWORM) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_EARTH_EATER); HP(1); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_MUD_SLAP); } + } SCENE { + ABILITY_POPUP(player, ABILITY_EARTH_EATER); + HP_BAR(player, damage: -25); + MESSAGE("Orthworm restored HP using its Earth Eater!"); + } +} + +SINGLE_BATTLE_TEST("Earth Eater does not activate if protected (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_MUD_SLAP) == TYPE_GROUND); + PLAYER(SPECIES_ORTHWORM) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_EARTH_EATER); HP(1); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PROTECT); MOVE(opponent, MOVE_MUD_SLAP); } + } SCENE { + NONE_OF { + ABILITY_POPUP(player, ABILITY_EARTH_EATER); HP_BAR(player); + MESSAGE("Orthworm restored HP using its Earth Eater!"); + } + } +} + +SINGLE_BATTLE_TEST("Earth Eater activates on status moves (Multi)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SAND_ATTACK) == TYPE_GROUND); + ASSUME(GetMoveCategory(MOVE_SAND_ATTACK) == DAMAGE_CATEGORY_STATUS); + PLAYER(SPECIES_ORTHWORM) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_EARTH_EATER); HP(1); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SAND_ATTACK); } + } SCENE { + ABILITY_POPUP(player, ABILITY_EARTH_EATER); + HP_BAR(player, damage: -25); + MESSAGE("Orthworm restored HP using its Earth Eater!"); + } +} +#endif diff --git a/test/battle/ability/effect_spore.c b/test/battle/ability/effect_spore.c index 59b680f42231..546a85497c15 100644 --- a/test/battle/ability/effect_spore.c +++ b/test/battle/ability/effect_spore.c @@ -122,3 +122,111 @@ SINGLE_BATTLE_TEST("Effect Spore will check if it can inflict status onto attack STATUS_ICON(player, sleep: TRUE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Effect Spore only inflicts status on contact (Traits)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_SWIFT; } + GIVEN { + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + ASSUME(!MoveMakesContact(MOVE_SWIFT)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_TECHNICIAN); Innates(ABILITY_EFFECT_SPORE); } + } WHEN { + TURN { MOVE(player, move, WITH_RNG(RNG_EFFECT_SPORE, 1)); } + TURN {} + } SCENE { + if (MoveMakesContact(move)) { + ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + MESSAGE("Wobbuffet was poisoned by the opposing Breloom's Effect Spore!"); + STATUS_ICON(player, poison: TRUE); + } else { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + MESSAGE("Wobbuffet was poisoned by the opposing Breloom's Effect Spore!"); + STATUS_ICON(player, poison: TRUE); + } + } + } +} + +SINGLE_BATTLE_TEST("Effect Spore causes poison 9% of the time (Traits)") +{ + PASSES_RANDOMLY(9, 100, RNG_EFFECT_SPORE); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_TECHNICIAN); Innates(ABILITY_EFFECT_SPORE); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + MESSAGE("Wobbuffet was poisoned by the opposing Breloom's Effect Spore!"); + STATUS_ICON(player, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Effect Spore causes paralysis 10% of the time (Traits)") +{ + PASSES_RANDOMLY(10, 100, RNG_EFFECT_SPORE); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_TECHNICIAN); Innates(ABILITY_EFFECT_SPORE); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, player); + MESSAGE("The opposing Breloom's Effect Spore paralyzed Wobbuffet, so it may be unable to move!"); + STATUS_ICON(player, paralysis: TRUE); + } +} + +SINGLE_BATTLE_TEST("Effect Spore causes sleep 11% of the time (Traits)") +{ + PASSES_RANDOMLY(11, 100, RNG_EFFECT_SPORE); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_TECHNICIAN); Innates(ABILITY_EFFECT_SPORE); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, player); + MESSAGE("The opposing Breloom's Effect Spore made Wobbuffet sleep!"); + STATUS_ICON(player, sleep: TRUE); + } +} + +SINGLE_BATTLE_TEST("Effect Spore will check if it can inflict status onto attacker, not itself (Traits)") +{ + PASSES_RANDOMLY(11, 100, RNG_EFFECT_SPORE); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRELOOM) { Status1(STATUS1_BURN); Ability(ABILITY_TECHNICIAN); Innates(ABILITY_EFFECT_SPORE); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, player); + MESSAGE("The opposing Breloom's Effect Spore made Wobbuffet sleep!"); + STATUS_ICON(player, sleep: TRUE); + } +} +#endif diff --git a/test/battle/ability/electric_surge.c b/test/battle/ability/electric_surge.c index 3213cfc3235b..25b62f2a2c42 100644 --- a/test/battle/ability/electric_surge.c +++ b/test/battle/ability/electric_surge.c @@ -13,3 +13,18 @@ SINGLE_BATTLE_TEST("Electric Surge creates Electric Terrain when entering the ba MESSAGE("An electric current ran across the battlefield!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Electric Surge creates Electric Terrain when entering the battle (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TAPU_KOKO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ELECTRIC_SURGE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN {} + } SCENE { + ABILITY_POPUP(player, ABILITY_ELECTRIC_SURGE); + MESSAGE("An electric current ran across the battlefield!"); + } +} +#endif diff --git a/test/battle/ability/electromorphosis.c b/test/battle/ability/electromorphosis.c index 5291a7d9737f..af314b3f6627 100644 --- a/test/battle/ability/electromorphosis.c +++ b/test/battle/ability/electromorphosis.c @@ -54,3 +54,59 @@ SINGLE_BATTLE_TEST("Electromorphosis sets up Charge when hit by any move") EXPECT_MUL_EQ(dmgBefore, Q_4_12(2.0), dmgAfter); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Electromorphosis sets up Charge when hit by any move (Traits)") +{ + s16 dmgBefore, dmgAfter; + u16 move; + + PARAMETRIZE {move = MOVE_SCRATCH; } + PARAMETRIZE {move = MOVE_GUST; } + + GIVEN { + ASSUME(!IsBattleMoveStatus(MOVE_SCRATCH)); + ASSUME(!IsBattleMoveStatus(MOVE_GUST)); + ASSUME(GetMoveCategory(MOVE_GUST) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(!IsBattleMoveStatus(MOVE_THUNDER_SHOCK)); + ASSUME(GetMoveType(MOVE_THUNDER_SHOCK) == TYPE_ELECTRIC); + + PLAYER(SPECIES_BELLIBOLT) { Ability(ABILITY_DAMP); Innates(ABILITY_ELECTROMORPHOSIS); Speed(10); } + OPPONENT(SPECIES_WOBBUFFET) {Ability(ABILITY_LIMBER); Speed(5) ;} // Limber, so it doesn't get paralyzed. + } + WHEN { + TURN { MOVE(player, MOVE_THUNDER_SHOCK), MOVE(opponent, move); } + TURN { MOVE(player, MOVE_THUNDER_SHOCK), MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_SHOCK, player); + HP_BAR(opponent, captureDamage: &dmgBefore); + + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_ELECTROMORPHOSIS); + if (move == MOVE_SCRATCH) { + MESSAGE("Being hit by Scratch charged Bellibolt with power!"); + } + else { + MESSAGE("Being hit by Gust charged Bellibolt with power!"); + } + + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_SHOCK, player); + HP_BAR(opponent, captureDamage: &dmgAfter); + + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_ELECTROMORPHOSIS); + if (move == MOVE_SCRATCH) { + MESSAGE("Being hit by Scratch charged Bellibolt with power!"); + } + else { + MESSAGE("Being hit by Gust charged Bellibolt with power!"); + } + } + THEN { + EXPECT_MUL_EQ(dmgBefore, Q_4_12(2.0), dmgAfter); + } +} +#endif diff --git a/test/battle/ability/embody_aspect.c b/test/battle/ability/embody_aspect.c index 326981125fdf..84bec09d5f92 100644 --- a/test/battle/ability/embody_aspect.c +++ b/test/battle/ability/embody_aspect.c @@ -58,3 +58,43 @@ SINGLE_BATTLE_TEST("Embody Aspect activates when it's no longer effected by Neut MESSAGE("The opposing Ogerpon's Embody Aspect raised its Speed!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Embody Aspect raises a stat depending on the users form by one stage (Traits)") +{ + u16 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_OGERPON_TEAL_TERA; ability = ABILITY_EMBODY_ASPECT_TEAL_MASK; } + PARAMETRIZE { species = SPECIES_OGERPON_HEARTHFLAME_TERA; ability = ABILITY_EMBODY_ASPECT_HEARTHFLAME_MASK; } + PARAMETRIZE { species = SPECIES_OGERPON_WELLSPRING_TERA; ability = ABILITY_EMBODY_ASPECT_WELLSPRING_MASK; } + PARAMETRIZE { species = SPECIES_OGERPON_CORNERSTONE_TERA; ability = ABILITY_EMBODY_ASPECT_CORNERSTONE_MASK; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ability); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + if (ability == ABILITY_EMBODY_ASPECT_TEAL_MASK) + MESSAGE("The opposing Ogerpon's Embody Aspect raised its Speed!"); + else if (ability == ABILITY_EMBODY_ASPECT_HEARTHFLAME_MASK) + MESSAGE("The opposing Ogerpon's Embody Aspect raised its Attack!"); + else if (ability == ABILITY_EMBODY_ASPECT_WELLSPRING_MASK) + MESSAGE("The opposing Ogerpon's Embody Aspect raised its Sp. Def!"); + else if (ability == ABILITY_EMBODY_ASPECT_CORNERSTONE_MASK) + MESSAGE("The opposing Ogerpon's Embody Aspect raised its Defense!"); + } THEN { + if (ability == ABILITY_EMBODY_ASPECT_TEAL_MASK) + EXPECT_EQ(opponent->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 1); + else if (ability == ABILITY_EMBODY_ASPECT_HEARTHFLAME_MASK) + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + else if (ability == ABILITY_EMBODY_ASPECT_WELLSPRING_MASK) + EXPECT_EQ(opponent->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 1); + else if (ability == ABILITY_EMBODY_ASPECT_CORNERSTONE_MASK) + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} +#endif diff --git a/test/battle/ability/emergency_exit.c b/test/battle/ability/emergency_exit.c index 6112267aa531..256b2a084f07 100644 --- a/test/battle/ability/emergency_exit.c +++ b/test/battle/ability/emergency_exit.c @@ -279,3 +279,354 @@ SINGLE_BATTLE_TEST("Emergency Exit will trigger due to Jump Kick recoil") } } +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Emergency Exit switches out when taking 50% max-hp damage (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUPER_FANG); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player); + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + +SINGLE_BATTLE_TEST("Emergency Exit does not switch out when going below 50% max-HP but healed via held item back above the threshold (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); Item(ITEM_SITRUS_BERRY); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUPER_FANG); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + +SINGLE_BATTLE_TEST("Emergency Exit switches out when going below 50% max-HP but healing via held item is not enough to go back above the threshold (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(133); Item(ITEM_ORAN_BERRY); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUPER_FANG); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + +DOUBLE_BATTLE_TEST("Only the fastest Wimp Out (Emergency Exit) user switches out (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ZAPDOS) { Speed(10); } + PLAYER(SPECIES_WOBBUFFET) { Speed(10); } + OPPONENT(SPECIES_WIMPOD) { Speed(1); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_WIMP_OUT); Item(ITEM_FOCUS_SASH); }; + OPPONENT(SPECIES_WIMPOD) { Speed(2); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_WIMP_OUT); Item(ITEM_FOCUS_SASH); }; + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_HYPER_VOICE); SEND_OUT(opponentRight, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerLeft); + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + ABILITY_POPUP(opponentRight, ABILITY_WIMP_OUT); + } +} + +SINGLE_BATTLE_TEST("Emergency Exit activates when taking residual damage and falling under 50% max-hp - Burn (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(134); Status1(STATUS1_BURN); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SEND_OUT(opponent, 1); } + } SCENE { + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + +SINGLE_BATTLE_TEST("Emergency Exit activates when healing from under 50% max-hp and taking residual damage to under 50% max-hp - Burn (Traits)") +{ + // Might fail if users set healing higher than burn damage + GIVEN { + ASSUME(GetMoveEffect(MOVE_AQUA_RING) == EFFECT_AQUA_RING); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(130); Status1(STATUS1_BURN); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_AQUA_RING); SEND_OUT(opponent, 1); } + } SCENE { + HP_BAR(opponent); + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + +SINGLE_BATTLE_TEST("Emergency Exit activates when taking residual damage and falling under 50% max-hp - Weather (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(134); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SANDSTORM); SEND_OUT(opponent, 1); } + } SCENE { + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + +SINGLE_BATTLE_TEST("Emergency Exit activates when healing from under 50% max-hp and taking residual damage to under 50% max-hp - Sticky Barb (Traits)") +{ + // Might fail if users set healing higher than sticky barb damage + GIVEN { + ASSUME(GetMoveEffect(MOVE_AQUA_RING) == EFFECT_AQUA_RING); + ASSUME(GetItemHoldEffect(ITEM_STICKY_BARB) == HOLD_EFFECT_STICKY_BARB); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(130); Item(ITEM_STICKY_BARB); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_AQUA_RING); SEND_OUT(opponent, 1); } + } SCENE { + HP_BAR(opponent); + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + +SINGLE_BATTLE_TEST("Emergency Exit activates when taking residual damage and falling under 50% max-hp - Salt Cure (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(160); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SALT_CURE); SEND_OUT(opponent, 1); } + } SCENE { + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + +WILD_BATTLE_TEST("Emergency Exit makes the pokemon flee during wild battle (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); }; + } WHEN { + TURN { MOVE(player, MOVE_SUPER_FANG);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player); + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } THEN { + EXPECT_EQ(gBattleOutcome, B_OUTCOME_MON_TELEPORTED); + } +} + +WILD_BATTLE_TEST("Emergency Exit activates when taking residual damage and falling under 50% max-hp (wild battle) (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(134); Status1(STATUS1_BURN); }; + } WHEN { + TURN { } + } SCENE { + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } THEN { + EXPECT_EQ(gBattleOutcome, B_OUTCOME_MON_TELEPORTED); + } +} + +WILD_BATTLE_TEST("Emergency Exit makes the player ran during wild battle (Traits)") +{ + GIVEN { + PLAYER(SPECIES_GOLISOPOD) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); }; + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUPER_FANG);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, opponent); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_EMERGENCY_EXIT); + } THEN { + EXPECT_EQ(gBattleOutcome, B_OUTCOME_PLAYER_TELEPORTED); + } +} + +WILD_BATTLE_TEST("Emergency Exit activates when taking residual damage and falling under 50% max-hp (wild battle player side) (Traits)") +{ + GIVEN { + PLAYER(SPECIES_GOLISOPOD) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(134); }; + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SANDSTORM);} + } SCENE { + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_EMERGENCY_EXIT); + } THEN { + EXPECT_EQ(gBattleOutcome, B_OUTCOME_PLAYER_TELEPORTED); + } +} + +SINGLE_BATTLE_TEST("Emergency Exit will trigger due to recoil damage (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_MIND_BLOWN) == EFFECT_MAX_HP_50_RECOIL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_MIND_BLOWN); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIND_BLOWN, opponent); + HP_BAR(player); + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + +SINGLE_BATTLE_TEST("Emergency Exit will trigger due to confusion damage (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_CONFUSE_RAY) == EFFECT_CONFUSE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(133); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_CONFUSE_RAY); + MOVE(opponent, MOVE_POUND); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, opponent); + HP_BAR(opponent); + NOT ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + +SINGLE_BATTLE_TEST("Emergency Exit is not triggered by Pain Split (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_PAIN_SPLIT) == EFFECT_PAIN_SPLIT); + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(133); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PAIN_SPLIT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PAIN_SPLIT, player); + HP_BAR(opponent); + NOT ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + +SINGLE_BATTLE_TEST("Emergency Exit will trigger due to Jump Kick recoil (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_JUMP_KICK) == EFFECT_RECOIL_IF_MISS); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_JUMP_KICK, hit: FALSE); SEND_OUT(opponent, 1); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_JUMP_KICK, opponent); + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Emergency Exit does not switch out when going below 50% max-HP but healed via held item back above the threshold (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); Items(ITEM_PECHA_BERRY, ITEM_SITRUS_BERRY); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUPER_FANG); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + +SINGLE_BATTLE_TEST("Emergency Exit switches out when going below 50% max-HP but healing via held item is not enough to go back above the threshold (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(133); Items(ITEM_PECHA_BERRY, ITEM_ORAN_BERRY); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUPER_FANG); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + +DOUBLE_BATTLE_TEST("Only the fastest Wimp Out (Emergency Exit) user switches out (Multi)") +{ + GIVEN { + PLAYER(SPECIES_ZAPDOS) { Speed(10); } + PLAYER(SPECIES_WOBBUFFET) { Speed(10); } + OPPONENT(SPECIES_WIMPOD) { Speed(1); Ability(ABILITY_WIMP_OUT); Items(ITEM_PECHA_BERRY, ITEM_FOCUS_SASH); }; + OPPONENT(SPECIES_WIMPOD) { Speed(2); Ability(ABILITY_WIMP_OUT); Items(ITEM_PECHA_BERRY, ITEM_FOCUS_SASH); }; + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_HYPER_VOICE); SEND_OUT(opponentRight, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerLeft); + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + ABILITY_POPUP(opponentRight, ABILITY_WIMP_OUT); + } +} + +SINGLE_BATTLE_TEST("Emergency Exit activates when healing from under 50% max-hp and taking residual damage to under 50% max-hp - Sticky Barb (Multi)") +{ + // Might fail if users set healing higher than sticky barb damage + GIVEN { + ASSUME(GetMoveEffect(MOVE_AQUA_RING) == EFFECT_AQUA_RING); + ASSUME(GetItemHoldEffect(ITEM_STICKY_BARB) == HOLD_EFFECT_STICKY_BARB); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(130); Items(ITEM_PECHA_BERRY, ITEM_STICKY_BARB); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_AQUA_RING); SEND_OUT(opponent, 1); } + } SCENE { + HP_BAR(opponent); + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} +#endif diff --git a/test/battle/ability/fairy_aura.c b/test/battle/ability/fairy_aura.c index a51abf1be59f..26c0df06429c 100644 --- a/test/battle/ability/fairy_aura.c +++ b/test/battle/ability/fairy_aura.c @@ -106,3 +106,110 @@ DOUBLE_BATTLE_TEST("Fairy Aura's effect doesn't stack multiple times") EXPECT_EQ(damage[5], damage[2]); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Fairy Aura increases the power of all Fairy-type attacks by 33% (Traits)") +{ + s16 damage[8]; + + GIVEN { + PLAYER(SPECIES_XERNEAS) { Ability(ABILITY_LIGHT_METAL); } + PLAYER(SPECIES_XERNEAS) { Ability(ABILITY_LIGHT_METAL); } + PLAYER(SPECIES_XERNEAS) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FAIRY_AURA); } + OPPONENT(SPECIES_XERNEAS) { Ability(ABILITY_LIGHT_METAL); } + OPPONENT(SPECIES_XERNEAS) { Ability(ABILITY_LIGHT_METAL); } + } WHEN { + TURN { + MOVE(playerLeft, MOVE_PLAY_ROUGH, target:opponentLeft, secondaryEffect:FALSE); + MOVE(playerRight, MOVE_PLAY_ROUGH, target:opponentRight, secondaryEffect:FALSE); + MOVE(opponentLeft, MOVE_PLAY_ROUGH, target:playerLeft, secondaryEffect:FALSE); + MOVE(opponentRight, MOVE_PLAY_ROUGH, target:playerRight, secondaryEffect:FALSE); + } + TURN { SWITCH(playerRight, 2); } + TURN { + MOVE(playerLeft, MOVE_PLAY_ROUGH, target:opponentLeft, secondaryEffect:FALSE); + MOVE(playerRight, MOVE_PLAY_ROUGH, target:opponentRight, secondaryEffect:FALSE); + MOVE(opponentLeft, MOVE_PLAY_ROUGH, target:playerLeft, secondaryEffect:FALSE); + MOVE(opponentRight, MOVE_PLAY_ROUGH, target:playerRight, secondaryEffect:FALSE); + } + } SCENE { + // Turn 1 + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLAY_ROUGH, opponentRight); + HP_BAR(playerRight, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLAY_ROUGH, playerRight); + HP_BAR(opponentRight, captureDamage: &damage[1]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLAY_ROUGH, opponentLeft); + HP_BAR(playerLeft, captureDamage: &damage[2]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLAY_ROUGH, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damage[3]); + + // Turn 2 + ABILITY_POPUP(playerRight, ABILITY_FAIRY_AURA); + + // Turn 3 + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLAY_ROUGH, opponentRight); + HP_BAR(playerRight, captureDamage: &damage[7]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLAY_ROUGH, playerRight); + HP_BAR(opponentRight, captureDamage: &damage[5]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLAY_ROUGH, opponentLeft); + HP_BAR(playerLeft, captureDamage: &damage[6]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLAY_ROUGH, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damage[4]); + + } THEN { + EXPECT_MUL_EQ(damage[0], UQ_4_12(1.333), damage[4]); + EXPECT_MUL_EQ(damage[1], UQ_4_12(1.333), damage[5]); + EXPECT_MUL_EQ(damage[2], UQ_4_12(1.333), damage[6]); + EXPECT_MUL_EQ(damage[3], UQ_4_12(1.333), damage[7]); + } +} + +DOUBLE_BATTLE_TEST("Fairy Aura's effect doesn't stack multiple times (Traits)") +{ + s16 damage[6]; + + GIVEN { + PLAYER(SPECIES_XERNEAS) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FAIRY_AURA); } + PLAYER(SPECIES_WOBBUFFET) { HP(9999); MaxHP(9999); } + PLAYER(SPECIES_XERNEAS) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FAIRY_AURA); } + OPPONENT(SPECIES_WOBBUFFET) { HP(9999); MaxHP(9999); } + OPPONENT(SPECIES_WOBBUFFET) { HP(9999); MaxHP(9999); } + } WHEN { + TURN { + MOVE(playerLeft, MOVE_PLAY_ROUGH, target:opponentLeft, secondaryEffect:FALSE); + MOVE(opponentLeft, MOVE_PLAY_ROUGH, target:playerLeft, secondaryEffect:FALSE); + MOVE(opponentRight, MOVE_PLAY_ROUGH, target:playerLeft, secondaryEffect:FALSE); + } + TURN { SWITCH(playerRight, 2); } + TURN { + MOVE(playerLeft, MOVE_PLAY_ROUGH, target:opponentLeft, secondaryEffect:FALSE); + MOVE(opponentLeft, MOVE_PLAY_ROUGH, target:playerLeft, secondaryEffect:FALSE); + MOVE(opponentRight, MOVE_PLAY_ROUGH, target:playerLeft, secondaryEffect:FALSE); + } + } SCENE { + // Turn 1 + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLAY_ROUGH, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLAY_ROUGH, opponentLeft); + HP_BAR(playerLeft, captureDamage: &damage[1]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLAY_ROUGH, opponentRight); + HP_BAR(playerLeft, captureDamage: &damage[2]); + + // Turn 2 + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Xerneas"); + + // Turn 3 + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLAY_ROUGH, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damage[3]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLAY_ROUGH, opponentLeft); + HP_BAR(playerLeft, captureDamage: &damage[4]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLAY_ROUGH, opponentRight); + HP_BAR(playerLeft, captureDamage: &damage[5]); + } THEN { + EXPECT_EQ(damage[3], damage[0]); + EXPECT_EQ(damage[4], damage[1]); + EXPECT_EQ(damage[5], damage[2]); + } +} +#endif diff --git a/test/battle/ability/filter.c b/test/battle/ability/filter.c index db3d353b7982..a3723e584dc3 100644 --- a/test/battle/ability/filter.c +++ b/test/battle/ability/filter.c @@ -23,3 +23,28 @@ SINGLE_BATTLE_TEST("Filter reduces damage to Super Effective moves by 0.75", s16 EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.75), results[1].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Filter reduces damage to Super Effective moves by 0.75 (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; } + PARAMETRIZE { ability = ABILITY_FILTER; } + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_MR_MIME].types[0] == TYPE_PSYCHIC); + ASSUME(gSpeciesInfo[SPECIES_MR_MIME].types[1] == TYPE_FAIRY); + ASSUME(GetMoveType(MOVE_POISON_JAB) == TYPE_POISON); + ASSUME(gTypeEffectivenessTable[TYPE_POISON][TYPE_FAIRY] > UQ_4_12(1.0)); + ASSUME(gTypeEffectivenessTable[TYPE_POISON][TYPE_PSYCHIC] == UQ_4_12(1.0)); + PLAYER(SPECIES_MR_MIME) { Ability(ABILITY_TECHNICIAN); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_POISON_JAB); } + } SCENE { + HP_BAR(player, captureDamage: &results[i].damage); + MESSAGE("It's super effective!"); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.75), results[1].damage); + } +} +#endif diff --git a/test/battle/ability/flame_body.c b/test/battle/ability/flame_body.c index 8db6f2c142c5..e78166c178fd 100644 --- a/test/battle/ability/flame_body.c +++ b/test/battle/ability/flame_body.c @@ -50,3 +50,52 @@ SINGLE_BATTLE_TEST("Flame Body triggers 1/3 times (Gen3) or 30% (Gen 4+) of the STATUS_ICON(player, burn: TRUE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Flame Body inflicts burn on contact (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_SWIFT; } + GIVEN { + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + ASSUME(!MoveMakesContact(MOVE_SWIFT)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MAGMAR) { Ability(ABILITY_VITAL_SPIRIT); Innates(ABILITY_FLAME_BODY); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + if (MoveMakesContact(move)) { + ABILITY_POPUP(opponent, ABILITY_FLAME_BODY); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, player); + MESSAGE("The opposing Magmar's Flame Body burned Wobbuffet!"); + STATUS_ICON(player, burn: TRUE); + } else { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_FLAME_BODY); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, player); + MESSAGE("The opposing Magmar's Flame Body burned Wobbuffet!"); + STATUS_ICON(player, burn: TRUE); + } + } + } +} + +SINGLE_BATTLE_TEST("Flame Body triggers 30% of the time (Traits)") +{ + PASSES_RANDOMLY(3, 10, RNG_FLAME_BODY); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_4); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MAGMAR) { Ability(ABILITY_VITAL_SPIRIT); Innates(ABILITY_FLAME_BODY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_FLAME_BODY); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, player); + MESSAGE("The opposing Magmar's Flame Body burned Wobbuffet!"); + STATUS_ICON(player, burn: TRUE); + } +} +#endif diff --git a/test/battle/ability/flare_boost.c b/test/battle/ability/flare_boost.c index 54bd3b9d38b7..03bbbfe681cf 100644 --- a/test/battle/ability/flare_boost.c +++ b/test/battle/ability/flare_boost.c @@ -19,3 +19,24 @@ SINGLE_BATTLE_TEST("Flare Boost increases Sp. Attack by 50% when the Pokémon is EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Flare Boost increases Sp. Attack by 50% when the Pokémon is burned (Traits)", s16 damage) +{ + u32 status1; + PARAMETRIZE { status1 = STATUS1_NONE; } + PARAMETRIZE { status1 = STATUS1_BURN; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_SWIFT) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_DRIFBLIM) { Ability(ABILITY_UNBURDEN); Innates(ABILITY_FLARE_BOOST); Status1(status1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SWIFT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWIFT, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} +#endif diff --git a/test/battle/ability/flash_fire.c b/test/battle/ability/flash_fire.c index 4cdcec997348..15733c136cfd 100644 --- a/test/battle/ability/flash_fire.c +++ b/test/battle/ability/flash_fire.c @@ -26,3 +26,31 @@ SINGLE_BATTLE_TEST("Flash Fire boosts fire type moves by 50% but no subsequent i EXPECT_EQ(damage[1], damage[2]); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Flash Fire boosts fire type moves by 50% but no subsequent increase is applied (Traits)") +{ + s16 damage[3]; + + GIVEN { + PLAYER(SPECIES_HEATRAN) { Ability(ABILITY_FLAME_BODY); Innates(ABILITY_FLASH_FIRE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_EMBER, secondaryEffect: FALSE); MOVE(opponent, MOVE_EMBER); } + TURN { MOVE(player, MOVE_EMBER, secondaryEffect: FALSE); MOVE(opponent, MOVE_EMBER); } + TURN { MOVE(player, MOVE_EMBER, secondaryEffect: FALSE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ABILITY_POPUP(player, ABILITY_FLASH_FIRE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, player); + HP_BAR(opponent, captureDamage: &damage[1]); + ABILITY_POPUP(player, ABILITY_FLASH_FIRE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, player); + HP_BAR(opponent, captureDamage: &damage[2]); + } THEN { + EXPECT_MUL_EQ(damage[0], UQ_4_12(1.5), damage[1]); + EXPECT_EQ(damage[1], damage[2]); + } +} +#endif diff --git a/test/battle/ability/flower_gift.c b/test/battle/ability/flower_gift.c index 8e5f9faca070..a19f82350c97 100644 --- a/test/battle/ability/flower_gift.c +++ b/test/battle/ability/flower_gift.c @@ -256,3 +256,202 @@ SINGLE_BATTLE_TEST("Flower Gift does not transform Cherrim back to normal when s EXPECT_EQ(player->species, SPECIES_CHERRIM_SUNSHINE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Flower Gift transforms Cherrim in harsh sunlight (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CHERRIM_OVERCAST) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FLOWER_GIFT); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ABILITY_POPUP(player, ABILITY_FLOWER_GIFT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Cherrim transformed!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_CHERRIM_SUNSHINE); + } +} + +TO_DO_BATTLE_TEST("Flower Gift doesn't transform Cherrim if Cloud Nine/Air Lock is on the field (Traits)"); + +SINGLE_BATTLE_TEST("Flower Gift transforms Cherrim back to normal when weather changes (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CHERRIM_OVERCAST) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FLOWER_GIFT); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); } + TURN { MOVE(opponent, MOVE_RAIN_DANCE); } + } SCENE { + // transforms in sun + ABILITY_POPUP(player, ABILITY_FLOWER_GIFT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Cherrim transformed!"); + // back to normal + ABILITY_POPUP(player, ABILITY_FLOWER_GIFT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Cherrim transformed!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_CHERRIM_OVERCAST); + } +} + +SINGLE_BATTLE_TEST("Flower Gift transforms Cherrim back to normal under Cloud Nine/Air Lock (Traits)") +{ + u32 species = 0; + enum Ability ability = 0; + PARAMETRIZE { species = SPECIES_PSYDUCK; ability = ABILITY_CLOUD_NINE; } + PARAMETRIZE { species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } + GIVEN { + PLAYER(SPECIES_CHERRIM_OVERCAST) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FLOWER_GIFT); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); } + TURN { SWITCH(opponent, 1); } + } SCENE { + // transforms + ABILITY_POPUP(player, ABILITY_FLOWER_GIFT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Cherrim transformed!"); + // back to normal + ABILITY_POPUP(opponent, ability); + ABILITY_POPUP(player, ABILITY_FLOWER_GIFT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Cherrim transformed!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_CHERRIM_OVERCAST); + } +} + +DOUBLE_BATTLE_TEST("Flower Gift increases the attack of Cherrim and its allies by 1.5x (Traits)", s16 damageL, s16 damageR) +{ + bool32 sunny; + PARAMETRIZE { sunny = FALSE; } + PARAMETRIZE { sunny = TRUE; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_CHERRIM_OVERCAST) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FLOWER_GIFT); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (sunny) + TURN { MOVE(playerLeft, MOVE_SUNNY_DAY); } + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); + MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + // sun activates + if (sunny) { + ABILITY_POPUP(playerLeft, ABILITY_FLOWER_GIFT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, playerLeft); + MESSAGE("Cherrim transformed!"); + } + // player uses Scratch + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft); + HP_BAR(opponentLeft, captureDamage: &results[i].damageL); + // partner uses Scratch + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerRight); + HP_BAR(opponentLeft, captureDamage: &results[i].damageR); + } FINALLY { + EXPECT_MUL_EQ(results[0].damageL, UQ_4_12(1.5), results[1].damageL); + EXPECT_MUL_EQ(results[0].damageR, UQ_4_12(1.5), results[1].damageR); + } +} + +DOUBLE_BATTLE_TEST("Flower Gift increases the Sp. Def of Cherrim and its allies by 1.5x (Traits)", s16 damageL, s16 damageR) +{ + bool32 sunny; + PARAMETRIZE { sunny = FALSE; } + PARAMETRIZE { sunny = TRUE; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_HYPER_VOICE) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_CHERRIM_OVERCAST) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FLOWER_GIFT); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (sunny) + TURN { MOVE(playerLeft, MOVE_SUNNY_DAY); } + TURN { MOVE(opponentLeft, MOVE_HYPER_VOICE, target: playerLeft); } + } SCENE { + // sun activates + if (sunny) { + ABILITY_POPUP(playerLeft, ABILITY_FLOWER_GIFT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, playerLeft); + MESSAGE("Cherrim transformed!"); + } + // opponentLeft uses Hyper Voice + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, opponentLeft); + HP_BAR(playerLeft, captureDamage: &results[i].damageL); + HP_BAR(playerRight, captureDamage: &results[i].damageR); + } FINALLY { + EXPECT_MUL_EQ(results[1].damageL, UQ_4_12(1.5), results[0].damageL); + EXPECT_MUL_EQ(results[1].damageR, UQ_4_12(1.5), results[0].damageR); + } +} + +SINGLE_BATTLE_TEST("Flower Gift transforms Cherrim back when it switches out (Traits)") +{ + GIVEN { + ASSUME(B_WEATHER_FORMS >= GEN_5); + PLAYER(SPECIES_CHERRIM) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FLOWER_GIFT); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); } + TURN { SWITCH(player, 1); } + } SCENE { + // transforms in sun + ABILITY_POPUP(player, ABILITY_FLOWER_GIFT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Cherrim transformed!"); + SWITCH_OUT_MESSAGE("Cherrim"); + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), SPECIES_CHERRIM); + } +} + +SINGLE_BATTLE_TEST("Flower Gift transforms Cherrim back when it uses a move that forces it to switch out (Traits)") +{ + GIVEN { + ASSUME(B_WEATHER_FORMS >= GEN_5); + PLAYER(SPECIES_CHERRIM) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FLOWER_GIFT); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); } + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); } + } SCENE { + // transforms in sun + ABILITY_POPUP(player, ABILITY_FLOWER_GIFT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Cherrim transformed!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), SPECIES_CHERRIM); + } +} + +DOUBLE_BATTLE_TEST("Flower Gift reverts Cherrim back after Teraform Zero clears weather (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TERAPAGOS_TERASTAL); + PLAYER(SPECIES_CHERRIM) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FLOWER_GIFT); } + OPPONENT(SPECIES_GROUDON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DROUGHT); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_DROUGHT); + ABILITY_POPUP(playerRight, ABILITY_FLOWER_GIFT); + ABILITY_POPUP(playerLeft, ABILITY_TERAFORM_ZERO); + ABILITY_POPUP(playerRight, ABILITY_FLOWER_GIFT); + } THEN { + EXPECT_EQ(playerRight->species, SPECIES_CHERRIM); + } +} + +#endif diff --git a/test/battle/ability/flower_veil.c b/test/battle/ability/flower_veil.c index 69a31958e180..e02c5f780b13 100644 --- a/test/battle/ability/flower_veil.c +++ b/test/battle/ability/flower_veil.c @@ -82,3 +82,74 @@ DOUBLE_BATTLE_TEST("Flower Veil's stat reduction protection considers Contrary") EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Flower Veil prevents status on allied Grass-types - right target (Traits)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISON_GAS; } + PARAMETRIZE { move = MOVE_WILL_O_WISP; } + PARAMETRIZE { move = MOVE_THUNDER_WAVE; } + PARAMETRIZE { move = MOVE_HYPNOSIS; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_COMFEY) { Ability(ABILITY_NATURAL_CURE); Innates(ABILITY_FLOWER_VEIL); } + OPPONENT(SPECIES_CHIKORITA); + } WHEN { + TURN { MOVE(playerLeft, move, target: opponentRight); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, move, playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_FLOWER_VEIL); + MESSAGE("The opposing Chikorita surrounded itself with a veil of petals!"); + } +} + +DOUBLE_BATTLE_TEST("Flower Veil prevents status on allied Grass-types - left target (Traits)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISON_GAS; } + PARAMETRIZE { move = MOVE_WILL_O_WISP; } + PARAMETRIZE { move = MOVE_THUNDER_WAVE; } + PARAMETRIZE { move = MOVE_HYPNOSIS; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_CHIKORITA); + OPPONENT(SPECIES_COMFEY) { Ability(ABILITY_NATURAL_CURE); Innates(ABILITY_FLOWER_VEIL); } + } WHEN { + TURN { MOVE(playerLeft, move, target: opponentLeft); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, move, playerLeft); + ABILITY_POPUP(opponentRight, ABILITY_FLOWER_VEIL); + MESSAGE("The opposing Chikorita surrounded itself with a veil of petals!"); + } +} + +DOUBLE_BATTLE_TEST("Flower Veil's stat reduction protection considers Contrary (Traits)") // Eg. If a move would reduce stats due to Contrary, it will be protected by Mist. +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SWAGGER) == EFFECT_SWAGGER); + ASSUME(GetSpeciesType(SPECIES_SNIVY, 0) == TYPE_GRASS || GetSpeciesType(SPECIES_SNIVY, 1) == TYPE_GRASS); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_COMFEY) { Ability(ABILITY_NATURAL_CURE); Innates(ABILITY_FLOWER_VEIL); } + OPPONENT(SPECIES_SNIVY) { Ability(ABILITY_OVERGROW); Innates(ABILITY_CONTRARY); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SWAGGER, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWAGGER, playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_FLOWER_VEIL); + MESSAGE("The opposing Snivy surrounded itself with a veil of petals!"); + } THEN { + EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} + +#endif diff --git a/test/battle/ability/fluffy.c b/test/battle/ability/fluffy.c index 68afbd8993cc..704fdf52b76e 100644 --- a/test/battle/ability/fluffy.c +++ b/test/battle/ability/fluffy.c @@ -99,3 +99,133 @@ SINGLE_BATTLE_TEST("Fluffy does not halve damage taken from moves that make dire EXPECT_EQ(results[0].damage, results[1].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Fluffy halves damage taken from moves that make direct contact (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_KLUTZ; } + PARAMETRIZE { ability = ABILITY_FLUFFY; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_STUFFUL) { Ability(ABILITY_CUTE_CHARM); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(0.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Fluffy doubles damage taken from fire type moves (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_KLUTZ; } + PARAMETRIZE { ability = ABILITY_FLUFFY; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_STUFFUL) { Ability(ABILITY_CUTE_CHARM); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_EMBER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Fluffy does not alter damage of fire-type moves that make direct contact (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_KLUTZ; } + PARAMETRIZE { ability = ABILITY_FLUFFY; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_STUFFUL) { Ability(ABILITY_CUTE_CHARM); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_FIRE_PUNCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PUNCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Fluffy halves damage taken from moves that make direct contact even if protected by Protective Pads (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_KLUTZ; } + PARAMETRIZE { ability = ABILITY_FLUFFY; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_PROTECTIVE_PADS); } + OPPONENT(SPECIES_STUFFUL) { Ability(ABILITY_CUTE_CHARM); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(0.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Fluffy does not halve damage taken from moves that make direct contact but are ignored by Punching Glove (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_KLUTZ; } + PARAMETRIZE { ability = ABILITY_FLUFFY; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_PUNCHING_GLOVE); } + OPPONENT(SPECIES_STUFFUL) { Ability(ABILITY_CUTE_CHARM); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_THUNDER_PUNCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_PUNCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Fluffy halves damage taken from moves that make direct contact even if protected by Protective Pads (Multi)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_KLUTZ; } + PARAMETRIZE { ability = ABILITY_FLUFFY; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_PROTECTIVE_PADS); } + OPPONENT(SPECIES_STUFFUL) { Ability(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(0.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Fluffy does not halve damage taken from moves that make direct contact but are ignored by Punching Glove (Multi)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_KLUTZ; } + PARAMETRIZE { ability = ABILITY_FLUFFY; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_PUNCHING_GLOVE); } + OPPONENT(SPECIES_STUFFUL) { Ability(ability); } + } WHEN { + TURN { MOVE(player, MOVE_THUNDER_PUNCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_PUNCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} +#endif diff --git a/test/battle/ability/forecast.c b/test/battle/ability/forecast.c index b6889620ab16..11dd0ca2eb74 100644 --- a/test/battle/ability/forecast.c +++ b/test/battle/ability/forecast.c @@ -446,3 +446,463 @@ DOUBLE_BATTLE_TEST("Forecast reverts Castform back after Teraform Zero clears we EXPECT_EQ(playerRight->species, SPECIES_CASTFORM_NORMAL); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Forecast transforms Castform in weather from an opponent's move (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_SUNNY_DAY; } + PARAMETRIZE { move = MOVE_RAIN_DANCE; } + PARAMETRIZE { move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + GIVEN { + PLAYER(SPECIES_CASTFORM_NORMAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FORECAST); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ABILITY_POPUP(player, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Castform transformed!"); + } THEN { + switch (move) + { + case MOVE_SUNNY_DAY: + EXPECT_EQ(player->species, SPECIES_CASTFORM_SUNNY); + break; + case MOVE_RAIN_DANCE: + EXPECT_EQ(player->species, SPECIES_CASTFORM_RAINY); + break; + case MOVE_HAIL: + case MOVE_SNOWSCAPE: + EXPECT_EQ(player->species, SPECIES_CASTFORM_SNOWY); + break; + } + } +} + +SINGLE_BATTLE_TEST("Forecast transforms Castform in weather from its own move (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_SUNNY_DAY; } + PARAMETRIZE { move = MOVE_RAIN_DANCE; } + PARAMETRIZE { move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + GIVEN { + PLAYER(SPECIES_CASTFORM_NORMAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FORECAST); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ABILITY_POPUP(player, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Castform transformed!"); + } THEN { + switch (move) + { + case MOVE_SUNNY_DAY: + EXPECT_EQ(player->species, SPECIES_CASTFORM_SUNNY); + break; + case MOVE_RAIN_DANCE: + EXPECT_EQ(player->species, SPECIES_CASTFORM_RAINY); + break; + case MOVE_HAIL: + case MOVE_SNOWSCAPE: + EXPECT_EQ(player->species, SPECIES_CASTFORM_SNOWY); + break; + } + } +} + +DOUBLE_BATTLE_TEST("Forecast transforms Castform in weather from a partner's move (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_SUNNY_DAY; } + PARAMETRIZE { move = MOVE_RAIN_DANCE; } + PARAMETRIZE { move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + GIVEN { + PLAYER(SPECIES_CASTFORM_NORMAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FORECAST); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, move); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, playerLeft); + MESSAGE("Castform transformed!"); + } THEN { + switch (move) + { + case MOVE_SUNNY_DAY: + EXPECT_EQ(playerLeft->species, SPECIES_CASTFORM_SUNNY); + break; + case MOVE_RAIN_DANCE: + EXPECT_EQ(playerLeft->species, SPECIES_CASTFORM_RAINY); + break; + case MOVE_HAIL: + case MOVE_SNOWSCAPE: + EXPECT_EQ(playerLeft->species, SPECIES_CASTFORM_SNOWY); + break; + } + } +} + +DOUBLE_BATTLE_TEST("Forecast transforms all Castforms present in weather (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_SUNNY_DAY; } + PARAMETRIZE { move = MOVE_RAIN_DANCE; } + PARAMETRIZE { move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + GIVEN { + PLAYER(SPECIES_CASTFORM_NORMAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FORECAST); Speed(10); } + PLAYER(SPECIES_CASTFORM_NORMAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FORECAST); Speed(5); } + OPPONENT(SPECIES_CASTFORM_NORMAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FORECAST); Speed(7); } + OPPONENT(SPECIES_CASTFORM_NORMAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FORECAST); Speed(1); } + } WHEN { + TURN { MOVE(playerRight, move); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, playerLeft); + MESSAGE("Castform transformed!"); + ABILITY_POPUP(opponentLeft, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, opponentLeft); + MESSAGE("The opposing Castform transformed!"); + ABILITY_POPUP(playerRight, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, playerRight); + MESSAGE("Castform transformed!"); + ABILITY_POPUP(opponentRight, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, opponentRight); + MESSAGE("The opposing Castform transformed!"); + } THEN { + switch (move) + { + case MOVE_SUNNY_DAY: + EXPECT_EQ(playerLeft->species, SPECIES_CASTFORM_SUNNY); + EXPECT_EQ(playerRight->species, SPECIES_CASTFORM_SUNNY); + EXPECT_EQ(opponentLeft->species, SPECIES_CASTFORM_SUNNY); + EXPECT_EQ(opponentRight->species, SPECIES_CASTFORM_SUNNY); + break; + case MOVE_RAIN_DANCE: + EXPECT_EQ(playerLeft->species, SPECIES_CASTFORM_RAINY); + EXPECT_EQ(playerRight->species, SPECIES_CASTFORM_RAINY); + EXPECT_EQ(opponentLeft->species, SPECIES_CASTFORM_RAINY); + EXPECT_EQ(opponentRight->species, SPECIES_CASTFORM_RAINY); + break; + case MOVE_HAIL: + case MOVE_SNOWSCAPE: + EXPECT_EQ(playerLeft->species, SPECIES_CASTFORM_SNOWY); + EXPECT_EQ(playerRight->species, SPECIES_CASTFORM_SNOWY); + EXPECT_EQ(opponentLeft->species, SPECIES_CASTFORM_SNOWY); + EXPECT_EQ(opponentRight->species, SPECIES_CASTFORM_SNOWY); + break; + } + } +} + +SINGLE_BATTLE_TEST("Forecast transforms Castform in weather from an ability (Traits)") +{ + u32 species; + enum Ability ability; + PARAMETRIZE { species = SPECIES_KYOGRE; ability = ABILITY_DRIZZLE; } + PARAMETRIZE { species = SPECIES_GROUDON; ability = ABILITY_DROUGHT; } + PARAMETRIZE { species = SPECIES_ABOMASNOW; ability = ABILITY_SNOW_WARNING; } + GIVEN { + PLAYER(SPECIES_CASTFORM_NORMAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FORECAST); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { SWITCH(opponent, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Castform transformed!"); + } THEN { + switch (ability) + { + case ABILITY_DROUGHT: + EXPECT_EQ(player->species, SPECIES_CASTFORM_SUNNY); + break; + case ABILITY_DRIZZLE: + EXPECT_EQ(player->species, SPECIES_CASTFORM_RAINY); + break; + case ABILITY_SNOW_WARNING: + EXPECT_EQ(player->species, SPECIES_CASTFORM_SNOWY); + break; + default: + break; + } + } +} + +SINGLE_BATTLE_TEST("Forecast transforms Castform in primal weather (Traits)") +{ + u32 species, item; + enum Ability ability; + PARAMETRIZE { species = SPECIES_KYOGRE; ability = ABILITY_PRIMORDIAL_SEA; item = ITEM_BLUE_ORB; } + PARAMETRIZE { species = SPECIES_GROUDON; ability = ABILITY_DESOLATE_LAND; item = ITEM_RED_ORB; } + GIVEN { + PLAYER(SPECIES_CASTFORM_NORMAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FORECAST); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(species) { Item(item); } + } WHEN { + TURN { SWITCH(opponent, 1); } + } SCENE { + ABILITY_POPUP(opponent, ability); + ABILITY_POPUP(player, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Castform transformed!"); + } THEN { + switch (ability) + { + case ABILITY_DESOLATE_LAND: + EXPECT_EQ(player->species, SPECIES_CASTFORM_SUNNY); + break; + case ABILITY_PRIMORDIAL_SEA: + EXPECT_EQ(player->species, SPECIES_CASTFORM_RAINY); + break; + default: + break; + } + } +} + +SINGLE_BATTLE_TEST("Forecast transforms Castform back to normal when weather expires (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CASTFORM_NORMAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FORECAST); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_RAIN_DANCE); } + TURN {} + TURN {} + TURN {} + TURN {} + TURN {} + } SCENE { + // transforms + ABILITY_POPUP(player, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Castform transformed!"); + // back to normal + ABILITY_POPUP(player, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Castform transformed!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_CASTFORM_NORMAL); + } +} + +SINGLE_BATTLE_TEST("Forecast transforms Castform back to normal when Sandstorm is active (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CASTFORM_NORMAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FORECAST); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_RAIN_DANCE); } + TURN { MOVE(player, MOVE_SANDSTORM); } + } SCENE { + // transforms + ABILITY_POPUP(player, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Castform transformed!"); + // back to normal + ABILITY_POPUP(player, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Castform transformed!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_CASTFORM_NORMAL); + } +} + +SINGLE_BATTLE_TEST("Forecast transforms Castform back to normal under Cloud Nine/Air Lock (Traits)") +{ + u32 species = 0; + enum Ability ability = 0; + PARAMETRIZE { species = SPECIES_PSYDUCK; ability = ABILITY_CLOUD_NINE; } + PARAMETRIZE { species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } + GIVEN { + PLAYER(SPECIES_CASTFORM_NORMAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FORECAST); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ability); } + } WHEN { + TURN { MOVE(player, MOVE_RAIN_DANCE); } + TURN { SWITCH(opponent, 1); } + } SCENE { + // transforms + ABILITY_POPUP(player, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Castform transformed!"); + // back to normal + ABILITY_POPUP(opponent, ability); + ABILITY_POPUP(player, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Castform transformed!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_CASTFORM_NORMAL); + } +} + +SINGLE_BATTLE_TEST("Forecast transforms Castform on switch-in (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_CASTFORM_NORMAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FORECAST); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_RAIN_DANCE); } + TURN { SWITCH(player, 1); } + } SCENE { + // turn 1 + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAIN_DANCE, player); + // turn 2 + ABILITY_POPUP(player, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Castform transformed!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_CASTFORM_RAINY); + } +} + +SINGLE_BATTLE_TEST("Forecast transforms Castform when weather changes (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CASTFORM_NORMAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FORECAST); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_RAIN_DANCE); } + TURN { MOVE(player, MOVE_SUNNY_DAY); } + } SCENE { + // transforms + ABILITY_POPUP(player, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Castform transformed!"); + // transforms again + ABILITY_POPUP(player, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Castform transformed!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_CASTFORM_SUNNY); + } +} + +SINGLE_BATTLE_TEST("Forecast transforms Castform back when it switches out (Traits)") +{ + GIVEN { + ASSUME(B_WEATHER_FORMS >= GEN_5); + PLAYER(SPECIES_CASTFORM) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FORECAST); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); } + TURN { SWITCH(player, 1); } + } SCENE { + // transforms in sun + ABILITY_POPUP(player, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Castform transformed!"); + SWITCH_OUT_MESSAGE("Castform"); + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), SPECIES_CASTFORM); + } +} + +SINGLE_BATTLE_TEST("Forecast transforms Castform back when it uses a move that forces it to switch out (Traits)") +{ + GIVEN { + ASSUME(B_WEATHER_FORMS >= GEN_5); + PLAYER(SPECIES_CASTFORM) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FORECAST); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); } + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); } + } SCENE { + // transforms in sun + ABILITY_POPUP(player, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Castform transformed!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), SPECIES_CASTFORM); + } +} + +SINGLE_BATTLE_TEST("Forecast transforms Castform when Cloud Nine ability user leaves the field (Traits)") +{ + u32 species = 0; + enum Ability ability = 0; + PARAMETRIZE { species = SPECIES_PSYDUCK; ability = ABILITY_CLOUD_NINE; } + PARAMETRIZE { species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } + + GIVEN { + PLAYER(SPECIES_CASTFORM) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FORECAST); } + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_CELEBRATE); } + TURN { SWITCH(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + MESSAGE("2 sent out Wobbuffet!"); + ABILITY_POPUP(player, ABILITY_FORECAST); + MESSAGE("Castform transformed!"); + } +} + +DOUBLE_BATTLE_TEST("Forecast reverts Castform back after Teraform Zero clears weather (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TERAPAGOS_TERASTAL); + PLAYER(SPECIES_CASTFORM) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FORECAST); } + OPPONENT(SPECIES_KYOGRE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DRIZZLE); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_DRIZZLE); + ABILITY_POPUP(playerRight, ABILITY_FORECAST); + ABILITY_POPUP(playerLeft, ABILITY_TERAFORM_ZERO); + ABILITY_POPUP(playerRight, ABILITY_FORECAST); + } THEN { + EXPECT_EQ(playerRight->species, SPECIES_CASTFORM_NORMAL); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Forecast transforms Castform in primal weather (Multi)") +{ + u32 species, item; + enum Ability ability; + PARAMETRIZE { species = SPECIES_KYOGRE; ability = ABILITY_PRIMORDIAL_SEA; item = ITEM_BLUE_ORB; } + PARAMETRIZE { species = SPECIES_GROUDON; ability = ABILITY_DESOLATE_LAND; item = ITEM_RED_ORB; } + GIVEN { + PLAYER(SPECIES_CASTFORM_NORMAL) { Ability(ABILITY_FORECAST); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(species) { Items(ITEM_PECHA_BERRY, item); } + } WHEN { + TURN { SWITCH(opponent, 1); } + } SCENE { + ABILITY_POPUP(opponent, ability); + ABILITY_POPUP(player, ABILITY_FORECAST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Castform transformed!"); + } THEN { + switch (ability) + { + case ABILITY_DESOLATE_LAND: + EXPECT_EQ(player->species, SPECIES_CASTFORM_SUNNY); + break; + case ABILITY_PRIMORDIAL_SEA: + EXPECT_EQ(player->species, SPECIES_CASTFORM_RAINY); + break; + default: + break; + } + } +} +#endif diff --git a/test/battle/ability/frisk.c b/test/battle/ability/frisk.c index b39f53c4819e..84aea719b1d2 100644 --- a/test/battle/ability/frisk.c +++ b/test/battle/ability/frisk.c @@ -80,3 +80,148 @@ DOUBLE_BATTLE_TEST("Frisk triggers for opponent in a Double Battle after switchi MESSAGE("The opposing Furret frisked Wynaut and found its Potion!"); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Frisk does not trigger when Pokémon hold no items (Traits)") +{ + GIVEN { + PLAYER(SPECIES_FURRET) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_FRISK); }; + PLAYER(SPECIES_FURRET) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_FRISK); }; + OPPONENT(SPECIES_SENTRET) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_FRISK); }; + OPPONENT(SPECIES_SENTRET) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_FRISK); }; + } WHEN { + TURN { ; } + } SCENE { + NONE_OF { + ABILITY_POPUP(playerLeft, ABILITY_FRISK); + ABILITY_POPUP(playerRight, ABILITY_FRISK); + ABILITY_POPUP(opponentLeft, ABILITY_FRISK); + ABILITY_POPUP(opponentRight, ABILITY_FRISK); + } + } +} + +SINGLE_BATTLE_TEST("Frisk triggers in a Single Battle (Traits)") +{ + GIVEN { + PLAYER(SPECIES_FURRET) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_FRISK); Item(ITEM_POTION); }; + OPPONENT(SPECIES_SENTRET) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_FRISK); Item(ITEM_POTION); }; + } WHEN { + TURN { ; } + } SCENE { + ABILITY_POPUP(player, ABILITY_FRISK); + MESSAGE("Furret frisked the opposing Sentret and found its Potion!"); + ABILITY_POPUP(opponent, ABILITY_FRISK); + MESSAGE("The opposing Sentret frisked Furret and found its Potion!"); + } +} + +DOUBLE_BATTLE_TEST("Frisk triggers for player in a Double Battle after switching-in after fainting (Traits)") +{ + struct BattlePokemon *target = NULL; + PARAMETRIZE { target = playerLeft; } + PARAMETRIZE { target = playerRight; } + + GIVEN { + ASSUME(!IsBattleMoveStatus(MOVE_POUND)); + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_FURRET) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_FRISK); }; + OPPONENT(SPECIES_WYNAUT) { Item(ITEM_POTION); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_POUND, target: target); SEND_OUT(target, 2); } + } SCENE { + MESSAGE("The opposing Wynaut used Pound!"); + MESSAGE("Wobbuffet fainted!"); + ABILITY_POPUP(target, ABILITY_FRISK); + MESSAGE("Furret frisked the opposing Wynaut and found its Potion!"); + } +} + +DOUBLE_BATTLE_TEST("Frisk triggers for opponent in a Double Battle after switching-in after fainting (Traits)") +{ + struct BattlePokemon *target = NULL; + PARAMETRIZE { target = opponentLeft; } + PARAMETRIZE { target = opponentRight; } + + GIVEN { + ASSUME(!IsBattleMoveStatus(MOVE_POUND)); + PLAYER(SPECIES_WYNAUT) { Item(ITEM_POTION); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_FURRET) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_FRISK); }; + } WHEN { + TURN { MOVE(playerLeft, MOVE_POUND, target: target); SEND_OUT(target, 2); } + } SCENE { + MESSAGE("Wynaut used Pound!"); + MESSAGE("The opposing Wobbuffet fainted!"); + ABILITY_POPUP(target, ABILITY_FRISK); + MESSAGE("The opposing Furret frisked Wynaut and found its Potion!"); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Frisk triggers in a Single Battle (Multi)") +{ + GIVEN { + PLAYER(SPECIES_FURRET) { Ability(ABILITY_FRISK); Items(ITEM_PECHA_BERRY, ITEM_POTION); }; + OPPONENT(SPECIES_SENTRET) { Ability(ABILITY_FRISK); Items(ITEM_PECHA_BERRY, ITEM_POTION); }; + } WHEN { + TURN { ; } + } SCENE { + ABILITY_POPUP(player, ABILITY_FRISK); + MESSAGE("Furret frisked the opposing Sentret and found its Potion!"); + ABILITY_POPUP(opponent, ABILITY_FRISK); + MESSAGE("The opposing Sentret frisked Furret and found its Potion!"); + } +} + +DOUBLE_BATTLE_TEST("Frisk triggers for player in a Double Battle after switching-in after fainting (Multi)") +{ + struct BattlePokemon *target = NULL; + PARAMETRIZE { target = playerLeft; } + PARAMETRIZE { target = playerRight; } + + GIVEN { + ASSUME(!IsBattleMoveStatus(MOVE_POUND)); + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_FURRET) { Ability(ABILITY_FRISK); }; + OPPONENT(SPECIES_WYNAUT) { Items(ITEM_PECHA_BERRY, ITEM_POTION); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_POUND, target: target); SEND_OUT(target, 2); } + } SCENE { + MESSAGE("The opposing Wynaut used Pound!"); + MESSAGE("Wobbuffet fainted!"); + ABILITY_POPUP(target, ABILITY_FRISK); + MESSAGE("Furret frisked the opposing Wynaut and found its Potion!"); + } +} + +DOUBLE_BATTLE_TEST("Frisk triggers for opponent in a Double Battle after switching-in after fainting (Multi)") +{ + struct BattlePokemon *target = NULL; + PARAMETRIZE { target = opponentLeft; } + PARAMETRIZE { target = opponentRight; } + + GIVEN { + ASSUME(!IsBattleMoveStatus(MOVE_POUND)); + PLAYER(SPECIES_WYNAUT) { Items(ITEM_PECHA_BERRY, ITEM_POTION); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_FURRET) { Ability(ABILITY_FRISK); }; + } WHEN { + TURN { MOVE(playerLeft, MOVE_POUND, target: target); SEND_OUT(target, 2); } + } SCENE { + MESSAGE("Wynaut used Pound!"); + MESSAGE("The opposing Wobbuffet fainted!"); + ABILITY_POPUP(target, ABILITY_FRISK); + MESSAGE("The opposing Furret frisked Wynaut and found its Potion!"); + } +} +#endif diff --git a/test/battle/ability/fur_coat.c b/test/battle/ability/fur_coat.c index 2ad6a3d5a2eb..4d26c59934b5 100644 --- a/test/battle/ability/fur_coat.c +++ b/test/battle/ability/fur_coat.c @@ -42,3 +42,42 @@ SINGLE_BATTLE_TEST("Fur Coat has no effect on self-inflicted confusion damage", EXPECT_EQ(results[0].damage, results[1].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Fur Coat doubles Defense (Traits)", s16 damage) +{ + u32 ability; + PARAMETRIZE { ability = ABILITY_FUR_COAT; } + PARAMETRIZE { ability = ABILITY_RATTLED; } + + GIVEN { + PLAYER(SPECIES_PERSIAN_ALOLA) { Ability(ABILITY_TECHNICIAN); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Fur Coat has no effect on self-inflicted confusion damage (Traits)", s16 damage) +{ + u32 ability; + PARAMETRIZE { ability = ABILITY_FUR_COAT; } + PARAMETRIZE { ability = ABILITY_RATTLED; } + + GIVEN { + PLAYER(SPECIES_PERSIAN_ALOLA) { Ability(ABILITY_TECHNICIAN); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_CONFUSE_RAY); MOVE(player, MOVE_POUND, WITH_RNG(RNG_CONFUSION, TRUE)); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} +#endif diff --git a/test/battle/ability/gale_wings.c b/test/battle/ability/gale_wings.c index cc40d98b8722..7eb79e2a4df8 100644 --- a/test/battle/ability/gale_wings.c +++ b/test/battle/ability/gale_wings.c @@ -87,3 +87,131 @@ SINGLE_BATTLE_TEST("Gale Wings doesn't increase priority of Flying-type Natural } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Gale Wings only grants priority at full HP (Gen 7+) (Traits)") +{ + u32 hp, config; + PARAMETRIZE { hp = 100; config = GEN_7; } + PARAMETRIZE { hp = 99; config = GEN_7; } + PARAMETRIZE { hp = 100; config = GEN_6; } + PARAMETRIZE { hp = 99; config = GEN_6; } + GIVEN { + WITH_CONFIG(CONFIG_GALE_WINGS, config); + ASSUME(GetMoveType(MOVE_AERIAL_ACE) == TYPE_FLYING); + PLAYER(SPECIES_TALONFLAME) { Ability(ABILITY_FLAME_BODY); Innates(ABILITY_GALE_WINGS); HP(hp); MaxHP(100); Speed(1);} + OPPONENT(SPECIES_WOBBUFFET) { Speed(100);}; + } WHEN { + TURN { MOVE(player, MOVE_AERIAL_ACE); } + } SCENE { + if (hp == 100 || config <= GEN_6) { + MESSAGE("Talonflame used Aerial Ace!"); + MESSAGE("The opposing Wobbuffet used Celebrate!"); + } + else { + MESSAGE("The opposing Wobbuffet used Celebrate!"); + MESSAGE("Talonflame used Aerial Ace!"); + } + } +} + +SINGLE_BATTLE_TEST("Gale Wings only grants priority to Flying-type moves (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_AERIAL_ACE; } + PARAMETRIZE { move = MOVE_FLARE_BLITZ; } + GIVEN { + ASSUME(GetMoveType(MOVE_AERIAL_ACE) == TYPE_FLYING); + ASSUME(GetMoveType(MOVE_FLARE_BLITZ) == TYPE_FIRE); + PLAYER(SPECIES_TALONFLAME) { Ability(ABILITY_FLAME_BODY); Innates(ABILITY_GALE_WINGS); HP(100); MaxHP(100); Speed(1);} + OPPONENT(SPECIES_WOBBUFFET) { Speed(100);}; + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + if (move == MOVE_AERIAL_ACE) { + MESSAGE("Talonflame used Aerial Ace!"); + MESSAGE("The opposing Wobbuffet used Celebrate!"); + } + else { + MESSAGE("The opposing Wobbuffet used Celebrate!"); + MESSAGE("Talonflame used Flare Blitz!"); + } + } +} + +SINGLE_BATTLE_TEST("Gale Wings doesn't increase priority of Flying-type Natural Gift, Judgment, Hidden Power, or Tera Blast (Traits)") +{ + u32 move; + u16 heldItem; + PARAMETRIZE { move = MOVE_NATURAL_GIFT; heldItem = ITEM_LUM_BERRY; } + PARAMETRIZE { move = MOVE_JUDGMENT; heldItem = ITEM_SKY_PLATE; } + PARAMETRIZE { move = MOVE_HIDDEN_POWER; heldItem = ITEM_NONE; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + ASSUME(GetMoveEffect(MOVE_JUDGMENT) == EFFECT_CHANGE_TYPE_ON_ITEM); + // IV combinations sourced from https://www.smogon.com/forums/threads/hidden-power-iv-combinations.78083/ + ASSUME(GetMoveEffect(MOVE_HIDDEN_POWER) == EFFECT_HIDDEN_POWER); + ASSUME(GetMoveEffect(MOVE_TERA_BLAST) == EFFECT_TERA_BLAST); + ASSUME(gItemsInfo[ITEM_SKY_PLATE].holdEffect == HOLD_EFFECT_PLATE); + ASSUME(gItemsInfo[ITEM_SKY_PLATE].secondaryId == TYPE_FLYING); + ASSUME(gNaturalGiftTable[ITEM_TO_BERRY(ITEM_LUM_BERRY)].type == TYPE_FLYING); + OPPONENT(SPECIES_TALONFLAME) { Ability(ABILITY_FLAME_BODY); Innates(ABILITY_GALE_WINGS); Speed(1); Item(heldItem); HPIV(31); AttackIV(3); DefenseIV(31); SpAttackIV(30); SpDefenseIV(30); SpeedIV(30); TeraType(TYPE_FLYING); } + PLAYER(SPECIES_WOBBUFFET) { Speed(100); }; + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + MESSAGE("Wobbuffet used Celebrate!"); + if (move == MOVE_NATURAL_GIFT) { + MESSAGE("The opposing Talonflame used Natural Gift!"); + } + else if (move == MOVE_JUDGMENT) { + MESSAGE("The opposing Talonflame used Judgment!"); + } + else if (move == MOVE_HIDDEN_POWER) { + MESSAGE("The opposing Talonflame used Hidden Power!"); + } + else { + MESSAGE("The opposing Talonflame used Tera Blast!"); + } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Gale Wings doesn't increase priority of Flying-type Natural Gift, Judgment, Hidden Power, or Tera Blast (Multi)") +{ + u32 move; + u16 heldItem; + PARAMETRIZE { move = MOVE_NATURAL_GIFT; heldItem = ITEM_LUM_BERRY; } + PARAMETRIZE { move = MOVE_JUDGMENT; heldItem = ITEM_SKY_PLATE; } + PARAMETRIZE { move = MOVE_HIDDEN_POWER; heldItem = ITEM_NONE; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + ASSUME(GetMoveEffect(MOVE_JUDGMENT) == EFFECT_CHANGE_TYPE_ON_ITEM); + // IV combinations sourced from https://www.smogon.com/forums/threads/hidden-power-iv-combinations.78083/ + ASSUME(GetMoveEffect(MOVE_HIDDEN_POWER) == EFFECT_HIDDEN_POWER); + ASSUME(GetMoveEffect(MOVE_TERA_BLAST) == EFFECT_TERA_BLAST); + ASSUME(gItemsInfo[ITEM_SKY_PLATE].holdEffect == HOLD_EFFECT_PLATE); + ASSUME(gItemsInfo[ITEM_SKY_PLATE].secondaryId == TYPE_FLYING); + ASSUME(gNaturalGiftTable[ITEM_TO_BERRY(ITEM_LUM_BERRY)].type == TYPE_FLYING); + OPPONENT(SPECIES_TALONFLAME) { Ability(ABILITY_GALE_WINGS); Speed(1); Items(ITEM_PECHA_BERRY, heldItem); HPIV(31); AttackIV(3); DefenseIV(31); SpAttackIV(30); SpDefenseIV(30); SpeedIV(30); TeraType(TYPE_FLYING); } + PLAYER(SPECIES_WOBBUFFET) { Speed(100); }; + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + MESSAGE("Wobbuffet used Celebrate!"); + if (move == MOVE_NATURAL_GIFT) { + MESSAGE("The opposing Talonflame used Natural Gift!"); + } + else if (move == MOVE_JUDGMENT) { + MESSAGE("The opposing Talonflame used Judgment!"); + } + else if (move == MOVE_HIDDEN_POWER) { + MESSAGE("The opposing Talonflame used Hidden Power!"); + } + else { + MESSAGE("The opposing Talonflame used Tera Blast!"); + } + } +} +#endif diff --git a/test/battle/ability/galvanize.c b/test/battle/ability/galvanize.c index 8388780dc0a5..216f45ec2a9b 100644 --- a/test/battle/ability/galvanize.c +++ b/test/battle/ability/galvanize.c @@ -232,3 +232,221 @@ SINGLE_BATTLE_TEST("Galvanize doesn't affect damaging Z-Move types") TO_DO_BATTLE_TEST("Galvanize doesn't affect Max Strike's type"); TO_DO_BATTLE_TEST("(DYNAMAX) Galvanize turns Max Strike into Max Lightning when not used by Gigantamax Pikachu/Toxtricity"); //TO_DO_BATTLE_TEST("(DYNAMAX) Galvanize doesn't turn Max Strike into Max Lightning when used by Gigantamax Pikachu/Toxtricity, instead becoming G-Max Volt Crash/Stun Shock"); // Marked in Bulbapedia as "needs research", so this assumes that it behaves like Pixilate. + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Galvanize turns a normal type move into Electric (Traits)") +{ + GIVEN { + PLAYER(SPECIES_KRABBY); + OPPONENT(SPECIES_GEODUDE_ALOLA) { Ability(ABILITY_STURDY); Innates(ABILITY_GALVANIZE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Galvanize can not turn certain moves into Electric type moves (Traits)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_HIDDEN_POWER; } + PARAMETRIZE { move = MOVE_WEATHER_BALL; } + PARAMETRIZE { move = MOVE_MULTI_ATTACK; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_HIDDEN_POWER) == EFFECT_HIDDEN_POWER); + ASSUME(GetMoveEffect(MOVE_WEATHER_BALL) == EFFECT_WEATHER_BALL); + ASSUME(GetMoveEffect(MOVE_MULTI_ATTACK) == EFFECT_CHANGE_TYPE_ON_ITEM); + PLAYER(SPECIES_KRABBY); + OPPONENT(SPECIES_GEODUDE_ALOLA) { Ability(ABILITY_STURDY); Innates(ABILITY_GALVANIZE); } + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + NOT MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Galvanize boosts power of affected moves by 20% (Gen7+) or 30% (Gen1-6) (Traits)", s16 damage) +{ + enum Ability ability; + u32 genConfig; + PARAMETRIZE { ability = ABILITY_STURDY; genConfig = GEN_7; } + PARAMETRIZE { ability = ABILITY_STURDY; genConfig = GEN_6; } + PARAMETRIZE { ability = ABILITY_GALVANIZE; genConfig = GEN_7; } + PARAMETRIZE { ability = ABILITY_GALVANIZE; genConfig = GEN_6; } + + GIVEN { + WITH_CONFIG(CONFIG_ATE_MULTIPLIER, genConfig); + PLAYER(SPECIES_GEODUDE_ALOLA) { Ability(ABILITY_MAGNET_PULL); Innates(ability); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + if (genConfig >= GEN_7) + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.8), results[2].damage); // STAB + ate + else + EXPECT_MUL_EQ(results[1].damage, Q_4_12(1.95), results[3].damage); // STAB + ate + } +} + +SINGLE_BATTLE_TEST("Galvanize doesn't affect Weather Ball's type (Traits)", s16 damage) +{ + u16 move; + enum Ability ability; + PARAMETRIZE { move = MOVE_CELEBRATE; ability = ABILITY_STURDY; } + PARAMETRIZE { move = MOVE_SUNNY_DAY; ability = ABILITY_STURDY; } + PARAMETRIZE { move = MOVE_CELEBRATE; ability = ABILITY_GALVANIZE; } + PARAMETRIZE { move = MOVE_SUNNY_DAY; ability = ABILITY_GALVANIZE; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_WEATHER_BALL) == EFFECT_WEATHER_BALL); + ASSUME(GetMoveType(MOVE_WEATHER_BALL) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_PINSIR, 0) == TYPE_BUG); + PLAYER(SPECIES_GEODUDE_ALOLA) { Ability(ABILITY_MAGNET_PULL); Innates(ability); } + OPPONENT(SPECIES_PINSIR); + } WHEN { + TURN { MOVE(player, move); } + TURN { MOVE(player, MOVE_WEATHER_BALL); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + if (move == MOVE_SUNNY_DAY) + MESSAGE("It's super effective!"); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(6.0), results[1].damage); // double base power + type effectiveness + sun 50% boost + EXPECT_MUL_EQ(results[2].damage, Q_4_12(6.0), results[3].damage); + EXPECT_EQ(results[0].damage, results[2].damage); + EXPECT_EQ(results[1].damage, results[3].damage); + } +} + +SINGLE_BATTLE_TEST("Galvanize doesn't affect Natural Gift's type (Traits)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_STURDY; } + PARAMETRIZE { ability = ABILITY_GALVANIZE; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + ASSUME(gNaturalGiftTable[ITEM_TO_BERRY(ITEM_ORAN_BERRY)].type == TYPE_POISON); + ASSUME(GetSpeciesType(SPECIES_BELDUM, 0) == TYPE_STEEL); + PLAYER(SPECIES_GEODUDE_ALOLA) { Ability(ABILITY_MAGNET_PULL); Innates(ability); Item(ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_BELDUM); + } WHEN { + TURN { MOVE(player, MOVE_NATURAL_GIFT); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player); } + MESSAGE("It doesn't affect the opposing Beldum…"); + } +} + +SINGLE_BATTLE_TEST("Galvanize doesn't affect Judgment / Techno Blast / Multi-Attack's type (Traits)") +{ + u16 move, item; + PARAMETRIZE { move = MOVE_JUDGMENT; item = ITEM_SPLASH_PLATE; } + PARAMETRIZE { move = MOVE_TECHNO_BLAST; item = ITEM_DOUSE_DRIVE; } + PARAMETRIZE { move = MOVE_MULTI_ATTACK; item = ITEM_WATER_MEMORY; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_JUDGMENT) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetMoveEffect(MOVE_TECHNO_BLAST) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetMoveEffect(MOVE_MULTI_ATTACK) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(gItemsInfo[ITEM_SPLASH_PLATE].holdEffect == HOLD_EFFECT_PLATE); + ASSUME(gItemsInfo[ITEM_SPLASH_PLATE].secondaryId == TYPE_WATER); + ASSUME(gItemsInfo[ITEM_DOUSE_DRIVE].holdEffect == HOLD_EFFECT_DRIVE); + ASSUME(gItemsInfo[ITEM_DOUSE_DRIVE].secondaryId == TYPE_WATER); + ASSUME(gItemsInfo[ITEM_WATER_MEMORY].holdEffect == HOLD_EFFECT_MEMORY); + ASSUME(gItemsInfo[ITEM_WATER_MEMORY].secondaryId == TYPE_WATER); + ASSUME(GetSpeciesType(SPECIES_VAPOREON, 0) == TYPE_WATER); + PLAYER(SPECIES_GEODUDE_ALOLA) { Ability(ABILITY_MAGNET_PULL); Innates(ABILITY_GALVANIZE); Item(item); } + OPPONENT(SPECIES_VAPOREON) { Ability(ABILITY_ADAPTABILITY); Innates(ABILITY_WATER_ABSORB); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, move, player); } + if (move == MOVE_JUDGMENT) + MESSAGE("The opposing Vaporeon's Water Absorb made Judgment useless!"); + else if (move == MOVE_TECHNO_BLAST) + MESSAGE("The opposing Vaporeon's Water Absorb made Techno Blast useless!"); + else if (move == MOVE_MULTI_ATTACK) + MESSAGE("The opposing Vaporeon's Water Absorb made Multi-Attack useless!"); + } +} + +SINGLE_BATTLE_TEST("Galvanize doesn't affect Hidden Power's type (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_HIDDEN_POWER) == EFFECT_HIDDEN_POWER); + ASSUME(gTypesInfo[TYPE_ELECTRIC].isHiddenPowerType == TRUE); + ASSUME(GetSpeciesType(SPECIES_DIGLETT, 0) == TYPE_GROUND); + PLAYER(SPECIES_GEODUDE_ALOLA) { Ability(ABILITY_MAGNET_PULL); Innates(ABILITY_GALVANIZE); HPIV(31); AttackIV(31); DefenseIV(31); SpAttackIV(30); SpDefenseIV(31); SpeedIV(30); } // HP Water + OPPONENT(SPECIES_VAPOREON) { Ability(ABILITY_ADAPTABILITY); Innates(ABILITY_WATER_ABSORB); } + } WHEN { + TURN { MOVE(player, MOVE_HIDDEN_POWER); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_HIDDEN_POWER, player); } + MESSAGE("The opposing Vaporeon's Water Absorb made Hidden Power useless!"); + } +} + +TO_DO_BATTLE_TEST("Galvanize doesn't affect Tera Starstorm's type (Traits)"); +TO_DO_BATTLE_TEST("Galvanize doesn't affect Max Strike's type (Traits)"); +TO_DO_BATTLE_TEST("Galvanize doesn't affect Terrain Pulse's type (Traits)"); +TO_DO_BATTLE_TEST("Galvanize doesn't affect damaging Z-Move types (Traits)"); +TO_DO_BATTLE_TEST("(DYNAMAX) Galvanize turns Max Strike into Max Lightning when not used by Gigantamax Pikachu/Toxtricity (Traits)"); +//TO_DO_BATTLE_TEST("(DYNAMAX) Galvanize doesn't turn Max Strike into Max Lightning when used by Gigantamax Pikachu/Toxtricity, instead becoming G-Max Volt Crash/Stun Shock"); // Marked in Bulbapedia as "needs research", so this assumes that it behaves like Pixilate. +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Galvanize doesn't affect Natural Gift's type (Multi)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_STURDY; } + PARAMETRIZE { ability = ABILITY_GALVANIZE; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + ASSUME(gNaturalGiftTable[ITEM_TO_BERRY(ITEM_ORAN_BERRY)].type == TYPE_POISON); + ASSUME(GetSpeciesType(SPECIES_BELDUM, 0) == TYPE_STEEL); + PLAYER(SPECIES_GEODUDE_ALOLA) { Ability(ability); Items(ITEM_GREAT_BALL, ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_BELDUM); + } WHEN { + TURN { MOVE(player, MOVE_NATURAL_GIFT); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player); } + MESSAGE("It doesn't affect the opposing Beldum…"); + } +} + +SINGLE_BATTLE_TEST("Galvanize doesn't affect Judgment / Techno Blast / Multi-Attack's type (Multi)") +{ + u16 move, item; + PARAMETRIZE { move = MOVE_JUDGMENT; item = ITEM_SPLASH_PLATE; } + PARAMETRIZE { move = MOVE_TECHNO_BLAST; item = ITEM_DOUSE_DRIVE; } + PARAMETRIZE { move = MOVE_MULTI_ATTACK; item = ITEM_WATER_MEMORY; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_JUDGMENT) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetMoveEffect(MOVE_TECHNO_BLAST) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetMoveEffect(MOVE_MULTI_ATTACK) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(gItemsInfo[ITEM_SPLASH_PLATE].holdEffect == HOLD_EFFECT_PLATE); + ASSUME(gItemsInfo[ITEM_SPLASH_PLATE].secondaryId == TYPE_WATER); + ASSUME(gItemsInfo[ITEM_DOUSE_DRIVE].holdEffect == HOLD_EFFECT_DRIVE); + ASSUME(gItemsInfo[ITEM_DOUSE_DRIVE].secondaryId == TYPE_WATER); + ASSUME(gItemsInfo[ITEM_WATER_MEMORY].holdEffect == HOLD_EFFECT_MEMORY); + ASSUME(gItemsInfo[ITEM_WATER_MEMORY].secondaryId == TYPE_WATER); + ASSUME(GetSpeciesType(SPECIES_VAPOREON, 0) == TYPE_WATER); + PLAYER(SPECIES_GEODUDE_ALOLA) { Ability(ABILITY_GALVANIZE); Items(ITEM_GREAT_BALL, item); } + OPPONENT(SPECIES_VAPOREON) { Ability(ABILITY_WATER_ABSORB); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, move, player); } + if (move == MOVE_JUDGMENT) + MESSAGE("The opposing Vaporeon's Water Absorb made Judgment useless!"); + else if (move == MOVE_TECHNO_BLAST) + MESSAGE("The opposing Vaporeon's Water Absorb made Techno Blast useless!"); + else if (move == MOVE_MULTI_ATTACK) + MESSAGE("The opposing Vaporeon's Water Absorb made Multi-Attack useless!"); + } +} +#endif diff --git a/test/battle/ability/good_as_gold.c b/test/battle/ability/good_as_gold.c index fc6c6bc8c467..3ac60f55ba1f 100644 --- a/test/battle/ability/good_as_gold.c +++ b/test/battle/ability/good_as_gold.c @@ -68,3 +68,72 @@ DOUBLE_BATTLE_TEST("Good as Gold protects from partner's status moves") MESSAGE("It doesn't affect the opposing Gholdengo…"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Good as Gold protects from status moves (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_TOXIC) == DAMAGE_CATEGORY_STATUS); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GHOLDENGO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GOOD_AS_GOLD); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + ABILITY_POPUP(opponent, ABILITY_GOOD_AS_GOLD); + MESSAGE("It doesn't affect the opposing Gholdengo…"); + } +} + +SINGLE_BATTLE_TEST("Good as Gold doesn't protect the user from it's own moves (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_NASTY_PLOT) == DAMAGE_CATEGORY_STATUS); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GHOLDENGO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GOOD_AS_GOLD); } + } WHEN { + TURN { MOVE(opponent, MOVE_NASTY_PLOT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_NASTY_PLOT, opponent); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_GOOD_AS_GOLD); + MESSAGE("It doesn't affect the opposing Gholdengo…"); + } + } +} + +SINGLE_BATTLE_TEST("Good as Gold doesn't protect from moves that target the field (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_STEALTH_ROCK) == DAMAGE_CATEGORY_STATUS); + ASSUME(GetMoveTarget(MOVE_STEALTH_ROCK) == MOVE_TARGET_OPPONENTS_FIELD); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GHOLDENGO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GOOD_AS_GOLD); } + } WHEN { + TURN { MOVE(player, MOVE_STEALTH_ROCK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEALTH_ROCK, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_GOOD_AS_GOLD); + MESSAGE("It doesn't affect the opposing Gholdengo…"); + } + } +} + +DOUBLE_BATTLE_TEST("Good as Gold protects from partner's status moves (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_HELPING_HAND) == DAMAGE_CATEGORY_STATUS); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GHOLDENGO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GOOD_AS_GOLD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentRight, MOVE_HELPING_HAND); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_HELPING_HAND, opponentRight); + ABILITY_POPUP(opponentLeft, ABILITY_GOOD_AS_GOLD); + MESSAGE("It doesn't affect the opposing Gholdengo…"); + } +} +#endif diff --git a/test/battle/ability/grassy_surge.c b/test/battle/ability/grassy_surge.c index e8f1a0cbaedc..c9f19c3aca5f 100644 --- a/test/battle/ability/grassy_surge.c +++ b/test/battle/ability/grassy_surge.c @@ -13,3 +13,18 @@ SINGLE_BATTLE_TEST("Grassy Surge creates Grassy Terrain when entering the battle MESSAGE("Grass grew to cover the battlefield!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Grassy Surge creates Grassy Terrain when entering the battle (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TAPU_BULU) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GRASSY_SURGE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN {} + } SCENE { + ABILITY_POPUP(player, ABILITY_GRASSY_SURGE); + MESSAGE("Grass grew to cover the battlefield!"); + } +} +#endif diff --git a/test/battle/ability/grim_neigh.c b/test/battle/ability/grim_neigh.c index 2fe20473df55..81ff21cf618f 100644 --- a/test/battle/ability/grim_neigh.c +++ b/test/battle/ability/grim_neigh.c @@ -103,3 +103,108 @@ DOUBLE_BATTLE_TEST("Grim Neigh does not increase damage done by the same move th EXPECT_EQ(damage[0], damage[1]); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Grim Neigh raises Sp. Attack by one stage after directly causing a Pokemon to faint (Traits)") +{ + u32 species = 0, abilityPopUp = 0; + enum Ability ability = 0; + PARAMETRIZE { species = SPECIES_SPECTRIER; ability = ABILITY_GRIM_NEIGH; abilityPopUp = ABILITY_GRIM_NEIGH; } + PARAMETRIZE { species = SPECIES_CALYREX_SHADOW; ability = ABILITY_AS_ONE_SHADOW_RIDER; abilityPopUp = ABILITY_GRIM_NEIGH; } + GIVEN { + ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + PLAYER(SPECIES_SNORUNT) { HP(1); } + OPPONENT(SPECIES_GLALIE) { HP(1); } + OPPONENT(SPECIES_ABRA) { HP(1); } + OPPONENT(SPECIES_ABRA); + } WHEN { + TURN { MOVE(playerLeft, MOVE_DISCHARGE); SEND_OUT(opponentLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DISCHARGE, playerLeft); + MESSAGE("The opposing Glalie fainted!"); + MESSAGE("Snorunt fainted!"); + MESSAGE("The opposing Abra fainted!"); + ABILITY_POPUP(playerLeft, abilityPopUp); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + if (species == SPECIES_SPECTRIER) + MESSAGE("Spectrier's Sp. Atk drastically rose!"); + else + MESSAGE("Calyrex's Sp. Atk drastically rose!"); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 3); + } +} + +DOUBLE_BATTLE_TEST("Grim Neigh does not trigger if Pokemon faint to indirect damage or damage from other Pokemon (Traits)") +{ + u32 species = 0, abilityPopUp = 0; + enum Ability ability = 0; + PARAMETRIZE { species = SPECIES_SPECTRIER; ability = ABILITY_GRIM_NEIGH; abilityPopUp = ABILITY_GRIM_NEIGH; } + PARAMETRIZE { species = SPECIES_CALYREX_SHADOW; ability = ABILITY_AS_ONE_SHADOW_RIDER; abilityPopUp = ABILITY_GRIM_NEIGH; } + GIVEN { + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + PLAYER(SPECIES_SNORUNT) { HP(1); Status1(STATUS1_POISON); } + OPPONENT(SPECIES_GLALIE) { HP(1); Status1(STATUS1_BURN); } + OPPONENT(SPECIES_ABRA) { HP(1); } + OPPONENT(SPECIES_ABRA); + } WHEN { + TURN { MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentRight); SEND_OUT(opponentLeft, 2); } + } SCENE { + int i; + + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, playerRight); + for (i = 0; i < 3; i++) { + ONE_OF { + MESSAGE("Snorunt fainted!"); + MESSAGE("The opposing Glalie fainted!"); + MESSAGE("The opposing Abra fainted!"); + } + NONE_OF { + ABILITY_POPUP(playerLeft, abilityPopUp); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Salamence's Moxie raised its Sp. Atk!"); + MESSAGE("Spectrier's Grim Neigh raised its Sp. Atk!"); + MESSAGE("Calyrex's Grim Neigh raised its Sp. Atk!"); + } + } + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Grim Neigh does not increase damage done by the same move that causes another Pokemon to faint (Traits)") +{ + s16 damage[2]; + u32 species = 0, abilityPopUp = 0; + enum Ability ability = 0; + PARAMETRIZE { species = SPECIES_SPECTRIER; ability = ABILITY_GRIM_NEIGH; abilityPopUp = ABILITY_GRIM_NEIGH; } + PARAMETRIZE { species = SPECIES_CALYREX_SHADOW; ability = ABILITY_AS_ONE_SHADOW_RIDER; abilityPopUp = ABILITY_GRIM_NEIGH; } + + GIVEN { + ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + PLAYER(SPECIES_ABRA) { HP(1); } + OPPONENT(SPECIES_GLALIE); + OPPONENT(SPECIES_GLALIE); + OPPONENT(SPECIES_ABRA); + } WHEN { + TURN { MOVE(playerLeft, MOVE_DISCHARGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DISCHARGE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damage[0]); + HP_BAR(playerRight); + HP_BAR(opponentRight, captureDamage: &damage[1]); + MESSAGE("Abra fainted!"); + ABILITY_POPUP(playerLeft, abilityPopUp); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + if (species == SPECIES_SPECTRIER) + MESSAGE("Spectrier's Sp. Atk rose!"); + else + MESSAGE("Calyrex's Sp. Atk rose!"); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(damage[0], damage[1]); + } +} +#endif diff --git a/test/battle/ability/guard_dog.c b/test/battle/ability/guard_dog.c index b8ebbafbad0e..44b68279c553 100644 --- a/test/battle/ability/guard_dog.c +++ b/test/battle/ability/guard_dog.c @@ -26,3 +26,31 @@ SINGLE_BATTLE_TEST("Guard Dog raises Attack when intimidated", s16 damage) EXPECT_MUL_EQ(results[1].damage, Q_4_12(1.5), results[0].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Guard Dog raises Attack when intimidated (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_INTIMIDATE; } + PARAMETRIZE { ability = ABILITY_SHED_SKIN; } + GIVEN { + PLAYER(SPECIES_OKIDOGI) { Ability(ABILITY_TOXIC_CHAIN); Innates(ABILITY_GUARD_DOG); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_UNNERVE); Innates(ability); } + } WHEN { + TURN { SWITCH(opponent, 1); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + if (ability == ABILITY_INTIMIDATE) + { + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + ABILITY_POPUP(player, ABILITY_GUARD_DOG); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Okidogi's Attack rose!"); + } + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[1].damage, Q_4_12(1.5), results[0].damage); + } +} +#endif diff --git a/test/battle/ability/gulp_missile.c b/test/battle/ability/gulp_missile.c index 5a3266ff17e5..2ac910642a24 100644 --- a/test/battle/ability/gulp_missile.c +++ b/test/battle/ability/gulp_missile.c @@ -201,3 +201,251 @@ SINGLE_BATTLE_TEST("Gulp Missile triggered by explosion doesn't freeze the game" TURN { MOVE(opponent, MOVE_SURF); MOVE(player, MOVE_EXPLOSION); } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("(Gulp Missile) If base Cramorant hits target with Surf it transforms into Gulping form if max HP is over 1/2 (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CRAMORANT) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GULP_MISSILE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SURF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURF, player); + HP_BAR(opponent); + ABILITY_POPUP(player, ABILITY_GULP_MISSILE); + } THEN { + EXPECT_EQ(player->species, SPECIES_CRAMORANT_GULPING); + } +} + +SINGLE_BATTLE_TEST("(Gulp Missile) If base Cramorant hits target with Surf it transforms into Gorging form if max HP is under 1/2 (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CRAMORANT) { HP(120); MaxHP(250); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GULP_MISSILE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SURF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURF, player); + HP_BAR(opponent); + ABILITY_POPUP(player, ABILITY_GULP_MISSILE); + } THEN { + EXPECT_EQ(player->species, SPECIES_CRAMORANT_GORGING); + } +} + +SINGLE_BATTLE_TEST("(Gulp Missile) If base Cramorant is under water it transforms into one of its forms (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CRAMORANT) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GULP_MISSILE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_DIVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DIVE, player); + NOT HP_BAR(opponent); + ABILITY_POPUP(player, ABILITY_GULP_MISSILE); + } THEN { + EXPECT_EQ(player->species, SPECIES_CRAMORANT_GULPING); + } +} + +SINGLE_BATTLE_TEST("(Gulp Missile) Power Herb does not prevent Cramaront from transforming (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CRAMORANT) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GULP_MISSILE); Item(ITEM_POWER_HERB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_DIVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DIVE, player); + MESSAGE("Cramorant became fully charged due to its Power Herb!"); + ABILITY_POPUP(player, ABILITY_GULP_MISSILE); + HP_BAR(opponent); + } THEN { + EXPECT_EQ(player->species, SPECIES_CRAMORANT_GULPING); + } +} + +SINGLE_BATTLE_TEST("(Gulp Missile) Transformed Cramorant deal 1/4 of damage opposing mon if hit by a damaging move, Gulping also lowers defense (Traits)") +{ + s16 gulpMissileDamage; + + GIVEN { + PLAYER(SPECIES_CRAMORANT) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GULP_MISSILE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SURF); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURF, player); + HP_BAR(opponent); + ABILITY_POPUP(player, ABILITY_GULP_MISSILE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_GULP_MISSILE); + HP_BAR(opponent, captureDamage: &gulpMissileDamage); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wobbuffet's Defense fell!"); + } THEN { + EXPECT_EQ(gulpMissileDamage, opponent->maxHP / 4); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("(Gulp Missile) Cramorant in Gorging paralyzes the target if hit by a damaging move (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CRAMORANT) { HP(120); MaxHP(250); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GULP_MISSILE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SURF); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURF, player); + HP_BAR(opponent); + ABILITY_POPUP(player, ABILITY_GULP_MISSILE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_GULP_MISSILE); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, opponent); + STATUS_ICON(opponent, paralysis: TRUE); + } +} + +SINGLE_BATTLE_TEST("(Gulp Missile) triggers even if the user is fainted by opposing mon (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CRAMORANT) { HP(1); MaxHP(250); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GULP_MISSILE); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SURF); MOVE(opponent, MOVE_SCRATCH); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURF, player); + HP_BAR(opponent); + ABILITY_POPUP(player, ABILITY_GULP_MISSILE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, opponent); + STATUS_ICON(opponent, paralysis: TRUE); + } +} + +SINGLE_BATTLE_TEST("(Gulp Missile) Transformed Cramorant Gulping lowers defense but is prevented by stat reduction preventing abilities (Traits)") +{ + u32 species; + enum Ability ability; + PARAMETRIZE { species = SPECIES_METAGROSS; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE { species = SPECIES_CORVIKNIGHT; ability = ABILITY_MIRROR_ARMOR; } + PARAMETRIZE { species = SPECIES_CHATOT; ability = ABILITY_BIG_PECKS; } + GIVEN { + PLAYER(SPECIES_CRAMORANT) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GULP_MISSILE); } + OPPONENT(species) { Ability(ability); HP(9999); MaxHP(9999); } // In Gen 5 data, Surf would be enough to knock out Chatot + } WHEN { + TURN { MOVE(player, MOVE_SURF); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURF, player); + HP_BAR(opponent); + ABILITY_POPUP(player, ABILITY_GULP_MISSILE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_GULP_MISSILE); + ABILITY_POPUP(opponent, ability); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("(Gulp Missile) Transformed Cramorant Gulping lowers defense and still triggers other effects after (Traits)") +{ + // Make sure attacker and target are correct after triggering the ability + enum Ability ability; + PARAMETRIZE { ability = ABILITY_INFILTRATOR; } + PARAMETRIZE { ability = ABILITY_CLEAR_BODY; } + GIVEN { + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_CRAMORANT) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GULP_MISSILE); Item(ITEM_ROCKY_HELMET); } + OPPONENT(SPECIES_DRAGAPULT) { Ability(ABILITY_INFILTRATOR); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SURF); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURF, player); + HP_BAR(opponent); + ABILITY_POPUP(player, ABILITY_GULP_MISSILE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_GULP_MISSILE); + HP_BAR(opponent); + if (ability == ABILITY_INFILTRATOR) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Dragapult's Defense fell!"); + } else { + ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Gulp Missile triggered by explosion doesn't freeze the game (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CRAMORANT) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GULP_MISSILE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SURF); MOVE(player, MOVE_EXPLOSION); } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("(Gulp Missile) Power Herb does not prevent Cramaront from transforming (Multi)") +{ + GIVEN { + PLAYER(SPECIES_CRAMORANT) { Ability(ABILITY_GULP_MISSILE); Items(ITEM_PECHA_BERRY, ITEM_POWER_HERB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_DIVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DIVE, player); + MESSAGE("Cramorant became fully charged due to its Power Herb!"); + ABILITY_POPUP(player, ABILITY_GULP_MISSILE); + HP_BAR(opponent); + } THEN { + EXPECT_EQ(player->species, SPECIES_CRAMORANT_GULPING); + } +} + +SINGLE_BATTLE_TEST("(Gulp Missile) Transformed Cramorant Gulping lowers defense and still triggers other effects after (Multi)") +{ + // Make sure attacker and target are correct after triggering the ability + enum Ability ability; + PARAMETRIZE { ability = ABILITY_INFILTRATOR; } + PARAMETRIZE { ability = ABILITY_CLEAR_BODY; } + GIVEN { + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_CRAMORANT) { Ability(ABILITY_GULP_MISSILE); Items(ITEM_PECHA_BERRY, ITEM_ROCKY_HELMET); } + OPPONENT(SPECIES_DRAGAPULT) { Ability(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SURF); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURF, player); + HP_BAR(opponent); + ABILITY_POPUP(player, ABILITY_GULP_MISSILE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_GULP_MISSILE); + HP_BAR(opponent); + if (ability == ABILITY_INFILTRATOR) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Dragapult's Defense fell!"); + } else { + ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(opponent); + } +} +#endif diff --git a/test/battle/ability/harvest.c b/test/battle/ability/harvest.c index 403d044f8f5e..39844fa26e21 100644 --- a/test/battle/ability/harvest.c +++ b/test/battle/ability/harvest.c @@ -262,3 +262,500 @@ SINGLE_BATTLE_TEST("Harvest can only restore the newest berry consumed that was EXPECT_GT(opponent->hp, opponent->maxHP / 2); // eats 2 Sitrus } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Harvest has a 50% chance to restore a Berry at the end of the turn (Traits)") +{ + PASSES_RANDOMLY(1, 2, RNG_HARVEST); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Harvest always restores a Berry in Sunlight (Traits)") +{ + PASSES_RANDOMLY(1, 1, RNG_HARVEST); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Harvest doesn't always restore a Berry if Cloud Nine/Air Lock is on the field (Traits)") +{ + PASSES_RANDOMLY(1, 2, RNG_HARVEST); + GIVEN { + PLAYER(SPECIES_GOLDUCK) { Ability(ABILITY_SWIFT_SWIM); Innates(ABILITY_CLOUD_NINE); } + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Harvest restores a Berry even after being switched out and back in (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_PARTING_SHOT) == EFFECT_PARTING_SHOT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_PARTING_SHOT); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_PARTING_SHOT); SEND_OUT(opponent, 0); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Harvest restores a Berry consumed by Fling (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FLING) == EFFECT_FLING); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_HARVEST); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_FLING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, opponent); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Harvest restores a Berry consumed by Natural Gift (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_HARVEST); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_NATURAL_GIFT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, opponent); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_SITRUS_BERRY); + } +} + +TO_DO_BATTLE_TEST("Harvest only works once per turn (Traits)"); // Check for berries that are consumed immediately, like Pecha Berry + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry when destroyed by Incinerate (Traits)") +{ + PASSES_RANDOMLY(1, 1, RNG_HARVEST); + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_INCINERATE, MOVE_EFFECT_INCINERATE)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_HARVEST); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_INCINERATE); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_INCINERATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry when knocked off by Knock Off (Traits)") +{ + PASSES_RANDOMLY(1, 1, RNG_HARVEST); + GIVEN { + ASSUME(GetMoveEffect(MOVE_KNOCK_OFF) == EFFECT_KNOCK_OFF); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_HARVEST); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry when eaten by Bug Bite/Pluck (Traits)") +{ + PASSES_RANDOMLY(1, 1, RNG_HARVEST); + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_BUG_BITE, MOVE_EFFECT_BUG_BITE)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_HARVEST); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_BUG_BITE); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BUG_BITE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry that's collected via Pickup (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Speed(50); Ability(ABILITY_GLUTTONY); Innates(ABILITY_PICKUP); } + OPPONENT(SPECIES_EXEGGUTOR) { Speed(10); Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Harvest order is affected by speed (Traits)") +{ + GIVEN { + PLAYER(SPECIES_EXEGGUTOR) { Speed(2); Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_EXEGGUTOR) { Speed(10); Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); } + } WHEN { + TURN { MOVE(playerRight, MOVE_BULLDOZE); MOVE(playerLeft, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLDOZE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_HARVEST); + ABILITY_POPUP(playerLeft, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponentLeft->item, ITEM_SITRUS_BERRY); + EXPECT_EQ(playerLeft->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry when transfered to another Pokémon (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TRICK) == EFFECT_TRICK); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_HARVEST); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_TRICK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Harvest can restore a Berry that was transferred from another Pokémon (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TRICK) == EFFECT_TRICK); + PLAYER(SPECIES_TORKOAL) { Ability(ABILITY_WHITE_SMOKE); Innates(ABILITY_DROUGHT); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_HARVEST); HP(100); MaxHP(500); } + } WHEN { + TURN { MOVE(opponent, MOVE_TRICK); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT_GT(opponent->hp, opponent->maxHP / 2); // eats 2 Sitrus + } +} + +SINGLE_BATTLE_TEST("Harvest can only restore the newest berry consumed that was transferred from another Pokémon instead of its original Berry (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TRICK) == EFFECT_TRICK); + ASSUME(gItemsInfo[ITEM_APICOT_BERRY].holdEffect == HOLD_EFFECT_SP_DEFENSE_UP); + PLAYER(SPECIES_TORKOAL) { Ability(ABILITY_WHITE_SMOKE); Innates(ABILITY_DROUGHT); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_HARVEST); HP(100); MaxHP(500); Item(ITEM_APICOT_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_TRICK); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT_GT(opponent->hp, opponent->maxHP / 2); // eats 2 Sitrus + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Harvest has a 50% chance to restore a Berry at the end of the turn (Multi)") +{ + PASSES_RANDOMLY(1, 2, RNG_HARVEST); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Harvest always restores a Berry in Sunlight (Multi)") +{ + PASSES_RANDOMLY(1, 1, RNG_HARVEST); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Harvest doesn't always restore a Berry if Cloud Nine/Air Lock is on the field (Multi)") +{ + PASSES_RANDOMLY(1, 2, RNG_HARVEST); + GIVEN { + PLAYER(SPECIES_GOLDUCK) { Ability(ABILITY_CLOUD_NINE); } + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Harvest restores a Berry even after being switched out and back in (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_PARTING_SHOT) == EFFECT_PARTING_SHOT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_PARTING_SHOT); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_PARTING_SHOT); SEND_OUT(opponent, 0); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Harvest restores a Berry consumed by Natural Gift (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_NATURAL_GIFT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, opponent); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_SITRUS_BERRY); + } +} + +TO_DO_BATTLE_TEST("Harvest only works once per turn (Multi)"); // Check for berries that are consumed immediately, like Pecha Berry + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry when destroyed by Incinerate (Multi)") +{ + PASSES_RANDOMLY(1, 1, RNG_HARVEST); + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_INCINERATE, MOVE_EFFECT_INCINERATE)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_INCINERATE); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_INCINERATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry when knocked off by Knock Off (Multi)") +{ + PASSES_RANDOMLY(1, 1, RNG_HARVEST); + GIVEN { + ASSUME(GetMoveEffect(MOVE_KNOCK_OFF) == EFFECT_KNOCK_OFF); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry when eaten by Bug Bite/Pluck (Multi)") +{ + PASSES_RANDOMLY(1, 1, RNG_HARVEST); + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_BUG_BITE, MOVE_EFFECT_BUG_BITE)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_BUG_BITE); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BUG_BITE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry that's collected via Pickup (Multi)") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Speed(50); Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_EXEGGUTOR) { Speed(10); Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Harvest order is affected by speed (Multi)") +{ + GIVEN { + PLAYER(SPECIES_EXEGGUTOR) { Speed(2); Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_EXEGGUTOR) { Speed(10); Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); } + } WHEN { + TURN { MOVE(playerRight, MOVE_BULLDOZE); MOVE(playerLeft, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLDOZE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_HARVEST); + ABILITY_POPUP(playerLeft, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponentLeft->item, ITEM_SITRUS_BERRY); + EXPECT_EQ(playerLeft->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry when transfered to another Pokémon (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TRICK) == EFFECT_TRICK); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_TRICK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Harvest can restore a Berry that was transferred from another Pokémon (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TRICK) == EFFECT_TRICK); + PLAYER(SPECIES_TORKOAL) { Ability(ABILITY_DROUGHT); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); HP(100); MaxHP(500); } + } WHEN { + TURN { MOVE(opponent, MOVE_TRICK); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT_GT(opponent->hp, opponent->maxHP / 2); // eats 2 Sitrus + } +} + +SINGLE_BATTLE_TEST("Harvest can only restore the newest berry consumed that was transferred from another Pokémon instead of its original Berry (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TRICK) == EFFECT_TRICK); + ASSUME(gItemsInfo[ITEM_APICOT_BERRY].holdEffect == HOLD_EFFECT_SP_DEFENSE_UP); + PLAYER(SPECIES_TORKOAL) { Ability(ABILITY_DROUGHT); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); HP(100); MaxHP(500); Items(ITEM_GREAT_BALL, ITEM_APICOT_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_TRICK); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT_GT(opponent->hp, opponent->maxHP / 2); // eats 2 Sitrus + } +} +#endif diff --git a/test/battle/ability/healer.c b/test/battle/ability/healer.c index 93422ed8a0fe..f56efb830194 100644 --- a/test/battle/ability/healer.c +++ b/test/battle/ability/healer.c @@ -47,3 +47,52 @@ DOUBLE_BATTLE_TEST("Healer cures status condition before burn or poison damage i // Triple battles TO_DO_BATTLE_TEST("Healer has a 30% chance of curing each of its ally's status conditions independently"); + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Healer cures adjacent ally's status condition 30% of the time (Traits)") +{ + u16 status; + PARAMETRIZE { status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_BURN; } + // PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_FROSTBITE; } + PASSES_RANDOMLY(30, 100, RNG_HEALER); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Status1(status); } + OPPONENT(SPECIES_CHANSEY) { Ability(ABILITY_SERENE_GRACE); Innates(ABILITY_HEALER); } + } WHEN { + TURN { } + } SCENE { + MESSAGE("The opposing Chansey's Healer cured the opposing Wobbuffet's problem!"); + } +} + +DOUBLE_BATTLE_TEST("Healer cures status condition before burn or poison damage is dealt (Traits)") +{ + u16 status; + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_FROSTBITE; } + PASSES_RANDOMLY(30, 100, RNG_HEALER); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Status1(status); } + OPPONENT(SPECIES_CHANSEY) { Ability(ABILITY_SERENE_GRACE); Innates(ABILITY_HEALER); } + } WHEN { + TURN {} + } SCENE { + NOT MESSAGE("The opposing Wobbuffet fainted!"); + MESSAGE("The opposing Chansey's Healer cured the opposing Wobbuffet's problem!"); + } +} + +// Triple battles +TO_DO_BATTLE_TEST("Healer has a 30% chance of curing each of its ally's status conditions independently (Traits)"); +#endif diff --git a/test/battle/ability/hospitality.c b/test/battle/ability/hospitality.c index 925c7b6ce15c..8cfb5ee637bc 100644 --- a/test/battle/ability/hospitality.c +++ b/test/battle/ability/hospitality.c @@ -111,3 +111,116 @@ DOUBLE_BATTLE_TEST("Hospitality is blocked by Heal Block") } } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Hospitality user restores 25% of ally's health (Traits)") +{ + s16 health; + + PARAMETRIZE { health = 75; } + PARAMETRIZE { health = 100; } + + GIVEN { + PLAYER(SPECIES_POLTCHAGEIST) { Ability(ABILITY_HEATPROOF); Innates(ABILITY_HOSPITALITY); } + PLAYER(SPECIES_WOBBUFFET) { HP(health); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { } + } SCENE { + if (health == 75) { + ABILITY_POPUP(playerLeft, ABILITY_HOSPITALITY); + MESSAGE("Wobbuffet drank down all the matcha that Poltchageist made!"); + HP_BAR(playerRight, damage: -25); + } else { + NONE_OF { + ABILITY_POPUP(playerLeft, ABILITY_HOSPITALITY); + MESSAGE("Wobbuffet drank down all the matcha that Poltchageist made!"); + HP_BAR(playerRight, damage: -25); + } + } + } +} + +DOUBLE_BATTLE_TEST("Hospitality user restores 25% of ally's health on switch-in (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) + PLAYER(SPECIES_WOBBUFFET) { HP(75); MaxHP(100); } + PLAYER(SPECIES_POLTCHAGEIST) { Ability(ABILITY_HEATPROOF); Innates(ABILITY_HOSPITALITY); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(playerLeft, 2); } + } SCENE { + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Poltchageist"); + ABILITY_POPUP(playerLeft, ABILITY_HOSPITALITY); + MESSAGE("Wobbuffet drank down all the matcha that Poltchageist made!"); + HP_BAR(playerRight, damage: -25); + } +} + +DOUBLE_BATTLE_TEST("Hospitality ignores Substitute (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_POLTCHAGEIST) { Ability(ABILITY_HEATPROOF); Innates(ABILITY_HOSPITALITY); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, MOVE_SUBSTITUTE); } + TURN { SWITCH(playerLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, playerRight); + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Poltchageist"); + ABILITY_POPUP(playerLeft, ABILITY_HOSPITALITY); + MESSAGE("Wobbuffet drank down all the matcha that Poltchageist made!"); + } +} + +DOUBLE_BATTLE_TEST("Hospitality does not trigger if there is no ally on the field (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_POLTCHAGEIST) { Ability(ABILITY_HEATPROOF); Innates(ABILITY_HOSPITALITY); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_BLIZZARD); SEND_OUT(playerLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BLIZZARD, opponentLeft); + HP_BAR(playerLeft); + HP_BAR(playerRight); + MESSAGE("Wobbuffet fainted!"); + MESSAGE("Wobbuffet fainted!"); + SEND_IN_MESSAGE("Poltchageist"); + NOT ABILITY_POPUP(playerLeft, ABILITY_HOSPITALITY); + } +} + +DOUBLE_BATTLE_TEST("Hospitality is blocked by Heal Block (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_HEAL_BLOCK) == EFFECT_HEAL_BLOCK); + PLAYER(SPECIES_WOBBUFFET) + PLAYER(SPECIES_WOBBUFFET) { HP(75); MaxHP(100); } + PLAYER(SPECIES_POLTCHAGEIST) { Ability(ABILITY_HEATPROOF); Innates(ABILITY_HOSPITALITY); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_HEAL_BLOCK, target: playerRight); } + TURN { SWITCH(playerLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEAL_BLOCK, opponentLeft); + NONE_OF { + ABILITY_POPUP(playerLeft, ABILITY_HOSPITALITY); + MESSAGE("Wobbuffet drank down all the matcha that Poltchageist made!"); + HP_BAR(playerRight, damage: -25); + } + } +} +#endif diff --git a/test/battle/ability/hunger_switch.c b/test/battle/ability/hunger_switch.c index 0c2dc8a53373..3432f3b08701 100644 --- a/test/battle/ability/hunger_switch.c +++ b/test/battle/ability/hunger_switch.c @@ -70,3 +70,75 @@ SINGLE_BATTLE_TEST("Hunger Switch does not switch Morpeko's form after switching EXPECT_EQ(player->species, SPECIES_MORPEKO_HANGRY); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Hunger Switch switches Morpeko's forms at the end of the turn (Traits)") +{ + u16 species; + PARAMETRIZE { species = SPECIES_MORPEKO_FULL_BELLY; } + PARAMETRIZE { species = SPECIES_MORPEKO_HANGRY; } + GIVEN { + PLAYER(species) { Speed(2); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_HUNGER_SWITCH); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(1); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); } + } SCENE { + MESSAGE("Morpeko used Celebrate!"); + MESSAGE("The opposing Wobbuffet used Celebrate!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + } THEN { + if (species == SPECIES_MORPEKO_FULL_BELLY) + EXPECT_EQ(player->species, SPECIES_MORPEKO_HANGRY); + else + EXPECT_EQ(player->species, SPECIES_MORPEKO_FULL_BELLY); + } +} + +SINGLE_BATTLE_TEST("Hunger Switch does not switch a mon transformed into Morpeko's form (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TRANSFORM) == EFFECT_TRANSFORM); + PLAYER(SPECIES_MORPEKO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_HUNGER_SWITCH); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TRANSFORM); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, opponent); + } +} + +SINGLE_BATTLE_TEST("Hunger Switch does not switch Morpeko's form when Terastallized (Traits)") +{ + GIVEN { + PLAYER(SPECIES_MORPEKO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_HUNGER_SWITCH); TeraType(TYPE_NORMAL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { } + TURN { MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_TERA); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + } +} + +SINGLE_BATTLE_TEST("Hunger Switch does not switch Morpeko's form after switching out while Terastallized (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ROAR) == EFFECT_ROAR); + PLAYER(SPECIES_MORPEKO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_HUNGER_SWITCH); TeraType(TYPE_NORMAL); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { } + TURN { MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_ROAR); } + TURN { SWITCH(player, 0); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + } THEN { + EXPECT_EQ(player->species, SPECIES_MORPEKO_HANGRY); + } +} +#endif diff --git a/test/battle/ability/hydration.c b/test/battle/ability/hydration.c index 7df2681c13fc..d8f2696a09bb 100644 --- a/test/battle/ability/hydration.c +++ b/test/battle/ability/hydration.c @@ -27,3 +27,32 @@ SINGLE_BATTLE_TEST("Hydration doesn't cure status conditions if Cloud Nine/Air L MESSAGE("Vaporeon was hurt by its burn!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Hydration cures non-volatile Status conditions if it is raining (Traits)") +{ + GIVEN { + PLAYER(SPECIES_VAPOREON) { Ability(ABILITY_WATER_ABSORB); Innates(ABILITY_HYDRATION); Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_RAIN_DANCE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_HYDRATION); + MESSAGE("Vaporeon's Hydration cured its burn problem!"); + STATUS_ICON(player, none: TRUE); + } +} + +SINGLE_BATTLE_TEST("Hydration doesn't cure status conditions if Cloud Nine/Air Lock is on the field (Traits)") +{ + GIVEN { + PLAYER(SPECIES_VAPOREON) { Ability(ABILITY_WATER_ABSORB); Innates(ABILITY_HYDRATION); Status1(STATUS1_BURN); } + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_SWIFT_SWIM); Innates(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(player, MOVE_RAIN_DANCE); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_HYDRATION); + MESSAGE("Vaporeon was hurt by its burn!"); + } +} +#endif diff --git a/test/battle/ability/hyper_cutter.c b/test/battle/ability/hyper_cutter.c index 7b1070e6136a..ea3ed17946bf 100644 --- a/test/battle/ability/hyper_cutter.c +++ b/test/battle/ability/hyper_cutter.c @@ -154,3 +154,159 @@ SINGLE_BATTLE_TEST("Hyper Cutter doesn't prevent receiving negative Attack stage EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Hyper Cutter prevents intimidate (Traits)") +{ + s16 turnOneHit; + s16 turnTwoHit; + + GIVEN { + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_SHED_SKIN); } + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_KRABBY) { Ability(ABILITY_SHELL_ARMOR); Innates(ABILITY_HYPER_CUTTER); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_SCRATCH); } + + } SCENE { + HP_BAR(player, captureDamage: &turnOneHit); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + NONE_OF { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); } + ABILITY_POPUP(opponent, ABILITY_HYPER_CUTTER); + MESSAGE("The opposing Krabby's Hyper Cutter prevents Attack loss!"); + HP_BAR(player, captureDamage: &turnTwoHit); + } THEN { + EXPECT_EQ(turnOneHit, turnTwoHit); + } +} + +SINGLE_BATTLE_TEST("Hyper Cutter prevents Attack stage reduction from moves (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_GROWL) == EFFECT_ATTACK_DOWN); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KRABBY) { Ability(ABILITY_SHELL_ARMOR); Innates(ABILITY_HYPER_CUTTER); } + } WHEN { + TURN { MOVE(player, MOVE_GROWL); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_HYPER_CUTTER); + MESSAGE("The opposing Krabby's Hyper Cutter prevents Attack loss!"); + } +} + +SINGLE_BATTLE_TEST("Hyper Cutter doesn't prevent Attack reduction from burn (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_WILL_O_WISP) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_WILL_O_WISP) == MOVE_EFFECT_BURN); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KRABBY) { Ability(ABILITY_SHELL_ARMOR); Innates(ABILITY_HYPER_CUTTER); } + } WHEN { + TURN { MOVE(player, MOVE_WILL_O_WISP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WILL_O_WISP, player); + MESSAGE("The opposing Krabby was burned!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Hyper Cutter is ignored by Mold Breaker (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_GROWL) == EFFECT_ATTACK_DOWN); + PLAYER(SPECIES_PINSIR) { Ability(ABILITY_MOXIE); Innates(ABILITY_MOLD_BREAKER); } + OPPONENT(SPECIES_KRABBY) { Ability(ABILITY_SHELL_ARMOR); Innates(ABILITY_HYPER_CUTTER); } + } WHEN { + TURN { MOVE(player, MOVE_GROWL); } + } SCENE { + ABILITY_POPUP(player, ABILITY_MOLD_BREAKER); + MESSAGE("Pinsir breaks the mold!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, player); + MESSAGE("The opposing Krabby's Attack fell!"); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_HYPER_CUTTER); + MESSAGE("The opposing Krabby's Hyper Cutter prevents Attack loss!"); + } + } +} + +SINGLE_BATTLE_TEST("Hyper Cutter doesn't prevent Attack stage reduction from moves used by the user (Traits)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffectSelf(MOVE_SUPERPOWER, MOVE_EFFECT_ATK_DEF_DOWN) == TRUE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KRABBY) { Ability(ABILITY_SHELL_ARMOR); Innates(ABILITY_HYPER_CUTTER); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUPERPOWER); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPERPOWER, opponent); + MESSAGE("The opposing Krabby's Attack fell!"); + MESSAGE("The opposing Krabby's Defense fell!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Hyper Cutter doesn't prevent Topsy-Turvy (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SWORDS_DANCE) == EFFECT_ATTACK_UP_2); + ASSUME(GetMoveEffect(MOVE_TOPSY_TURVY) == EFFECT_TOPSY_TURVY); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KRABBY) { Ability(ABILITY_SHELL_ARMOR); Innates(ABILITY_HYPER_CUTTER); } + } WHEN { + TURN { MOVE(opponent, MOVE_SWORDS_DANCE); MOVE(player, MOVE_TOPSY_TURVY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponent); + MESSAGE("The opposing Krabby's Attack sharply rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOPSY_TURVY, player); + MESSAGE("All stat changes on the opposing Krabby were inverted!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 2); + } +} + +SINGLE_BATTLE_TEST("Hyper Cutter doesn't prevent Spectral Thief from resetting positive Attack stage changes (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SWORDS_DANCE) == EFFECT_ATTACK_UP_2); + ASSUME(GetMoveEffect(MOVE_SPECTRAL_THIEF) == EFFECT_SPECTRAL_THIEF); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KRABBY) { Ability(ABILITY_SHELL_ARMOR); Innates(ABILITY_HYPER_CUTTER); } + } WHEN { + TURN { MOVE(opponent, MOVE_SWORDS_DANCE); MOVE(player, MOVE_SPECTRAL_THIEF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponent); + MESSAGE("The opposing Krabby's Attack sharply rose!"); + MESSAGE("Wobbuffet stole the target's boosted stats!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPECTRAL_THIEF, player); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Hyper Cutter doesn't prevent receiving negative Attack stage changes from Baton Pass (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_GROWL) == EFFECT_ATTACK_DOWN); + ASSUME(GetMoveEffect(MOVE_BATON_PASS) == EFFECT_BATON_PASS); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KRABBY) { Ability(ABILITY_SHELL_ARMOR); Innates(ABILITY_HYPER_CUTTER); } + } WHEN { + TURN { MOVE(player, MOVE_GROWL); + MOVE(opponent, MOVE_BATON_PASS); + SEND_OUT(opponent, 1); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BATON_PASS, opponent); + MESSAGE("2 sent out Krabby!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + } +} +#endif diff --git a/test/battle/ability/ice_body.c b/test/battle/ability/ice_body.c index 07890d52f222..2c2588c86616 100644 --- a/test/battle/ability/ice_body.c +++ b/test/battle/ability/ice_body.c @@ -52,3 +52,52 @@ SINGLE_BATTLE_TEST("Ice Body doesn't recover HP if Cloud Nine/Air Lock is on the NOT ABILITY_POPUP(player, ABILITY_ICE_BODY); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Ice Body prevents damage from hail (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_MOODY); Innates(ABILITY_ICE_BODY); } + OPPONENT(SPECIES_GLALIE) { Ability(ABILITY_MOODY); Innates(ABILITY_ICE_BODY); } + } WHEN { + TURN { MOVE(player, move); MOVE(opponent, MOVE_SKILL_SWAP); } + } SCENE { + NONE_OF { HP_BAR(player); } + } +} + +SINGLE_BATTLE_TEST("Ice Body recovers 1/16th of Max HP in hail. (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + GIVEN { + PLAYER(SPECIES_GLALIE) { Ability(ABILITY_INNER_FOCUS); Innates(ABILITY_ICE_BODY); HP(1); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ICE_BODY); + HP_BAR(player, damage: -(100 / 16)); + MESSAGE("Glalie's Ice Body healed it a little bit!"); + } +} + +SINGLE_BATTLE_TEST("Ice Body doesn't recover HP if Cloud Nine/Air Lock is on the field (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + GIVEN { + PLAYER(SPECIES_GLALIE) { Ability(ABILITY_MOODY); Innates(ABILITY_ICE_BODY); HP(1); MaxHP(100); } + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_SWIFT_SWIM); Innates(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ICE_BODY); + } +} +#endif diff --git a/test/battle/ability/ice_face.c b/test/battle/ability/ice_face.c index 4abb9a9baf11..2d5524a4faed 100644 --- a/test/battle/ability/ice_face.c +++ b/test/battle/ability/ice_face.c @@ -175,3 +175,170 @@ SINGLE_BATTLE_TEST("Ice Face is not restored if hail or snow and Eiscue are alre } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Ice Face blocks physical moves, changing Eiscue into its Noice Face form (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_EISCUE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ICE_FACE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + } +} + +SINGLE_BATTLE_TEST("Ice Face does not block special moves, Eiscue stays in Ice Face form (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_EMBER) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_EISCUE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ICE_FACE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_EMBER); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ICE_FACE); + } +} + +SINGLE_BATTLE_TEST("Ice Face is restored if hail or snow begins while Noice Face Eiscue is out (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + PARAMETRIZE { move = MOVE_HAIL; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveEffect(MOVE_SNOWSCAPE) == EFFECT_SNOWSCAPE); + ASSUME(GetMoveEffect(MOVE_HAIL) == EFFECT_HAIL); + PLAYER(SPECIES_EISCUE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ICE_FACE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(opponent, move); } + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + } +} + +SINGLE_BATTLE_TEST("Ice Face is restored if Noice Face Eiscue is sent in while hail or snow is active (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + PARAMETRIZE { move = MOVE_HAIL; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveEffect(MOVE_SNOWSCAPE) == EFFECT_SNOWSCAPE); + ASSUME(GetMoveEffect(MOVE_HAIL) == EFFECT_HAIL); + PLAYER(SPECIES_EISCUE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ICE_FACE); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_SCRATCH); } + TURN { SWITCH(player, 1); MOVE(opponent, move); } + TURN { SWITCH(player, 0); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + } +} + +SINGLE_BATTLE_TEST("Ice Face is not restored if Eiscue changes into Noice Face form while there's already hail or snow (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + PARAMETRIZE { move = MOVE_HAIL; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveEffect(MOVE_SNOWSCAPE) == EFFECT_SNOWSCAPE); + ASSUME(GetMoveEffect(MOVE_HAIL) == EFFECT_HAIL); + PLAYER(SPECIES_EISCUE) { HP(1); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ICE_FACE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + MESSAGE("Eiscue used Celebrate!"); + MESSAGE("Eiscue fainted!"); + } +} + +SINGLE_BATTLE_TEST("Ice Face form change persists after switching out (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_EISCUE) { HP(1); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ICE_FACE); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_CELEBRATE); } + TURN { SWITCH(player, 0); MOVE(opponent, MOVE_SCRATCH); SEND_OUT(player, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + MESSAGE("Eiscue fainted!"); + } +} + +SINGLE_BATTLE_TEST("Ice Face doesn't transform Eiscue if Cloud Nine/Air Lock is on the field (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_EISCUE) { HP(1); } + OPPONENT(SPECIES_RAYQUAZA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_AIR_LOCK); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SNOWSCAPE); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + MESSAGE("Eiscue fainted!"); + } +} + +SINGLE_BATTLE_TEST("Ice Face is not restored if hail or snow and Eiscue are already out (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + PARAMETRIZE { move = MOVE_HAIL; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveEffect(MOVE_SNOWSCAPE) == EFFECT_SNOWSCAPE); + ASSUME(GetMoveEffect(MOVE_HAIL) == EFFECT_HAIL); + PLAYER(SPECIES_EISCUE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ICE_FACE); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); MOVE(player, move); } + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { SWITCH(opponent, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + NONE_OF { + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + } + } +} +#endif diff --git a/test/battle/ability/ice_scales.c b/test/battle/ability/ice_scales.c index ea96f64961bf..2d382d038f3d 100644 --- a/test/battle/ability/ice_scales.c +++ b/test/battle/ability/ice_scales.c @@ -28,3 +28,33 @@ SINGLE_BATTLE_TEST("Ice Scales halves the damage from special moves", s16 damage EXPECT_EQ(results[4].damage, results[5].damage); // Ice Scales doesn't affect the damage of physical moves } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Ice Scales halves the damage from special moves (Traits)", s16 damage) +{ + u32 move; + enum Ability ability; + PARAMETRIZE { ability = ABILITY_SHIELD_DUST; move = MOVE_PSYCHIC; } + PARAMETRIZE { ability = ABILITY_ICE_SCALES; move = MOVE_PSYCHIC; } + PARAMETRIZE { ability = ABILITY_SHIELD_DUST; move = MOVE_PSYSHOCK; } + PARAMETRIZE { ability = ABILITY_ICE_SCALES; move = MOVE_PSYSHOCK; } + PARAMETRIZE { ability = ABILITY_SHIELD_DUST; move = MOVE_SCRATCH; } + PARAMETRIZE { ability = ABILITY_ICE_SCALES; move = MOVE_SCRATCH; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_PSYCHIC) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMoveCategory(MOVE_PSYSHOCK) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMoveEffect(MOVE_PSYSHOCK) == EFFECT_PSYSHOCK); + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_FROSMOTH) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(0.5), results[1].damage); // Ice Scales halves the damage of Psychic + EXPECT_MUL_EQ(results[2].damage, UQ_4_12(0.5), results[3].damage); // Ice Scales halves the damage of Psyshock, even if it targets Defense + EXPECT_EQ(results[4].damage, results[5].damage); // Ice Scales doesn't affect the damage of physical moves + } +} +#endif diff --git a/test/battle/ability/illusion.c b/test/battle/ability/illusion.c index 21ecafc1543b..26fd6c70246b 100644 --- a/test/battle/ability/illusion.c +++ b/test/battle/ability/illusion.c @@ -131,3 +131,93 @@ SINGLE_BATTLE_TEST("Illusion breaks when attacked behind a substitute") MESSAGE("The opposing Zoroark's illusion wore off!"); } } + +// This test is eyes on only +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Illusion can only imitate Normal Form terapagos (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ZOROARK) { Moves(MOVE_CELEBRATE); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ILLUSION); } + PLAYER(SPECIES_TERAPAGOS) { Moves(MOVE_CELEBRATE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + // Zoroark is out, should be normal form Terapagos + // Switch to Terapagos which enters Terastal Form + TURN { SWITCH(player, 1); } + // Switch back to Zoroark, should not be Terastal Terapagos + TURN { SWITCH(player, 0); MOVE(opponent, MOVE_SCRATCH);} + // Switch back to Terapagos + TURN { SWITCH(player, 1); } + // Terapagos Stellar, Zoroark gets Roared in, should not be Stellar Terapagos + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_ROAR); } + // Reveal the Zoroark + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_SCRATCH); } + } +} + +SINGLE_BATTLE_TEST("Illusion breaks if the target faints (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ZOROARK) { HP(1); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ILLUSION); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ILLUSION_OFF, player); + MESSAGE("Zoroark's illusion wore off!"); + } +} + +SINGLE_BATTLE_TEST("Illusion breaks if the attacker faints (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FINAL_GAMBIT) == EFFECT_FINAL_GAMBIT); + PLAYER(SPECIES_ZOROARK) { HP(1); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ILLUSION); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FINAL_GAMBIT); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FINAL_GAMBIT, player); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ILLUSION_OFF, player); + MESSAGE("Zoroark's illusion wore off!"); + } +} + +SINGLE_BATTLE_TEST("Illusion cannot imitate if the user is on the last slot (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_ZOROARK) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ILLUSION); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(player, 1); } + } THEN { + EXPECT_EQ(player->species, SPECIES_ZOROARK); + EXPECT_EQ(gBattleStruct->illusion[0].state, ILLUSION_OFF); // Battler is Zoroark and not Illusioned + } +} + + +SINGLE_BATTLE_TEST("Illusion breaks when attacked behind a substitute (Traits)") +{ + GIVEN { + PLAYER(SPECIES_DRAGAPULT) {Ability(ABILITY_INFILTRATOR); Speed(1);}; + OPPONENT(SPECIES_WOBBUFFET) {Speed(2);}; + OPPONENT(SPECIES_ZOROARK) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ILLUSION); Speed(2);}; + OPPONENT(SPECIES_WYNAUT) {Speed(2);}; + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_SHED_TAIL); SEND_OUT(opponent, 1);} + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SWAP_FROM_SUBSTITUTE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ILLUSION_OFF, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SWAP_TO_SUBSTITUTE, opponent); + MESSAGE("The opposing Zoroark's illusion wore off!"); + } +} + +#endif diff --git a/test/battle/ability/immunity.c b/test/battle/ability/immunity.c index 9199638ad578..1badb477e5f0 100644 --- a/test/battle/ability/immunity.c +++ b/test/battle/ability/immunity.c @@ -79,3 +79,84 @@ SINGLE_BATTLE_TEST("Immunity cures existing poison on turn 0") EXPECT_EQ(player->status1, STATUS1_NONE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Immunity prevents Poison Sting poison (Traits)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_POISON_STING, MOVE_EFFECT_POISON) == TRUE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SNORLAX) { Ability(ABILITY_GLUTTONY); Innates(ABILITY_IMMUNITY); } + } WHEN { + TURN { MOVE(player, MOVE_POISON_STING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_STING, player); + NOT STATUS_ICON(opponent, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Immunity prevents Toxic bad poison (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SNORLAX) { Ability(ABILITY_GLUTTONY); Innates(ABILITY_IMMUNITY); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC); } + } SCENE { + MESSAGE("Wobbuffet used Toxic!"); + ABILITY_POPUP(opponent, ABILITY_IMMUNITY); + MESSAGE("It doesn't affect the opposing Snorlax…"); + NOT STATUS_ICON(opponent, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Immunity prevents Toxic Spikes poison (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOXIC_SPIKES) == EFFECT_TOXIC_SPIKES); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SNORLAX) { Ability(ABILITY_GLUTTONY); Innates(ABILITY_IMMUNITY); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC_SPIKES); } + TURN { SWITCH(opponent, 1); } + } SCENE { + NOT STATUS_ICON(opponent, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Immunity doesn't prevent Pokémon from being poisoned by Toxic Spikes on switch-in if forced in by phazing with Mold Breaker, but it cures it immediately (Traits)") +{ + 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_GLUTTONY); Innates(ABILITY_IMMUNITY); } + OPPONENT(SPECIES_PINSIR) { Ability(ABILITY_MOXIE); Innates(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); + } +} + +SINGLE_BATTLE_TEST("Immunity cures existing poison on turn 0 (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ZANGOOSE) { + Ability(ABILITY_GLUTTONY); Innates(ABILITY_IMMUNITY); + Status1(STATUS1_POISON); + } + OPPONENT(SPECIES_WOBBUFFET); + } SCENE { + ABILITY_POPUP(player, ABILITY_IMMUNITY); + TURN { MOVE(player, MOVE_SPLASH); } + } THEN { + EXPECT_EQ(player->status1, STATUS1_NONE); + } +} +#endif diff --git a/test/battle/ability/infiltrator.c b/test/battle/ability/infiltrator.c index bd85b7d5b885..07d27b8dc514 100644 --- a/test/battle/ability/infiltrator.c +++ b/test/battle/ability/infiltrator.c @@ -236,3 +236,241 @@ SINGLE_BATTLE_TEST("Infiltrator doesn't ignore a battler's Substitute when using NOT ANIMATION(ANIM_TYPE_MOVE, move, player); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Infiltrator bypasses the opponent's Light Screen/Reflect/Aurora Veil (Traits)", s16 damage) +{ + u32 screenMove, attackingMove, ability; + + PARAMETRIZE { screenMove = MOVE_LIGHT_SCREEN; attackingMove = MOVE_WATER_GUN; ability = ABILITY_INFILTRATOR; } + PARAMETRIZE { screenMove = MOVE_LIGHT_SCREEN; attackingMove = MOVE_WATER_GUN; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE { screenMove = MOVE_REFLECT; attackingMove = MOVE_SCRATCH; ability = ABILITY_INFILTRATOR; } + PARAMETRIZE { screenMove = MOVE_REFLECT; attackingMove = MOVE_SCRATCH; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE { screenMove = MOVE_AURORA_VEIL; attackingMove = MOVE_WATER_GUN; ability = ABILITY_INFILTRATOR; } + PARAMETRIZE { screenMove = MOVE_AURORA_VEIL; attackingMove = MOVE_WATER_GUN; ability = ABILITY_CLEAR_BODY; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_LIGHT_SCREEN) == EFFECT_LIGHT_SCREEN); + ASSUME(GetMoveEffect(MOVE_REFLECT) == EFFECT_REFLECT); + ASSUME(GetMoveEffect(MOVE_AURORA_VEIL) == EFFECT_AURORA_VEIL); + ASSUME(GetMoveCategory(MOVE_WATER_GUN) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_DRAGAPULT) { Ability(ABILITY_CLEAR_BODY); Innates(ability); } + OPPONENT(SPECIES_ABOMASNOW) { Ability(ABILITY_SOUNDPROOF); Innates(ABILITY_SNOW_WARNING); } + } WHEN { + TURN { MOVE(opponent, screenMove); MOVE(player, attackingMove); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, screenMove, opponent); + ANIMATION(ANIM_TYPE_MOVE, attackingMove, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(0.5), results[1].damage); + EXPECT_MUL_EQ(results[2].damage, UQ_4_12(0.5), results[3].damage); + EXPECT_MUL_EQ(results[4].damage, UQ_4_12(0.5), results[5].damage); + } +} + +DOUBLE_BATTLE_TEST("Infiltrator doesn't bypass an ally's Light Screen/Reflect/Aurora Veil (Traits)", s16 damage) +{ + u32 screenMove, attackingMove, ability; + + PARAMETRIZE { screenMove = MOVE_LIGHT_SCREEN; attackingMove = MOVE_WATER_GUN; ability = ABILITY_INFILTRATOR; } + PARAMETRIZE { screenMove = MOVE_LIGHT_SCREEN; attackingMove = MOVE_WATER_GUN; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE { screenMove = MOVE_REFLECT; attackingMove = MOVE_SCRATCH; ability = ABILITY_INFILTRATOR; } + PARAMETRIZE { screenMove = MOVE_REFLECT; attackingMove = MOVE_SCRATCH; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE { screenMove = MOVE_AURORA_VEIL; attackingMove = MOVE_WATER_GUN; ability = ABILITY_INFILTRATOR; } + PARAMETRIZE { screenMove = MOVE_AURORA_VEIL; attackingMove = MOVE_WATER_GUN; ability = ABILITY_CLEAR_BODY; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_LIGHT_SCREEN) == EFFECT_LIGHT_SCREEN); + ASSUME(GetMoveEffect(MOVE_REFLECT) == EFFECT_REFLECT); + ASSUME(GetMoveEffect(MOVE_AURORA_VEIL) == EFFECT_AURORA_VEIL); + ASSUME(GetMoveCategory(MOVE_WATER_GUN) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_DRAGAPULT) { Ability(ABILITY_CLEAR_BODY); Innates(ability); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ABOMASNOW) { Ability(ABILITY_SOUNDPROOF); Innates(ABILITY_SNOW_WARNING); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerRight, screenMove); MOVE(playerLeft, attackingMove, target: playerRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, screenMove, playerRight); + ANIMATION(ANIM_TYPE_MOVE, attackingMove, playerLeft); + HP_BAR(playerRight, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_EQ(results[2].damage, results[3].damage); + EXPECT_EQ(results[4].damage, results[5].damage); + } +} + +SINGLE_BATTLE_TEST("Infiltrator bypasses the opponent's Mist (Traits)") +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE { ability = ABILITY_INFILTRATOR; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_MIST) == EFFECT_MIST); + ASSUME(GetMoveEffect(MOVE_SCREECH) == EFFECT_DEFENSE_DOWN_2); + PLAYER(SPECIES_DRAGAPULT) { Ability(ABILITY_CLEAR_BODY); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_MIST); MOVE(player, MOVE_SCREECH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIST, opponent); + if (ability == ABILITY_INFILTRATOR) + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCREECH, player); + else + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SCREECH, player); + } +} + +DOUBLE_BATTLE_TEST("Infiltrator doesn't bypass an ally's Mist (Traits)") +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE { ability = ABILITY_INFILTRATOR; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_MIST) == EFFECT_MIST); + ASSUME(GetMoveEffect(MOVE_SCREECH) == EFFECT_DEFENSE_DOWN_2); + PLAYER(SPECIES_DRAGAPULT) { Ability(ABILITY_CLEAR_BODY); Innates(ability); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerRight, MOVE_MIST); MOVE(playerLeft, MOVE_SCREECH, target: playerRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIST, playerRight); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SCREECH, playerLeft); + } +} + +SINGLE_BATTLE_TEST("Infiltrator bypasses the opponent's Safeguard (Traits)") +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE { ability = ABILITY_INFILTRATOR; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SAFEGUARD) == EFFECT_SAFEGUARD); + ASSUME(GetMoveEffect(MOVE_THUNDER_WAVE) == EFFECT_NON_VOLATILE_STATUS); + PLAYER(SPECIES_DRAGAPULT) { Ability(ABILITY_CLEAR_BODY); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SAFEGUARD); MOVE(player, MOVE_THUNDER_WAVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SAFEGUARD, opponent); + if (ability == ABILITY_INFILTRATOR) + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_WAVE, player); + else + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_WAVE, player); + } +} + +DOUBLE_BATTLE_TEST("Infiltrator doesn't bypass an ally's Safeguard (Traits)") +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE { ability = ABILITY_INFILTRATOR; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SAFEGUARD) == EFFECT_SAFEGUARD); + ASSUME(GetMoveEffect(MOVE_THUNDER_WAVE) == EFFECT_NON_VOLATILE_STATUS); + PLAYER(SPECIES_DRAGAPULT) { Ability(ABILITY_CLEAR_BODY); Innates(ability); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerRight, MOVE_SAFEGUARD); MOVE(playerLeft, MOVE_THUNDER_WAVE, target: playerRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SAFEGUARD, playerRight); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_WAVE, playerLeft); + } +} + +SINGLE_BATTLE_TEST("Infiltrator bypasses the opponent's Substitute (Gen 6+) (Traits)") +{ + u32 ability, config; + + PARAMETRIZE { ability = ABILITY_CLEAR_BODY; config = GEN_5; } + PARAMETRIZE { ability = ABILITY_INFILTRATOR; config = GEN_5; } + PARAMETRIZE { ability = ABILITY_CLEAR_BODY; config = GEN_6; } + PARAMETRIZE { ability = ABILITY_INFILTRATOR; config = GEN_6; } + + GIVEN { + WITH_CONFIG(CONFIG_INFILTRATOR_SUBSTITUTE, config); + ASSUME(GetMoveEffect(MOVE_SUBSTITUTE) == EFFECT_SUBSTITUTE); + ASSUME(!MoveIgnoresSubstitute(MOVE_SCRATCH)); + PLAYER(SPECIES_DRAGAPULT) { Ability(ABILITY_CLEAR_BODY); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, opponent); + if (ability == ABILITY_INFILTRATOR && config >= GEN_6) { + NOT SUB_HIT(opponent); + } else { + SUB_HIT(opponent); + } + } +} + +DOUBLE_BATTLE_TEST("Infiltrator bypasses an ally's Substitute (Gen 6+) (Traits)") +{ + u32 ability, config; + + PARAMETRIZE { ability = ABILITY_CLEAR_BODY; config = GEN_5; } + PARAMETRIZE { ability = ABILITY_INFILTRATOR; config = GEN_5; } + PARAMETRIZE { ability = ABILITY_CLEAR_BODY; config = GEN_6; } + PARAMETRIZE { ability = ABILITY_INFILTRATOR; config = GEN_6; } + + GIVEN { + WITH_CONFIG(CONFIG_INFILTRATOR_SUBSTITUTE, config); + ASSUME(GetMoveEffect(MOVE_SUBSTITUTE) == EFFECT_SUBSTITUTE); + ASSUME(!MoveIgnoresSubstitute(MOVE_SCRATCH)); + PLAYER(SPECIES_DRAGAPULT) { Ability(ABILITY_CLEAR_BODY); Innates(ability); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerRight, MOVE_SUBSTITUTE); MOVE(playerLeft, MOVE_SCRATCH, target: playerRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, playerRight); + if (ability == ABILITY_INFILTRATOR && config == GEN_6) { + NOT SUB_HIT(playerRight); + } else { + SUB_HIT(playerRight); + } + } +} + +SINGLE_BATTLE_TEST("Infiltrator doesn't ignore a battler's Substitute when using Transform or Sky Drop (Traits)") +{ + u32 ability, move; + + PARAMETRIZE { ability = ABILITY_CLEAR_BODY; move = MOVE_TRANSFORM; } + PARAMETRIZE { ability = ABILITY_INFILTRATOR; move = MOVE_TRANSFORM; } + PARAMETRIZE { ability = ABILITY_CLEAR_BODY; move = MOVE_SKY_DROP; } + PARAMETRIZE { ability = ABILITY_INFILTRATOR; move = MOVE_SKY_DROP; } + + GIVEN { + WITH_CONFIG(CONFIG_INFILTRATOR_SUBSTITUTE, GEN_6); + ASSUME(GetMoveEffect(MOVE_SUBSTITUTE) == EFFECT_SUBSTITUTE); + ASSUME(GetMoveEffect(MOVE_TRANSFORM) == EFFECT_TRANSFORM); + ASSUME(GetMoveEffect(MOVE_SKY_DROP) == EFFECT_SKY_DROP); + PLAYER(SPECIES_DRAGAPULT) { Ability(ABILITY_CLEAR_BODY); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, opponent); + NOT ANIMATION(ANIM_TYPE_MOVE, move, player); + } +} +#endif \ No newline at end of file diff --git a/test/battle/ability/innards_out.c b/test/battle/ability/innards_out.c index 87f730c1bb7a..9806542625df 100644 --- a/test/battle/ability/innards_out.c +++ b/test/battle/ability/innards_out.c @@ -128,3 +128,111 @@ SINGLE_BATTLE_TEST("Innards Out triggers if Future Sight user is back on the fie HP_BAR(opponent); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Innards Out deal dmg on fainting equal to the amount of dmg inflicted on the Innards Out mon (Traits)") +{ + u16 hp = 0; + PARAMETRIZE { hp = 5; } + PARAMETRIZE { hp = 15; } + PARAMETRIZE { hp = 50; } + PARAMETRIZE { hp = 100; } // This takes out Wobbuffet. + + GIVEN { + PLAYER(SPECIES_PYUKUMUKU) { HP(hp); Ability(ABILITY_UNAWARE); Innates(ABILITY_INNARDS_OUT); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(70); SpAttack(1000); } + OPPONENT(SPECIES_WOBBUFFET); + ASSUME(!IsBattleMoveStatus(MOVE_PSYCHIC)); + ASSUME(GetMoveCategory(MOVE_PSYCHIC) == DAMAGE_CATEGORY_SPECIAL); + } WHEN { + TURN { MOVE(opponent, MOVE_PSYCHIC); SEND_OUT(player, 1); if (hp == 100) { SEND_OUT(opponent, 1); } } + } SCENE { + MESSAGE("The opposing Wobbuffet used Psychic!"); + HP_BAR(player, hp); + ABILITY_POPUP(player, ABILITY_INNARDS_OUT); + HP_BAR(opponent, hp); + } +} + +// According to Showdown Innards Out triggers, but does nothing. +SINGLE_BATTLE_TEST("Innards Out does not damage Magic Guard Pokemon (Traits)") +{ + GIVEN { + PLAYER(SPECIES_PYUKUMUKU) { HP(1); Ability(ABILITY_UNAWARE); Innates(ABILITY_INNARDS_OUT); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CLEFABLE) { Ability(ABILITY_CUTE_CHARM); Innates(ABILITY_MAGIC_GUARD); } + ASSUME(!IsBattleMoveStatus(MOVE_PSYCHIC)); + } WHEN { + TURN { MOVE(opponent, MOVE_PSYCHIC); SEND_OUT(player, 1); } + } SCENE { + MESSAGE("The opposing Clefable used Psychic!"); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_INNARDS_OUT); + NOT HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Innards Out uses correct damage amount for Future Sight (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FUTURE_SIGHT) == EFFECT_FUTURE_SIGHT); + PLAYER(SPECIES_PYUKUMUKU) { HP(1); Ability(ABILITY_UNAWARE); Innates(ABILITY_INNARDS_OUT); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_FUTURE_SIGHT); } + TURN { } + TURN { SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, opponent); + MESSAGE("Pyukumuku took the Future Sight attack!"); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_INNARDS_OUT); + HP_BAR(opponent, damage: 1); + } +} + +SINGLE_BATTLE_TEST("Innards Out doesn't trigger if Future Sight user is not on field (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FUTURE_SIGHT) == EFFECT_FUTURE_SIGHT); + PLAYER(SPECIES_PYUKUMUKU) { HP(1); Ability(ABILITY_UNAWARE); Innates(ABILITY_INNARDS_OUT); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_FUTURE_SIGHT); } + TURN { SWITCH(opponent, 1); } + TURN { SEND_OUT(player, 1); } //SEND_OUT(opponent, 0); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, opponent); + MESSAGE("Pyukumuku took the Future Sight attack!"); + HP_BAR(player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_INNARDS_OUT); + HP_BAR(opponent); + } + } +} + +SINGLE_BATTLE_TEST("Innards Out triggers if Future Sight user is back on the field (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FUTURE_SIGHT) == EFFECT_FUTURE_SIGHT); + PLAYER(SPECIES_PYUKUMUKU) { HP(1); Ability(ABILITY_UNAWARE); Innates(ABILITY_INNARDS_OUT); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_FUTURE_SIGHT); } + TURN { SWITCH(opponent, 1); } + TURN { SWITCH(opponent, 0); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, opponent); + MESSAGE("Pyukumuku took the Future Sight attack!"); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_INNARDS_OUT); + HP_BAR(opponent); + } +} +#endif diff --git a/test/battle/ability/inner_focus.c b/test/battle/ability/inner_focus.c index ef0d8ea8a0f0..ac0306462edb 100644 --- a/test/battle/ability/inner_focus.c +++ b/test/battle/ability/inner_focus.c @@ -82,3 +82,87 @@ SINGLE_BATTLE_TEST("Mold Breaker ignores Inner Focus") MESSAGE("The opposing Zubat flinched and couldn't move!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Inner Focus doesn't prevent intimidate (Gen3-7) (Traits)") +{ + s16 turnOneHit; + s16 turnTwoHit; + + GIVEN { + WITH_CONFIG(CONFIG_UPDATED_INTIMIDATE, GEN_7); + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_SHED_SKIN); }; + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); }; + OPPONENT(SPECIES_ZUBAT) { Ability(ABILITY_INFILTRATOR); Innates(ABILITY_INNER_FOCUS); }; + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_SCRATCH); } + + } SCENE { + // Turn 1 + HP_BAR(player, captureDamage: &turnOneHit); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Ekans's Intimidate cuts the opposing Zubat's Attack!"); + // Turn 2 + HP_BAR(player, captureDamage: &turnTwoHit); + } THEN { + EXPECT_GT(turnOneHit, turnTwoHit); + } +} + +SINGLE_BATTLE_TEST("Inner Focus prevents intimidate (Gen8+) (Traits)") +{ + s16 turnOneHit; + s16 turnTwoHit; + + GIVEN { + WITH_CONFIG(CONFIG_UPDATED_INTIMIDATE, GEN_8); + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_SHED_SKIN); }; + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); }; + OPPONENT(SPECIES_ZUBAT) { Ability(ABILITY_INFILTRATOR); Innates(ABILITY_INNER_FOCUS); }; + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_SCRATCH); } + + } SCENE { + HP_BAR(player, captureDamage: &turnOneHit); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + NONE_OF { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); } + ABILITY_POPUP(opponent, ABILITY_INNER_FOCUS); + MESSAGE("The opposing Zubat's Inner Focus prevents stat loss!"); + HP_BAR(player, captureDamage: &turnTwoHit); + } THEN { + EXPECT_EQ(turnOneHit, turnTwoHit); + } +} + +SINGLE_BATTLE_TEST("Inner Focus prevents flinching (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZUBAT) { Ability(ABILITY_INFILTRATOR); Innates(ABILITY_INNER_FOCUS); }; + } WHEN { + TURN { MOVE(player, MOVE_FAKE_OUT); + MOVE(opponent, MOVE_SCRATCH); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FAKE_OUT, player); + NONE_OF { MESSAGE("The opposing Zubat flinched and couldn't move!"); } + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + } +} + +SINGLE_BATTLE_TEST("Mold Breaker ignores Inner Focus (Traits)") +{ + GIVEN { + PLAYER(SPECIES_PINSIR) { Ability(ABILITY_HYPER_CUTTER); Innates(ABILITY_MOLD_BREAKER); }; + OPPONENT(SPECIES_ZUBAT) { Ability(ABILITY_INFILTRATOR); Innates(ABILITY_INNER_FOCUS); }; + } WHEN { + TURN { MOVE(player, MOVE_FAKE_OUT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FAKE_OUT, player); + MESSAGE("The opposing Zubat flinched and couldn't move!"); + } +} +#endif diff --git a/test/battle/ability/insomnia.c b/test/battle/ability/insomnia.c index c95b29490780..fa376b799d4c 100644 --- a/test/battle/ability/insomnia.c +++ b/test/battle/ability/insomnia.c @@ -58,3 +58,63 @@ SINGLE_BATTLE_TEST("Insomnia prevents Rest") } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Insomnia prevents sleep (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + PLAYER(SPECIES_DROWZEE) { Ability(ABILITY_INSOMNIA); Innates(ABILITY_INSOMNIA); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SPORE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_INSOMNIA); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, player); + STATUS_ICON(player, sleep: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Insomnia prevents yawn (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_YAWN) == EFFECT_YAWN); + PLAYER(SPECIES_DROWZEE) { Ability(ABILITY_INSOMNIA); Innates(ABILITY_INSOMNIA); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_YAWN); } + TURN {} + TURN {} + } SCENE { + ABILITY_POPUP(player, ABILITY_INSOMNIA); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_YAWN, opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, player); + STATUS_ICON(player, sleep: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Insomnia prevents Rest (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_REST) == EFFECT_REST); + PLAYER(SPECIES_DROWZEE) { Ability(ABILITY_INSOMNIA); Innates(ABILITY_INSOMNIA); HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_REST); } + } SCENE { + ABILITY_POPUP(player, ABILITY_INSOMNIA); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, player); + STATUS_ICON(player, sleep: TRUE); + HP_BAR(player); + } + } +} +#endif diff --git a/test/battle/ability/intimidate.c b/test/battle/ability/intimidate.c index 443566cb7647..c2fada544405 100644 --- a/test/battle/ability/intimidate.c +++ b/test/battle/ability/intimidate.c @@ -443,3 +443,506 @@ DOUBLE_BATTLE_TEST("Intimidate will not miss timing for competitive") ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Intimidate (opponent) lowers player's attack after switch out (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_INTIMIDATE; } + PARAMETRIZE { ability = ABILITY_SHED_SKIN; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_UNNERVE); Innates(ability); } + } WHEN { + TURN { SWITCH(opponent, 1); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + if (ability == ABILITY_INTIMIDATE) + { + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("The opposing Arbok's Intimidate cuts Wobbuffet's Attack!"); + } + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Intimidate (opponent) lowers player's attack after KO (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_INTIMIDATE; } + PARAMETRIZE { ability = ABILITY_SHED_SKIN; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(2); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_UNNERVE); Innates(ability); Speed(1); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + HP_BAR(opponent); + if (ability == ABILITY_INTIMIDATE) + { + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("The opposing Arbok's Intimidate cuts Wobbuffet's Attack!"); + } + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} + +DOUBLE_BATTLE_TEST("Intimidate doesn't activate on an empty field in a double battle (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + PLAYER(SPECIES_ABRA); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_EXPLOSION); SEND_OUT(playerLeft, 2); SEND_OUT(opponentLeft, 2); SEND_OUT(playerRight, 3); SEND_OUT(opponentRight, 3); } + TURN { MOVE(playerLeft, MOVE_CELEBRATE); } + } SCENE { + HP_BAR(playerLeft, hp: 0); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, playerLeft); + // Everyone faints. + + SEND_IN_MESSAGE("Ekans"); + NONE_OF { + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + } + MESSAGE("2 sent out Arbok!"); + NONE_OF { + ABILITY_POPUP(opponentLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + } + SEND_IN_MESSAGE("Abra"); + MESSAGE("2 sent out Wynaut!"); + // Intimidate activates after all battlers have been brought out + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("Ekans's Intimidate cuts the opposing Arbok's Attack!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("Ekans's Intimidate cuts the opposing Wynaut's Attack!"); + + ABILITY_POPUP(opponentLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("The opposing Arbok's Intimidate cuts Ekans's Attack!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("The opposing Arbok's Intimidate cuts Abra's Attack!"); + } +} + +SINGLE_BATTLE_TEST("Intimidate and Eject Button don't force the opponent to Attack (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_EJECT_BUTTON].holdEffect == HOLD_EFFECT_EJECT_BUTTON); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_HITMONTOP) { Ability(ABILITY_TECHNICIAN); Innates(ABILITY_INTIMIDATE); Moves(MOVE_SCRATCH); } + } WHEN { + TURN { + MOVE(player, MOVE_QUICK_ATTACK); + MOVE(opponent, MOVE_SCRATCH); + SEND_OUT(opponent, 1); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Wobbuffet is switched out with the Eject Button!"); + MESSAGE("2 sent out Hitmontop!"); + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + MESSAGE("The opposing Hitmontop's Intimidate cuts Wobbuffet's Attack!"); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + MESSAGE("The opposing Hitmontop used Scratch!"); + } + } +} + +DOUBLE_BATTLE_TEST("Intimidate activates on an empty slot (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_CROAGUNK); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_HITMONTOP) { Ability(ABILITY_TECHNICIAN); Innates(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_RALTS); + OPPONENT(SPECIES_AZURILL); + } WHEN { + TURN { + SWITCH(playerLeft, 2); + MOVE(playerRight, MOVE_GUNK_SHOT, target: opponentLeft); + MOVE(opponentRight, MOVE_SPLASH); + } + TURN { + SWITCH(playerLeft, 3); + MOVE(playerRight, MOVE_SPLASH); + } + + + } SCENE { + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Wynaut"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GUNK_SHOT, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, opponentRight); + SWITCH_OUT_MESSAGE("Wynaut"); + SEND_IN_MESSAGE("Hitmontop"); + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + NONE_OF { + MESSAGE("Hitmontop's Intimidate cuts the opposing Ralts's Attack!"); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("Hitmontop's Intimidate cuts the opposing Azurill's Attack!"); + } +} + +DOUBLE_BATTLE_TEST("Intimidate activates immediately after the mon was switched in as long as one opposing mon is alive (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TAPU_KOKO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ELECTRIC_SURGE); }; + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); Item(ITEM_ELECTRIC_SEED); } + OPPONENT(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_U_TURN, target: opponentLeft); SEND_OUT(playerLeft, 2); SEND_OUT(opponentLeft, 2); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_ELECTRIC_SURGE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, playerLeft); + HP_BAR(opponentLeft); + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Intimidate can not further lower opponents Atk stat if it is at minimum stages (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_CHARM); } + TURN { MOVE(opponent, MOVE_CHARM); } + TURN { MOVE(opponent, MOVE_CHARM); } + TURN { SWITCH(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, opponent); + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("The opposing Arbok's Intimidate cuts Wobbuffet's Attack!"); + } + MESSAGE("Wobbuffet's Attack won't go any lower!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], MIN_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Intimidate is not going to trigger if a mon switches out through u-turn and the opposing field is empty (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TREECKO); + OPPONENT(SPECIES_TORCHIC); + } WHEN { + TURN { + MOVE(opponentRight, MOVE_HEALING_WISH); + MOVE(playerLeft, MOVE_U_TURN, target: opponentLeft); + SEND_OUT(playerLeft, 2); + SEND_OUT(opponentLeft, 2); + SEND_OUT(opponentRight, 3); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEALING_WISH, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, playerLeft); + HP_BAR(opponentLeft); + NOT ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + MESSAGE("2 sent out Treecko!"); + MESSAGE("2 sent out Torchic!"); + NOT ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + } +} + +DOUBLE_BATTLE_TEST("Intimidate will correctly decrease the attack of the second mon after Protosynthesis activated (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WALKING_WAKE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTOSYNTHESIS); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(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); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + ABILITY_POPUP(playerLeft, ABILITY_PROTOSYNTHESIS); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } + } +} + +SINGLE_BATTLE_TEST("Intimidate does not lose timing after mega evolution and switch out by a hit escape move (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_U_TURN) == EFFECT_HIT_ESCAPE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MANECTRIC) { Item(ITEM_MANECTITE); } + OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_U_TURN, gimmick: GIMMICK_MEGA); SEND_OUT(opponent, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + } +} + +DOUBLE_BATTLE_TEST("Intimidate drop down both opposing atk before eject pack has the chance to activate (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(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 (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_MILOTIC) { Ability(ABILITY_MARVEL_SCALE); Innates(ABILITY_COMPETITIVE); } + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(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); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Intimidate and Eject Button don't force the opponent to Attack (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_EJECT_BUTTON].holdEffect == HOLD_EFFECT_EJECT_BUTTON); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_HITMONTOP) { Moves(MOVE_SCRATCH); } + } WHEN { + TURN { + MOVE(player, MOVE_QUICK_ATTACK); + MOVE(opponent, MOVE_SCRATCH); + SEND_OUT(opponent, 1); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Wobbuffet is switched out with the Eject Button!"); + MESSAGE("2 sent out Hitmontop!"); + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + MESSAGE("The opposing Hitmontop's Intimidate cuts Wobbuffet's Attack!"); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + MESSAGE("The opposing Hitmontop used Scratch!"); + } + } +} + +DOUBLE_BATTLE_TEST("Intimidate activates immediately after the mon was switched in as long as one opposing mon is alive (Multi)") +{ + GIVEN { + PLAYER(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); }; + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); Items(ITEM_GREAT_BALL, ITEM_ELECTRIC_SEED); } + OPPONENT(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_U_TURN, target: opponentLeft); SEND_OUT(playerLeft, 2); SEND_OUT(opponentLeft, 2); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_ELECTRIC_SURGE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, playerLeft); + HP_BAR(opponentLeft); + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Intimidate activates when it's no longer affected by Neutralizing Gas - opponent caused switches (Multi)") +{ + u32 move, item; + PARAMETRIZE { move = MOVE_SCRATCH; item = ITEM_EJECT_BUTTON; } + PARAMETRIZE { move = MOVE_GROWL; item = ITEM_EJECT_PACK; } + PARAMETRIZE { move = MOVE_ROAR; item = ITEM_NONE; } + PARAMETRIZE { move = MOVE_DRAGON_TAIL; item = ITEM_NONE; } + GIVEN { + ASSUME(gItemsInfo[ITEM_EJECT_BUTTON].holdEffect == HOLD_EFFECT_EJECT_BUTTON); + ASSUME(gItemsInfo[ITEM_EJECT_PACK].holdEffect == HOLD_EFFECT_EJECT_PACK); + ASSUME(GetMoveEffect(MOVE_GROWL) == EFFECT_ATTACK_DOWN); + ASSUME(GetMoveEffect(MOVE_ROAR) == EFFECT_ROAR); + ASSUME(GetMoveEffect(MOVE_DRAGON_TAIL) == EFFECT_HIT_SWITCH_TARGET); + PLAYER(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); Items(ITEM_GREAT_BALL, item); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_INTIMIDATE); } + } WHEN { + if (item != ITEM_NONE) { + TURN { MOVE(opponent, move); SEND_OUT(player, 1); } + } else { + TURN { MOVE(opponent, move); } + } + } SCENE { + ABILITY_POPUP(player, ABILITY_NEUTRALIZING_GAS); + MESSAGE("Neutralizing gas filled the area!"); + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + if (item != ITEM_NONE) + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("The effects of the neutralizing gas wore off!"); + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + if (item != ITEM_NONE) { + SEND_IN_MESSAGE("Wobbuffet"); + } else { + MESSAGE("Wobbuffet was dragged out!"); + } + } +} + +DOUBLE_BATTLE_TEST("Intimidate will correctly decrease the attack of the second mon after Protosynthesis activated (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WALKING_WAKE) { Ability(ABILITY_PROTOSYNTHESIS); Items(ITEM_GREAT_BALL, ITEM_BOOSTER_ENERGY); } + 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); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + ABILITY_POPUP(playerLeft, ABILITY_PROTOSYNTHESIS); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } + } +} + +SINGLE_BATTLE_TEST("Intimidate does not lose timing after mega evolution and switch out by a hit escape move (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_U_TURN) == EFFECT_HIT_ESCAPE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MANECTRIC) { Items(ITEM_GREAT_BALL, ITEM_MANECTITE); } + OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_INTIMIDATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_U_TURN, gimmick: GIMMICK_MEGA); SEND_OUT(opponent, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + } +} + +DOUBLE_BATTLE_TEST("Intimidate drop down both opposing atk before eject pack has the chance to activate (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, 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 (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, 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); + } +} +#endif diff --git a/test/battle/ability/intrepid_sword.c b/test/battle/ability/intrepid_sword.c index 2ea924fc26bd..fcc6a71884c0 100644 --- a/test/battle/ability/intrepid_sword.c +++ b/test/battle/ability/intrepid_sword.c @@ -156,3 +156,91 @@ SINGLE_BATTLE_TEST("Intrepid Sword and Dauntless Shield do not proc at max stage } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Intrepid Sword raises Attack by one stage (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZACIAN) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_INTREPID_SWORD); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_INTREPID_SWORD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing 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 every time it switches in (Gen8) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_INTREPID_SWORD, GEN_8); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZACIAN) { Ability(ABILITY_LIGHT_METAL); Innates(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("The opposing Zacian's Intrepid Sword raised its Attack!"); + ABILITY_POPUP(opponent, ABILITY_INTREPID_SWORD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing 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 (Gen9+) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_INTREPID_SWORD, GEN_9); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZACIAN) { Ability(ABILITY_LIGHT_METAL); Innates(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("The opposing 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("The opposing Zacian's Intrepid Sword raised its Attack!"); + } + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} + + +SINGLE_BATTLE_TEST("Intrepid Sword and Dauntless Shield do not proc at max stage (Baton Pass) (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_BATON_PASS) == EFFECT_BATON_PASS); + ASSUME(GetMoveEffect(MOVE_IRON_DEFENSE) == EFFECT_DEFENSE_UP_2); + ASSUME(GetMoveEffect(MOVE_SWORDS_DANCE) == EFFECT_ATTACK_UP_2); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ZAMAZENTA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DAUNTLESS_SHIELD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZACIAN) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_INTREPID_SWORD); } + } WHEN { + TURN { MOVE(player, MOVE_IRON_DEFENSE); MOVE(opponent, MOVE_SWORDS_DANCE);} + TURN { MOVE(player, MOVE_IRON_DEFENSE); MOVE(opponent, MOVE_SWORDS_DANCE);} + TURN { MOVE(player, MOVE_IRON_DEFENSE); MOVE(opponent, MOVE_SWORDS_DANCE);} + TURN { MOVE(player, MOVE_BATON_PASS); MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(player, 1); SEND_OUT(opponent, 1);} + } SCENE { + NONE_OF { + ABILITY_POPUP(player, ABILITY_DAUNTLESS_SHIELD); + ABILITY_POPUP(opponent, ABILITY_INTREPID_SWORD); + } + } +} +#endif diff --git a/test/battle/ability/iron_fist.c b/test/battle/ability/iron_fist.c index 360b3606be21..e66a0fbcf0e4 100644 --- a/test/battle/ability/iron_fist.c +++ b/test/battle/ability/iron_fist.c @@ -24,3 +24,29 @@ SINGLE_BATTLE_TEST("Iron Fist increases the power of punching moves by 20%", s16 EXPECT_EQ(results[2].damage, results[3].damage); // Iron Fist does not affect non-punching moves } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Iron Fist increases the power of punching moves by 20% (Traits)", s16 damage) +{ + u32 move, ability; + PARAMETRIZE { move = MOVE_BULLET_PUNCH; ability = ABILITY_IRON_FIST; } + PARAMETRIZE { move = MOVE_BULLET_PUNCH; ability = ABILITY_BLAZE; } + PARAMETRIZE { move = MOVE_SCRATCH; ability = ABILITY_IRON_FIST; } + PARAMETRIZE { move = MOVE_SCRATCH; ability = ABILITY_BLAZE; } + + GIVEN { + ASSUME(IsPunchingMove(MOVE_BULLET_PUNCH)); + ASSUME(!IsPunchingMove(MOVE_SCRATCH)); + ASSUME(GetMovePower(MOVE_BULLET_PUNCH) == GetMovePower(MOVE_SCRATCH)); + PLAYER(SPECIES_CHIMCHAR) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[1].damage, Q_4_12(1.2), results[0].damage); // Iron Fist affects punching moves + EXPECT_EQ(results[2].damage, results[3].damage); // Iron Fist does not affect non-punching moves + } +} +#endif diff --git a/test/battle/ability/keen_eye.c b/test/battle/ability/keen_eye.c index 7dbef04ee8ce..0f2e27ce7bdc 100644 --- a/test/battle/ability/keen_eye.c +++ b/test/battle/ability/keen_eye.c @@ -202,3 +202,201 @@ SINGLE_BATTLE_TEST("Keen Eye & Gen9+ Illuminate don't prevent Spectral Thief fro EXPECT_EQ(opponent->statStages[STAT_ACC], DEFAULT_STAT_STAGE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Keen Eye, Gen9+ Illuminate & Minds Eye prevent accuracy stage reduction from moves (Traits)") +{ + enum Ability ability, ability2; + u32 species; + + PARAMETRIZE { species = SPECIES_HITMONCHAN; ability = ABILITY_KEEN_EYE; ability2 = ABILITY_IRON_FIST; } + PARAMETRIZE { species = SPECIES_STARYU; ability = ABILITY_ILLUMINATE; ability2 = ABILITY_NATURAL_CURE; } + PARAMETRIZE { species = SPECIES_URSALUNA_BLOODMOON; ability = ABILITY_MINDS_EYE; ability2 = ABILITY_GUTS; } + + PASSES_RANDOMLY(100, 100, RNG_ACCURACY); + GIVEN { + WITH_CONFIG(CONFIG_ILLUMINATE_EFFECT, GEN_9); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ability2); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SAND_ATTACK); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(opponent, ability); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + if (species == SPECIES_HITMONCHAN) + MESSAGE("The opposing Hitmonchan's Keen Eye prevents accuracy loss!"); + else if (species == SPECIES_STARYU) + MESSAGE("The opposing Staryu's Illuminate prevents accuracy loss!"); + else + MESSAGE("The opposing Ursaluna's Mind's Eye prevents accuracy loss!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + } +} + +SINGLE_BATTLE_TEST("Keen Eye, Gen9+ Illuminate & Minds Eye ignore target's evasion stat (Traits)") +{ + enum Ability ability, ability2; + u32 species; + + PARAMETRIZE { species = SPECIES_HITMONCHAN; ability = ABILITY_KEEN_EYE; ability2 = ABILITY_IRON_FIST; } + PARAMETRIZE { species = SPECIES_STARYU; ability = ABILITY_ILLUMINATE; ability2 = ABILITY_NATURAL_CURE; } + PARAMETRIZE { species = SPECIES_URSALUNA_BLOODMOON; ability = ABILITY_MINDS_EYE; ability2 = ABILITY_GUTS; } + + PASSES_RANDOMLY(100, 100, RNG_ACCURACY); + GIVEN { + WITH_CONFIG(CONFIG_ILLUMINATE_EFFECT, GEN_9); + ASSUME(GetMoveEffect(MOVE_DOUBLE_TEAM) == EFFECT_EVASION_UP); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ability2); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_DOUBLE_TEAM); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_TEAM, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + } +} + +SINGLE_BATTLE_TEST("Keen Eye, Gen9+ Illuminate & Minds Eye are ignored by Mold Breaker abilities (Traits)") +{ + enum Ability abilityPlayer = ABILITY_NONE, abilityOpponent = ABILITY_NONE, ability2 = ABILITY_NONE; + u16 speciesPlayer = SPECIES_NONE, speciesOpponent = SPECIES_NONE; + + u32 j; + static const u16 moldBreakerAbilities[][2] = { + {SPECIES_PINSIR, ABILITY_MOLD_BREAKER}, + {SPECIES_RESHIRAM, ABILITY_TURBOBLAZE}, + {SPECIES_ZEKROM, ABILITY_TERAVOLT}, + }; + + for (j = 0; j < ARRAY_COUNT(moldBreakerAbilities); j++) { + speciesPlayer = moldBreakerAbilities[j][0]; abilityPlayer = moldBreakerAbilities[j][1]; + PARAMETRIZE { speciesOpponent = SPECIES_HITMONCHAN; abilityOpponent = ABILITY_KEEN_EYE; ability2 = ABILITY_IRON_FIST; } + PARAMETRIZE { speciesOpponent = SPECIES_STARYU; abilityOpponent = ABILITY_ILLUMINATE; ability2 = ABILITY_NATURAL_CURE; } + PARAMETRIZE { speciesOpponent = SPECIES_URSALUNA_BLOODMOON; abilityOpponent = ABILITY_MINDS_EYE; ability2 = ABILITY_GUTS; } + } + + PASSES_RANDOMLY(GetMoveAccuracy(MOVE_SCRATCH) * 3 / 4, 100, RNG_ACCURACY); + GIVEN { + WITH_CONFIG(CONFIG_ILLUMINATE_EFFECT, GEN_9); + PLAYER(speciesPlayer) { Ability(abilityPlayer); } + OPPONENT(speciesOpponent) { Ability(ability2); Innates(abilityOpponent); } + } WHEN { + TURN { MOVE(player, MOVE_SAND_ATTACK); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(player, abilityPlayer); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SAND_ATTACK, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + } +} + +SINGLE_BATTLE_TEST("Keen Eye, Gen9+ Illuminate & Minds Eye don't prevent Topsy-Turvy (Traits)") +{ + enum Ability ability, ability2; + u32 species; + + PARAMETRIZE { species = SPECIES_HITMONCHAN; ability = ABILITY_KEEN_EYE; ability2 = ABILITY_IRON_FIST; } + PARAMETRIZE { species = SPECIES_STARYU; ability = ABILITY_ILLUMINATE; ability2 = ABILITY_NATURAL_CURE; } + PARAMETRIZE { species = SPECIES_URSALUNA_BLOODMOON; ability = ABILITY_MINDS_EYE; ability2 = ABILITY_GUTS; } + + GIVEN { + WITH_CONFIG(CONFIG_ILLUMINATE_EFFECT, GEN_9); + ASSUME(GetMoveEffect(MOVE_HONE_CLAWS) == EFFECT_ATTACK_ACCURACY_UP); + ASSUME(GetMoveEffect(MOVE_TOPSY_TURVY) == EFFECT_TOPSY_TURVY); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ability2); Innates(ability); } + } WHEN { + TURN { MOVE(opponent, MOVE_HONE_CLAWS); MOVE(player, MOVE_TOPSY_TURVY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HONE_CLAWS, opponent); + if (species == SPECIES_HITMONCHAN) { + MESSAGE("The opposing Hitmonchan's Attack rose!"); + MESSAGE("The opposing Hitmonchan's accuracy rose!"); + } else if (species == SPECIES_STARYU) { + MESSAGE("The opposing Staryu's Attack rose!"); + MESSAGE("The opposing Staryu's accuracy rose!"); + } else { + MESSAGE("The opposing Ursaluna's Attack rose!"); + MESSAGE("The opposing Ursaluna's accuracy rose!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOPSY_TURVY, player); + if (species == SPECIES_HITMONCHAN) + MESSAGE("All stat changes on the opposing Hitmonchan were inverted!"); + else if (species == SPECIES_STARYU) + MESSAGE("All stat changes on the opposing Staryu were inverted!"); + else + MESSAGE("All stat changes on the opposing Ursaluna were inverted!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ACC], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Keen Eye, Gen9+ Illuminate & Minds Eye don't prevent receiving negative Attack stage changes from Baton Pass (Traits)") +{ + enum Ability ability, ability2; + u32 species; + PARAMETRIZE { species = SPECIES_HITMONCHAN; ability = ABILITY_KEEN_EYE; ability2 = ABILITY_IRON_FIST; } + PARAMETRIZE { species = SPECIES_STARYU; ability = ABILITY_ILLUMINATE; ability2 = ABILITY_NATURAL_CURE; } + PARAMETRIZE { species = SPECIES_URSALUNA_BLOODMOON; ability = ABILITY_MINDS_EYE; ability2 = ABILITY_GUTS; } + + GIVEN { + WITH_CONFIG(CONFIG_ILLUMINATE_EFFECT, GEN_9); + ASSUME(GetMoveEffect(MOVE_BATON_PASS) == EFFECT_BATON_PASS); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ability2); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SAND_ATTACK); + MOVE(opponent, MOVE_BATON_PASS); + SEND_OUT(opponent, 1); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SAND_ATTACK, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BATON_PASS, opponent); + if (species == SPECIES_HITMONCHAN) + MESSAGE("2 sent out Hitmonchan!"); + else if (species == SPECIES_STARYU) + MESSAGE("2 sent out Staryu!"); + else + MESSAGE("2 sent out Ursaluna!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ACC], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Keen Eye & Gen9+ Illuminate don't prevent Spectral Thief from resetting positive accuracy stage changes (Traits)") +{ + enum Ability ability, ability2; + u32 species; + + PARAMETRIZE { species = SPECIES_HITMONCHAN; ability = ABILITY_KEEN_EYE; ability2 = ABILITY_IRON_FIST; } + PARAMETRIZE { species = SPECIES_STARYU; ability = ABILITY_ILLUMINATE; ability2 = ABILITY_NATURAL_CURE; } + + GIVEN { + WITH_CONFIG(CONFIG_ILLUMINATE_EFFECT, GEN_9); + ASSUME(GetMoveEffect(MOVE_HONE_CLAWS) == EFFECT_ATTACK_ACCURACY_UP); + ASSUME(GetMoveEffect(MOVE_SPECTRAL_THIEF) == EFFECT_SPECTRAL_THIEF); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ability2); Innates(ability); } + } WHEN { + TURN { MOVE(opponent, MOVE_HONE_CLAWS); MOVE(player, MOVE_SPECTRAL_THIEF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HONE_CLAWS, opponent); + if (species == SPECIES_HITMONCHAN) + { + MESSAGE("The opposing Hitmonchan's Attack rose!"); + MESSAGE("The opposing Hitmonchan's accuracy rose!"); + } + else + { + MESSAGE("The opposing Staryu's Attack rose!"); + MESSAGE("The opposing Staryu's accuracy rose!"); + } + MESSAGE("Wobbuffet stole the target's boosted stats!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPECTRAL_THIEF, player); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ACC], DEFAULT_STAT_STAGE); + } +} +#endif diff --git a/test/battle/ability/leaf_guard.c b/test/battle/ability/leaf_guard.c index 8efa7abdf3ab..e92591a494a1 100644 --- a/test/battle/ability/leaf_guard.c +++ b/test/battle/ability/leaf_guard.c @@ -163,3 +163,218 @@ SINGLE_BATTLE_TEST("Leaf Guard doesn't prevent Rest if Cloud Nine/Air Lock is on HP_BAR(player); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Leaf Guard prevents non-volatile status conditions in sun (Traits)") +{ + u32 move; + u16 status; + PARAMETRIZE { move = MOVE_WILL_O_WISP; status = STATUS1_BURN; } + PARAMETRIZE { move = MOVE_HYPNOSIS; status = STATUS1_SLEEP; } + PARAMETRIZE { move = MOVE_THUNDER_WAVE; status = STATUS1_PARALYSIS; } + PARAMETRIZE { move = MOVE_TOXIC; status = STATUS1_TOXIC_POISON; } + // PARAMETRIZE { move = MOVE_POWDER_SNOW; status = STATUS1_FREEZE; } // Pointless since you can't freeze in sunlight anyway + GIVEN { + ASSUME(GetMoveEffect(MOVE_WILL_O_WISP) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_WILL_O_WISP) == MOVE_EFFECT_BURN); + ASSUME(GetMoveEffect(MOVE_HYPNOSIS) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_HYPNOSIS) == MOVE_EFFECT_SLEEP); + ASSUME(GetMoveEffect(MOVE_THUNDER_WAVE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_THUNDER_WAVE) == MOVE_EFFECT_PARALYSIS); + ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + PLAYER(SPECIES_LEAFEON) { Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_LEAF_GUARD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, move); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ABILITY_POPUP(player, ABILITY_LEAF_GUARD); + MESSAGE("It doesn't affect Leafeon…"); + NOT STATUS_ICON(player, status); + } +} + +SINGLE_BATTLE_TEST("Leaf Guard doesn't prevent non-volatile status conditions if Cloud Nine/Air Lock is on the field (Traits)") +{ + u32 move, species, ability; + u16 status; + PARAMETRIZE { move = MOVE_WILL_O_WISP; status = STATUS1_BURN; species = SPECIES_GOLDUCK; ability = ABILITY_CLOUD_NINE; } + PARAMETRIZE { move = MOVE_HYPNOSIS; status = STATUS1_SLEEP; species = SPECIES_GOLDUCK; ability = ABILITY_CLOUD_NINE; } + PARAMETRIZE { move = MOVE_THUNDER_WAVE; status = STATUS1_PARALYSIS; species = SPECIES_GOLDUCK; ability = ABILITY_CLOUD_NINE; } + PARAMETRIZE { move = MOVE_TOXIC; status = STATUS1_TOXIC_POISON; species = SPECIES_GOLDUCK; ability = ABILITY_CLOUD_NINE; } + PARAMETRIZE { move = MOVE_WILL_O_WISP; status = STATUS1_BURN; species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } + PARAMETRIZE { move = MOVE_HYPNOSIS; status = STATUS1_SLEEP; species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } + PARAMETRIZE { move = MOVE_THUNDER_WAVE; status = STATUS1_PARALYSIS; species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } + PARAMETRIZE { move = MOVE_TOXIC; status = STATUS1_TOXIC_POISON; species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } + // PARAMETRIZE { move = MOVE_POWDER_SNOW; status = STATUS1_FREEZE; } // Pointless since you can't freeze in sunlight anyway + GIVEN { + ASSUME(GetMoveEffect(MOVE_WILL_O_WISP) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_WILL_O_WISP) == MOVE_EFFECT_BURN); + ASSUME(GetMoveEffect(MOVE_HYPNOSIS) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_HYPNOSIS) == MOVE_EFFECT_SLEEP); + ASSUME(GetMoveEffect(MOVE_THUNDER_WAVE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_THUNDER_WAVE) == MOVE_EFFECT_PARALYSIS); + ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + PLAYER(SPECIES_LEAFEON) { Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_LEAF_GUARD); } + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + NONE_OF { + ABILITY_POPUP(player, ABILITY_LEAF_GUARD); + MESSAGE("It doesn't affect Leafeon…"); + } + STATUS_ICON(player, status); + } +} + +SINGLE_BATTLE_TEST("Leaf Guard prevents status conditions from Flame Orb and Toxic Orb (Traits)") +{ + u32 item; + PARAMETRIZE { item = ITEM_FLAME_ORB; } + PARAMETRIZE { item = ITEM_TOXIC_ORB; } + GIVEN { + ASSUME(gItemsInfo[ITEM_FLAME_ORB].holdEffect == HOLD_EFFECT_FLAME_ORB); + ASSUME(gItemsInfo[ITEM_TOXIC_ORB].holdEffect == HOLD_EFFECT_TOXIC_ORB); + PLAYER(SPECIES_LEAFEON) { Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_LEAF_GUARD); Item(item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); } + } SCENE { + if (item == ITEM_FLAME_ORB) { + NONE_OF { MESSAGE("Leafeon was burned!"); STATUS_ICON(player, burn: TRUE); } + } + else { + NONE_OF { MESSAGE("Leafeon was badly poisoned!"); STATUS_ICON(player, badPoison: TRUE); } + } + } +} + +SINGLE_BATTLE_TEST("Leaf Guard doesn't prevent status conditions from Flame Orb and Toxic Orb if Cloud Nine/Air Lock is on the field (Traits)") +{ + u32 item, species, ability; + PARAMETRIZE { item = ITEM_FLAME_ORB; species = SPECIES_GOLDUCK; ability = ABILITY_CLOUD_NINE; } + PARAMETRIZE { item = ITEM_TOXIC_ORB; species = SPECIES_GOLDUCK; ability = ABILITY_CLOUD_NINE; } + PARAMETRIZE { item = ITEM_FLAME_ORB; species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } + PARAMETRIZE { item = ITEM_TOXIC_ORB; species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } + GIVEN { + ASSUME(gItemsInfo[ITEM_FLAME_ORB].holdEffect == HOLD_EFFECT_FLAME_ORB); + ASSUME(gItemsInfo[ITEM_TOXIC_ORB].holdEffect == HOLD_EFFECT_TOXIC_ORB); + PLAYER(SPECIES_LEAFEON) { Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_LEAF_GUARD); Item(item); } + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); } + } SCENE { + if (item == ITEM_FLAME_ORB) { + MESSAGE("Leafeon was burned!"); + STATUS_ICON(player, burn: TRUE); + } + else { + MESSAGE("Leafeon was badly poisoned!"); + STATUS_ICON(player, badPoison: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Leaf Guard prevents Rest during sun (Gen 5+) (Traits)") +{ + u32 gen; + PARAMETRIZE { gen = GEN_4; } + PARAMETRIZE { gen = GEN_5; } + GIVEN { + WITH_CONFIG(CONFIG_LEAF_GUARD_PREVENTS_REST, gen); + ASSUME(GetMoveEffect(MOVE_REST) == EFFECT_REST); + PLAYER(SPECIES_LEAFEON) { Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_LEAF_GUARD); HP(100); MaxHP(200); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); MOVE(player, MOVE_REST); } + } SCENE { + if (gen >= GEN_5) { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, player); + STATUS_ICON(player, sleep: TRUE); + HP_BAR(player); + } + } + else { + STATUS_ICON(player, sleep: TRUE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, player); + HP_BAR(player); + } + } +} + +SINGLE_BATTLE_TEST("Leaf Guard doesn't prevent Rest if Cloud Nine/Air Lock is on the field (Traits)") +{ + u32 species, ability; + PARAMETRIZE { species = SPECIES_GOLDUCK; ability = ABILITY_CLOUD_NINE; } + PARAMETRIZE { species = SPECIES_GOLDUCK; ability = ABILITY_CLOUD_NINE; } + PARAMETRIZE { species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } + PARAMETRIZE { species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } + GIVEN { + WITH_CONFIG(CONFIG_LEAF_GUARD_PREVENTS_REST, GEN_5); + ASSUME(GetMoveEffect(MOVE_REST) == EFFECT_REST); + PLAYER(SPECIES_LEAFEON) { Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_LEAF_GUARD); HP(100); MaxHP(200); } + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); MOVE(player, MOVE_REST); } + } SCENE { + STATUS_ICON(player, sleep: TRUE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, player); + HP_BAR(player); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Leaf Guard prevents status conditions from Flame Orb and Toxic Orb (Multi)") +{ + u32 item; + PARAMETRIZE { item = ITEM_FLAME_ORB; } + PARAMETRIZE { item = ITEM_TOXIC_ORB; } + GIVEN { + ASSUME(gItemsInfo[ITEM_FLAME_ORB].holdEffect == HOLD_EFFECT_FLAME_ORB); + ASSUME(gItemsInfo[ITEM_TOXIC_ORB].holdEffect == HOLD_EFFECT_TOXIC_ORB); + PLAYER(SPECIES_LEAFEON) { Ability(ABILITY_LEAF_GUARD); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); } + } SCENE { + if (item == ITEM_FLAME_ORB) { + NONE_OF { MESSAGE("Leafeon was burned!"); STATUS_ICON(player, burn: TRUE); } + } + else { + NONE_OF { MESSAGE("Leafeon was badly poisoned!"); STATUS_ICON(player, badPoison: TRUE); } + } + } +} + +SINGLE_BATTLE_TEST("Leaf Guard doesn't prevent status conditions from Flame Orb and Toxic Orb if Cloud Nine/Air Lock is on the field (Multi)") +{ + u32 item, species, ability; + PARAMETRIZE { item = ITEM_FLAME_ORB; species = SPECIES_GOLDUCK; ability = ABILITY_CLOUD_NINE; } + PARAMETRIZE { item = ITEM_TOXIC_ORB; species = SPECIES_GOLDUCK; ability = ABILITY_CLOUD_NINE; } + PARAMETRIZE { item = ITEM_FLAME_ORB; species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } + PARAMETRIZE { item = ITEM_TOXIC_ORB; species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } + GIVEN { + ASSUME(gItemsInfo[ITEM_FLAME_ORB].holdEffect == HOLD_EFFECT_FLAME_ORB); + ASSUME(gItemsInfo[ITEM_TOXIC_ORB].holdEffect == HOLD_EFFECT_TOXIC_ORB); + PLAYER(SPECIES_LEAFEON) { Ability(ABILITY_LEAF_GUARD); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(species) { Ability(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); } + } SCENE { + if (item == ITEM_FLAME_ORB) { + MESSAGE("Leafeon was burned!"); + STATUS_ICON(player, burn: TRUE); + } + else { + MESSAGE("Leafeon was badly poisoned!"); + STATUS_ICON(player, badPoison: TRUE); + } + } +} +#endif diff --git a/test/battle/ability/levitate.c b/test/battle/ability/levitate.c index 51ac0dca2c1f..934bb34cdd5a 100644 --- a/test/battle/ability/levitate.c +++ b/test/battle/ability/levitate.c @@ -113,3 +113,141 @@ AI_SINGLE_BATTLE_TEST("Levitate is seen correctly by switch AI") } } +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Levitate activates when targeted by ground type moves (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_MUD_SLAP) == TYPE_GROUND); + PLAYER(SPECIES_LUNATONE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_LEVITATE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_MUD_SLAP); } + } SCENE { + ABILITY_POPUP(player, ABILITY_LEVITATE); + MESSAGE("Lunatone makes Ground-type moves miss with Levitate!"); + } +} + +SINGLE_BATTLE_TEST("Levitate does not activate if protected (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_MUD_SLAP) == TYPE_GROUND); + PLAYER(SPECIES_LUNATONE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_LEVITATE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PROTECT); MOVE(opponent, MOVE_MUD_SLAP); } + } SCENE { + NONE_OF { + ABILITY_POPUP(player, ABILITY_LEVITATE); + MESSAGE("Lunatone makes Ground-type moves miss with Levitate!"); + } + } +} + +SINGLE_BATTLE_TEST("Levitate does not activate on status moves (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SAND_ATTACK) == TYPE_GROUND); + ASSUME(GetMoveCategory(MOVE_SAND_ATTACK) == DAMAGE_CATEGORY_STATUS); + PLAYER(SPECIES_LUNATONE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_LEVITATE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SAND_ATTACK); } + } SCENE { + NONE_OF { + ABILITY_POPUP(player, ABILITY_LEVITATE); + MESSAGE("Lunatone makes Ground-type moves miss with Levitate!"); + } + } +} + +SINGLE_BATTLE_TEST("Levitate does not activate if attacked by an opponent with Mold Breaker (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_MUD_SLAP) == TYPE_GROUND); + PLAYER(SPECIES_LUNATONE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_LEVITATE); } + OPPONENT(SPECIES_TINKATON) { Ability(ABILITY_PICKPOCKET); Innates(ABILITY_MOLD_BREAKER); } + } WHEN { + TURN { MOVE(player, MOVE_PROTECT); MOVE(opponent, MOVE_MUD_SLAP); } + } SCENE { + NONE_OF { + ABILITY_POPUP(player, ABILITY_LEVITATE); + MESSAGE("Lunatone makes Ground-type moves miss with Levitate!"); + } + } +} + +DOUBLE_BATTLE_TEST("Levitate does not cause single remaining target to take higher damage (Traits)") +{ + s16 damage[3]; + GIVEN { + PLAYER(SPECIES_REGIROCK) { Speed(1); } + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + OPPONENT(SPECIES_LUNATONE) { Speed(3); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_LEVITATE); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(2); } + } WHEN { + TURN { MOVE(playerRight, MOVE_CELEBRATE); MOVE(playerLeft, MOVE_EARTHQUAKE); } + TURN { MOVE(playerRight, MOVE_MEMENTO, target:opponentLeft); MOVE(playerLeft, MOVE_EARTHQUAKE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, playerLeft); + HP_BAR(playerRight, captureDamage: &damage[0]); + HP_BAR(opponentRight, captureDamage: &damage[1]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_MEMENTO, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, playerLeft); + HP_BAR(opponentRight, captureDamage: &damage[2]); + + } THEN { + EXPECT_EQ(damage[0], damage[1]); + EXPECT_EQ(damage[1], damage[2]); + } +} + +AI_SINGLE_BATTLE_TEST("Levitate is seen correctly by switch AI (Traits)") +{ + enum Ability ability = ABILITY_NONE, item = ITEM_NONE; + + PARAMETRIZE { ability = ABILITY_OWN_TEMPO, item = ITEM_NONE ; } + PARAMETRIZE { ability = ABILITY_MOLD_BREAKER, item = ITEM_NONE ; } + PARAMETRIZE { ability = ABILITY_MOLD_BREAKER, item = ITEM_ABILITY_SHIELD ; } + + GIVEN { + ASSUME(GetMoveType(MOVE_MUD_SLAP) == TYPE_GROUND); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_TINKATON) { Ability(ability); Speed(3); } + OPPONENT(SPECIES_PONYTA) { Level(1); Item(ITEM_EJECT_PACK); Moves(MOVE_OVERHEAT); Speed(4); } // Forces switchout + OPPONENT(SPECIES_VIKAVOLT) { HP(1); Speed(1); Ability(ABILITY_LEVITATE); Moves(MOVE_FLAMETHROWER); Item(item); } + OPPONENT(SPECIES_HYPNO) { Speed(1); Moves(MOVE_IRON_HEAD); } + } WHEN { + if ((ability == ABILITY_MOLD_BREAKER) && (item != ITEM_ABILITY_SHIELD)) + TURN { MOVE(player, MOVE_MUD_SLAP); EXPECT_SEND_OUT(opponent, 2); } + else + TURN { MOVE(player, MOVE_MUD_SLAP); EXPECT_SEND_OUT(opponent, 1); } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +AI_SINGLE_BATTLE_TEST("Levitate is seen correctly by switch AI (Multi)") +{ + enum Ability ability = ABILITY_NONE, item = ITEM_NONE; + + PARAMETRIZE { ability = ABILITY_OWN_TEMPO, item = ITEM_NONE ; } + PARAMETRIZE { ability = ABILITY_MOLD_BREAKER, item = ITEM_NONE ; } + PARAMETRIZE { ability = ABILITY_MOLD_BREAKER, item = ITEM_ABILITY_SHIELD ; } + + GIVEN { + ASSUME(GetMoveType(MOVE_MUD_SLAP) == TYPE_GROUND); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_TINKATON) { Ability(ability); Speed(3); } + OPPONENT(SPECIES_PONYTA) { Level(1); Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); Moves(MOVE_OVERHEAT); Speed(4); } // Forces switchout + OPPONENT(SPECIES_VIKAVOLT) { HP(1); Speed(1); Ability(ABILITY_LEVITATE); Moves(MOVE_FLAMETHROWER); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_HYPNO) { Speed(1); Moves(MOVE_IRON_HEAD); } + } WHEN { + if ((ability == ABILITY_MOLD_BREAKER) && (item != ITEM_ABILITY_SHIELD)) + TURN { MOVE(player, MOVE_MUD_SLAP); EXPECT_SEND_OUT(opponent, 2); } + else + TURN { MOVE(player, MOVE_MUD_SLAP); EXPECT_SEND_OUT(opponent, 1); } + } +} +#endif diff --git a/test/battle/ability/lightning_rod.c b/test/battle/ability/lightning_rod.c index e1fedbe17d46..f2791550130a 100644 --- a/test/battle/ability/lightning_rod.c +++ b/test/battle/ability/lightning_rod.c @@ -130,3 +130,120 @@ DOUBLE_BATTLE_TEST("Lightning Rod absorbs moves that targets all battlers but do HP_BAR(playerRight); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Lightning Rod absorbs Electric-type moves and increases the Sp. Attack [Gen5+] (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_THUNDERBOLT) == TYPE_ELECTRIC); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_RAICHU) { Ability(ABILITY_STATIC); Innates(ABILITY_LIGHTNING_ROD); } + } WHEN { + TURN { MOVE(player, MOVE_THUNDERBOLT); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, player); + HP_BAR(opponent); + }; + ABILITY_POPUP(opponent, ABILITY_LIGHTNING_ROD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Raichu's Sp. Atk rose!"); + } else { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_LIGHTNING_ROD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Raichu's Sp. Atk rose!"); + }; + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, player); + HP_BAR(opponent); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +DOUBLE_BATTLE_TEST("Lightning Rod forces single-target Electric-type moves to target the Pokémon with this Ability. (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_THUNDERBOLT) == TYPE_ELECTRIC); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_RAICHU) { Ability(ABILITY_STATIC); Innates(ABILITY_LIGHTNING_ROD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(playerLeft, MOVE_THUNDERBOLT, target: opponentRight); + MOVE(playerRight, MOVE_THUNDERBOLT, target: opponentRight); + MOVE(opponentLeft, MOVE_CELEBRATE); + MOVE(opponentRight, MOVE_CELEBRATE); + } + } SCENE { + if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) { + NONE_OF { + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + }; + ABILITY_POPUP(opponentLeft, ABILITY_LIGHTNING_ROD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("The opposing Raichu's Sp. Atk rose!"); + ABILITY_POPUP(opponentLeft, ABILITY_LIGHTNING_ROD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("The opposing Raichu's Sp. Atk rose!"); + } else { + NONE_OF { + HP_BAR(opponentRight); + }; + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, playerLeft); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, playerRight); + HP_BAR(opponentLeft); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Lightning Rod redirects an ally's attack (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_THUNDERBOLT) == TYPE_ELECTRIC); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_RAICHU) { Ability(ABILITY_STATIC); Innates(ABILITY_LIGHTNING_ROD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentRight, MOVE_THUNDERBOLT, target: playerLeft); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Thunderbolt!"); + if (B_REDIRECT_ABILITY_ALLIES >= GEN_5) + { + NOT HP_BAR(playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_LIGHTNING_ROD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("The opposing Raichu's Sp. Atk rose!"); + } + else + { + HP_BAR(playerLeft); + } + } +} + +DOUBLE_BATTLE_TEST("Lightning Rod absorbs moves that targets all battlers but does not redirect (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_DISCHARGE) == TYPE_ELECTRIC); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_RAICHU) { Ability(ABILITY_STATIC); Innates(ABILITY_LIGHTNING_ROD); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_DISCHARGE); } + } SCENE { + NOT HP_BAR(opponentRight); + ABILITY_POPUP(opponentRight, ABILITY_LIGHTNING_ROD); + HP_BAR(opponentLeft); + HP_BAR(playerRight); + } +} +#endif diff --git a/test/battle/ability/limber.c b/test/battle/ability/limber.c index fc4e398c1fa5..1f8160d293b4 100644 --- a/test/battle/ability/limber.c +++ b/test/battle/ability/limber.c @@ -36,3 +36,40 @@ SINGLE_BATTLE_TEST("Limber prevents paralysis from Thunder Wave") } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Limber prevents paralysis (Traits)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_THUNDER_SHOCK, MOVE_EFFECT_PARALYSIS) == TRUE); + PLAYER(SPECIES_PERSIAN) { Ability(ABILITY_UNNERVE); Innates(ABILITY_LIMBER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_THUNDER_SHOCK); } + } SCENE { + HP_BAR(player); + NONE_OF { + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, player); + STATUS_ICON(player, paralysis: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Limber prevents paralysis from Thunder Wave (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_THUNDER_WAVE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_THUNDER_WAVE) == MOVE_EFFECT_PARALYSIS); + PLAYER(SPECIES_PERSIAN) { Ability(ABILITY_UNNERVE); Innates(ABILITY_LIMBER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_THUNDER_WAVE); } + } SCENE { + MESSAGE("It doesn't affect Persian…"); + NONE_OF { + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, player); + STATUS_ICON(player, paralysis: TRUE); + } + } +} +#endif diff --git a/test/battle/ability/liquid_ooze.c b/test/battle/ability/liquid_ooze.c index 437f8e4c7a39..dade0ee76cd1 100644 --- a/test/battle/ability/liquid_ooze.c +++ b/test/battle/ability/liquid_ooze.c @@ -218,3 +218,223 @@ SINGLE_BATTLE_TEST("Liquid Ooze HP loss from Leech Seed is blocked by Magic Guar NOT HP_BAR(player); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Liquid Ooze causes Absorb users to lose HP instead of heal (Traits)") +{ + s16 damage; + s16 healed; + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TENTACOOL) { Ability(ABILITY_CLEAR_BODY); Innates(ABILITY_LIQUID_OOZE); } + } WHEN { + TURN { MOVE(player, MOVE_ABSORB); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ABSORB, player); + HP_BAR(opponent, captureDamage: &damage); + HP_BAR(player, captureDamage: &healed); + MESSAGE("Wobbuffet sucked up the liquid ooze!"); + } THEN { + EXPECT_MUL_EQ(damage, Q_4_12(0.5), healed); + } +} + +SINGLE_BATTLE_TEST("Liquid Ooze causes Leech Seed users to lose HP instead of heal (Traits)") +{ + s16 damage; + s16 healed; + + GIVEN { + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_TENTACOOL) { Ability(ABILITY_CLEAR_BODY); Innates(ABILITY_LIQUID_OOZE); } + } WHEN { + TURN { MOVE(player, MOVE_LEECH_SEED); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LEECH_SEED, player); + HP_BAR(opponent, captureDamage: &damage); + HP_BAR(player, captureDamage: &healed); + } THEN { + EXPECT_EQ(damage, healed); + } +} + +DOUBLE_BATTLE_TEST("Liquid Ooze causes Matcha Gatcha users to lose HP instead of heal (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_MATCHA_GOTCHA) == EFFECT_ABSORB); + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TENTACOOL) { Ability(ABILITY_CLEAR_BODY); Innates(ABILITY_LIQUID_OOZE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_MATCHA_GOTCHA); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MATCHA_GOTCHA, playerLeft); + HP_BAR(opponentLeft); + HP_BAR(playerLeft); + MESSAGE("Wobbuffet sucked up the liquid ooze!"); + MESSAGE("Wobbuffet fainted!"); + } +} + +DOUBLE_BATTLE_TEST("Liquid Ooze will faint Matcha Gatcha users if it deals enough damage (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_MATCHA_GOTCHA) == EFFECT_ABSORB); + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TENTACOOL) { Ability(ABILITY_CLEAR_BODY); Innates(ABILITY_LIQUID_OOZE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_MATCHA_GOTCHA); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MATCHA_GOTCHA, playerLeft); + HP_BAR(opponentLeft); + HP_BAR(playerLeft); + MESSAGE("Wobbuffet sucked up the liquid ooze!"); + MESSAGE("Wobbuffet fainted!"); + } +} + +SINGLE_BATTLE_TEST("Liquid Ooze causes Strength Sap users to lose HP instead of heal (Traits)") +{ + s16 lostHp; + s32 atkStat; + + PARAMETRIZE { atkStat = 100; } + PARAMETRIZE { atkStat = 490; } // Checks that attacker can faint with no problems. + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Attack(atkStat); Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_LIQUID_OOZE); } + } WHEN { + TURN { MOVE(player, MOVE_STRENGTH_SAP); if (atkStat == 490) { SEND_OUT(player, 1); } } + } SCENE { + MESSAGE("Wobbuffet used Strength Sap!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRENGTH_SAP, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wobbuffet's Attack fell!"); + ABILITY_POPUP(opponent, ABILITY_LIQUID_OOZE); + HP_BAR(player, captureDamage: &lostHp); + MESSAGE("Wobbuffet sucked up the liquid ooze!"); + if (atkStat >= 490) { + MESSAGE("Wobbuffet fainted!"); + SEND_IN_MESSAGE("Wobbuffet"); + } + } THEN { + EXPECT_EQ(lostHp, atkStat); + } +} + +/* * https://bulbapedia.bulbagarden.net/wiki/Liquid_Ooze_(Ability)#In_battle: + * If the recipient of Leech Seed's effect were to faint due to Liquid Ooze on the same turn as the victim of Leech Seed, then the victim faints before the recipient. This means that the victim's team loses the battle if both teams had their final Pokémon sent out. + */ +SINGLE_BATTLE_TEST("Liquid Ooze causes leech seed victim to faint before seeder (Traits)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE { ability = ABILITY_LIQUID_OOZE; } + GIVEN { + PLAYER(SPECIES_BULBASAUR) { HP(1); } + OPPONENT(SPECIES_TENTACOOL) { HP(1); Ability(ABILITY_CLEAR_BODY); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_LEECH_SEED); } + } SCENE { + // Player seeds opponent + MESSAGE("Bulbasaur used Leech Seed!"); + // Drain at end of turn + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_LEECH_SEED_DRAIN, opponent); + if (ability != ABILITY_LIQUID_OOZE) { + MESSAGE("The opposing Tentacool fainted!"); + MESSAGE("The opposing Tentacool's health is sapped by Leech Seed!"); + } else { + MESSAGE("The opposing Tentacool fainted!"); + ABILITY_POPUP(opponent, ABILITY_LIQUID_OOZE); + MESSAGE("Bulbasaur sucked up the liquid ooze!"); + MESSAGE("Bulbasaur fainted!"); + } + } +} + +SINGLE_BATTLE_TEST("Liquid Ooze causes Dream Eater users to lose HP instead of heal (Gen 5+) (Traits)") +{ + s16 damage; + GIVEN { + WITH_CONFIG(CONFIG_DREAM_EATER_LIQUID_OOZE, GEN_5); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + ASSUME(GetMoveEffect(MOVE_DREAM_EATER) == EFFECT_DREAM_EATER); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TENTACRUEL) { Ability(ABILITY_CLEAR_BODY); Innates(ABILITY_LIQUID_OOZE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_SPORE); } + TURN { MOVE(player, MOVE_DREAM_EATER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DREAM_EATER, player); + HP_BAR(opponent); + HP_BAR(player, captureDamage: &damage); + } THEN { + EXPECT_GT(damage, 0); // Positive damage + } +} + +SINGLE_BATTLE_TEST("Liquid Ooze does not cause Dream Eater users to lose HP instead of heal (Gen 3-4) (Traits)") +{ + s16 damage; + GIVEN { + WITH_CONFIG(CONFIG_DREAM_EATER_LIQUID_OOZE, GEN_3); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + ASSUME(GetMoveEffect(MOVE_DREAM_EATER) == EFFECT_DREAM_EATER); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TENTACRUEL) { Ability(ABILITY_CLEAR_BODY); Innates(ABILITY_LIQUID_OOZE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_SPORE); } + TURN { MOVE(player, MOVE_DREAM_EATER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DREAM_EATER, player); + HP_BAR(opponent); + HP_BAR(player, captureDamage: &damage); + } THEN { + EXPECT_LT(damage, 0); // Negative damage = Heal + } +} + +SINGLE_BATTLE_TEST("Liquid Ooze HP loss from Absorb is blocked by Magic Guard (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CLEFFA) { Ability(ABILITY_CUTE_CHARM); Innates(ABILITY_MAGIC_GUARD); } + OPPONENT(SPECIES_TENTACOOL) { Ability(ABILITY_CLEAR_BODY); Innates(ABILITY_LIQUID_OOZE); } + } WHEN { + TURN { MOVE(player, MOVE_ABSORB); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ABSORB, player); + HP_BAR(opponent); + NONE_OF { + HP_BAR(player); + MESSAGE("Wobbuffet sucked up the liquid ooze!"); + } + } +} + +SINGLE_BATTLE_TEST("Liquid Ooze HP loss from Leech Seed is blocked by Magic Guard (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CLEFFA) { Ability(ABILITY_CUTE_CHARM); Innates(ABILITY_MAGIC_GUARD); } + OPPONENT(SPECIES_TENTACOOL) { Ability(ABILITY_CLEAR_BODY); Innates(ABILITY_LIQUID_OOZE); } + } WHEN { + TURN { MOVE(player, MOVE_LEECH_SEED); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LEECH_SEED, player); + HP_BAR(opponent); + NOT HP_BAR(player); + } +} +#endif diff --git a/test/battle/ability/liquid_voice.c b/test/battle/ability/liquid_voice.c index 6d3fa992f166..c9a271e32c19 100644 --- a/test/battle/ability/liquid_voice.c +++ b/test/battle/ability/liquid_voice.c @@ -19,3 +19,18 @@ SINGLE_BATTLE_TEST("Liquid voice turns a sound move into a Water-type move") MESSAGE("It's super effective!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Liquid voice turns a sound move into a Water-type move (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TYPHLOSION); + OPPONENT(SPECIES_PRIMARINA) { Ability(ABILITY_TORRENT); Innates(ABILITY_LIQUID_VOICE); } + } WHEN { + TURN { MOVE(opponent, MOVE_HYPER_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, opponent); + MESSAGE("It's super effective!"); + } +} +#endif diff --git a/test/battle/ability/magic_bounce.c b/test/battle/ability/magic_bounce.c index efc3173de66f..26a80c785ee7 100644 --- a/test/battle/ability/magic_bounce.c +++ b/test/battle/ability/magic_bounce.c @@ -136,3 +136,140 @@ SINGLE_BATTLE_TEST("Magic Bounce bounced back status moves can not be bounced ba STATUS_ICON(player, badPoison: TRUE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Magic Bounce bounces back status moves (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_ESPEON) { Ability(ABILITY_ADAPTABILITY); Innates(ABILITY_MAGIC_BOUNCE); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_MAGIC_BOUNCE); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + MESSAGE("Wynaut's Toxic was bounced back by the opposing Espeon's Magic Bounce!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, opponent); + STATUS_ICON(player, badPoison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Magic Bounce bounces back powder moves (Traits)") +{ + GIVEN { + ASSUME(IsPowderMove(MOVE_STUN_SPORE)); + ASSUME(GetMoveEffect(MOVE_STUN_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_STUN_SPORE) == MOVE_EFFECT_PARALYSIS); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_ESPEON) { Ability(ABILITY_ADAPTABILITY); Innates(ABILITY_MAGIC_BOUNCE); } + } WHEN { + TURN { MOVE(player, MOVE_STUN_SPORE); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_MAGIC_BOUNCE); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + MESSAGE("Wynaut's Stun Spore was bounced back by the opposing Espeon's Magic Bounce!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, opponent); + STATUS_ICON(player, paralysis: TRUE); + } +} + +SINGLE_BATTLE_TEST("Magic Bounce cannot bounce back powder moves against Grass Types (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_POWDER_GRASS, GEN_6); + ASSUME(IsPowderMove(MOVE_STUN_SPORE)); + ASSUME(GetSpeciesType(SPECIES_ODDISH, 0) == TYPE_GRASS); + PLAYER(SPECIES_ODDISH); + OPPONENT(SPECIES_ESPEON) { Ability(ABILITY_ADAPTABILITY); Innates(ABILITY_MAGIC_BOUNCE); } + } WHEN { + TURN { MOVE(player, MOVE_STUN_SPORE); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_MAGIC_BOUNCE); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, player); + MESSAGE("Oddish's Stun Spore was bounced back by the opposing Espeon's Magic Bounce!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, opponent); + MESSAGE("It doesn't affect Oddish…"); + NOT STATUS_ICON(player, paralysis: TRUE); + } +} + +DOUBLE_BATTLE_TEST("Magic Bounce bounces back moves hitting both foes at two foes (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_LEER) == EFFECT_DEFENSE_DOWN); + ASSUME(GetMoveTarget(MOVE_LEER) == MOVE_TARGET_BOTH); + PLAYER(SPECIES_ABRA); + PLAYER(SPECIES_KADABRA); + OPPONENT(SPECIES_ESPEON) { Ability(ABILITY_ADAPTABILITY); Innates(ABILITY_MAGIC_BOUNCE); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_LEER); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_MAGIC_BOUNCE); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_LEER, playerLeft); + MESSAGE("Abra's Leer was bounced back by the opposing Espeon's Magic Bounce!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_LEER, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Abra's Defense fell!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Kadabra's Defense fell!"); + // Also check if second original target gets hit by Leer as this was once bugged + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("The opposing Wynaut's Defense fell!"); + } +} + +DOUBLE_BATTLE_TEST("Magic Bounce bounces back moves hitting foes field (Traits)") +{ + u32 battlerOne, battlerTwo, abilityBattlerOne, abilityBattlerTwo; + + PARAMETRIZE { battlerOne = SPECIES_NATU; abilityBattlerOne = ABILITY_MAGIC_BOUNCE; + battlerTwo = SPECIES_ESPEON; abilityBattlerTwo = ABILITY_SYNCHRONIZE; } + PARAMETRIZE { battlerOne = SPECIES_NATU; abilityBattlerOne = ABILITY_KEEN_EYE; + battlerTwo = SPECIES_ESPEON; abilityBattlerTwo = ABILITY_MAGIC_BOUNCE; } + + GIVEN { + ASSUME(GetMoveTarget(MOVE_STEALTH_ROCK) == MOVE_TARGET_OPPONENTS_FIELD); + PLAYER(SPECIES_ABRA); + PLAYER(SPECIES_KADABRA); + OPPONENT(battlerOne) { Ability(ABILITY_EARLY_BIRD); Innates(abilityBattlerOne); } + OPPONENT(battlerTwo) { Ability(ABILITY_ADAPTABILITY); Innates(abilityBattlerTwo); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_STEALTH_ROCK); } + } SCENE { + if (abilityBattlerOne == ABILITY_MAGIC_BOUNCE) + ABILITY_POPUP(opponentLeft, ABILITY_MAGIC_BOUNCE); + else + ABILITY_POPUP(opponentRight, ABILITY_MAGIC_BOUNCE); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STEALTH_ROCK, playerLeft); + if (abilityBattlerOne == ABILITY_MAGIC_BOUNCE) { + MESSAGE("Abra's Stealth Rock was bounced back by the opposing Natu's Magic Bounce!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEALTH_ROCK, opponentLeft); + } else { + MESSAGE("Abra's Stealth Rock was bounced back by the opposing Espeon's Magic Bounce!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEALTH_ROCK, opponentRight); + } + } +} + +SINGLE_BATTLE_TEST("Magic Bounce bounced back status moves can not be bounced back by Magic Bounce (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + PLAYER(SPECIES_ESPEON) { Ability(ABILITY_ADAPTABILITY); Innates(ABILITY_MAGIC_BOUNCE); } + OPPONENT(SPECIES_ESPEON) { Ability(ABILITY_ADAPTABILITY); Innates(ABILITY_MAGIC_BOUNCE); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_MAGIC_BOUNCE); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + MESSAGE("Espeon's Toxic was bounced back by the opposing Espeon's Magic Bounce!"); + NOT ABILITY_POPUP(player, ABILITY_MAGIC_BOUNCE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, opponent); + STATUS_ICON(player, badPoison: TRUE); + } +} +#endif diff --git a/test/battle/ability/magic_guard.c b/test/battle/ability/magic_guard.c index 7c080c1fa56a..36dcd9fd01a5 100644 --- a/test/battle/ability/magic_guard.c +++ b/test/battle/ability/magic_guard.c @@ -44,3 +44,49 @@ SINGLE_BATTLE_TEST("Magic Guard does not ignore speed stat changes caused by par ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Magic Guard prevents recoil damage to the user (Traits)") +{ + GIVEN { + ASSUME(GetMoveRecoil(MOVE_DOUBLE_EDGE) == 33); + PLAYER(SPECIES_CLEFABLE) { Ability(ABILITY_CUTE_CHARM); Innates(ABILITY_MAGIC_GUARD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_DOUBLE_EDGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_EDGE, player); + HP_BAR(opponent); + NOT HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Magic Guard ignores immobilization that can be caused by paralysis (Traits)") +{ + if (B_MAGIC_GUARD == GEN_4) + PASSES_RANDOMLY(1, 1, RNG_PARALYSIS); + else + PASSES_RANDOMLY(75, 100, RNG_PARALYSIS); + GIVEN { + PLAYER(SPECIES_CLEFABLE) { Ability(ABILITY_CUTE_CHARM); Innates(ABILITY_MAGIC_GUARD); Status1(STATUS1_PARALYSIS);} + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } +} + +SINGLE_BATTLE_TEST("Magic Guard does not ignore speed stat changes caused by paralysis (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CLEFABLE) { Speed(100); Ability(ABILITY_CUTE_CHARM); Innates(ABILITY_MAGIC_GUARD); Status1(STATUS1_PARALYSIS);} + OPPONENT(SPECIES_WOBBUFFET) { Speed(99); } + } WHEN { + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } +} +#endif diff --git a/test/battle/ability/magician.c b/test/battle/ability/magician.c index a54ba2df7de8..dd3f2454a80e 100644 --- a/test/battle/ability/magician.c +++ b/test/battle/ability/magician.c @@ -53,3 +53,113 @@ DOUBLE_BATTLE_TEST("Magician steal the item from the fastest possible target") EXPECT(playerLeft->item == ITEM_ULTRA_BALL); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Magician gets self-damage recoil after stealing Life Orb (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LIFE_ORB].holdEffect == HOLD_EFFECT_LIFE_ORB); + ASSUME(!IsBattleMoveStatus(MOVE_SCRATCH)); + PLAYER(SPECIES_DELPHOX) { Ability(ABILITY_BLAZE); Innates(ABILITY_MAGICIAN); Item(ITEM_NONE); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LIFE_ORB); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + // 1st turn + MESSAGE("Delphox used Scratch!"); + ABILITY_POPUP(player, ABILITY_MAGICIAN); + MESSAGE("Delphox stole the opposing Wobbuffet's Life Orb!"); + HP_BAR(player); + MESSAGE("Delphox was hurt by the Life Orb!"); + // 2nd turn - Life Orb recoil happens now + MESSAGE("Delphox used Scratch!"); + HP_BAR(player); + MESSAGE("Delphox was hurt by the Life Orb!"); + } +} + +DOUBLE_BATTLE_TEST("Magician steal the item from the fastest possible target (Traits)") +{ + u32 playerRightSpeed = 0; + u32 opponentLeftSpeed = 0; + u32 opponentRightSpeed = 0; + + PARAMETRIZE { playerRightSpeed = 4; opponentLeftSpeed = 2; opponentRightSpeed = 3; } + PARAMETRIZE { playerRightSpeed = 3; opponentLeftSpeed = 4; opponentRightSpeed = 2; } + PARAMETRIZE { playerRightSpeed = 2; opponentLeftSpeed = 3; opponentRightSpeed = 4; } + + GIVEN { + PLAYER(SPECIES_DELPHOX) { Speed(1); Ability(ABILITY_BLAZE); Innates(ABILITY_MAGICIAN); Item(ITEM_NONE); } + PLAYER(SPECIES_WOBBUFFET) { Speed(playerRightSpeed); Item(ITEM_POKE_BALL); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(opponentLeftSpeed); Item(ITEM_GREAT_BALL); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(opponentRightSpeed); Item(ITEM_ULTRA_BALL); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SURF); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_MAGICIAN); + } THEN { + if (playerRightSpeed == 4) + EXPECT(playerLeft->item == ITEM_POKE_BALL); + else if (opponentLeftSpeed == 4) + EXPECT(playerLeft->item == ITEM_GREAT_BALL); + else if (playerRightSpeed == 4) + EXPECT(playerLeft->item == ITEM_ULTRA_BALL); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Magician gets self-damage recoil after stealing Life Orb (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LIFE_ORB].holdEffect == HOLD_EFFECT_LIFE_ORB); + ASSUME(!IsBattleMoveStatus(MOVE_SCRATCH)); + PLAYER(SPECIES_DELPHOX) { Ability(ABILITY_MAGICIAN); Items(ITEM_NONE, ITEM_NONE); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LIFE_ORB); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + // 1st turn + MESSAGE("Delphox used Scratch!"); + ABILITY_POPUP(player, ABILITY_MAGICIAN); + MESSAGE("Delphox stole the opposing Wobbuffet's Life Orb!"); + HP_BAR(player); + MESSAGE("Delphox was hurt by the Life Orb!"); + // 2nd turn - Life Orb recoil happens now + MESSAGE("Delphox used Scratch!"); + HP_BAR(player); + MESSAGE("Delphox was hurt by the Life Orb!"); + } +} + +DOUBLE_BATTLE_TEST("Magician steal the item from the fastest possible target (Multi)") +{ + u32 playerRightSpeed = 0; + u32 opponentLeftSpeed = 0; + u32 opponentRightSpeed = 0; + + PARAMETRIZE { playerRightSpeed = 4; opponentLeftSpeed = 2; opponentRightSpeed = 3; } + PARAMETRIZE { playerRightSpeed = 3; opponentLeftSpeed = 4; opponentRightSpeed = 2; } + PARAMETRIZE { playerRightSpeed = 2; opponentLeftSpeed = 3; opponentRightSpeed = 4; } + + GIVEN { + PLAYER(SPECIES_DELPHOX) { Speed(1); Ability(ABILITY_MAGICIAN); Items(ITEM_NONE, ITEM_NONE); } + PLAYER(SPECIES_WOBBUFFET) { Speed(playerRightSpeed); Items(ITEM_NONE, ITEM_POKE_BALL); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(opponentLeftSpeed); Items(ITEM_NONE, ITEM_GREAT_BALL); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(opponentRightSpeed); Items(ITEM_NONE, ITEM_ULTRA_BALL); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SURF); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_MAGICIAN); + } THEN { + if (playerRightSpeed == 4) + EXPECT(playerLeft->item == ITEM_POKE_BALL); + else if (opponentLeftSpeed == 4) + EXPECT(playerLeft->item == ITEM_GREAT_BALL); + else if (playerRightSpeed == 4) + EXPECT(playerLeft->item == ITEM_ULTRA_BALL); + } +} +#endif diff --git a/test/battle/ability/merciless.c b/test/battle/ability/merciless.c index 1502b709c590..8d48ad9666d0 100644 --- a/test/battle/ability/merciless.c +++ b/test/battle/ability/merciless.c @@ -13,3 +13,18 @@ SINGLE_BATTLE_TEST("Merciless causes a move to result in a critical hit if the t MESSAGE("A critical hit!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Merciless causes a move to result in a critical hit if the target is poisoned (Traits)") +{ + GIVEN { + PLAYER(SPECIES_MAREANIE) { Ability(ABILITY_LIMBER); Innates(ABILITY_MERCILESS); } + OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("A critical hit!"); + } +} +#endif diff --git a/test/battle/ability/mimicry.c b/test/battle/ability/mimicry.c index ff65ebaa12c1..7220108c6e15 100644 --- a/test/battle/ability/mimicry.c +++ b/test/battle/ability/mimicry.c @@ -124,3 +124,91 @@ DOUBLE_BATTLE_TEST("Mimicry triggers after Skill Swap") EXPECT_EQ(playerRight->types[1], TYPE_GRASS); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Mimicry changes the battler's type based on Terrain (Traits)") +{ + u32 j; + u32 terrainMove = MOVE_NONE; + enum Type terrainType = TYPE_NONE; + + for (j = 0; j < ARRAY_COUNT(terrainData); j++) + PARAMETRIZE { terrainMove = terrainData[j][0]; terrainType = terrainData[j][1]; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_STUNFISK_GALAR) { Ability(ABILITY_STATIC); Innates(ABILITY_MIMICRY); } + } WHEN { + TURN { MOVE(player, terrainMove); } + } SCENE { + ABILITY_POPUP(opponent); + switch (terrainMove) + { + case MOVE_ELECTRIC_TERRAIN: MESSAGE("The opposing Stunfisk's type changed to Electric!"); break; + case MOVE_PSYCHIC_TERRAIN: MESSAGE("The opposing Stunfisk's type changed to Psychic!"); break; + case MOVE_GRASSY_TERRAIN: MESSAGE("The opposing Stunfisk's type changed to Grass!"); break; + case MOVE_MISTY_TERRAIN: MESSAGE("The opposing Stunfisk's type changed to Fairy!"); break; + } + } THEN { + EXPECT_EQ(gBattleMons[B_POSITION_OPPONENT_LEFT].types[0], terrainType); + EXPECT_EQ(gBattleMons[B_POSITION_OPPONENT_LEFT].types[1], terrainType); + } +} + +SINGLE_BATTLE_TEST("Mimicry restores the battler's types when terrain is removed by Steel Roller and Ice Spinner (Traits)") +{ + u32 j; + u32 terrainMove = MOVE_NONE; + u32 removeTerrainMove = MOVE_NONE; + + for (j = 0; j < ARRAY_COUNT(terrainData); j++) + { + PARAMETRIZE { removeTerrainMove = MOVE_STEEL_ROLLER; terrainMove = terrainData[j][0]; } + PARAMETRIZE { removeTerrainMove = MOVE_ICE_SPINNER; terrainMove = terrainData[j][0]; } + } + + GIVEN { + ASSUME(GetSpeciesType(SPECIES_STUNFISK_GALAR, 0) == TYPE_GROUND); + ASSUME(GetSpeciesType(SPECIES_STUNFISK_GALAR, 1) == TYPE_STEEL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_STUNFISK_GALAR) { Ability(ABILITY_STATIC); Innates(ABILITY_MIMICRY); } + } WHEN { + TURN { MOVE(opponent, terrainMove); MOVE(player, removeTerrainMove); } + } SCENE { + switch (terrainMove) + { + case MOVE_ELECTRIC_TERRAIN: MESSAGE("The electricity disappeared from the battlefield."); break; + case MOVE_PSYCHIC_TERRAIN: MESSAGE("The weirdness disappeared from the battlefield!"); break; + case MOVE_GRASSY_TERRAIN: MESSAGE("The grass disappeared from the battlefield."); break; + case MOVE_MISTY_TERRAIN: MESSAGE("The mist disappeared from the battlefield."); break; + } + } THEN { + EXPECT_EQ(gBattleMons[B_POSITION_OPPONENT_LEFT].types[0], TYPE_GROUND); + EXPECT_EQ(gBattleMons[B_POSITION_OPPONENT_LEFT].types[1], TYPE_STEEL); + } +} + +DOUBLE_BATTLE_TEST("Mimicry can trigger multiple times in a turn (Traits)") +{ + GIVEN { + PLAYER(SPECIES_STUNFISK_GALAR) { Speed(50); Ability(ABILITY_STATIC); Innates(ABILITY_MIMICRY); } + PLAYER(SPECIES_MORELULL) { Speed(40); } + OPPONENT(SPECIES_IGGLYBUFF) { Speed(60); } + OPPONENT(SPECIES_BAGON) { Speed(70); } + } WHEN { + TURN { MOVE(opponentRight, MOVE_ELECTRIC_TERRAIN); MOVE(opponentLeft, MOVE_MISTY_TERRAIN); } + } SCENE { + MESSAGE("The opposing Bagon used Electric Terrain!"); + ABILITY_POPUP(playerLeft, ABILITY_MIMICRY); + MESSAGE("Stunfisk's type changed to Electric!"); + // igglybuff + MESSAGE("The opposing Igglybuff used Misty Terrain!"); + ABILITY_POPUP(playerLeft, ABILITY_MIMICRY); + MESSAGE("Stunfisk's type changed to Fairy!"); + } THEN { + EXPECT_EQ(gBattleMons[0].types[0], TYPE_FAIRY); + EXPECT_EQ(gBattleMons[0].types[1], TYPE_FAIRY); + EXPECT_EQ(gBattleMons[0].types[2], TYPE_MYSTERY); + } +} +#endif diff --git a/test/battle/ability/minds_eye.c b/test/battle/ability/minds_eye.c index 2bc17b93d1f3..62b93c8aa26e 100644 --- a/test/battle/ability/minds_eye.c +++ b/test/battle/ability/minds_eye.c @@ -70,3 +70,75 @@ AI_SINGLE_BATTLE_TEST("AI doesn't use accuracy-lowering moves if it knows that t } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Mind's Eye allows to hit Ghost-type Pokémon with Normal- and Fighting-type moves (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_KARATE_CHOP; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_MINDS_EYE); }; + OPPONENT(SPECIES_GASTLY); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + } +} + +// No current official way to test this, effect based on Smogon's NatDex format. +SINGLE_BATTLE_TEST("Mind's Eye doesn't bypass a Ghost-type's Wonder Guard (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_KARATE_CHOP; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_SCRAPPY); }; + OPPONENT(SPECIES_SHEDINJA) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_WONDER_GUARD); }; + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + } + ABILITY_POPUP(opponent, ABILITY_WONDER_GUARD); + MESSAGE("The opposing Shedinja avoided damage with Wonder Guard!"); + } +} + +//// AI TESTS //// + +AI_SINGLE_BATTLE_TEST("AI doesn't use accuracy-lowering moves if it knows that the foe has Mind's Eye (Traits)") +{ + enum Ability abilityAI = ABILITY_NONE; + + PARAMETRIZE { abilityAI = ABILITY_SWIFT_SWIM; } + PARAMETRIZE { abilityAI = ABILITY_MOLD_BREAKER; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SAND_ATTACK) == EFFECT_ACCURACY_DOWN); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_URSALUNA_BLOODMOON) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_MINDS_EYE); } + OPPONENT(SPECIES_BASCULEGION) { Moves(MOVE_CELEBRATE, MOVE_SAND_ATTACK); Ability(abilityAI); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); + if (abilityAI == ABILITY_MOLD_BREAKER) { + SCORE_GT(opponent, MOVE_SAND_ATTACK, MOVE_CELEBRATE); + } else { + SCORE_LT_VAL(opponent, MOVE_SAND_ATTACK, AI_SCORE_DEFAULT); + } + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + if (abilityAI == ABILITY_MOLD_BREAKER) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SAND_ATTACK, opponent); + } + } +} +#endif diff --git a/test/battle/ability/mirror_armor.c b/test/battle/ability/mirror_armor.c index 786ac6eaa829..8e3f5bba017f 100644 --- a/test/battle/ability/mirror_armor.c +++ b/test/battle/ability/mirror_armor.c @@ -228,3 +228,265 @@ SINGLE_BATTLE_TEST("Mirror Armor reflects Obstruct defense drop") ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Mirror Armor lowers a stat of the attacking Pokémon (Traits)") +{ + u16 move, statId; + + PARAMETRIZE { move = MOVE_LEER; statId = STAT_DEF; } + PARAMETRIZE { move = MOVE_GROWL; statId = STAT_ATK; } + PARAMETRIZE { move = MOVE_SWEET_SCENT; statId = STAT_EVASION; } + PARAMETRIZE { move = MOVE_SAND_ATTACK; statId = STAT_ACC; } + PARAMETRIZE { move = MOVE_CONFIDE; statId = STAT_SPATK; } + PARAMETRIZE { move = MOVE_FAKE_TEARS; statId = STAT_SPDEF; } + + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_PRESSURE); Innates(ABILITY_MIRROR_ARMOR);} + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + switch (statId) + { + case STAT_DEF: + MESSAGE("The opposing Wynaut's Defense fell!"); + break; + case STAT_ATK: + MESSAGE("The opposing Wynaut's Attack fell!"); + break; + case STAT_EVASION: + if (GetMoveEffect(move) == EFFECT_EVASION_DOWN_2) { + MESSAGE("The opposing Wynaut's evasiveness harshly fell!"); + } else { + MESSAGE("The opposing Wynaut's evasiveness fell!"); + } + break; + case STAT_ACC: + MESSAGE("The opposing Wynaut's accuracy fell!"); + break; + case STAT_SPATK: + MESSAGE("The opposing Wynaut's Sp. Atk fell!"); + break; + case STAT_SPDEF: + MESSAGE("The opposing Wynaut's Sp. Def harshly fell!"); + break; + } + } THEN { + EXPECT_EQ(player->statStages[statId], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[statId], (statId == STAT_SPDEF || (statId == STAT_EVASION && GetMoveEffect(move) == EFFECT_EVASION_DOWN_2)) ? DEFAULT_STAT_STAGE - 2 : DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Mirror Armor triggers even if the attacking Pokemon also has Mirror Armor ability (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_PRESSURE); Innates(ABILITY_MIRROR_ARMOR); } + OPPONENT(SPECIES_CORVIKNIGHT) { Ability(ABILITY_PRESSURE); Innates(ABILITY_MIRROR_ARMOR); } + } WHEN { + TURN { MOVE(opponent, MOVE_LEER); } + } SCENE { + MESSAGE("The opposing Corviknight used Leer!"); + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Corviknight's Defense fell!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Mirror Armor doesn't lower the stats of an attacking Pokemon with the Clear Body ability (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_PRESSURE); Innates(ABILITY_MIRROR_ARMOR); } + OPPONENT(SPECIES_WYNAUT) { Ability(ABILITY_PRESSURE); Innates(ABILITY_CLEAR_BODY); } + } WHEN { + TURN { MOVE(opponent, MOVE_LEER); } + } SCENE { + MESSAGE("The opposing Wynaut used Leer!"); + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + MESSAGE("The opposing Wynaut's Clear Body prevents stat loss!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Mirror Armor lowers the Attack of Pokemon with Intimidate (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); } + OPPONENT(SPECIES_GYARADOS) { Ability(ABILITY_INTIMIDATE); } + } WHEN { + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Gyarados's Attack fell!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Mirror Armor doesn't lower the stats of an attacking Pokemon behind Substitute (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_PRESSURE); Innates(ABILITY_MIRROR_ARMOR); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); } + TURN { MOVE(opponent, MOVE_LEER); } + } SCENE { + MESSAGE("The opposing Wynaut used Substitute!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, opponent); + MESSAGE("The opposing Wynaut used Leer!"); + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Mirror Armor raises the stat of an attacking Pokemon with Contrary (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_PRESSURE); Innates(ABILITY_MIRROR_ARMOR);} + OPPONENT(SPECIES_SHUCKLE) {Ability(ABILITY_STURDY); Innates(ABILITY_CONTRARY);} + } WHEN { + TURN { MOVE(opponent, MOVE_LEER); } + } SCENE { + MESSAGE("The opposing Shuckle used Leer!"); + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Shuckle's Defense rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Mirror Armor doesn't lower the stat of the attacking Pokemon if it is already at -6 (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_PRESSURE); Innates(ABILITY_MIRROR_ARMOR);} + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_SCREECH); } + TURN { MOVE(player, MOVE_SCREECH); } + TURN { MOVE(player, MOVE_SCREECH); } + TURN { MOVE(opponent, MOVE_LEER); } + } SCENE { + MESSAGE("Corviknight used Screech!"); + MESSAGE("Corviknight used Screech!"); + MESSAGE("Corviknight used Screech!"); + MESSAGE("The opposing Wynaut used Leer!"); + ABILITY_POPUP(player, ABILITY_MIRROR_ARMOR); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wynaut's Defense won't go any lower!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_DEF], MIN_STAT_STAGE); + } +} + +// This behaviour needs to be verified in the actual games. Currently it's written to follow Showdown's logic. +DOUBLE_BATTLE_TEST("Mirror Armor lowers Speed of the partner Pokemon after Court Change was used by the opponent after it set up Sticky Web (Traits)") +{ + KNOWN_FAILING; + GIVEN { + ASSUME(GetMoveEffect(MOVE_STICKY_WEB) == EFFECT_STICKY_WEB); + ASSUME(GetMoveEffect(MOVE_COURT_CHANGE) == EFFECT_COURT_CHANGE); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_PRESSURE); Innates(ABILITY_MIRROR_ARMOR); Item(ITEM_IRON_BALL); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_STICKY_WEB); } + TURN { MOVE(opponentLeft, MOVE_COURT_CHANGE); } + TURN { SWITCH(playerRight, 2);} + TURN { } + } SCENE { + MESSAGE("Wobbuffet used Sticky Web!"); + MESSAGE("The opposing Wynaut used Court Change!"); + MESSAGE("The opposing Wynaut swapped the battle effects affecting each side of the field!"); + SEND_IN_MESSAGE("Corviknight"); + MESSAGE("Corviknight was caught in a sticky web!"); + ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Wobbuffet's Speed fell!"); + } +} + +SINGLE_BATTLE_TEST("Mirror Armor reflects Tangling Hair speed drop (Traits)") +{ + GIVEN { + PLAYER(SPECIES_DUGTRIO) { Ability(ABILITY_SAND_RUSH); Innates(ABILITY_TANGLING_HAIR); } + OPPONENT(SPECIES_CORVIKNIGHT) { Ability(ABILITY_PRESSURE); Innates(ABILITY_MIRROR_ARMOR); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(player, ABILITY_TANGLING_HAIR); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + ABILITY_POPUP(opponent, ABILITY_MIRROR_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } +} + +SINGLE_BATTLE_TEST("Mirror Armor reflects Obstruct defense drop (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CORVIKNIGHT) { Ability(ABILITY_PRESSURE); Innates(ABILITY_MIRROR_ARMOR); } + } WHEN { + TURN { MOVE(player, MOVE_OBSTRUCT); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_OBSTRUCT, player); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + ABILITY_POPUP(opponent, ABILITY_MIRROR_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +// This behaviour needs to be verified in the actual games. Currently it's written to follow Showdown's logic. +DOUBLE_BATTLE_TEST("Mirror Armor lowers Speed of the partner Pokemon after Court Change was used by the opponent after it set up Sticky Web (Multi)") +{ + KNOWN_FAILING; + GIVEN { + ASSUME(GetMoveEffect(MOVE_STICKY_WEB) == EFFECT_STICKY_WEB); + ASSUME(GetMoveEffect(MOVE_COURT_CHANGE) == EFFECT_COURT_CHANGE); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_MIRROR_ARMOR); Items(ITEM_PECHA_BERRY, ITEM_IRON_BALL); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_STICKY_WEB); } + TURN { MOVE(opponentLeft, MOVE_COURT_CHANGE); } + TURN { SWITCH(playerRight, 2);} + TURN { } + } SCENE { + MESSAGE("Wobbuffet used Sticky Web!"); + MESSAGE("The opposing Wynaut used Court Change!"); + MESSAGE("The opposing Wynaut swapped the battle effects affecting each side of the field!"); + SEND_IN_MESSAGE("Corviknight"); + MESSAGE("Corviknight was caught in a sticky web!"); + ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Wobbuffet's Speed fell!"); + } +} +#endif diff --git a/test/battle/ability/misty_surge.c b/test/battle/ability/misty_surge.c index d09f3e8f653c..bc8ebf1c903b 100644 --- a/test/battle/ability/misty_surge.c +++ b/test/battle/ability/misty_surge.c @@ -13,3 +13,18 @@ SINGLE_BATTLE_TEST("Misty Surge creates Misty Terrain when entering the battle") MESSAGE("Mist swirled around the battlefield!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Misty Surge creates Misty Terrain when entering the battle (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TAPU_FINI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_MISTY_SURGE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN {} + } SCENE { + ABILITY_POPUP(player, ABILITY_MISTY_SURGE); + MESSAGE("Mist swirled around the battlefield!"); + } +} +#endif diff --git a/test/battle/ability/mold_breaker.c b/test/battle/ability/mold_breaker.c index fb6f9d11ab93..a30ec17aed40 100644 --- a/test/battle/ability/mold_breaker.c +++ b/test/battle/ability/mold_breaker.c @@ -20,3 +20,25 @@ SINGLE_BATTLE_TEST("Mold Breaker cancels damage reduction from Ice Scales", s16 } TO_DO_BATTLE_TEST("TODO: Write more Mold Breaker (Ability) test titles") + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Mold Breaker cancels damage reduction from Ice Scales (Traits)", s16 damage) +{ + u16 ability; + PARAMETRIZE { ability = ABILITY_SHADOW_TAG; } + PARAMETRIZE { ability = ABILITY_MOLD_BREAKER; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_PSYCHIC) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ability); } + OPPONENT(SPECIES_FROSMOTH) { Ability(ABILITY_SHIELD_DUST); Innates(ABILITY_ICE_SCALES); } + } WHEN { + TURN { MOVE(player, MOVE_PSYCHIC); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[1].damage, UQ_4_12(0.5), results[0].damage); + } +} + +TO_DO_BATTLE_TEST("TODO: Write more Mold Breaker (Ability) test titles (Traits)") +#endif diff --git a/test/battle/ability/moody.c b/test/battle/ability/moody.c index ca04ec1f704e..dc3f239bf67e 100644 --- a/test/battle/ability/moody.c +++ b/test/battle/ability/moody.c @@ -74,3 +74,79 @@ SINGLE_BATTLE_TEST("Moody randomly raises the holder's Attack, Defense, Sp. Atk, EXPECT_NE(player->statStages[stat], DEFAULT_STAT_STAGE + 1); // Both raised and lowered } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Moody randomly raises the user's Attack, Defense, Sp. Atk, Sp. Def, or Speed by two stages (Traits)") +{ + u32 config, statsNum; + + PARAMETRIZE { config = GEN_8; statsNum = NUM_STATS; } + PARAMETRIZE { config = GEN_7; statsNum = NUM_BATTLE_STATS; } + + // HP is not included + PASSES_RANDOMLY(1, statsNum - 1, RNG_MOODY_INCREASE); + GIVEN { + WITH_CONFIG(CONFIG_MOODY_ACC_EVASION, config); + PLAYER(SPECIES_OCTILLERY) { Ability(ABILITY_SUCTION_CUPS); Innates(ABILITY_MOODY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_MOODY); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Octillery's Attack sharply rose!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Moody randomly lowers the user's Attack, Defense, Sp. Atk, Sp. Def, or Speed by one stage (Traits)") +{ + u32 config, statsNum; + + PARAMETRIZE { config = GEN_8; statsNum = NUM_STATS; } + PARAMETRIZE { config = GEN_7; statsNum = NUM_BATTLE_STATS; } + + // One stat becomes unavailable due to it already increasing + PASSES_RANDOMLY(1, statsNum - 2, RNG_MOODY_DECREASE); + GIVEN { + WITH_CONFIG(CONFIG_MOODY_ACC_EVASION, config); + PLAYER(SPECIES_OCTILLERY) { Ability(ABILITY_SUCTION_CUPS); Innates(ABILITY_MOODY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_MOODY); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Octillery's Attack fell!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Moody randomly raises the holder's Attack, Defense, Sp. Atk, Sp. Def, or Speed by two stages and lowers a different stat by one stage (Traits)") +{ + u32 config, statsNum; + + PARAMETRIZE { config = GEN_8; statsNum = NUM_STATS; } + PARAMETRIZE { config = GEN_7; statsNum = NUM_BATTLE_STATS; } + + PASSES_RANDOMLY(statsNum - 1, statsNum - 1, RNG_MOODY_DECREASE); + GIVEN { + WITH_CONFIG(CONFIG_MOODY_ACC_EVASION, config); + PLAYER(SPECIES_OCTILLERY) { Ability(ABILITY_SUCTION_CUPS); Innates(ABILITY_MOODY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_MOODY); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + for (int stat = STAT_ATK; stat < statsNum; stat++) + EXPECT_NE(player->statStages[stat], DEFAULT_STAT_STAGE + 1); // Both raised and lowered + } +} +#endif diff --git a/test/battle/ability/motor_drive.c b/test/battle/ability/motor_drive.c index 7ee02328eee3..bff7cdf67c7f 100644 --- a/test/battle/ability/motor_drive.c +++ b/test/battle/ability/motor_drive.c @@ -30,3 +30,35 @@ DOUBLE_BATTLE_TEST("Motor Drive absorbs moves that target all battlers but does ABILITY_POPUP(opponentRight, ABILITY_MOTOR_DRIVE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Motor Drive absorbs status moves (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EMOLGA) { Ability(ABILITY_STATIC); Innates(ABILITY_MOTOR_DRIVE); } + } WHEN { + TURN { MOVE(player, MOVE_THUNDER_WAVE); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_MOTOR_DRIVE); + } +} + +DOUBLE_BATTLE_TEST("Motor Drive absorbs moves that target all battlers but does not redirect (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_DISCHARGE) == TYPE_ELECTRIC); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EMOLGA) { Ability(ABILITY_STATIC); Innates(ABILITY_MOTOR_DRIVE); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_DISCHARGE); } + } SCENE { + HP_BAR(opponentLeft); + HP_BAR(playerRight); + NOT HP_BAR(opponentRight); + ABILITY_POPUP(opponentRight, ABILITY_MOTOR_DRIVE); + } +} +#endif diff --git a/test/battle/ability/moxie.c b/test/battle/ability/moxie.c index 0903c24db0b0..1d48a210f7e2 100644 --- a/test/battle/ability/moxie.c +++ b/test/battle/ability/moxie.c @@ -148,3 +148,153 @@ DOUBLE_BATTLE_TEST("Moxie/Chilling Neigh does not increase damage done by the sa EXPECT_EQ(damage[0], damage[1]); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Moxie/Chilling Neigh raises Attack by one stage after directly causing a Pokemon to faint (Traits)") +{ + u32 species = 0, abilityPopUp = 0; + enum Ability ability = ABILITY_NONE; + PARAMETRIZE { species = SPECIES_SALAMENCE; ability = ABILITY_MOXIE; abilityPopUp = ABILITY_MOXIE; } + PARAMETRIZE { species = SPECIES_GLASTRIER; ability = ABILITY_CHILLING_NEIGH; abilityPopUp = ABILITY_CHILLING_NEIGH; } + PARAMETRIZE { species = SPECIES_CALYREX_ICE; ability = ABILITY_AS_ONE_ICE_RIDER; abilityPopUp = ABILITY_CHILLING_NEIGH; } + GIVEN { + ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + PLAYER(SPECIES_SNORUNT) { HP(1); } + OPPONENT(SPECIES_GLALIE) { HP(1); } + OPPONENT(SPECIES_ABRA) { HP(1); } + OPPONENT(SPECIES_ABRA); + } WHEN { + TURN { MOVE(playerLeft, MOVE_EARTHQUAKE); SEND_OUT(opponentLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, playerLeft); + MESSAGE("The opposing Glalie fainted!"); + MESSAGE("Snorunt fainted!"); + MESSAGE("The opposing Abra fainted!"); + ABILITY_POPUP(playerLeft, abilityPopUp); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + if (species == SPECIES_SALAMENCE) + MESSAGE("Salamence's Attack drastically rose!"); + else if (species == SPECIES_GLASTRIER) + MESSAGE("Glastrier's Attack drastically rose!"); + else + MESSAGE("Calyrex's Attack drastically rose!"); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 3); + } +} + +DOUBLE_BATTLE_TEST("Moxie/Chilling Neigh does not trigger if Pokemon faint to indirect damage or damage from other Pokemon (Traits)") +{ + u32 species = 0, abilityPopUp = 0; + enum Ability ability = ABILITY_NONE; + PARAMETRIZE { species = SPECIES_SALAMENCE; ability = ABILITY_MOXIE; abilityPopUp = ABILITY_MOXIE; } + PARAMETRIZE { species = SPECIES_GLASTRIER; ability = ABILITY_CHILLING_NEIGH; abilityPopUp = ABILITY_CHILLING_NEIGH; } + PARAMETRIZE { species = SPECIES_CALYREX_ICE; ability = ABILITY_AS_ONE_ICE_RIDER; abilityPopUp = ABILITY_CHILLING_NEIGH; } + GIVEN { + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + PLAYER(SPECIES_SNORUNT) { HP(1); Status1(STATUS1_POISON); } + OPPONENT(SPECIES_GLALIE) { HP(1); Status1(STATUS1_BURN); } + OPPONENT(SPECIES_ABRA) { HP(1); } + OPPONENT(SPECIES_ABRA); + } WHEN { + TURN { MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentRight); SEND_OUT(opponentLeft, 2); } + } SCENE { + int i; + + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, playerRight); + for (i = 0; i < 3; i++) { + ONE_OF { + MESSAGE("Snorunt fainted!"); + MESSAGE("The opposing Glalie fainted!"); + MESSAGE("The opposing Abra fainted!"); + } + NONE_OF { + ABILITY_POPUP(playerLeft, abilityPopUp); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Salamence's Moxie raised its Attack!"); + MESSAGE("Glastrier's Chilling Neigh raised its Attack!"); + MESSAGE("Calyrex's Chilling Neigh raised its Attack!"); + } + } + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Moxie/Chilling Neigh does not trigger when already at maximum Attack stage (Traits)") +{ + u32 species = 0, abilityPopUp = 0; + enum Ability ability = ABILITY_NONE; + PARAMETRIZE { species = SPECIES_SALAMENCE; ability = ABILITY_MOXIE; abilityPopUp = ABILITY_MOXIE; } + PARAMETRIZE { species = SPECIES_GLASTRIER; ability = ABILITY_CHILLING_NEIGH; abilityPopUp = ABILITY_CHILLING_NEIGH; } + PARAMETRIZE { species = SPECIES_CALYREX_ICE; ability = ABILITY_AS_ONE_ICE_RIDER; abilityPopUp = ABILITY_CHILLING_NEIGH; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_BELLY_DRUM) == EFFECT_BELLY_DRUM); + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_SNORUNT) { HP(1); } + OPPONENT(SPECIES_SNORUNT); + } WHEN { + TURN { MOVE(player, MOVE_BELLY_DRUM); } + TURN { MOVE(player, MOVE_QUICK_ATTACK); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BELLY_DRUM, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + if (species == SPECIES_SALAMENCE) + MESSAGE("Salamence cut its own HP and maximized its Attack!"); + else if (species == SPECIES_GLASTRIER) + MESSAGE("Glastrier cut its own HP and maximized its Attack!"); + else + MESSAGE("Calyrex cut its own HP and maximized its Attack!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player); + MESSAGE("The opposing Snorunt fainted!"); + NONE_OF { + ABILITY_POPUP(player, abilityPopUp); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Salamence's Moxie raised its Attack!"); + MESSAGE("Glastrier's Chilling Neigh raised its Attack!"); + MESSAGE("Calyrex's Chilling Neigh raised its Attack!"); + } + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], MAX_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Moxie/Chilling Neigh does not increase damage done by the same move that causes another Pokemon to faint (Traits)") +{ + s16 damage[2]; + u32 species = 0, abilityPopUp = 0; + enum Ability ability = ABILITY_NONE; + PARAMETRIZE { species = SPECIES_SALAMENCE; ability = ABILITY_MOXIE; abilityPopUp = ABILITY_MOXIE; } + PARAMETRIZE { species = SPECIES_GLASTRIER; ability = ABILITY_CHILLING_NEIGH; abilityPopUp = ABILITY_CHILLING_NEIGH; } + PARAMETRIZE { species = SPECIES_CALYREX_ICE; ability = ABILITY_AS_ONE_ICE_RIDER; abilityPopUp = ABILITY_CHILLING_NEIGH; } + + GIVEN { + ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + PLAYER(SPECIES_ABRA) { HP(1); } + OPPONENT(SPECIES_GLALIE); + OPPONENT(SPECIES_GLALIE); + OPPONENT(SPECIES_ABRA); + } WHEN { + TURN { MOVE(playerLeft, MOVE_EARTHQUAKE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damage[0]); + HP_BAR(playerRight); + HP_BAR(opponentRight, captureDamage: &damage[1]); + MESSAGE("Abra fainted!"); + ABILITY_POPUP(playerLeft, abilityPopUp); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + if (species == SPECIES_SALAMENCE) + MESSAGE("Salamence's Attack rose!"); + else if (species == SPECIES_GLASTRIER) + MESSAGE("Glastrier's Attack rose!"); + else + MESSAGE("Calyrex's Attack rose!"); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(damage[0], damage[1]); + } +} +#endif diff --git a/test/battle/ability/mummy.c b/test/battle/ability/mummy.c index ea1b0363da79..4873cc0ac18f 100644 --- a/test/battle/ability/mummy.c +++ b/test/battle/ability/mummy.c @@ -98,3 +98,122 @@ SINGLE_BATTLE_TEST("Mummy doesn't replace abilities that can't be suppressed") } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Mummy/Lingering Aroma replace the attacker's ability on contact (Traits)") +{ + u32 move, species; + enum Ability ability; + + PARAMETRIZE { move = MOVE_AQUA_JET; ability = ABILITY_MUMMY; species = SPECIES_YAMASK; } + PARAMETRIZE { move = MOVE_WATER_GUN; ability = ABILITY_MUMMY; species = SPECIES_YAMASK;} + PARAMETRIZE { move = MOVE_AQUA_JET; ability = ABILITY_LINGERING_AROMA; species = SPECIES_OINKOLOGNE; } + PARAMETRIZE { move = MOVE_WATER_GUN; ability = ABILITY_LINGERING_AROMA; species = SPECIES_OINKOLOGNE; } + GIVEN { + ASSUME(MoveMakesContact(MOVE_AQUA_JET)); + ASSUME(!MoveMakesContact(MOVE_WATER_GUN)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + if (MoveMakesContact(move)) { + ABILITY_POPUP(opponent, ability); + if (ability == ABILITY_MUMMY) + MESSAGE("Wobbuffet acquired Mummy!"); + else + MESSAGE("Wobbuffet acquired Lingering Aroma!"); + } else { + NONE_OF { + ABILITY_POPUP(opponent, ability); + if (ability == ABILITY_MUMMY) + MESSAGE("Wobbuffet acquired Mummy!"); + else + MESSAGE("Wobbuffet acquired Lingering Aroma!"); + } + } + } +} + +SINGLE_BATTLE_TEST("Mummy and Lingering Aroma don't replace each other (Traits)") +{ + enum Ability ability1, species1, ability2, species2, innate1, innate2; + + // Mummy only + PARAMETRIZE { ability1 = ABILITY_MUMMY; innate1 = ABILITY_LIGHT_METAL; ability2 = ABILITY_MUMMY; innate2 = ABILITY_LIGHT_METAL; species1 = species2 = SPECIES_YAMASK; } + PARAMETRIZE { ability1 = ABILITY_MUMMY; innate1 = ABILITY_LIGHT_METAL; ability2 = ABILITY_LIGHT_METAL; innate2 = ABILITY_MUMMY; species1 = species2 = SPECIES_YAMASK; } + PARAMETRIZE { ability1 = ABILITY_LIGHT_METAL; innate1 = ABILITY_MUMMY; ability2 = ABILITY_MUMMY; innate2 = ABILITY_LIGHT_METAL; species1 = species2 = SPECIES_YAMASK; } + PARAMETRIZE { ability1 = ABILITY_LIGHT_METAL; innate1 = ABILITY_MUMMY; ability2 = ABILITY_LIGHT_METAL; innate2 = ABILITY_MUMMY; species1 = species2 = SPECIES_YAMASK; } + // Lingering Aroma to Mummy + PARAMETRIZE { ability1 = ABILITY_MUMMY; innate1 = ABILITY_LIGHT_METAL; ability2 = ABILITY_LINGERING_AROMA; innate2 = ABILITY_LIGHT_METAL; species1 = SPECIES_YAMASK; species2 = SPECIES_OINKOLOGNE; } + PARAMETRIZE { ability1 = ABILITY_MUMMY; innate1 = ABILITY_LIGHT_METAL; ability2 = ABILITY_LIGHT_METAL; innate2 = ABILITY_LINGERING_AROMA; species1 = SPECIES_YAMASK; species2 = SPECIES_OINKOLOGNE; } + PARAMETRIZE { ability1 = ABILITY_LIGHT_METAL; innate1 = ABILITY_MUMMY; ability2 = ABILITY_LINGERING_AROMA; innate2 = ABILITY_LIGHT_METAL; species1 = SPECIES_YAMASK; species2 = SPECIES_OINKOLOGNE; } + PARAMETRIZE { ability1 = ABILITY_LIGHT_METAL; innate1 = ABILITY_MUMMY; ability2 = ABILITY_LIGHT_METAL; innate2 = ABILITY_LINGERING_AROMA; species1 = SPECIES_YAMASK; species2 = SPECIES_OINKOLOGNE; } + // Lingering Aroma only + PARAMETRIZE { ability1 = ABILITY_LINGERING_AROMA; innate1 = ABILITY_LIGHT_METAL; ability2 = ABILITY_LINGERING_AROMA; innate2 = ABILITY_LIGHT_METAL; species1 = species2 = SPECIES_OINKOLOGNE; } + PARAMETRIZE { ability1 = ABILITY_LINGERING_AROMA; innate1 = ABILITY_LIGHT_METAL; ability2 = ABILITY_LIGHT_METAL; innate2 = ABILITY_LINGERING_AROMA; species1 = species2 = SPECIES_OINKOLOGNE; } + PARAMETRIZE { ability1 = ABILITY_LIGHT_METAL; innate1 = ABILITY_LINGERING_AROMA; ability2 = ABILITY_LINGERING_AROMA; innate2 = ABILITY_LIGHT_METAL; species1 = species2 = SPECIES_OINKOLOGNE; } + PARAMETRIZE { ability1 = ABILITY_LIGHT_METAL; innate1 = ABILITY_LINGERING_AROMA; ability2 = ABILITY_LIGHT_METAL; innate2 = ABILITY_LINGERING_AROMA; species1 = species2 = SPECIES_OINKOLOGNE; } + // Mummy to Lingering Aroma + PARAMETRIZE { ability1 = ABILITY_MUMMY; innate1 = ABILITY_LINGERING_AROMA; ability2 = ABILITY_MUMMY; innate2 = ABILITY_LIGHT_METAL; species1 = SPECIES_OINKOLOGNE; species2 = SPECIES_YAMASK; } + PARAMETRIZE { ability1 = ABILITY_MUMMY; innate1 = ABILITY_LINGERING_AROMA; ability2 = ABILITY_LIGHT_METAL; innate2 = ABILITY_MUMMY; species1 = SPECIES_OINKOLOGNE; species2 = SPECIES_YAMASK; } + PARAMETRIZE { ability1 = ABILITY_LINGERING_AROMA; innate1 = ABILITY_MUMMY; ability2 = ABILITY_MUMMY; innate2 = ABILITY_LIGHT_METAL; species1 = SPECIES_OINKOLOGNE; species2 = SPECIES_YAMASK; } + PARAMETRIZE { ability1 = ABILITY_LINGERING_AROMA; innate1 = ABILITY_MUMMY; ability2 = ABILITY_LIGHT_METAL; innate2 = ABILITY_MUMMY; species1 = SPECIES_OINKOLOGNE; species2 = SPECIES_YAMASK; } + + GIVEN { + ASSUME(MoveMakesContact(MOVE_AQUA_JET)); + PLAYER(species1) { Ability(ability1); Innates(innate1); Speed(2); } + OPPONENT(species2) { Ability(ability2); Innates(innate2); Speed(1); } + } WHEN { + TURN { MOVE(player, MOVE_AQUA_JET); MOVE(opponent, MOVE_AQUA_JET); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_JET, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_JET, opponent); + NONE_OF { + ABILITY_POPUP(player, ability1); + ABILITY_POPUP(player, ability2); + ABILITY_POPUP(opponent, ability1); + ABILITY_POPUP(opponent, ability2); + MESSAGE("Yamask acquired Mummy!"); + MESSAGE("Yamask acquired Lingering Aroma!"); + MESSAGE("Oinkologne acquired Mummy!"); + MESSAGE("Oinkologne acquired Lingering Aroma!"); + } + } +} + +SINGLE_BATTLE_TEST("Mummy doesn't replace abilities that can't be suppressed (Traits)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_ARCEUS; ability = ABILITY_MULTITYPE; } + PARAMETRIZE { species = SPECIES_AEGISLASH; ability = ABILITY_STANCE_CHANGE; } + PARAMETRIZE { species = SPECIES_MINIOR; ability = ABILITY_SHIELDS_DOWN; } + PARAMETRIZE { species = SPECIES_WISHIWASHI; ability = ABILITY_SCHOOLING; } + PARAMETRIZE { species = SPECIES_MIMIKYU; ability = ABILITY_DISGUISE; } + PARAMETRIZE { species = SPECIES_GRENINJA_BATTLE_BOND; ability = ABILITY_BATTLE_BOND; } + PARAMETRIZE { species = SPECIES_ZYGARDE; ability = ABILITY_POWER_CONSTRUCT; } + PARAMETRIZE { species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } + PARAMETRIZE { species = SPECIES_SILVALLY; ability = ABILITY_RKS_SYSTEM; } + PARAMETRIZE { species = SPECIES_CRAMORANT; ability = ABILITY_GULP_MISSILE; } + PARAMETRIZE { species = SPECIES_EISCUE; ability = ABILITY_ICE_FACE; } + PARAMETRIZE { species = SPECIES_CALYREX_ICE; ability = ABILITY_AS_ONE_ICE_RIDER; } + PARAMETRIZE { species = SPECIES_CALYREX_SHADOW; ability = ABILITY_AS_ONE_SHADOW_RIDER; } + PARAMETRIZE { species = SPECIES_PALAFIN_ZERO; ability = ABILITY_ZERO_TO_HERO; } + PARAMETRIZE { species = SPECIES_TATSUGIRI; ability = ABILITY_COMMANDER; } + + GIVEN { + PLAYER(SPECIES_YAMASK) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_MUMMY); } + OPPONENT(species) { Ability(ability); } + } WHEN { + TURN { MOVE(opponent, MOVE_AQUA_JET); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_JET, opponent); + NONE_OF { + ABILITY_POPUP(player, ABILITY_MUMMY); + ABILITY_POPUP(opponent, ABILITY_MUMMY); + } + } +} +#endif diff --git a/test/battle/ability/mycelium_might.c b/test/battle/ability/mycelium_might.c index e5a8b569e1e6..902135de9004 100644 --- a/test/battle/ability/mycelium_might.c +++ b/test/battle/ability/mycelium_might.c @@ -94,3 +94,72 @@ DOUBLE_BATTLE_TEST("Mycelium Might priority bracket will not change if the abili ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Mycelium Might causes the user to move last in the priority bracket if it uses a status move (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TOEDSCOOL) { Speed(100); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_MYCELIUM_MIGHT); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(1); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SPORE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + STATUS_ICON(opponent, sleep: TRUE); + } +} + +SINGLE_BATTLE_TEST("Mycelium Might will respect the speed if both battlers have the ability (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TOEDSCOOL) { Speed(100); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_MYCELIUM_MIGHT); } + OPPONENT(SPECIES_TOEDSCOOL) { Speed(1); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_MYCELIUM_MIGHT); } + } WHEN { + TURN { MOVE(opponent, MOVE_WILL_O_WISP); MOVE(player, MOVE_WILL_O_WISP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WILL_O_WISP, player); + STATUS_ICON(opponent, burn: TRUE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WILL_O_WISP, opponent); + STATUS_ICON(player, burn: TRUE); + } +} + +SINGLE_BATTLE_TEST("Mycelium Might ignores opposing abilities (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TOEDSCOOL) { Speed(100); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_MYCELIUM_MIGHT); } + OPPONENT(SPECIES_BELDUM) { Speed(1); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_CLEAR_BODY);} + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SCREECH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCREECH, player); + NOT ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + } +} + +SINGLE_BATTLE_TEST("Mycelium Might vs Stall action order depends on speed (Traits)") +{ + u32 speed; + PARAMETRIZE { speed = 99; } + PARAMETRIZE { speed = 101; } + GIVEN { + PLAYER(SPECIES_TOEDSCOOL) { Speed(100); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_MYCELIUM_MIGHT); } + OPPONENT(SPECIES_SABLEYE) { Speed(speed); Ability(ABILITY_KEEN_EYE); Innates(ABILITY_STALL);} + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + if (speed < 100) + { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } + else + { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } + } +} +#endif diff --git a/test/battle/ability/neuroforce.c b/test/battle/ability/neuroforce.c index 32f3e91e569e..0bba0c315c91 100644 --- a/test/battle/ability/neuroforce.c +++ b/test/battle/ability/neuroforce.c @@ -23,3 +23,28 @@ SINGLE_BATTLE_TEST("Neuroforce increases the strength of super-effective moves b EXPECT_EQ(results[2].damage, results[3].damage); // Neuroforce doesn't boost the power of other moves } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Neuroforce increases the strength of super-effective moves by 25% (Traits)", s16 damage) +{ + u32 move; + enum Ability ability; + PARAMETRIZE { ability = ABILITY_NEUROFORCE; move = MOVE_SHADOW_BALL; } + PARAMETRIZE { ability = ABILITY_KLUTZ; move = MOVE_SHADOW_BALL; } + PARAMETRIZE { ability = ABILITY_NEUROFORCE; move = MOVE_SCRATCH; } + PARAMETRIZE { ability = ABILITY_KLUTZ; move = MOVE_SCRATCH; } + GIVEN { + ASSUME(GetMoveType(MOVE_SHADOW_BALL) == TYPE_GHOST); + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + PLAYER(SPECIES_NECROZMA_ULTRA) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[1].damage, UQ_4_12(1.25), results[0].damage); // Neuroforce boosts the power of super-effective moves + EXPECT_EQ(results[2].damage, results[3].damage); // Neuroforce doesn't boost the power of other moves + } +} +#endif diff --git a/test/battle/ability/normalize.c b/test/battle/ability/normalize.c index 09acb4b59fe9..5397383146c6 100644 --- a/test/battle/ability/normalize.c +++ b/test/battle/ability/normalize.c @@ -342,3 +342,414 @@ SINGLE_BATTLE_TEST("Normalize doesn't affect damaging Z-Move types") MESSAGE("It's super effective!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Normalize turns a move into a Normal-type move (Traits)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_CUTE_CHARM; } + PARAMETRIZE { ability = ABILITY_NORMALIZE; } + GIVEN { + ASSUME(GetSpeciesType(SPECIES_GASTLY, 0) == TYPE_GHOST); + PLAYER(SPECIES_GASTLY); + OPPONENT(SPECIES_SKITTY) { Ability(ABILITY_WONDER_SKIN); Innates(ability); Moves(MOVE_WATER_GUN);} + } WHEN { + TURN { MOVE(opponent, MOVE_WATER_GUN); } + } SCENE { + if (ability == ABILITY_CUTE_CHARM) + { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + NOT { MESSAGE("It doesn't affect Gastly…"); } + } + else + { + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); } + MESSAGE("It doesn't affect Gastly…"); + } + } +} + +SINGLE_BATTLE_TEST("Normalize affects status moves (Traits)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_CUTE_CHARM; } + PARAMETRIZE { ability = ABILITY_NORMALIZE; } + GIVEN { + ASSUME(GetMoveType(MOVE_THUNDER_WAVE) == TYPE_ELECTRIC); + ASSUME(GetSpeciesType(SPECIES_DRILBUR, 0) == TYPE_GROUND); + PLAYER(SPECIES_DRILBUR); + OPPONENT(SPECIES_SKITTY) { Ability(ABILITY_WONDER_SKIN); Innates(ability); Moves(MOVE_THUNDER_WAVE);} + } WHEN { + TURN { MOVE(opponent, MOVE_THUNDER_WAVE); } + } SCENE { + if (ability == ABILITY_CUTE_CHARM) + { + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_WAVE, opponent); } + MESSAGE("But it failed!"); + } + else + { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_WAVE, opponent); + NOT { MESSAGE("But it failed!"); } + } + } +} + +SINGLE_BATTLE_TEST("Normalize still makes Freeze-Dry do super effective damage to Water-type Pokémon (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_CUTE_CHARM; } + PARAMETRIZE { ability = ABILITY_NORMALIZE; } + GIVEN { + ASSUME(GetMoveType(MOVE_FREEZE_DRY) == TYPE_ICE); + ASSUME(GetMoveEffect(MOVE_FREEZE_DRY) == EFFECT_SUPER_EFFECTIVE_ON_ARG); + ASSUME(GetSpeciesType(SPECIES_MUDKIP, 0) == TYPE_WATER); + PLAYER(SPECIES_MUDKIP); + OPPONENT(SPECIES_SKITTY) { Ability(ABILITY_WONDER_SKIN); Innates(ability); Moves(MOVE_FREEZE_DRY);} + } WHEN { + TURN { MOVE(opponent, MOVE_FREEZE_DRY); } + } SCENE { + MESSAGE("It's super effective!"); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.8), results[1].damage); // STAB + Ate + } +} + +SINGLE_BATTLE_TEST("Normalize doesn't boost power of unaffected moves by 20% (< Gen7) (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_CUTE_CHARM; } + PARAMETRIZE { ability = ABILITY_NORMALIZE; } + + GIVEN { + WITH_CONFIG(CONFIG_ATE_MULTIPLIER, GEN_6); + PLAYER(SPECIES_DELCATTY) { Ability(ABILITY_WONDER_SKIN); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_POUND); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); // No boost + } +} + +SINGLE_BATTLE_TEST("Normalize boosts power of unaffected moves by 20% (Gen7+) (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_CUTE_CHARM; } + PARAMETRIZE { ability = ABILITY_NORMALIZE; } + + GIVEN { + WITH_CONFIG(CONFIG_ATE_MULTIPLIER, GEN_7); + PLAYER(SPECIES_DELCATTY) { Ability(ABILITY_WONDER_SKIN); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_POUND); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(1.2), results[1].damage); // Ate + } +} + +SINGLE_BATTLE_TEST("Normalize doesn't boost power of affected moves by 20% (< Gen7) (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_CUTE_CHARM; } + PARAMETRIZE { ability = ABILITY_NORMALIZE; } + + GIVEN { + WITH_CONFIG(CONFIG_ATE_MULTIPLIER, GEN_6); + PLAYER(SPECIES_SKITTY) { Ability(ABILITY_WONDER_SKIN); Innates(ability); Moves(MOVE_WATER_GUN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_WATER_GUN); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); // STAB + no ate + } +} + +SINGLE_BATTLE_TEST("Normalize boosts power of affected moves by 20% (Gen7+) (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_CUTE_CHARM; } + PARAMETRIZE { ability = ABILITY_NORMALIZE; } + + GIVEN { + WITH_CONFIG(CONFIG_ATE_MULTIPLIER, GEN_7); + PLAYER(SPECIES_SKITTY) { Ability(ABILITY_WONDER_SKIN); Innates(ability); Moves(MOVE_WATER_GUN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_WATER_GUN); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.8), results[1].damage); // STAB + ate + } +} + +SINGLE_BATTLE_TEST("Normalize-affected moves become Electric-type under Electrify's effect (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ELECTRIFY) == EFFECT_ELECTRIFY); + PLAYER(SPECIES_SKITTY) { Ability(ABILITY_WONDER_SKIN); Innates(ABILITY_NORMALIZE); } + OPPONENT(SPECIES_ROOKIDEE) { Item(ITEM_WACAN_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_ELECTRIFY); MOVE(player, MOVE_WATER_GUN); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} + +SINGLE_BATTLE_TEST("Normalize-affected moves become Electric-type under Ion Deluge's effect (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ION_DELUGE) == EFFECT_ION_DELUGE); + PLAYER(SPECIES_SKITTY) { Ability(ABILITY_WONDER_SKIN); Innates(ABILITY_NORMALIZE); Moves(MOVE_WATER_GUN); } + OPPONENT(SPECIES_ROOKIDEE) { Item(ITEM_WACAN_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_ION_DELUGE); MOVE(player, MOVE_WATER_GUN); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} + +SINGLE_BATTLE_TEST("Normalize doesn't affect Weather Ball's type (Traits)", s16 damage) +{ + u16 move; + enum Ability ability; + PARAMETRIZE { move = MOVE_CELEBRATE; ability = ABILITY_CUTE_CHARM; } + PARAMETRIZE { move = MOVE_SUNNY_DAY; ability = ABILITY_CUTE_CHARM; } + PARAMETRIZE { move = MOVE_CELEBRATE; ability = ABILITY_NORMALIZE; } + PARAMETRIZE { move = MOVE_SUNNY_DAY; ability = ABILITY_NORMALIZE; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_WEATHER_BALL) == EFFECT_WEATHER_BALL); + ASSUME(GetMoveType(MOVE_WEATHER_BALL) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_MEGANIUM, 0) == TYPE_GRASS); + PLAYER(SPECIES_SKITTY) { Ability(ABILITY_WONDER_SKIN); Innates(ability); } + OPPONENT(SPECIES_MEGANIUM); + } WHEN { + TURN { MOVE(player, move); } + TURN { MOVE(player, MOVE_WEATHER_BALL); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + if (move == MOVE_SUNNY_DAY) + MESSAGE("It's super effective!"); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(4.0), results[1].damage); // double base power + type effectiveness + sun 50% boost - STAB + EXPECT_MUL_EQ(results[2].damage, Q_4_12(4.0), results[3].damage); + EXPECT_EQ(results[0].damage, results[2].damage); + EXPECT_EQ(results[1].damage, results[3].damage); + } +} + +SINGLE_BATTLE_TEST("Normalize doesn't affect Natural Gift's type (Traits)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_CUTE_CHARM; } + PARAMETRIZE { ability = ABILITY_NORMALIZE; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + ASSUME(gNaturalGiftTable[ITEM_TO_BERRY(ITEM_ORAN_BERRY)].type == TYPE_POISON); + ASSUME(GetSpeciesType(SPECIES_BELDUM, 0) == TYPE_STEEL); + PLAYER(SPECIES_SKITTY) { Ability(ABILITY_WONDER_SKIN); Innates(ability); Item(ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_BELDUM); + } WHEN { + TURN { MOVE(player, MOVE_NATURAL_GIFT); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player); } + MESSAGE("It doesn't affect the opposing Beldum…"); + } +} + +SINGLE_BATTLE_TEST("Normalize doesn't affect Judgment / Techno Blast / Multi-Attack's type (Traits)") +{ + u16 move, item; + PARAMETRIZE { move = MOVE_JUDGMENT; item = ITEM_ZAP_PLATE; } + PARAMETRIZE { move = MOVE_TECHNO_BLAST; item = ITEM_SHOCK_DRIVE; } + PARAMETRIZE { move = MOVE_MULTI_ATTACK; item = ITEM_ELECTRIC_MEMORY; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_JUDGMENT) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetMoveEffect(MOVE_TECHNO_BLAST) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetMoveEffect(MOVE_MULTI_ATTACK) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(gItemsInfo[ITEM_ZAP_PLATE].holdEffect == HOLD_EFFECT_PLATE); + ASSUME(gItemsInfo[ITEM_ZAP_PLATE].secondaryId == TYPE_ELECTRIC); + ASSUME(gItemsInfo[ITEM_SHOCK_DRIVE].holdEffect == HOLD_EFFECT_DRIVE); + ASSUME(gItemsInfo[ITEM_SHOCK_DRIVE].secondaryId == TYPE_ELECTRIC); + ASSUME(gItemsInfo[ITEM_ELECTRIC_MEMORY].holdEffect == HOLD_EFFECT_MEMORY); + ASSUME(gItemsInfo[ITEM_ELECTRIC_MEMORY].secondaryId == TYPE_ELECTRIC); + ASSUME(GetSpeciesType(SPECIES_DIGLETT, 0) == TYPE_GROUND); + PLAYER(SPECIES_SKITTY) { Ability(ABILITY_WONDER_SKIN); Innates(ABILITY_NORMALIZE); Item(item); } + OPPONENT(SPECIES_DIGLETT); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, move, player); } + MESSAGE("It doesn't affect the opposing Diglett…"); + } +} + +SINGLE_BATTLE_TEST("Normalize doesn't affect Hidden Power's type (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_HIDDEN_POWER) == EFFECT_HIDDEN_POWER); + ASSUME(gTypesInfo[TYPE_ELECTRIC].isHiddenPowerType == TRUE); + ASSUME(GetSpeciesType(SPECIES_DIGLETT, 0) == TYPE_GROUND); + PLAYER(SPECIES_SKITTY) { Ability(ABILITY_WONDER_SKIN); Innates(ABILITY_NORMALIZE); HPIV(31); AttackIV(31); DefenseIV(31); SpAttackIV(30); SpDefenseIV(31); SpeedIV(31); } // HP Electric + OPPONENT(SPECIES_DIGLETT); + } WHEN { + TURN { MOVE(player, MOVE_HIDDEN_POWER); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_HIDDEN_POWER, player); } + MESSAGE("It doesn't affect the opposing Diglett…"); + } +} + +SINGLE_BATTLE_TEST("Normalize doesn't change Tera Blast's type when Terastallized (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TERA_BLAST) == EFFECT_TERA_BLAST); + ASSUME(GetMoveType(MOVE_TERA_BLAST) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_MISDREAVUS, 0) == TYPE_GHOST); + PLAYER(SPECIES_SKITTY) { Ability(ABILITY_WONDER_SKIN); Innates(ABILITY_NORMALIZE); TeraType(TYPE_DARK); } + OPPONENT(SPECIES_MISDREAVUS); + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST, gimmick: GIMMICK_TERA); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Normalize makes Flying Press do Normal/Flying damage (Traits)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_CUTE_CHARM; } + PARAMETRIZE { ability = ABILITY_NORMALIZE; } + GIVEN { + ASSUME(GetSpeciesType(SPECIES_GOLEM, 0) == TYPE_ROCK || GetSpeciesType(SPECIES_GOLEM, 1) == TYPE_ROCK); + PLAYER(SPECIES_SKITTY) { Ability(ABILITY_WONDER_SKIN); Innates(ability); Moves(MOVE_FLYING_PRESS); } + OPPONENT(SPECIES_GOLEM); + } WHEN { + TURN { MOVE(player, MOVE_FLYING_PRESS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLYING_PRESS, player); + if (ability == ABILITY_NORMALIZE) + MESSAGE("It's not very effective…"); + else + NOT { MESSAGE("It's not very effective…"); } + } +} + +SINGLE_BATTLE_TEST("Normalize doesn't affect Terrain Pulse's type (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TERRAIN_PULSE) == EFFECT_TERRAIN_PULSE); + ASSUME(GetMoveType(MOVE_TERRAIN_PULSE) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_SANDSHREW, 0) == TYPE_GROUND || GetSpeciesType(SPECIES_SANDSHREW, 1) == TYPE_GROUND); + PLAYER(SPECIES_SKITTY) { Ability(ABILITY_WONDER_SKIN); Innates(ABILITY_NORMALIZE); } + OPPONENT(SPECIES_SANDSHREW); + } WHEN { + TURN { MOVE(opponent, MOVE_ELECTRIC_TERRAIN); MOVE(player, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_TERRAIN_PULSE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, opponent); + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_TERRAIN_PULSE, player); } + MESSAGE("It doesn't affect the opposing Sandshrew…"); + } +} + +SINGLE_BATTLE_TEST("Normalize doesn't affect damaging Z-Move types (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_WATER_GUN) == TYPE_WATER); + ASSUME(GetSpeciesType(SPECIES_GOLEM, 0) == TYPE_ROCK || GetSpeciesType(SPECIES_GOLEM, 1) == TYPE_ROCK); + PLAYER(SPECIES_SKITTY) { Ability(ABILITY_WONDER_SKIN); Innates(ABILITY_NORMALIZE); Item(ITEM_WATERIUM_Z); Moves(MOVE_WATER_GUN); } + OPPONENT(SPECIES_GOLEM); + } WHEN { + TURN { MOVE(player, MOVE_WATER_GUN, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYDRO_VORTEX, player); + MESSAGE("It's super effective!"); + } +} + +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Normalize-affected moves become Electric-type under Electrify's effect (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ELECTRIFY) == EFFECT_ELECTRIFY); + PLAYER(SPECIES_SKITTY) { Ability(ABILITY_NORMALIZE); } + OPPONENT(SPECIES_ROOKIDEE) { Items(ITEM_PECHA_BERRY, ITEM_WACAN_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_ELECTRIFY); MOVE(player, MOVE_WATER_GUN); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} + +SINGLE_BATTLE_TEST("Normalize-affected moves become Electric-type under Ion Deluge's effect (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ION_DELUGE) == EFFECT_ION_DELUGE); + PLAYER(SPECIES_SKITTY) { Ability(ABILITY_NORMALIZE); Moves(MOVE_WATER_GUN); } + OPPONENT(SPECIES_ROOKIDEE) { Items(ITEM_PECHA_BERRY, ITEM_WACAN_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_ION_DELUGE); MOVE(player, MOVE_WATER_GUN); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} + +SINGLE_BATTLE_TEST("Normalize doesn't affect Natural Gift's type (Multi)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_CUTE_CHARM; } + PARAMETRIZE { ability = ABILITY_NORMALIZE; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + ASSUME(gNaturalGiftTable[ITEM_TO_BERRY(ITEM_ORAN_BERRY)].type == TYPE_POISON); + ASSUME(GetSpeciesType(SPECIES_BELDUM, 0) == TYPE_STEEL); + PLAYER(SPECIES_SKITTY) { Ability(ability); Items(ITEM_GREAT_BALL, ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_BELDUM); + } WHEN { + TURN { MOVE(player, MOVE_NATURAL_GIFT); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player); } + MESSAGE("It doesn't affect the opposing Beldum…"); + } +} + +SINGLE_BATTLE_TEST("Normalize doesn't affect Judgment / Techno Blast / Multi-Attack's type (Multi)") +{ + u16 move, item; + PARAMETRIZE { move = MOVE_JUDGMENT; item = ITEM_ZAP_PLATE; } + PARAMETRIZE { move = MOVE_TECHNO_BLAST; item = ITEM_SHOCK_DRIVE; } + PARAMETRIZE { move = MOVE_MULTI_ATTACK; item = ITEM_ELECTRIC_MEMORY; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_JUDGMENT) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetMoveEffect(MOVE_TECHNO_BLAST) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetMoveEffect(MOVE_MULTI_ATTACK) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(gItemsInfo[ITEM_ZAP_PLATE].holdEffect == HOLD_EFFECT_PLATE); + ASSUME(gItemsInfo[ITEM_ZAP_PLATE].secondaryId == TYPE_ELECTRIC); + ASSUME(gItemsInfo[ITEM_SHOCK_DRIVE].holdEffect == HOLD_EFFECT_DRIVE); + ASSUME(gItemsInfo[ITEM_SHOCK_DRIVE].secondaryId == TYPE_ELECTRIC); + ASSUME(gItemsInfo[ITEM_ELECTRIC_MEMORY].holdEffect == HOLD_EFFECT_MEMORY); + ASSUME(gItemsInfo[ITEM_ELECTRIC_MEMORY].secondaryId == TYPE_ELECTRIC); + ASSUME(GetSpeciesType(SPECIES_DIGLETT, 0) == TYPE_GROUND); + PLAYER(SPECIES_SKITTY) { Ability(ABILITY_NORMALIZE); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_DIGLETT); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, move, player); } + MESSAGE("It doesn't affect the opposing Diglett…"); + } +} +#endif diff --git a/test/battle/ability/oblivious.c b/test/battle/ability/oblivious.c index 03f71d7f5530..e66158223019 100644 --- a/test/battle/ability/oblivious.c +++ b/test/battle/ability/oblivious.c @@ -99,3 +99,104 @@ SINGLE_BATTLE_TEST("Oblivious prevents Intimidate (Gen8+)") MESSAGE("Slowpoke's Oblivious prevents stat loss!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Oblivious prevents Infatuation (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ATTRACT) == EFFECT_ATTRACT); + PLAYER(SPECIES_SLOWPOKE) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_OBLIVIOUS); Gender(MON_MALE); } + OPPONENT(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); } + } WHEN { + TURN { MOVE(opponent, MOVE_ATTRACT); } + } SCENE { + ABILITY_POPUP(player, ABILITY_OBLIVIOUS); + NONE_OF { ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_INFATUATION, player); } + MESSAGE("It doesn't affect Slowpoke…"); + } +} + +SINGLE_BATTLE_TEST("Oblivious prevents Captivate (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_CAPTIVATE) == EFFECT_CAPTIVATE); + PLAYER(SPECIES_SLOWPOKE) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_OBLIVIOUS); Gender(MON_MALE); } + OPPONENT(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); } + } WHEN { + TURN { MOVE(opponent, MOVE_CAPTIVATE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_OBLIVIOUS); + NONE_OF { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); } + MESSAGE("It doesn't affect Slowpoke…"); + } +} + +SINGLE_BATTLE_TEST("Oblivious prevents Taunt (Gen6+) (Traits)") +{ + u32 gen = 0; + PARAMETRIZE { gen = GEN_5; } + PARAMETRIZE { gen = GEN_6; } + GIVEN { + WITH_CONFIG(CONFIG_OBLIVIOUS_TAUNT, gen); + ASSUME(GetMoveEffect(MOVE_TAUNT) == EFFECT_TAUNT); + PLAYER(SPECIES_SLOWPOKE) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_OBLIVIOUS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TAUNT); } + TURN { MOVE(player, MOVE_SPORE, allowed: gen == GEN_6); } + } SCENE { + if (gen == GEN_6) { + NONE_OF { ANIMATION(ANIM_TYPE_MOVE, MOVE_TAUNT, opponent); } + ABILITY_POPUP(player, ABILITY_OBLIVIOUS); + MESSAGE("It doesn't affect Slowpoke…"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAUNT, opponent); + NONE_OF { + ABILITY_POPUP(player, ABILITY_OBLIVIOUS); + MESSAGE("It doesn't affect Slowpoke…"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + } + } + } +} + +SINGLE_BATTLE_TEST("Oblivious doesn't prevent Intimidate (Gen3-7) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_UPDATED_INTIMIDATE, GEN_7); + PLAYER(SPECIES_SLOWPOKE) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_OBLIVIOUS); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + } WHEN { + TURN { SWITCH(opponent, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + NONE_OF { + ABILITY_POPUP(player, ABILITY_OBLIVIOUS); + MESSAGE("Slowpoke's Oblivious prevents stat loss!"); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("The opposing Ekans's Intimidate cuts Slowpoke's Attack!"); + } +} + +SINGLE_BATTLE_TEST("Oblivious prevents Intimidate (Gen8+) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_UPDATED_INTIMIDATE, GEN_8); + PLAYER(SPECIES_SLOWPOKE) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_OBLIVIOUS); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + } WHEN { + TURN { SWITCH(opponent, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + ABILITY_POPUP(player, ABILITY_OBLIVIOUS); + NONE_OF { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); } + MESSAGE("Slowpoke's Oblivious prevents stat loss!"); + } +} +#endif diff --git a/test/battle/ability/opportunist.c b/test/battle/ability/opportunist.c index b78ebc96a0aa..ffaa70db3cb0 100644 --- a/test/battle/ability/opportunist.c +++ b/test/battle/ability/opportunist.c @@ -315,3 +315,357 @@ DOUBLE_BATTLE_TEST("Opportunist and Mirror Herb resolve correctly") EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Opportunist only copies foe's positive stat changes in a turn (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_FRISK; } + PARAMETRIZE { ability = ABILITY_OPPORTUNIST; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + OPPONENT(SPECIES_ESPATHRA) { Speed(5); Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SHELL_SMASH); } + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + if (ability == ABILITY_FRISK) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SMASH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SMASH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[1].damage); + // stat boosts should be the same + EXPECT_EQ(player->statStages[STAT_ATK], opponent->statStages[STAT_ATK]); + EXPECT_EQ(player->statStages[STAT_SPATK], opponent->statStages[STAT_SPATK]); + EXPECT_EQ(player->statStages[STAT_SPEED], opponent->statStages[STAT_SPEED]); + // opportunist should not copy stat drops from shell smash + EXPECT_LT(player->statStages[STAT_DEF], opponent->statStages[STAT_DEF]); + EXPECT_LT(player->statStages[STAT_SPDEF], opponent->statStages[STAT_SPDEF]); + } +} + +DOUBLE_BATTLE_TEST("Opportunist raises Attack only once when partner has Intimidate against Contrary foe in a double battle (Traits)", s16 damageLeft, s16 damageRight) +{ + enum Ability abilityLeft, abilityRight; + + PARAMETRIZE { abilityLeft = ABILITY_CONTRARY; abilityRight = ABILITY_CONTRARY; } + PARAMETRIZE { abilityLeft = ABILITY_TANGLED_FEET; abilityRight = ABILITY_TANGLED_FEET; } + PARAMETRIZE { abilityLeft = ABILITY_CONTRARY; abilityRight = ABILITY_TANGLED_FEET; } + PARAMETRIZE { abilityLeft = ABILITY_TANGLED_FEET; abilityRight = ABILITY_CONTRARY; } + + GIVEN { + PLAYER(SPECIES_MIGHTYENA) { Ability(ABILITY_QUICK_FEET); Innates(ABILITY_INTIMIDATE); } + PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_OPPORTUNIST); } + OPPONENT(SPECIES_SPINDA) { Ability(ABILITY_LIGHT_METAL); Innates(abilityLeft); } + OPPONENT(SPECIES_SPINDA) { Ability(ABILITY_LIGHT_METAL); Innates(abilityRight); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); MOVE(opponentRight, MOVE_SCRATCH, target: playerRight); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + if (abilityLeft == ABILITY_CONTRARY) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("The opposing Spinda's Attack rose!"); + } else { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("Mightyena's Intimidate cuts the opposing Spinda's Attack!"); + } + if (abilityRight == ABILITY_CONTRARY) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("The opposing Spinda's Attack rose!"); + } else { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("Mightyena's Intimidate cuts the opposing Spinda's Attack!"); + } + + if ((abilityLeft == ABILITY_CONTRARY && abilityRight != ABILITY_CONTRARY) + || (abilityLeft != ABILITY_CONTRARY && abilityRight == ABILITY_CONTRARY)) { + ABILITY_POPUP(playerRight, ABILITY_OPPORTUNIST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Espathra's Attack rose!"); + } else if (abilityLeft == ABILITY_CONTRARY && abilityRight == ABILITY_CONTRARY) { + ABILITY_POPUP(playerRight, ABILITY_OPPORTUNIST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Espathra's Attack sharply rose!"); + } + + HP_BAR(playerLeft, captureDamage: &results[i].damageLeft); + HP_BAR(playerRight, captureDamage: &results[i].damageRight); + } THEN { + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + (abilityLeft == ABILITY_CONTRARY ? 1 : - 1)); + EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + (abilityRight == ABILITY_CONTRARY ? 1 : - 1)); + if ((abilityLeft == ABILITY_CONTRARY && abilityRight != ABILITY_CONTRARY) + || (abilityLeft != ABILITY_CONTRARY && abilityRight == ABILITY_CONTRARY)) { + EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } else if (abilityLeft == ABILITY_CONTRARY && abilityRight == ABILITY_CONTRARY) { + EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } + } + FINALLY { + EXPECT_MUL_EQ(results[1].damageLeft, Q_4_12(2.25), results[0].damageLeft); + EXPECT_MUL_EQ(results[1].damageRight, Q_4_12(2.25), results[0].damageRight); + } +} + +SINGLE_BATTLE_TEST("Opportunist does not accumulate opposing mon's stat changes (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ESPATHRA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_OPPORTUNIST); } + } WHEN { + TURN { MOVE(player, MOVE_SWORDS_DANCE); } + TURN { MOVE(player, MOVE_SWORDS_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 4); + } +} + +SINGLE_BATTLE_TEST("Opportunist copies each stat increase individually from ability and move (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ZACIAN) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_INTREPID_SWORD); } + OPPONENT(SPECIES_ESPATHRA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_OPPORTUNIST); } + } WHEN { + TURN { SWITCH(player, 1); } + TURN { MOVE(player, MOVE_SWORDS_DANCE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_INTREPID_SWORD); + ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 3); + } +} + +SINGLE_BATTLE_TEST("Opportunist doesn't copy foe stat increases gained via Opportunist (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_OPPORTUNIST); } + OPPONENT(SPECIES_ESPATHRA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_OPPORTUNIST); } + } WHEN { + TURN { MOVE(player, MOVE_SWORDS_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST); + NOT ABILITY_POPUP(player, ABILITY_OPPORTUNIST); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], player->statStages[STAT_ATK]); + } +} + +SINGLE_BATTLE_TEST("Opportunist copies foe stat increase gained via Swagger and Flatter (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_OPPORTUNIST); } + OPPONENT(SPECIES_ESPATHRA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_OPPORTUNIST); } + } WHEN { + TURN { MOVE(opponent, MOVE_FLATTER); } + TURN { MOVE(opponent, MOVE_SWAGGER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLATTER, opponent); + ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWAGGER, opponent); + ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +DOUBLE_BATTLE_TEST("Opportunist doesn't copy ally stat increases (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_OPPORTUNIST); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SWORDS_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, playerLeft); + NOT ABILITY_POPUP(playerRight, ABILITY_OPPORTUNIST); + } THEN { + EXPECT_EQ(playerRight->statStages[STAT_SPATK], DEFAULT_STAT_STAGE ); + } +} + +DOUBLE_BATTLE_TEST("Opportunist copies the stat increase of each opposing mon (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_OPPORTUNIST); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentRight, MOVE_SWORDS_DANCE); MOVE(opponentLeft, MOVE_SWORDS_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponentLeft); + ABILITY_POPUP(playerRight, ABILITY_OPPORTUNIST); + } THEN { + EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 4); + } +} + + +DOUBLE_BATTLE_TEST("Opportunist copies the stat of each Pokémon that were raised at the same time (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_OPPORTUNIST); } + OPPONENT(SPECIES_ZACIAN) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_INTREPID_SWORD); } + OPPONENT(SPECIES_ZACIAN) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_INTREPID_SWORD); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_INTREPID_SWORD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ABILITY_POPUP(opponentRight, ABILITY_INTREPID_SWORD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + ABILITY_POPUP(playerRight, ABILITY_OPPORTUNIST); + MESSAGE("Espathra's Attack sharply rose!"); + } THEN { + EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Opportunist copies the increase not the stages (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ESPATHRA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_OPPORTUNIST); } + } WHEN { + TURN { MOVE(player, MOVE_CHARM); MOVE(opponent, MOVE_CHARM); } + TURN { MOVE(player, MOVE_CHARM); MOVE(opponent, MOVE_CHARM); } + TURN { MOVE(player, MOVE_CHARM); MOVE(opponent, MOVE_GROWL); } + TURN { MOVE(player, MOVE_BELLY_DRUM); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, opponent); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, opponent); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARM, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, opponent); + + ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 5); // + 11 + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 6); // + 11 + } +} + +SINGLE_BATTLE_TEST("Opportunist copies the stat increase from the incoming mon (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_OPPORTUNIST); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_ZACIAN) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_INTREPID_SWORD); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ABILITY_POPUP(opponent, ABILITY_INTREPID_SWORD); + ABILITY_POPUP(player, ABILITY_OPPORTUNIST); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Opportunist and Mirror Herb stack stat increases (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ZACIAN) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_INTREPID_SWORD); } + OPPONENT(SPECIES_ESPATHRA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_OPPORTUNIST); Item(ITEM_MIRROR_HERB); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_INTREPID_SWORD); + ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Opportunist and Mirror Herb resolve correctly (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ZACIAN) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_INTREPID_SWORD); } + PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_OPPORTUNIST); } + OPPONENT(SPECIES_MEOWSCARADA) { Item(ITEM_MIRROR_HERB); } + OPPONENT(SPECIES_ESPATHRA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_OPPORTUNIST); Item(ITEM_MIRROR_HERB); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_INTREPID_SWORD); + ABILITY_POPUP(opponentRight, ABILITY_OPPORTUNIST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Opportunist and Mirror Herb stack stat increases (Multi)") +{ + GIVEN { + PLAYER(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); } + OPPONENT(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); Items(ITEM_PECHA_BERRY, ITEM_MIRROR_HERB); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_INTREPID_SWORD); + ABILITY_POPUP(opponent, ABILITY_OPPORTUNIST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Opportunist and Mirror Herb resolve correctly (Multi)") +{ + GIVEN { + PLAYER(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); } + PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); } + OPPONENT(SPECIES_MEOWSCARADA) { Items(ITEM_PECHA_BERRY, ITEM_MIRROR_HERB); } + OPPONENT(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); Items(ITEM_PECHA_BERRY, ITEM_MIRROR_HERB); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_INTREPID_SWORD); + ABILITY_POPUP(opponentRight, ABILITY_OPPORTUNIST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} +#endif diff --git a/test/battle/ability/orichalcum_pulse.c b/test/battle/ability/orichalcum_pulse.c index 62509ce0c67b..fee8ebf8cca8 100644 --- a/test/battle/ability/orichalcum_pulse.c +++ b/test/battle/ability/orichalcum_pulse.c @@ -110,3 +110,74 @@ SINGLE_BATTLE_TEST("Orichalcum Pulse does not boost physical moves if holder has EXPECT_MUL_EQ(results[1].damage, Q_4_12(1.3333), results[0].damage); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Orichalcum Pulse sets up sun for 8 turns with Heat Rock (Multi)") +{ + GIVEN { + PLAYER(SPECIES_KORAIDON) { Moves(MOVE_CELEBRATE); Ability(ABILITY_ORICHALCUM_PULSE); Items(ITEM_PECHA_BERRY, ITEM_HEAT_ROCK); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ORICHALCUM_PULSE); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight is strong."); + MESSAGE("The sunlight faded."); + } +} + +SINGLE_BATTLE_TEST("Orichalcum Pulse boost applies even if the target holds Utility Umbrella (Multi)", s16 damage) +{ + u16 targetItem; + PARAMETRIZE { targetItem = ITEM_NONE; } + PARAMETRIZE { targetItem = ITEM_UTILITY_UMBRELLA; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_UTILITY_UMBRELLA].holdEffect == HOLD_EFFECT_UTILITY_UMBRELLA); + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_KORAIDON) { Ability(ABILITY_ORICHALCUM_PULSE); Moves(MOVE_SCRATCH); Speed(5); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); Speed(10); Items(ITEM_PECHA_BERRY, targetItem); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ORICHALCUM_PULSE); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Orichalcum Pulse does not boost physical moves if holder has Utility Umbrella (Multi)", s16 damage) +{ + u16 holdItem; + PARAMETRIZE { holdItem = ITEM_NONE; } + PARAMETRIZE { holdItem = ITEM_UTILITY_UMBRELLA; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_UTILITY_UMBRELLA].holdEffect == HOLD_EFFECT_UTILITY_UMBRELLA); + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_KORAIDON) { Ability(ABILITY_ORICHALCUM_PULSE); Moves(MOVE_SCRATCH); Speed(5); Items(ITEM_PECHA_BERRY, holdItem); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); Speed(10); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ORICHALCUM_PULSE); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[1].damage, Q_4_12(1.3333), results[0].damage); + } +} +#endif diff --git a/test/battle/ability/overcoat.c b/test/battle/ability/overcoat.c index 7346691c46c2..a3e8a9f4baa1 100644 --- a/test/battle/ability/overcoat.c +++ b/test/battle/ability/overcoat.c @@ -97,3 +97,102 @@ SINGLE_BATTLE_TEST("Overcoat blocks Effect Spore's effect (Gen6+)") EXPECT_NE(player->status1, 0); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Overcoat blocks powder and spore moves (Gen6+) (Traits)") +{ + u32 gen = 0; + PARAMETRIZE { gen = GEN_5; } + PARAMETRIZE { gen = GEN_6; } + GIVEN { + WITH_CONFIG(CONFIG_POWDER_OVERCOAT, gen); + ASSUME(IsPowderMove(MOVE_STUN_SPORE)); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_PINECO) { Ability(ABILITY_STURDY); Innates(ABILITY_OVERCOAT); } + } WHEN { + TURN { MOVE(player, MOVE_STUN_SPORE); } + } SCENE { + if (gen == GEN_6) { + ABILITY_POPUP(opponent, ABILITY_OVERCOAT); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, player); + MESSAGE("It doesn't affect the opposing Pineco…"); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_OVERCOAT); + MESSAGE("It doesn't affect the opposing Pineco…"); + } + } + } +} + +DOUBLE_BATTLE_TEST("Overcoat blocks damage from sandstorm (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT) { Speed(50); } + PLAYER(SPECIES_HELIOLISK) { Speed(40); Ability(ABILITY_DRY_SKIN); Innates(ABILITY_SAND_VEIL); } + OPPONENT(SPECIES_PINECO) { Speed(30); Ability(ABILITY_STURDY); Innates(ABILITY_OVERCOAT); } + OPPONENT(SPECIES_STARLY) { Speed(20); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SANDSTORM); } + } SCENE { + MESSAGE("Wynaut used Sandstorm!"); + MESSAGE("The sandstorm is raging."); + HP_BAR(playerLeft); + NONE_OF { + HP_BAR(playerRight); + HP_BAR(opponentLeft); + } + HP_BAR(opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Overcoat blocks damage from hail (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_HAIL) == EFFECT_HAIL); + PLAYER(SPECIES_WYNAUT) { Speed(50); Ability(ABILITY_SNOW_CLOAK); } + PLAYER(SPECIES_SOLOSIS) { Speed(40); Ability(ABILITY_RUN_AWAY); } + OPPONENT(SPECIES_PINECO) { Speed(30); Ability(ABILITY_STURDY); Innates(ABILITY_OVERCOAT); } + OPPONENT(SPECIES_SNORUNT) { Speed(20); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_HAIL); MOVE(playerRight, MOVE_SKILL_SWAP, target: playerLeft); } + } SCENE { + MESSAGE("Wynaut used Hail!"); + MESSAGE("Solosis used Skill Swap!"); + HP_BAR(playerLeft); + NONE_OF { + HP_BAR(playerRight); + HP_BAR(opponentLeft); + HP_BAR(opponentRight); // ice type + } + } +} + +SINGLE_BATTLE_TEST("Overcoat blocks Effect Spore's effect (Gen6+) (Traits)") +{ + u32 config; + PARAMETRIZE { config = GEN_5; } + PARAMETRIZE { config = GEN_6; } + GIVEN { + WITH_CONFIG(CONFIG_POWDER_OVERCOAT, config); + PLAYER(SPECIES_PINECO) {Ability(ABILITY_STURDY); Innates(ABILITY_OVERCOAT);} + OPPONENT(SPECIES_SHROOMISH) {Ability(ABILITY_TECHNICIAN); Innates(ABILITY_EFFECT_SPORE);} + } WHEN { + TURN { MOVE(player, MOVE_TACKLE, WITH_RNG(RNG_EFFECT_SPORE, 1)); } + } SCENE { + MESSAGE("Pineco used Tackle!"); + if (config == GEN_6) { + NOT ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + } + else { + ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + } + } THEN { + if (config == GEN_6) + EXPECT_EQ(player->status1, 0); + else + EXPECT_NE(player->status1, 0); + } +} +#endif diff --git a/test/battle/ability/overgrow.c b/test/battle/ability/overgrow.c index 3ba779009306..b39cc1c9bd34 100644 --- a/test/battle/ability/overgrow.c +++ b/test/battle/ability/overgrow.c @@ -18,3 +18,23 @@ SINGLE_BATTLE_TEST("Overgrow boosts Grass-type moves in a pinch", s16 damage) EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Overgrow boosts Grass-type moves in a pinch (Traits)", s16 damage) +{ + u16 hp; + PARAMETRIZE { hp = 99; } + PARAMETRIZE { hp = 33; } + GIVEN { + ASSUME(GetMoveType(MOVE_VINE_WHIP) == TYPE_GRASS); + PLAYER(SPECIES_BULBASAUR) { Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_OVERGROW); MaxHP(99); HP(hp); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_VINE_WHIP); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} +#endif diff --git a/test/battle/ability/own_tempo.c b/test/battle/ability/own_tempo.c index 06b6f366ff05..0dc37e9afe3b 100644 --- a/test/battle/ability/own_tempo.c +++ b/test/battle/ability/own_tempo.c @@ -143,3 +143,143 @@ SINGLE_BATTLE_TEST("Own Tempo prevents confusion from items") ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Own Tempo doesn't prevent Intimidate (Gen3-7) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_UPDATED_INTIMIDATE, GEN_7); + ASSUME(GetMoveEffect(MOVE_CONFUSE_RAY) == EFFECT_CONFUSE); + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); }; + OPPONENT(SPECIES_SLOWPOKE) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_OWN_TEMPO); }; + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_OWN_TEMPO); + MESSAGE("The opposing Slowpoke's Own Tempo prevents stat loss!"); + } + } +} + +SINGLE_BATTLE_TEST("Own Tempo prevents Intimidate but no other stat down changes (Gen8+) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_UPDATED_INTIMIDATE, GEN_8); + ASSUME(GetMoveEffect(MOVE_CONFUSE_RAY) == EFFECT_CONFUSE); + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); }; + OPPONENT(SPECIES_SLOWPOKE) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_OWN_TEMPO); }; + } WHEN { + TURN { MOVE(player, MOVE_SCARY_FACE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponent, ABILITY_OWN_TEMPO); + MESSAGE("The opposing Slowpoke's Own Tempo prevents stat loss!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCARY_FACE, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_OWN_TEMPO); + MESSAGE("The opposing Slowpoke's Own Tempo prevents stat loss!"); + } + } +} + +SINGLE_BATTLE_TEST("Own Tempo prevents confusion from moves by the opponent (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_CONFUSE_RAY) == EFFECT_CONFUSE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SLOWPOKE) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_OWN_TEMPO); }; + } WHEN { + TURN { MOVE(player, MOVE_CONFUSE_RAY); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_OWN_TEMPO); + MESSAGE("The opposing Slowpoke's Own Tempo prevents confusion!"); + } +} + +SINGLE_BATTLE_TEST("Own Tempo prevents confusion from moves by the user (Traits)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffectSelf(MOVE_PETAL_DANCE, MOVE_EFFECT_THRASH)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SLOWPOKE) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_OWN_TEMPO); }; + } WHEN { + TURN { MOVE(opponent, MOVE_PETAL_DANCE); } + TURN { MOVE(opponent, MOVE_PETAL_DANCE); } + TURN { MOVE(opponent, MOVE_PETAL_DANCE); } + TURN { MOVE(opponent, MOVE_PETAL_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PETAL_DANCE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PETAL_DANCE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PETAL_DANCE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PETAL_DANCE, opponent); + NONE_OF { MESSAGE("The opposing Slowpoke became confused due to fatigue!"); } + } +} + +SINGLE_BATTLE_TEST("Mold Breaker ignores Own Tempo (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_CONFUSE_RAY) == EFFECT_CONFUSE); + PLAYER(SPECIES_PINSIR) { Ability(ABILITY_HYPER_CUTTER); Innates(ABILITY_MOLD_BREAKER); } + OPPONENT(SPECIES_SLOWPOKE) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_OWN_TEMPO); }; + } WHEN { + TURN { MOVE(player, MOVE_CONFUSE_RAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, player); + NOT MESSAGE("The opposing Slowpoke's Own Tempo prevents confusion!"); + } +} + +SINGLE_BATTLE_TEST("Mold Breaker does not prevent Own Tempo from curing confusion right after (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_CONFUSE_RAY) == EFFECT_CONFUSE); + PLAYER(SPECIES_PINSIR) { Ability(ABILITY_HYPER_CUTTER); Innates(ABILITY_MOLD_BREAKER); }; + OPPONENT(SPECIES_SLOWPOKE) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_OWN_TEMPO); }; + } WHEN { + TURN { MOVE(player, MOVE_CONFUSE_RAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, player); + MESSAGE("The opposing Slowpoke became confused!"); + NONE_OF { + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, opponent); + } + ABILITY_POPUP(opponent, ABILITY_OWN_TEMPO); + MESSAGE("The opposing Slowpoke's Own Tempo cured its confusion problem!"); + } +} + +SINGLE_BATTLE_TEST("Own Tempo prevents confusion from items (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_BERSERK_GENE].holdEffect == HOLD_EFFECT_BERSERK_GENE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SLOWPOKE) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_OWN_TEMPO); Item(ITEM_BERSERK_GENE); }; + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ABILITY_POPUP(opponent, ABILITY_OWN_TEMPO); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Own Tempo prevents confusion from items (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_BERSERK_GENE].holdEffect == HOLD_EFFECT_BERSERK_GENE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SLOWPOKE) { Ability(ABILITY_OWN_TEMPO); Items(ITEM_PECHA_BERRY, ITEM_BERSERK_GENE); }; + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ABILITY_POPUP(opponent, ABILITY_OWN_TEMPO); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + } +} +#endif diff --git a/test/battle/ability/parental_bond.c b/test/battle/ability/parental_bond.c index d8be7c76f2fe..1e599a831d9b 100644 --- a/test/battle/ability/parental_bond.c +++ b/test/battle/ability/parental_bond.c @@ -398,3 +398,347 @@ The second strike now deals 25% of its usual damage (unless it is a set-damage m Parental Bond does not affect Z-Moves or Max Moves. */ // TONS OF TESTS NEEDED. FOR NOW, THIS SINGLE TEST IS MADE TO MAKE SURE AN ISSUE WAS FIXED. + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Parental Bond converts Scratch into a two-strike move (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) != DAMAGE_CATEGORY_STATUS); + ASSUME(GetMoveStrikeCount(MOVE_SCRATCH) < 2); + ASSUME(GetMoveEffect(MOVE_SCRATCH) == EFFECT_HIT); + PLAYER(SPECIES_KANGASKHAN) { Ability(ABILITY_EARLY_BIRD); Innates(ABILITY_PARENTAL_BOND); Item(ITEM_KANGASKHANITE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +SINGLE_BATTLE_TEST("Parental Bond does not convert a move with three or more strikes to a two-strike move (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_TRIPLE_KICK) != DAMAGE_CATEGORY_STATUS); + ASSUME(GetMoveStrikeCount(MOVE_TRIPLE_KICK) == 3); + PLAYER(SPECIES_KANGASKHAN) { Ability(ABILITY_EARLY_BIRD); Innates(ABILITY_PARENTAL_BOND); Item(ITEM_KANGASKHANITE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TRIPLE_KICK); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRIPLE_KICK, player); + HP_BAR(opponent); + HP_BAR(opponent); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +SINGLE_BATTLE_TEST("Parental Bond converts multi-target moves into a two-strike move in Single Battles (Traits)") +{ + u16 move; + PARAMETRIZE { move = MOVE_EARTHQUAKE; } + PARAMETRIZE { move = MOVE_ICY_WIND; } + + GIVEN { + ASSUME(GetMoveStrikeCount(MOVE_EARTHQUAKE) < 2); + ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(GetMoveStrikeCount(MOVE_ICY_WIND) < 2); + ASSUME(GetMoveTarget(MOVE_ICY_WIND) == MOVE_TARGET_BOTH); + PLAYER(SPECIES_KANGASKHAN) { Ability(ABILITY_EARLY_BIRD); Innates(ABILITY_PARENTAL_BOND); Item(ITEM_KANGASKHANITE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +DOUBLE_BATTLE_TEST("Parental Bond does not convert multi-target moves into a two-strike move in Double Battles, even if it only damages one (Traits)") +{ + GIVEN { + ASSUME(GetMoveStrikeCount(MOVE_EARTHQUAKE) < 2); + ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(GetSpeciesType(SPECIES_PIDGEY, 1) == TYPE_FLYING); + PLAYER(SPECIES_KANGASKHAN) { Ability(ABILITY_EARLY_BIRD); Innates(ABILITY_PARENTAL_BOND); Item(ITEM_KANGASKHANITE); } + PLAYER(SPECIES_PIDGEY); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY); + } WHEN { + TURN { MOVE(playerLeft, MOVE_EARTHQUAKE); MOVE(playerRight, MOVE_CELEBRATE); MOVE(opponentLeft, MOVE_CELEBRATE); MOVE(opponentRight, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, playerLeft); + HP_BAR(opponentLeft); + MESSAGE("It doesn't affect Pidgey…"); + MESSAGE("It doesn't affect the opposing Pidgey…"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight); + } +} + +SINGLE_BATTLE_TEST("Parental Bond-converted moves only hit once on Lightning Rod/Storm Drain mons (Traits)") +{ + u16 move, species; + enum Type type; + enum Ability ability; + PARAMETRIZE { move = MOVE_THUNDERBOLT; ability = ABILITY_LIGHTNING_ROD; species = SPECIES_RAICHU; type = TYPE_ELECTRIC; } + PARAMETRIZE { move = MOVE_SURF; ability = ABILITY_STORM_DRAIN; species = SPECIES_LILEEP; type = TYPE_WATER; } + GIVEN { + ASSUME(GetMoveStrikeCount(move) < 2); + ASSUME(GetMoveType(move) == type); + PLAYER(SPECIES_KANGASKHAN) { Ability(ABILITY_EARLY_BIRD); Innates(ABILITY_PARENTAL_BOND); Item(ITEM_KANGASKHANITE); } + OPPONENT(species) { Ability(ABILITY_EARLY_BIRD); Innates(ability); } + } WHEN { + TURN { MOVE(player, move); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(opponent, ability); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + ABILITY_POPUP(opponent, ability); + }; + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +SINGLE_BATTLE_TEST("Parental Bond has no affect on multi hit moves and they still hit twice 37.5/35% of the time (Traits)") +{ + u32 genConfig, passes, trials; + PARAMETRIZE { genConfig = GEN_4; passes = 3; trials = 8; } // 37.5% + PARAMETRIZE { genConfig = GEN_5; passes = 7; trials = 20; } // 35% + PASSES_RANDOMLY(passes, trials, RNG_HITS); + + GIVEN { + WITH_CONFIG(CONFIG_MULTI_HIT_CHANCE, genConfig); + ASSUME(GetMoveCategory(MOVE_COMET_PUNCH) != DAMAGE_CATEGORY_STATUS); + ASSUME(GetMoveEffect(MOVE_COMET_PUNCH) == EFFECT_MULTI_HIT); + PLAYER(SPECIES_KANGASKHAN) { Ability(ABILITY_EARLY_BIRD); Innates(ABILITY_PARENTAL_BOND); Item(ITEM_KANGASKHANITE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_COMET_PUNCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_COMET_PUNCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COMET_PUNCH, player); + MESSAGE("The Pokémon was hit 2 time(s)!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +SINGLE_BATTLE_TEST("Parental Bond has no affect on multi hit moves and they still hit thrice 37.5/35% of the time (Traits)") +{ + u32 genConfig, passes, trials; + PARAMETRIZE { genConfig = GEN_4; passes = 3; trials = 8; } // 37.5% + PARAMETRIZE { genConfig = GEN_5; passes = 7; trials = 20; } // 35% + PASSES_RANDOMLY(passes, trials, RNG_HITS); + + GIVEN { + WITH_CONFIG(CONFIG_MULTI_HIT_CHANCE, genConfig); + ASSUME(GetMoveCategory(MOVE_COMET_PUNCH) != DAMAGE_CATEGORY_STATUS); + ASSUME(GetMoveEffect(MOVE_COMET_PUNCH) == EFFECT_MULTI_HIT); + PLAYER(SPECIES_KANGASKHAN) { Ability(ABILITY_EARLY_BIRD); Innates(ABILITY_PARENTAL_BOND); Item(ITEM_KANGASKHANITE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_COMET_PUNCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_COMET_PUNCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COMET_PUNCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COMET_PUNCH, player); + MESSAGE("The Pokémon was hit 3 time(s)!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +SINGLE_BATTLE_TEST("Parental Bond has no affect on multi hit moves and they still hit four times 12.5/15% of the time (Traits)") +{ + u32 genConfig, passes, trials; + PARAMETRIZE { genConfig = GEN_4; passes = 1; trials = 8; } // 12.5% + PARAMETRIZE { genConfig = GEN_5; passes = 3; trials = 20; } // 15% + PASSES_RANDOMLY(passes, trials, RNG_HITS); + + GIVEN { + WITH_CONFIG(CONFIG_MULTI_HIT_CHANCE, genConfig); + ASSUME(GetMoveCategory(MOVE_COMET_PUNCH) != DAMAGE_CATEGORY_STATUS); + ASSUME(GetMoveEffect(MOVE_COMET_PUNCH) == EFFECT_MULTI_HIT); + PLAYER(SPECIES_KANGASKHAN) { Item(ITEM_KANGASKHANITE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_COMET_PUNCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_COMET_PUNCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COMET_PUNCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COMET_PUNCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COMET_PUNCH, player); + MESSAGE("The Pokémon was hit 4 time(s)!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +SINGLE_BATTLE_TEST("Parental Bond has no affect on multi hit moves and they still hit five times 12.5/15% of the time (Traits)") +{ + u32 genConfig, passes, trials; + PARAMETRIZE { genConfig = GEN_4; passes = 1; trials = 8; } // 12.5% + PARAMETRIZE { genConfig = GEN_5; passes = 3; trials = 20; } // 15% + PASSES_RANDOMLY(passes, trials, RNG_HITS); + + GIVEN { + WITH_CONFIG(CONFIG_MULTI_HIT_CHANCE, genConfig); + ASSUME(GetMoveCategory(MOVE_COMET_PUNCH) != DAMAGE_CATEGORY_STATUS); + ASSUME(GetMoveEffect(MOVE_COMET_PUNCH) == EFFECT_MULTI_HIT); + PLAYER(SPECIES_KANGASKHAN) { Ability(ABILITY_EARLY_BIRD); Innates(ABILITY_PARENTAL_BOND); Item(ITEM_KANGASKHANITE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_COMET_PUNCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_COMET_PUNCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COMET_PUNCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COMET_PUNCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COMET_PUNCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COMET_PUNCH, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_COMET_PUNCH, player); + MESSAGE("The Pokémon was hit 5 time(s)!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +SINGLE_BATTLE_TEST("Parental Bond Smack Down effect triggers after 2nd hit (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SMACK_DOWN) == EFFECT_SMACK_DOWN); + ASSUME(GetMoveCategory(MOVE_SMACK_DOWN) != DAMAGE_CATEGORY_STATUS); + ASSUME(GetMoveStrikeCount(MOVE_SMACK_DOWN) < 2); + PLAYER(SPECIES_KANGASKHAN) { Ability(ABILITY_EARLY_BIRD); Innates(ABILITY_PARENTAL_BOND); Item(ITEM_KANGASKHANITE); } + OPPONENT(SPECIES_SKARMORY); + } WHEN { + TURN { MOVE(player, MOVE_SMACK_DOWN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SMACK_DOWN, player); + HP_BAR(opponent); + NOT MESSAGE("The opposing Skarmory fell straight down!"); + HP_BAR(opponent); + MESSAGE("The opposing Skarmory fell straight down!"); + } +} + +SINGLE_BATTLE_TEST("Parental Bond Snore strikes twice while asleep (Traits)") +{ + s16 damage[2]; + GIVEN { + ASSUME(GetMoveEffect(MOVE_SNORE) == EFFECT_SNORE); + PLAYER(SPECIES_KANGASKHAN_MEGA) { Ability(ABILITY_EARLY_BIRD); Innates(ABILITY_PARENTAL_BOND); Status1(STATUS1_SLEEP); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SNORE); } + } SCENE { + MESSAGE("Kangaskhan is fast asleep."); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SNORE, player); + HP_BAR(opponent, captureDamage: &damage[0]); + HP_BAR(opponent, captureDamage: &damage[1]); + MESSAGE("The Pokémon was hit 2 time(s)!"); + } THEN { + if (B_PARENTAL_BOND_DMG >= GEN_7) + EXPECT_MUL_EQ(damage[0], Q_4_12(0.25), damage[1]); + else + EXPECT_MUL_EQ(damage[0], Q_4_12(0.5), damage[1]); + } +} + +SINGLE_BATTLE_TEST("Parental Bond only triggers Dragon Tail's target switch out on the second hit (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_DRAGON_TAIL) == EFFECT_HIT_SWITCH_TARGET); + PLAYER(SPECIES_KANGASKHAN) { Ability(ABILITY_EARLY_BIRD); Innates(ABILITY_PARENTAL_BOND); Item(ITEM_KANGASKHANITE); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + HP_BAR(opponent); + HP_BAR(opponent); + MESSAGE("The opposing Wynaut was dragged out!"); + } +} + +SINGLE_BATTLE_TEST("Parental Bond does not trigger on semi-invulnerable moves (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_FLY) != DAMAGE_CATEGORY_STATUS); + ASSUME(GetMoveStrikeCount(MOVE_FLY) < 2); + ASSUME(GetMoveEffect(MOVE_FLY) == EFFECT_SEMI_INVULNERABLE); + PLAYER(SPECIES_KANGASKHAN) { Ability(ABILITY_EARLY_BIRD); Innates(ABILITY_PARENTAL_BOND); Item(ITEM_KANGASKHANITE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FLY); MOVE(opponent, MOVE_CELEBRATE); } + TURN { SKIP_TURN(player); } + } SCENE { + HP_BAR(opponent); + NOT HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Parental Bond does not trigger on two turn attacks (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_RAZOR_WIND) != DAMAGE_CATEGORY_STATUS); + ASSUME(GetMoveStrikeCount(MOVE_RAZOR_WIND) < 2); + ASSUME(GetMoveEffect(MOVE_RAZOR_WIND) == EFFECT_TWO_TURNS_ATTACK); + PLAYER(SPECIES_KANGASKHAN) { Ability(ABILITY_EARLY_BIRD); Innates(ABILITY_PARENTAL_BOND); Item(ITEM_KANGASKHANITE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_RAZOR_WIND); MOVE(opponent, MOVE_CELEBRATE); } + TURN { SKIP_TURN(player); } + } SCENE { + HP_BAR(opponent); + NOT HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Parental Bond does not trigger Scale Shot effect on Drain Punch (Traits)") +{ + GIVEN { + PLAYER(SPECIES_KANGASKHAN) { Ability(ABILITY_EARLY_BIRD); Innates(ABILITY_PARENTAL_BOND); Item(ITEM_KANGASKHANITE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_DRAIN_PUNCH); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAIN_PUNCH, player); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} + +TO_DO_BATTLE_TEST("Parental Bond tests (Traits)"); +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Parental Bond converts Scratch into a two-strike move (Multi)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) != DAMAGE_CATEGORY_STATUS); + ASSUME(GetMoveStrikeCount(MOVE_SCRATCH) < 2); + ASSUME(GetMoveEffect(MOVE_SCRATCH) == EFFECT_HIT); + PLAYER(SPECIES_KANGASKHAN) { Items(ITEM_PECHA_BERRY, ITEM_KANGASKHANITE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_MEGA); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + MESSAGE("Kangaskhan's Kangaskhanite is reacting to 1's Mega Ring!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, player); + MESSAGE("Kangaskhan has Mega Evolved into Mega Kangaskhan!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } THEN { + EXPECT_EQ(player->species, SPECIES_KANGASKHAN_MEGA); + } +} +#endif diff --git a/test/battle/ability/pastel_veil.c b/test/battle/ability/pastel_veil.c index 11bf32bb9a09..3e7a6650cf03 100644 --- a/test/battle/ability/pastel_veil.c +++ b/test/battle/ability/pastel_veil.c @@ -187,3 +187,186 @@ DOUBLE_BATTLE_TEST("Pastel Veil cures partner's poison on switch in") STATUS_ICON(opponentLeft, none: TRUE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Pastel Veil prevents Poison Sting poison (Traits)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_POISON_STING, MOVE_EFFECT_POISON) == TRUE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PONYTA_GALAR) { Ability(ABILITY_ANTICIPATION); Innates(ABILITY_PASTEL_VEIL); } + } WHEN { + TURN { MOVE(player, MOVE_POISON_STING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_STING, player); + NOT STATUS_ICON(opponent, poison: TRUE); + } +} + +DOUBLE_BATTLE_TEST("Pastel Veil prevents Poison Sting poison on partner (Traits)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_POISON_STING, MOVE_EFFECT_POISON) == TRUE); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_PONYTA_GALAR) { Ability(ABILITY_ANTICIPATION); Innates(ABILITY_PASTEL_VEIL); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_POISON_STING, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_STING, playerLeft); + NOT STATUS_ICON(opponentRight, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Pastel Veil immediately cures Mold Breaker poison (Traits)") +{ + GIVEN { + PLAYER(SPECIES_PINSIR) { Ability(ABILITY_HYPER_CUTTER); Innates(ABILITY_MOLD_BREAKER); } + OPPONENT(SPECIES_PONYTA_GALAR) { Ability(ABILITY_ANTICIPATION); Innates(ABILITY_PASTEL_VEIL); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + STATUS_ICON(opponent, badPoison: TRUE); + ABILITY_POPUP(opponent, ABILITY_PASTEL_VEIL); + MESSAGE("The opposing Ponyta's Pastel Veil cured its poison problem!"); + STATUS_ICON(opponent, none: TRUE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + } +} + +DOUBLE_BATTLE_TEST("Pastel Veil does not cure Mold Breaker poison on partner (Traits)") +{ + GIVEN { + PLAYER(SPECIES_PINSIR) { Ability(ABILITY_HYPER_CUTTER); Innates(ABILITY_MOLD_BREAKER); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_PONYTA_GALAR) { Ability(ABILITY_ANTICIPATION); Innates(ABILITY_PASTEL_VEIL); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_TOXIC, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, playerLeft, target: opponentRight); + STATUS_ICON(opponentRight, badPoison: TRUE); + NOT STATUS_ICON(opponentRight, none: TRUE); + } +} + +SINGLE_BATTLE_TEST("Pastel Veil prevents Toxic bad poison (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PONYTA_GALAR) { Ability(ABILITY_ANTICIPATION); Innates(ABILITY_PASTEL_VEIL); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC); } + } SCENE { + MESSAGE("Wobbuffet used Toxic!"); + ABILITY_POPUP(opponent, ABILITY_PASTEL_VEIL); + MESSAGE("It doesn't affect the opposing Ponyta…"); + NOT STATUS_ICON(opponent, badPoison: TRUE); + } +} + +DOUBLE_BATTLE_TEST("Pastel Veil prevents Toxic bad poison on partner - right target (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_PONYTA_GALAR) { Ability(ABILITY_ANTICIPATION); Innates(ABILITY_PASTEL_VEIL); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_TOXIC, target: opponentRight); } + } SCENE { + MESSAGE("Wobbuffet used Toxic!"); + ABILITY_POPUP(opponentLeft, ABILITY_PASTEL_VEIL); + MESSAGE("It doesn't affect the opposing Wynaut…"); + NOT STATUS_ICON(opponentRight, badPoison: TRUE); + } +} + +DOUBLE_BATTLE_TEST("Pastel Veil prevents Toxic bad poison on partner - left target (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_PONYTA_GALAR) { Ability(ABILITY_ANTICIPATION); Innates(ABILITY_PASTEL_VEIL); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_TOXIC, target: opponentLeft); } + } SCENE { + MESSAGE("Wobbuffet used Toxic!"); + ABILITY_POPUP(opponentRight, ABILITY_PASTEL_VEIL); + MESSAGE("It doesn't affect the opposing Wynaut…"); + NOT STATUS_ICON(opponentLeft, badPoison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Pastel Veil prevents Toxic Spikes poison (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOXIC_SPIKES) == EFFECT_TOXIC_SPIKES); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PONYTA_GALAR) { Ability(ABILITY_ANTICIPATION); Innates(ABILITY_PASTEL_VEIL); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC_SPIKES); } + TURN { SWITCH(opponent, 1); } + } SCENE { + MESSAGE("2 sent out Ponyta!"); + NOT STATUS_ICON(opponent, poison: TRUE); + } +} + +DOUBLE_BATTLE_TEST("Pastel Veil prevents Toxic Spikes poison on partner (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOXIC_SPIKES) == EFFECT_TOXIC_SPIKES); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_PONYTA_GALAR) { Ability(ABILITY_ANTICIPATION); Innates(ABILITY_PASTEL_VEIL); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_TOXIC_SPIKES); } + TURN { SWITCH(opponentRight, 2); } + } SCENE { + MESSAGE("2 sent out Wynaut!"); + NOT STATUS_ICON(opponentRight, poison: TRUE); + } +} + +DOUBLE_BATTLE_TEST("Pastel Veil cures partner's poison on initial switch in (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + OPPONENT(SPECIES_PONYTA_GALAR) { Ability(ABILITY_ANTICIPATION); Innates(ABILITY_PASTEL_VEIL); } + } WHEN { + TURN {} + } SCENE { + MESSAGE("2 sent out Wobbuffet and Ponyta!"); + ABILITY_POPUP(opponentRight, ABILITY_PASTEL_VEIL); + MESSAGE("The opposing Wobbuffet was cured of its poisoning!"); + STATUS_ICON(opponentLeft, none: TRUE); + } +} + +DOUBLE_BATTLE_TEST("Pastel Veil cures partner's poison on switch in (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_PONYTA_GALAR) { Ability(ABILITY_ANTICIPATION); Innates(ABILITY_PASTEL_VEIL); } + } WHEN { + TURN { SWITCH(opponentRight, 2); } + } SCENE { + MESSAGE("2 sent out Ponyta!"); + ABILITY_POPUP(opponentRight, ABILITY_PASTEL_VEIL); + MESSAGE("The opposing Wobbuffet was cured of its poisoning!"); + STATUS_ICON(opponentLeft, none: TRUE); + } +} +#endif diff --git a/test/battle/ability/pickpocket.c b/test/battle/ability/pickpocket.c index e6b92a6e8e04..9f5da6551e3f 100644 --- a/test/battle/ability/pickpocket.c +++ b/test/battle/ability/pickpocket.c @@ -310,3 +310,615 @@ SINGLE_BATTLE_TEST("Pickpocket does not prevent King's Rock or Razor Fang flinch EXPECT(player->item == ITEM_NONE); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Pickpocket checks contact/effect per target for spread moves") +{ + GIVEN { + ASSUME(GetSpeciesType(SPECIES_CLEFAIRY, 0) == TYPE_FAIRY); + ASSUME(GetMoveType(MOVE_BREAKING_SWIPE) == TYPE_DRAGON); + ASSUME(GetMoveTarget(MOVE_BREAKING_SWIPE) == MOVE_TARGET_BOTH); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MAGOST_BERRY); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PICKPOCKET); } + OPPONENT(SPECIES_CLEFAIRY); + } WHEN { + TURN { MOVE(playerLeft, MOVE_BREAKING_SWIPE); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!"); + } THEN { + EXPECT(opponentLeft->item == ITEM_MAGOST_BERRY); + EXPECT(playerLeft->item == ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Pickpocket activates for the fastest itemless target when both are hit by a contact spread move (Traits)") +{ + GIVEN { + ASSUME(GetMoveTarget(MOVE_BREAKING_SWIPE) == MOVE_TARGET_BOTH); + PLAYER(SPECIES_WOBBUFFET) { Speed(20); Item(ITEM_MAGOST_BERRY); } + PLAYER(SPECIES_WYNAUT) { Speed(10); } + OPPONENT(SPECIES_SNEASEL) { Speed(40); Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PICKPOCKET); } + OPPONENT(SPECIES_SNEASEL) { Speed(30); Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PICKPOCKET); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_BREAKING_SWIPE); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!"); + } THEN { + EXPECT(opponentLeft->item == ITEM_MAGOST_BERRY); + EXPECT(opponentRight->item == ITEM_NONE); + EXPECT(playerLeft->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickpocket steals the attacker's item unless it already has one (Traits)") +{ + bool32 targetHasItem; + PARAMETRIZE { targetHasItem = FALSE; } + PARAMETRIZE { targetHasItem = TRUE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MAGOST_BERRY); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PICKPOCKET); Item(targetHasItem ? ITEM_EVIOLITE : ITEM_NONE); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + if (targetHasItem) { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!"); + } + } else { + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!"); + } + } THEN { + if (targetHasItem) { + EXPECT(opponent->item == ITEM_EVIOLITE); + EXPECT(player->item == ITEM_MAGOST_BERRY); + } else { + EXPECT(opponent->item == ITEM_MAGOST_BERRY); + EXPECT(player->item == ITEM_NONE); + } + } +} + +SINGLE_BATTLE_TEST("Pickpocket does not activate if the user faints (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MAGOST_BERRY); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PICKPOCKET); HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!"); + } + MESSAGE("The opposing Sneasel fainted!"); + } THEN { + EXPECT(opponent->item == ITEM_NONE); + EXPECT(player->item == ITEM_MAGOST_BERRY); + } +} + +SINGLE_BATTLE_TEST("Pickpocket cannot steal from Sticky Hold (Traits)") +{ + GIVEN { + PLAYER(SPECIES_GRIMER) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_STICKY_HOLD); Item(ITEM_MAGOST_BERRY); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PICKPOCKET); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + ABILITY_POPUP(player, ABILITY_STICKY_HOLD); + MESSAGE("Grimer's item cannot be removed!"); + } THEN { + EXPECT(opponent->item == ITEM_NONE); + EXPECT(player->item == ITEM_MAGOST_BERRY); + } +} + +SINGLE_BATTLE_TEST("Pickpocket cannot steal restricted held items (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_NORMALIUM_Z].holdEffect == HOLD_EFFECT_Z_CRYSTAL); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PICKPOCKET); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + } + } THEN { + EXPECT(opponent->item == ITEM_NONE); + EXPECT(player->item == ITEM_NORMALIUM_Z); + } +} + +SINGLE_BATTLE_TEST("Pickpocket activates after the final hit of a multi-strike move (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FURY_SWIPES) == EFFECT_MULTI_HIT); + ASSUME(MoveMakesContact(MOVE_FURY_SWIPES)); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MAGOST_BERRY); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PICKPOCKET); } + } WHEN { + TURN { MOVE(player, MOVE_FURY_SWIPES, WITH_RNG(RNG_HITS, 3)); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, player); + MESSAGE("The Pokémon was hit 3 time(s)!"); + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!"); + } THEN { + EXPECT(opponent->item == ITEM_MAGOST_BERRY); + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickpocket activates after Magician steals an item (Traits)") +{ + GIVEN { + PLAYER(SPECIES_DELPHOX) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_MAGICIAN); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PICKPOCKET); Item(ITEM_MAGOST_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(player, ABILITY_MAGICIAN); + MESSAGE("Delphox stole the opposing Sneasel's Magost Berry!"); + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Delphox's Magost Berry!"); + } THEN { + EXPECT(opponent->item == ITEM_MAGOST_BERRY); + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickpocket activates after Sticky Barb transfers (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_STICKY_BARB].holdEffect == HOLD_EFFECT_STICKY_BARB); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PICKPOCKET); Item(ITEM_STICKY_BARB); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("The Sticky Barb attached itself to Wobbuffet!"); + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Sticky Barb!"); + } THEN { + EXPECT(opponent->item == ITEM_STICKY_BARB); + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickpocket activates after Thief or Covet steals an item (Traits)") +{ + u16 move; + PARAMETRIZE { move = MOVE_THIEF; } + PARAMETRIZE { move = MOVE_COVET; } + GIVEN { + ASSUME(GetMoveEffect(move) == EFFECT_STEAL_ITEM); + ASSUME(MoveMakesContact(move)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PICKPOCKET); Item(ITEM_MAGOST_BERRY); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + MESSAGE("Wobbuffet stole the opposing Sneasel's Magost Berry!"); + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!"); + } THEN { + EXPECT(opponent->item == ITEM_MAGOST_BERRY); + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickpocket activates after Focus Sash is consumed (Traits)") +{ + GIVEN { + ASSUME(MoveMakesContact(MOVE_SEISMIC_TOSS)); + ASSUME(gItemsInfo[ITEM_FOCUS_SASH].holdEffect == HOLD_EFFECT_FOCUS_SASH); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MAGOST_BERRY); Level(100); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PICKPOCKET); Item(ITEM_FOCUS_SASH); MaxHP(6); HP(6); } + } WHEN { + TURN { MOVE(player, MOVE_SEISMIC_TOSS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SEISMIC_TOSS, player); + MESSAGE("The opposing Sneasel hung on using its Focus Sash!"); + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!"); + } THEN { + EXPECT(opponent->item == ITEM_MAGOST_BERRY); + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickpocket activates after Knock Off, Bug Bite, or Pluck (Traits)") +{ + u16 move; + PARAMETRIZE { move = MOVE_KNOCK_OFF; } + PARAMETRIZE { move = MOVE_BUG_BITE; } + PARAMETRIZE { move = MOVE_PLUCK; } + GIVEN { + ASSUME(MoveMakesContact(move)); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MAGOST_BERRY); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PICKPOCKET); Item(ITEM_ORAN_BERRY); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!"); + } THEN { + EXPECT(opponent->item == ITEM_MAGOST_BERRY); + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickpocket steals Life Orb after it activates (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LIFE_ORB].holdEffect == HOLD_EFFECT_LIFE_ORB); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LIFE_ORB); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PICKPOCKET); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet was hurt by the Life Orb!"); + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Life Orb!"); + } THEN { + EXPECT(opponent->item == ITEM_LIFE_ORB); + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickpocket steals Shell Bell after it heals the user (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_SHELL_BELL].holdEffect == HOLD_EFFECT_SHELL_BELL); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_SHELL_BELL); MaxHP(100); HP(66); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PICKPOCKET); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + HP_BAR(player); + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Shell Bell!"); + } THEN { + EXPECT(opponent->item == ITEM_SHELL_BELL); + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickpocket does not prevent King's Rock or Razor Fang flinches (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_KINGS_ROCK].holdEffect == HOLD_EFFECT_FLINCH); + PLAYER(SPECIES_WOBBUFFET) { Speed(20); Item(ITEM_KINGS_ROCK); } + OPPONENT(SPECIES_SNEASEL) { Speed(10); Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PICKPOCKET); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH, WITH_RNG(RNG_HOLD_EFFECT_FLINCH, 1)); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's King's Rock!"); + MESSAGE("The opposing Sneasel flinched and couldn't move!"); + } THEN { + EXPECT(opponent->item == ITEM_KINGS_ROCK); + EXPECT(player->item == ITEM_NONE); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +DOUBLE_BATTLE_TEST("Pickpocket checks contact/effect per target for spread moves (Multi)") +{ + GIVEN { + ASSUME(GetSpeciesType(SPECIES_CLEFAIRY, 0) == TYPE_FAIRY); + ASSUME(GetMoveType(MOVE_BREAKING_SWIPE) == TYPE_DRAGON); + ASSUME(GetMoveTarget(MOVE_BREAKING_SWIPE) == MOVE_TARGET_BOTH); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NUGGET, ITEM_MAGOST_BERRY); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); } + OPPONENT(SPECIES_CLEFAIRY); + } WHEN { + TURN { MOVE(playerLeft, MOVE_BREAKING_SWIPE); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!"); + } THEN { + EXPECT(opponentLeft->item == ITEM_MAGOST_BERRY); + EXPECT(playerLeft->item == ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Pickpocket activates for the fastest itemless target when both are hit by a contact spread move (Multi)") +{ + GIVEN { + ASSUME(GetMoveTarget(MOVE_BREAKING_SWIPE) == MOVE_TARGET_BOTH); + PLAYER(SPECIES_WOBBUFFET) { Speed(20); Items(ITEM_NUGGET, ITEM_MAGOST_BERRY); } + PLAYER(SPECIES_WYNAUT) { Speed(10); } + OPPONENT(SPECIES_SNEASEL) { Speed(40); Ability(ABILITY_PICKPOCKET); } + OPPONENT(SPECIES_SNEASEL) { Speed(30); Ability(ABILITY_PICKPOCKET); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_BREAKING_SWIPE); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!"); + } THEN { + EXPECT(opponentLeft->item == ITEM_MAGOST_BERRY); + EXPECT(opponentRight->item == ITEM_NONE); + EXPECT(playerLeft->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickpocket steals the attacker's item unless it already has one (Multi)") +{ + bool32 targetHasItem; + PARAMETRIZE { targetHasItem = FALSE; } + PARAMETRIZE { targetHasItem = TRUE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NUGGET, ITEM_MAGOST_BERRY); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); Items(ITEM_NUGGET, targetHasItem ? ITEM_EVIOLITE : ITEM_NONE); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + if (targetHasItem) { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!"); + } + } else { + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!"); + } + } THEN { + if (targetHasItem) { + EXPECT(opponent->item == ITEM_EVIOLITE); + EXPECT(player->item == ITEM_MAGOST_BERRY); + } else { + EXPECT(opponent->item == ITEM_MAGOST_BERRY); + EXPECT(player->item == ITEM_NONE); + } + } +} + +SINGLE_BATTLE_TEST("Pickpocket does not activate if the user faints (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NUGGET, ITEM_MAGOST_BERRY); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!"); + } + MESSAGE("The opposing Sneasel fainted!"); + } THEN { + EXPECT(opponent->item == ITEM_NONE); + EXPECT(player->item == ITEM_MAGOST_BERRY); + } +} + +SINGLE_BATTLE_TEST("Pickpocket cannot steal from Sticky Hold (Multi)") +{ + GIVEN { + PLAYER(SPECIES_GRIMER) { Ability(ABILITY_STICKY_HOLD); Items(ITEM_NUGGET, ITEM_MAGOST_BERRY); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + ABILITY_POPUP(player, ABILITY_STICKY_HOLD); + MESSAGE("Grimer's item cannot be removed!"); + } THEN { + EXPECT(opponent->item == ITEM_NONE); + EXPECT(player->item == ITEM_MAGOST_BERRY); + } +} + +SINGLE_BATTLE_TEST("Pickpocket cannot steal restricted held items (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_NORMALIUM_Z].holdEffect == HOLD_EFFECT_Z_CRYSTAL); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + } + } THEN { + EXPECT(opponent->item == ITEM_NONE); + EXPECT(player->item == ITEM_NORMALIUM_Z); + } +} + +SINGLE_BATTLE_TEST("Pickpocket activates after the final hit of a multi-strike move (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FURY_SWIPES) == EFFECT_MULTI_HIT); + ASSUME(MoveMakesContact(MOVE_FURY_SWIPES)); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NUGGET, ITEM_MAGOST_BERRY); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); } + } WHEN { + TURN { MOVE(player, MOVE_FURY_SWIPES, WITH_RNG(RNG_HITS, 3)); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, player); + MESSAGE("The Pokémon was hit 3 time(s)!"); + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!"); + } THEN { + EXPECT(opponent->item == ITEM_MAGOST_BERRY); + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickpocket activates after Magician steals an item (Multi)") +{ + GIVEN { + PLAYER(SPECIES_DELPHOX) { Ability(ABILITY_MAGICIAN); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); Items(ITEM_NUGGET, ITEM_MAGOST_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(player, ABILITY_MAGICIAN); + MESSAGE("Delphox stole the opposing Sneasel's Magost Berry!"); + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Delphox's Magost Berry!"); + } THEN { + EXPECT(opponent->item == ITEM_MAGOST_BERRY); + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickpocket activates after Sticky Barb transfers (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_STICKY_BARB].holdEffect == HOLD_EFFECT_STICKY_BARB); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); Items(ITEM_NUGGET, ITEM_STICKY_BARB); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("The Sticky Barb attached itself to Wobbuffet!"); + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Sticky Barb!"); + } THEN { + EXPECT(opponent->item == ITEM_STICKY_BARB); + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickpocket activates after Thief or Covet steals an item (Multi)") +{ + u16 move; + PARAMETRIZE { move = MOVE_THIEF; } + PARAMETRIZE { move = MOVE_COVET; } + GIVEN { + ASSUME(GetMoveEffect(move) == EFFECT_STEAL_ITEM); + ASSUME(MoveMakesContact(move)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); Items(ITEM_NUGGET, ITEM_MAGOST_BERRY); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + MESSAGE("Wobbuffet stole the opposing Sneasel's Magost Berry!"); + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!"); + } THEN { + EXPECT(opponent->item == ITEM_MAGOST_BERRY); + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickpocket activates after Focus Sash is consumed (Multi)") +{ + GIVEN { + ASSUME(MoveMakesContact(MOVE_SEISMIC_TOSS)); + ASSUME(gItemsInfo[ITEM_FOCUS_SASH].holdEffect == HOLD_EFFECT_FOCUS_SASH); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NUGGET, ITEM_MAGOST_BERRY); Level(100); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); Items(ITEM_NUGGET, ITEM_FOCUS_SASH); MaxHP(6); HP(6); } + } WHEN { + TURN { MOVE(player, MOVE_SEISMIC_TOSS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SEISMIC_TOSS, player); + MESSAGE("The opposing Sneasel hung on using its Focus Sash!"); + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!"); + } THEN { + EXPECT(opponent->item == ITEM_MAGOST_BERRY); + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickpocket activates after Knock Off, Bug Bite, or Pluck (Multi)") +{ + u16 move; + PARAMETRIZE { move = MOVE_KNOCK_OFF; } + PARAMETRIZE { move = MOVE_BUG_BITE; } + PARAMETRIZE { move = MOVE_PLUCK; } + GIVEN { + ASSUME(MoveMakesContact(move)); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NUGGET, ITEM_MAGOST_BERRY); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); Items(ITEM_NUGGET, ITEM_ORAN_BERRY); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Magost Berry!"); + } THEN { + EXPECT(opponent->item == ITEM_MAGOST_BERRY); + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickpocket steals Life Orb after it activates (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LIFE_ORB].holdEffect == HOLD_EFFECT_LIFE_ORB); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NUGGET, ITEM_LIFE_ORB); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet was hurt by the Life Orb!"); + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Life Orb!"); + } THEN { + EXPECT(opponent->item == ITEM_LIFE_ORB); + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickpocket steals Shell Bell after it heals the user (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_SHELL_BELL].holdEffect == HOLD_EFFECT_SHELL_BELL); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NUGGET, ITEM_SHELL_BELL); MaxHP(100); HP(66); } + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + HP_BAR(player); + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's Shell Bell!"); + } THEN { + EXPECT(opponent->item == ITEM_SHELL_BELL); + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickpocket does not prevent King's Rock or Razor Fang flinches (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_KINGS_ROCK].holdEffect == HOLD_EFFECT_FLINCH); + PLAYER(SPECIES_WOBBUFFET) { Speed(20); Items(ITEM_NUGGET, ITEM_KINGS_ROCK); } + OPPONENT(SPECIES_SNEASEL) { Speed(10); Ability(ABILITY_PICKPOCKET); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH, WITH_RNG(RNG_HOLD_EFFECT_FLINCH, 1)); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Wobbuffet's King's Rock!"); + MESSAGE("The opposing Sneasel flinched and couldn't move!"); + } THEN { + EXPECT(opponent->item == ITEM_KINGS_ROCK); + EXPECT(player->item == ITEM_NONE); + } +} +#endif \ No newline at end of file diff --git a/test/battle/ability/pickup.c b/test/battle/ability/pickup.c index bf7482336115..6b4a6f186d12 100644 --- a/test/battle/ability/pickup.c +++ b/test/battle/ability/pickup.c @@ -310,3 +310,616 @@ DOUBLE_BATTLE_TEST("Pickup doesn't trigger more than once per turn") EXPECT_LT(playerLeft->hp, playerLeft->maxHP/2); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Pickup grants an item used by another Pokémon (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } THEN { + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + } +} + +WILD_BATTLE_TEST("Pickup grants an item used by itself in wild battles (Gen9+) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_PICKUP_WILD, GEN_9); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_PICKUP); MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } THEN { + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant the user their item outside wild battles (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZIGZAGOON) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_PICKUP); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant another Pokémon's popped Air Balloon (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_AIR_BALLOON].holdEffect == HOLD_EFFECT_AIR_BALLOON); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_AIR_BALLOON); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Air Balloon!"); + } + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an item not used that turn (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an item after its holder faints (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_MEMENTO); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an used item if holder is replaced (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_PARTING_SHOT) == EFFECT_PARTING_SHOT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(300); HP(151); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(300); HP(151); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_PARTING_SHOT); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); MOVE(opponent, MOVE_PARTING_SHOT); SEND_OUT(opponent, 0); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, opponent); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an item if it destroyed the item with Incinerate (Traits)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_INCINERATE, MOVE_EFFECT_INCINERATE)); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_INCINERATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_INCINERATE, player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an item if it knocked off that item (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_KNOCK_OFF) == EFFECT_KNOCK_OFF); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an item if the user eats it with Bug Bite/Pluck (Traits)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_BUG_BITE, MOVE_EFFECT_BUG_BITE)); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_BUG_BITE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BUG_BITE, player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an used item if its user already restored it (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_RECYCLE) == EFFECT_RECYCLE); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_RECYCLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RECYCLE, opponent); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup restores an item that has been Flinged (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FLING) == EFFECT_FLING); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_FLING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, opponent); + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } THEN { + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Pickup restores an item that was used by Natural Gift (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_NATURAL_GIFT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, opponent); + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } THEN { + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + } +} + +DOUBLE_BATTLE_TEST("Pickup triggers based on Speed order (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Speed(1); Ability(ABILITY_RUN_AWAY); Innates(ABILITY_PICKUP); } + PLAYER(SPECIES_WOBBUFFET) { Speed(2); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(3); MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_ZIGZAGOON) { Speed(50); Ability(ABILITY_RUN_AWAY); Innates(ABILITY_PICKUP); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft); + ABILITY_POPUP(opponentRight, ABILITY_PICKUP); + NOT ABILITY_POPUP(playerLeft, ABILITY_PICKUP); + } THEN { + EXPECT_EQ(opponentRight->item, ITEM_SITRUS_BERRY); + EXPECT_EQ(playerLeft->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Pickup grants a random item used by another Pokémon (Traits)") +{ + PASSES_RANDOMLY(1, 3, RNG_PICKUP); + GIVEN { + ASSUME(gItemsInfo[ITEM_WHITE_HERB].holdEffect == HOLD_EFFECT_WHITE_HERB); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_PICKUP); } + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_WHITE_HERB); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_WHITE_HERB); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_BULLDOZE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLDOZE, playerLeft); + ABILITY_POPUP(playerLeft, ABILITY_PICKUP); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_SITRUS_BERRY); + } +} + +DOUBLE_BATTLE_TEST("Pickup doesn't trigger more than once per turn (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { HP(1); Ability(ABILITY_RUN_AWAY); Innates(ABILITY_PICKUP); } + PLAYER(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_BULLDOZE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLDOZE, playerLeft); + ABILITY_POPUP(playerLeft, ABILITY_PICKUP); + NOT ABILITY_POPUP(playerLeft, ABILITY_PICKUP); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_NONE); + EXPECT_GT(playerLeft->hp, 1); + EXPECT_LT(playerLeft->hp, playerLeft->maxHP/2); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Pickup grants an item used by another Pokémon (Multi)") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); Items(ITEM_PECHA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } THEN { + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + } +} + +WILD_BATTLE_TEST("Pickup grants an item used by itself in wild battles (Gen9+) (Multi)") +{ + GIVEN { + WITH_CONFIG(CONFIG_PICKUP_WILD, GEN_9); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); MaxHP(100); HP(51); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } THEN { + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant the user their item outside wild battles (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); MaxHP(500); HP(251); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant another Pokémon's popped Air Balloon (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_AIR_BALLOON].holdEffect == HOLD_EFFECT_AIR_BALLOON); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); Items(ITEM_PECHA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_AIR_BALLOON); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Air Balloon!"); + } + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an item not used that turn (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); Items(ITEM_PECHA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an item after its holder faints (Multi)") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); Items(ITEM_PECHA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_MEMENTO); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an used item if holder is replaced (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_PARTING_SHOT) == EFFECT_PARTING_SHOT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); Items(ITEM_PECHA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(300); HP(151); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(300); HP(151); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_PARTING_SHOT); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); MOVE(opponent, MOVE_PARTING_SHOT); SEND_OUT(opponent, 0); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, opponent); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an item if it destroyed the item with Incinerate (Multi)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_INCINERATE, MOVE_EFFECT_INCINERATE)); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); Items(ITEM_PECHA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_INCINERATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_INCINERATE, player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an item if it knocked off that item (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_KNOCK_OFF) == EFFECT_KNOCK_OFF); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); Items(ITEM_PECHA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an item if the user eats it with Bug Bite/Pluck (Multi)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_BUG_BITE, MOVE_EFFECT_BUG_BITE)); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); Items(ITEM_PECHA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_BUG_BITE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BUG_BITE, player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an used item if its user already restored it (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_RECYCLE) == EFFECT_RECYCLE); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); Items(ITEM_PECHA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_RECYCLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RECYCLE, opponent); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup restores an item that has been Flinged (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FLING) == EFFECT_FLING); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); Items(ITEM_PECHA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_FLING); } + TURN { MOVE(opponent, MOVE_FLING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, opponent); + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } THEN { + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Pickup restores an item that was used by Natural Gift (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); Items(ITEM_PECHA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_NATURAL_GIFT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, opponent); + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } THEN { + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + } +} + +DOUBLE_BATTLE_TEST("Pickup triggers based on Speed order (Multi)") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Speed(1); Ability(ABILITY_PICKUP); Items(ITEM_PECHA_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Speed(2); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(3); MaxHP(100); HP(51); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_ZIGZAGOON) { Speed(50); Ability(ABILITY_PICKUP); Items(ITEM_PECHA_BERRY); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft); + ABILITY_POPUP(opponentRight, ABILITY_PICKUP); + NOT ABILITY_POPUP(playerLeft, ABILITY_PICKUP); + } THEN { + EXPECT_EQ(opponentRight->item, ITEM_SITRUS_BERRY); + EXPECT_EQ(playerLeft->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Pickup grants a random item used by another Pokémon (Multi)") +{ + PASSES_RANDOMLY(1, 3, RNG_PICKUP); + GIVEN { + ASSUME(gItemsInfo[ITEM_WHITE_HERB].holdEffect == HOLD_EFFECT_WHITE_HERB); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); Items(ITEM_PECHA_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_WHITE_HERB); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_WHITE_HERB); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_BULLDOZE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLDOZE, playerLeft); + ABILITY_POPUP(playerLeft, ABILITY_PICKUP); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_SITRUS_BERRY); + } +} + +DOUBLE_BATTLE_TEST("Pickup doesn't trigger more than once per turn (Multi)") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { HP(1); Ability(ABILITY_PICKUP); Items(ITEM_PECHA_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Items(ITEM_GREAT_BALL, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_BULLDOZE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLDOZE, playerLeft); + ABILITY_POPUP(playerLeft, ABILITY_PICKUP); + NOT ABILITY_POPUP(playerLeft, ABILITY_PICKUP); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_NONE); + EXPECT_GT(playerLeft->hp, 1); + EXPECT_LT(playerLeft->hp, playerLeft->maxHP/2); + } +} +#endif diff --git a/test/battle/ability/pixilate.c b/test/battle/ability/pixilate.c index 58c6488903cb..4e1873a51371 100644 --- a/test/battle/ability/pixilate.c +++ b/test/battle/ability/pixilate.c @@ -238,3 +238,161 @@ SINGLE_BATTLE_TEST("Pixilate doesn't affect damaging Z-Move types") TO_DO_BATTLE_TEST("Pixilate doesn't affect Max Strike's type"); TO_DO_BATTLE_TEST("(DYNAMAX) Pixilate turns Max Strike into Max Starfall when not used by Gigantamax Alcremie"); TO_DO_BATTLE_TEST("(DYNAMAX) Pixilate doesn't turn Max Strike into Max Starfall when used by Gigantamax Alcremie, instead becoming G-Max Finale"); + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Pixilate turns a Normal-type move into a Fairy-type move (Traits)") +{ + GIVEN { + PLAYER(SPECIES_DRAGONITE); + OPPONENT(SPECIES_ALTARIA) { Ability(ABILITY_CLOUD_NINE); Innates(ABILITY_PIXILATE); Item(ITEM_ALTARIANITE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Pixilate boosts power of affected moves by 20% (Gen7+) or 30% (Gen1-6) (Traits)", s16 damage) +{ + enum Ability ability; + u32 genConfig; + PARAMETRIZE { ability = ABILITY_CUTE_CHARM; genConfig = GEN_7; } + PARAMETRIZE { ability = ABILITY_CUTE_CHARM; genConfig = GEN_6; } + PARAMETRIZE { ability = ABILITY_PIXILATE; genConfig = GEN_7; } + PARAMETRIZE { ability = ABILITY_PIXILATE; genConfig = GEN_6; } + + GIVEN { + WITH_CONFIG(CONFIG_ATE_MULTIPLIER, genConfig); + PLAYER(SPECIES_SYLVEON) { Ability(ABILITY_TECHNICIAN); Innates(ability); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + if (genConfig >= GEN_7) + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.8), results[2].damage); // STAB + ate + else + EXPECT_MUL_EQ(results[1].damage, Q_4_12(1.95), results[3].damage); // STAB + ate + } +} + +SINGLE_BATTLE_TEST("Pixilate doesn't affect Weather Ball's type (Traits)", s16 damage) +{ + u16 move; + enum Ability ability; + PARAMETRIZE { move = MOVE_CELEBRATE; ability = ABILITY_STURDY; } + PARAMETRIZE { move = MOVE_SUNNY_DAY; ability = ABILITY_STURDY; } + PARAMETRIZE { move = MOVE_CELEBRATE; ability = ABILITY_PIXILATE; } + PARAMETRIZE { move = MOVE_SUNNY_DAY; ability = ABILITY_PIXILATE; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_WEATHER_BALL) == EFFECT_WEATHER_BALL); + ASSUME(GetMoveType(MOVE_WEATHER_BALL) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_PINSIR, 0) == TYPE_BUG); + PLAYER(SPECIES_SYLVEON) { Level(5); Ability(ABILITY_TECHNICIAN); Innates(ability); } + OPPONENT(SPECIES_PINSIR); + } WHEN { + TURN { MOVE(player, move); } + TURN { MOVE(player, MOVE_WEATHER_BALL); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + if (move == MOVE_SUNNY_DAY) + MESSAGE("It's super effective!"); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(6.0), results[1].damage); // double base power + type effectiveness + sun 50% boost + EXPECT_MUL_EQ(results[2].damage, Q_4_12(6.0), results[3].damage); + EXPECT_EQ(results[0].damage, results[2].damage); + EXPECT_EQ(results[1].damage, results[3].damage); + } +} + +SINGLE_BATTLE_TEST("Pixilate doesn't affect Natural Gift's type (Traits)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_CUTE_CHARM; } + PARAMETRIZE { ability = ABILITY_PIXILATE; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + ASSUME(gNaturalGiftTable[ITEM_TO_BERRY(ITEM_ORAN_BERRY)].type == TYPE_POISON); + ASSUME(GetSpeciesType(SPECIES_BELDUM, 0) == TYPE_STEEL); + PLAYER(SPECIES_SYLVEON) { Ability(ABILITY_TECHNICIAN); Innates(ability); Item(ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_BELDUM); + } WHEN { + TURN { MOVE(player, MOVE_NATURAL_GIFT); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player); } + MESSAGE("It doesn't affect the opposing Beldum…"); + } +} + +SINGLE_BATTLE_TEST("Pixilate doesn't affect Judgment / Techno Blast / Multi-Attack's type (Traits)") +{ + u16 move, item; + PARAMETRIZE { move = MOVE_JUDGMENT; item = ITEM_ZAP_PLATE; } + PARAMETRIZE { move = MOVE_TECHNO_BLAST; item = ITEM_SHOCK_DRIVE; } + PARAMETRIZE { move = MOVE_MULTI_ATTACK; item = ITEM_ELECTRIC_MEMORY; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_JUDGMENT) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetMoveEffect(MOVE_TECHNO_BLAST) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetMoveEffect(MOVE_MULTI_ATTACK) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(gItemsInfo[ITEM_ZAP_PLATE].holdEffect == HOLD_EFFECT_PLATE); + ASSUME(gItemsInfo[ITEM_ZAP_PLATE].secondaryId == TYPE_ELECTRIC); + ASSUME(gItemsInfo[ITEM_SHOCK_DRIVE].holdEffect == HOLD_EFFECT_DRIVE); + ASSUME(gItemsInfo[ITEM_SHOCK_DRIVE].secondaryId == TYPE_ELECTRIC); + ASSUME(gItemsInfo[ITEM_ELECTRIC_MEMORY].holdEffect == HOLD_EFFECT_MEMORY); + ASSUME(gItemsInfo[ITEM_ELECTRIC_MEMORY].secondaryId == TYPE_ELECTRIC); + ASSUME(GetSpeciesType(SPECIES_DIGLETT, 0) == TYPE_GROUND); + PLAYER(SPECIES_SYLVEON) { Ability(ABILITY_TECHNICIAN); Innates(ABILITY_PIXILATE); Item(item); } + OPPONENT(SPECIES_DIGLETT); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, move, player); } + MESSAGE("It doesn't affect the opposing Diglett…"); + } +} + +SINGLE_BATTLE_TEST("Pixilate doesn't affect Hidden Power's type (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_HIDDEN_POWER) == EFFECT_HIDDEN_POWER); + ASSUME(gTypesInfo[TYPE_ELECTRIC].isHiddenPowerType == TRUE); + ASSUME(GetSpeciesType(SPECIES_DIGLETT, 0) == TYPE_GROUND); + PLAYER(SPECIES_SYLVEON) { Ability(ABILITY_TECHNICIAN); Innates(ABILITY_PIXILATE); HPIV(31); AttackIV(31); DefenseIV(31); SpAttackIV(30); SpDefenseIV(31); SpeedIV(31); } // HP Electric + OPPONENT(SPECIES_DIGLETT); + } WHEN { + TURN { MOVE(player, MOVE_HIDDEN_POWER); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_HIDDEN_POWER, player); } + MESSAGE("It doesn't affect the opposing Diglett…"); + } +} + +TO_DO_BATTLE_TEST("Pixilate doesn't override Electrify (Gen7+) (Traits)"); +TO_DO_BATTLE_TEST("Pixilate doesn't override Ion Deluge (Gen7+) (Traits)"); // Ion Deluge doesn't exist in Gen 8+, but we probably could assume it behaves similar to under Electrify. TODO: Test by hacking SV. +TO_DO_BATTLE_TEST("Pixilate overrides Electrify (Gen6) (Traits)") +TO_DO_BATTLE_TEST("Pixilate overrides Ion Deluge (Gen6) (Traits)") +TO_DO_BATTLE_TEST("Pixilate doesn't affect Tera Starstorm's type (Traits)"); +TO_DO_BATTLE_TEST("Pixilate doesn't affect Max Strike's type (Traits)"); +TO_DO_BATTLE_TEST("Pixilate doesn't affect Terrain Pulse's type (Traits)"); +TO_DO_BATTLE_TEST("Pixilate doesn't affect damaging Z-Move types (Traits)"); +TO_DO_BATTLE_TEST("(DYNAMAX) Pixilate turns Max Strike into Max Starfall when not used by Gigantamax Alcremie (Traits)"); +TO_DO_BATTLE_TEST("(DYNAMAX) Pixilate doesn't turn Max Strike into Max Starfall when used by Gigantamax Alcremie, instead becoming G-Max Finale (Traits)"); +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Pixilate turns a Normal-type move into a Fairy-type move (Multi)") +{ + GIVEN { + PLAYER(SPECIES_DRAGONITE); + OPPONENT(SPECIES_ALTARIA) { Items(ITEM_PECHA_BERRY, ITEM_ALTARIANITE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH, gimmick: GIMMICK_MEGA); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + MESSAGE("It's super effective!"); + } +} +#endif diff --git a/test/battle/ability/poison_heal.c b/test/battle/ability/poison_heal.c index 9d7acc38a811..4d7d9fef16ba 100644 --- a/test/battle/ability/poison_heal.c +++ b/test/battle/ability/poison_heal.c @@ -75,3 +75,99 @@ SINGLE_BATTLE_TEST("Poison Heal activates before Toxic Orb") } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Poison Heal heals from (Toxic) Poison damage (Traits)") +{ + u8 status; + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + + GIVEN { + PLAYER(SPECIES_SHROOMISH) { Ability(ABILITY_QUICK_FEET); Innates(ABILITY_POISON_HEAL); Status1(status); HP(1), MaxHP(400); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_POISON_HEAL); + MESSAGE("The poisoning healed Shroomish a little bit!"); + HP_BAR(player, damage: -50); + } +} + +SINGLE_BATTLE_TEST("Poison Heal heals from Toxic Poison damage are constant (Traits)") +{ + s16 turnOneHit; + s16 turnTwoHit; + + GIVEN { + PLAYER(SPECIES_SHROOMISH) { Ability(ABILITY_QUICK_FEET); Innates(ABILITY_POISON_HEAL); Status1(STATUS1_TOXIC_POISON); HP(1), MaxHP(400); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { } + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_POISON_HEAL); + MESSAGE("The poisoning healed Shroomish a little bit!"); + HP_BAR(player, captureDamage: &turnOneHit); + + ABILITY_POPUP(player, ABILITY_POISON_HEAL); + MESSAGE("The poisoning healed Shroomish a little bit!"); + HP_BAR(player, captureDamage: &turnTwoHit); + } THEN { + EXPECT_EQ(turnOneHit, turnTwoHit); + } +} + +SINGLE_BATTLE_TEST("Poison Heal does not heal or cause damage when under Heal Block (Traits)") +{ + GIVEN { + PLAYER(SPECIES_SHROOMISH) { Ability(ABILITY_QUICK_FEET); Innates(ABILITY_POISON_HEAL); Status1(STATUS1_POISON); HP(1), MaxHP(400); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_HEAL_BLOCK); } + } SCENE { + NONE_OF { + ABILITY_POPUP(player, ABILITY_POISON_HEAL); + MESSAGE("The poisoning healed Shroomish a little bit!"); + HP_BAR(player, damage: -50); + } + } +} + +SINGLE_BATTLE_TEST("Poison Heal activates before Toxic Orb (Traits)") +{ + GIVEN { + PLAYER(SPECIES_SHROOMISH) { Ability(ABILITY_QUICK_FEET); Innates(ABILITY_POISON_HEAL); Item(ITEM_TOXIC_ORB); HP(1), MaxHP(400); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); } + } SCENE { + NONE_OF { + ABILITY_POPUP(player, ABILITY_POISON_HEAL); + MESSAGE("The poisoning healed Shroomish a little bit!"); + HP_BAR(player, damage: -50); + HP_BAR(player, damage: 50); + } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Poison Heal activates before Toxic Orb (Multi)") +{ + GIVEN { + PLAYER(SPECIES_SHROOMISH) { Ability(ABILITY_POISON_HEAL); Items(ITEM_WHITE_HERB, ITEM_TOXIC_ORB); HP(1), MaxHP(400); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); } + } SCENE { + NONE_OF { + ABILITY_POPUP(player, ABILITY_POISON_HEAL); + MESSAGE("The poisoning healed Shroomish a little bit!"); + HP_BAR(player, damage: -50); + HP_BAR(player, damage: 50); + } + } +} +#endif diff --git a/test/battle/ability/poison_point.c b/test/battle/ability/poison_point.c index 2b6c4aa19f19..82b3a8fd6edf 100644 --- a/test/battle/ability/poison_point.c +++ b/test/battle/ability/poison_point.c @@ -71,3 +71,73 @@ SINGLE_BATTLE_TEST("Poison Point will not poison Poison-Type targets with corros } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Poison Point inflicts poison on contact (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_SWIFT; } + GIVEN { + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + ASSUME(!MoveMakesContact(MOVE_SWIFT)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_NIDORAN_M) { Ability(ABILITY_RIVALRY); Innates(ABILITY_POISON_POINT); } + } WHEN { + TURN { MOVE(player, move); } + TURN {} + } SCENE { + if (MoveMakesContact(move)) { + ABILITY_POPUP(opponent, ABILITY_POISON_POINT); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + MESSAGE("Wobbuffet was poisoned by the opposing Nidoran♂'s Poison Point!"); + STATUS_ICON(player, poison: TRUE); + } else { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_POISON_POINT); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + MESSAGE("Wobbuffet was poisoned by the opposing Nidoran♂'s Poison Point!"); + STATUS_ICON(player, poison: TRUE); + } + } + } +} + +SINGLE_BATTLE_TEST("Poison Point triggers 30% of the time (Traits)") +{ + PASSES_RANDOMLY(3, 10, RNG_POISON_POINT); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_4); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_NIDORAN_M) { Ability(ABILITY_RIVALRY); Innates(ABILITY_POISON_POINT); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_POISON_POINT); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + MESSAGE("Wobbuffet was poisoned by the opposing Nidoran♂'s Poison Point!"); + STATUS_ICON(player, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Poison Point will not poison Poison-Type targets with corrosion (Traits)") +{ + GIVEN { + ASSUME(MoveMakesContact(MOVE_TACKLE)); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); } + OPPONENT(SPECIES_NIDORAN_M) { Ability(ABILITY_RIVALRY); Innates(ABILITY_POISON_POINT); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + TURN {} + } SCENE { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_POISON_POINT); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + MESSAGE("Salandit was poisoned by the opposing Nidoran♂'s Poison Point!"); + STATUS_ICON(player, poison: TRUE); + } + } +} +#endif diff --git a/test/battle/ability/poison_puppeteer.c b/test/battle/ability/poison_puppeteer.c index 6956271236b5..a886603fb805 100644 --- a/test/battle/ability/poison_puppeteer.c +++ b/test/battle/ability/poison_puppeteer.c @@ -67,3 +67,72 @@ SINGLE_BATTLE_TEST("Poison Puppeteer does not trigger if poison is Toxic Spikes } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Poison Puppeteer confuses target if it was poisoned by a damaging move (Traits)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_POISON_STING, MOVE_EFFECT_POISON) == TRUE); + PLAYER(SPECIES_PECHARUNT) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_POISON_PUPPETEER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_POISON_STING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_STING, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, poison: TRUE); + ABILITY_POPUP(player, ABILITY_POISON_PUPPETEER); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, opponent); + MESSAGE("The opposing Wobbuffet became confused!"); + } +} + +SINGLE_BATTLE_TEST("Poison Puppeteer confuses target if it was (badly) poisoned by a status move (Traits)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_POISON_POWDER; } + PARAMETRIZE { move = MOVE_TOXIC; } + + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_POISON_STING, MOVE_EFFECT_POISON) == TRUE); + PLAYER(SPECIES_PECHARUNT) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_POISON_PUPPETEER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + if (move == MOVE_POISON_POWDER) + STATUS_ICON(opponent, poison: TRUE); + else + STATUS_ICON(opponent, badPoison: TRUE); + ABILITY_POPUP(player, ABILITY_POISON_PUPPETEER); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, opponent); + MESSAGE("The opposing Wobbuffet became confused!"); + } +} + +SINGLE_BATTLE_TEST("Poison Puppeteer does not trigger if poison is Toxic Spikes induced (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOXIC_SPIKES) == EFFECT_TOXIC_SPIKES); + PLAYER(SPECIES_PECHARUNT) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_POISON_PUPPETEER); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TOXIC_SPIKES);} + TURN { SWITCH(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, poison: TRUE); + NONE_OF { + ABILITY_POPUP(player, ABILITY_POISON_PUPPETEER); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, opponent); + MESSAGE("The opposing Wobbuffet became confused!"); + } + } +} +#endif diff --git a/test/battle/ability/poison_touch.c b/test/battle/ability/poison_touch.c index e3775d74274e..72d1c369ee9c 100644 --- a/test/battle/ability/poison_touch.c +++ b/test/battle/ability/poison_touch.c @@ -75,3 +75,107 @@ SINGLE_BATTLE_TEST("Poison Touch applies between multi-hit move hits") STATUS_ICON(opponent, poison: TRUE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Poison Touch has a 30% chance to poison when attacking with contact moves (Traits)") +{ + PASSES_RANDOMLY(3, 10, RNG_POISON_TOUCH); + GIVEN { + ASSUME(GetMovePower(MOVE_SCRATCH) > 0); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_GRIMER) { Ability(ABILITY_STICKY_HOLD); Innates(ABILITY_POISON_TOUCH); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("The opposing Wobbuffet was poisoned by Grimer's Poison Touch!"); + STATUS_ICON(opponent, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Poison Touch only applies when using contact moves (Traits)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_SWIFT; } + GIVEN { + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + ASSUME(!MoveMakesContact(MOVE_SWIFT)); + PLAYER(SPECIES_GRIMER) { Ability(ABILITY_STICKY_HOLD); Innates(ABILITY_POISON_TOUCH); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + if (MoveMakesContact(move)) { + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("The opposing Wobbuffet was poisoned by Grimer's Poison Touch!"); + STATUS_ICON(opponent, poison: TRUE); + } else { + NONE_OF { + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("The opposing Wobbuffet was poisoned by Grimer's Poison Touch!"); + STATUS_ICON(opponent, poison: TRUE); + } + } + } +} + +SINGLE_BATTLE_TEST("Poison Touch applies between multi-hit move hits (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ARM_THRUST) == EFFECT_MULTI_HIT); + ASSUME(MoveMakesContact(MOVE_ARM_THRUST)); + ASSUME(gItemsInfo[ITEM_PECHA_BERRY].holdEffect == HOLD_EFFECT_CURE_PSN); + PLAYER(SPECIES_GRIMER) { Ability(ABILITY_STICKY_HOLD); Innates(ABILITY_POISON_TOUCH); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_PECHA_BERRY); }; + } WHEN { + TURN { MOVE(player, MOVE_ARM_THRUST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ARM_THRUST, player); + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("The opposing Wobbuffet was poisoned by Grimer's Poison Touch!"); + STATUS_ICON(opponent, poison: TRUE); + MESSAGE("The opposing Wobbuffet's Pecha Berry cured its poison!"); + STATUS_ICON(opponent, poison: FALSE); + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("The opposing Wobbuffet was poisoned by Grimer's Poison Touch!"); + STATUS_ICON(opponent, poison: TRUE); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Poison Touch applies between multi-hit move hits (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ARM_THRUST) == EFFECT_MULTI_HIT); + ASSUME(MoveMakesContact(MOVE_ARM_THRUST)); + ASSUME(gItemsInfo[ITEM_PECHA_BERRY].holdEffect == HOLD_EFFECT_CURE_PSN); + PLAYER(SPECIES_GRIMER) { Ability(ABILITY_POISON_TOUCH); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_PECHA_BERRY); }; + } WHEN { + TURN { MOVE(player, MOVE_ARM_THRUST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ARM_THRUST, player); + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("The opposing Wobbuffet was poisoned by Grimer's Poison Touch!"); + STATUS_ICON(opponent, poison: TRUE); + MESSAGE("The opposing Wobbuffet's Pecha Berry cured its poison!"); + STATUS_ICON(opponent, poison: FALSE); + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("The opposing Wobbuffet was poisoned by Grimer's Poison Touch!"); + STATUS_ICON(opponent, poison: TRUE); + } +} +#endif diff --git a/test/battle/ability/power_construct.c b/test/battle/ability/power_construct.c index 07d52c17678e..fc67a0ca8faf 100644 --- a/test/battle/ability/power_construct.c +++ b/test/battle/ability/power_construct.c @@ -55,3 +55,58 @@ WILD_BATTLE_TEST("Power Construct Zygarde reverts to its original form upon catc EXPECT_EQ(GetMonData(&gPlayerParty[1], MON_DATA_SPECIES), baseSpecies); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Power Construct switches Zygarde's form when HP is below half (Traits)") +{ + u16 baseSpecies; + PARAMETRIZE { baseSpecies = SPECIES_ZYGARDE_10_POWER_CONSTRUCT; } + PARAMETRIZE { baseSpecies = SPECIES_ZYGARDE_50_POWER_CONSTRUCT; } + + GIVEN { + PLAYER(baseSpecies) + { + Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_POWER_CONSTRUCT); + HP((GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP) / 2) + 1); + } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + MESSAGE("You sense the presence of many!"); + ABILITY_POPUP(player, ABILITY_POWER_CONSTRUCT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_POWER_CONSTRUCT, player); + } THEN { + EXPECT_EQ(player->species, SPECIES_ZYGARDE_COMPLETE); + } +} + +WILD_BATTLE_TEST("Power Construct Zygarde reverts to its original form upon catching (Traits)") +{ + u16 baseSpecies; + PARAMETRIZE { baseSpecies = SPECIES_ZYGARDE_10_POWER_CONSTRUCT; } + PARAMETRIZE { baseSpecies = SPECIES_ZYGARDE_50_POWER_CONSTRUCT; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(baseSpecies) + { + Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_POWER_CONSTRUCT); + HP((GetMonData(&OPPONENT_PARTY[0], MON_DATA_MAX_HP) / 2) + 1); + } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { USE_ITEM(player, ITEM_MASTER_BALL); } + } SCENE { + // Turn 1 + MESSAGE("You sense the presence of many!"); + ABILITY_POPUP(opponent, ABILITY_POWER_CONSTRUCT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_POWER_CONSTRUCT, opponent); + + // Turn 2 + ANIMATION(ANIM_TYPE_SPECIAL, B_ANIM_BALL_THROW, player); + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[1], MON_DATA_SPECIES), baseSpecies); + } +} +#endif diff --git a/test/battle/ability/prankster.c b/test/battle/ability/prankster.c index 0f1fcd093048..0b2ebf660819 100644 --- a/test/battle/ability/prankster.c +++ b/test/battle/ability/prankster.c @@ -253,3 +253,252 @@ SINGLE_BATTLE_TEST("Prankster-affected moves that are bounced back by Magic Boun TO_DO_BATTLE_TEST("Prankster-affected moves called via Nature Power don't affect Dark-type Pokémon"); TO_DO_BATTLE_TEST("Prankster increases the priority of status Z-Moves by 1"); TO_DO_BATTLE_TEST("Prankster increases the priority of Extreme Evoboost by 1"); + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Prankster-affected moves don't affect Dark-type Pokémon (Gen7+) (Traits)") +{ + u32 gen; + PARAMETRIZE { gen = GEN_6; } + PARAMETRIZE { gen = GEN_7; } + GIVEN { + WITH_CONFIG(CONFIG_PRANKSTER_DARK_TYPES, gen); + PLAYER(SPECIES_UMBREON); + OPPONENT(SPECIES_VOLBEAT) { Ability(ABILITY_ILLUMINATE); Innates(ABILITY_PRANKSTER); } + } WHEN { + TURN { MOVE(opponent, MOVE_CONFUSE_RAY); } + } SCENE { + if (gen == GEN_6) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, opponent); + } else { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, opponent); + MESSAGE("It doesn't affect Umbreon…"); + } + } +} + +SINGLE_BATTLE_TEST("Prankster-affected moves don't affect Dark-type Pokémon after they switch-in (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_PRANKSTER_DARK_TYPES, GEN_7); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_UMBREON); + OPPONENT(SPECIES_VOLBEAT) { Ability(ABILITY_ILLUMINATE); Innates(ABILITY_PRANKSTER); } + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_CONFUSE_RAY); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, opponent); + MESSAGE("It doesn't affect Umbreon…"); + } +} + +DOUBLE_BATTLE_TEST("Prankster-affected moves affect Ally Dark-type Pokémon (Traits)") +{ + GIVEN { + PLAYER(SPECIES_VOLBEAT) { Ability(ABILITY_ILLUMINATE); Innates(ABILITY_PRANKSTER); } + PLAYER(SPECIES_UMBREON); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_CONFUSE_RAY, target: playerRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, playerLeft); + NOT MESSAGE("It doesn't affect Umbreon…"); + } +} + +SINGLE_BATTLE_TEST("Prankster-affected moves called via Assist don't affect Dark-type Pokémon (Gen 7+) (Traits)") +{ + u32 gen; + PARAMETRIZE { gen = GEN_6; } + PARAMETRIZE { gen = GEN_7; } + GIVEN { + WITH_CONFIG(CONFIG_PRANKSTER_DARK_TYPES, gen); + PLAYER(SPECIES_UMBREON); + OPPONENT(SPECIES_VOLBEAT) { Ability(ABILITY_ILLUMINATE); Innates(ABILITY_PRANKSTER); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CONFUSE_RAY); }; + } WHEN { + TURN { MOVE(opponent, MOVE_ASSIST); } + } SCENE { + if (gen == GEN_6) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, opponent); + } else { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, opponent); + MESSAGE("It doesn't affect Umbreon…"); + } + } +} + +// Tested on Showdown, even though Bulbapedia says otherwise. +DOUBLE_BATTLE_TEST("Prankster-affected moves called via Instruct do not affect Dark-type Pokémon (Traits)") +{ + u32 gen; + PARAMETRIZE { gen = GEN_6; } + PARAMETRIZE { gen = GEN_7; } + GIVEN { + WITH_CONFIG(CONFIG_PRANKSTER_DARK_TYPES, gen); + PLAYER(SPECIES_VOLBEAT) { Speed(20); Ability(ABILITY_ILLUMINATE); Innates(ABILITY_PRANKSTER); } + PLAYER(SPECIES_WOBBUFFET) { Speed(10);} + OPPONENT(SPECIES_UMBREON) { Speed(15); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(1); } + OPPONENT(SPECIES_UMBREON) { Speed(1); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_CONFUSE_RAY, target: opponentLeft); + MOVE(opponentLeft, MOVE_U_TURN, target: playerRight, WITH_RNG(RNG_CONFUSION, FALSE)); + SEND_OUT(opponentLeft, 2); + MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft); + } + } SCENE { + if (gen == GEN_6) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, playerLeft); + } else { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, playerLeft); + MESSAGE("It doesn't affect the opposing Umbreon…"); + } + MESSAGE("Wobbuffet used Instruct!"); + MESSAGE("Volbeat used Confuse Ray!"); + if (gen == GEN_6) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, playerLeft); + } else { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, playerLeft); + MESSAGE("It doesn't affect the opposing Umbreon…"); + } + } +} + +SINGLE_BATTLE_TEST("Prankster increases the priority of moves by 1 (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(10); } + OPPONENT(SPECIES_VOLBEAT) { Speed(5); Ability(ABILITY_ILLUMINATE); Innates(ABILITY_PRANKSTER); } + } WHEN { + TURN { MOVE(opponent, MOVE_CONFUSE_RAY); MOVE(player, MOVE_CELEBRATE, WITH_RNG(RNG_CONFUSION, FALSE)); } // RNG_CONFUSION so that Wobb doesn't hit itself. + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } +} + +DOUBLE_BATTLE_TEST("Moves called via Prankster-affected After you affect Dark-type Pokémon (Traits)") +{ + GIVEN { + PLAYER(SPECIES_VOLBEAT) { Speed(1); Ability(ABILITY_ILLUMINATE); Innates(ABILITY_PRANKSTER); } + PLAYER(SPECIES_WOBBUFFET) { Speed(1);} + OPPONENT(SPECIES_UMBREON) { Speed(10); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_AFTER_YOU, target: playerRight); + MOVE(playerRight, MOVE_CONFUSE_RAY, target: opponentLeft); + } + } SCENE { + MESSAGE("Volbeat used After You!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_AFTER_YOU, playerLeft); + MESSAGE("Wobbuffet used Confuse Ray!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, playerRight); + MESSAGE("The opposing Umbreon became confused!"); + } +} + +SINGLE_BATTLE_TEST("Prankster is blocked by Quick Guard in Gen5+ (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_VOLBEAT) { Ability(ABILITY_ILLUMINATE); Innates(ABILITY_PRANKSTER); } + } WHEN { + TURN { MOVE(player, MOVE_QUICK_GUARD); MOVE(opponent, MOVE_CONFUSE_RAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_GUARD, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, opponent); + MESSAGE("Wobbuffet protected itself!"); + } +} + +DOUBLE_BATTLE_TEST("Prankster-affected moves that target all Pokémon are successful regardless of the presence of Dark-type Pokémon (Traits)") +{ + GIVEN { + ASSUME(GetMoveTarget(MOVE_CAPTIVATE) == MOVE_TARGET_BOTH); + PLAYER(SPECIES_ILLUMISE) { Ability(ABILITY_ILLUMINATE); Innates(ABILITY_PRANKSTER); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_UMBREON); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_CAPTIVATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CAPTIVATE, playerLeft); + } +} + +SINGLE_BATTLE_TEST("Prankster-affected moves can still be bounced back by Dark-types using Magic Coat (Traits)") +{ + GIVEN { + PLAYER(SPECIES_UMBREON); + OPPONENT(SPECIES_VOLBEAT) { Ability(ABILITY_ILLUMINATE); Innates(ABILITY_PRANKSTER); } + } WHEN { + TURN { MOVE(player, MOVE_MAGIC_COAT); MOVE(opponent, MOVE_CONFUSE_RAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MAGIC_COAT, player); + MESSAGE("The opposing Volbeat used Confuse Ray!"); + MESSAGE("Umbreon bounced the Confuse Ray back!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, player); + MESSAGE("The opposing Volbeat became confused!"); + } +} + +SINGLE_BATTLE_TEST("Prankster-affected moves which are reflected by Magic Coat can affect Dark-type Pokémon, unless the Pokémon that bounced the move also has Prankster (Traits)") +{ + enum Ability sableyeAbility; + + PARAMETRIZE { sableyeAbility = ABILITY_PRANKSTER; } + PARAMETRIZE { sableyeAbility = ABILITY_KEEN_EYE; } + + GIVEN { + WITH_CONFIG(CONFIG_PRANKSTER_DARK_TYPES, GEN_7); + PLAYER(SPECIES_SABLEYE) { Ability(ABILITY_ILLUMINATE); Innates(sableyeAbility); } + OPPONENT(SPECIES_MURKROW) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PRANKSTER); } + } WHEN { + TURN { MOVE(player, MOVE_MAGIC_COAT); MOVE(opponent, MOVE_CONFUSE_RAY); } + } SCENE { + MESSAGE("Sableye used Magic Coat!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MAGIC_COAT, player); + MESSAGE("The opposing Murkrow used Confuse Ray!"); + MESSAGE("Sableye bounced the Confuse Ray back!"); + if (sableyeAbility == ABILITY_PRANKSTER) { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, player); + MESSAGE("It doesn't affect the opposing Murkrow…"); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, player); + MESSAGE("The opposing Murkrow became confused!"); + } + } +} + +SINGLE_BATTLE_TEST("Prankster-affected moves can still be bounced back by a Dark-type with Magic Bounce (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ABSOL) { Item(ITEM_ABSOLITE); } + OPPONENT(SPECIES_VOLBEAT) { Ability(ABILITY_ILLUMINATE); Innates(ABILITY_PRANKSTER); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); MOVE(opponent, MOVE_CONFUSE_RAY); } + } SCENE { + MESSAGE("The opposing Volbeat's Confuse Ray was bounced back by Absol's Magic Bounce!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, player); + } +} + +SINGLE_BATTLE_TEST("Prankster-affected moves that are bounced back by Magic Bounce can affect Dark-type Pokémon (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ABSOL) { Item(ITEM_ABSOLITE); } + OPPONENT(SPECIES_MURKROW) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PRANKSTER); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); MOVE(opponent, MOVE_CONFUSE_RAY); } + } SCENE { + MESSAGE("The opposing Murkrow's Confuse Ray was bounced back by Absol's Magic Bounce!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, player); + MESSAGE("The opposing Murkrow became confused!"); + } +} + +TO_DO_BATTLE_TEST("Prankster-affected moves called via Nature Power don't affect Dark-type Pokémon (Traits)"); +TO_DO_BATTLE_TEST("Prankster increases the priority of status Z-Moves by 1 (Traits)"); +TO_DO_BATTLE_TEST("Prankster increases the priority of Extreme Evoboost by 1 (Traits)"); +#endif diff --git a/test/battle/ability/pressure.c b/test/battle/ability/pressure.c index db92d198b7a4..873e725bcebf 100644 --- a/test/battle/ability/pressure.c +++ b/test/battle/ability/pressure.c @@ -68,3 +68,73 @@ SINGLE_BATTLE_TEST("Pressure's effect doesn't apply to Sticky Web") EXPECT_EQ(player->pp[0], 19); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Pressure causes opponent's moves to use up 1 additional PP (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { MovesWithPP({MOVE_POUND, 35}); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_PRESSURE); } + } WHEN { + TURN { MOVE(player, MOVE_POUND); } + } THEN { + EXPECT_EQ(player->pp[0], 33); + } +} + +DOUBLE_BATTLE_TEST("Pressure's effect stacks with multiple Pokémon (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { MovesWithPP({MOVE_SWIFT, 20}); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_PRESSURE); } + OPPONENT(SPECIES_WYNAUT) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_PRESSURE); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SWIFT); } + } THEN { + EXPECT_EQ(playerLeft->pp[0], 17); + } +} + +SINGLE_BATTLE_TEST("Pressure's effect applies to Imprison and Snatch (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { MovesWithPP({MOVE_IMPRISON, 10}, {MOVE_SNATCH, 10}); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_PRESSURE); } + } WHEN { + TURN { MOVE(player, MOVE_IMPRISON); } + TURN { MOVE(player, MOVE_SNATCH); } + } THEN { + EXPECT_EQ(player->pp[0], 8); + EXPECT_EQ(player->pp[1], 8); + } +} + +SINGLE_BATTLE_TEST("Pressure's effect applies to Spikes, Stealth Rock and Toxic Spikes (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { MovesWithPP({MOVE_SPIKES, 20}, {MOVE_STEALTH_ROCK, 20}, {MOVE_TOXIC_SPIKES, 20}); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_PRESSURE); } + } WHEN { + TURN { MOVE(player, MOVE_SPIKES); } + TURN { MOVE(player, MOVE_STEALTH_ROCK); } + TURN { MOVE(player, MOVE_TOXIC_SPIKES); } + } THEN { + EXPECT_EQ(player->pp[0], 18); + EXPECT_EQ(player->pp[1], 18); + EXPECT_EQ(player->pp[2], 18); + } +} + +SINGLE_BATTLE_TEST("Pressure's effect doesn't apply to Sticky Web (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { MovesWithPP({MOVE_STICKY_WEB, 20}); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_PRESSURE); } + } WHEN { + TURN { MOVE(player, MOVE_STICKY_WEB); } + } THEN { + EXPECT_EQ(player->pp[0], 19); + } +} +#endif diff --git a/test/battle/ability/primordial_sea.c b/test/battle/ability/primordial_sea.c index 7cf8dee53114..931d55300b56 100644 --- a/test/battle/ability/primordial_sea.c +++ b/test/battle/ability/primordial_sea.c @@ -143,3 +143,27 @@ SINGLE_BATTLE_TEST("Primordial Sea can be replaced by Desolate Land") EXPECT(gBattleWeather & B_WEATHER_SUN_PRIMAL); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Primordial Sea blocks damaging Fire-type moves (Multi)") +{ + GIVEN { + PLAYER(SPECIES_KYOGRE) {Items(ITEM_PECHA_BERRY, ITEM_BLUE_ORB);} + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_EMBER); } + TURN { MOVE(opponent, MOVE_EMBER); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Ember!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); + MESSAGE("The Fire-type attack fizzled out in the heavy rain!"); + NOT HP_BAR(player); + MESSAGE("The opposing Wobbuffet used Ember!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); + MESSAGE("The Fire-type attack fizzled out in the heavy rain!"); + NOT HP_BAR(player); + } THEN { + EXPECT_EQ(player->hp, player->maxHP); + } +} +#endif diff --git a/test/battle/ability/prism_armor.c b/test/battle/ability/prism_armor.c index 36fbc27f99c9..f9b3b6977e7d 100644 --- a/test/battle/ability/prism_armor.c +++ b/test/battle/ability/prism_armor.c @@ -23,3 +23,28 @@ SINGLE_BATTLE_TEST("Prism Armor reduces damage to Super Effective moves by 0.75" EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.75), results[1].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Prism Armor reduces damage to Super Effective moves by 0.75 (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_LIGHT_METAL; } + PARAMETRIZE { ability = ABILITY_PRISM_ARMOR; } + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_NECROZMA].types[0] == TYPE_PSYCHIC); + ASSUME(gSpeciesInfo[SPECIES_NECROZMA].types[1] == TYPE_PSYCHIC); + ASSUME(GetMoveType(MOVE_DARK_PULSE) == TYPE_DARK); + ASSUME(gTypeEffectivenessTable[TYPE_POISON][TYPE_FAIRY] > UQ_4_12(1.0)); + ASSUME(gTypeEffectivenessTable[TYPE_POISON][TYPE_PSYCHIC] == UQ_4_12(1.0)); + PLAYER(SPECIES_NECROZMA) { Ability(ABILITY_LEVITATE); Innates(ability); } + OPPONENT(SPECIES_WEEZING) { Ability(ABILITY_LEVITATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_DARK_PULSE); } + } SCENE { + HP_BAR(player, captureDamage: &results[i].damage); + MESSAGE("It's super effective!"); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.75), results[1].damage); + } +} +#endif diff --git a/test/battle/ability/protean.c b/test/battle/ability/protean.c index 7ebec1ff6e0e..868f9ee26741 100644 --- a/test/battle/ability/protean.c +++ b/test/battle/ability/protean.c @@ -96,3 +96,101 @@ SINGLE_BATTLE_TEST("Protean/Libero does not change the user's type when using St ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, opponent); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Protean/Libero changes the type of the user to the move used every time (Gen6-8) (Traits)") +{ + u32 ability, species; + PARAMETRIZE { ability = ABILITY_PROTEAN; species = SPECIES_KECLEON; } + PARAMETRIZE { ability = ABILITY_LIBERO; species = SPECIES_RABOOT; } + GIVEN { + WITH_CONFIG(CONFIG_PROTEAN_LIBERO, GEN_6); + PLAYER(SPECIES_REGIROCK); + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_WATER_GUN); } + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { SWITCH(opponent, 1); } + TURN { SWITCH(opponent, 0); } + TURN { MOVE(opponent, MOVE_WATER_GUN); } + } SCENE { + ABILITY_POPUP(opponent, ability); + if (species == SPECIES_KECLEON) + MESSAGE("The opposing Kecleon transformed into the Water type!"); + else + MESSAGE("The opposing Raboot transformed into the Water type!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + ABILITY_POPUP(opponent, ability); + if (species == SPECIES_KECLEON) + MESSAGE("The opposing Kecleon transformed into the Normal type!"); + else + MESSAGE("The opposing Raboot transformed into the Normal type!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(opponent, ability); + if (species == SPECIES_KECLEON) + MESSAGE("The opposing Kecleon transformed into the Water type!"); + else + MESSAGE("The opposing Raboot transformed into the Water type!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + } +} + +SINGLE_BATTLE_TEST("Protean/Libero changes the type of the user only once per switch in (Gen9+) (Traits)") +{ + u32 ability, species; + PARAMETRIZE { ability = ABILITY_PROTEAN; species = SPECIES_KECLEON; } + PARAMETRIZE { ability = ABILITY_LIBERO; species = SPECIES_RABOOT; } + GIVEN { + WITH_CONFIG(CONFIG_PROTEAN_LIBERO, GEN_9); + PLAYER(SPECIES_REGIROCK); + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_WATER_GUN); } + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { SWITCH(opponent, 1); } + TURN { SWITCH(opponent, 0); } + TURN { MOVE(opponent, MOVE_WATER_GUN); } + } SCENE { + ABILITY_POPUP(opponent, ability); + if (species == SPECIES_KECLEON) + MESSAGE("The opposing Kecleon transformed into the Water type!"); + else + MESSAGE("The opposing Raboot transformed into the Water type!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + NONE_OF { + ABILITY_POPUP(opponent, ability); + MESSAGE("The opposing Kecleon transformed into the Normal type!"); + MESSAGE("The opposing Raboot transformed into the Normal type!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(opponent, ability); + if (species == SPECIES_KECLEON) + MESSAGE("The opposing Kecleon transformed into the Water type!"); + else + MESSAGE("The opposing Raboot transformed into the Water type!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + } +} + +SINGLE_BATTLE_TEST("Protean/Libero does not change the user's type when using Struggle (Traits)") +{ + u32 ability, species; + PARAMETRIZE { ability = ABILITY_PROTEAN; species = SPECIES_GRENINJA; } + PARAMETRIZE { ability = ABILITY_LIBERO; species = SPECIES_RABOOT; } + GIVEN { + PLAYER(SPECIES_REGIROCK); + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(opponent, MOVE_STRUGGLE); } + } SCENE { + NONE_OF { + ABILITY_POPUP(opponent, ability); + MESSAGE("The opposing Greninja transformed into the Normal type!"); + MESSAGE("The opposing Raboot transformed into the Normal type!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, opponent); + } +} +#endif diff --git a/test/battle/ability/protosynthesis.c b/test/battle/ability/protosynthesis.c index f6d55b38a314..79f071aa5f12 100644 --- a/test/battle/ability/protosynthesis.c +++ b/test/battle/ability/protosynthesis.c @@ -347,3 +347,384 @@ SINGLE_BATTLE_TEST("Protosynthesis damage calculation is correct") EXPECT_EQ(expectedDamage, dmg); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Protosynthesis boosts the highest stat (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WALKING_WAKE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTOSYNTHESIS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("The harsh sunlight activated Walking Wake's Protosynthesis!"); + MESSAGE("Walking Wake's Sp. Atk was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis boosts either Attack or Special Attack, not both (Traits)") +{ + u16 species; + u32 move; + s16 damage[2]; + + PARAMETRIZE { species = SPECIES_ROARING_MOON; move = MOVE_SCRATCH; } + PARAMETRIZE { species = SPECIES_ROARING_MOON; move = MOVE_ROUND; } + + PARAMETRIZE { species = SPECIES_WALKING_WAKE; move = MOVE_SCRATCH; } + PARAMETRIZE { species = SPECIES_WALKING_WAKE; move = MOVE_ROUND; } + + GIVEN { + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTOSYNTHESIS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + TURN { MOVE(opponent, MOVE_SUNNY_DAY); MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + if ((move == MOVE_SCRATCH && species == SPECIES_ROARING_MOON) || (move == MOVE_ROUND && species == SPECIES_WALKING_WAKE)) + EXPECT_MUL_EQ(damage[0], Q_4_12(1.3), damage[1]); + else + EXPECT_EQ(damage[0], damage[1]); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis ability pop up activates only once during the duration of sunny day (Traits)") +{ + u16 turns; + + GIVEN { + WITH_CONFIG(CONFIG_ABILITY_WEATHER, GEN_6); + PLAYER(SPECIES_WALKING_WAKE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTOSYNTHESIS); } + OPPONENT(SPECIES_NINETALES) { Ability(ABILITY_FLASH_FIRE); Innates(ABILITY_DROUGHT); }; + } WHEN { + for (turns = 0; turns < 5; turns++) + TURN {} + TURN { MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("The harsh sunlight activated Walking Wake's Protosynthesis!"); + MESSAGE("Walking Wake's Sp. Atk was heightened!"); + NONE_OF { + for (turns = 0; turns < 4; turns++) { + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("The harsh sunlight activated Walking Wake's Protosynthesis!"); + MESSAGE("Walking Wake's Sp. Atk was heightened!"); + } + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("The harsh sunlight activated Walking Wake's Protosynthesis!"); + MESSAGE("Walking Wake's Sp. Atk was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis activates on switch-in (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ROARING_MOON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTOSYNTHESIS); } + OPPONENT(SPECIES_NINETALES) { Ability(ABILITY_FLASH_FIRE); Innates(ABILITY_DROUGHT); }; + } WHEN { + TURN { SWITCH(player, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("The harsh sunlight activated Roaring Moon's Protosynthesis!"); + MESSAGE("Roaring Moon's Attack was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis prioritizes stats in the case of a tie in the following order: Atk, Def, Sp.Atk, Sp.Def, Speed (Traits)") +{ + u8 stats[] = {1, 1, 1, 1, 1}; + + PARAMETRIZE { stats[4] = 255; stats[3] = 255; stats[2] = 255; stats[1] = 255; stats[0] = 255; } + PARAMETRIZE { stats[4] = 255; stats[3] = 255; stats[2] = 255; stats[1] = 255; } + PARAMETRIZE { stats[4] = 255; stats[3] = 255; stats[2] = 255; } + PARAMETRIZE { stats[4] = 255; stats[3] = 255; } + GIVEN { + PLAYER(SPECIES_GREAT_TUSK) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTOSYNTHESIS); Attack(stats[0]); Defense(stats[1]); SpAttack(stats[2]); SpDefense(stats[3]); Speed(stats[4]); } + OPPONENT(SPECIES_GROUDON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DROUGHT); Speed(5); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + switch(i) { + case 0: + MESSAGE("Great Tusk's Attack was heightened!"); + break; + case 1: + MESSAGE("Great Tusk's Defense was heightened!"); + break; + case 2: + MESSAGE("Great Tusk's Sp. Atk was heightened!"); + break; + case 3: + MESSAGE("Great Tusk's Sp. Def was heightened!"); + break; + } + } +} + +SINGLE_BATTLE_TEST("Protosynthesis activates in Sun before Booster Energy (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_GREAT_TUSK) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTOSYNTHESIS); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_NINETALES) { Ability(ABILITY_FLASH_FIRE); Innates(ABILITY_DROUGHT); } + } WHEN { + TURN { SWITCH(player, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + } THEN { + EXPECT_EQ(player->item, ITEM_BOOSTER_ENERGY); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis doesn't activate for a transformed battler (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_GREAT_TUSK) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTOSYNTHESIS); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_NINETALES) { Ability(ABILITY_FLASH_FIRE); Innates(ABILITY_DROUGHT); Item(ITEM_BOOSTER_ENERGY); } + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_TRANSFORM); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRANSFORM, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_PROTOSYNTHESIS); + } THEN { + EXPECT_EQ(player->item, ITEM_BOOSTER_ENERGY); + EXPECT_EQ(opponent->item, ITEM_BOOSTER_ENERGY); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis activates even if the Pokémon is holding an Utility Umbrella (Traits)") +{ + GIVEN { + PLAYER(SPECIES_GREAT_TUSK) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTOSYNTHESIS); Item(ITEM_UTILITY_UMBRELLA); } + OPPONENT(SPECIES_NINETALES) { Ability(ABILITY_FLASH_FIRE); Innates(ABILITY_DROUGHT); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis doesn't activate if Cloud Nine/Air Lock is on the field (Traits)") +{ + u32 species; + enum Ability ability; + PARAMETRIZE { species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } + PARAMETRIZE { species = SPECIES_GOLDUCK; ability = ABILITY_CLOUD_NINE; } + + GIVEN { + PLAYER(SPECIES_GREAT_TUSK) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTOSYNTHESIS); } + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ABILITY_POPUP(opponent, ability); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + NOT ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis activates after weather was reset (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WALKING_WAKE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTOSYNTHESIS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); } + TURN { MOVE(player, MOVE_RAIN_DANCE); } + TURN { MOVE(player, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAIN_DANCE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis accounts for Sticky Web when determining the boosted stat (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(1); } + PLAYER(SPECIES_FLUTTER_MANE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTOSYNTHESIS); Attack(50); Defense(50); SpAttack(150); SpDefense(140); Speed(180); } + OPPONENT(SPECIES_GALVANTULA) { Speed(60); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(1); } + } WHEN { + TURN { MOVE(opponent, MOVE_STICKY_WEB); MOVE(player, MOVE_SUNNY_DAY); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_SPLASH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("The harsh sunlight activated Flutter Mane's Protosynthesis!"); + MESSAGE("Flutter Mane's Sp. Atk was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis keeps its initial boosted stat after Speed is lowered (Traits)") +{ + s16 damage[2]; + + GIVEN { + PLAYER(SPECIES_FLUTTER_MANE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTOSYNTHESIS); Attack(10); Defense(10); SpAttack(150); SpDefense(120); Speed(180); Moves(MOVE_ROUND); } + OPPONENT(SPECIES_NINETALES) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DROUGHT); Moves(MOVE_ICY_WIND, MOVE_CELEBRATE); Speed(100); } + } WHEN { + TURN { MOVE(player, MOVE_ROUND); MOVE(opponent, MOVE_ICY_WIND); } + TURN { MOVE(player, MOVE_ROUND); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ICY_WIND, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} + + +SINGLE_BATTLE_TEST("Protosynthesis damage calculation is correct (Traits)") +{ + s16 dmg; + s16 expectedDamage; + + PARAMETRIZE { expectedDamage = 127; } + PARAMETRIZE { expectedDamage = 126; } + PARAMETRIZE { expectedDamage = 124; } + PARAMETRIZE { expectedDamage = 123; } + PARAMETRIZE { expectedDamage = 121; } + PARAMETRIZE { expectedDamage = 120; } + PARAMETRIZE { expectedDamage = 118; } + PARAMETRIZE { expectedDamage = 118; } + PARAMETRIZE { expectedDamage = 117; } + PARAMETRIZE { expectedDamage = 115; } + PARAMETRIZE { expectedDamage = 114; } + PARAMETRIZE { expectedDamage = 112; } + PARAMETRIZE { expectedDamage = 111; } + PARAMETRIZE { expectedDamage = 109; } + PARAMETRIZE { expectedDamage = 109; } + PARAMETRIZE { expectedDamage = 108; } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_CLOSE_COMBAT) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_GOUGING_FIRE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTOSYNTHESIS); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_URSHIFU_RAPID_STRIKE); + } WHEN { + TURN { MOVE(opponent, MOVE_CLOSE_COMBAT, WITH_RNG(RNG_DAMAGE_MODIFIER, i)); } + } SCENE { + HP_BAR(player, captureDamage: &dmg); + } THEN { + EXPECT_EQ(expectedDamage, dmg); + } +} + +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Protosynthesis activates in Sun before Booster Energy (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_GREAT_TUSK) { Ability(ABILITY_PROTOSYNTHESIS); Items(ITEM_PECHA_BERRY, ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_NINETALES) { Ability(ABILITY_DROUGHT); } + } WHEN { + TURN { SWITCH(player, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + } THEN { + EXPECT_EQ(player->item, ITEM_BOOSTER_ENERGY); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis doesn't activate for a transformed battler (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_GREAT_TUSK) { Ability(ABILITY_PROTOSYNTHESIS); Items(ITEM_PECHA_BERRY, ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_NINETALES) { Ability(ABILITY_DROUGHT); Items(ITEM_PECHA_BERRY, ITEM_BOOSTER_ENERGY); } + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_TRANSFORM); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRANSFORM, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_PROTOSYNTHESIS); + } THEN { + EXPECT_EQ(player->item, ITEM_BOOSTER_ENERGY); + EXPECT_EQ(opponent->item, ITEM_BOOSTER_ENERGY); + EXPECT_EQ(opponent->ability, ABILITY_PROTOSYNTHESIS); + } +} + +SINGLE_BATTLE_TEST("Protosynthesis activates even if the Pokémon is holding an Utility Umbrella (Multi)") +{ + GIVEN { + PLAYER(SPECIES_GREAT_TUSK) { Ability(ABILITY_PROTOSYNTHESIS); Items(ITEM_PECHA_BERRY, ITEM_UTILITY_UMBRELLA); } + OPPONENT(SPECIES_NINETALES) { Ability(ABILITY_DROUGHT); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + } +} + + +SINGLE_BATTLE_TEST("Protosynthesis damage calculation is correct (Multi)") +{ + s16 dmg; + s16 expectedDamage; + + PARAMETRIZE { expectedDamage = 127; } + PARAMETRIZE { expectedDamage = 126; } + PARAMETRIZE { expectedDamage = 124; } + PARAMETRIZE { expectedDamage = 123; } + PARAMETRIZE { expectedDamage = 121; } + PARAMETRIZE { expectedDamage = 120; } + PARAMETRIZE { expectedDamage = 118; } + PARAMETRIZE { expectedDamage = 118; } + PARAMETRIZE { expectedDamage = 117; } + PARAMETRIZE { expectedDamage = 115; } + PARAMETRIZE { expectedDamage = 114; } + PARAMETRIZE { expectedDamage = 112; } + PARAMETRIZE { expectedDamage = 111; } + PARAMETRIZE { expectedDamage = 109; } + PARAMETRIZE { expectedDamage = 109; } + PARAMETRIZE { expectedDamage = 108; } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_CLOSE_COMBAT) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_GOUGING_FIRE) { Ability(ABILITY_PROTOSYNTHESIS); Items(ITEM_NUGGET, ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_URSHIFU_RAPID_STRIKE); + } WHEN { + TURN { MOVE(opponent, MOVE_CLOSE_COMBAT, WITH_RNG(RNG_DAMAGE_MODIFIER, i)); } + } SCENE { + HP_BAR(player, captureDamage: &dmg); + } THEN { + EXPECT_EQ(expectedDamage, dmg); + } +} + +#endif diff --git a/test/battle/ability/psychic_surge.c b/test/battle/ability/psychic_surge.c index 02b2080043b4..1cfc6e0ffe9c 100644 --- a/test/battle/ability/psychic_surge.c +++ b/test/battle/ability/psychic_surge.c @@ -13,3 +13,18 @@ SINGLE_BATTLE_TEST("Psychic Surge creates Psychic Terrain when entering the batt MESSAGE("The battlefield got weird!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Psychic Surge creates Psychic Terrain when entering the battle (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TAPU_LELE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PSYCHIC_SURGE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN {} + } SCENE { + ABILITY_POPUP(player, ABILITY_PSYCHIC_SURGE); + MESSAGE("The battlefield got weird!"); + } +} +#endif diff --git a/test/battle/ability/purifying_salt.c b/test/battle/ability/purifying_salt.c index df348752fb85..07a536978262 100644 --- a/test/battle/ability/purifying_salt.c +++ b/test/battle/ability/purifying_salt.c @@ -135,3 +135,140 @@ SINGLE_BATTLE_TEST("Purifying Salt protects from secondary effect burn") NOT STATUS_ICON(player, STATUS1_BURN); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Purifying Salt halves damage from Ghost-type moves (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_STURDY; } + PARAMETRIZE { ability = ABILITY_PURIFYING_SALT; } + GIVEN { + ASSUME(GetMoveType(MOVE_SHADOW_BALL) == TYPE_GHOST); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GARGANACL) { Ability(ABILITY_CLEAR_BODY); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SHADOW_BALL); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(0.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Purifying Salt halves damage from dynamic Ghost-type moves (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_STURDY; } + PARAMETRIZE { ability = ABILITY_PURIFYING_SALT; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_TERA_BLAST) == EFFECT_TERA_BLAST); + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_GHOST); } + OPPONENT(SPECIES_GARGANACL) { Ability(ABILITY_CLEAR_BODY); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST, gimmick: GIMMICK_TERA); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(0.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Purifying Salt makes Rest fail (Traits)") +{ + GIVEN { + PLAYER(SPECIES_GARGANACL) { Ability(ABILITY_CLEAR_BODY); Innates(ABILITY_PURIFYING_SALT); HP(1); MaxHP(100);} + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_REST); } + } SCENE { + NONE_OF { + MESSAGE("Garganacl went to sleep!"); + } + } +} + +SINGLE_BATTLE_TEST("Purifying Salt grants immunity to status effects (Traits)") +{ + u32 move; + u16 status; + PARAMETRIZE { move = MOVE_WILL_O_WISP; status = STATUS1_BURN; } + PARAMETRIZE { move = MOVE_HYPNOSIS; status = STATUS1_SLEEP; } + PARAMETRIZE { move = MOVE_THUNDER_WAVE; status = STATUS1_PARALYSIS; } + PARAMETRIZE { move = MOVE_TOXIC; status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { move = MOVE_POWDER_SNOW; status = STATUS1_FREEZE; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_WILL_O_WISP) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_WILL_O_WISP) == MOVE_EFFECT_BURN); + ASSUME(GetMoveEffect(MOVE_HYPNOSIS) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_HYPNOSIS) == MOVE_EFFECT_SLEEP); + ASSUME(GetMoveEffect(MOVE_THUNDER_WAVE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_THUNDER_WAVE) == MOVE_EFFECT_PARALYSIS); + ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + ASSUME(MoveHasAdditionalEffect(MOVE_POWDER_SNOW, MOVE_EFFECT_FREEZE_OR_FROSTBITE) == TRUE); + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_CLEAR_BODY); Innates(ABILITY_PURIFYING_SALT); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + if (move != MOVE_POWDER_SNOW) { + NOT ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ABILITY_POPUP(player, ABILITY_PURIFYING_SALT); + MESSAGE("It doesn't affect Wobbuffet…"); + NOT STATUS_ICON(player, status); + } else { + NONE_OF { + ABILITY_POPUP(player, ABILITY_PURIFYING_SALT); + STATUS_ICON(player, status); + } + } + } +} + +SINGLE_BATTLE_TEST("Purifying Salt user can't be poisoned by Toxic Spikes (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_GARGANACL) { Ability(ABILITY_CLEAR_BODY); Innates(ABILITY_PURIFYING_SALT); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TOXIC_SPIKES); } + TURN { SWITCH(player, 1); } + } SCENE { + SEND_IN_MESSAGE("Garganacl"); + } THEN { + EXPECT_EQ(player->status1, STATUS1_NONE); + } +} + +SINGLE_BATTLE_TEST("Purifying Salt doesn't prevent Pokémon from being poisoned by Toxic Spikes on switch-in if forced in by phazing with Mold Breaker (Traits)") +{ + 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_CLEAR_BODY); Innates(ABILITY_PURIFYING_SALT); } + OPPONENT(SPECIES_PINSIR) { Ability(ABILITY_HYPER_CUTTER); Innates(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); + } +} + +SINGLE_BATTLE_TEST("Purifying Salt protects from secondary effect burn (Traits)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_EMBER, MOVE_EFFECT_BURN)); + PLAYER(SPECIES_GARGANACL) { Ability(ABILITY_CLEAR_BODY); Innates(ABILITY_PURIFYING_SALT); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_EMBER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); + NOT STATUS_ICON(player, STATUS1_BURN); + } +} +#endif diff --git a/test/battle/ability/quark_drive.c b/test/battle/ability/quark_drive.c index bdb8a26201d6..686e8c0c6792 100644 --- a/test/battle/ability/quark_drive.c +++ b/test/battle/ability/quark_drive.c @@ -236,3 +236,224 @@ SINGLE_BATTLE_TEST("Quark Drive doesn't activate for a transformed battler") EXPECT_EQ(opponent->ability, ABILITY_QUARK_DRIVE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Quark Drive boosts the highest stat (Traits)") +{ + GIVEN { + PLAYER(SPECIES_IRON_MOTH) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_QUARK_DRIVE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, player); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + MESSAGE("The Electric Terrain activated Iron Moth's Quark Drive!"); + MESSAGE("Iron Moth's Sp. Atk was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Quark Drive boosts either Attack or Special Attack, not both (Traits)") +{ + u16 species; + u32 move; + s16 damage[2]; + + PARAMETRIZE { species = SPECIES_IRON_VALIANT; move = MOVE_SCRATCH; } + PARAMETRIZE { species = SPECIES_IRON_VALIANT; move = MOVE_ROUND; } + + PARAMETRIZE { species = SPECIES_IRON_MOTH; move = MOVE_SCRATCH; } + PARAMETRIZE { species = SPECIES_IRON_MOTH; move = MOVE_ROUND; } + + GIVEN { + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_QUARK_DRIVE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + TURN { MOVE(opponent, MOVE_ELECTRIC_TERRAIN); MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, opponent); + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + if ((move == MOVE_SCRATCH && species == SPECIES_IRON_VALIANT) || (move == MOVE_ROUND && species == SPECIES_IRON_MOTH)) + EXPECT_MUL_EQ(damage[0], Q_4_12(1.3), damage[1]); + else + EXPECT_EQ(damage[0], damage[1]); + } +} + +SINGLE_BATTLE_TEST("Quark Drive keeps its initial boosted stat after Speed is lowered (Traits)") +{ + s16 damage[2]; + + GIVEN { + PLAYER(SPECIES_IRON_MOTH) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_QUARK_DRIVE); Attack(10); Defense(10); SpAttack(150); SpDefense(120); Speed(180); Moves(MOVE_ROUND, MOVE_CELEBRATE); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ELECTRIC_SURGE); Moves(MOVE_CELEBRATE, MOVE_ICY_WIND); Speed(100); } + } WHEN { + TURN { MOVE(player, MOVE_ROUND); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_ICY_WIND); } + TURN { MOVE(player, MOVE_ROUND); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ICY_WIND, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} + +SINGLE_BATTLE_TEST("Quark Drive ability pop up activates only once during the duration of electric terrain (Traits)") +{ + u16 turns; + + GIVEN { + PLAYER(SPECIES_IRON_MOTH) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_QUARK_DRIVE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); } + for (turns = 0; turns < 4; turns++) + TURN {} + TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, player); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + MESSAGE("The Electric Terrain activated Iron Moth's Quark Drive!"); + MESSAGE("Iron Moth's Sp. Atk was heightened!"); + NONE_OF { + for (turns = 0; turns < 4; turns++) { + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + MESSAGE("The Electric Terrain activated Iron Moth's Quark Drive!"); + MESSAGE("Iron Moth's Sp. Atk was heightened!"); + } + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, player); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + MESSAGE("The Electric Terrain activated Iron Moth's Quark Drive!"); + MESSAGE("Iron Moth's Sp. Atk was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Quark Drive activates on switch-in (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_IRON_MOTH) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_QUARK_DRIVE); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ELECTRIC_SURGE); }; + } WHEN { + TURN { SWITCH(player, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + MESSAGE("The Electric Terrain activated Iron Moth's Quark Drive!"); + MESSAGE("Iron Moth's Sp. Atk was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Quark Drive activates on Electric Terrain even if not grounded (Traits)") +{ + GIVEN { + ASSUME(GetSpeciesType(SPECIES_IRON_JUGULIS, 0) == TYPE_FLYING || GetSpeciesType(SPECIES_IRON_JUGULIS, 1) == TYPE_FLYING); + PLAYER(SPECIES_IRON_JUGULIS) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_QUARK_DRIVE); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ELECTRIC_SURGE); }; + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + } +} + +SINGLE_BATTLE_TEST("Quark Drive prioritizes stats in the case of a tie in the following order: Atk, Def, Sp.Atk, Sp.Def, Speed (Traits)") +{ + u8 stats[] = {1, 1, 1, 1, 1}; + + PARAMETRIZE { stats[4] = 255; stats[3] = 255; stats[2] = 255; stats[1] = 255; stats[0] = 255; } + PARAMETRIZE { stats[4] = 255; stats[3] = 255; stats[2] = 255; stats[1] = 255; } + PARAMETRIZE { stats[4] = 255; stats[3] = 255; stats[2] = 255; } + PARAMETRIZE { stats[4] = 255; stats[3] = 255; } + GIVEN { + PLAYER(SPECIES_IRON_TREADS) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_QUARK_DRIVE); Attack(stats[0]); Defense(stats[1]); SpAttack(stats[2]); SpDefense(stats[3]); Speed(stats[4]); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ELECTRIC_SURGE); Speed(5); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + switch(i) { + case 0: + MESSAGE("Iron Treads's Attack was heightened!"); + break; + case 1: + MESSAGE("Iron Treads's Defense was heightened!"); + break; + case 2: + MESSAGE("Iron Treads's Sp. Atk was heightened!"); + break; + case 3: + MESSAGE("Iron Treads's Sp. Def was heightened!"); + break; + } + } +} + +SINGLE_BATTLE_TEST("Quark Drive activates in Electric Terrain before Booster Energy (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_IRON_TREADS) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_QUARK_DRIVE); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ELECTRIC_SURGE); } + } WHEN { + TURN { SWITCH(player, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + } THEN { + EXPECT_EQ(player->item, ITEM_BOOSTER_ENERGY); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Quark Drive activates in Electric Terrain before Booster Energy (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_IRON_TREADS) { Ability(ABILITY_QUARK_DRIVE); Items(ITEM_PECHA_BERRY, ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); } + } WHEN { + TURN { SWITCH(player, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + } THEN { + EXPECT_EQ(player->item, ITEM_BOOSTER_ENERGY); + } +} + +SINGLE_BATTLE_TEST("Quark Drive doesn't activate for a transformed battler (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_IRON_TREADS) { Ability(ABILITY_QUARK_DRIVE); Items(ITEM_PECHA_BERRY, ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); Items(ITEM_PECHA_BERRY, ITEM_BOOSTER_ENERGY); } + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_TRANSFORM); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRANSFORM, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_QUARK_DRIVE); + } THEN { + EXPECT_EQ(player->item, ITEM_BOOSTER_ENERGY); + EXPECT_EQ(opponent->item, ITEM_BOOSTER_ENERGY); + EXPECT_EQ(opponent->ability, ABILITY_QUARK_DRIVE); + } +} +#endif diff --git a/test/battle/ability/quick_draw.c b/test/battle/ability/quick_draw.c index aeb711b86ba9..4a93a70a8980 100644 --- a/test/battle/ability/quick_draw.c +++ b/test/battle/ability/quick_draw.c @@ -30,3 +30,35 @@ SINGLE_BATTLE_TEST("Quick Draw does not activate 70% of the time") MESSAGE("Slowbro used Scratch!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Quick Draw has a 30% chance of going first (Traits)") +{ + PASSES_RANDOMLY(3, 10, RNG_QUICK_DRAW); + GIVEN { + PLAYER(SPECIES_SLOWBRO_GALAR) { Ability(ABILITY_OWN_TEMPO); Innates(ABILITY_QUICK_DRAW); Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(100); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(player, ABILITY_QUICK_DRAW); + MESSAGE("Slowbro used Scratch!"); + MESSAGE("The opposing Wobbuffet used Celebrate!"); + } +} + +SINGLE_BATTLE_TEST("Quick Draw does not activate 70% of the time (Traits)") +{ + PASSES_RANDOMLY(7, 10, RNG_QUICK_DRAW); + GIVEN { + PLAYER(SPECIES_SLOWBRO_GALAR) { Ability(ABILITY_OWN_TEMPO); Innates(ABILITY_QUICK_DRAW); Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(100); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SCRATCH); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_QUICK_DRAW); + MESSAGE("The opposing Wobbuffet used Celebrate!"); + MESSAGE("Slowbro used Scratch!"); + } +} +#endif diff --git a/test/battle/ability/rain_dish.c b/test/battle/ability/rain_dish.c index dc7de954c377..ffe7546cee30 100644 --- a/test/battle/ability/rain_dish.c +++ b/test/battle/ability/rain_dish.c @@ -30,3 +30,31 @@ SINGLE_BATTLE_TEST("Rain Dish doesn't recover HP if Cloud Nine/Air Lock is on th NOT ABILITY_POPUP(player, ABILITY_RAIN_DISH); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Rain Dish recovers 1/16th of Max HP in Rain (Traits)") +{ + GIVEN { + PLAYER(SPECIES_LUDICOLO) { Ability(ABILITY_SWIFT_SWIM); Innates(ABILITY_RAIN_DISH); HP(1); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_RAIN_DANCE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_RAIN_DISH); + MESSAGE("Ludicolo's Rain Dish restored its HP a little!"); + HP_BAR(player, damage: -(100 / 16)); + } +} + +SINGLE_BATTLE_TEST("Rain Dish doesn't recover HP if Cloud Nine/Air Lock is on the field (Traits)") +{ + GIVEN { + PLAYER(SPECIES_LUDICOLO) { Ability(ABILITY_SWIFT_SWIM); Innates(ABILITY_RAIN_DISH); HP(1); MaxHP(100); } + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_SWIFT_SWIM); Innates(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(opponent, MOVE_RAIN_DANCE); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_RAIN_DISH); + } +} +#endif diff --git a/test/battle/ability/rattled.c b/test/battle/ability/rattled.c index 485ce9daf086..153b9aac24ed 100644 --- a/test/battle/ability/rattled.c +++ b/test/battle/ability/rattled.c @@ -111,3 +111,104 @@ SINGLE_BATTLE_TEST("Rattled triggers correctly when hit by U-Turn") // Specific SEND_IN_MESSAGE("Wynaut"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Rattled boosts speed by 1 when hit by Bug, Dark or Ghost type move (Traits)") +{ + u16 move; + PARAMETRIZE { move = MOVE_FURY_CUTTER; } + PARAMETRIZE { move = MOVE_FEINT_ATTACK; } + PARAMETRIZE { move = MOVE_SHADOW_PUNCH; } + PARAMETRIZE { move = MOVE_SCRATCH; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) {Speed(42) ;} + OPPONENT(SPECIES_SUDOWOODO) {Speed(40); Ability(ABILITY_STURDY); Innates(ABILITY_RATTLED); } + } WHEN { + TURN { MOVE(player, move); } + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + if (move != MOVE_SCRATCH) { + ABILITY_POPUP(opponent, ABILITY_RATTLED); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Sudowoodo's Speed rose!"); + } + MESSAGE("The opposing Sudowoodo used Celebrate!"); + // Sudowoodo is now faster + if (move != MOVE_SCRATCH){ + MESSAGE("The opposing Sudowoodo used Celebrate!"); + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_RATTLED); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Sudowoodo's Speed rose!"); + } + else { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + MESSAGE("The opposing Sudowoodo used Celebrate!"); + } + } +} + +SINGLE_BATTLE_TEST("Rattled does not boost speed by 1 when affected by Intimidate (Gen5-7) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_UPDATED_INTIMIDATE, GEN_7); + PLAYER(SPECIES_GYARADOS) { Ability(ABILITY_MOXIE); Innates(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_SUDOWOODO) { Ability(ABILITY_STURDY); Innates(ABILITY_RATTLED); } + } WHEN { + TURN {} + } SCENE { + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Gyarados's Intimidate cuts the opposing Sudowoodo's Attack!"); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_RATTLED); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Sudowoodo's Speed rose!"); + } + } +} + +SINGLE_BATTLE_TEST("Rattled boosts speed by 1 when affected by Intimidate (Gen8+) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_UPDATED_INTIMIDATE, GEN_8); + PLAYER(SPECIES_GYARADOS) { Ability(ABILITY_MOXIE); Innates(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_SUDOWOODO) { Ability(ABILITY_STURDY); Innates(ABILITY_RATTLED); } + } WHEN { + TURN {} + } SCENE { + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Gyarados's Intimidate cuts the opposing Sudowoodo's Attack!"); + ABILITY_POPUP(opponent, ABILITY_RATTLED); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Sudowoodo's Speed rose!"); + } +} + +SINGLE_BATTLE_TEST("Rattled triggers correctly when hit by U-Turn (Traits)") // Specific test here, because of #3124 +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_U_TURN) == EFFECT_HIT_ESCAPE); + ASSUME(GetMoveType(MOVE_U_TURN) == TYPE_BUG); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_SUDOWOODO) {Ability(ABILITY_STURDY); Innates(ABILITY_RATTLED); } + OPPONENT(SPECIES_SUDOWOODO); + } WHEN { + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); } + } SCENE { + MESSAGE("Wobbuffet used U-turn!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_RATTLED); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Sudowoodo's Speed rose!"); + SEND_IN_MESSAGE("Wynaut"); + } +} +#endif diff --git a/test/battle/ability/refrigerate.c b/test/battle/ability/refrigerate.c index f0d7ec89eef4..8854f4afd743 100644 --- a/test/battle/ability/refrigerate.c +++ b/test/battle/ability/refrigerate.c @@ -237,3 +237,193 @@ SINGLE_BATTLE_TEST("Refrigerate doesn't affect damaging Z-Move types") TO_DO_BATTLE_TEST("Refrigerate doesn't affect Max Strike's type"); TO_DO_BATTLE_TEST("(DYNAMAX) Refrigerate turns Max Strike into Max Hailstorm when not used by Gigantamax Lapras"); //TO_DO_BATTLE_TEST("(DYNAMAX) Refrigerate doesn't turn Max Strike into Max Hailstorm when used by Gigantamax Lapras, instead becoming G-Max Resonance"); // Marked in Bulbapedia as "needs research", so this assumes that it behaves like Pixilate. + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Refrigerate turns a Normal-type move into a Ice-type move (Traits)") +{ + GIVEN { + PLAYER(SPECIES_MEGANIUM); + OPPONENT(SPECIES_AMAURA) { Ability(ABILITY_SNOW_WARNING); Innates(ABILITY_REFRIGERATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Refrigerate boosts power of affected moves by 20% (Gen7+) or 30% (Gen1-6) (Traits)", s16 damage) +{ + enum Ability ability; + u32 genConfig; + PARAMETRIZE { ability = ABILITY_SNOW_WARNING; genConfig = GEN_7; } + PARAMETRIZE { ability = ABILITY_SNOW_WARNING; genConfig = GEN_6; } + PARAMETRIZE { ability = ABILITY_REFRIGERATE; genConfig = GEN_7; } + PARAMETRIZE { ability = ABILITY_REFRIGERATE; genConfig = GEN_6; } + + GIVEN { + WITH_CONFIG(CONFIG_ATE_MULTIPLIER, genConfig); + PLAYER(SPECIES_AMAURA) { Ability(ABILITY_SNOW_WARNING); Innates(ability); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + if (genConfig >= GEN_7) + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.8), results[2].damage); // STAB + ate + else + EXPECT_MUL_EQ(results[1].damage, Q_4_12(1.95), results[3].damage); // STAB + ate + } +} + +SINGLE_BATTLE_TEST("Refrigerate doesn't affect Weather Ball's type (Traits)", s16 damage) +{ + u16 move; + enum Ability ability; + PARAMETRIZE { move = MOVE_CELEBRATE; ability = ABILITY_SNOW_WARNING; } + PARAMETRIZE { move = MOVE_SUNNY_DAY; ability = ABILITY_SNOW_WARNING; } + PARAMETRIZE { move = MOVE_CELEBRATE; ability = ABILITY_REFRIGERATE; } + PARAMETRIZE { move = MOVE_SUNNY_DAY; ability = ABILITY_REFRIGERATE; } + GIVEN { + WITH_CONFIG(CONFIG_SNOW_WARNING, GEN_9); //To prevent capturing hail damage + ASSUME(GetMoveEffect(MOVE_WEATHER_BALL) == EFFECT_WEATHER_BALL); + ASSUME(GetSpeciesType(SPECIES_PINSIR, 0) == TYPE_BUG); + PLAYER(SPECIES_AMAURA) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_PINSIR); + } WHEN { + TURN { MOVE(player, move); } + TURN { MOVE(player, MOVE_WEATHER_BALL); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + if (move == MOVE_SUNNY_DAY) + MESSAGE("It's super effective!"); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[1].damage); // double base power + type effectiveness + sun 50% boost vs hail no type effectiveness + EXPECT_MUL_EQ(results[2].damage, Q_4_12(6.0), results[3].damage); // double base power + type effectiveness + sun 50% boost + EXPECT_MUL_EQ(results[2].damage, Q_4_12(3.0), results[0].damage); // type effectiveness + sun 50% boost, both have double base power + EXPECT_EQ(results[1].damage, results[3].damage); + } +} + +SINGLE_BATTLE_TEST("Refrigerate doesn't affect Natural Gift's type (Traits)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_SNOW_WARNING; } + PARAMETRIZE { ability = ABILITY_REFRIGERATE; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + ASSUME(gNaturalGiftTable[ITEM_TO_BERRY(ITEM_ORAN_BERRY)].type == TYPE_POISON); + ASSUME(GetSpeciesType(SPECIES_BELDUM, 0) == TYPE_STEEL); + PLAYER(SPECIES_AMAURA) { Ability(ABILITY_SNOW_WARNING); Innates(ability); Item(ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_BELDUM); + } WHEN { + TURN { MOVE(player, MOVE_NATURAL_GIFT); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player); } + MESSAGE("It doesn't affect the opposing Beldum…"); + } +} + +SINGLE_BATTLE_TEST("Refrigerate doesn't affect Judgment / Techno Blast / Multi-Attack's type (Traits)") +{ + u16 move, item; + PARAMETRIZE { move = MOVE_JUDGMENT; item = ITEM_ZAP_PLATE; } + PARAMETRIZE { move = MOVE_TECHNO_BLAST; item = ITEM_SHOCK_DRIVE; } + PARAMETRIZE { move = MOVE_MULTI_ATTACK; item = ITEM_ELECTRIC_MEMORY; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_JUDGMENT) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetMoveEffect(MOVE_TECHNO_BLAST) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetMoveEffect(MOVE_MULTI_ATTACK) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(gItemsInfo[ITEM_ZAP_PLATE].holdEffect == HOLD_EFFECT_PLATE); + ASSUME(gItemsInfo[ITEM_ZAP_PLATE].secondaryId == TYPE_ELECTRIC); + ASSUME(gItemsInfo[ITEM_SHOCK_DRIVE].holdEffect == HOLD_EFFECT_DRIVE); + ASSUME(gItemsInfo[ITEM_SHOCK_DRIVE].secondaryId == TYPE_ELECTRIC); + ASSUME(gItemsInfo[ITEM_ELECTRIC_MEMORY].holdEffect == HOLD_EFFECT_MEMORY); + ASSUME(gItemsInfo[ITEM_ELECTRIC_MEMORY].secondaryId == TYPE_ELECTRIC); + ASSUME(GetSpeciesType(SPECIES_DIGLETT, 0) == TYPE_GROUND); + PLAYER(SPECIES_AMAURA) { Ability(ABILITY_SNOW_WARNING); Innates(ABILITY_REFRIGERATE); Item(item); } + OPPONENT(SPECIES_DIGLETT); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, move, player); } + MESSAGE("It doesn't affect the opposing Diglett…"); + } +} + +SINGLE_BATTLE_TEST("Refrigerate doesn't affect Hidden Power's type (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_HIDDEN_POWER) == EFFECT_HIDDEN_POWER); + ASSUME(gTypesInfo[TYPE_ELECTRIC].isHiddenPowerType == TRUE); + ASSUME(GetSpeciesType(SPECIES_DIGLETT, 0) == TYPE_GROUND); + PLAYER(SPECIES_AMAURA) { Ability(ABILITY_SNOW_WARNING); Innates(ABILITY_REFRIGERATE); HPIV(31); AttackIV(31); DefenseIV(31); SpAttackIV(30); SpDefenseIV(31); SpeedIV(31); } // HP Electric + OPPONENT(SPECIES_DIGLETT); + } WHEN { + TURN { MOVE(player, MOVE_HIDDEN_POWER); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_HIDDEN_POWER, player); } + MESSAGE("It doesn't affect the opposing Diglett…"); + } +} + +TO_DO_BATTLE_TEST("Refrigerate doesn't override Electrify (Gen7+) (Traits)"); // No mon with Refrigerate exists in Gen8+, but probably behaves similar to Pixilate, which does. +TO_DO_BATTLE_TEST("Refrigerate doesn't override Ion Deluge (Gen7+) (Traits)"); // Ion Deluge doesn't exist in Gen 8+, but we probably could assume it behaves similar to under Electrify. TODO: Test by hacking SV. +TO_DO_BATTLE_TEST("Refrigerate overrides Electrify (Gen6) (Traits)") +TO_DO_BATTLE_TEST("Refrigerate overrides Ion Deluge (Gen6) (Traits)") +TO_DO_BATTLE_TEST("Refrigerate doesn't affect Tera Starstorm's type (Traits)"); +TO_DO_BATTLE_TEST("Refrigerate doesn't affect Max Strike's type (Traits)"); +TO_DO_BATTLE_TEST("Refrigerate doesn't affect Terrain Pulse's type (Traits)"); +TO_DO_BATTLE_TEST("Refrigerate doesn't affect damaging Z-Move types (Traits)"); +TO_DO_BATTLE_TEST("(DYNAMAX) Refrigerate turns Max Strike into Max Hailstorm when not used by Gigantamax Lapras (Traits)"); +//TO_DO_BATTLE_TEST("(DYNAMAX) Refrigerate doesn't turn Max Strike into Max Hailstorm when used by Gigantamax Lapras, instead becoming G-Max Resonance (Traits)"); // Marked in Bulbapedia as "needs research", so this assumes that it behaves like Pixilate. +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Refrigerate doesn't affect Natural Gift's type (Multi)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_SNOW_WARNING; } + PARAMETRIZE { ability = ABILITY_REFRIGERATE; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + ASSUME(gNaturalGiftTable[ITEM_TO_BERRY(ITEM_ORAN_BERRY)].type == TYPE_POISON); + ASSUME(GetSpeciesType(SPECIES_BELDUM, 0) == TYPE_STEEL); + PLAYER(SPECIES_AMAURA) { Ability(ability); Items(ITEM_GREAT_BALL, ITEM_ORAN_BERRY); } + OPPONENT(SPECIES_BELDUM); + } WHEN { + TURN { MOVE(player, MOVE_NATURAL_GIFT); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player); } + MESSAGE("It doesn't affect the opposing Beldum…"); + } +} + +SINGLE_BATTLE_TEST("Refrigerate doesn't affect Judgment / Techno Blast / Multi-Attack's type (Multi)") +{ + u16 move, item; + PARAMETRIZE { move = MOVE_JUDGMENT; item = ITEM_ZAP_PLATE; } + PARAMETRIZE { move = MOVE_TECHNO_BLAST; item = ITEM_SHOCK_DRIVE; } + PARAMETRIZE { move = MOVE_MULTI_ATTACK; item = ITEM_ELECTRIC_MEMORY; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_JUDGMENT) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetMoveEffect(MOVE_TECHNO_BLAST) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(GetMoveEffect(MOVE_MULTI_ATTACK) == EFFECT_CHANGE_TYPE_ON_ITEM); + ASSUME(gItemsInfo[ITEM_ZAP_PLATE].holdEffect == HOLD_EFFECT_PLATE); + ASSUME(gItemsInfo[ITEM_ZAP_PLATE].secondaryId == TYPE_ELECTRIC); + ASSUME(gItemsInfo[ITEM_SHOCK_DRIVE].holdEffect == HOLD_EFFECT_DRIVE); + ASSUME(gItemsInfo[ITEM_SHOCK_DRIVE].secondaryId == TYPE_ELECTRIC); + ASSUME(gItemsInfo[ITEM_ELECTRIC_MEMORY].holdEffect == HOLD_EFFECT_MEMORY); + ASSUME(gItemsInfo[ITEM_ELECTRIC_MEMORY].secondaryId == TYPE_ELECTRIC); + ASSUME(GetSpeciesType(SPECIES_DIGLETT, 0) == TYPE_GROUND); + PLAYER(SPECIES_AMAURA) { Ability(ABILITY_REFRIGERATE); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_DIGLETT); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, move, player); } + MESSAGE("It doesn't affect the opposing Diglett…"); + } +} +#endif diff --git a/test/battle/ability/regenerator.c b/test/battle/ability/regenerator.c index 0f1b4327728b..cbdb1459b7ca 100644 --- a/test/battle/ability/regenerator.c +++ b/test/battle/ability/regenerator.c @@ -48,3 +48,53 @@ SINGLE_BATTLE_TEST("Regenerator heals 1/3 of max HP upon switching out but doesn EXPECT_LE(player->hp, player->maxHP); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Regenerator heals 1/3 of max HP upon switching out (Traits)") +{ + u32 currHP; + PARAMETRIZE { currHP = 1; } + PARAMETRIZE { currHP = 2; } + PARAMETRIZE { currHP = 3; } + GIVEN { + PLAYER(SPECIES_SLOWBRO) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_REGENERATOR); HP(currHP); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(player, 1); } + TURN { SWITCH(player, 0); } + } SCENE { + SWITCH_OUT_MESSAGE("Slowbro"); + SEND_IN_MESSAGE("Wobbuffet"); + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Slowbro"); + } THEN { + EXPECT_EQ(player->hp, player->maxHP / 3 + currHP); + } +} + +SINGLE_BATTLE_TEST("Regenerator heals 1/3 of max HP upon switching out but doesn't surpass max HP (Traits)") +{ + u32 currHP; + PARAMETRIZE { currHP = 5; } + PARAMETRIZE { currHP = 4; } + PARAMETRIZE { currHP = 3; } + PARAMETRIZE { currHP = 2; } + PARAMETRIZE { currHP = 1; } + GIVEN { + PLAYER(SPECIES_SLOWBRO) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_REGENERATOR); HP(currHP); MaxHP(5); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(player, 1); } + TURN { SWITCH(player, 0); } + } SCENE { + SWITCH_OUT_MESSAGE("Slowbro"); + SEND_IN_MESSAGE("Wobbuffet"); + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Slowbro"); + } THEN { + EXPECT_LE(player->hp, player->maxHP); + } +} +#endif diff --git a/test/battle/ability/rivalry.c b/test/battle/ability/rivalry.c index 7db0fc1c9180..485810825db1 100644 --- a/test/battle/ability/rivalry.c +++ b/test/battle/ability/rivalry.c @@ -101,3 +101,99 @@ SINGLE_BATTLE_TEST("Rivalry doesn't modify power if the target is genderless", s EXPECT(results[2].damage == results[3].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Rivalry increases power by x1.25 towards Pokémon of the same gender (Traits)", s16 damage) +{ + u16 species; + enum Ability ability; + PARAMETRIZE { species = SPECIES_NIDOKING; ability = ABILITY_POISON_POINT; } + PARAMETRIZE { species = SPECIES_NIDOKING; ability = ABILITY_RIVALRY; } + PARAMETRIZE { species = SPECIES_NIDOQUEEN; ability = ABILITY_POISON_POINT; } + PARAMETRIZE { species = SPECIES_NIDOQUEEN; ability = ABILITY_RIVALRY; } + + GIVEN { + PLAYER(species) { Ability(ABILITY_SHEER_FORCE); Innates(ability); } + OPPONENT(species); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.25), results[1].damage); + EXPECT_MUL_EQ(results[2].damage, Q_4_12(1.25), results[3].damage); + } +} + +SINGLE_BATTLE_TEST("Rivalry decreases power by x0.75 towards Pokémon of different gender (Traits)", s16 damage) +{ + u16 species1, species2; + enum Ability ability; + PARAMETRIZE { species1 = SPECIES_NIDOKING; species2 = SPECIES_NIDOQUEEN; ability = ABILITY_POISON_POINT; } + PARAMETRIZE { species1 = SPECIES_NIDOKING; species2 = SPECIES_NIDOQUEEN; ability = ABILITY_RIVALRY; } + PARAMETRIZE { species1 = SPECIES_NIDOQUEEN; species2 = SPECIES_NIDOKING; ability = ABILITY_POISON_POINT; } + PARAMETRIZE { species1 = SPECIES_NIDOQUEEN; species2 = SPECIES_NIDOKING; ability = ABILITY_RIVALRY; } + + GIVEN { + PLAYER(species1) { Ability(ABILITY_SHEER_FORCE); Innates(ability); } + OPPONENT(species2); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.75), results[1].damage); + EXPECT_MUL_EQ(results[2].damage, Q_4_12(0.75), results[3].damage); + } +} + +SINGLE_BATTLE_TEST("Rivalry doesn't modify power if the attacker is genderless (Traits)", s16 damage) +{ + u16 species; + enum Ability ability; + PARAMETRIZE { species = SPECIES_NIDOKING; ability = ABILITY_POISON_POINT; } + PARAMETRIZE { species = SPECIES_NIDOKING; ability = ABILITY_RIVALRY; } + PARAMETRIZE { species = SPECIES_NIDOQUEEN; ability = ABILITY_POISON_POINT; } + PARAMETRIZE { species = SPECIES_NIDOQUEEN; ability = ABILITY_RIVALRY; } + + GIVEN { + ASSUME(GetSpeciesAbility(SPECIES_PORYGON, 0) == ABILITY_TRACE); + PLAYER(SPECIES_PORYGON) { Ability(ABILITY_LEVITATE); Innates(ability); } // No genderless mon naturally gets Rivalry + OPPONENT(species) { Ability(ABILITY_SHEER_FORCE); Innates(ability); }; + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT(results[0].damage == results[1].damage); + EXPECT(results[2].damage == results[3].damage); + } +} + + +SINGLE_BATTLE_TEST("Rivalry doesn't modify power if the target is genderless (Traits)", s16 damage) +{ + u16 species; + enum Ability ability; + PARAMETRIZE { species = SPECIES_NIDOKING; ability = ABILITY_POISON_POINT; } + PARAMETRIZE { species = SPECIES_NIDOKING; ability = ABILITY_RIVALRY; } + PARAMETRIZE { species = SPECIES_NIDOQUEEN; ability = ABILITY_POISON_POINT; } + PARAMETRIZE { species = SPECIES_NIDOQUEEN; ability = ABILITY_RIVALRY; } + + GIVEN { + PLAYER(species) { Ability(ABILITY_SHEER_FORCE); Innates(ability); }; + OPPONENT(SPECIES_PORYGON); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT(results[0].damage == results[1].damage); + EXPECT(results[2].damage == results[3].damage); + } +} +#endif diff --git a/test/battle/ability/rocky_payload.c b/test/battle/ability/rocky_payload.c index d688e5c87e27..4ebdc40fda55 100644 --- a/test/battle/ability/rocky_payload.c +++ b/test/battle/ability/rocky_payload.c @@ -31,3 +31,36 @@ SINGLE_BATTLE_TEST("Rocky Payload increases Rock-type move damage", s16 damage) EXPECT_MUL_EQ(results[4].damage, Q_4_12(1.5), results[5].damage); // Power Gem should be affected } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Rocky Payload increases Rock-type move damage (Traits)", s16 damage) +{ + u32 move; + enum Ability ability; + + PARAMETRIZE { move = MOVE_SCRATCH; ability = ABILITY_BIG_PECKS; } + PARAMETRIZE { move = MOVE_SCRATCH; ability = ABILITY_ROCKY_PAYLOAD; } + PARAMETRIZE { move = MOVE_ROCK_THROW; ability = ABILITY_BIG_PECKS; } + PARAMETRIZE { move = MOVE_ROCK_THROW; ability = ABILITY_ROCKY_PAYLOAD; } + PARAMETRIZE { move = MOVE_POWER_GEM; ability = ABILITY_BIG_PECKS; } + PARAMETRIZE { move = MOVE_POWER_GEM; ability = ABILITY_ROCKY_PAYLOAD; } + + GIVEN { + ASSUME(GetMoveType(MOVE_SCRATCH) != TYPE_ROCK); + ASSUME(GetMoveType(MOVE_ROCK_THROW) == TYPE_ROCK); + ASSUME(GetMoveType(MOVE_POWER_GEM) == TYPE_ROCK); + ASSUME(GetMoveCategory(MOVE_ROCK_THROW) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_POWER_GEM) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_BOMBIRDIER) { Ability(ABILITY_KEEN_EYE); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); // Scratch should be unaffected + EXPECT_MUL_EQ(results[2].damage, Q_4_12(1.5), results[3].damage); // Rock Throw should be affected + EXPECT_MUL_EQ(results[4].damage, Q_4_12(1.5), results[5].damage); // Power Gem should be affected + } +} +#endif diff --git a/test/battle/ability/sand_force.c b/test/battle/ability/sand_force.c index ba062f3fa31e..6ba958da9eb5 100644 --- a/test/battle/ability/sand_force.c +++ b/test/battle/ability/sand_force.c @@ -61,3 +61,66 @@ SINGLE_BATTLE_TEST("Sand Force don't increase move power if Cloud Nine/Air Lock EXPECT_EQ(results[0].damage, results[1].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Sand Force prevents damage from sandstorm (Traits)") +{ + enum Type type1 = GetSpeciesType(SPECIES_SHELLOS, 0); + enum Type type2 = GetSpeciesType(SPECIES_SHELLOS, 1); + GIVEN { + ASSUME(type1 != TYPE_ROCK && type2 != TYPE_ROCK); + ASSUME(type1 != TYPE_GROUND && type2 != TYPE_GROUND); + ASSUME(type1 != TYPE_STEEL && type2 != TYPE_STEEL); + PLAYER(SPECIES_SHELLOS) { Ability(ABILITY_STICKY_HOLD); Innates(ABILITY_SAND_FORCE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SANDSTORM); } + } SCENE { + NOT HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Sand Force increases the power of Rock-, Ground- and Steel-type moves by 30% in sandstorm (Traits)", s16 damage) +{ + u32 moveOpponent, movePlayer; + PARAMETRIZE { moveOpponent = MOVE_CELEBRATE; movePlayer = MOVE_ROCK_THROW; } + PARAMETRIZE { moveOpponent = MOVE_SANDSTORM; movePlayer = MOVE_ROCK_THROW; } + PARAMETRIZE { moveOpponent = MOVE_CELEBRATE; movePlayer = MOVE_EARTHQUAKE; } + PARAMETRIZE { moveOpponent = MOVE_SANDSTORM; movePlayer = MOVE_EARTHQUAKE; } + PARAMETRIZE { moveOpponent = MOVE_CELEBRATE; movePlayer = MOVE_IRON_HEAD; } + PARAMETRIZE { moveOpponent = MOVE_SANDSTORM; movePlayer = MOVE_IRON_HEAD; } + GIVEN { + ASSUME(GetMoveType(MOVE_ROCK_THROW) == TYPE_ROCK); + ASSUME(GetMoveType(MOVE_EARTHQUAKE) == TYPE_GROUND); + ASSUME(GetMoveType(MOVE_IRON_HEAD) == TYPE_STEEL); + PLAYER(SPECIES_SHELLOS) { Ability(ABILITY_STICKY_HOLD); Innates(ABILITY_SAND_FORCE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, moveOpponent); MOVE(player, movePlayer); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.3), results[1].damage); + EXPECT_MUL_EQ(results[2].damage, Q_4_12(1.3), results[3].damage); + EXPECT_MUL_EQ(results[4].damage, Q_4_12(1.3), results[5].damage); + } +} + +SINGLE_BATTLE_TEST("Sand Force don't increase move power if Cloud Nine/Air Lock is on the field (Traits)", s16 damage) +{ + u32 move; + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SANDSTORM; } + GIVEN { + ASSUME(GetMoveType(MOVE_ROCK_THROW) == TYPE_ROCK); + PLAYER(SPECIES_SHELLOS) { Ability(ABILITY_STICKY_HOLD); Innates(ABILITY_SAND_FORCE); } + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_SWIFT_SWIM); Innates(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_ROCK_THROW); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} +#endif diff --git a/test/battle/ability/sand_rush.c b/test/battle/ability/sand_rush.c index 9d6ef98274e5..38c0d31057d3 100644 --- a/test/battle/ability/sand_rush.c +++ b/test/battle/ability/sand_rush.c @@ -49,3 +49,54 @@ SINGLE_BATTLE_TEST("Sand Rush doesn't double speed if Cloud Nine/Air Lock is on ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Sand Rush prevents damage from sandstorm (Traits)") +{ + enum Type type1 = GetSpeciesType(SPECIES_STOUTLAND, 0); + enum Type type2 = GetSpeciesType(SPECIES_STOUTLAND, 1); + GIVEN { + ASSUME(type1 != TYPE_ROCK && type2 != TYPE_ROCK); + ASSUME(type1 != TYPE_GROUND && type2 != TYPE_GROUND); + ASSUME(type1 != TYPE_STEEL && type2 != TYPE_STEEL); + PLAYER(SPECIES_STOUTLAND) { Ability(ABILITY_SCRAPPY); Innates(ABILITY_SAND_RUSH); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SANDSTORM); } + } SCENE { + NOT HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Sand Rush doubles speed from sandstorm (Traits)") +{ + GIVEN { + PLAYER(SPECIES_SANDSLASH) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_SAND_RUSH); Speed(100); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(199); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SANDSTORM); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SANDSTORM, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +SINGLE_BATTLE_TEST("Sand Rush doesn't double speed if Cloud Nine/Air Lock is on the field (Traits)") +{ + GIVEN { + PLAYER(SPECIES_SANDSLASH) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_SAND_RUSH); Speed(100); } + OPPONENT(SPECIES_GOLDUCK) { Speed(199); Ability(ABILITY_SWIFT_SWIM); Innates(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SANDSTORM); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SANDSTORM, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } +} +#endif diff --git a/test/battle/ability/sand_spit.c b/test/battle/ability/sand_spit.c index cb971794432e..23a937d6286a 100644 --- a/test/battle/ability/sand_spit.c +++ b/test/battle/ability/sand_spit.c @@ -75,3 +75,35 @@ SINGLE_BATTLE_TEST("Sand Spit triggers even if the user is knocked out by the hi MESSAGE("The sandstorm is raging."); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Sand Spit sets up sandstorm for 8 turns when hit with Smooth Rock (Multi)") +{ + GIVEN { + PLAYER(SPECIES_SANDSLASH) { Moves(MOVE_CELEBRATE); Ability(ABILITY_SAND_SPIT); Items(ITEM_PECHA_BERRY, ITEM_SMOOTH_ROCK); } + OPPONENT(SPECIES_LANDORUS) { Moves(MOVE_TACKLE, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + MESSAGE("The opposing Landorus used Tackle!"); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_SAND_SPIT); + MESSAGE("A sandstorm kicked up!"); + MESSAGE("The sandstorm is raging."); + MESSAGE("The sandstorm is raging."); + MESSAGE("The sandstorm is raging."); + MESSAGE("The sandstorm is raging."); + MESSAGE("The sandstorm is raging."); + MESSAGE("The sandstorm is raging."); + MESSAGE("The sandstorm is raging."); + MESSAGE("The sandstorm subsided."); + } +} +#endif diff --git a/test/battle/ability/sand_stream.c b/test/battle/ability/sand_stream.c index a1b9464c2a50..703e569f0e17 100644 --- a/test/battle/ability/sand_stream.c +++ b/test/battle/ability/sand_stream.c @@ -81,3 +81,33 @@ SINGLE_BATTLE_TEST("Sand Stream sets up permanent sandstorm (Gen3-5)") NOT MESSAGE("The sandstorm subsided."); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Sand Stream sets up sandstorm for 8 turns with Smooth Rock (Gen6+) (Multi)") +{ + GIVEN { + WITH_CONFIG(CONFIG_ABILITY_WEATHER, GEN_6); + PLAYER(SPECIES_HIPPOWDON) { Moves(MOVE_CELEBRATE); Ability(ABILITY_SAND_STREAM); Items(ITEM_PECHA_BERRY, ITEM_SMOOTH_ROCK); } + OPPONENT(SPECIES_SANDSLASH) { Moves(MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_SAND_STREAM); + MESSAGE("The sandstorm is raging."); + MESSAGE("The sandstorm is raging."); + MESSAGE("The sandstorm is raging."); + MESSAGE("The sandstorm is raging."); + MESSAGE("The sandstorm is raging."); + MESSAGE("The sandstorm is raging."); + MESSAGE("The sandstorm is raging."); + MESSAGE("The sandstorm subsided."); + } +} +#endif diff --git a/test/battle/ability/sand_veil.c b/test/battle/ability/sand_veil.c index e2bfdd02f85f..3b0d85fb429a 100644 --- a/test/battle/ability/sand_veil.c +++ b/test/battle/ability/sand_veil.c @@ -43,3 +43,48 @@ SINGLE_BATTLE_TEST("Sand Veil doesn't increase evasion if Cloud Nine/Air Lock is HP_BAR(player); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Sand Veil prevents damage from sandstorm (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CACNEA) { Ability(ABILITY_WATER_ABSORB); Innates(ABILITY_SAND_VEIL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SANDSTORM); } + TURN {} + } SCENE { + NONE_OF { HP_BAR(player); } + } +} + +SINGLE_BATTLE_TEST("Sand Veil increases evasion during sandstorm (Traits)") +{ + PASSES_RANDOMLY(4, 5, RNG_ACCURACY); + GIVEN { + ASSUME(GetMoveAccuracy(MOVE_POUND) == 100); + PLAYER(SPECIES_SANDSHREW) { Ability(ABILITY_SAND_RUSH); Innates(ABILITY_SAND_VEIL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SANDSTORM); } + TURN { MOVE(opponent, MOVE_POUND); } + } SCENE { + HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Sand Veil doesn't increase evasion if Cloud Nine/Air Lock is on the field (Traits)") +{ + PASSES_RANDOMLY(5, 5, RNG_ACCURACY); + GIVEN { + ASSUME(GetMoveAccuracy(MOVE_POUND) == 100); + PLAYER(SPECIES_SANDSHREW) { Ability(ABILITY_SAND_RUSH); Innates(ABILITY_SAND_VEIL); } + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_SWIFT_SWIM); Innates(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SANDSTORM); } + TURN { MOVE(opponent, MOVE_POUND); } + } SCENE { + HP_BAR(player); + } +} +#endif diff --git a/test/battle/ability/sap_sipper.c b/test/battle/ability/sap_sipper.c index aeb746d2c66a..263aa6e81f07 100644 --- a/test/battle/ability/sap_sipper.c +++ b/test/battle/ability/sap_sipper.c @@ -78,3 +78,83 @@ SINGLE_BATTLE_TEST("Sap Sipper blocks multi-hit grass type moves") } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Sap Sipper negates damage from Grass-type moves (Traits)") +{ + GIVEN { + PLAYER(SPECIES_MARILL) { Ability(ABILITY_THICK_FAT); Innates(ABILITY_SAP_SIPPER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_VINE_WHIP); } + } SCENE { + NONE_OF { HP_BAR(player); } + } +} + +SINGLE_BATTLE_TEST("Sap Sipper negates effects from Grass-type moves (Traits)") +{ + GIVEN { + PLAYER(SPECIES_MARILL) { Ability(ABILITY_THICK_FAT); Innates(ABILITY_SAP_SIPPER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SPORE); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, player); + STATUS_ICON(player, sleep: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Sap Sipper increases Attack by one stage when hit by a Grass-type move (Traits)") +{ + GIVEN { + PLAYER(SPECIES_MARILL) { Ability(ABILITY_THICK_FAT); Innates(ABILITY_SAP_SIPPER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_VINE_WHIP); } + } SCENE { + ABILITY_POPUP(player, ABILITY_SAP_SIPPER); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Marill's Attack rose!"); + } +} + +SINGLE_BATTLE_TEST("Sap Sipper does not increase Attack if already maxed (Traits)") +{ + GIVEN { + PLAYER(SPECIES_MARILL) { Ability(ABILITY_THICK_FAT); Innates(ABILITY_SAP_SIPPER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BELLY_DRUM); MOVE(opponent, MOVE_VINE_WHIP); } + } SCENE { + ABILITY_POPUP(player, ABILITY_SAP_SIPPER); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Marill's Attack rose!"); + } + } +} + +SINGLE_BATTLE_TEST("Sap Sipper blocks multi-hit grass type moves (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_BULLET_SEED) == EFFECT_MULTI_HIT); + PLAYER(SPECIES_MARILL) { Ability(ABILITY_THICK_FAT); Innates(ABILITY_SAP_SIPPER); } + OPPONENT(SPECIES_SHELLDER) { Ability(ABILITY_SHELL_ARMOR); Innates(ABILITY_SKILL_LINK); } + } WHEN { + TURN { MOVE(opponent, MOVE_BULLET_SEED); } + } SCENE { + MESSAGE("The opposing Shellder used Bullet Seed!"); + ABILITY_POPUP(player, ABILITY_SAP_SIPPER); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Marill's Attack rose!"); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, opponent); + HP_BAR(player); + MESSAGE("The Pokémon was hit 5 time(s)!"); + } + } +} +#endif diff --git a/test/battle/ability/schooling.c b/test/battle/ability/schooling.c index f4da6a2caf61..837717dce5aa 100644 --- a/test/battle/ability/schooling.c +++ b/test/battle/ability/schooling.c @@ -106,3 +106,111 @@ SINGLE_BATTLE_TEST("Schooling switches Level 20+ Wishiwashi's form when HP is he EXPECT_EQ(player->species, SPECIES_WISHIWASHI_SOLO); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Schooling switches Level 20+ Wishiwashi's form when HP is 25-percent or less at the end of the turn (Traits)") +{ + u16 level; + PARAMETRIZE { level = 19; } + PARAMETRIZE { level = 20; } + + GIVEN { + ASSUME(GetSpeciesBaseHP(SPECIES_WISHIWASHI_SOLO) == GetSpeciesBaseHP(SPECIES_WISHIWASHI_SCHOOL)); + PLAYER(SPECIES_WISHIWASHI_SOLO) + { + Level(level); + HP(GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP) / 2); + Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_SCHOOLING); + } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_SUPER_FANG); } + } SCENE { + if (level >= 20) + { + ABILITY_POPUP(player, ABILITY_SCHOOLING); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + } + MESSAGE("Wishiwashi used Celebrate!"); + MESSAGE("The opposing Wobbuffet used Super Fang!"); + HP_BAR(player); + if (level >= 20) + { + ABILITY_POPUP(player, ABILITY_SCHOOLING); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + } + } THEN { + EXPECT_EQ(player->species, SPECIES_WISHIWASHI_SOLO); + } +} + +SINGLE_BATTLE_TEST("Schooling switches Level 20+ Wishiwashi's form when HP is over 25-percent before the first turn (Traits)") +{ + u16 level; + bool32 overQuarterHP; + PARAMETRIZE { level = 19; overQuarterHP = FALSE; } + PARAMETRIZE { level = 20; overQuarterHP = FALSE; } + PARAMETRIZE { level = 19; overQuarterHP = TRUE; } + PARAMETRIZE { level = 20; overQuarterHP = TRUE; } + + GIVEN { + ASSUME(GetSpeciesBaseHP(SPECIES_WISHIWASHI_SOLO) == GetSpeciesBaseHP(SPECIES_WISHIWASHI_SCHOOL)); + PLAYER(SPECIES_WISHIWASHI_SOLO) + { + Level(level); + HP(GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP) / (overQuarterHP ? 2 : 4)); + Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_SCHOOLING); + } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + if (level >= 20 && overQuarterHP) + { + ABILITY_POPUP(player, ABILITY_SCHOOLING); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + } + MESSAGE("Wishiwashi used Celebrate!"); + MESSAGE("The opposing Wobbuffet used Celebrate!"); + } THEN { + if (level >= 20 && overQuarterHP) + EXPECT_EQ(player->species, SPECIES_WISHIWASHI_SCHOOL); + else + EXPECT_EQ(player->species, SPECIES_WISHIWASHI_SOLO); + } +} + +SINGLE_BATTLE_TEST("Schooling switches Level 20+ Wishiwashi's form when HP is healed above 25-percent (Traits)") +{ + u16 level; + PARAMETRIZE { level = 19; } + PARAMETRIZE { level = 20; } + + GIVEN { + ASSUME(GetSpeciesBaseHP(SPECIES_WISHIWASHI_SOLO) == GetSpeciesBaseHP(SPECIES_WISHIWASHI_SCHOOL)); + PLAYER(SPECIES_WISHIWASHI_SOLO) + { + Level(level); + HP(GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP) / 4); + Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_SCHOOLING); + } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_HEAL_PULSE); } + } SCENE { + MESSAGE("Wishiwashi used Celebrate!"); + MESSAGE("The opposing Wobbuffet used Heal Pulse!"); + HP_BAR(player); + if (level >= 20) + { + ABILITY_POPUP(player, ABILITY_SCHOOLING); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + } + } THEN { + if (level >= 20) + EXPECT_EQ(player->species, SPECIES_WISHIWASHI_SCHOOL); + else + EXPECT_EQ(player->species, SPECIES_WISHIWASHI_SOLO); + } +} +#endif diff --git a/test/battle/ability/scrappy.c b/test/battle/ability/scrappy.c index 58543911f862..4e1e9cf090a3 100644 --- a/test/battle/ability/scrappy.c +++ b/test/battle/ability/scrappy.c @@ -92,3 +92,97 @@ SINGLE_BATTLE_TEST("Scrappy doesn't bypass a Ghost-type's Wonder Guard") MESSAGE("The opposing Shedinja avoided damage with Wonder Guard!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Scrappy doesn't prevent Intimidate (Gen4-7) (Traits)") +{ + s16 turnOneHit; + s16 turnTwoHit; + + GIVEN { + WITH_CONFIG(CONFIG_UPDATED_INTIMIDATE, GEN_7); + PLAYER(SPECIES_EKANS) { Ability(ABILITY_MOXIE); Innates(ABILITY_SHED_SKIN); }; + PLAYER(SPECIES_EKANS) { Ability(ABILITY_MOXIE); Innates(ABILITY_INTIMIDATE); }; + OPPONENT(SPECIES_KANGASKHAN) { Ability(ABILITY_EARLY_BIRD); Innates(ABILITY_SCRAPPY); }; + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_SCRATCH); } + + } SCENE { + HP_BAR(player, captureDamage: &turnOneHit); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_SCRAPPY); + MESSAGE("The opposing Kangaskhan's Scrappy prevents stat loss!"); + } + HP_BAR(player, captureDamage: &turnTwoHit); + } THEN { + EXPECT_GT(turnOneHit, turnTwoHit); + } +} + +SINGLE_BATTLE_TEST("Scrappy prevents Intimidate (Gen8+) (Traits)") +{ + s16 turnOneHit; + s16 turnTwoHit; + + GIVEN { + WITH_CONFIG(CONFIG_UPDATED_INTIMIDATE, GEN_8); + PLAYER(SPECIES_EKANS) { Ability(ABILITY_MOXIE); Innates(ABILITY_SHED_SKIN); }; + PLAYER(SPECIES_EKANS) { Ability(ABILITY_MOXIE); Innates(ABILITY_INTIMIDATE); }; + OPPONENT(SPECIES_KANGASKHAN) { Ability(ABILITY_EARLY_BIRD); Innates(ABILITY_SCRAPPY); }; + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_SCRATCH); } + + } SCENE { + HP_BAR(player, captureDamage: &turnOneHit); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + NONE_OF { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); } + ABILITY_POPUP(opponent, ABILITY_SCRAPPY); + MESSAGE("The opposing Kangaskhan's Scrappy prevents stat loss!"); + HP_BAR(player, captureDamage: &turnTwoHit); + } THEN { + EXPECT_EQ(turnOneHit, turnTwoHit); + } +} + +SINGLE_BATTLE_TEST("Scrappy allows to hit Ghost-type Pokémon with Normal- and Fighting-type moves (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_KARATE_CHOP; } + + GIVEN { + PLAYER(SPECIES_KANGASKHAN) { Ability(ABILITY_EARLY_BIRD); Innates(ABILITY_SCRAPPY); }; + OPPONENT(SPECIES_GASTLY); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Scrappy doesn't bypass a Ghost-type's Wonder Guard (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_KARATE_CHOP; } + + GIVEN { + PLAYER(SPECIES_KANGASKHAN) { Ability(ABILITY_EARLY_BIRD); Innates(ABILITY_SCRAPPY); }; + OPPONENT(SPECIES_SHEDINJA) { Ability(ABILITY_EARLY_BIRD); Innates(ABILITY_WONDER_GUARD); }; + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + } + ABILITY_POPUP(opponent, ABILITY_WONDER_GUARD); + MESSAGE("The opposing Shedinja avoided damage with Wonder Guard!"); + } +} +#endif diff --git a/test/battle/ability/seed_sower.c b/test/battle/ability/seed_sower.c index 5785d4684a19..b94fde0ae411 100644 --- a/test/battle/ability/seed_sower.c +++ b/test/battle/ability/seed_sower.c @@ -143,3 +143,149 @@ DOUBLE_BATTLE_TEST("Multi-target moves hit correct battlers after Seed Sower is #undef ABILITY_PARAM #undef MOVE_HIT + + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Seed Sower sets up Grassy Terrain when hit by an attack (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ARBOLIVA) { Ability(ABILITY_HARVEST); Innates(ABILITY_SEED_SOWER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Scratch!"); + HP_BAR(player); + ABILITY_POPUP(player); + MESSAGE("Grass grew to cover the battlefield!"); + } +} + +#define ABILITY_PARAM(n)(abilities[n] = (k == n) ? ABILITY_SEED_SOWER : ABILITY_HARVEST) +#define MOVE_HIT(target, position) \ +{ \ + if (abilities[position] == ABILITY_SEED_SOWER) { \ + ABILITY_POPUP(target); \ + MESSAGE("Grass grew to cover the battlefield!");\ + } \ +} + +DOUBLE_BATTLE_TEST("Multi-target moves hit correct battlers after Seed Sower is triggered (Traits)") // #2796 +{ + u32 j, k, l; + u16 usedMove = MOVE_NONE; + static const u16 moves[] = {MOVE_HYPER_VOICE, MOVE_SURF}; + u16 abilities[MAX_BATTLERS_COUNT] = {0}; + u8 attacker = 0; + + for (j = 0; j < ARRAY_COUNT(moves); j++) + { + for (k = 0; k < MAX_BATTLERS_COUNT; k++) + { + for (l = 0; l < MAX_BATTLERS_COUNT; l++) + { + if (k == l) + continue; // No tests needed when attacker has Seed Sower + if ((k & BIT_SIDE) == (l & BIT_SIDE) && moves[j] == MOVE_HYPER_VOICE) + continue; // No tests needed when partners has Seed Sower and Hyper Voice is used. + PARAMETRIZE { attacker = l; usedMove = moves[j]; ABILITY_PARAM(0); ABILITY_PARAM(1); ABILITY_PARAM(2); ABILITY_PARAM(3); } + } + } + } + + GIVEN { + ASSUME(GetMoveTarget(MOVE_HYPER_VOICE) == MOVE_TARGET_BOTH); + ASSUME(GetMoveTarget(MOVE_SURF) == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(SPECIES_ARBOLIVA) { Ability(ABILITY_HARVEST); Innates(abilities[B_POSITION_PLAYER_LEFT]); } + PLAYER(SPECIES_ARBOLIVA) { Ability(ABILITY_HARVEST); Innates(abilities[B_POSITION_PLAYER_RIGHT]); } + OPPONENT(SPECIES_ARBOLIVA) { Ability(ABILITY_HARVEST); Innates(abilities[B_POSITION_OPPONENT_LEFT]); } + OPPONENT(SPECIES_ARBOLIVA) { Ability(ABILITY_HARVEST); Innates(abilities[B_POSITION_OPPONENT_RIGHT]); } + } WHEN { + TURN { + MOVE(opponentLeft, (attacker == B_POSITION_OPPONENT_LEFT) ? usedMove : MOVE_CELEBRATE); + MOVE(opponentRight, (attacker == B_POSITION_OPPONENT_RIGHT) ? usedMove : MOVE_CELEBRATE); + MOVE(playerLeft, (attacker == B_POSITION_PLAYER_LEFT) ? usedMove : MOVE_CELEBRATE); + MOVE(playerRight, (attacker == B_POSITION_PLAYER_RIGHT) ? usedMove : MOVE_CELEBRATE); + } + } SCENE { + // ANIMATION(ANIM_TYPE_MOVE, usedMove); + if (usedMove == MOVE_HYPER_VOICE) { + if ((attacker & BIT_SIDE) == B_SIDE_OPPONENT) { + if (attacker == B_POSITION_OPPONENT_LEFT) { + HP_BAR(playerLeft); + HP_BAR(playerRight); + MOVE_HIT(playerLeft, B_POSITION_PLAYER_LEFT); + MOVE_HIT(playerRight, B_POSITION_PLAYER_RIGHT); + } else { + HP_BAR(playerLeft); + HP_BAR(playerRight); + MOVE_HIT(playerRight, B_POSITION_PLAYER_RIGHT); + MOVE_HIT(playerLeft, B_POSITION_PLAYER_LEFT); + } + NONE_OF { + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + } + } else { + if (attacker == B_POSITION_PLAYER_LEFT) { + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + MOVE_HIT(opponentLeft, B_POSITION_OPPONENT_LEFT); + MOVE_HIT(opponentRight, B_POSITION_OPPONENT_RIGHT); + } else { + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + MOVE_HIT(opponentRight, B_POSITION_OPPONENT_RIGHT); + MOVE_HIT(opponentLeft, B_POSITION_OPPONENT_LEFT); + } + NONE_OF { + HP_BAR(playerLeft); + HP_BAR(playerRight); + } + } + } else { // SURF + switch (attacker) { + case B_POSITION_PLAYER_LEFT: + HP_BAR(opponentLeft); + HP_BAR(playerRight); + HP_BAR(opponentRight); + MOVE_HIT(opponentLeft, B_POSITION_OPPONENT_LEFT); + MOVE_HIT(playerRight, B_POSITION_PLAYER_RIGHT); + MOVE_HIT(opponentRight, B_POSITION_OPPONENT_RIGHT); + NOT HP_BAR(playerLeft); + break; + case B_POSITION_OPPONENT_LEFT: + HP_BAR(playerLeft); + HP_BAR(playerRight); + HP_BAR(opponentRight); + MOVE_HIT(playerLeft, B_POSITION_PLAYER_LEFT); + MOVE_HIT(playerRight, B_POSITION_PLAYER_RIGHT); + MOVE_HIT(opponentRight, B_POSITION_OPPONENT_RIGHT); + NOT HP_BAR(opponentLeft); + break; + case B_POSITION_PLAYER_RIGHT: + HP_BAR(playerLeft); + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + MOVE_HIT(playerLeft, B_POSITION_PLAYER_LEFT); + MOVE_HIT(opponentLeft, B_POSITION_OPPONENT_LEFT); + MOVE_HIT(opponentRight, B_POSITION_OPPONENT_RIGHT); + NOT HP_BAR(playerRight); + break; + case B_POSITION_OPPONENT_RIGHT: + HP_BAR(playerLeft); + HP_BAR(opponentLeft); + HP_BAR(playerRight); + MOVE_HIT(playerLeft, B_POSITION_PLAYER_LEFT); + MOVE_HIT(opponentLeft, B_POSITION_OPPONENT_LEFT); + MOVE_HIT(playerRight, B_POSITION_PLAYER_RIGHT); + NOT HP_BAR(opponentRight); + break; + } + } + } +} + +#undef ABILITY_PARAM +#undef MOVE_HIT +#endif diff --git a/test/battle/ability/sharpness.c b/test/battle/ability/sharpness.c index f737bab72a55..ca99995eea8e 100644 --- a/test/battle/ability/sharpness.c +++ b/test/battle/ability/sharpness.c @@ -24,3 +24,29 @@ SINGLE_BATTLE_TEST("Sharpness increases the power of slicing moves by 50%", s16 EXPECT_EQ(results[2].damage, results[3].damage); // Sharpness does not affect non-slicing moves } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Sharpness increases the power of slicing moves by 50% (Traits)", s16 damage) +{ + u32 move; + enum Ability ability; + PARAMETRIZE { move = MOVE_AERIAL_ACE; ability = ABILITY_SHARPNESS; } + PARAMETRIZE { move = MOVE_AERIAL_ACE; ability = ABILITY_STEADFAST; } + PARAMETRIZE { move = MOVE_SCRATCH; ability = ABILITY_SHARPNESS; } + PARAMETRIZE { move = MOVE_SCRATCH; ability = ABILITY_STEADFAST; } + + GIVEN { + ASSUME(IsSlicingMove(MOVE_AERIAL_ACE)); + ASSUME(!IsSlicingMove(MOVE_SCRATCH)); + PLAYER(SPECIES_GALLADE) { Ability(ABILITY_JUSTIFIED); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[1].damage, Q_4_12(1.5), results[0].damage); // Sharpness affects slicing moves + EXPECT_EQ(results[2].damage, results[3].damage); // Sharpness does not affect non-slicing moves + } +} +#endif diff --git a/test/battle/ability/shed_skin.c b/test/battle/ability/shed_skin.c index 73f09bc9e9de..32759771cb00 100644 --- a/test/battle/ability/shed_skin.c +++ b/test/battle/ability/shed_skin.c @@ -22,3 +22,24 @@ SINGLE_BATTLE_TEST("Shed Skin triggers 33% (Gen3, Gen5+) or 30% (Gen 4) of the t STATUS_ICON(opponent, poison: FALSE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Shed Skin triggers 33% of the time (Traits)") +{ + if (B_ABILITY_TRIGGER_CHANCE == GEN_4) + PASSES_RANDOMLY(30, 100, RNG_SHED_SKIN); + else + PASSES_RANDOMLY(33, 100, RNG_SHED_SKIN); + GIVEN { + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ARBOK) { Status1(STATUS1_POISON); Ability(ABILITY_UNNERVE); Innates(ABILITY_SHED_SKIN); } + } WHEN { + TURN; + } SCENE { + ABILITY_POPUP(opponent, ABILITY_SHED_SKIN); + MESSAGE("The opposing Arbok's Shed Skin cured its poison problem!"); + STATUS_ICON(opponent, poison: FALSE); + } +} +#endif diff --git a/test/battle/ability/sheer_force.c b/test/battle/ability/sheer_force.c index 701c96360e49..becbf7610bfe 100644 --- a/test/battle/ability/sheer_force.c +++ b/test/battle/ability/sheer_force.c @@ -1422,3 +1422,1964 @@ AI_SINGLE_BATTLE_TEST("AI sees Sheer Force skips additional effects") TURN { EXPECT_MOVE(opponent, expectedMove); } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Magnitude (Traits)", s16 damage) +{ + enum Ability ability = 0; + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } + PARAMETRIZE { ability = ABILITY_ANGER_POINT; } + GIVEN { + PLAYER(SPECIES_TAUROS) { Ability(ABILITY_INTIMIDATE); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_MAGNITUDE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Eruption (Traits)", s16 damage) +{ + enum Ability ability = 0; + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } + PARAMETRIZE { ability = ABILITY_ANGER_POINT; } + GIVEN { + PLAYER(SPECIES_TAUROS) { Ability(ABILITY_INTIMIDATE); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ERUPTION); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Water Spout (Traits)", s16 damage) +{ + enum Ability ability = 0; + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } + PARAMETRIZE { ability = ABILITY_ANGER_POINT; } + GIVEN { + PLAYER(SPECIES_TAUROS) { Ability(ABILITY_INTIMIDATE); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_WATER_SPOUT); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Present (Traits)", s16 damage) +{ + enum Ability ability = 0; + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } + PARAMETRIZE { ability = ABILITY_ANGER_POINT; } + GIVEN { + PLAYER(SPECIES_TAUROS) { Ability(ABILITY_INTIMIDATE); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + //Test will fail if present heals because the hp change would be 0 + //so we want a damaging version of present + TURN { MOVE(player, MOVE_PRESENT, WITH_RNG(RNG_PRESENT, 1)); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Psywave (Traits)", s16 damage) +{ + enum Ability ability = 0; + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } + PARAMETRIZE { ability = ABILITY_ANGER_POINT; } + GIVEN { + PLAYER(SPECIES_TAUROS) { Ability(ABILITY_INTIMIDATE); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PSYWAVE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Round (Traits)", s16 damage) +{ + enum Ability ability = 0; + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } + PARAMETRIZE { ability = ABILITY_ANGER_POINT; } + GIVEN { + PLAYER(SPECIES_TAUROS) { Ability(ABILITY_INTIMIDATE); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROUND); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Gyro Ball (Traits)", s16 damage) +{ + enum Ability ability = 0; + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } + PARAMETRIZE { ability = ABILITY_ANGER_POINT; } + GIVEN { + PLAYER(SPECIES_TAUROS) { Ability(ABILITY_INTIMIDATE); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_GYRO_BALL); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Electro Ball (Traits)", s16 damage) +{ + enum Ability ability = 0; + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } + PARAMETRIZE { ability = ABILITY_ANGER_POINT; } + GIVEN { + PLAYER(SPECIES_TAUROS) { Ability(ABILITY_INTIMIDATE); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ELECTRO_BALL); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Dragon Energy (Traits)", s16 damage) +{ + enum Ability ability = 0; + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } + PARAMETRIZE { ability = ABILITY_ANGER_POINT; } + GIVEN { + PLAYER(SPECIES_TAUROS) { Ability(ABILITY_INTIMIDATE); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_ENERGY); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Belch (Traits)", s16 damage) +{ + enum Ability ability = 0; + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } + PARAMETRIZE { ability = ABILITY_ANGER_POINT; } + GIVEN { + PLAYER(SPECIES_TAUROS) { Ability(ABILITY_INTIMIDATE); Innates(ability); HP(1); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BELCH); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Shell Trap (Traits)", s16 damage) +{ + enum Ability ability = 0; + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } + PARAMETRIZE { ability = ABILITY_ANGER_POINT; } + GIVEN { + PLAYER(SPECIES_TAUROS) { Ability(ABILITY_INTIMIDATE); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SHELL_TRAP); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Burn Up (Traits)", s16 damage) +{ + enum Ability ability = 0; + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } + PARAMETRIZE { ability = ABILITY_ZEN_MODE; } + GIVEN { + PLAYER(SPECIES_DARMANITAN) { Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BURN_UP); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Double Shock (Traits)", s16 damage) +{ + u16 move = 0; + PARAMETRIZE { move = MOVE_SKILL_SWAP; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + GIVEN { + PLAYER(SPECIES_PIKACHU); + OPPONENT(SPECIES_TAUROS) { Ability(ABILITY_INTIMIDATE); Innates(ABILITY_SHEER_FORCE); }; + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_DOUBLE_SHOCK); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Steel Roller (Traits)", s16 damage) +{ + enum Ability ability = 0; + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } + PARAMETRIZE { ability = ABILITY_ANGER_POINT; } + GIVEN { + PLAYER(SPECIES_TAUROS) { Ability(ABILITY_INTIMIDATE); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_GRASSY_TERRAIN); MOVE(player, MOVE_STEEL_ROLLER); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Synchronoise (Traits)", s16 damage) +{ + enum Ability ability = 0; + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } + PARAMETRIZE { ability = ABILITY_ANGER_POINT; } + GIVEN { + PLAYER(SPECIES_TAUROS) { Ability(ABILITY_INTIMIDATE); Innates(ability); HP(1); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_CHANSEY); + } WHEN { + TURN { MOVE(player, MOVE_SYNCHRONOISE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Aura Wheel (Traits)", s16 damage) +{ + u16 move = 0; + PARAMETRIZE { move = MOVE_SKILL_SWAP; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + GIVEN { + PLAYER(SPECIES_MORPEKO); + OPPONENT(SPECIES_TAUROS) { Ability(ABILITY_INTIMIDATE); Innates(ABILITY_SHEER_FORCE); }; + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_AURA_WHEEL); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Hyperspace Fury (Traits)", s16 damage) +{ + u16 move = 0; + PARAMETRIZE { move = MOVE_SKILL_SWAP; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + GIVEN { + PLAYER(SPECIES_HOOPA_UNBOUND); + OPPONENT(SPECIES_TAUROS) { Ability(ABILITY_INTIMIDATE); Innates(ABILITY_SHEER_FORCE); }; + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_HYPERSPACE_FURY); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Bolt Beak (Traits)", s16 damage) +{ + enum Ability ability = 0; + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } + PARAMETRIZE { ability = ABILITY_ANGER_POINT; } + GIVEN { + PLAYER(SPECIES_TAUROS) { Ability(ABILITY_INTIMIDATE); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BOLT_BEAK); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Fishious Rend (Traits)", s16 damage) +{ + enum Ability ability = 0; + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } + PARAMETRIZE { ability = ABILITY_ANGER_POINT; } + GIVEN { + PLAYER(SPECIES_TAUROS) { Ability(ABILITY_INTIMIDATE); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FISHIOUS_REND); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Comeuppance (Traits)", s16 damage) +{ + enum Ability ability = 0; + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } + PARAMETRIZE { ability = ABILITY_ANGER_POINT; } + GIVEN { + PLAYER(SPECIES_TAUROS) { Ability(ABILITY_INTIMIDATE); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_COMEUPPANCE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Payback (Traits)", s16 damage) +{ + enum Ability ability = 0; + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } + PARAMETRIZE { ability = ABILITY_ANGER_POINT; } + GIVEN { + PLAYER(SPECIES_TAUROS) { Ability(ABILITY_INTIMIDATE); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PAYBACK); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} + +// Tests split by generation +DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to boost (Gen1) (Traits)") +{ + s16 damage1, damage2; + u32 move = 0; + for (u32 j = MOVE_POUND; j < MOVES_COUNT_GEN1; j++) + { + if (GetMoveCategory(j) != DAMAGE_CATEGORY_STATUS && !IgnoreMoveForSheerForceBoost(j)) + PARAMETRIZE { move = j; } + } + GIVEN { + PLAYER(SPECIES_STEELIX) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_SHEER_FORCE); Item(ITEM_BLUK_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_TELEPATHY); Level(100); Item(ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_STEELIX) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_STURDY); Item(ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_TELEPATHY); Level(100); Item(ITEM_BLUK_BERRY); } + } WHEN { + if (move == MOVE_ALLURING_VOICE || move == MOVE_BURNING_JEALOUSY) // Alluring Voice requires the target to boost stats to have an effect + TURN { MOVE(opponentRight, MOVE_AGILITY); MOVE(playerRight, MOVE_AGILITY); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_UPPER_HAND) // Upper Hand requires the target to be using a damaging priority move + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); MOVE(playerRight, move, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_COUNTER || move == MOVE_UPPER_HAND) + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); + MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); + MOVE(playerLeft, move, target: opponentRight); + MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_MIRROR_COAT || move == MOVE_METAL_BURST) + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_SUCKER_PUNCH || move == MOVE_THUNDERCLAP) + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_DREAM_EATER) + { + TURN { MOVE(playerLeft, MOVE_HYPNOSIS, target: opponentRight); MOVE(opponentLeft, MOVE_HYPNOSIS, target: playerRight); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SNORE) + { + TURN { MOVE(opponentRight, MOVE_HYPNOSIS, target: playerLeft); MOVE(playerRight, MOVE_HYPNOSIS, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SPIT_UP || move == MOVE_LAST_RESORT) + { + TURN { MOVE(playerLeft, MOVE_STOCKPILE); MOVE(opponentLeft, MOVE_STOCKPILE); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + switch (GetMoveEffect(move)) + { + case EFFECT_TWO_TURNS_ATTACK: + case EFFECT_SEMI_INVULNERABLE: + case EFFECT_SOLAR_BEAM: + case EFFECT_SKY_DROP: + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { ; } + break; + case EFFECT_FUTURE_SIGHT: + TURN { ; } + TURN { ; } + break; + case EFFECT_BIDE: + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + break; + default: + break; + } + } SCENE { + if (GetMoveEffect(move) != EFFECT_FUTURE_SIGHT) + { + HP_BAR(opponentRight, captureDamage: &damage1); + HP_BAR(playerRight, captureDamage: &damage2); + } + else + { + HP_BAR(playerRight, captureDamage: &damage2); + HP_BAR(opponentRight, captureDamage: &damage1); + } + } THEN { + if (IsMoveSheerForceBoosted(move)) + EXPECT_GT(damage1, damage2); + else + EXPECT_EQ(damage2, damage1); + } +} + +DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to boost (Gen2) (Traits)") +{ + s16 damage1, damage2; + u32 move = 0; + for (u32 j = MOVE_SKETCH; j < MOVES_COUNT_GEN2; j++) + { + if (GetMoveCategory(j) != DAMAGE_CATEGORY_STATUS && !IgnoreMoveForSheerForceBoost(j)) + PARAMETRIZE { move = j; } + } + GIVEN { + PLAYER(SPECIES_STEELIX) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_SHEER_FORCE); Item(ITEM_BLUK_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_TELEPATHY); Level(100); Item(ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_STEELIX) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_STURDY); Item(ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_TELEPATHY); Level(100); Item(ITEM_BLUK_BERRY); } + } WHEN { + if (move == MOVE_ALLURING_VOICE || move == MOVE_BURNING_JEALOUSY) // Alluring Voice requires the target to boost stats to have an effect + TURN { MOVE(opponentRight, MOVE_AGILITY); MOVE(playerRight, MOVE_AGILITY); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_UPPER_HAND) // Upper Hand requires the target to be using a damaging priority move + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); MOVE(playerRight, move, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_COUNTER || move == MOVE_UPPER_HAND) + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); + MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); + MOVE(playerLeft, move, target: opponentRight); + MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_MIRROR_COAT || move == MOVE_METAL_BURST) + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_SUCKER_PUNCH || move == MOVE_THUNDERCLAP) + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_DREAM_EATER) + { + TURN { MOVE(playerLeft, MOVE_HYPNOSIS, target: opponentRight); MOVE(opponentLeft, MOVE_HYPNOSIS, target: playerRight); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SNORE) + { + TURN { MOVE(opponentRight, MOVE_HYPNOSIS, target: playerLeft); MOVE(playerRight, MOVE_HYPNOSIS, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SPIT_UP || move == MOVE_LAST_RESORT) + { + TURN { MOVE(playerLeft, MOVE_STOCKPILE); MOVE(opponentLeft, MOVE_STOCKPILE); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + switch (GetMoveEffect(move)) + { + case EFFECT_TWO_TURNS_ATTACK: + case EFFECT_SEMI_INVULNERABLE: + case EFFECT_SOLAR_BEAM: + case EFFECT_SKY_DROP: + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { ; } + break; + case EFFECT_FUTURE_SIGHT: + TURN { ; } + TURN { ; } + break; + case EFFECT_BIDE: + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + break; + default: + break; + } + } SCENE { + if (GetMoveEffect(move) != EFFECT_FUTURE_SIGHT) + { + HP_BAR(opponentRight, captureDamage: &damage1); + HP_BAR(playerRight, captureDamage: &damage2); + } + else + { + HP_BAR(playerRight, captureDamage: &damage2); + HP_BAR(opponentRight, captureDamage: &damage1); + } + } THEN { + if (IsMoveSheerForceBoosted(move)) + EXPECT_GT(damage1, damage2); + else + EXPECT_EQ(damage2, damage1); + } +} + +DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to boost (Gen3) (Traits)") +{ + s16 damage1, damage2; + u32 move = 0; + for (u32 j = MOVE_FAKE_OUT; j < MOVES_COUNT_GEN3; j++) + { + if (GetMoveCategory(j) != DAMAGE_CATEGORY_STATUS && !IgnoreMoveForSheerForceBoost(j)) + PARAMETRIZE { move = j; } + } + GIVEN { + PLAYER(SPECIES_STEELIX) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_SHEER_FORCE); Item(ITEM_BLUK_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_TELEPATHY); Level(100); Item(ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_STEELIX) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_STURDY); Item(ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_TELEPATHY); Level(100); Item(ITEM_BLUK_BERRY); } + } WHEN { + if (move == MOVE_ALLURING_VOICE || move == MOVE_BURNING_JEALOUSY) // Alluring Voice requires the target to boost stats to have an effect + TURN { MOVE(opponentRight, MOVE_AGILITY); MOVE(playerRight, MOVE_AGILITY); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_UPPER_HAND) // Upper Hand requires the target to be using a damaging priority move + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); MOVE(playerRight, move, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_COUNTER || move == MOVE_UPPER_HAND) + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); + MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); + MOVE(playerLeft, move, target: opponentRight); + MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_MIRROR_COAT || move == MOVE_METAL_BURST) + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_SUCKER_PUNCH || move == MOVE_THUNDERCLAP) + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_DREAM_EATER) + { + TURN { MOVE(playerLeft, MOVE_HYPNOSIS, target: opponentRight); MOVE(opponentLeft, MOVE_HYPNOSIS, target: playerRight); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SNORE) + { + TURN { MOVE(opponentRight, MOVE_HYPNOSIS, target: playerLeft); MOVE(playerRight, MOVE_HYPNOSIS, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SPIT_UP || move == MOVE_LAST_RESORT) + { + TURN { MOVE(playerLeft, MOVE_STOCKPILE); MOVE(opponentLeft, MOVE_STOCKPILE); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + switch (GetMoveEffect(move)) + { + case EFFECT_TWO_TURNS_ATTACK: + case EFFECT_SEMI_INVULNERABLE: + case EFFECT_SOLAR_BEAM: + case EFFECT_SKY_DROP: + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { ; } + break; + case EFFECT_FUTURE_SIGHT: + TURN { ; } + TURN { ; } + break; + case EFFECT_BIDE: + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + break; + default: + break; + } + } SCENE { + if (GetMoveEffect(move) != EFFECT_FUTURE_SIGHT) + { + HP_BAR(opponentRight, captureDamage: &damage1); + HP_BAR(playerRight, captureDamage: &damage2); + } + else + { + HP_BAR(playerRight, captureDamage: &damage2); + HP_BAR(opponentRight, captureDamage: &damage1); + } + } THEN { + if (IsMoveSheerForceBoosted(move)) + EXPECT_GT(damage1, damage2); + else + EXPECT_EQ(damage2, damage1); + } +} + +DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to boost (Gen4) (Traits)") +{ + s16 damage1, damage2; + u32 move = 0; + for (u32 j = MOVE_ROOST; j < MOVES_COUNT_GEN4; j++) + { + if (GetMoveCategory(j) != DAMAGE_CATEGORY_STATUS && !IgnoreMoveForSheerForceBoost(j)) + PARAMETRIZE { move = j; } + } + GIVEN { + PLAYER(SPECIES_STEELIX) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_SHEER_FORCE); Item(ITEM_BLUK_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_TELEPATHY); Level(100); Item(ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_STEELIX) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_STURDY); Item(ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_TELEPATHY); Level(100); Item(ITEM_BLUK_BERRY); } + } WHEN { + if (move == MOVE_ALLURING_VOICE || move == MOVE_BURNING_JEALOUSY) // Alluring Voice requires the target to boost stats to have an effect + TURN { MOVE(opponentRight, MOVE_AGILITY); MOVE(playerRight, MOVE_AGILITY); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_UPPER_HAND) // Upper Hand requires the target to be using a damaging priority move + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); MOVE(playerRight, move, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_COUNTER || move == MOVE_UPPER_HAND) + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); + MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); + MOVE(playerLeft, move, target: opponentRight); + MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_MIRROR_COAT || move == MOVE_METAL_BURST) + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_SUCKER_PUNCH || move == MOVE_THUNDERCLAP) + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_DREAM_EATER) + { + TURN { MOVE(playerLeft, MOVE_HYPNOSIS, target: opponentRight); MOVE(opponentLeft, MOVE_HYPNOSIS, target: playerRight); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SNORE) + { + TURN { MOVE(opponentRight, MOVE_HYPNOSIS, target: playerLeft); MOVE(playerRight, MOVE_HYPNOSIS, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SPIT_UP || move == MOVE_LAST_RESORT) + { + TURN { MOVE(playerLeft, MOVE_STOCKPILE); MOVE(opponentLeft, MOVE_STOCKPILE); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + switch (GetMoveEffect(move)) + { + case EFFECT_TWO_TURNS_ATTACK: + case EFFECT_SEMI_INVULNERABLE: + case EFFECT_SOLAR_BEAM: + case EFFECT_SKY_DROP: + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { ; } + break; + case EFFECT_FUTURE_SIGHT: + TURN { ; } + TURN { ; } + break; + case EFFECT_BIDE: + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + break; + default: + break; + } + } SCENE { + if (GetMoveEffect(move) != EFFECT_FUTURE_SIGHT) + { + HP_BAR(opponentRight, captureDamage: &damage1); + HP_BAR(playerRight, captureDamage: &damage2); + } + else + { + HP_BAR(playerRight, captureDamage: &damage2); + HP_BAR(opponentRight, captureDamage: &damage1); + } + } THEN { + if (IsMoveSheerForceBoosted(move)) + EXPECT_GT(damage1, damage2); + else + EXPECT_EQ(damage2, damage1); + } +} + +DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to boost (Gen5) (Traits)") +{ + s16 damage1, damage2; + u32 move = 0; + for (u32 j = MOVE_HONE_CLAWS; j < MOVES_COUNT_GEN5; j++) + { + if (GetMoveCategory(j) != DAMAGE_CATEGORY_STATUS && !IgnoreMoveForSheerForceBoost(j)) + PARAMETRIZE { move = j; } + } + GIVEN { + PLAYER(SPECIES_STEELIX) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_SHEER_FORCE); Item(ITEM_BLUK_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_TELEPATHY); Level(100); Item(ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_STEELIX) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_STURDY); Item(ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_TELEPATHY); Level(100); Item(ITEM_BLUK_BERRY); } + } WHEN { + if (move == MOVE_ALLURING_VOICE || move == MOVE_BURNING_JEALOUSY) // Alluring Voice requires the target to boost stats to have an effect + TURN { MOVE(opponentRight, MOVE_AGILITY); MOVE(playerRight, MOVE_AGILITY); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_UPPER_HAND) // Upper Hand requires the target to be using a damaging priority move + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); MOVE(playerRight, move, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_COUNTER || move == MOVE_UPPER_HAND) + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); + MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); + MOVE(playerLeft, move, target: opponentRight); + MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_MIRROR_COAT || move == MOVE_METAL_BURST) + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_SUCKER_PUNCH || move == MOVE_THUNDERCLAP) + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_DREAM_EATER) + { + TURN { MOVE(playerLeft, MOVE_HYPNOSIS, target: opponentRight); MOVE(opponentLeft, MOVE_HYPNOSIS, target: playerRight); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SNORE) + { + TURN { MOVE(opponentRight, MOVE_HYPNOSIS, target: playerLeft); MOVE(playerRight, MOVE_HYPNOSIS, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SPIT_UP || move == MOVE_LAST_RESORT) + { + TURN { MOVE(playerLeft, MOVE_STOCKPILE); MOVE(opponentLeft, MOVE_STOCKPILE); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + switch (GetMoveEffect(move)) + { + case EFFECT_TWO_TURNS_ATTACK: + case EFFECT_SEMI_INVULNERABLE: + case EFFECT_SOLAR_BEAM: + case EFFECT_SKY_DROP: + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { ; } + break; + case EFFECT_FUTURE_SIGHT: + TURN { ; } + TURN { ; } + break; + case EFFECT_BIDE: + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + break; + default: + break; + } + } SCENE { + if (GetMoveEffect(move) != EFFECT_FUTURE_SIGHT) + { + HP_BAR(opponentRight, captureDamage: &damage1); + HP_BAR(playerRight, captureDamage: &damage2); + } + else + { + HP_BAR(playerRight, captureDamage: &damage2); + HP_BAR(opponentRight, captureDamage: &damage1); + } + } THEN { + if (IsMoveSheerForceBoosted(move)) + EXPECT_GT(damage1, damage2); + else + EXPECT_EQ(damage2, damage1); + } +} + +DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to boost (Gen6) (Traits)") +{ + s16 damage1, damage2; + u32 move = 0; + for (u32 j = MOVE_FLYING_PRESS; j < MOVES_COUNT_GEN6; j++) + { + if (GetMoveCategory(j) != DAMAGE_CATEGORY_STATUS && !IgnoreMoveForSheerForceBoost(j)) + PARAMETRIZE { move = j; } + } + GIVEN { + PLAYER(SPECIES_STEELIX) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_SHEER_FORCE); Item(ITEM_BLUK_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_TELEPATHY); Level(100); Item(ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_STEELIX) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_STURDY); Item(ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_TELEPATHY); Level(100); Item(ITEM_BLUK_BERRY); } + } WHEN { + if (move == MOVE_ALLURING_VOICE || move == MOVE_BURNING_JEALOUSY) // Alluring Voice requires the target to boost stats to have an effect + TURN { MOVE(opponentRight, MOVE_AGILITY); MOVE(playerRight, MOVE_AGILITY); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_UPPER_HAND) // Upper Hand requires the target to be using a damaging priority move + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); MOVE(playerRight, move, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_COUNTER || move == MOVE_UPPER_HAND) + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); + MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); + MOVE(playerLeft, move, target: opponentRight); + MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_MIRROR_COAT || move == MOVE_METAL_BURST) + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_SUCKER_PUNCH || move == MOVE_THUNDERCLAP) + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_DREAM_EATER) + { + TURN { MOVE(playerLeft, MOVE_HYPNOSIS, target: opponentRight); MOVE(opponentLeft, MOVE_HYPNOSIS, target: playerRight); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SNORE) + { + TURN { MOVE(opponentRight, MOVE_HYPNOSIS, target: playerLeft); MOVE(playerRight, MOVE_HYPNOSIS, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SPIT_UP || move == MOVE_LAST_RESORT) + { + TURN { MOVE(playerLeft, MOVE_STOCKPILE); MOVE(opponentLeft, MOVE_STOCKPILE); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + switch (GetMoveEffect(move)) + { + case EFFECT_TWO_TURNS_ATTACK: + case EFFECT_SEMI_INVULNERABLE: + case EFFECT_SOLAR_BEAM: + case EFFECT_SKY_DROP: + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { ; } + break; + case EFFECT_FUTURE_SIGHT: + TURN { ; } + TURN { ; } + break; + case EFFECT_BIDE: + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + break; + default: + break; + } + } SCENE { + if (GetMoveEffect(move) != EFFECT_FUTURE_SIGHT) + { + HP_BAR(opponentRight, captureDamage: &damage1); + HP_BAR(playerRight, captureDamage: &damage2); + } + else + { + HP_BAR(playerRight, captureDamage: &damage2); + HP_BAR(opponentRight, captureDamage: &damage1); + } + } THEN { + if (IsMoveSheerForceBoosted(move)) + EXPECT_GT(damage1, damage2); + else + EXPECT_EQ(damage2, damage1); + } +} + +DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to boost (Gen7) (Traits)") +{ + s16 damage1, damage2; + u32 move = 0; + for (u32 j = MOVE_SHORE_UP; j < MOVES_COUNT_GEN7; j++) + { + if (GetMoveCategory(j) != DAMAGE_CATEGORY_STATUS && !IgnoreMoveForSheerForceBoost(j)) + PARAMETRIZE { move = j; } + } + GIVEN { + PLAYER(SPECIES_STEELIX) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_SHEER_FORCE); Item(ITEM_BLUK_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_TELEPATHY); Level(100); Item(ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_STEELIX) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_STURDY); Item(ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_TELEPATHY); Level(100); Item(ITEM_BLUK_BERRY); } + } WHEN { + if (move == MOVE_ALLURING_VOICE || move == MOVE_BURNING_JEALOUSY) // Alluring Voice requires the target to boost stats to have an effect + TURN { MOVE(opponentRight, MOVE_AGILITY); MOVE(playerRight, MOVE_AGILITY); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_UPPER_HAND) // Upper Hand requires the target to be using a damaging priority move + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); MOVE(playerRight, move, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_COUNTER || move == MOVE_UPPER_HAND) + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); + MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); + MOVE(playerLeft, move, target: opponentRight); + MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_MIRROR_COAT || move == MOVE_METAL_BURST) + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_SUCKER_PUNCH || move == MOVE_THUNDERCLAP) + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_DREAM_EATER) + { + TURN { MOVE(playerLeft, MOVE_HYPNOSIS, target: opponentRight); MOVE(opponentLeft, MOVE_HYPNOSIS, target: playerRight); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SNORE) + { + TURN { MOVE(opponentRight, MOVE_HYPNOSIS, target: playerLeft); MOVE(playerRight, MOVE_HYPNOSIS, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SPIT_UP || move == MOVE_LAST_RESORT) + { + TURN { MOVE(playerLeft, MOVE_STOCKPILE); MOVE(opponentLeft, MOVE_STOCKPILE); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + switch (GetMoveEffect(move)) + { + case EFFECT_TWO_TURNS_ATTACK: + case EFFECT_SEMI_INVULNERABLE: + case EFFECT_SOLAR_BEAM: + case EFFECT_SKY_DROP: + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { ; } + break; + case EFFECT_FUTURE_SIGHT: + TURN { ; } + TURN { ; } + break; + case EFFECT_BIDE: + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + break; + default: + break; + } + } SCENE { + if (GetMoveEffect(move) != EFFECT_FUTURE_SIGHT) + { + HP_BAR(opponentRight, captureDamage: &damage1); + HP_BAR(playerRight, captureDamage: &damage2); + } + else + { + HP_BAR(playerRight, captureDamage: &damage2); + HP_BAR(opponentRight, captureDamage: &damage1); + } + } THEN { + if (IsMoveSheerForceBoosted(move)) + EXPECT_GT(damage1, damage2); + else + EXPECT_EQ(damage2, damage1); + } +} + +DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to boost (Gen8) (Traits)") +{ + s16 damage1, damage2; + u32 move = 0; + for (u32 j = MOVE_DYNAMAX_CANNON; j < MOVES_COUNT_GEN8; j++) + { + if (GetMoveCategory(j) != DAMAGE_CATEGORY_STATUS && !IgnoreMoveForSheerForceBoost(j)) + PARAMETRIZE { move = j; } + } + GIVEN { + PLAYER(SPECIES_STEELIX) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_SHEER_FORCE); Item(ITEM_BLUK_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_TELEPATHY); Level(100); Item(ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_STEELIX) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_STURDY); Item(ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_TELEPATHY); Level(100); Item(ITEM_BLUK_BERRY); } + } WHEN { + if (move == MOVE_ALLURING_VOICE || move == MOVE_BURNING_JEALOUSY) // Alluring Voice requires the target to boost stats to have an effect + TURN { MOVE(opponentRight, MOVE_AGILITY); MOVE(playerRight, MOVE_AGILITY); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_UPPER_HAND) // Upper Hand requires the target to be using a damaging priority move + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); MOVE(playerRight, move, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_COUNTER || move == MOVE_UPPER_HAND) + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); + MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); + MOVE(playerLeft, move, target: opponentRight); + MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_MIRROR_COAT || move == MOVE_METAL_BURST) + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_SUCKER_PUNCH || move == MOVE_THUNDERCLAP) + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_DREAM_EATER) + { + TURN { MOVE(playerLeft, MOVE_HYPNOSIS, target: opponentRight); MOVE(opponentLeft, MOVE_HYPNOSIS, target: playerRight); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SNORE) + { + TURN { MOVE(opponentRight, MOVE_HYPNOSIS, target: playerLeft); MOVE(playerRight, MOVE_HYPNOSIS, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SPIT_UP || move == MOVE_LAST_RESORT) + { + TURN { MOVE(playerLeft, MOVE_STOCKPILE); MOVE(opponentLeft, MOVE_STOCKPILE); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + switch (GetMoveEffect(move)) + { + case EFFECT_TWO_TURNS_ATTACK: + case EFFECT_SEMI_INVULNERABLE: + case EFFECT_SOLAR_BEAM: + case EFFECT_SKY_DROP: + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { ; } + break; + case EFFECT_FUTURE_SIGHT: + TURN { ; } + TURN { ; } + break; + case EFFECT_BIDE: + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + break; + default: + break; + } + } SCENE { + if (GetMoveEffect(move) != EFFECT_FUTURE_SIGHT) + { + HP_BAR(opponentRight, captureDamage: &damage1); + HP_BAR(playerRight, captureDamage: &damage2); + } + else + { + HP_BAR(playerRight, captureDamage: &damage2); + HP_BAR(opponentRight, captureDamage: &damage1); + } + } THEN { + if (IsMoveSheerForceBoosted(move)) + EXPECT_GT(damage1, damage2); + else + EXPECT_EQ(damage2, damage1); + } +} + +// Last test should always go up to MOVES_COUNT to catch users moves +DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to boost (Gen9) (Traits)") +{ + s16 damage1, damage2; + u32 move = 0; + for (u32 j = MOVE_TERA_BLAST; j < MOVES_COUNT; j++) + { + if (GetMoveCategory(j) != DAMAGE_CATEGORY_STATUS && !IgnoreMoveForSheerForceBoost(j)) + PARAMETRIZE { move = j; } + } + GIVEN { + PLAYER(SPECIES_STEELIX) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_SHEER_FORCE); Item(ITEM_BLUK_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_TELEPATHY); Level(100); Item(ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_STEELIX) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_STURDY); Item(ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_TELEPATHY); Level(100); Item(ITEM_BLUK_BERRY); } + } WHEN { + if (move == MOVE_ALLURING_VOICE || move == MOVE_BURNING_JEALOUSY) // Alluring Voice requires the target to boost stats to have an effect + TURN { MOVE(opponentRight, MOVE_AGILITY); MOVE(playerRight, MOVE_AGILITY); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_UPPER_HAND) // Upper Hand requires the target to be using a damaging priority move + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); MOVE(playerRight, move, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_COUNTER || move == MOVE_UPPER_HAND) + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); + MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); + MOVE(playerLeft, move, target: opponentRight); + MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_MIRROR_COAT || move == MOVE_METAL_BURST) + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_SUCKER_PUNCH || move == MOVE_THUNDERCLAP) + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_DREAM_EATER) + { + TURN { MOVE(playerLeft, MOVE_HYPNOSIS, target: opponentRight); MOVE(opponentLeft, MOVE_HYPNOSIS, target: playerRight); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SNORE) + { + TURN { MOVE(opponentRight, MOVE_HYPNOSIS, target: playerLeft); MOVE(playerRight, MOVE_HYPNOSIS, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SPIT_UP || move == MOVE_LAST_RESORT) + { + TURN { MOVE(playerLeft, MOVE_STOCKPILE); MOVE(opponentLeft, MOVE_STOCKPILE); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + switch (GetMoveEffect(move)) + { + case EFFECT_TWO_TURNS_ATTACK: + case EFFECT_SEMI_INVULNERABLE: + case EFFECT_SOLAR_BEAM: + case EFFECT_SKY_DROP: + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { ; } + break; + case EFFECT_FUTURE_SIGHT: + TURN { ; } + TURN { ; } + break; + case EFFECT_BIDE: + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + break; + default: + break; + } + } SCENE { + if (GetMoveEffect(move) != EFFECT_FUTURE_SIGHT) + { + HP_BAR(opponentRight, captureDamage: &damage1); + HP_BAR(playerRight, captureDamage: &damage2); + } + else + { + HP_BAR(playerRight, captureDamage: &damage2); + HP_BAR(opponentRight, captureDamage: &damage1); + } + } THEN { + if (IsMoveSheerForceBoosted(move)) + EXPECT_GT(damage1, damage2); + else + EXPECT_EQ(damage2, damage1); + } +} + +AI_SINGLE_BATTLE_TEST("AI sees Sheer Force skips additional effects (Traits)") +{ + u16 ability, expectedMove, move; + + PARAMETRIZE { ability = ABILITY_ROUGH_SKIN; move = MOVE_KARATE_CHOP; expectedMove = MOVE_POWER_UP_PUNCH; } + PARAMETRIZE { ability = ABILITY_ROUGH_SKIN; move = MOVE_BRICK_BREAK; expectedMove = MOVE_POWER_UP_PUNCH; } + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; move = MOVE_BRICK_BREAK; expectedMove = MOVE_BRICK_BREAK; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_DRUDDIGON) { Ability(ABILITY_MOLD_BREAKER); Innates(ability); Moves(MOVE_POWER_UP_PUNCH, move); } + } WHEN { + TURN { EXPECT_MOVE(opponent, expectedMove); } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Belch (Multi)", s16 damage) +{ + enum Ability ability = 0; + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } + PARAMETRIZE { ability = ABILITY_ANGER_POINT; } + GIVEN { + PLAYER(SPECIES_TAUROS) { Ability(ability); HP(1); Items(ITEM_PECHA_BERRY, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BELCH); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} + +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Synchronoise (Multi)", s16 damage) +{ + enum Ability ability = 0; + PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } + PARAMETRIZE { ability = ABILITY_ANGER_POINT; } + GIVEN { + PLAYER(SPECIES_TAUROS) { Ability(ability); HP(1); Items(ITEM_PECHA_BERRY, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_CHANSEY); + } WHEN { + TURN { MOVE(player, MOVE_SYNCHRONOISE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_NE(results[0].damage, 0); + } +} + +// Tests split by generation +DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to boost (Gen1) (Multi)") +{ + s16 damage1, damage2; + u32 move = 0; + for (u32 j = MOVE_POUND; j < MOVES_COUNT_GEN1; j++) + { + if (GetMoveCategory(j) != DAMAGE_CATEGORY_STATUS && !IgnoreMoveForSheerForceBoost(j)) + PARAMETRIZE { move = j; } + } + GIVEN { + PLAYER(SPECIES_STEELIX) { Ability(ABILITY_SHEER_FORCE); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Level(100); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_STEELIX) { Ability(ABILITY_STURDY); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Level(100); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + } WHEN { + if (move == MOVE_ALLURING_VOICE || move == MOVE_BURNING_JEALOUSY) // Alluring Voice requires the target to boost stats to have an effect + TURN { MOVE(opponentRight, MOVE_AGILITY); MOVE(playerRight, MOVE_AGILITY); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_UPPER_HAND) // Upper Hand requires the target to be using a damaging priority move + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); MOVE(playerRight, move, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_COUNTER || move == MOVE_UPPER_HAND) + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); + MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); + MOVE(playerLeft, move, target: opponentRight); + MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_MIRROR_COAT || move == MOVE_METAL_BURST) + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_SUCKER_PUNCH || move == MOVE_THUNDERCLAP) + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_DREAM_EATER) + { + TURN { MOVE(playerLeft, MOVE_HYPNOSIS, target: opponentRight); MOVE(opponentLeft, MOVE_HYPNOSIS, target: playerRight); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SNORE) + { + TURN { MOVE(opponentRight, MOVE_HYPNOSIS, target: playerLeft); MOVE(playerRight, MOVE_HYPNOSIS, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SPIT_UP || move == MOVE_LAST_RESORT) + { + TURN { MOVE(playerLeft, MOVE_STOCKPILE); MOVE(opponentLeft, MOVE_STOCKPILE); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + switch (GetMoveEffect(move)) + { + case EFFECT_TWO_TURNS_ATTACK: + case EFFECT_SEMI_INVULNERABLE: + case EFFECT_SOLAR_BEAM: + case EFFECT_SKY_DROP: + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { ; } + break; + case EFFECT_FUTURE_SIGHT: + TURN { ; } + TURN { ; } + break; + case EFFECT_BIDE: + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + break; + default: + break; + } + } SCENE { + if (GetMoveEffect(move) != EFFECT_FUTURE_SIGHT) + { + HP_BAR(opponentRight, captureDamage: &damage1); + HP_BAR(playerRight, captureDamage: &damage2); + } + else + { + HP_BAR(playerRight, captureDamage: &damage2); + HP_BAR(opponentRight, captureDamage: &damage1); + } + } THEN { + if (IsMoveSheerForceBoosted(move)) { + if (!(damage1 > damage2)) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); + EXPECT_GT(damage1, damage2); + } else { + if (damage1 != damage2) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); + EXPECT_EQ(damage2, damage1); + } + } +} + +DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to boost (Gen2) (Multi)") +{ + s16 damage1, damage2; + u32 move = 0; + for (u32 j = MOVE_SKETCH; j < MOVES_COUNT_GEN2; j++) + { + if (GetMoveCategory(j) != DAMAGE_CATEGORY_STATUS && !IgnoreMoveForSheerForceBoost(j)) + PARAMETRIZE { move = j; } + } + GIVEN { + PLAYER(SPECIES_STEELIX) { Ability(ABILITY_SHEER_FORCE); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Level(100); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_STEELIX) { Ability(ABILITY_STURDY); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Level(100); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + } WHEN { + if (move == MOVE_ALLURING_VOICE || move == MOVE_BURNING_JEALOUSY) // Alluring Voice requires the target to boost stats to have an effect + TURN { MOVE(opponentRight, MOVE_AGILITY); MOVE(playerRight, MOVE_AGILITY); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_UPPER_HAND) // Upper Hand requires the target to be using a damaging priority move + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); MOVE(playerRight, move, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_COUNTER || move == MOVE_UPPER_HAND) + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); + MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); + MOVE(playerLeft, move, target: opponentRight); + MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_MIRROR_COAT || move == MOVE_METAL_BURST) + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_SUCKER_PUNCH || move == MOVE_THUNDERCLAP) + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_DREAM_EATER) + { + TURN { MOVE(playerLeft, MOVE_HYPNOSIS, target: opponentRight); MOVE(opponentLeft, MOVE_HYPNOSIS, target: playerRight); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SNORE) + { + TURN { MOVE(opponentRight, MOVE_HYPNOSIS, target: playerLeft); MOVE(playerRight, MOVE_HYPNOSIS, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SPIT_UP || move == MOVE_LAST_RESORT) + { + TURN { MOVE(playerLeft, MOVE_STOCKPILE); MOVE(opponentLeft, MOVE_STOCKPILE); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + switch (GetMoveEffect(move)) + { + case EFFECT_TWO_TURNS_ATTACK: + case EFFECT_SEMI_INVULNERABLE: + case EFFECT_SOLAR_BEAM: + case EFFECT_SKY_DROP: + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { ; } + break; + case EFFECT_FUTURE_SIGHT: + TURN { ; } + TURN { ; } + break; + case EFFECT_BIDE: + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + break; + default: + break; + } + } SCENE { + if (GetMoveEffect(move) != EFFECT_FUTURE_SIGHT) + { + HP_BAR(opponentRight, captureDamage: &damage1); + HP_BAR(playerRight, captureDamage: &damage2); + } + else + { + HP_BAR(playerRight, captureDamage: &damage2); + HP_BAR(opponentRight, captureDamage: &damage1); + } + } THEN { + if (IsMoveSheerForceBoosted(move)) { + if (!(damage1 > damage2)) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); + EXPECT_GT(damage1, damage2); + } else { + if (damage1 != damage2) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); + EXPECT_EQ(damage2, damage1); + } + } +} + +DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to boost (Gen3) (Multi)") +{ + s16 damage1, damage2; + u32 move = 0; + for (u32 j = MOVE_FAKE_OUT; j < MOVES_COUNT_GEN3; j++) + { + if (GetMoveCategory(j) != DAMAGE_CATEGORY_STATUS && !IgnoreMoveForSheerForceBoost(j)) + PARAMETRIZE { move = j; } + } + GIVEN { + PLAYER(SPECIES_STEELIX) { Ability(ABILITY_SHEER_FORCE); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Level(100); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_STEELIX) { Ability(ABILITY_STURDY); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Level(100); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + } WHEN { + if (move == MOVE_ALLURING_VOICE || move == MOVE_BURNING_JEALOUSY) // Alluring Voice requires the target to boost stats to have an effect + TURN { MOVE(opponentRight, MOVE_AGILITY); MOVE(playerRight, MOVE_AGILITY); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_UPPER_HAND) // Upper Hand requires the target to be using a damaging priority move + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); MOVE(playerRight, move, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_COUNTER || move == MOVE_UPPER_HAND) + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); + MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); + MOVE(playerLeft, move, target: opponentRight); + MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_MIRROR_COAT || move == MOVE_METAL_BURST) + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_SUCKER_PUNCH || move == MOVE_THUNDERCLAP) + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_DREAM_EATER) + { + TURN { MOVE(playerLeft, MOVE_HYPNOSIS, target: opponentRight); MOVE(opponentLeft, MOVE_HYPNOSIS, target: playerRight); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SNORE) + { + TURN { MOVE(opponentRight, MOVE_HYPNOSIS, target: playerLeft); MOVE(playerRight, MOVE_HYPNOSIS, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SPIT_UP || move == MOVE_LAST_RESORT) + { + TURN { MOVE(playerLeft, MOVE_STOCKPILE); MOVE(opponentLeft, MOVE_STOCKPILE); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + switch (GetMoveEffect(move)) + { + case EFFECT_TWO_TURNS_ATTACK: + case EFFECT_SEMI_INVULNERABLE: + case EFFECT_SOLAR_BEAM: + case EFFECT_SKY_DROP: + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { ; } + break; + case EFFECT_FUTURE_SIGHT: + TURN { ; } + TURN { ; } + break; + case EFFECT_BIDE: + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + break; + default: + break; + } + } SCENE { + if (GetMoveEffect(move) != EFFECT_FUTURE_SIGHT) + { + HP_BAR(opponentRight, captureDamage: &damage1); + HP_BAR(playerRight, captureDamage: &damage2); + } + else + { + HP_BAR(playerRight, captureDamage: &damage2); + HP_BAR(opponentRight, captureDamage: &damage1); + } + } THEN { + if (IsMoveSheerForceBoosted(move)) { + if (!(damage1 > damage2)) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); + EXPECT_GT(damage1, damage2); + } else { + if (damage1 != damage2) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); + EXPECT_EQ(damage2, damage1); + } + } +} + +DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to boost (Gen4) (Multi)") +{ + s16 damage1, damage2; + u32 move = 0; + for (u32 j = MOVE_ROOST; j < MOVES_COUNT_GEN4; j++) + { + if (GetMoveCategory(j) != DAMAGE_CATEGORY_STATUS && !IgnoreMoveForSheerForceBoost(j)) + PARAMETRIZE { move = j; } + } + GIVEN { + PLAYER(SPECIES_STEELIX) { Ability(ABILITY_SHEER_FORCE); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Level(100); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_STEELIX) { Ability(ABILITY_STURDY); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Level(100); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + } WHEN { + if (move == MOVE_ALLURING_VOICE || move == MOVE_BURNING_JEALOUSY) // Alluring Voice requires the target to boost stats to have an effect + TURN { MOVE(opponentRight, MOVE_AGILITY); MOVE(playerRight, MOVE_AGILITY); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_UPPER_HAND) // Upper Hand requires the target to be using a damaging priority move + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); MOVE(playerRight, move, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_COUNTER || move == MOVE_UPPER_HAND) + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); + MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); + MOVE(playerLeft, move, target: opponentRight); + MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_MIRROR_COAT || move == MOVE_METAL_BURST) + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_SUCKER_PUNCH || move == MOVE_THUNDERCLAP) + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_DREAM_EATER) + { + TURN { MOVE(playerLeft, MOVE_HYPNOSIS, target: opponentRight); MOVE(opponentLeft, MOVE_HYPNOSIS, target: playerRight); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SNORE) + { + TURN { MOVE(opponentRight, MOVE_HYPNOSIS, target: playerLeft); MOVE(playerRight, MOVE_HYPNOSIS, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SPIT_UP || move == MOVE_LAST_RESORT) + { + TURN { MOVE(playerLeft, MOVE_STOCKPILE); MOVE(opponentLeft, MOVE_STOCKPILE); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + switch (GetMoveEffect(move)) + { + case EFFECT_TWO_TURNS_ATTACK: + case EFFECT_SEMI_INVULNERABLE: + case EFFECT_SOLAR_BEAM: + case EFFECT_SKY_DROP: + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { ; } + break; + case EFFECT_FUTURE_SIGHT: + TURN { ; } + TURN { ; } + break; + case EFFECT_BIDE: + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + break; + default: + break; + } + } SCENE { + if (GetMoveEffect(move) != EFFECT_FUTURE_SIGHT) + { + HP_BAR(opponentRight, captureDamage: &damage1); + HP_BAR(playerRight, captureDamage: &damage2); + } + else + { + HP_BAR(playerRight, captureDamage: &damage2); + HP_BAR(opponentRight, captureDamage: &damage1); + } + } THEN { + if (IsMoveSheerForceBoosted(move)) { + if (!(damage1 > damage2)) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); + EXPECT_GT(damage1, damage2); + } else { + if (damage1 != damage2) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); + EXPECT_EQ(damage2, damage1); + } + } +} + +DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to boost (Gen5) (Multi)") +{ + s16 damage1, damage2; + u32 move = 0; + for (u32 j = MOVE_HONE_CLAWS; j < MOVES_COUNT_GEN5; j++) + { + if (GetMoveCategory(j) != DAMAGE_CATEGORY_STATUS && !IgnoreMoveForSheerForceBoost(j)) + PARAMETRIZE { move = j; } + } + GIVEN { + PLAYER(SPECIES_STEELIX) { Ability(ABILITY_SHEER_FORCE); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Level(100); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_STEELIX) { Ability(ABILITY_STURDY); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Level(100); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + } WHEN { + if (move == MOVE_ALLURING_VOICE || move == MOVE_BURNING_JEALOUSY) // Alluring Voice requires the target to boost stats to have an effect + TURN { MOVE(opponentRight, MOVE_AGILITY); MOVE(playerRight, MOVE_AGILITY); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_UPPER_HAND) // Upper Hand requires the target to be using a damaging priority move + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); MOVE(playerRight, move, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_COUNTER || move == MOVE_UPPER_HAND) + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); + MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); + MOVE(playerLeft, move, target: opponentRight); + MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_MIRROR_COAT || move == MOVE_METAL_BURST) + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_SUCKER_PUNCH || move == MOVE_THUNDERCLAP) + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_DREAM_EATER) + { + TURN { MOVE(playerLeft, MOVE_HYPNOSIS, target: opponentRight); MOVE(opponentLeft, MOVE_HYPNOSIS, target: playerRight); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SNORE) + { + TURN { MOVE(opponentRight, MOVE_HYPNOSIS, target: playerLeft); MOVE(playerRight, MOVE_HYPNOSIS, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SPIT_UP || move == MOVE_LAST_RESORT) + { + TURN { MOVE(playerLeft, MOVE_STOCKPILE); MOVE(opponentLeft, MOVE_STOCKPILE); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + switch (GetMoveEffect(move)) + { + case EFFECT_TWO_TURNS_ATTACK: + case EFFECT_SEMI_INVULNERABLE: + case EFFECT_SOLAR_BEAM: + case EFFECT_SKY_DROP: + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { ; } + break; + case EFFECT_FUTURE_SIGHT: + TURN { ; } + TURN { ; } + break; + case EFFECT_BIDE: + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + break; + default: + break; + } + } SCENE { + if (GetMoveEffect(move) != EFFECT_FUTURE_SIGHT) + { + HP_BAR(opponentRight, captureDamage: &damage1); + HP_BAR(playerRight, captureDamage: &damage2); + } + else + { + HP_BAR(playerRight, captureDamage: &damage2); + HP_BAR(opponentRight, captureDamage: &damage1); + } + } THEN { + if (IsMoveSheerForceBoosted(move)) { + if (!(damage1 > damage2)) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); + EXPECT_GT(damage1, damage2); + } else { + if (damage1 != damage2) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); + EXPECT_EQ(damage2, damage1); + } + } +} + +DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to boost (Gen6) (Multi)") +{ + s16 damage1, damage2; + u32 move = 0; + for (u32 j = MOVE_FLYING_PRESS; j < MOVES_COUNT_GEN6; j++) + { + if (GetMoveCategory(j) != DAMAGE_CATEGORY_STATUS && !IgnoreMoveForSheerForceBoost(j)) + PARAMETRIZE { move = j; } + } + GIVEN { + PLAYER(SPECIES_STEELIX) { Ability(ABILITY_SHEER_FORCE); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Level(100); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_STEELIX) { Ability(ABILITY_STURDY); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Level(100); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + } WHEN { + if (move == MOVE_ALLURING_VOICE || move == MOVE_BURNING_JEALOUSY) // Alluring Voice requires the target to boost stats to have an effect + TURN { MOVE(opponentRight, MOVE_AGILITY); MOVE(playerRight, MOVE_AGILITY); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_UPPER_HAND) // Upper Hand requires the target to be using a damaging priority move + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); MOVE(playerRight, move, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_COUNTER || move == MOVE_UPPER_HAND) + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); + MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); + MOVE(playerLeft, move, target: opponentRight); + MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_MIRROR_COAT || move == MOVE_METAL_BURST) + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_SUCKER_PUNCH || move == MOVE_THUNDERCLAP) + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_DREAM_EATER) + { + TURN { MOVE(playerLeft, MOVE_HYPNOSIS, target: opponentRight); MOVE(opponentLeft, MOVE_HYPNOSIS, target: playerRight); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SNORE) + { + TURN { MOVE(opponentRight, MOVE_HYPNOSIS, target: playerLeft); MOVE(playerRight, MOVE_HYPNOSIS, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SPIT_UP || move == MOVE_LAST_RESORT) + { + TURN { MOVE(playerLeft, MOVE_STOCKPILE); MOVE(opponentLeft, MOVE_STOCKPILE); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + switch (GetMoveEffect(move)) + { + case EFFECT_TWO_TURNS_ATTACK: + case EFFECT_SEMI_INVULNERABLE: + case EFFECT_SOLAR_BEAM: + case EFFECT_SKY_DROP: + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { ; } + break; + case EFFECT_FUTURE_SIGHT: + TURN { ; } + TURN { ; } + break; + case EFFECT_BIDE: + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + break; + default: + break; + } + } SCENE { + if (GetMoveEffect(move) != EFFECT_FUTURE_SIGHT) + { + HP_BAR(opponentRight, captureDamage: &damage1); + HP_BAR(playerRight, captureDamage: &damage2); + } + else + { + HP_BAR(playerRight, captureDamage: &damage2); + HP_BAR(opponentRight, captureDamage: &damage1); + } + } THEN { + if (IsMoveSheerForceBoosted(move)) { + if (!(damage1 > damage2)) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); + EXPECT_GT(damage1, damage2); + } else { + if (damage1 != damage2) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); + EXPECT_EQ(damage2, damage1); + } + } +} + +DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to boost (Gen7) (Multi)") +{ + s16 damage1, damage2; + u32 move = 0; + for (u32 j = MOVE_SHORE_UP; j < MOVES_COUNT_GEN7; j++) + { + if (GetMoveCategory(j) != DAMAGE_CATEGORY_STATUS && !IgnoreMoveForSheerForceBoost(j)) + PARAMETRIZE { move = j; } + } + GIVEN { + PLAYER(SPECIES_STEELIX) { Ability(ABILITY_SHEER_FORCE); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Level(100); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_STEELIX) { Ability(ABILITY_STURDY); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Level(100); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + } WHEN { + if (move == MOVE_ALLURING_VOICE || move == MOVE_BURNING_JEALOUSY) // Alluring Voice requires the target to boost stats to have an effect + TURN { MOVE(opponentRight, MOVE_AGILITY); MOVE(playerRight, MOVE_AGILITY); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_UPPER_HAND) // Upper Hand requires the target to be using a damaging priority move + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); MOVE(playerRight, move, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_COUNTER || move == MOVE_UPPER_HAND) + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); + MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); + MOVE(playerLeft, move, target: opponentRight); + MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_MIRROR_COAT || move == MOVE_METAL_BURST) + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_SUCKER_PUNCH || move == MOVE_THUNDERCLAP) + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_DREAM_EATER) + { + TURN { MOVE(playerLeft, MOVE_HYPNOSIS, target: opponentRight); MOVE(opponentLeft, MOVE_HYPNOSIS, target: playerRight); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SNORE) + { + TURN { MOVE(opponentRight, MOVE_HYPNOSIS, target: playerLeft); MOVE(playerRight, MOVE_HYPNOSIS, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SPIT_UP || move == MOVE_LAST_RESORT) + { + TURN { MOVE(playerLeft, MOVE_STOCKPILE); MOVE(opponentLeft, MOVE_STOCKPILE); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + switch (GetMoveEffect(move)) + { + case EFFECT_TWO_TURNS_ATTACK: + case EFFECT_SEMI_INVULNERABLE: + case EFFECT_SOLAR_BEAM: + case EFFECT_SKY_DROP: + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { ; } + break; + case EFFECT_FUTURE_SIGHT: + TURN { ; } + TURN { ; } + break; + case EFFECT_BIDE: + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + break; + default: + break; + } + } SCENE { + if (GetMoveEffect(move) != EFFECT_FUTURE_SIGHT) + { + HP_BAR(opponentRight, captureDamage: &damage1); + HP_BAR(playerRight, captureDamage: &damage2); + } + else + { + HP_BAR(playerRight, captureDamage: &damage2); + HP_BAR(opponentRight, captureDamage: &damage1); + } + } THEN { + if (IsMoveSheerForceBoosted(move)) { + if (!(damage1 > damage2)) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); + EXPECT_GT(damage1, damage2); + } else { + if (damage1 != damage2) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); + EXPECT_EQ(damage2, damage1); + } + } +} + +DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to boost (Gen8) (Multi)") +{ + s16 damage1, damage2; + u32 move = 0; + for (u32 j = MOVE_DYNAMAX_CANNON; j < MOVES_COUNT_GEN8; j++) + { + if (GetMoveCategory(j) != DAMAGE_CATEGORY_STATUS && !IgnoreMoveForSheerForceBoost(j)) + PARAMETRIZE { move = j; } + } + GIVEN { + PLAYER(SPECIES_STEELIX) { Ability(ABILITY_SHEER_FORCE); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Level(100); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_STEELIX) { Ability(ABILITY_STURDY); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Level(100); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + } WHEN { + if (move == MOVE_ALLURING_VOICE || move == MOVE_BURNING_JEALOUSY) // Alluring Voice requires the target to boost stats to have an effect + TURN { MOVE(opponentRight, MOVE_AGILITY); MOVE(playerRight, MOVE_AGILITY); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_UPPER_HAND) // Upper Hand requires the target to be using a damaging priority move + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); MOVE(playerRight, move, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_COUNTER || move == MOVE_UPPER_HAND) + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); + MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); + MOVE(playerLeft, move, target: opponentRight); + MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_MIRROR_COAT || move == MOVE_METAL_BURST) + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_SUCKER_PUNCH || move == MOVE_THUNDERCLAP) + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_DREAM_EATER) + { + TURN { MOVE(playerLeft, MOVE_HYPNOSIS, target: opponentRight); MOVE(opponentLeft, MOVE_HYPNOSIS, target: playerRight); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SNORE) + { + TURN { MOVE(opponentRight, MOVE_HYPNOSIS, target: playerLeft); MOVE(playerRight, MOVE_HYPNOSIS, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SPIT_UP || move == MOVE_LAST_RESORT) + { + TURN { MOVE(playerLeft, MOVE_STOCKPILE); MOVE(opponentLeft, MOVE_STOCKPILE); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + switch (GetMoveEffect(move)) + { + case EFFECT_TWO_TURNS_ATTACK: + case EFFECT_SEMI_INVULNERABLE: + case EFFECT_SOLAR_BEAM: + case EFFECT_SKY_DROP: + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { ; } + break; + case EFFECT_FUTURE_SIGHT: + TURN { ; } + TURN { ; } + break; + case EFFECT_BIDE: + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + break; + default: + break; + } + } SCENE { + if (GetMoveEffect(move) != EFFECT_FUTURE_SIGHT) + { + HP_BAR(opponentRight, captureDamage: &damage1); + HP_BAR(playerRight, captureDamage: &damage2); + } + else + { + HP_BAR(playerRight, captureDamage: &damage2); + HP_BAR(opponentRight, captureDamage: &damage1); + } + } THEN { + if (IsMoveSheerForceBoosted(move)) { + if (!(damage1 > damage2)) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); + EXPECT_GT(damage1, damage2); + } else { + if (damage1 != damage2) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); + EXPECT_EQ(damage2, damage1); + } + } +} + +// Last test should always go up to MOVES_COUNT to catch users moves +DOUBLE_BATTLE_TEST("Sheer Force only boosts the damage of moves it's supposed to boost (Gen9) (Multi)") +{ + s16 damage1, damage2; + u32 move = 0; + for (u32 j = MOVE_TERA_BLAST; j < MOVES_COUNT; j++) + { + if (GetMoveCategory(j) != DAMAGE_CATEGORY_STATUS && !IgnoreMoveForSheerForceBoost(j)) + PARAMETRIZE { move = j; } + } + GIVEN { + PLAYER(SPECIES_STEELIX) { Ability(ABILITY_SHEER_FORCE); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Level(100); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_STEELIX) { Ability(ABILITY_STURDY); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Level(100); Items(ITEM_PECHA_BERRY, ITEM_BLUK_BERRY); } + } WHEN { + if (move == MOVE_ALLURING_VOICE || move == MOVE_BURNING_JEALOUSY) // Alluring Voice requires the target to boost stats to have an effect + TURN { MOVE(opponentRight, MOVE_AGILITY); MOVE(playerRight, MOVE_AGILITY); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_UPPER_HAND) // Upper Hand requires the target to be using a damaging priority move + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); MOVE(playerRight, move, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_COUNTER || move == MOVE_UPPER_HAND) + TURN { MOVE(opponentRight, MOVE_QUICK_ATTACK, target: playerLeft); + MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); + MOVE(playerLeft, move, target: opponentRight); + MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_MIRROR_COAT || move == MOVE_METAL_BURST) + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_SUCKER_PUNCH || move == MOVE_THUNDERCLAP) + TURN { MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + else if (move == MOVE_DREAM_EATER) + { + TURN { MOVE(playerLeft, MOVE_HYPNOSIS, target: opponentRight); MOVE(opponentLeft, MOVE_HYPNOSIS, target: playerRight); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SNORE) + { + TURN { MOVE(opponentRight, MOVE_HYPNOSIS, target: playerLeft); MOVE(playerRight, MOVE_HYPNOSIS, target: opponentLeft); MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else if (move == MOVE_SPIT_UP || move == MOVE_LAST_RESORT) + { + TURN { MOVE(playerLeft, MOVE_STOCKPILE); MOVE(opponentLeft, MOVE_STOCKPILE); } + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + } + else + TURN { MOVE(playerLeft, move, target: opponentRight); MOVE(opponentLeft, move, target: playerRight); } + switch (GetMoveEffect(move)) + { + case EFFECT_TWO_TURNS_ATTACK: + case EFFECT_SEMI_INVULNERABLE: + case EFFECT_SOLAR_BEAM: + case EFFECT_SKY_DROP: + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { ; } + break; + case EFFECT_FUTURE_SIGHT: + TURN { ; } + TURN { ; } + break; + case EFFECT_BIDE: + TURN { MOVE(opponentRight, MOVE_WATER_GUN, target: playerLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + TURN { SKIP_TURN(playerLeft); SKIP_TURN(opponentLeft); } + break; + default: + break; + } + } SCENE { + if (GetMoveEffect(move) != EFFECT_FUTURE_SIGHT) + { + HP_BAR(opponentRight, captureDamage: &damage1); + HP_BAR(playerRight, captureDamage: &damage2); + } + else + { + HP_BAR(playerRight, captureDamage: &damage2); + HP_BAR(opponentRight, captureDamage: &damage1); + } + } THEN { + if (IsMoveSheerForceBoosted(move)) { + if (!(damage1 > damage2)) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); + EXPECT_GT(damage1, damage2); + } else { + if (damage1 != damage2) + DebugPrintf("Move that failed: %S", gMovesInfo[move].name); + EXPECT_EQ(damage2, damage1); + } + } +} + +#endif diff --git a/test/battle/ability/shield_dust.c b/test/battle/ability/shield_dust.c index 2fc4f51d5c30..f7f0305b260e 100644 --- a/test/battle/ability/shield_dust.c +++ b/test/battle/ability/shield_dust.c @@ -216,3 +216,221 @@ AI_SINGLE_BATTLE_TEST("AI will score secondary effects against shield dust corre } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Shield Dust blocks secondary effects (Traits)") +{ + u16 move; + PARAMETRIZE { move = MOVE_NUZZLE; } + PARAMETRIZE { move = MOVE_INFERNO; } + PARAMETRIZE { move = MOVE_MORTAL_SPIN; } + PARAMETRIZE { move = MOVE_FAKE_OUT; } + PARAMETRIZE { move = MOVE_ROCK_TOMB; } + PARAMETRIZE { move = MOVE_SPIRIT_SHACKLE; } + PARAMETRIZE { move = MOVE_PSYCHIC_NOISE; } + + GIVEN { + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_NUZZLE, MOVE_EFFECT_PARALYSIS, 100) == TRUE); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_INFERNO, MOVE_EFFECT_BURN, 100) == TRUE); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_MORTAL_SPIN, MOVE_EFFECT_POISON, 100) == TRUE); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_FAKE_OUT, MOVE_EFFECT_FLINCH, 100) == TRUE); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_ROCK_TOMB, MOVE_EFFECT_SPD_MINUS_1, 100) == TRUE); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_SPIRIT_SHACKLE, MOVE_EFFECT_PREVENT_ESCAPE, 100) == TRUE); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_PSYCHIC_NOISE, MOVE_EFFECT_PSYCHIC_NOISE, 100) == TRUE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_VIVILLON) { Ability(ABILITY_COMPOUND_EYES); Innates(ABILITY_SHIELD_DUST); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + NONE_OF { + MESSAGE("The opposing Vivillon is paralyzed, so it may be unable to move!"); + MESSAGE("The opposing Vivillon was burned!"); + MESSAGE("The opposing Vivillon was poisoned!"); + MESSAGE("The opposing Vivillon flinched and couldn't move!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Vivillon was prevented from healing!"); + } + } THEN { // Can't find good way to test trapping + EXPECT(!opponent->volatiles.escapePrevention); + } +} + +SINGLE_BATTLE_TEST("Shield Dust does not block primary effects (Traits)") +{ + u16 move; + PARAMETRIZE { move = MOVE_INFESTATION; } + PARAMETRIZE { move = MOVE_THOUSAND_ARROWS; } + PARAMETRIZE { move = MOVE_JAW_LOCK; } + PARAMETRIZE { move = MOVE_PAY_DAY; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_THOUSAND_ARROWS) == EFFECT_SMACK_DOWN); + ASSUME(GetMoveEffect(MOVE_SMACK_DOWN) == EFFECT_SMACK_DOWN); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_INFESTATION, MOVE_EFFECT_WRAP, 0) == TRUE); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_JAW_LOCK, MOVE_EFFECT_TRAP_BOTH, 0) == TRUE); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_PAY_DAY, MOVE_EFFECT_PAYDAY, 0) == TRUE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_VIVILLON) { Ability(ABILITY_SHIELD_DUST); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + switch (move) + { + case MOVE_INFESTATION: + MESSAGE("The opposing Vivillon has been afflicted with an infestation by Wobbuffet!"); + break; + case MOVE_THOUSAND_ARROWS: + MESSAGE("The opposing Vivillon fell straight down!"); + break; + case MOVE_JAW_LOCK: + MESSAGE("Neither Pokémon can run away!"); + break; + case MOVE_PAY_DAY: + MESSAGE("Coins were scattered everywhere!"); + break; + } + } THEN { // Can't find good way to test trapping + if (move == MOVE_JAW_LOCK) { + EXPECT(opponent->volatiles.escapePrevention); + EXPECT(player->volatiles.escapePrevention); + } + } +} + +SINGLE_BATTLE_TEST("Shield Dust does not block self-targeting effects, primary or secondary (Traits)") +{ + u16 move; + PARAMETRIZE { move = MOVE_POWER_UP_PUNCH; } + PARAMETRIZE { move = MOVE_FLAME_CHARGE; } + PARAMETRIZE { move = MOVE_LEAF_STORM; } + PARAMETRIZE { move = MOVE_METEOR_ASSAULT; } + + GIVEN { + ASSUME(MoveHasAdditionalEffectSelf(MOVE_FLAME_CHARGE, MOVE_EFFECT_SPD_PLUS_1) == TRUE); + ASSUME(MoveHasAdditionalEffectSelf(MOVE_POWER_UP_PUNCH, MOVE_EFFECT_ATK_PLUS_1) == TRUE); + ASSUME(MoveHasAdditionalEffectSelf(MOVE_LEAF_STORM, MOVE_EFFECT_SP_ATK_MINUS_2) == TRUE); + ASSUME(MoveHasAdditionalEffectSelf(MOVE_METEOR_ASSAULT, MOVE_EFFECT_RECHARGE) == TRUE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_VIVILLON) { Ability(ABILITY_COMPOUND_EYES); Innates(ABILITY_SHIELD_DUST); } + } WHEN { + TURN { MOVE(player, move); } + if (move == MOVE_METEOR_ASSAULT) { + TURN { SKIP_TURN(player); } + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + switch (move) + { + case MOVE_POWER_UP_PUNCH: + case MOVE_FLAME_CHARGE: + case MOVE_LEAF_STORM: + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + break; + case MOVE_METEOR_ASSAULT: // second turn + MESSAGE("Wobbuffet must recharge!"); + break; + } + } +} + +DOUBLE_BATTLE_TEST("Shield Dust does or does not block Sparkling Aria depending on number of targets hit (Traits)") +{ + u32 moveToUse; + PARAMETRIZE { moveToUse = MOVE_FINAL_GAMBIT; } + PARAMETRIZE { moveToUse = MOVE_SCRATCH; } + GIVEN { + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_VIVILLON) { Ability(ABILITY_COMPOUND_EYES); Innates(ABILITY_SHIELD_DUST); Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, moveToUse, target: opponentRight); MOVE(playerLeft, MOVE_SPARKLING_ARIA); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPARKLING_ARIA, playerLeft); + if (moveToUse == MOVE_SCRATCH) { + MESSAGE("The opposing Vivillon's burn was cured!"); + STATUS_ICON(opponentLeft, none: TRUE); + } else { + NONE_OF { + MESSAGE("The opposing Vivillon's burn was cured!"); + STATUS_ICON(opponentLeft, none: TRUE); + } + } + } +} + +DOUBLE_BATTLE_TEST("Shield Dust blocks Sparkling Aria if all other targets avoid getting hit by (Traits)") +{ + GIVEN { + PLAYER(SPECIES_PRIMARINA); + PLAYER(SPECIES_VIVILLON) { Ability(ABILITY_COMPOUND_EYES); Innates(ABILITY_SHIELD_DUST); Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WYNAUT) { Status1(STATUS1_BURN); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_FLY, target:playerLeft); MOVE(opponentRight, MOVE_PROTECT); MOVE(playerRight, MOVE_CELEBRATE); MOVE(playerLeft, MOVE_SPARKLING_ARIA); } + } SCENE { + NOT MESSAGE("Vivillon's burn was cured!"); + } +} + +SINGLE_BATTLE_TEST("Shield Dust blocks Sparkling Aria in singles (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_VIVILLON) { Ability(ABILITY_COMPOUND_EYES); Innates(ABILITY_SHIELD_DUST); Status1(STATUS1_BURN); } + } WHEN { + TURN { MOVE(player, MOVE_SPARKLING_ARIA); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPARKLING_ARIA, player); + NONE_OF { + MESSAGE("The opposing Vivillon's burn was cured!"); + STATUS_ICON(opponent, none: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Shield Dust does not prevent ability stat changes (Traits)") +{ + GIVEN { + PLAYER(SPECIES_VIVILLON) { Ability(ABILITY_COMPOUND_EYES); Innates(ABILITY_SHIELD_DUST); } + OPPONENT(SPECIES_ELDEGOSS) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_COTTON_DOWN); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Vivillon's Speed fell!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI will score secondary effects against shield dust correctly (Traits)") +{ + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + GIVEN { + PLAYER(SPECIES_DUSTOX){ Ability(ABILITY_COMPOUND_EYES); Innates(ABILITY_SHIELD_DUST); Moves(MOVE_GUST); } + OPPONENT(SPECIES_SUNFLORA){ Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_EARLY_BIRD); Moves(MOVE_MYSTICAL_FIRE, MOVE_FIERY_DANCE); } + } WHEN { + TURN { + MOVE(player, MOVE_GUST); + EXPECT_MOVE(opponent, MOVE_FIERY_DANCE); + } + } +} + +AI_SINGLE_BATTLE_TEST("AI will score secondary effects against shield dust correctly when it has Mold Breaker (Traits)") +{ + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + GIVEN { + PLAYER(SPECIES_DUSTOX){ Ability(ABILITY_COMPOUND_EYES); Innates(ABILITY_SHIELD_DUST); Moves(MOVE_GUST); } + OPPONENT(SPECIES_SUNFLORA){ Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_MOLD_BREAKER); Moves(MOVE_MYSTICAL_FIRE, MOVE_FIERY_DANCE); } + } WHEN { + TURN { + MOVE(player, MOVE_GUST); + EXPECT_MOVE(opponent, MOVE_MYSTICAL_FIRE); + } + } +} +#endif diff --git a/test/battle/ability/shields_down.c b/test/battle/ability/shields_down.c index d16aa240da36..15a3f17e2e72 100644 --- a/test/battle/ability/shields_down.c +++ b/test/battle/ability/shields_down.c @@ -75,3 +75,80 @@ SINGLE_BATTLE_TEST("Shields Down protects Minior Meteor from status conditions") EXPECT(opponent->status1 & STATUS1_BURN); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Minior Core doesn't transform into Minior Meteor on switch-in if it has 1/2 or less health (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_MINIOR_CORE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_SHIELDS_DOWN); HP(50); MaxHP(100); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); SEND_OUT(opponent, 1); } + } SCENE { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_SHIELDS_DOWN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, opponent); + } + } THEN { + EXPECT_EQ(opponent->species, SPECIES_MINIOR_CORE); + } +} + +SINGLE_BATTLE_TEST("Minior Core transforms into Minior Meteor on switch-in if it has more than 1/2 health (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_MINIOR_CORE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_SHIELDS_DOWN); HP(51); MaxHP(101); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); SEND_OUT(opponent, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_SHIELDS_DOWN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, opponent); + } THEN { + EXPECT_EQ(opponent->species, SPECIES_MINIOR_METEOR); + } +} + +SINGLE_BATTLE_TEST("Minior Core transforms into Minior Meteor on battle start if it has more than 1/2 health (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_MINIOR_CORE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_SHIELDS_DOWN); HP(51); MaxHP(101); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_SHIELDS_DOWN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, opponent); + } THEN { + EXPECT_EQ(opponent->species, SPECIES_MINIOR_METEOR); + } +} + +SINGLE_BATTLE_TEST("Shields Down protects Minior Meteor from status conditions (Traits)") +{ + u32 species, hp; + PARAMETRIZE { species = SPECIES_MINIOR_METEOR; hp = 300; } + PARAMETRIZE { species = SPECIES_MINIOR_CORE; hp = 100; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_WILL_O_WISP) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_WILL_O_WISP) == MOVE_EFFECT_BURN); + PLAYER(SPECIES_WYNAUT); + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_SHIELDS_DOWN); HP(hp); MaxHP(300); } + } WHEN { + TURN { MOVE(player, MOVE_WILL_O_WISP); } + } SCENE { + if (species == SPECIES_MINIOR_METEOR) + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_WILL_O_WISP, player); + else + ANIMATION(ANIM_TYPE_MOVE, MOVE_WILL_O_WISP, player); + } THEN { + if (species == SPECIES_MINIOR_METEOR) + EXPECT_EQ(opponent->status1, STATUS1_NONE); + else + EXPECT(opponent->status1 & STATUS1_BURN); + } +} +#endif diff --git a/test/battle/ability/slush_rush.c b/test/battle/ability/slush_rush.c index 76509db019be..3c60e349c090 100644 --- a/test/battle/ability/slush_rush.c +++ b/test/battle/ability/slush_rush.c @@ -64,3 +64,69 @@ SINGLE_BATTLE_TEST("Slush Rush doesn't prevent non-Ice types from taking damage HP_BAR(player); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Slush Rush doubles speed from hail (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CETITAN) { Ability(ABILITY_THICK_FAT); Innates(ABILITY_SLUSH_RUSH); Speed(100); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(199); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_HAIL); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HAIL, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +SINGLE_BATTLE_TEST("Slush Rush doubles speed from snow (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CETITAN) { Ability(ABILITY_THICK_FAT); Innates(ABILITY_SLUSH_RUSH); Speed(100); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(199); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SNOWSCAPE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SNOWSCAPE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +SINGLE_BATTLE_TEST("Slush Rush doesn't double speed if Cloud Nine/Air Lock is on the field (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CETITAN) { Ability(ABILITY_THICK_FAT); Innates(ABILITY_SLUSH_RUSH); Speed(100); } + OPPONENT(SPECIES_GOLDUCK) { Speed(199); Ability(ABILITY_SWIFT_SWIM); Innates(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SNOWSCAPE); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SNOWSCAPE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } +} + +SINGLE_BATTLE_TEST("Slush Rush doesn't prevent non-Ice types from taking damage in Hail (Traits)") +{ + GIVEN { + ASSUME(GetSpeciesType(SPECIES_WOBBUFFET, 0) != TYPE_ICE); + ASSUME(GetSpeciesType(SPECIES_WOBBUFFET, 1) != TYPE_ICE); + ASSUME(GetMoveEffect(MOVE_HAIL) == EFFECT_HAIL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CETITAN) { Ability(ABILITY_THICK_FAT); Innates(ABILITY_SLUSH_RUSH); } + } WHEN { + TURN { MOVE(player, MOVE_HAIL); MOVE(opponent, MOVE_SKILL_SWAP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HAIL, player); + HP_BAR(player); + } +} +#endif diff --git a/test/battle/ability/snow_cloak.c b/test/battle/ability/snow_cloak.c index 5169ecd2ae38..4bef7df3f6a4 100644 --- a/test/battle/ability/snow_cloak.c +++ b/test/battle/ability/snow_cloak.c @@ -55,3 +55,60 @@ SINGLE_BATTLE_TEST("Snow Cloak increases evasion during snow") HP_BAR(player); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Snow Cloak prevents damage from hail (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GLACEON) { Ability(ABILITY_ICE_BODY); Innates(ABILITY_SNOW_CLOAK); } + } WHEN { + TURN { MOVE(player, MOVE_HAIL); MOVE(opponent, MOVE_SKILL_SWAP); } + } SCENE { + NONE_OF { HP_BAR(player); } + } +} + +SINGLE_BATTLE_TEST("Snow Cloak increases evasion during hail (Traits)") +{ + PASSES_RANDOMLY(4, 5, RNG_ACCURACY); + GIVEN { + ASSUME(GetMoveAccuracy(MOVE_POUND) == 100); + PLAYER(SPECIES_GLACEON) { Ability(ABILITY_ICE_BODY); Innates(ABILITY_SNOW_CLOAK); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_HAIL); } + TURN { MOVE(opponent, MOVE_POUND); } + } SCENE { + HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Snow Cloak doesn't increase evasion if Cloud Nine/Air Lock is on the field (Traits)") +{ + PASSES_RANDOMLY(10, 10, RNG_ACCURACY); + GIVEN { + PLAYER(SPECIES_GLACEON) { Ability(ABILITY_ICE_BODY); Innates(ABILITY_SNOW_CLOAK); } + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_SWIFT_SWIM); Innates(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(player, MOVE_HAIL); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Snow Cloak increases evasion during snow (Traits)") +{ + PASSES_RANDOMLY(4, 5, RNG_ACCURACY); + GIVEN { + ASSUME(GetMoveAccuracy(MOVE_POUND) == 100); + PLAYER(SPECIES_GLACEON) { Ability(ABILITY_ICE_BODY); Innates(ABILITY_SNOW_CLOAK); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SNOWSCAPE); } + TURN { MOVE(opponent, MOVE_POUND); } + } SCENE { + HP_BAR(player); + } +} +#endif diff --git a/test/battle/ability/snow_warning.c b/test/battle/ability/snow_warning.c index bfb27c5f4923..68bd22ebc5d6 100644 --- a/test/battle/ability/snow_warning.c +++ b/test/battle/ability/snow_warning.c @@ -136,3 +136,93 @@ SINGLE_BATTLE_TEST("Snow Warning sets up snow for 8 turns with Icy Rock (Gen9+)" MESSAGE("The snow stopped."); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Snow Warning summons hail (Gen4-8) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_SNOW_WARNING, GEN_8); + PLAYER(SPECIES_ABOMASNOW) { Ability(ABILITY_SOUNDPROOF); Innates(ABILITY_SNOW_WARNING); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN {} + } SCENE { + MESSAGE("It started to hail!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HAIL_CONTINUES); + } +} + +SINGLE_BATTLE_TEST("Snow Warning summons snow (Gen9+) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_SNOW_WARNING, GEN_9); + PLAYER(SPECIES_ABOMASNOW) { Ability(ABILITY_SOUNDPROOF); Innates(ABILITY_SNOW_WARNING); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN {} + } SCENE { + MESSAGE("It started to snow!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SNOW_CONTINUES); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Snow Warning sets up hail for 8 turns with Icy Rock (Gen6-8) (Multi)") +{ + GIVEN { + WITH_CONFIG(CONFIG_SNOW_WARNING, GEN_8); + WITH_CONFIG(CONFIG_ABILITY_WEATHER, GEN_6); + PLAYER(SPECIES_ABOMASNOW) { Moves(MOVE_CELEBRATE); Ability(ABILITY_SNOW_WARNING); Items(ITEM_PECHA_BERRY, ITEM_ICY_ROCK); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_SNOW_WARNING); + MESSAGE("The hail is crashing down."); + MESSAGE("The hail is crashing down."); + MESSAGE("The hail is crashing down."); + MESSAGE("The hail is crashing down."); + MESSAGE("The hail is crashing down."); + MESSAGE("The hail is crashing down."); + MESSAGE("The hail is crashing down."); + MESSAGE("The hail stopped."); + } +} + +SINGLE_BATTLE_TEST("Snow Warning sets up snow for 8 turns with Icy Rock (Gen9+) (Multi)") +{ + GIVEN { + WITH_CONFIG(CONFIG_SNOW_WARNING, GEN_9); + WITH_CONFIG(CONFIG_ABILITY_WEATHER, GEN_9); + PLAYER(SPECIES_ABOMASNOW) { Moves(MOVE_CELEBRATE); Ability(ABILITY_SNOW_WARNING); Items(ITEM_PECHA_BERRY, ITEM_ICY_ROCK); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_SNOW_WARNING); + MESSAGE("Snow continues to fall."); + MESSAGE("Snow continues to fall."); + MESSAGE("Snow continues to fall."); + MESSAGE("Snow continues to fall."); + MESSAGE("Snow continues to fall."); + MESSAGE("Snow continues to fall."); + MESSAGE("Snow continues to fall."); + MESSAGE("The snow stopped."); + } +} +#endif diff --git a/test/battle/ability/solar_power.c b/test/battle/ability/solar_power.c index 1fdccad5be90..2cdf42f3ea3c 100644 --- a/test/battle/ability/solar_power.c +++ b/test/battle/ability/solar_power.c @@ -68,3 +68,73 @@ SINGLE_BATTLE_TEST("Solar Power doesn't cause the Pokémon to lose 1/8 max HP if EXPECT_EQ(player->hp, player->maxHP); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Solar Power increases a Sp. Attack by x1.5 in Sun (Traits)", s16 damage) +{ + u32 move; + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SUNNY_DAY; } + GIVEN { + ASSUME(GetMovePower(MOVE_HYPER_VOICE) > 0); + ASSUME(GetMoveCategory(MOVE_HYPER_VOICE) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_CHARIZARD) { Ability(ABILITY_BLAZE); Innates(ABILITY_SOLAR_POWER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_HYPER_VOICE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + if (move == MOVE_SUNNY_DAY) + HP_BAR(player); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Solar Power doesn't increases a Sp. Attack if Cloud Nine/Air Lock is on the field (Traits)", s16 damage) +{ + u32 move; + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SUNNY_DAY; } + GIVEN { + ASSUME(GetMovePower(MOVE_HYPER_VOICE) > 0); + ASSUME(GetMoveCategory(MOVE_HYPER_VOICE) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_CHARIZARD) { Ability(ABILITY_BLAZE); Innates(ABILITY_SOLAR_POWER); } + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_SWIFT_SWIM); Innates(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_HYPER_VOICE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Solar Power causes the Pokémon to lose 1/8 max HP in Sun (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CHARIZARD) { Ability(ABILITY_BLAZE); Innates(ABILITY_SOLAR_POWER); MaxHP(80); HP(80); } + OPPONENT(SPECIES_WOBBUFFET); + } SCENE { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + HP_BAR(player); + } THEN { + EXPECT_EQ(player->hp, player->maxHP - player->maxHP/8); + } +} + +SINGLE_BATTLE_TEST("Solar Power doesn't cause the Pokémon to lose 1/8 max HP if Cloud Nine/Air Lock is on the field (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CHARIZARD) { Ability(ABILITY_BLAZE); Innates(ABILITY_SOLAR_POWER); MaxHP(80); HP(80); } + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_SWIFT_SWIM); Innates(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + NOT HP_BAR(player); + } THEN { + EXPECT_EQ(player->hp, player->maxHP); + } +} +#endif diff --git a/test/battle/ability/solid_rock.c b/test/battle/ability/solid_rock.c index d25fca625900..26ff7993bc0d 100644 --- a/test/battle/ability/solid_rock.c +++ b/test/battle/ability/solid_rock.c @@ -23,3 +23,28 @@ SINGLE_BATTLE_TEST("Solid Rock reduces damage to Super Effective moves by 0.75", EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.75), results[1].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Solid Rock reduces damage to Super Effective moves by 0.75 (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_STURDY; } + PARAMETRIZE { ability = ABILITY_SOLID_ROCK; } + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_CARRACOSTA].types[0] == TYPE_WATER); + ASSUME(gSpeciesInfo[SPECIES_CARRACOSTA].types[1] == TYPE_ROCK); + ASSUME(GetMoveType(MOVE_CLOSE_COMBAT) == TYPE_FIGHTING); + ASSUME(gTypeEffectivenessTable[TYPE_FIGHTING][TYPE_ROCK] > UQ_4_12(1.0)); + ASSUME(gTypeEffectivenessTable[TYPE_FIGHTING][TYPE_WATER] == UQ_4_12(1.0)); + PLAYER(SPECIES_CARRACOSTA) { Ability(ABILITY_SWIFT_SWIM); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_CLOSE_COMBAT); } + } SCENE { + HP_BAR(player, captureDamage: &results[i].damage); + MESSAGE("It's super effective!"); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.75), results[1].damage); + } +} +#endif diff --git a/test/battle/ability/soul_heart.c b/test/battle/ability/soul_heart.c index 6f20acb53b0c..88dfd35ddfe8 100644 --- a/test/battle/ability/soul_heart.c +++ b/test/battle/ability/soul_heart.c @@ -21,3 +21,27 @@ SINGLE_BATTLE_TEST("Soul Heart boosts Sp. Atk after opponent uses Memento") } TO_DO_BATTLE_TEST("TODO: Write Soul Heart (Ability) test titles") + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Soul Heart boosts Sp. Atk after opponent uses Memento (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_MEMENTO) == EFFECT_MEMENTO); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MAGEARNA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_SOUL_HEART); } + } WHEN { + TURN { MOVE(player, MOVE_MEMENTO); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Wobbuffet fainted!"); + ABILITY_POPUP(opponent, ABILITY_SOUL_HEART); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE - 1); // -2 from Memento, +1 from Soul Heart + } +} + +TO_DO_BATTLE_TEST("TODO: Write Soul Heart (Ability) test titles (Traits)") + +#endif diff --git a/test/battle/ability/soundproof.c b/test/battle/ability/soundproof.c index 6af6231cc2c8..50a710cc70cc 100644 --- a/test/battle/ability/soundproof.c +++ b/test/battle/ability/soundproof.c @@ -18,3 +18,23 @@ SINGLE_BATTLE_TEST("Soundproof makes sound moves fail against the ability user") } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Soundproof makes sound moves fail against the ability user (Traits)") +{ + GIVEN { + ASSUME(IsSoundMove(MOVE_BOOMBURST)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXPLOUD) { Ability(ABILITY_SCRAPPY); Innates(ABILITY_SOUNDPROOF); } + } WHEN { + TURN { MOVE(player, MOVE_BOOMBURST); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_SOUNDPROOF); + MESSAGE("The opposing Exploud's Soundproof blocks Boomburst!"); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BOOMBURST, player); + HP_BAR(opponent); + } + } +} +#endif diff --git a/test/battle/ability/speed_boost.c b/test/battle/ability/speed_boost.c index b8c9bd496642..78a45e5e1be2 100644 --- a/test/battle/ability/speed_boost.c +++ b/test/battle/ability/speed_boost.c @@ -18,3 +18,23 @@ SINGLE_BATTLE_TEST("Speed Boost gradually boosts Speed") MESSAGE("The opposing Wobbuffet used Celebrate!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Speed Boost gradually boosts Speed (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TORCHIC) { Ability(ABILITY_BLAZE); Innates(ABILITY_SPEED_BOOST); Speed(99); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(100); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Celebrate!"); + MESSAGE("Torchic used Celebrate!"); + ABILITY_POPUP(player, ABILITY_SPEED_BOOST); + MESSAGE("Torchic's Speed Boost raised its Speed!"); + MESSAGE("Torchic used Celebrate!"); + MESSAGE("The opposing Wobbuffet used Celebrate!"); + } +} +#endif diff --git a/test/battle/ability/stalwart.c b/test/battle/ability/stalwart.c index 3cca8e58add3..58744105bfa2 100644 --- a/test/battle/ability/stalwart.c +++ b/test/battle/ability/stalwart.c @@ -56,3 +56,59 @@ DOUBLE_BATTLE_TEST("Stalwart stops Lightning Rod and Storm Drain from redirectin } } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Stalwart ignores redirection from Follow-Me (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ARCHALUDON) { Ability(ABILITY_STURDY); Innates(ABILITY_STALWART); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_FOLLOW_ME); MOVE(playerLeft, MOVE_DRACO_METEOR, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FOLLOW_ME, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRACO_METEOR, playerLeft); + HP_BAR(opponentRight); + NOT HP_BAR(opponentLeft); + } +} + +DOUBLE_BATTLE_TEST("Stalwart stops Lightning Rod and Storm Drain from redirecting moves (Traits)") +{ + enum Ability ability; + u32 species; + PARAMETRIZE { ability = ABILITY_STORM_DRAIN; species = SPECIES_LUMINEON; } + PARAMETRIZE { ability = ABILITY_LIGHTNING_ROD; species = SPECIES_RAICHU; } + GIVEN { + ASSUME(GetMoveType(MOVE_SPARK) == TYPE_ELECTRIC); + ASSUME(GetMoveType(MOVE_WATER_GUN) == TYPE_WATER); + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_STURDY); Innates(ABILITY_STALWART); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + if (ability == ABILITY_LIGHTNING_ROD) + MOVE(playerLeft, MOVE_SPARK, target: opponentRight); + else + MOVE(playerLeft, MOVE_WATER_GUN, target: opponentRight); + } + } SCENE { + if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) { + HP_BAR(opponentRight); + NONE_OF { + ABILITY_POPUP(opponentLeft, ability); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("The opposing Raichu's Sp. Atk rose!"); + } + } else { + HP_BAR(opponentRight); + NONE_OF { + HP_BAR(opponentLeft); + } + } + } +} +#endif diff --git a/test/battle/ability/stamina.c b/test/battle/ability/stamina.c index 0d377f276f61..5ae3c1cfec6c 100644 --- a/test/battle/ability/stamina.c +++ b/test/battle/ability/stamina.c @@ -124,3 +124,115 @@ SINGLE_BATTLE_TEST("Stamina is not activated by users own Substitute") EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Stamina raises Defense by 1 when hit by a move (Traits)") +{ + s16 turnOneHit, turnTwoHit; + u16 move; + + PARAMETRIZE {move = MOVE_SCRATCH; } + PARAMETRIZE {move = MOVE_GUST; } + + GIVEN { + ASSUME(!IsBattleMoveStatus(MOVE_SCRATCH)); + ASSUME(!IsBattleMoveStatus(MOVE_GUST)); + ASSUME(GetMoveCategory(MOVE_GUST) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_STAMINA); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + TURN { MOVE(opponent, move); } + } SCENE { + STAMINA_HIT(opponent, player, move, "Wobbuffet's Defense rose!", turnOneHit); + STAMINA_HIT(opponent, player, move, "Wobbuffet's Defense rose!", turnTwoHit); + } + THEN { + if (move == MOVE_SCRATCH) { + EXPECT_MUL_EQ(turnTwoHit, Q_4_12(1.5), turnOneHit); + } + else { + EXPECT_EQ(turnTwoHit, turnOneHit); + } + } +} + +DOUBLE_BATTLE_TEST("Stamina activates correctly for every battler with the ability when hit by a multi target move (Traits)") +{ + enum Ability abilityLeft, abilityRight; + + PARAMETRIZE {abilityLeft = ABILITY_NONE, abilityRight = ABILITY_STAMINA; } + PARAMETRIZE {abilityLeft = ABILITY_STAMINA, abilityRight = ABILITY_NONE; } + PARAMETRIZE {abilityLeft = ABILITY_STAMINA, abilityRight = ABILITY_STAMINA; } + + GIVEN { + ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_LIGHT_METAL); Innates(abilityLeft); Speed(10); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_LIGHT_METAL); Innates(abilityRight); Speed(5); } + OPPONENT(SPECIES_WOBBUFFET) {Speed(20); } + OPPONENT(SPECIES_WOBBUFFET) {Speed(15); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_EARTHQUAKE);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, opponentLeft); + + HP_BAR(playerLeft); + HP_BAR(playerRight); + NOT HP_BAR(opponentLeft); // We need to check the attacker itself does NOT get damaged. There was an issue when the targets would get overwritten by the Stamina's stat raise. + HP_BAR(opponentRight); + + if (abilityLeft == ABILITY_STAMINA) { + STAMINA_STAT_RAISE(playerLeft, "Wobbuffet's Defense rose!"); + } + + if (abilityRight == ABILITY_STAMINA) { + STAMINA_STAT_RAISE(playerRight, "Wobbuffet's Defense rose!"); + } + + NOT HP_BAR(opponentLeft); // We need to check the attacker itself does NOT get damaged. There was an issue when the targets would get overwritten by the Stamina's stat raise. + } + THEN { + EXPECT_NE(playerLeft->hp, playerLeft->maxHP); + EXPECT_NE(playerRight->hp, playerRight->maxHP); + EXPECT_NE(opponentRight->hp, opponentRight->maxHP); + EXPECT_EQ(opponentLeft->hp, opponentLeft->maxHP); + } +} + +SINGLE_BATTLE_TEST("Stamina activates for every hit of a multi hit move (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MUDBRAY) { Ability(ABILITY_OWN_TEMPO); Innates(ABILITY_STAMINA); } + } WHEN { + TURN { MOVE(player, MOVE_DOUBLE_KICK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_KICK, player); + HP_BAR(opponent); + STAMINA_STAT_RAISE(opponent, "The opposing Mudbray's Defense rose!"); + STAMINA_STAT_RAISE(opponent, "The opposing Mudbray's Defense rose!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Stamina is not activated by users own Substitute (Traits)") +{ + GIVEN { + PLAYER(SPECIES_MUDBRAY) { Ability(ABILITY_OWN_TEMPO); Innates(ABILITY_STAMINA); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, player); + MESSAGE("Mudbray put in a substitute!"); + NONE_OF { + ABILITY_POPUP(player, ABILITY_STAMINA); + MESSAGE("Mudbray's Defense rose!"); + } + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} +#endif diff --git a/test/battle/ability/stance_change.c b/test/battle/ability/stance_change.c index ebddb8a8849b..9dc17af69886 100644 --- a/test/battle/ability/stance_change.c +++ b/test/battle/ability/stance_change.c @@ -78,3 +78,83 @@ SINGLE_BATTLE_TEST("Stance Change doesn't change Aegislash to Shield if King's S EXPECT_EQ(player->species, SPECIES_AEGISLASH_BLADE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Stance Change changes Aegislash from Shield to Blade when using a damaging move (Traits)") +{ + u16 move; + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_SWIFT; } + PARAMETRIZE { move = MOVE_GROWL; } + GIVEN { + PLAYER(SPECIES_AEGISLASH_SHIELD) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_STANCE_CHANGE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + if (move != MOVE_GROWL) { + ABILITY_POPUP(player, ABILITY_STANCE_CHANGE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + } else { + NONE_OF { + ABILITY_POPUP(player, ABILITY_STANCE_CHANGE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + } + } + ANIMATION(ANIM_TYPE_MOVE, move, player); + } THEN { + if (move != MOVE_GROWL) + EXPECT_EQ(player->species, SPECIES_AEGISLASH_BLADE); + else + EXPECT_EQ(player->species, SPECIES_AEGISLASH_SHIELD); + } +} + +SINGLE_BATTLE_TEST("Stance Change changes Aegislash from Blade to Shield when using King's Shield (Traits)") +{ + u16 move; + PARAMETRIZE { move = MOVE_PROTECT; } + PARAMETRIZE { move = MOVE_KINGS_SHIELD; } + GIVEN { + PLAYER(SPECIES_AEGISLASH_BLADE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_STANCE_CHANGE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + if (move == MOVE_KINGS_SHIELD) { + ABILITY_POPUP(player, ABILITY_STANCE_CHANGE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + } else { + NONE_OF { + ABILITY_POPUP(player, ABILITY_STANCE_CHANGE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + } + } + ANIMATION(ANIM_TYPE_MOVE, move, player); + } THEN { + if (move == MOVE_KINGS_SHIELD) + EXPECT_EQ(player->species, SPECIES_AEGISLASH_SHIELD); + else + EXPECT_EQ(player->species, SPECIES_AEGISLASH_BLADE); + } +} + +SINGLE_BATTLE_TEST("Stance Change doesn't change Aegislash to Shield if King's Shield is called by a different move - Sleep Talk (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SLEEP_TALK) == EFFECT_SLEEP_TALK); + PLAYER(SPECIES_AEGISLASH_BLADE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_STANCE_CHANGE); Moves(MOVE_KINGS_SHIELD, MOVE_SLEEP_TALK); Status1(STATUS1_SLEEP_TURN(3)); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SLEEP_TALK); } + } SCENE { + NONE_OF { + ABILITY_POPUP(player, ABILITY_STANCE_CHANGE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_KINGS_SHIELD, player); + } THEN { + EXPECT_EQ(player->species, SPECIES_AEGISLASH_BLADE); + } +} +#endif diff --git a/test/battle/ability/static.c b/test/battle/ability/static.c index c40197e742ae..0ed2ff58c179 100644 --- a/test/battle/ability/static.c +++ b/test/battle/ability/static.c @@ -65,3 +65,67 @@ SINGLE_BATTLE_TEST("Static triggers even if attacker is under substitute") STATUS_ICON(player, paralysis: TRUE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Static inflicts paralysis on contact (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_SWIFT; } + GIVEN { + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + ASSUME(!MoveMakesContact(MOVE_SWIFT)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIKACHU) { Ability(ABILITY_LIGHTNING_ROD); Innates(ABILITY_STATIC); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + if (MoveMakesContact(move)) { + ABILITY_POPUP(opponent, ABILITY_STATIC); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, player); + MESSAGE("The opposing Pikachu's Static paralyzed Wobbuffet, so it may be unable to move!"); + STATUS_ICON(player, paralysis: TRUE); + } else { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_STATIC); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, player); + MESSAGE("The opposing Pikachu's Static paralyzed Wobbuffet, so it may be unable to move!"); + STATUS_ICON(player, paralysis: TRUE); + } + } + } +} + +SINGLE_BATTLE_TEST("Static triggers 30% of the time (Traits)") +{ + PASSES_RANDOMLY(3, 10, RNG_STATIC); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_4); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIKACHU) { Ability(ABILITY_LIGHTNING_ROD); Innates(ABILITY_STATIC); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_STATIC); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, player); + MESSAGE("The opposing Pikachu's Static paralyzed Wobbuffet, so it may be unable to move!"); + STATUS_ICON(player, paralysis: TRUE); + } +} + +SINGLE_BATTLE_TEST("Static triggers even if attacker is under substitute (Traits)") +{ + GIVEN { + ASSUME(MoveMakesContact(MOVE_TACKLE)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIKACHU) { Ability(ABILITY_LIGHTNING_ROD); Innates(ABILITY_STATIC); } + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); } + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_STATIC); + STATUS_ICON(player, paralysis: TRUE); + } +} +#endif diff --git a/test/battle/ability/steadfast.c b/test/battle/ability/steadfast.c index 2d6fb5d90166..9fff61b7b570 100644 --- a/test/battle/ability/steadfast.c +++ b/test/battle/ability/steadfast.c @@ -54,3 +54,78 @@ DOUBLE_BATTLE_TEST("Steadfast doesn't activate if the user has already moved") } TO_DO_BATTLE_TEST("TODO: Write Steadfast (Ability) test titles") + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Steadfast boosts Speed when the user attempts to move but is flinched (Traits)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_FAKE_OUT, MOVE_EFFECT_FLINCH, 100)); + PLAYER(SPECIES_LUCARIO) { Ability(ABILITY_JUSTIFIED); Innates(ABILITY_STEADFAST); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_FAKE_OUT); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_STEADFAST); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Steadfast doesn't activate if the user wasn't flinched (Traits)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_FAKE_OUT, MOVE_EFFECT_FLINCH, 100)); + ASSUME(GetItemHoldEffect(ITEM_COVERT_CLOAK) == HOLD_EFFECT_COVERT_CLOAK); + PLAYER(SPECIES_LUCARIO) { Ability(ABILITY_JUSTIFIED); Innates(ABILITY_STEADFAST); Item(ITEM_COVERT_CLOAK); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_FAKE_OUT); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_STEADFAST); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Steadfast doesn't activate if the user has already moved (Traits)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_BITE, MOVE_EFFECT_FLINCH)); + ASSUME(GetMoveEffect(MOVE_INSTRUCT) == EFFECT_INSTRUCT); + PLAYER(SPECIES_LUCARIO) { Ability(ABILITY_JUSTIFIED); Innates(ABILITY_STEADFAST); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SWORDS_DANCE); MOVE(opponentLeft, MOVE_BITE, target: playerLeft); MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, playerLeft); + NOT ABILITY_POPUP(playerLeft, ABILITY_STEADFAST); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} + +TO_DO_BATTLE_TEST("TODO: Write Steadfast (Ability) test titles (Traits)") +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Steadfast doesn't activate if the user wasn't flinched (Multi)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_FAKE_OUT, MOVE_EFFECT_FLINCH, 100)); + ASSUME(GetItemHoldEffect(ITEM_COVERT_CLOAK) == HOLD_EFFECT_COVERT_CLOAK); + PLAYER(SPECIES_LUCARIO) { Ability(ABILITY_STEADFAST); Items(ITEM_PECHA_BERRY, ITEM_COVERT_CLOAK); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_FAKE_OUT); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_STEADFAST); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} +#endif diff --git a/test/battle/ability/steam_engine.c b/test/battle/ability/steam_engine.c index 1f487bf6bfbd..e74ffd14849b 100644 --- a/test/battle/ability/steam_engine.c +++ b/test/battle/ability/steam_engine.c @@ -21,3 +21,27 @@ SINGLE_BATTLE_TEST("Steam Engine raises speed when hit by a Fire or Water move") EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 6); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Steam Engine raises speed when hit by a Fire or Water move (Traits)") +{ + u16 move; + + PARAMETRIZE { move = MOVE_EMBER; } + PARAMETRIZE { move = MOVE_WATER_GUN; } + + GIVEN { + PLAYER(SPECIES_COALOSSAL) { Ability(ABILITY_FLAME_BODY); Innates(ABILITY_STEAM_ENGINE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ABILITY_POPUP(player, ABILITY_STEAM_ENGINE); + MESSAGE("Coalossal's Speed drastically rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 6); + } +} + +#endif diff --git a/test/battle/ability/steelworker.c b/test/battle/ability/steelworker.c index b4fa720cdcf5..ca836454f025 100644 --- a/test/battle/ability/steelworker.c +++ b/test/battle/ability/steelworker.c @@ -31,3 +31,36 @@ SINGLE_BATTLE_TEST("Steelworker increases Steel-type move damage", s16 damage) EXPECT_MUL_EQ(results[4].damage, Q_4_12(1.5), results[5].damage); // Flash Cannon should be affected } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Steelworker increases Steel-type move damage (Traits)", s16 damage) +{ + u32 move; + enum Ability ability; + + PARAMETRIZE { move = MOVE_SCRATCH; ability = ABILITY_KLUTZ; } + PARAMETRIZE { move = MOVE_SCRATCH; ability = ABILITY_STEELWORKER; } + PARAMETRIZE { move = MOVE_ANCHOR_SHOT; ability = ABILITY_KLUTZ; } + PARAMETRIZE { move = MOVE_ANCHOR_SHOT; ability = ABILITY_STEELWORKER; } + PARAMETRIZE { move = MOVE_FLASH_CANNON; ability = ABILITY_KLUTZ; } + PARAMETRIZE { move = MOVE_FLASH_CANNON; ability = ABILITY_STEELWORKER; } + + GIVEN { + ASSUME(GetMoveType(MOVE_SCRATCH) != TYPE_STEEL); + ASSUME(GetMoveType(MOVE_ANCHOR_SHOT) == TYPE_STEEL); + ASSUME(GetMoveType(MOVE_FLASH_CANNON) == TYPE_STEEL); + ASSUME(GetMoveCategory(MOVE_ANCHOR_SHOT) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_FLASH_CANNON) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_DHELMISE) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); // Scratch should be unaffected + EXPECT_MUL_EQ(results[2].damage, Q_4_12(1.5), results[3].damage); // Anchor Shot should be affected + EXPECT_MUL_EQ(results[4].damage, Q_4_12(1.5), results[5].damage); // Flash Cannon should be affected + } +} +#endif diff --git a/test/battle/ability/stench.c b/test/battle/ability/stench.c index e6a5c9cdccec..3e5cfda3961a 100644 --- a/test/battle/ability/stench.c +++ b/test/battle/ability/stench.c @@ -80,3 +80,103 @@ DOUBLE_BATTLE_TEST("Stench doesn't trigger if partner uses a move") } // TODO: Test against interaction with multi hits + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Stench has a 10% chance to flinch (Traits)") +{ + PASSES_RANDOMLY(1, 10, RNG_STENCH); + GIVEN { + ASSUME(GetMovePower(MOVE_SCRATCH) > 0); + PLAYER(SPECIES_GRIMER) { Ability(ABILITY_STICKY_HOLD); Innates(ABILITY_STENCH); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + } +} + +SINGLE_BATTLE_TEST("Stench does not stack with King's Rock (Traits)") +{ + PASSES_RANDOMLY(1, 10, RNG_STENCH); + GIVEN { + ASSUME(gItemsInfo[ITEM_KINGS_ROCK].holdEffect == HOLD_EFFECT_FLINCH); + ASSUME(GetMovePower(MOVE_SCRATCH) > 0); + + PLAYER(SPECIES_GRIMER) { Ability(ABILITY_STICKY_HOLD); Innates(ABILITY_STENCH); Item(ITEM_KINGS_ROCK); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + } +} + +DOUBLE_BATTLE_TEST("Stench only triggers if target takes damage (Traits)") +{ + GIVEN { + ASSUME(GetMovePower(MOVE_SCRATCH) > 0); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_FAKE_OUT, MOVE_EFFECT_FLINCH, 100)); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_GRIMER) { Ability(ABILITY_STICKY_HOLD); Innates(ABILITY_STENCH); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(playerLeft, MOVE_FAKE_OUT, target: opponentLeft); + MOVE(opponentLeft, MOVE_SCRATCH, WITH_RNG(RNG_STENCH, TRUE), target: playerRight); + MOVE(playerRight, MOVE_SCRATCH, target: opponentRight); + } + TURN { + MOVE(opponentLeft, MOVE_SCARY_FACE, WITH_RNG(RNG_STENCH, TRUE), target: playerRight); + MOVE(playerRight, MOVE_SCRATCH, target: opponentRight); + } + } SCENE { + NONE_OF { MESSAGE("Wynaut flinched and couldn't move!"); } + } +} + +DOUBLE_BATTLE_TEST("Stench doesn't trigger if partner uses a move (Traits)") +{ + GIVEN { + ASSUME(GetMovePower(MOVE_SCRATCH) > 0); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_FAKE_OUT, MOVE_EFFECT_FLINCH, 100)); + PLAYER(SPECIES_WOBBUFFET) { Speed(20); } + PLAYER(SPECIES_WYNAUT) { Speed(10); } + OPPONENT(SPECIES_GRIMER) { Speed(100); Ability(ABILITY_STICKY_HOLD); Innates(ABILITY_STENCH); } + OPPONENT(SPECIES_WOBBUFFET) {Speed(50); } + } WHEN { + TURN { + MOVE(playerLeft, MOVE_FAKE_OUT, target: opponentLeft); + MOVE(opponentRight, MOVE_SCRATCH, target: playerRight); + MOVE(playerRight, MOVE_SCRATCH, target: opponentRight); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FAKE_OUT, playerLeft); + MESSAGE("The opposing Grimer flinched and couldn't move!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentRight); + NOT MESSAGE("Wynaut flinched and couldn't move!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerRight); + } +} + +// TODO: Test against interaction with multi hits +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Stench does not stack with King's Rock (Multi)") +{ + PASSES_RANDOMLY(1, 10, RNG_STENCH); + GIVEN { + ASSUME(gItemsInfo[ITEM_KINGS_ROCK].holdEffect == HOLD_EFFECT_FLINCH); + ASSUME(GetMovePower(MOVE_SCRATCH) > 0); + + PLAYER(SPECIES_GRIMER) { Ability(ABILITY_STENCH); Items(ITEM_PECHA_BERRY, ITEM_KINGS_ROCK); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + } +} +#endif diff --git a/test/battle/ability/sticky_hold.c b/test/battle/ability/sticky_hold.c index b4fb047cbe11..2969f5ee39df 100644 --- a/test/battle/ability/sticky_hold.c +++ b/test/battle/ability/sticky_hold.c @@ -40,3 +40,86 @@ SINGLE_BATTLE_TEST("Sticky Hold prevents Incinerate from destroying gems") EXPECT_EQ(gBattleMons[B_POSITION_OPPONENT_LEFT].item, ITEM_GHOST_GEM); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Sticky Hold prevents item theft (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_THIEF) == EFFECT_STEAL_ITEM); + PLAYER(SPECIES_URSALUNA) { Item(ITEM_NONE); } + OPPONENT(SPECIES_GASTRODON) { Ability(ABILITY_STORM_DRAIN); Innates(ABILITY_STICKY_HOLD); Item(ITEM_LIFE_ORB); } + } WHEN { + TURN { MOVE(player, MOVE_THIEF); } + } SCENE { + MESSAGE("Ursaluna used Thief!"); + ABILITY_POPUP(opponent, ABILITY_STICKY_HOLD); + MESSAGE("The opposing Gastrodon's Sticky Hold made Thief ineffective!"); + } +} + +SINGLE_BATTLE_TEST("Sticky Hold prevents Incinerate from destroying berries (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_INCINERATE); } + OPPONENT(SPECIES_GASTRODON) { Ability(ABILITY_STORM_DRAIN); Innates(ABILITY_STICKY_HOLD); Item(ITEM_CHERI_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_INCINERATE); } + } THEN { + EXPECT_EQ(gBattleMons[B_POSITION_OPPONENT_LEFT].item, ITEM_CHERI_BERRY); + } +} + +SINGLE_BATTLE_TEST("Sticky Hold prevents Incinerate from destroying gems (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_INCINERATE_GEMS, GEN_6); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_INCINERATE); } + OPPONENT(SPECIES_GASTRODON) { Ability(ABILITY_STORM_DRAIN); Innates(ABILITY_STICKY_HOLD); Item(ITEM_GHOST_GEM); } + } WHEN { + TURN { MOVE(player, MOVE_INCINERATE); } + } THEN { + EXPECT_EQ(gBattleMons[B_POSITION_OPPONENT_LEFT].item, ITEM_GHOST_GEM); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Sticky Hold prevents item theft (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_THIEF) == EFFECT_STEAL_ITEM); + PLAYER(SPECIES_URSALUNA) { Items(ITEM_PECHA_BERRY, ITEM_NONE); } + OPPONENT(SPECIES_GASTRODON) { Ability(ABILITY_STICKY_HOLD); Items(ITEM_PECHA_BERRY, ITEM_LIFE_ORB); } + } WHEN { + TURN { MOVE(player, MOVE_THIEF); } + } SCENE { + MESSAGE("Ursaluna used Thief!"); + ABILITY_POPUP(opponent, ABILITY_STICKY_HOLD); + MESSAGE("The opposing Gastrodon's Sticky Hold made Thief ineffective!"); + } +} +SINGLE_BATTLE_TEST("Sticky Hold prevents Incinerate from destroying berries (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_INCINERATE); } + OPPONENT(SPECIES_GASTRODON) { Ability(ABILITY_STICKY_HOLD); Items(ITEM_NUGGET, ITEM_CHERI_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_INCINERATE); } + } THEN { + EXPECT_EQ(gBattleMons[B_POSITION_OPPONENT_LEFT].item, ITEM_CHERI_BERRY); + } +} + +SINGLE_BATTLE_TEST("Sticky Hold prevents Incinerate from destroying gems (Multi)") +{ + GIVEN { + WITH_CONFIG(CONFIG_INCINERATE_GEMS, GEN_6); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_INCINERATE); } + OPPONENT(SPECIES_GASTRODON) { Ability(ABILITY_STICKY_HOLD); Items(ITEM_GREAT_BALL, ITEM_GHOST_GEM); } + } WHEN { + TURN { MOVE(player, MOVE_INCINERATE); } + } THEN { + EXPECT_EQ(gBattleMons[B_POSITION_OPPONENT_LEFT].item, ITEM_GHOST_GEM); + } +} +#endif diff --git a/test/battle/ability/storm_drain.c b/test/battle/ability/storm_drain.c index c1cf5c890b71..178f3f8d2659 100644 --- a/test/battle/ability/storm_drain.c +++ b/test/battle/ability/storm_drain.c @@ -79,3 +79,76 @@ DOUBLE_BATTLE_TEST("Storm Drain forces single-target Water-type moves to target ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Storm Drain absorbs Water-type moves and increases the Sp. Attack [Gen5+] (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_WATER_GUN) == TYPE_WATER); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GASTRODON_EAST) { Ability(ABILITY_SAND_FORCE); Innates(ABILITY_STORM_DRAIN); } + } WHEN { + TURN { MOVE(player, MOVE_WATER_GUN); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, player); + HP_BAR(opponent); + }; + ABILITY_POPUP(opponent, ABILITY_STORM_DRAIN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Gastrodon's Sp. Atk rose!"); + } else { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_STORM_DRAIN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Gastrodon's Sp. Atk rose!"); + }; + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, player); + HP_BAR(opponent); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +DOUBLE_BATTLE_TEST("Storm Drain forces single-target Water-type moves to target the Pokémon with this Ability. (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_WATER_GUN) == TYPE_WATER); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GASTRODON_EAST) { Ability(ABILITY_SAND_FORCE); Innates(ABILITY_STORM_DRAIN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(playerLeft, MOVE_WATER_GUN, target: opponentRight); + MOVE(playerRight, MOVE_WATER_GUN, target: opponentRight); + MOVE(opponentLeft, MOVE_CELEBRATE); + MOVE(opponentRight, MOVE_CELEBRATE); + } + } SCENE { + if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) { + NONE_OF { + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + }; + ABILITY_POPUP(opponentLeft, ABILITY_STORM_DRAIN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("The opposing Gastrodon's Sp. Atk rose!"); + ABILITY_POPUP(opponentLeft, ABILITY_STORM_DRAIN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("The opposing Gastrodon's Sp. Atk rose!"); + } else { + NONE_OF { + HP_BAR(opponentRight); + }; + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, playerLeft); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, playerRight); + HP_BAR(opponentLeft); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight); + } +} +#endif diff --git a/test/battle/ability/sturdy.c b/test/battle/ability/sturdy.c index 7087de3a020f..708156cb5e88 100644 --- a/test/battle/ability/sturdy.c +++ b/test/battle/ability/sturdy.c @@ -64,3 +64,50 @@ SINGLE_BATTLE_TEST("Sturdy does not prevent non-OHKOs") HP_BAR(player, hp: 0); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Sturdy prevents OHKO moves (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FISSURE) == EFFECT_OHKO); + PLAYER(SPECIES_GEODUDE) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_STURDY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_FISSURE); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Fissure!"); + ABILITY_POPUP(player, ABILITY_STURDY); + MESSAGE("Geodude was protected by Sturdy!"); + } THEN { + EXPECT_EQ(player->hp, player->maxHP); + } +} + +SINGLE_BATTLE_TEST("Sturdy prevents OHKOs (Traits)") +{ + GIVEN { + PLAYER(SPECIES_GEODUDE) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_STURDY); MaxHP(100); HP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SEISMIC_TOSS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SEISMIC_TOSS, opponent); + HP_BAR(player, hp: 1); + ABILITY_POPUP(player, ABILITY_STURDY); + MESSAGE("Geodude endured the hit using Sturdy!"); + } +} + +SINGLE_BATTLE_TEST("Sturdy does not prevent non-OHKOs (Traits)") +{ + GIVEN { + PLAYER(SPECIES_GEODUDE) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_STURDY); MaxHP(100); HP(99); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SEISMIC_TOSS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SEISMIC_TOSS, opponent); + HP_BAR(player, hp: 0); + } +} +#endif diff --git a/test/battle/ability/super_luck.c b/test/battle/ability/super_luck.c index d271c6ff696c..7529895a8a5d 100644 --- a/test/battle/ability/super_luck.c +++ b/test/battle/ability/super_luck.c @@ -23,3 +23,28 @@ SINGLE_BATTLE_TEST("Super Luck increases the critical hit ratio by 1 stage") } TO_DO_BATTLE_TEST("Super Luck increases the chances of wild Pokémon holding items (Gen8+)"); + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Super Luck increases the critical hit ratio by 1 stage (Traits)") +{ + u32 j, genConfig = 0, passes = 0, trials = 0; + + PARAMETRIZE { genConfig = GEN_1; passes = 5; trials = 32; } // ~15.6% with Togepi's base speed + for (j = GEN_2; j <= GEN_9; j++) + PARAMETRIZE { genConfig = j; passes = 1; trials = 8; } // 12.5% + PASSES_RANDOMLY(passes, trials, RNG_CRITICAL_HIT); + GIVEN { + ASSUME(GetSpeciesBaseSpeed(SPECIES_TOGEPI) == 20); + WITH_CONFIG(CONFIG_CRIT_CHANCE, genConfig); + PLAYER(SPECIES_TOGEPI) { Ability(ABILITY_HUSTLE); Innates(ABILITY_SUPER_LUCK); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("A critical hit!"); + } +} + +TO_DO_BATTLE_TEST("Super Luck increases the chances of wild Pokémon holding items (Gen8+) (Traits)"); +#endif diff --git a/test/battle/ability/supersweet_syrup.c b/test/battle/ability/supersweet_syrup.c index 572f69775762..8523e2d68d4f 100644 --- a/test/battle/ability/supersweet_syrup.c +++ b/test/battle/ability/supersweet_syrup.c @@ -81,3 +81,86 @@ SINGLE_BATTLE_TEST("Supersweet Syrup can not further lower opponents evasion if EXPECT_EQ(player->statStages[STAT_EVASION], MIN_STAT_STAGE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Supersweet Syrup lowers evasion once per battle by one stage (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_DIPPLIN) { Ability(ABILITY_GLUTTONY); Innates(ABILITY_SUPERSWEET_SYRUP); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(opponent, 1); } + TURN { SWITCH(opponent, 0); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_SUPERSWEET_SYRUP); + MESSAGE("A supersweet aroma is wafting from the syrup covering the opposing Dipplin!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("2 withdrew Dipplin!"); + MESSAGE("2 withdrew Wobbuffet!"); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_SUPERSWEET_SYRUP); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("A supersweet aroma is wafting from the syrup covering Foes Dipplin!"); + } + } THEN { + EXPECT_EQ(player->statStages[STAT_EVASION], DEFAULT_STAT_STAGE - 1); + } +} + +DOUBLE_BATTLE_TEST("Supersweet Syrup lowers evasion of both opposing mon's in battle (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_DIPPLIN) { Ability(ABILITY_GLUTTONY); Innates(ABILITY_SUPERSWEET_SYRUP); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_SUPERSWEET_SYRUP); + MESSAGE("A supersweet aroma is wafting from the syrup covering the opposing Dipplin!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_EVASION], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(playerRight->statStages[STAT_EVASION], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Supersweet Syrup can not further lower opponents evasion if it is at minimum stages (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ODDISH); + OPPONENT(SPECIES_ODDISH); + OPPONENT(SPECIES_HYDRAPPLE) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_SUPERSWEET_SYRUP); } + } WHEN { + TURN { MOVE(opponent, MOVE_SWEET_SCENT); } + TURN { MOVE(opponent, MOVE_SWEET_SCENT); } + TURN { MOVE(opponent, MOVE_SWEET_SCENT); } + if (GetMoveEffect(MOVE_SWEET_SCENT) == EFFECT_EVASION_DOWN) { + TURN { MOVE(opponent, MOVE_SWEET_SCENT); } + TURN { MOVE(opponent, MOVE_SWEET_SCENT); } + TURN { MOVE(opponent, MOVE_SWEET_SCENT); } + } + TURN { SWITCH(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWEET_SCENT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWEET_SCENT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWEET_SCENT, opponent); + if (GetMoveEffect(MOVE_SWEET_SCENT) == EFFECT_EVASION_DOWN) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWEET_SCENT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWEET_SCENT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWEET_SCENT, opponent); + } + ABILITY_POPUP(opponent, ABILITY_SUPERSWEET_SYRUP); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Oddish's evasiveness fell!"); + } + MESSAGE("Oddish's evasiveness won't go any lower!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_EVASION], MIN_STAT_STAGE); + } +} +#endif diff --git a/test/battle/ability/supreme_overlord.c b/test/battle/ability/supreme_overlord.c index 17f9abe7b73a..ceb3b775dbdc 100644 --- a/test/battle/ability/supreme_overlord.c +++ b/test/battle/ability/supreme_overlord.c @@ -133,3 +133,138 @@ SINGLE_BATTLE_TEST("Supreme Overlord's message displays correctly after all batt MESSAGE("The opposing Kingambit gained strength from the fallen!"); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Supreme Overlord boosts Attack by an additive 10% per fainted mon on its side upon switch in (Traits)", s16 damage) +{ + bool32 switchMon = 0; + PARAMETRIZE { switchMon = FALSE; } + PARAMETRIZE { switchMon = TRUE; } + GIVEN { + PLAYER(SPECIES_KINGAMBIT) { Ability(ABILITY_PRESSURE); Innates(ABILITY_SUPREME_OVERLORD); } + PLAYER(SPECIES_PAWNIARD); + PLAYER(SPECIES_PAWNIARD); + PLAYER(SPECIES_PAWNIARD); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (switchMon) + TURN { SWITCH(playerLeft, 3); } + TURN { MOVE(playerRight, MOVE_MEMENTO, target: opponentRight); SEND_OUT(playerRight, 2); } + if (switchMon) + TURN { SWITCH(playerLeft, 0); } + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + if (switchMon) { + ABILITY_POPUP(playerLeft, ABILITY_SUPREME_OVERLORD); + MESSAGE("Kingambit gained strength from the fallen!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft); + HP_BAR(opponentLeft, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.1), results[1].damage); + } +} + +DOUBLE_BATTLE_TEST("Supreme Overlord's boost caps at a 1.5x multipler (Traits)", s16 damage) +{ + u32 faintCount = 0; + PARAMETRIZE { faintCount = 5; } + PARAMETRIZE { faintCount = 6; } + GIVEN { + PLAYER(SPECIES_PAWNIARD); + PLAYER(SPECIES_PAWNIARD); + PLAYER(SPECIES_PAWNIARD); + PLAYER(SPECIES_KINGAMBIT) { Ability(ABILITY_PRESSURE); Innates(ABILITY_SUPREME_OVERLORD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_MEMENTO, target: opponentRight); SEND_OUT(playerLeft, 2); } + TURN { MOVE(playerLeft, MOVE_MEMENTO, target: opponentRight); SEND_OUT(playerLeft, 0); USE_ITEM(playerRight, ITEM_REVIVE, 0); } + TURN { MOVE(playerLeft, MOVE_MEMENTO, target: opponentRight); SEND_OUT(playerLeft, 2); USE_ITEM(playerRight, ITEM_REVIVE, 2); } + TURN { MOVE(playerLeft, MOVE_MEMENTO, target: opponentRight); SEND_OUT(playerLeft, 0); USE_ITEM(playerRight, ITEM_REVIVE, 0); } + TURN { MOVE(playerLeft, MOVE_MEMENTO, target: opponentRight); SEND_OUT(playerLeft, 2); USE_ITEM(playerRight, ITEM_REVIVE, 2); } + if (faintCount == 6) + TURN { MOVE(playerLeft, MOVE_MEMENTO, target: opponentRight); SEND_OUT(playerLeft, 0); USE_ITEM(playerRight, ITEM_REVIVE, 0); } + TURN { SWITCH(playerRight, 3); } + TURN { MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + ABILITY_POPUP(playerRight, ABILITY_SUPREME_OVERLORD); + MESSAGE("Kingambit gained strength from the fallen!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerRight); + HP_BAR(opponentLeft, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Supreme Overlord does not boost attack if party members are already fainted at the start of the battle (Traits)", s16 damage) +{ + u32 fainted = 0; + + PARAMETRIZE { fainted = FALSE; } + PARAMETRIZE { fainted = TRUE; } + GIVEN { + PLAYER(SPECIES_KINGAMBIT) { Ability(ABILITY_PRESSURE); Innates(ABILITY_SUPREME_OVERLORD); } + PLAYER(SPECIES_PAWNIARD) { HP(fainted ? 0 : 1); } + PLAYER(SPECIES_PAWNIARD) { HP(fainted ? 0 : 1); } + PLAYER(SPECIES_PAWNIARD) { HP(fainted ? 0 : 1); } + PLAYER(SPECIES_PAWNIARD) { HP(fainted ? 0 : 1); } + PLAYER(SPECIES_PAWNIARD) { HP(fainted ? 0 : 1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH, target: opponent); } + } SCENE { + NONE_OF { + ABILITY_POPUP(player, ABILITY_SUPREME_OVERLORD); + MESSAGE("Kingambit gained strength from the fallen!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Supreme Overlord's message displays correctly after all battlers fainted - Player (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + PLAYER(SPECIES_WOBBUFFET) { HP(1);} + PLAYER(SPECIES_KINGAMBIT) { Ability(ABILITY_PRESSURE); Innates(ABILITY_SUPREME_OVERLORD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_EXPLOSION); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + } SCENE { + HP_BAR(opponent, hp: 0); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, opponent); + // Everyone faints. + SEND_IN_MESSAGE("Kingambit"); + MESSAGE("2 sent out Wobbuffet!"); + ABILITY_POPUP(player, ABILITY_SUPREME_OVERLORD); + MESSAGE("Kingambit gained strength from the fallen!"); + } +} + +SINGLE_BATTLE_TEST("Supreme Overlord's message displays correctly after all battlers fainted - Opponent (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(1);} + OPPONENT(SPECIES_KINGAMBIT) { Ability(ABILITY_PRESSURE); Innates(ABILITY_SUPREME_OVERLORD); } + } WHEN { + TURN { MOVE(player, MOVE_EXPLOSION); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + } SCENE { + HP_BAR(player, hp: 0); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, player); + // Everyone faints. + SEND_IN_MESSAGE("Wobbuffet"); + MESSAGE("2 sent out Kingambit!"); + ABILITY_POPUP(opponent, ABILITY_SUPREME_OVERLORD); + MESSAGE("The opposing Kingambit gained strength from the fallen!"); + } +} +#endif diff --git a/test/battle/ability/swarm.c b/test/battle/ability/swarm.c index ce94ae22e21a..a3f915a83e83 100644 --- a/test/battle/ability/swarm.c +++ b/test/battle/ability/swarm.c @@ -27,3 +27,30 @@ SINGLE_BATTLE_TEST("Swarm boosts Bug-type moves in a pinch", s16 damage) EXPECT_EQ(results[1].damage, 72); } } + +SINGLE_BATTLE_TEST("Swarm boosts Bug-type moves in a pinch", s16 damage) +{ + u16 hp; + PARAMETRIZE { hp = 99; } + PARAMETRIZE { hp = 33; } + GIVEN { + ASSUME(GetMoveType(MOVE_BUG_BITE) == TYPE_BUG); + ASSUME(GetMovePower(MOVE_BUG_BITE) == 60); + ASSUME(GetMoveCategory(MOVE_BUG_BITE) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetSpeciesType(SPECIES_LEDYBA, 0) == TYPE_BUG); + ASSUME(GetSpeciesType(SPECIES_WOBBUFFET, 0) == TYPE_PSYCHIC); + ASSUME(GetSpeciesType(SPECIES_WOBBUFFET, 1) == TYPE_PSYCHIC); + PLAYER(SPECIES_LEDYBA) { Ability(ABILITY_RATTLED); Innates(ABILITY_SWARM); MaxHP(99); HP(hp); Attack(45); } + OPPONENT(SPECIES_WOBBUFFET) { Defense(121); } + } WHEN { + TURN { MOVE(player, MOVE_BUG_BITE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + // Due to numerics related to rounding on each applied multiplier, + // the 50% move power increase doesn't manifest as a 50% damage increase, but as a 44% damage increase in this case. + // Values obtained from https://calc.pokemonshowdown.com (Neutral nature and 0 IVs on both sides) + EXPECT_EQ(results[0].damage, 50); + EXPECT_EQ(results[1].damage, 72); + } +} diff --git a/test/battle/ability/sweet_veil.c b/test/battle/ability/sweet_veil.c index 956058620382..c084d4333d3f 100644 --- a/test/battle/ability/sweet_veil.c +++ b/test/battle/ability/sweet_veil.c @@ -58,3 +58,57 @@ DOUBLE_BATTLE_TEST("Sweet Veil prevents Yawn activation") NOT STATUS_ICON(opponentLeft, sleep: TRUE); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Sweet Veil prevents Sleep on partner - right target (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_BOUNSWEET) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_SWEET_VEIL); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_HYPNOSIS, target: opponentRight); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPNOSIS, playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_SWEET_VEIL); + NOT STATUS_ICON(opponentRight, sleep: TRUE); + } +} + +DOUBLE_BATTLE_TEST("Sweet Veil prevents Sleep on partner - left target (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_BOUNSWEET) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_SWEET_VEIL); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_HYPNOSIS, target: opponentLeft); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPNOSIS, playerLeft); + ABILITY_POPUP(opponentRight, ABILITY_SWEET_VEIL); + NOT STATUS_ICON(opponentLeft, sleep: TRUE); + } +} + +DOUBLE_BATTLE_TEST("Sweet Veil prevents Yawn activation (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_BOUNSWEET) { Ability(ABILITY_OBLIVIOUS); Innates(ABILITY_SWEET_VEIL); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_YAWN, target: opponentLeft); } + TURN { SWITCH(opponentRight, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_YAWN, playerLeft); + + // Turn 2 + ABILITY_POPUP(opponentRight, ABILITY_SWEET_VEIL); + NOT STATUS_ICON(opponentLeft, sleep: TRUE); + } +} +#endif diff --git a/test/battle/ability/sword_of_ruin.c b/test/battle/ability/sword_of_ruin.c index 40c84b213f20..25fa9b57ccf8 100644 --- a/test/battle/ability/sword_of_ruin.c +++ b/test/battle/ability/sword_of_ruin.c @@ -188,3 +188,161 @@ DOUBLE_BATTLE_TEST("Sword of Ruin's Defense reduction is ignored by Gastro Acid" EXPECT_LT(results[0].damage, results[1].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Sword of Ruin reduces Defense if opposing mon's ability doesn't match (Traits)") +{ + s16 damage[2]; + + GIVEN { + PLAYER(SPECIES_CHIEN_PAO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_SWORD_OF_RUIN); } + OPPONENT(SPECIES_CHIEN_PAO) { Ability(ABILITY_LIGHT_METAL); } + OPPONENT(SPECIES_CHIEN_PAO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_SWORD_OF_RUIN); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_ROLE_PLAY); } + TURN { SWITCH(opponent, 1); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(player, ABILITY_SWORD_OF_RUIN); + MESSAGE("Chien-Pao's Sword of Ruin weakened the Defense of all surrounding Pokémon!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[1], Q_4_12(1.33), damage[0]); + } +} + +SINGLE_BATTLE_TEST("Sword of Ruin's message displays correctly after all battlers fainted - Player (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + PLAYER(SPECIES_WOBBUFFET) { HP(1);} + PLAYER(SPECIES_CHIEN_PAO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_SWORD_OF_RUIN); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_EXPLOSION); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_RUINATION); } + } SCENE { + HP_BAR(opponent, hp: 0); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, opponent); + // Everyone faints. + MESSAGE("Go! Chien-Pao!"); + MESSAGE("2 sent out Wobbuffet!"); + ABILITY_POPUP(player, ABILITY_SWORD_OF_RUIN); + MESSAGE("Chien-Pao's Sword of Ruin weakened the Defense of all surrounding Pokémon!"); + } +} + +SINGLE_BATTLE_TEST("Sword of Ruin's message displays correctly after all battlers fainted - Opponent (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(1);} + OPPONENT(SPECIES_CHIEN_PAO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_SWORD_OF_RUIN); } + } WHEN { + TURN { MOVE(player, MOVE_EXPLOSION); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_RUINATION); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + HP_BAR(player, hp: 0); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, player); + // Everyone faints. + SEND_IN_MESSAGE("Wobbuffet"); + MESSAGE("2 sent out Chien-Pao!"); + ABILITY_POPUP(opponent, ABILITY_SWORD_OF_RUIN); + MESSAGE("The opposing Chien-Pao's Sword of Ruin weakened the Defense of all surrounding Pokémon!"); + } +} + +DOUBLE_BATTLE_TEST("Sword of Ruin increases damage taken by special moves in Wonder Room (Traits)", s16 damage) +{ + bool32 useWonderRoom; + u32 move; + + PARAMETRIZE { useWonderRoom = FALSE; move = MOVE_SCRATCH; } + PARAMETRIZE { useWonderRoom = FALSE; move = MOVE_ROUND; } + PARAMETRIZE { useWonderRoom = TRUE; move = MOVE_SCRATCH; } + PARAMETRIZE { useWonderRoom = TRUE; move = MOVE_ROUND; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_WONDER_ROOM) == EFFECT_WONDER_ROOM); + ASSUME(GetMoveCategory(MOVE_ROUND) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMoveEffect(MOVE_ROUND) != EFFECT_PSYSHOCK); + PLAYER(SPECIES_CHIEN_PAO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_SWORD_OF_RUIN); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (useWonderRoom) + TURN { MOVE(opponentLeft, MOVE_WONDER_ROOM); MOVE(playerRight, move, target: opponentLeft); } + else + TURN { MOVE(playerRight, move, target: opponentLeft); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_SWORD_OF_RUIN); + MESSAGE("Chien-Pao's Sword of Ruin weakened the Defense of all surrounding Pokémon!"); + ANIMATION(ANIM_TYPE_MOVE, move, playerRight); + HP_BAR(opponentLeft, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_LT(results[2].damage, results[0].damage); // In Wonder Room, physical move deals less damage + EXPECT_GT(results[3].damage, results[1].damage); // In Wonder Room, special move deals more damage + } +} + +SINGLE_BATTLE_TEST("Sword of Ruin doesn't activate when dragged out by Mold Breaker attacker (Traits)") +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_MOLD_BREAKER; } + PARAMETRIZE { ability = ABILITY_SAND_RUSH; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_DRAGON_TAIL) == EFFECT_HIT_SWITCH_TARGET); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_CHIEN_PAO) { Ability(ABILITY_SWORD_OF_RUIN); } + OPPONENT(SPECIES_EXCADRILL) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, opponent); + if (ability == ABILITY_MOLD_BREAKER) + { + NONE_OF { + ABILITY_POPUP(player, ABILITY_SWORD_OF_RUIN); + MESSAGE("Chien-Pao's Sword of Ruin weakened the Defense of all surrounding Pokémon!"); + } + } + else + { + ABILITY_POPUP(player, ABILITY_SWORD_OF_RUIN); + MESSAGE("Chien-Pao's Sword of Ruin weakened the Defense of all surrounding Pokémon!"); + } + } +} + +DOUBLE_BATTLE_TEST("Sword of Ruin's Defense reduction is not ignored by Mold Breaker", s16 damage) +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_MOLD_BREAKER; } + PARAMETRIZE { ability = ABILITY_SAND_RUSH; } + + GIVEN { + PLAYER(SPECIES_CHIEN_PAO) { Ability(ABILITY_SWORD_OF_RUIN); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_EXCADRILL) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerRight); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_SWORD_OF_RUIN); + MESSAGE("Chien-Pao's Sword of Ruin weakened the Defense of all surrounding Pokémon!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); + HP_BAR(playerRight, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} +#endif diff --git a/test/battle/ability/symbiosis.c b/test/battle/ability/symbiosis.c index 44e7c5f1bd8e..a4836ee057a0 100644 --- a/test/battle/ability/symbiosis.c +++ b/test/battle/ability/symbiosis.c @@ -178,3 +178,361 @@ DOUBLE_BATTLE_TEST("Symbiosis transfers its item after Gem consumption, but befo EXPECT_EQ(playerRight->item, ITEM_NONE); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Symbiosis transfers its item to an ally after it consumes an item (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_ROOM_SERVICE].holdEffect == HOLD_EFFECT_ROOM_SERVICE); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_ROOM_SERVICE); } + PLAYER(SPECIES_ORANGURU) { Ability(ABILITY_TELEPATHY); Innates(ABILITY_SYMBIOSIS); Item(ITEM_TOXIC_ORB); } + OPPONENT(SPECIES_KIRLIA); + OPPONENT(SPECIES_SHUCKLE); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_TRICK_ROOM); } + } SCENE { + MESSAGE("The opposing Kirlia used Trick Room!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Using Room Service, the Speed of Wobbuffet fell!"); + // symbiosis triggers + ABILITY_POPUP(playerRight, ABILITY_SYMBIOSIS); + MESSAGE("Oranguru passed its Toxic Orb to Wobbuffet through Symbiosis!"); + // end of turn, wobb gets poisoned + MESSAGE("Wobbuffet was badly poisoned!"); + STATUS_ICON(playerLeft, STATUS1_TOXIC_POISON); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_TOXIC_ORB); + EXPECT_EQ(playerRight->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Symbiosis triggers after partners berry eaten from bug bite (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LIECHI_BERRY].holdEffect == HOLD_EFFECT_ATTACK_UP); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LIECHI_BERRY); } + PLAYER(SPECIES_ORANGURU) { Ability(ABILITY_TELEPATHY); Innates(ABILITY_SYMBIOSIS); Item(ITEM_TOXIC_ORB); } + OPPONENT(SPECIES_STARAVIA); + OPPONENT(SPECIES_SHUCKLE); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_BUG_BITE, target: playerLeft); } + } SCENE { + MESSAGE("The opposing Staravia used Bug Bite!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BUG_BITE, opponentLeft); + HP_BAR(playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("Using Liechi Berry, the Attack of the opposing Staravia rose!"); + // symbiosis triggers + ABILITY_POPUP(playerRight, ABILITY_SYMBIOSIS); + MESSAGE("Oranguru passed its Toxic Orb to Wobbuffet through Symbiosis!"); + // end of turn, wobb gets poisoned + MESSAGE("Wobbuffet was badly poisoned!"); + STATUS_ICON(playerLeft, STATUS1_TOXIC_POISON); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_TOXIC_ORB); + EXPECT_EQ(playerRight->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Symbiosis triggers after partner bestows its item (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(100); Item(ITEM_FLAME_ORB); } + PLAYER(SPECIES_ORANGURU) { Speed(75); Ability(ABILITY_TELEPATHY); Innates(ABILITY_SYMBIOSIS); Item(ITEM_TOXIC_ORB); } + OPPONENT(SPECIES_STARAVIA) { Speed(50); } + OPPONENT(SPECIES_SHUCKLE) { Speed(25); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_BESTOW, target: opponentLeft); } + } SCENE { + MESSAGE("Wobbuffet used Bestow!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BESTOW, playerLeft); + MESSAGE("The opposing Staravia received Flame Orb from Wobbuffet!"); + // symbiosis triggers + ABILITY_POPUP(playerRight, ABILITY_SYMBIOSIS); + MESSAGE("Oranguru passed its Toxic Orb to Wobbuffet through Symbiosis!"); + // end of turn, wobb gets poisoned + MESSAGE("Wobbuffet was badly poisoned!"); + STATUS_ICON(playerLeft, STATUS1_TOXIC_POISON); + // staravia gets burned + MESSAGE("The opposing Staravia was burned!"); + STATUS_ICON(opponentLeft, STATUS1_BURN); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_TOXIC_ORB); + EXPECT_EQ(playerRight->item, ITEM_NONE); + EXPECT_EQ(opponentLeft->item, ITEM_FLAME_ORB); + } +} + +DOUBLE_BATTLE_TEST("Symbiosis triggers after partner flings its item (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(100); Item(ITEM_FLAME_ORB); } + PLAYER(SPECIES_ORANGURU) { Speed(75); Ability(ABILITY_TELEPATHY); Innates(ABILITY_SYMBIOSIS); Item(ITEM_TOXIC_ORB); } + OPPONENT(SPECIES_STARAVIA) { Speed(50); } + OPPONENT(SPECIES_SHUCKLE) { Speed(25); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_FLING, target: opponentLeft); } + } SCENE { + MESSAGE("Wobbuffet used Fling!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, playerLeft); + MESSAGE("The opposing Staravia was burned!"); + STATUS_ICON(opponentLeft, STATUS1_BURN); + // symbiosis triggers + ABILITY_POPUP(playerRight, ABILITY_SYMBIOSIS); + MESSAGE("Oranguru passed its Toxic Orb to Wobbuffet through Symbiosis!"); + // end of turn, wobb gets poisoned + MESSAGE("Wobbuffet was badly poisoned!"); + STATUS_ICON(playerLeft, STATUS1_TOXIC_POISON); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_TOXIC_ORB); + EXPECT_EQ(playerRight->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Symbiosis transfers its item to an ally after it consumes a weakness berry (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_CHILAN_BERRY].holdEffect == HOLD_EFFECT_RESIST_BERRY); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_CHILAN_BERRY); } + PLAYER(SPECIES_ORANGURU) { Ability(ABILITY_TELEPATHY); Innates(ABILITY_SYMBIOSIS); Item(ITEM_TOXIC_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft); + ABILITY_POPUP(playerRight, ABILITY_SYMBIOSIS); + STATUS_ICON(playerLeft, STATUS1_TOXIC_POISON); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_TOXIC_ORB); + EXPECT_EQ(playerRight->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Symbiosis transfers its item after Gem consumption and move execution (Gen7+) (Traits)") +{ + GIVEN { + ASSUME(GetItemHoldEffect(ITEM_NORMAL_GEM) == HOLD_EFFECT_GEMS); + WITH_CONFIG(CONFIG_SYMBIOSIS_GEMS, GEN_7); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMAL_GEM); } + PLAYER(SPECIES_ORANGURU) { Ability(ABILITY_TELEPATHY); Innates(ABILITY_SYMBIOSIS); Item(ITEM_TOXIC_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("The Normal Gem strengthened Wobbuffet's power!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft); + ABILITY_POPUP(playerRight, ABILITY_SYMBIOSIS); + STATUS_ICON(playerLeft, STATUS1_TOXIC_POISON); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_TOXIC_ORB); + EXPECT_EQ(playerRight->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Symbiosis transfers its item after Gem consumption, but before move execution (Gen6) (Traits)") +{ + GIVEN { + ASSUME(GetItemHoldEffect(ITEM_NORMAL_GEM) == HOLD_EFFECT_GEMS); + WITH_CONFIG(CONFIG_SYMBIOSIS_GEMS, GEN_6); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMAL_GEM); } + PLAYER(SPECIES_ORANGURU) { Ability(ABILITY_TELEPATHY); Innates(ABILITY_SYMBIOSIS); Item(ITEM_TOXIC_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("The Normal Gem strengthened Wobbuffet's power!"); + ABILITY_POPUP(playerRight, ABILITY_SYMBIOSIS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft); + STATUS_ICON(playerLeft, STATUS1_TOXIC_POISON); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_TOXIC_ORB); + EXPECT_EQ(playerRight->item, ITEM_NONE); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +DOUBLE_BATTLE_TEST("Symbiosis transfers its item to an ally after it consumes an item (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_ROOM_SERVICE].holdEffect == HOLD_EFFECT_ROOM_SERVICE); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_ROOM_SERVICE); } + PLAYER(SPECIES_ORANGURU) { Ability(ABILITY_SYMBIOSIS); Items(ITEM_PECHA_BERRY, ITEM_TOXIC_ORB); } + OPPONENT(SPECIES_KIRLIA); + OPPONENT(SPECIES_SHUCKLE); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_TRICK_ROOM); } + } SCENE { + MESSAGE("The opposing Kirlia used Trick Room!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Using Room Service, the Speed of Wobbuffet fell!"); + // symbiosis triggers + ABILITY_POPUP(playerRight, ABILITY_SYMBIOSIS); + MESSAGE("Oranguru passed its Toxic Orb to Wobbuffet through Symbiosis!"); + // end of turn, wobb gets poisoned + MESSAGE("Wobbuffet was badly poisoned!"); + STATUS_ICON(playerLeft, STATUS1_TOXIC_POISON); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_TOXIC_ORB); + EXPECT_EQ(playerRight->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Symbiosis triggers after partners berry eaten from bug bite (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LIECHI_BERRY].holdEffect == HOLD_EFFECT_ATTACK_UP); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LIECHI_BERRY); } + PLAYER(SPECIES_ORANGURU) { Ability(ABILITY_SYMBIOSIS); Items(ITEM_PECHA_BERRY, ITEM_TOXIC_ORB); } + OPPONENT(SPECIES_STARAVIA); + OPPONENT(SPECIES_SHUCKLE); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_BUG_BITE, target: playerLeft); } + } SCENE { + MESSAGE("The opposing Staravia used Bug Bite!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BUG_BITE, opponentLeft); + HP_BAR(playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("Using Liechi Berry, the Attack of the opposing Staravia rose!"); + // symbiosis triggers + ABILITY_POPUP(playerRight, ABILITY_SYMBIOSIS); + MESSAGE("Oranguru passed its Toxic Orb to Wobbuffet through Symbiosis!"); + // end of turn, wobb gets poisoned + MESSAGE("Wobbuffet was badly poisoned!"); + STATUS_ICON(playerLeft, STATUS1_TOXIC_POISON); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_TOXIC_ORB); + EXPECT_EQ(playerRight->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Symbiosis triggers after partner bestows its item (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(100); Items(ITEM_PECHA_BERRY, ITEM_FLAME_ORB); } + PLAYER(SPECIES_ORANGURU) { Speed(75); Ability(ABILITY_SYMBIOSIS); Items(ITEM_PECHA_BERRY, ITEM_TOXIC_ORB); } + OPPONENT(SPECIES_STARAVIA) { Speed(50); } + OPPONENT(SPECIES_SHUCKLE) { Speed(25); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_BESTOW, target: opponentLeft); } + } SCENE { + MESSAGE("Wobbuffet used Bestow!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BESTOW, playerLeft); + MESSAGE("The opposing Staravia received Flame Orb from Wobbuffet!"); + // symbiosis triggers + ABILITY_POPUP(playerRight, ABILITY_SYMBIOSIS); + MESSAGE("Oranguru passed its Toxic Orb to Wobbuffet through Symbiosis!"); + // end of turn, wobb gets poisoned + MESSAGE("Wobbuffet was badly poisoned!"); + STATUS_ICON(playerLeft, STATUS1_TOXIC_POISON); + // staravia gets burned + MESSAGE("The opposing Staravia was burned!"); + STATUS_ICON(opponentLeft, STATUS1_BURN); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_TOXIC_ORB); + EXPECT_EQ(playerRight->item, ITEM_NONE); + EXPECT_EQ(opponentLeft->item, ITEM_FLAME_ORB); + } +} + +DOUBLE_BATTLE_TEST("Symbiosis triggers after partner flings its item (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(100); Items(ITEM_PECHA_BERRY, ITEM_FLAME_ORB); } + PLAYER(SPECIES_ORANGURU) { Speed(75); Ability(ABILITY_SYMBIOSIS); Items(ITEM_PECHA_BERRY, ITEM_TOXIC_ORB); } + OPPONENT(SPECIES_STARAVIA) { Speed(50); } + OPPONENT(SPECIES_SHUCKLE) { Speed(25); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_FLING, target: opponentLeft); } + } SCENE { + MESSAGE("Wobbuffet used Fling!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, playerLeft); + MESSAGE("The opposing Staravia was burned!"); + STATUS_ICON(opponentLeft, STATUS1_BURN); + // symbiosis triggers + ABILITY_POPUP(playerRight, ABILITY_SYMBIOSIS); + MESSAGE("Oranguru passed its Toxic Orb to Wobbuffet through Symbiosis!"); + // end of turn, wobb gets poisoned + MESSAGE("Wobbuffet was badly poisoned!"); + STATUS_ICON(playerLeft, STATUS1_TOXIC_POISON); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_TOXIC_ORB); + EXPECT_EQ(playerRight->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Symbiosis transfers its item to an ally after it consumes a weakness berry (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_CHILAN_BERRY].holdEffect == HOLD_EFFECT_RESIST_BERRY); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_CHILAN_BERRY); } + PLAYER(SPECIES_ORANGURU) { Ability(ABILITY_SYMBIOSIS); Items(ITEM_PECHA_BERRY, ITEM_TOXIC_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft); + ABILITY_POPUP(playerRight, ABILITY_SYMBIOSIS); + STATUS_ICON(playerLeft, STATUS1_TOXIC_POISON); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_TOXIC_ORB); + EXPECT_EQ(playerRight->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Symbiosis transfers its item after Gem consumption and move execution (Gen7+) (Multi)") +{ + GIVEN { + ASSUME(GetItemHoldEffect(ITEM_NORMAL_GEM) == HOLD_EFFECT_GEMS); + WITH_CONFIG(CONFIG_SYMBIOSIS_GEMS, GEN_7); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMAL_GEM); } + PLAYER(SPECIES_ORANGURU) { Ability(ABILITY_SYMBIOSIS); Items(ITEM_PECHA_BERRY, ITEM_TOXIC_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("The Normal Gem strengthened Wobbuffet's power!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft); + ABILITY_POPUP(playerRight, ABILITY_SYMBIOSIS); + STATUS_ICON(playerLeft, STATUS1_TOXIC_POISON); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_TOXIC_ORB); + EXPECT_EQ(playerRight->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Symbiosis transfers its item after Gem consumption, but before move execution (Gen6) (Multi)") +{ + GIVEN { + ASSUME(GetItemHoldEffect(ITEM_NORMAL_GEM) == HOLD_EFFECT_GEMS); + WITH_CONFIG(CONFIG_SYMBIOSIS_GEMS, GEN_6); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMAL_GEM); } + PLAYER(SPECIES_ORANGURU) { Ability(ABILITY_SYMBIOSIS); Items(ITEM_PECHA_BERRY, ITEM_TOXIC_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("The Normal Gem strengthened Wobbuffet's power!"); + ABILITY_POPUP(playerRight, ABILITY_SYMBIOSIS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft); + STATUS_ICON(playerLeft, STATUS1_TOXIC_POISON); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_TOXIC_ORB); + EXPECT_EQ(playerRight->item, ITEM_NONE); + } +} +#endif diff --git a/test/battle/ability/synchronize.c b/test/battle/ability/synchronize.c index 8604814fab0a..912e3e6944b5 100644 --- a/test/battle/ability/synchronize.c +++ b/test/battle/ability/synchronize.c @@ -73,3 +73,73 @@ SINGLE_BATTLE_TEST("Synchronize will mirror back static activation") STATUS_ICON(player, paralysis: TRUE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Synchronize will mirror back non volatile status back at opposing mon (Traits)") +{ + + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ABRA) { Ability(ABILITY_INNER_FOCUS); Innates(ABILITY_SYNCHRONIZE); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, badPoison: TRUE); + ABILITY_POPUP(opponent, ABILITY_SYNCHRONIZE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + STATUS_ICON(player, badPoison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Synchronize will still show up the ability pop up even if it fails (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_PARALYZE_ELECTRIC, GEN_6); + ASSUME(MoveMakesContact(MOVE_TACKLE)); + PLAYER(SPECIES_PIKACHU) { Ability(ABILITY_LIGHTNING_ROD); Innates(ABILITY_STATIC); } + OPPONENT(SPECIES_ABRA) { Ability(ABILITY_INNER_FOCUS); Innates(ABILITY_SYNCHRONIZE); } + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + ABILITY_POPUP(player, ABILITY_STATIC); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, opponent); + STATUS_ICON(opponent, paralysis: TRUE); + ABILITY_POPUP(opponent, ABILITY_SYNCHRONIZE); + NONE_OF { + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, player); + STATUS_ICON(player, paralysis: TRUE); + } + } +} + + +SINGLE_BATTLE_TEST("Synchronize will mirror back static activation (Traits)") +{ + GIVEN { + ASSUME(MoveMakesContact(MOVE_TACKLE)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIKACHU) { Ability(ABILITY_STATIC); } + OPPONENT(SPECIES_ABRA) { Ability(ABILITY_INNER_FOCUS); Innates(ABILITY_SYNCHRONIZE); } + } WHEN { + TURN { MOVE(player, MOVE_SKILL_SWAP); } + TURN { SWITCH(opponent, 1); } + TURN { MOVE(opponent, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, player); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + ABILITY_POPUP(player, ABILITY_STATIC); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, opponent); + STATUS_ICON(opponent, paralysis: TRUE); + ABILITY_POPUP(opponent, ABILITY_SYNCHRONIZE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, player); + STATUS_ICON(player, paralysis: TRUE); + } +} +#endif diff --git a/test/battle/ability/tablets_of_ruin.c b/test/battle/ability/tablets_of_ruin.c index 74ac8c252ecd..c7f507c9bea6 100644 --- a/test/battle/ability/tablets_of_ruin.c +++ b/test/battle/ability/tablets_of_ruin.c @@ -73,3 +73,73 @@ SINGLE_BATTLE_TEST("Tablets of Ruin's message displays correctly after all battl MESSAGE("The opposing Wo-Chien's Tablets of Ruin weakened the Attack of all surrounding Pokémon!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Tablets of Ruin reduces Attack if opposing mon's ability doesn't match (Traits)") +{ + s16 damage[2]; + + GIVEN { + PLAYER(SPECIES_WO_CHIEN) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_TABLETS_OF_RUIN); } + OPPONENT(SPECIES_WO_CHIEN) { Ability(ABILITY_LIGHT_METAL); } + OPPONENT(SPECIES_WO_CHIEN) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_TABLETS_OF_RUIN); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_ENTRAINMENT); } + TURN { SWITCH(opponent, 1); } + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(player, ABILITY_TABLETS_OF_RUIN); + MESSAGE("Wo-Chien's Tablets of Ruin weakened the Attack of all surrounding Pokémon!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[0], Q_4_12(1.33), damage[1]); + } +} + +SINGLE_BATTLE_TEST("Tablets of Ruin's message displays correctly after all battlers fainted - Player (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + PLAYER(SPECIES_WOBBUFFET) { HP(1);} + PLAYER(SPECIES_WO_CHIEN) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_TABLETS_OF_RUIN); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_EXPLOSION); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_RUINATION); } + } SCENE { + HP_BAR(opponent, hp: 0); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, opponent); + // Everyone faints. + MESSAGE("Go! Wo-Chien!"); + MESSAGE("2 sent out Wobbuffet!"); + ABILITY_POPUP(player, ABILITY_TABLETS_OF_RUIN); + MESSAGE("Wo-Chien's Tablets of Ruin weakened the Attack of all surrounding Pokémon!"); + } +} + +SINGLE_BATTLE_TEST("Tablets of Ruin's message displays correctly after all battlers fainted - Opponent (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(1);} + OPPONENT(SPECIES_WO_CHIEN) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_TABLETS_OF_RUIN); } + } WHEN { + TURN { MOVE(player, MOVE_EXPLOSION); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_RUINATION); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + HP_BAR(player, hp: 0); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, player); + // Everyone faints. + SEND_IN_MESSAGE("Wobbuffet"); + MESSAGE("2 sent out Wo-Chien!"); + ABILITY_POPUP(opponent, ABILITY_TABLETS_OF_RUIN); + MESSAGE("The opposing Wo-Chien's Tablets of Ruin weakened the Attack of all surrounding Pokémon!"); + } +} +#endif diff --git a/test/battle/ability/tangling_hair.c b/test/battle/ability/tangling_hair.c index ec0798f1807f..3728a23d5eec 100644 --- a/test/battle/ability/tangling_hair.c +++ b/test/battle/ability/tangling_hair.c @@ -111,3 +111,148 @@ SINGLE_BATTLE_TEST("Tangling Hair will trigger if move is boosted by Sheer Force ABILITY_POPUP(player, ABILITY_TANGLING_HAIR); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Tangling Hair drops opposing mon's speed if ability user got hit by a contact move (Traits)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_SWIFT; } + + GIVEN { + ASSUME(MoveMakesContact(MOVE_SWIFT) == FALSE); + PLAYER(SPECIES_DUGTRIO) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_TANGLING_HAIR); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + if (move == MOVE_SCRATCH) { + ABILITY_POPUP(player, ABILITY_TANGLING_HAIR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wynaut's Speed fell!"); + } + } +} + +SINGLE_BATTLE_TEST("Tangling Hair does not cause Rocky Helmet miss activation (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_ROCKY_HELMET].holdEffect == HOLD_EFFECT_ROCKY_HELMET); + PLAYER(SPECIES_DUGTRIO) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_TANGLING_HAIR); Item(ITEM_ROCKY_HELMET); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(player, ABILITY_TANGLING_HAIR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wynaut's Speed fell!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("The opposing Wynaut was hurt by Dugtrio's Rocky Helmet!"); + } +} + +SINGLE_BATTLE_TEST("Tangling Hair Speed stat drop triggers defiant and keeps original attacker/target (Traits)") +{ + GIVEN { + PLAYER(SPECIES_DUGTRIO) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_TANGLING_HAIR); Item(ITEM_ROCKY_HELMET); } + OPPONENT(SPECIES_PAWNIARD) { Ability(ABILITY_PRESSURE); Innates(ABILITY_DEFIANT); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(player, ABILITY_TANGLING_HAIR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Pawniard's Speed fell!"); + ABILITY_POPUP(opponent, ABILITY_DEFIANT); + MESSAGE("The opposing Pawniard's Attack sharply rose!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("The opposing Pawniard was hurt by Dugtrio's Rocky Helmet!"); + } +} + +SINGLE_BATTLE_TEST("Tangling Hair does not activate on confusion damage (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_CONFUSE_RAY) == EFFECT_CONFUSE); + PLAYER(SPECIES_DUGTRIO) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_TANGLING_HAIR); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_CONFUSE_RAY); MOVE(player, MOVE_CELEBRATE, WITH_RNG(RNG_CONFUSION, TRUE)); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, opponent); + NONE_OF { + ABILITY_POPUP(player, ABILITY_TANGLING_HAIR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } + } +} + +SINGLE_BATTLE_TEST("Tangling Hair does not trigger on Clear Body (Traits)") +{ + GIVEN { + PLAYER(SPECIES_DUGTRIO) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_TANGLING_HAIR); } + OPPONENT(SPECIES_BELDUM) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_CLEAR_BODY); }; + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + NOT ABILITY_POPUP(player, ABILITY_TANGLING_HAIR); + } +} + +SINGLE_BATTLE_TEST("Tangling Hair will trigger if move is boosted by Sheer Force (Traits)") +{ + ASSUME(MoveIsAffectedBySheerForce(MOVE_POISON_JAB)); + GIVEN { + PLAYER(SPECIES_DUGTRIO) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_TANGLING_HAIR); } + OPPONENT(SPECIES_NIDOKING) { Ability(ABILITY_RIVALRY); Innates(ABILITY_SHEER_FORCE); }; + } WHEN { + TURN { MOVE(opponent, MOVE_POISON_JAB); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_JAB, opponent); + ABILITY_POPUP(player, ABILITY_TANGLING_HAIR); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Tangling Hair does not cause Rocky Helmet miss activation (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_ROCKY_HELMET].holdEffect == HOLD_EFFECT_ROCKY_HELMET); + PLAYER(SPECIES_DUGTRIO) { Ability(ABILITY_TANGLING_HAIR); Items(ITEM_PECHA_BERRY, ITEM_ROCKY_HELMET); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(player, ABILITY_TANGLING_HAIR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wynaut's Speed fell!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("The opposing Wynaut was hurt by Dugtrio's Rocky Helmet!"); + } +} + +SINGLE_BATTLE_TEST("Tangling Hair Speed stat drop triggers defiant and keeps original attacker/target (Multi)") +{ + GIVEN { + PLAYER(SPECIES_DUGTRIO) { Ability(ABILITY_TANGLING_HAIR); Items(ITEM_PECHA_BERRY, ITEM_ROCKY_HELMET); } + OPPONENT(SPECIES_PAWNIARD) { Ability(ABILITY_DEFIANT); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(player, ABILITY_TANGLING_HAIR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Pawniard's Speed fell!"); + ABILITY_POPUP(opponent, ABILITY_DEFIANT); + MESSAGE("The opposing Pawniard's Attack sharply rose!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("The opposing Pawniard was hurt by Dugtrio's Rocky Helmet!"); + } +} +#endif diff --git a/test/battle/ability/tera_shell.c b/test/battle/ability/tera_shell.c index 11a77ca7115d..23321cc3dd1a 100644 --- a/test/battle/ability/tera_shell.c +++ b/test/battle/ability/tera_shell.c @@ -112,3 +112,117 @@ SINGLE_BATTLE_TEST("Tera Shell respects immunity") } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Tera Shell makes all moves against Terapagos not very effective when at full HP (Traits)") +{ + u16 hp; + PARAMETRIZE { hp = 100; } + PARAMETRIZE { hp = 99; } + GIVEN { + PLAYER(SPECIES_TERAPAGOS_TERASTAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_TERA_SHELL); HP(hp); MaxHP(100);} + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + if (hp == 100) { + MESSAGE("The opposing Wobbuffet used Scratch!"); + ABILITY_POPUP(player, ABILITY_TERA_SHELL); + MESSAGE("Terapagos made its shell gleam! It's distorting type matchups!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + MESSAGE("It's not very effective…"); + } + else { + NONE_OF { + ABILITY_POPUP(player, ABILITY_TERA_SHELL); + MESSAGE("Terapagos made its shell gleam! It's distorting type matchups!"); + MESSAGE("It's not very effective…"); + } + } + } +} + +SINGLE_BATTLE_TEST("Tera Shell makes all hits of multi-hit moves against Terapagos not very effective (Traits)") +{ + s16 firstHit; + s16 secondHit; + GIVEN { + PLAYER(SPECIES_TERAPAGOS_TERASTAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_TERA_SHELL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DOUBLE_HIT); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Double Hit!"); + ABILITY_POPUP(player, ABILITY_TERA_SHELL); + MESSAGE("Terapagos made its shell gleam! It's distorting type matchups!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_HIT, opponent); + HP_BAR(player, captureDamage: &firstHit); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_HIT, opponent); + HP_BAR(player, captureDamage: &secondHit); + MESSAGE("It's not very effective…"); + } THEN { + EXPECT_EQ(firstHit, secondHit); + } +} + +DOUBLE_BATTLE_TEST("Tera Shell only makes the first hit of a double battle turn not very effective (Traits)") +{ + s16 firstHit; + s16 secondHit; + GIVEN { + PLAYER(SPECIES_TERAPAGOS_TERASTAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_TERA_SHELL); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_TERA_SHELL); + MESSAGE("Terapagos made its shell gleam! It's distorting type matchups!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); + HP_BAR(playerLeft, captureDamage: &firstHit); + MESSAGE("It's not very effective…"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentRight); + HP_BAR(playerLeft, captureDamage: &secondHit); + NOT MESSAGE("It's not very effective…"); + } THEN { + EXPECT_MUL_EQ(firstHit, Q_4_12(2.0), secondHit); + } +} + +DOUBLE_BATTLE_TEST("Tera Shell only makes the first hit against Terapagos from a multi-target move not very effective (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TERAPAGOS_TERASTAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_TERA_SHELL); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_BLIZZARD); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_TERA_SHELL); + MESSAGE("Terapagos made its shell gleam! It's distorting type matchups!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BLIZZARD, opponentLeft); + HP_BAR(playerLeft); + HP_BAR(playerRight); + MESSAGE("It's not very effective…"); + NOT MESSAGE("It's not very effective…"); + } +} + +SINGLE_BATTLE_TEST("Tera Shell respects immunity (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TERAPAGOS_TERASTAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_TERA_SHELL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SHADOW_BALL); } + } SCENE { + NONE_OF { + ABILITY_POPUP(player, ABILITY_TERA_SHELL); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHADOW_BALL, opponent); + } + } +} +#endif diff --git a/test/battle/ability/tera_shift.c b/test/battle/ability/tera_shift.c index 9213bf3b1be0..f08894a2c3b9 100644 --- a/test/battle/ability/tera_shift.c +++ b/test/battle/ability/tera_shift.c @@ -33,3 +33,21 @@ SINGLE_BATTLE_TEST("Tera Shift can't be suppressed by Neutralizing Gas") EXPECT_EQ(player->species, SPECIES_TERAPAGOS_TERASTAL); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Tera Shift transforms Terapagos into its Terastal form on switch in (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TERAPAGOS_NORMAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_TERA_SHIFT); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { ; } + } SCENE { + ABILITY_POPUP(player, ABILITY_TERA_SHIFT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Terapagos transformed!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_TERAPAGOS_TERASTAL); + } +} +#endif diff --git a/test/battle/ability/teraform_zero.c b/test/battle/ability/teraform_zero.c index 17f086bf084a..41f2f6ec8cdd 100644 --- a/test/battle/ability/teraform_zero.c +++ b/test/battle/ability/teraform_zero.c @@ -100,3 +100,21 @@ DOUBLE_BATTLE_TEST("Teraform Zero shouldn't cause Neutralizing Gas to show it's MESSAGE("Terapagos used Celebrate!"); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Teraform Zero clears weather and terrain upon activation (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TERAPAGOS_TERASTAL) {Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_TERAFORM_ZERO); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KYOGRE) {Ability(ABILITY_DRIZZLE); } + OPPONENT(SPECIES_TAPU_KOKO) {Ability(ABILITY_ELECTRIC_SURGE); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_TERAFORM_ZERO); + MESSAGE("The rain stopped."); + MESSAGE("The electricity disappeared from the battlefield."); + } +} +#endif diff --git a/test/battle/ability/thermal_exchange.c b/test/battle/ability/thermal_exchange.c index 1eb621594f4d..5faff52f58db 100644 --- a/test/battle/ability/thermal_exchange.c +++ b/test/battle/ability/thermal_exchange.c @@ -89,3 +89,74 @@ SINGLE_BATTLE_TEST("Thermal Exchange boosts attack if hit by a damaging fire typ EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Thermal Exchange makes Will-O-Wisp fail (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_WILL_O_WISP) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_WILL_O_WISP) == MOVE_EFFECT_BURN); + PLAYER(SPECIES_BAXCALIBUR) { Ability(ABILITY_ICE_BODY); Innates(ABILITY_THERMAL_EXCHANGE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_WILL_O_WISP); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WILL_O_WISP, opponent); + STATUS_ICON(player, burn: TRUE); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } +} + +SINGLE_BATTLE_TEST("Thermal Exchange prevents the user from getting burned when hitting Flame Body (Traits)") +{ + GIVEN { + PLAYER(SPECIES_BAXCALIBUR) { Ability(ABILITY_ICE_BODY); Innates(ABILITY_THERMAL_EXCHANGE); } + OPPONENT(SPECIES_PONYTA) { Ability(ABILITY_RUN_AWAY); Innates(ABILITY_FLAME_BODY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_FLAME_BODY); + STATUS_ICON(player, burn: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Thermal Exchange burn prevention can be bypassed with Mold Breaker but is cured after (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_WILL_O_WISP) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_WILL_O_WISP) == MOVE_EFFECT_BURN); + PLAYER(SPECIES_BAXCALIBUR) { Ability(ABILITY_ICE_BODY); Innates(ABILITY_THERMAL_EXCHANGE); } + OPPONENT(SPECIES_RAMPARDOS) { Ability(ABILITY_ICE_BODY); Innates(ABILITY_MOLD_BREAKER); } + } WHEN { + TURN { MOVE(opponent, MOVE_WILL_O_WISP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WILL_O_WISP, opponent); + STATUS_ICON(player, burn: TRUE); + ABILITY_POPUP(player, ABILITY_THERMAL_EXCHANGE); + STATUS_ICON(player, burn: FALSE); + NOT HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Thermal Exchange boosts attack if hit by a damaging fire type move (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_EMBER) == TYPE_FIRE); + PLAYER(SPECIES_BAXCALIBUR) { Ability(ABILITY_ICE_BODY); Innates(ABILITY_THERMAL_EXCHANGE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_EMBER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} +#endif diff --git a/test/battle/ability/torrent.c b/test/battle/ability/torrent.c index f0da964b93ee..cfcad21b68d3 100644 --- a/test/battle/ability/torrent.c +++ b/test/battle/ability/torrent.c @@ -18,3 +18,23 @@ SINGLE_BATTLE_TEST("Torrent boosts Water-type moves in a pinch", s16 damage) EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Torrent boosts Water-type moves in a pinch (Traits)", s16 damage) +{ + u16 hp; + PARAMETRIZE { hp = 99; } + PARAMETRIZE { hp = 33; } + GIVEN { + ASSUME(GetMoveType(MOVE_BUBBLE) == TYPE_WATER); + PLAYER(SPECIES_SQUIRTLE) { Ability(ABILITY_RAIN_DISH); Innates(ABILITY_TORRENT); MaxHP(99); HP(hp); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BUBBLE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} +#endif diff --git a/test/battle/ability/toxic_boost.c b/test/battle/ability/toxic_boost.c index 35a233ec2339..4f904772673b 100644 --- a/test/battle/ability/toxic_boost.c +++ b/test/battle/ability/toxic_boost.c @@ -21,3 +21,26 @@ SINGLE_BATTLE_TEST("Toxic Boost increases Attack by 50% when the Pokémon is poi EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[2].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Toxic Boost increases Attack by 50% when the Pokémon is poisoned (Traits)", s16 damage) +{ + u32 status1; + PARAMETRIZE { status1 = STATUS1_NONE; } + PARAMETRIZE { status1 = STATUS1_POISON; } + PARAMETRIZE { status1 = STATUS1_TOXIC_POISON; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_ZANGOOSE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_TOXIC_BOOST); Status1(status1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[2].damage); + } +} +#endif diff --git a/test/battle/ability/toxic_chain.c b/test/battle/ability/toxic_chain.c index b93af761cf24..94bb3170a547 100644 --- a/test/battle/ability/toxic_chain.c +++ b/test/battle/ability/toxic_chain.c @@ -110,3 +110,178 @@ SINGLE_BATTLE_TEST("Toxic Chain makes Lum/Pecha Berry trigger before being knock EXPECT(opponent->status1 == 0); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Toxic Chain inflicts bad poison when attacking (Traits)") +{ + PASSES_RANDOMLY(3, 10, RNG_TOXIC_CHAIN); + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) != DAMAGE_CATEGORY_STATUS); + ASSUME(GetMovePower(MOVE_SCRATCH) > 0); + PLAYER(SPECIES_OKIDOGI) { Ability(ABILITY_GUARD_DOG); Innates(ABILITY_TOXIC_CHAIN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(player, ABILITY_TOXIC_CHAIN); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("The opposing Wobbuffet was badly poisoned!"); + STATUS_ICON(opponent, badPoison: TRUE); + } THEN { + EXPECT(opponent->status1 & STATUS1_TOXIC_POISON); + } +} + +SINGLE_BATTLE_TEST("Toxic Chain inflicts bad poison on any hit of a multi-hit move (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_DOUBLE_SLAP) != DAMAGE_CATEGORY_STATUS); + ASSUME(GetMoveEffect(MOVE_DOUBLE_SLAP) == EFFECT_MULTI_HIT); + ASSUME(GetMovePower(MOVE_DOUBLE_SLAP) > 0); + ASSUME(gItemsInfo[ITEM_PECHA_BERRY].holdEffect == HOLD_EFFECT_CURE_PSN); + PLAYER(SPECIES_OKIDOGI) { Ability(ABILITY_GUARD_DOG); Innates(ABILITY_TOXIC_CHAIN); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_PECHA_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_DOUBLE_SLAP); } + } SCENE { + ABILITY_POPUP(player, ABILITY_TOXIC_CHAIN); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("The opposing Wobbuffet was badly poisoned!"); + STATUS_ICON(opponent, badPoison: TRUE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + STATUS_ICON(opponent, badPoison: FALSE); + ABILITY_POPUP(player, ABILITY_TOXIC_CHAIN); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("The opposing Wobbuffet was badly poisoned!"); + STATUS_ICON(opponent, badPoison: TRUE); + } THEN { + EXPECT(opponent->status1 & STATUS1_TOXIC_POISON); + } +} + +DOUBLE_BATTLE_TEST("Toxic Chain can inflict bad poison on both foes (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_RAZOR_LEAF) != DAMAGE_CATEGORY_STATUS); + ASSUME(GetMoveTarget(MOVE_RAZOR_LEAF) == MOVE_TARGET_BOTH); + ASSUME(GetMovePower(MOVE_RAZOR_LEAF) > 0); + PLAYER(SPECIES_OKIDOGI) { Ability(ABILITY_GUARD_DOG); Innates(ABILITY_TOXIC_CHAIN); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_RAZOR_LEAF, WITH_RNG(RNG_TOXIC_CHAIN, TRUE)); } + } SCENE { + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + ABILITY_POPUP(playerLeft, ABILITY_TOXIC_CHAIN); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponentLeft); + MESSAGE("The opposing Wobbuffet was badly poisoned!"); + STATUS_ICON(opponentLeft, badPoison: TRUE); + ABILITY_POPUP(playerLeft, ABILITY_TOXIC_CHAIN); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponentRight); + MESSAGE("The opposing Wynaut was badly poisoned!"); + STATUS_ICON(opponentRight, badPoison: TRUE); + } THEN { + EXPECT(opponentLeft->status1 & STATUS1_TOXIC_POISON); + EXPECT(opponentRight->status1 & STATUS1_TOXIC_POISON); + } +} + +SINGLE_BATTLE_TEST("Toxic Chain makes Lum/Pecha Berry trigger before being knocked off (Traits)") +{ + u16 item = 0; + + PARAMETRIZE { item = ITEM_PECHA_BERRY; } + PARAMETRIZE { item = ITEM_LUM_BERRY; } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_KNOCK_OFF) != DAMAGE_CATEGORY_STATUS); + ASSUME(GetMoveEffect(MOVE_KNOCK_OFF) == EFFECT_KNOCK_OFF); + ASSUME(GetMovePower(MOVE_KNOCK_OFF) > 0); + ASSUME(gItemsInfo[ITEM_PECHA_BERRY].holdEffect == HOLD_EFFECT_CURE_PSN); + ASSUME(gItemsInfo[ITEM_LUM_BERRY].holdEffect == HOLD_EFFECT_CURE_STATUS); + PLAYER(SPECIES_OKIDOGI) { Ability(ABILITY_GUARD_DOG); Innates(ABILITY_TOXIC_CHAIN); } + OPPONENT(SPECIES_WOBBUFFET) { Item(item); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF, WITH_RNG(RNG_TOXIC_CHAIN, TRUE)); } + } SCENE { + ABILITY_POPUP(player, ABILITY_TOXIC_CHAIN); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("The opposing Wobbuffet was badly poisoned!"); + STATUS_ICON(opponent, badPoison: TRUE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + STATUS_ICON(opponent, badPoison: FALSE); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); + MESSAGE("Okidogi knocked off the opposing Wobbuffet's Pecha Berry!"); + MESSAGE("Okidogi knocked off the opposing Wobbuffet's Lum Berry!"); + } + } THEN { + EXPECT(opponent->status1 == 0); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Toxic Chain inflicts bad poison on any hit of a multi-hit move (Multi)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_DOUBLE_SLAP) != DAMAGE_CATEGORY_STATUS); + ASSUME(GetMoveEffect(MOVE_DOUBLE_SLAP) == EFFECT_MULTI_HIT); + ASSUME(GetMovePower(MOVE_DOUBLE_SLAP) > 0); + ASSUME(gItemsInfo[ITEM_PECHA_BERRY].holdEffect == HOLD_EFFECT_CURE_PSN); + PLAYER(SPECIES_OKIDOGI) { Ability(ABILITY_TOXIC_CHAIN); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, ITEM_PECHA_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_DOUBLE_SLAP); } + } SCENE { + ABILITY_POPUP(player, ABILITY_TOXIC_CHAIN); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("The opposing Wobbuffet was badly poisoned!"); + STATUS_ICON(opponent, badPoison: TRUE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + STATUS_ICON(opponent, badPoison: FALSE); + ABILITY_POPUP(player, ABILITY_TOXIC_CHAIN); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("The opposing Wobbuffet was badly poisoned!"); + STATUS_ICON(opponent, badPoison: TRUE); + } THEN { + EXPECT(opponent->status1 & STATUS1_TOXIC_POISON); + } +} + +SINGLE_BATTLE_TEST("Toxic Chain makes Lum/Pecha Berry trigger before being knocked off (Multi)") +{ + u16 item = 0; + + PARAMETRIZE { item = ITEM_PECHA_BERRY; } + PARAMETRIZE { item = ITEM_LUM_BERRY; } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_KNOCK_OFF) != DAMAGE_CATEGORY_STATUS); + ASSUME(GetMoveEffect(MOVE_KNOCK_OFF) == EFFECT_KNOCK_OFF); + ASSUME(GetMovePower(MOVE_KNOCK_OFF) > 0); + ASSUME(gItemsInfo[ITEM_PECHA_BERRY].holdEffect == HOLD_EFFECT_CURE_PSN); + ASSUME(gItemsInfo[ITEM_LUM_BERRY].holdEffect == HOLD_EFFECT_CURE_STATUS); + PLAYER(SPECIES_OKIDOGI) { Ability(ABILITY_TOXIC_CHAIN); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, item); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF, WITH_RNG(RNG_TOXIC_CHAIN, TRUE)); } + } SCENE { + ABILITY_POPUP(player, ABILITY_TOXIC_CHAIN); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("The opposing Wobbuffet was badly poisoned!"); + STATUS_ICON(opponent, badPoison: TRUE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + STATUS_ICON(opponent, badPoison: FALSE); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); + MESSAGE("Okidogi knocked off the opposing Wobbuffet's Pecha Berry!"); + MESSAGE("Okidogi knocked off the opposing Wobbuffet's Lum Berry!"); + } + } THEN { + EXPECT(opponent->status1 == 0); + } +} +#endif diff --git a/test/battle/ability/toxic_debris.c b/test/battle/ability/toxic_debris.c index 587cb846ed36..70946a274ede 100644 --- a/test/battle/ability/toxic_debris.c +++ b/test/battle/ability/toxic_debris.c @@ -140,3 +140,156 @@ DOUBLE_BATTLE_TEST("Toxic Debris sets Toxic Spikes on the opposing side even whe MESSAGE("Poison spikes were scattered on the ground all around the opposing team!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Toxic Debris sets Toxic Spikes on the opposing side if hit by a physical attack (Traits)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SCRATCH;} + PARAMETRIZE { move = MOVE_SWIFT;} + + GIVEN { + PLAYER(SPECIES_GLIMMORA) { Ability(ABILITY_CORROSION); Innates(ABILITY_TOXIC_DEBRIS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + if (move == MOVE_SCRATCH) { + ABILITY_POPUP(player, ABILITY_TOXIC_DEBRIS); + MESSAGE("Poison spikes were scattered on the ground all around the opposing team!"); + } else { + NONE_OF { + ABILITY_POPUP(player, ABILITY_TOXIC_DEBRIS); + MESSAGE("Poison spikes were scattered on the ground all around the opposing team!"); + } + } + } +} + +SINGLE_BATTLE_TEST("Toxic Debris does not activate if two layers of Toxic Spikes are already up (Traits)") +{ + GIVEN { + PLAYER(SPECIES_GLIMMORA) { Ability(ABILITY_CORROSION); Innates(ABILITY_TOXIC_DEBRIS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(player, ABILITY_TOXIC_DEBRIS); + MESSAGE("Poison spikes were scattered on the ground all around the opposing team!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(player, ABILITY_TOXIC_DEBRIS); + MESSAGE("Poison spikes were scattered on the ground all around the opposing team!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + NONE_OF { + ABILITY_POPUP(player, ABILITY_TOXIC_DEBRIS); + MESSAGE("Poison spikes were scattered on the ground all around the opposing team!"); + } + } +} + +SINGLE_BATTLE_TEST("If a Substitute is hit, Toxic Debris does not set Toxic Spikes (Traits)") +{ + GIVEN { + PLAYER(SPECIES_GLIMMORA) { Ability(ABILITY_CORROSION); Innates(ABILITY_TOXIC_DEBRIS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); } + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + NONE_OF { + ABILITY_POPUP(player, ABILITY_TOXIC_DEBRIS); + MESSAGE("Poison spikes were scattered on the ground all around the opposing team!"); + } + } +} + +SINGLE_BATTLE_TEST("Each hit of a Multi Hit move activates Toxic Debris (Traits)") +{ + GIVEN { + PLAYER(SPECIES_GLIMMORA) { Ability(ABILITY_CORROSION); Innates(ABILITY_TOXIC_DEBRIS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_FURY_SWIPES); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, opponent); + ABILITY_POPUP(player, ABILITY_TOXIC_DEBRIS); + MESSAGE("Poison spikes were scattered on the ground all around the opposing team!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, opponent); + ABILITY_POPUP(player, ABILITY_TOXIC_DEBRIS); + MESSAGE("Poison spikes were scattered on the ground all around the opposing team!"); + } +} + +SINGLE_BATTLE_TEST("Toxic Debris activates if user faints after physical hit (Traits)") +{ + GIVEN { + PLAYER(SPECIES_GLIMMORA) { HP(1); Ability(ABILITY_CORROSION); Innates(ABILITY_TOXIC_DEBRIS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + MESSAGE("Glimmora fainted!"); + ABILITY_POPUP(player, ABILITY_TOXIC_DEBRIS); + MESSAGE("Poison spikes were scattered on the ground all around the opposing team!"); + } +} + +SINGLE_BATTLE_TEST("Air Balloon is popped after Toxic Debris activates") +{ + GIVEN { + PLAYER(SPECIES_GLIMMORA) { Ability(ABILITY_CORROSION); Innates(ABILITY_TOXIC_DEBRIS); Item(ITEM_AIR_BALLOON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(player, ABILITY_TOXIC_DEBRIS); + MESSAGE("Poison spikes were scattered on the ground all around the opposing team!"); + MESSAGE("Glimmora's Air Balloon popped!"); + } +} + +DOUBLE_BATTLE_TEST("Toxic Debris sets Toxic Spikes on the opposing side even when hit by an ally (Traits)") +{ + struct BattlePokemon *user = NULL; + + PARAMETRIZE{ user = opponentLeft; } + PARAMETRIZE{ user = opponentRight; } + PARAMETRIZE{ user = playerRight; } + GIVEN { + PLAYER(SPECIES_GLIMMORA) { Ability(ABILITY_CORROSION); Innates(ABILITY_TOXIC_DEBRIS); } + PLAYER(SPECIES_WYNAUT) { } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { } + } WHEN { + TURN { MOVE(user, MOVE_SCRATCH, target: playerLeft); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_TOXIC_DEBRIS); + MESSAGE("Poison spikes were scattered on the ground all around the opposing team!"); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Air Balloon is popped after Toxic Debris activates (Multi)") +{ + GIVEN { + PLAYER(SPECIES_GLIMMORA) { Ability(ABILITY_TOXIC_DEBRIS); Items(ITEM_PECHA_BERRY, ITEM_AIR_BALLOON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(player, ABILITY_TOXIC_DEBRIS); + MESSAGE("Poison spikes were scattered on the ground all around the opposing team!"); + MESSAGE("Glimmora's Air Balloon popped!"); + } +} +#endif diff --git a/test/battle/ability/transistor.c b/test/battle/ability/transistor.c index e57043a5c2e1..f446054822c3 100644 --- a/test/battle/ability/transistor.c +++ b/test/battle/ability/transistor.c @@ -54,3 +54,37 @@ SINGLE_BATTLE_TEST("Transistor is blocked by neutralizing gas", s16 damage) EXPECT_LT(results[0].damage, results[1].damage); // cannot test exact factor because ATK / SPATK introduces inaccuracies } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Transistor increases Electric-type attack / special attack (Traits)", s16 damage) +{ + u32 move; + enum Ability ability; + + PARAMETRIZE { move = MOVE_SCRATCH; ability = ABILITY_KLUTZ; } + PARAMETRIZE { move = MOVE_SCRATCH; ability = ABILITY_TRANSISTOR; } + PARAMETRIZE { move = MOVE_WILD_CHARGE; ability = ABILITY_KLUTZ; } + PARAMETRIZE { move = MOVE_WILD_CHARGE; ability = ABILITY_TRANSISTOR; } + PARAMETRIZE { move = MOVE_THUNDER_SHOCK; ability = ABILITY_KLUTZ; } + PARAMETRIZE { move = MOVE_THUNDER_SHOCK; ability = ABILITY_TRANSISTOR; } + + GIVEN { + ASSUME(GetMoveType(MOVE_SCRATCH) != TYPE_ELECTRIC); + ASSUME(GetMoveType(MOVE_WILD_CHARGE) == TYPE_ELECTRIC); + ASSUME(GetMoveType(MOVE_THUNDER_SHOCK) == TYPE_ELECTRIC); + ASSUME(GetMoveCategory(MOVE_WILD_CHARGE) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_THUNDER_SHOCK) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_REGIELEKI) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); // Scratch should be unaffected + + EXPECT_LT(results[2].damage, results[3].damage); // cannot test exact factor because ATK / SPATK introduces inaccuracies + EXPECT_LT(results[4].damage, results[5].damage); + } +} +#endif diff --git a/test/battle/ability/unburden.c b/test/battle/ability/unburden.c index 5f13692935d1..17b5a4f42551 100644 --- a/test/battle/ability/unburden.c +++ b/test/battle/ability/unburden.c @@ -99,3 +99,147 @@ SINGLE_BATTLE_TEST("Unburden doubling speed effect is ignored by Neutralizing Ga ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Unburden doubles speed once user uses item (Traits)") +{ + GIVEN { + ASSUME(GetItemHoldEffect(ITEM_GRASSY_SEED) == HOLD_EFFECT_TERRAIN_SEED); + ASSUME(GetMoveEffect(MOVE_U_TURN) == EFFECT_HIT_ESCAPE); + PLAYER(SPECIES_DRIFBLIM) { Ability(ABILITY_FLARE_BOOST); Innates(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 (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_KNOCK_OFF) == EFFECT_KNOCK_OFF); + PLAYER(SPECIES_DRIFBLIM) { Ability(ABILITY_FLARE_BOOST); Innates(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); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Unburden doubles speed once user uses item (Multi)") +{ + GIVEN { + ASSUME(GetItemHoldEffect(ITEM_GRASSY_SEED) == HOLD_EFFECT_TERRAIN_SEED); + ASSUME(GetMoveEffect(MOVE_U_TURN) == EFFECT_HIT_ESCAPE); + PLAYER(SPECIES_DRIFBLIM) { Ability(ABILITY_UNBURDEN); Items(ITEM_NONE, 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 (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_KNOCK_OFF) == EFFECT_KNOCK_OFF); + PLAYER(SPECIES_DRIFBLIM) { Ability(ABILITY_UNBURDEN); Items(ITEM_NONE, 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 (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_U_TURN) == EFFECT_HIT_ESCAPE); + ASSUME(GetMoveEffect(MOVE_KNOCK_OFF) == EFFECT_KNOCK_OFF); + PLAYER(SPECIES_DRIFBLIM) { Ability(ABILITY_UNBURDEN); Items(ITEM_NONE, 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 (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_U_TURN) == EFFECT_HIT_ESCAPE); + ASSUME(GetMoveEffect(MOVE_KNOCK_OFF) == EFFECT_KNOCK_OFF); + PLAYER(SPECIES_DRIFBLIM) { Ability(ABILITY_UNBURDEN); Items(ITEM_NONE, 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); + } +} +#endif diff --git a/test/battle/ability/unnerve.c b/test/battle/ability/unnerve.c index 048b9519648d..8def207c8fee 100644 --- a/test/battle/ability/unnerve.c +++ b/test/battle/ability/unnerve.c @@ -124,3 +124,165 @@ DOUBLE_BATTLE_TEST("Unnerve stops applying on death but applies on revive") } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Unnerve prevents opposing Pokémon from eating their own berries (Traits)") +{ + u16 mon; + enum Ability ability; + PARAMETRIZE { mon = SPECIES_JOLTIK, ability = ABILITY_UNNERVE; } + PARAMETRIZE { mon = SPECIES_CALYREX_ICE, ability = ABILITY_AS_ONE_ICE_RIDER; } + GIVEN { + ASSUME(gItemsInfo[ITEM_RAWST_BERRY].holdEffect == HOLD_EFFECT_CURE_BRN); + PLAYER(mon) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_RAWST_BERRY); Status1(STATUS1_BURN); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ability); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Unnerve doesn't prevent opposing Pokémon from using Natural Gift (Traits)") +{ + u16 mon; + enum Ability ability; + PARAMETRIZE { mon = SPECIES_JOLTIK, ability = ABILITY_UNNERVE; } + PARAMETRIZE { mon = SPECIES_CALYREX_ICE, ability = ABILITY_AS_ONE_ICE_RIDER; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + PLAYER(mon) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_ORAN_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_NATURAL_GIFT); } + } SCENE { + ABILITY_POPUP(player, ability); + HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Unnerve prints the correct string (player) (Traits)") +{ + u16 mon; + enum Ability ability; + PARAMETRIZE { mon = SPECIES_JOLTIK, ability = ABILITY_UNNERVE; } + PARAMETRIZE { mon = SPECIES_CALYREX_ICE, ability = ABILITY_AS_ONE_ICE_RIDER; } + GIVEN { + PLAYER(mon) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN {} + } SCENE { + ABILITY_POPUP(player, ability); + MESSAGE("The opposing team is too nervous to eat Berries!"); + } +} + +SINGLE_BATTLE_TEST("Unnerve prints the correct string (opponent) (Traits)") +{ + u16 mon; + enum Ability ability; + PARAMETRIZE { mon = SPECIES_JOLTIK, ability = ABILITY_UNNERVE; } + PARAMETRIZE { mon = SPECIES_CALYREX_ICE, ability = ABILITY_AS_ONE_ICE_RIDER; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(mon) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ability); + MESSAGE("Your team is too nervous to eat Berries!"); + } +} + +DOUBLE_BATTLE_TEST("Unnerve stops applying on death but applies on revive (Traits)") +{ + u16 mon; + u16 ability; + PARAMETRIZE { mon = SPECIES_JOLTIK, ability = ABILITY_UNNERVE;} + PARAMETRIZE { mon = SPECIES_CALYREX_ICE, ability = ABILITY_AS_ONE_ICE_RIDER; } + GIVEN { + ASSUME(gItemsInfo[ITEM_RAWST_BERRY].holdEffect == HOLD_EFFECT_CURE_BRN); + ASSUME(gItemsInfo[ITEM_REVIVE].battleUsage == EFFECT_ITEM_REVIVE); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(mon) { Ability(ABILITY_LIGHT_METAL); Innates(ability); HP(1); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_RAWST_BERRY); Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WYNAUT) { Item(ITEM_RAWST_BERRY); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerRight); } + TURN { USE_ITEM(playerLeft, ITEM_REVIVE, partyIndex: 1); SKIP_TURN(playerRight); MOVE(opponentLeft, MOVE_WILL_O_WISP, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, opponentRight); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + } + +} +#endif + +#if MAX_MON_ITEMS > 1 +// Remember to add a PARAMETRIZE for As One in the following tests: +SINGLE_BATTLE_TEST("Unnerve prevents opposing Pokémon from eating their own berries (Multi)") +{ + u16 mon; + enum Ability ability; + PARAMETRIZE { mon = SPECIES_JOLTIK, ability = ABILITY_UNNERVE; } + PARAMETRIZE { mon = SPECIES_CALYREX_ICE, ability = ABILITY_AS_ONE_ICE_RIDER; } + GIVEN { + ASSUME(gItemsInfo[ITEM_RAWST_BERRY].holdEffect == HOLD_EFFECT_CURE_BRN); + PLAYER(mon) { Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RAWST_BERRY); Status1(STATUS1_BURN); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ability); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Unnerve doesn't prevent opposing Pokémon from using Natural Gift (Multi)") +{ + u16 mon; + enum Ability ability; + PARAMETRIZE { mon = SPECIES_JOLTIK, ability = ABILITY_UNNERVE; } + PARAMETRIZE { mon = SPECIES_CALYREX_ICE, ability = ABILITY_AS_ONE_ICE_RIDER; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + PLAYER(mon) { Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_ORAN_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_NATURAL_GIFT); } + } SCENE { + ABILITY_POPUP(player, ability); + HP_BAR(player); + } +} + +DOUBLE_BATTLE_TEST("Unnerve stops applying on death but applies on revive (Multi)") +{ + u16 mon; + u16 ability; + PARAMETRIZE { mon = SPECIES_JOLTIK, ability = ABILITY_UNNERVE;} + PARAMETRIZE { mon = SPECIES_CALYREX_ICE, ability = ABILITY_AS_ONE_ICE_RIDER; } + GIVEN { + ASSUME(gItemsInfo[ITEM_RAWST_BERRY].holdEffect == HOLD_EFFECT_CURE_BRN); + ASSUME(gItemsInfo[ITEM_REVIVE].battleUsage == EFFECT_ITEM_REVIVE); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(mon) { Ability(ability); HP(1); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RAWST_BERRY); Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WYNAUT) { Items(ITEM_PECHA_BERRY, ITEM_RAWST_BERRY); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerRight); } + TURN { USE_ITEM(playerLeft, ITEM_REVIVE, partyIndex: 1); SKIP_TURN(playerRight); MOVE(opponentLeft, MOVE_WILL_O_WISP, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, opponentRight); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + } +} +#endif diff --git a/test/battle/ability/vessel_of_ruin.c b/test/battle/ability/vessel_of_ruin.c index 18ac502c08ab..608fcbc1e3ce 100644 --- a/test/battle/ability/vessel_of_ruin.c +++ b/test/battle/ability/vessel_of_ruin.c @@ -146,3 +146,97 @@ DOUBLE_BATTLE_TEST("Vessel of Ruin is active if removed by Mold Breaker Entrainm EXPECT_EQ(isSwordOfRuinActive, TRUE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Vessel of Ruin reduces Sp. Atk if opposing mon's ability doesn't match (Traits)") +{ + s16 damage[2]; + + GIVEN { + PLAYER(SPECIES_TING_LU) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_VESSEL_OF_RUIN); } + OPPONENT(SPECIES_TING_LU) { Ability(ABILITY_LIGHT_METAL); } + OPPONENT(SPECIES_TING_LU) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_VESSEL_OF_RUIN); } + } WHEN { + TURN { MOVE(opponent, MOVE_WATER_GUN); MOVE(player, MOVE_ENTRAINMENT); } + TURN { SWITCH(opponent, 1); } + TURN { MOVE(opponent, MOVE_WATER_GUN); } + } SCENE { + ABILITY_POPUP(player, ABILITY_VESSEL_OF_RUIN); + MESSAGE("Ting-Lu's Vessel of Ruin weakened the Sp. Atk of all surrounding Pokémon!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + HP_BAR(player, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + HP_BAR(player, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[0], Q_4_12(1.33), damage[1]); + } +} + +SINGLE_BATTLE_TEST("Vessel of Ruin's message displays correctly after all battlers fainted - Player (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + PLAYER(SPECIES_WOBBUFFET) { HP(1);} + PLAYER(SPECIES_TING_LU) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_VESSEL_OF_RUIN); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_EXPLOSION); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_RUINATION); } + } SCENE { + HP_BAR(opponent, hp: 0); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, opponent); + // Everyone faints. + MESSAGE("Go! Ting-Lu!"); + MESSAGE("2 sent out Wobbuffet!"); + ABILITY_POPUP(player, ABILITY_VESSEL_OF_RUIN); + MESSAGE("Ting-Lu's Vessel of Ruin weakened the Sp. Atk of all surrounding Pokémon!"); + } +} + +SINGLE_BATTLE_TEST("Vessel of Ruin's message displays correctly after all battlers fainted - Opponent (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(1);} + OPPONENT(SPECIES_TING_LU) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_VESSEL_OF_RUIN); } + } WHEN { + TURN { MOVE(player, MOVE_EXPLOSION); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_RUINATION); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + HP_BAR(player, hp: 0); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, player); + // Everyone faints. + SEND_IN_MESSAGE("Wobbuffet"); + MESSAGE("2 sent out Ting-Lu!"); + ABILITY_POPUP(opponent, ABILITY_VESSEL_OF_RUIN); + MESSAGE("The opposing Ting-Lu's Vessel of Ruin weakened the Sp. Atk of all surrounding Pokémon!"); + } +} + +SINGLE_BATTLE_TEST("Vessel of Ruin is still active if removed by Mold Breaker + Entrainment (Traits)") +{ + s16 damage[2]; + + GIVEN { + PLAYER(SPECIES_TING_LU) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_VESSEL_OF_RUIN); } + OPPONENT(SPECIES_PINSIR) { Ability(ABILITY_MOLD_BREAKER); } + } WHEN { + TURN { MOVE(opponent, MOVE_WATER_GUN); } + TURN { MOVE(opponent, MOVE_ENTRAINMENT); } + TURN { MOVE(opponent, MOVE_WATER_GUN); } + } SCENE { + ABILITY_POPUP(player, ABILITY_VESSEL_OF_RUIN); + MESSAGE("Ting-Lu's Vessel of Ruin weakened the Sp. Atk of all surrounding Pokémon!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + HP_BAR(player, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ENTRAINMENT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + HP_BAR(player, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} +#endif diff --git a/test/battle/ability/volt_absorb.c b/test/battle/ability/volt_absorb.c index becc76fd1344..710b7a7bee18 100644 --- a/test/battle/ability/volt_absorb.c +++ b/test/battle/ability/volt_absorb.c @@ -105,3 +105,133 @@ SINGLE_BATTLE_TEST("Volt Absorb prevents Cell Battery from activating") } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Volt Absorb heals 25% when hit by electric type moves (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_THUNDER_SHOCK) == TYPE_ELECTRIC); + PLAYER(SPECIES_JOLTEON) { Ability(ABILITY_QUICK_FEET); Innates(ABILITY_VOLT_ABSORB); HP(1); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_THUNDER_SHOCK); } + } SCENE { + ABILITY_POPUP(player, ABILITY_VOLT_ABSORB); + HP_BAR(player, damage: -25); + MESSAGE("Jolteon restored HP using its Volt Absorb!"); + } +} + +SINGLE_BATTLE_TEST("Volt Absorb does not activate if protected") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_THUNDER_SHOCK) == TYPE_ELECTRIC); + PLAYER(SPECIES_JOLTEON) { Ability(ABILITY_QUICK_FEET); Innates(ABILITY_VOLT_ABSORB); HP(1); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PROTECT); MOVE(opponent, MOVE_THUNDER_SHOCK); } + } SCENE { + NONE_OF { ABILITY_POPUP(player, ABILITY_VOLT_ABSORB); HP_BAR(player); MESSAGE("Jolteon restored HP using its Volt Absorb!"); } + } +} + +SINGLE_BATTLE_TEST("Volt Absorb activates on status moves (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_THUNDER_WAVE) == TYPE_ELECTRIC); + ASSUME(GetMoveCategory(MOVE_THUNDER_WAVE) == DAMAGE_CATEGORY_STATUS); + PLAYER(SPECIES_JOLTEON) { Ability(ABILITY_QUICK_FEET); Innates(ABILITY_VOLT_ABSORB); HP(1); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_THUNDER_WAVE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_VOLT_ABSORB); + HP_BAR(player, damage: -25); + MESSAGE("Jolteon restored HP using its Volt Absorb!"); + } +} + +SINGLE_BATTLE_TEST("Volt Absorb is only triggered once on multi strike moves (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_FURY_SWIPES) == TYPE_NORMAL); + ASSUME(GetMoveEffect(MOVE_FURY_SWIPES) == EFFECT_MULTI_HIT); + PLAYER(SPECIES_JOLTEON) { Ability(ABILITY_QUICK_FEET); Innates(ABILITY_VOLT_ABSORB); HP(1); MaxHP(100); } + OPPONENT(SPECIES_GRAVELER_ALOLA) { Ability(ABILITY_STURDY); Innates(ABILITY_GALVANIZE); } + } WHEN { + TURN { MOVE(opponent, MOVE_FURY_SWIPES); } + } SCENE { + ABILITY_POPUP(player, ABILITY_VOLT_ABSORB); + HP_BAR(player, damage: -25); + MESSAGE("Jolteon restored HP using its Volt Absorb!"); + } +} + +DOUBLE_BATTLE_TEST("Volt Absorb does not stop Electric Typed Explosion from damaging other Pokémon (Traits)") // Fixed issue #1961 +{ + s16 damage1, damage2; + GIVEN { + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + ASSUME(GetMoveType(MOVE_EXPLOSION) == TYPE_NORMAL); + PLAYER(SPECIES_JOLTEON) { Ability(ABILITY_QUICK_FEET); Innates(ABILITY_VOLT_ABSORB); HP(1); MaxHP(100); } + PLAYER(SPECIES_ABRA); + OPPONENT(SPECIES_GRAVELER_ALOLA) { Ability(ABILITY_STURDY); Innates(ABILITY_GALVANIZE); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_EXPLOSION); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_VOLT_ABSORB); + HP_BAR(playerLeft, damage: -25); + MESSAGE("Jolteon restored HP using its Volt Absorb!"); + HP_BAR(playerRight, captureDamage: &damage1); + HP_BAR(opponentRight, captureDamage: &damage2); + } THEN { + EXPECT_NE(damage1, 0); + EXPECT_NE(damage2, 0); + } +} + +SINGLE_BATTLE_TEST("Volt Absorb prevents Cell Battery from activating (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_THUNDER_SHOCK) == TYPE_ELECTRIC); + PLAYER(SPECIES_JOLTEON) { Ability(ABILITY_QUICK_FEET); Innates(ABILITY_VOLT_ABSORB); HP(1); MaxHP(100); Item(ITEM_CELL_BATTERY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_THUNDER_SHOCK); } + } SCENE { + ABILITY_POPUP(player, ABILITY_VOLT_ABSORB); + HP_BAR(player, damage: -25); + MESSAGE("Jolteon restored HP using its Volt Absorb!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Cell Battery, the Attack of Jolteon rose!"); + } + + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Volt Absorb prevents Cell Battery from activating (Multi)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_THUNDER_SHOCK) == TYPE_ELECTRIC); + PLAYER(SPECIES_JOLTEON) { Ability(ABILITY_VOLT_ABSORB); HP(1); MaxHP(100); Items(ITEM_PECHA_BERRY, ITEM_CELL_BATTERY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_THUNDER_SHOCK); } + } SCENE { + ABILITY_POPUP(player, ABILITY_VOLT_ABSORB); + HP_BAR(player, damage: -25); + MESSAGE("Jolteon restored HP using its Volt Absorb!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Cell Battery, the Attack of Jolteon rose!"); + } + + } +} +#endif diff --git a/test/battle/ability/wandering_spirit.c b/test/battle/ability/wandering_spirit.c index 9d8d7fad3698..687109534bec 100644 --- a/test/battle/ability/wandering_spirit.c +++ b/test/battle/ability/wandering_spirit.c @@ -17,3 +17,22 @@ SINGLE_BATTLE_TEST("Wandering Spirit copied ability should not trigger on fainte NOT ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); } } + +#if MAX_MON_TRAITS > 1 +TO_DO_BATTLE_TEST("TODO: Write Wandering Spirit (Ability) test titles (Traits)") + +SINGLE_BATTLE_TEST("Wandering Spirit copied ability should not trigger on fainted mon (Traits)") +{ + GIVEN { + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + PLAYER(SPECIES_WOBBUFFET) + OPPONENT(SPECIES_YAMASK_GALAR) { HP(1); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_WANDERING_SPIRIT); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_POISON_FANG); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_FANG, player); + NOT ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + } +} +#endif diff --git a/test/battle/ability/water_absorb.c b/test/battle/ability/water_absorb.c index 1c0406ebdc47..eb9cbcc5f261 100644 --- a/test/battle/ability/water_absorb.c +++ b/test/battle/ability/water_absorb.c @@ -83,3 +83,113 @@ SINGLE_BATTLE_TEST("Water Absorb prevents Absorb Bulb and Luminous Moss from act } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Water Absorb heals 25% when hit by water type moves (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_BUBBLE) == TYPE_WATER); + PLAYER(SPECIES_POLIWAG) { Ability(ABILITY_DAMP); Innates(ABILITY_WATER_ABSORB); HP(1); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_BUBBLE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_WATER_ABSORB); + HP_BAR(player, damage: -25); + MESSAGE("Poliwag restored HP using its Water Absorb!"); + } +} + +SINGLE_BATTLE_TEST("Water Absorb does not activate if protected (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_BUBBLE) == TYPE_WATER); + PLAYER(SPECIES_POLIWAG) { Ability(ABILITY_DAMP); Innates(ABILITY_WATER_ABSORB); HP(1); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PROTECT); MOVE(opponent, MOVE_BUBBLE); } + } SCENE { + NONE_OF { ABILITY_POPUP(player, ABILITY_WATER_ABSORB); HP_BAR(player); MESSAGE("Poliwag restored HP using its Water Absorb!"); } + } +} + +SINGLE_BATTLE_TEST("Water Absorb activates on status moves (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SOAK) == TYPE_WATER); + ASSUME(GetMoveCategory(MOVE_SOAK) == DAMAGE_CATEGORY_STATUS); + PLAYER(SPECIES_POLIWAG) { Ability(ABILITY_DAMP); Innates(ABILITY_WATER_ABSORB); HP(1); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SOAK); } + } SCENE { + ABILITY_POPUP(player, ABILITY_WATER_ABSORB); + HP_BAR(player, damage: -25); + MESSAGE("Poliwag restored HP using its Water Absorb!"); + } +} + +SINGLE_BATTLE_TEST("Water Absorb is only triggered once on multi strike moves (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_WATER_SHURIKEN) == TYPE_WATER); + ASSUME(GetMoveEffect(MOVE_WATER_SHURIKEN) == EFFECT_MULTI_HIT); + PLAYER(SPECIES_POLIWAG) { Ability(ABILITY_DAMP); Innates(ABILITY_WATER_ABSORB); HP(1); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_WATER_SHURIKEN); } + } SCENE { + ABILITY_POPUP(player, ABILITY_WATER_ABSORB); + HP_BAR(player, damage: -25); + MESSAGE("Poliwag restored HP using its Water Absorb!"); + } +} + +SINGLE_BATTLE_TEST("Water Absorb prevents Absorb Bulb and Luminous Moss from activating (Traits)") +{ + u32 item; + PARAMETRIZE { item = ITEM_ABSORB_BULB; } + PARAMETRIZE { item = ITEM_LUMINOUS_MOSS; } + GIVEN { + ASSUME(GetMoveType(MOVE_BUBBLE) == TYPE_WATER); + PLAYER(SPECIES_POLIWAG) { Ability(ABILITY_DAMP); Innates(ABILITY_WATER_ABSORB); HP(1); MaxHP(100); Item(item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_BUBBLE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_WATER_ABSORB); + HP_BAR(player, damage: -25); + MESSAGE("Poliwag restored HP using its Water Absorb!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } + + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Water Absorb prevents Absorb Bulb and Luminous Moss from activating (Multi)") +{ + u32 item; + PARAMETRIZE { item = ITEM_ABSORB_BULB; } + PARAMETRIZE { item = ITEM_LUMINOUS_MOSS; } + GIVEN { + ASSUME(GetMoveType(MOVE_BUBBLE) == TYPE_WATER); + PLAYER(SPECIES_POLIWAG) { Ability(ABILITY_WATER_ABSORB); HP(1); MaxHP(100); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_BUBBLE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_WATER_ABSORB); + HP_BAR(player, damage: -25); + MESSAGE("Poliwag restored HP using its Water Absorb!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } + + } +} +#endif diff --git a/test/battle/ability/water_bubble.c b/test/battle/ability/water_bubble.c index 1d1d3956d42c..760136cf7263 100644 --- a/test/battle/ability/water_bubble.c +++ b/test/battle/ability/water_bubble.c @@ -19,3 +19,24 @@ SINGLE_BATTLE_TEST("Water Bubble prevents burn from Will-o-Wisp") } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Water Bubble prevents burn from Will-o-Wisp (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_WILL_O_WISP) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_WILL_O_WISP) == MOVE_EFFECT_BURN); + PLAYER(SPECIES_DEWPIDER) { Ability(ABILITY_WATER_ABSORB); Innates(ABILITY_WATER_BUBBLE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_WILL_O_WISP); } + } SCENE { + ABILITY_POPUP(player, ABILITY_WATER_BUBBLE); + MESSAGE("It doesn't affect Dewpider…"); + NONE_OF { + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, player); + STATUS_ICON(player, burn: TRUE); + } + } +} +#endif diff --git a/test/battle/ability/water_compaction.c b/test/battle/ability/water_compaction.c index 8e7751e37660..8fb167c4f80b 100644 --- a/test/battle/ability/water_compaction.c +++ b/test/battle/ability/water_compaction.c @@ -59,3 +59,64 @@ SINGLE_BATTLE_TEST("Water Compaction does not affect damage taken from Water typ EXPECT_EQ(results[0].damage, results[1].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Water Compaction raises Defense 2 stages when hit by a water type move (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_WATER_GUN) == TYPE_WATER); + PLAYER(SPECIES_SANDYGAST) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_WATER_COMPACTION); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_WATER_GUN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + ABILITY_POPUP(player, ABILITY_WATER_COMPACTION); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Water Compaction raises Defense 2 stages on each hit of a multi-hit Water type move (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SURGING_STRIKES) == TYPE_WATER); + ASSUME(GetMoveStrikeCount(MOVE_SURGING_STRIKES) == 3); + PLAYER(SPECIES_SANDYGAST) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_WATER_COMPACTION); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SURGING_STRIKES); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURGING_STRIKES, opponent); + ABILITY_POPUP(player, ABILITY_WATER_COMPACTION); + MESSAGE("Sandygast's Defense sharply rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURGING_STRIKES, opponent); + ABILITY_POPUP(player, ABILITY_WATER_COMPACTION); + MESSAGE("Sandygast's Defense sharply rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURGING_STRIKES, opponent); + ABILITY_POPUP(player, ABILITY_WATER_COMPACTION); + MESSAGE("Sandygast's Defense sharply rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 6); + } +} + +SINGLE_BATTLE_TEST("Water Compaction does not affect damage taken from Water type moves (Traits)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_SAND_VEIL; } + PARAMETRIZE { ability = ABILITY_WATER_COMPACTION; } + GIVEN { + ASSUME(GetMoveType(MOVE_WATER_GUN) == TYPE_WATER); + PLAYER(SPECIES_SANDYGAST) { Ability(ABILITY_SAND_VEIL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_WATER_GUN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} +#endif diff --git a/test/battle/ability/weak_armor.c b/test/battle/ability/weak_armor.c index b8a9337d6722..595052f78f70 100644 --- a/test/battle/ability/weak_armor.c +++ b/test/battle/ability/weak_armor.c @@ -207,3 +207,204 @@ SINGLE_BATTLE_TEST("Weak Armor doesn't interrupt multi hit moves if Speed can't EXPECT_EQ(player->statStages[STAT_SPEED], MAX_STAT_STAGE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Weak Armor lowers Defense by 1 and boosts Speed by 1 (Gen5-6) or 2 (Gen7+) when hit by a physical attack (Traits)") +{ + u16 move, gen; + + PARAMETRIZE { move = MOVE_SCRATCH; gen = GEN_6; } + PARAMETRIZE { move = MOVE_SCRATCH; gen = GEN_7; } + PARAMETRIZE { move = MOVE_GUST; gen = GEN_7; } + + GIVEN { + WITH_CONFIG(CONFIG_WEAK_ARMOR_SPEED, gen); + PLAYER(SPECIES_SLUGMA) { Ability(ABILITY_FLAME_BODY); Innates(ABILITY_WEAK_ARMOR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + HP_BAR(player); + if (move == MOVE_SCRATCH) { + ABILITY_POPUP(player, ABILITY_WEAK_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } else { + NONE_OF { + ABILITY_POPUP(player, ABILITY_WEAK_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } + } + } THEN { + if (move == MOVE_SCRATCH) { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + (gen == GEN_7 ? 2 : 1)); + } else { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } + } +} + +// Oddly specific, but it was a bug at one point. +SINGLE_BATTLE_TEST("Weak Armor does not trigger when brought in by Dragon Tail and taking Stealth Rock damage (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_STEALTH_ROCK) == EFFECT_STEALTH_ROCK); + ASSUME(GetMoveEffect(MOVE_DRAGON_TAIL) == EFFECT_HIT_SWITCH_TARGET); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_SLUGMA) { Ability(ABILITY_FLAME_BODY); Innates(ABILITY_WEAK_ARMOR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_STEALTH_ROCK); } + TURN { MOVE(opponent, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEALTH_ROCK, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, opponent); + HP_BAR(player); + MESSAGE("Slugma was dragged out!"); + HP_BAR(player); + MESSAGE("Pointed stones dug into Slugma!"); + NONE_OF { + ABILITY_POPUP(player, ABILITY_WEAK_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Slugma's Weak Armor lowered its Defense!"); + MESSAGE("Slugma's Weak Armor sharply raised its Speed!"); + } + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Weak Armor still boosts Speed if Defense can't go any lower (Traits)") +{ + u16 gen; + + PARAMETRIZE { gen = GEN_6; } + PARAMETRIZE { gen = GEN_7; } + GIVEN { + WITH_CONFIG(CONFIG_WEAK_ARMOR_SPEED, gen); + PLAYER(SPECIES_SLUGMA) { Ability(ABILITY_FLAME_BODY); Innates(ABILITY_WEAK_ARMOR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCREECH); } + TURN { MOVE(opponent, MOVE_SCREECH); } + TURN { MOVE(opponent, MOVE_SCREECH); } + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_WEAK_ARMOR); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Slugma's Weak Armor lowered its Defense!"); + } + MESSAGE("Slugma's Defense won't go any lower!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + if (gen == GEN_6) + MESSAGE("Slugma's Weak Armor raised its Speed!"); + else + MESSAGE("Slugma's Weak Armor sharply raised its Speed!"); + + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], MIN_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + (gen == GEN_7 ? 2 : 1)); + } +} + +SINGLE_BATTLE_TEST("Weak Armor still lowers Defense if Speed can't go any higher (Traits)") +{ + GIVEN { + PLAYER(SPECIES_SLUGMA) { Ability(ABILITY_FLAME_BODY); Innates(ABILITY_WEAK_ARMOR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_AGILITY); } + TURN { MOVE(player, MOVE_AGILITY); } + TURN { MOVE(player, MOVE_AGILITY); } + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_WEAK_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Slugma's Weak Armor lowered its Defense!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Slugma's Weak Armor sharply raised its Speed!"); + } + MESSAGE("Slugma's Speed won't go any higher!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(player->statStages[STAT_SPEED], MAX_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Weak Armor doesn't interrupt multi hit moves if Defense can't go any lower (Traits)") +{ + u32 j; + GIVEN { + WITH_CONFIG(CONFIG_WEAK_ARMOR_SPEED, GEN_7); + PLAYER(SPECIES_MAGCARGO) { Ability(ABILITY_FLAME_BODY); Innates(ABILITY_WEAK_ARMOR); Defense(999); } + OPPONENT(SPECIES_CLOYSTER) { Ability(ABILITY_SHELL_ARMOR); Innates(ABILITY_SKILL_LINK); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCREECH); } + TURN { MOVE(opponent, MOVE_SCREECH); } + TURN { MOVE(opponent, MOVE_FURY_SWIPES); } + } SCENE { + for (j = 0; j < 2; j++) + { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, opponent); + ABILITY_POPUP(player, ABILITY_WEAK_ARMOR); + MESSAGE("Magcargo's Weak Armor lowered its Defense!"); + MESSAGE("Magcargo's Weak Armor sharply raised its Speed!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, opponent); + ABILITY_POPUP(player, ABILITY_WEAK_ARMOR); + MESSAGE("Magcargo's Defense won't go any lower!"); + MESSAGE("Magcargo's Weak Armor sharply raised its Speed!"); + for (j = 0; j < 2; j++) + { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, opponent); + // Ability doesn't activate if neither stat can be changed. + NONE_OF { + ABILITY_POPUP(player, ABILITY_WEAK_ARMOR); + MESSAGE("Magcargo's Defense won't go any lower!"); + MESSAGE("Magcargo's Speed won't go any higher!"); + } + } + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], MIN_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_SPEED], MAX_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Weak Armor doesn't interrupt multi hit moves if Speed can't go any higher (Traits)") +{ + u32 j; + GIVEN { + WITH_CONFIG(CONFIG_WEAK_ARMOR_SPEED, GEN_7); + PLAYER(SPECIES_MAGCARGO) { Ability(ABILITY_FLAME_BODY); Innates(ABILITY_WEAK_ARMOR); Defense(999); } + OPPONENT(SPECIES_CLOYSTER) { Ability(ABILITY_SHELL_ARMOR); Innates(ABILITY_SKILL_LINK); } + } WHEN { + TURN { MOVE(player, MOVE_AGILITY); } + TURN { MOVE(player, MOVE_AGILITY); } + TURN { MOVE(opponent, MOVE_FURY_SWIPES); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, opponent); + ABILITY_POPUP(player, ABILITY_WEAK_ARMOR); + MESSAGE("Magcargo's Weak Armor lowered its Defense!"); + MESSAGE("Magcargo's Weak Armor sharply raised its Speed!"); + for (j = 0; j < 4; j++) + { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, opponent); + ABILITY_POPUP(player, ABILITY_WEAK_ARMOR); + MESSAGE("Magcargo's Weak Armor lowered its Defense!"); + MESSAGE("Magcargo's Speed won't go any higher!"); + } + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 5); + EXPECT_EQ(player->statStages[STAT_SPEED], MAX_STAT_STAGE); + } +} +#endif diff --git a/test/battle/ability/wind_power.c b/test/battle/ability/wind_power.c index aacf1811ae22..03b9016f19dc 100644 --- a/test/battle/ability/wind_power.c +++ b/test/battle/ability/wind_power.c @@ -260,3 +260,251 @@ DOUBLE_BATTLE_TEST("Wind Power activates correctly when Tailwind is used") } } } + + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Wind Power sets up Charge for player when hit by a wind move (Traits)") +{ + s16 dmgBefore, dmgAfter; + u16 move; + + PARAMETRIZE {move = MOVE_SCRATCH; } + PARAMETRIZE {move = MOVE_AIR_CUTTER; } + + GIVEN { + PLAYER(SPECIES_WATTREL) { Ability(ABILITY_COMPETITIVE); Innates(ABILITY_WIND_POWER); Speed(10); } + OPPONENT(SPECIES_PERSIAN) {Ability(ABILITY_UNNERVE); Innates(ABILITY_LIMBER); Speed(5) ;} // Limber, so it doesn't get paralyzed. + } WHEN { + TURN { MOVE(player, MOVE_NUZZLE), MOVE(opponent, move); } + TURN { MOVE(player, MOVE_NUZZLE), MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_NUZZLE, player); + HP_BAR(opponent, captureDamage: &dmgBefore); + + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + HP_BAR(player); + if (move == MOVE_AIR_CUTTER) { + ABILITY_POPUP(player, ABILITY_WIND_POWER); + MESSAGE("Being hit by Air Cutter charged Wattrel with power!"); + } + + ANIMATION(ANIM_TYPE_MOVE, MOVE_NUZZLE, player); + HP_BAR(opponent, captureDamage: &dmgAfter); + + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + HP_BAR(player); + if (move == MOVE_AIR_CUTTER) { + ABILITY_POPUP(player, ABILITY_WIND_POWER); + MESSAGE("Being hit by Air Cutter charged Wattrel with power!"); + } + } + THEN { + if (move == MOVE_AIR_CUTTER) { + EXPECT_MUL_EQ(dmgBefore, Q_4_12(2.0), dmgAfter); + } + else { + EXPECT_EQ(dmgAfter, dmgBefore); + } + } +} + +SINGLE_BATTLE_TEST("Wind Power sets up Charge for player when hit by a wind move (Traits)") +{ + s16 dmgBefore, dmgAfter; + u16 move; + + PARAMETRIZE {move = MOVE_SCRATCH; } + PARAMETRIZE {move = MOVE_AIR_CUTTER; } + + GIVEN { + PLAYER(SPECIES_WATTREL) { Ability(ABILITY_COMPETITIVE); Innates(ABILITY_WIND_POWER); Speed(10); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_LIMBER); Speed(5) ;} // Limber, so it doesn't get paralyzed. + } WHEN { + TURN { MOVE(player, MOVE_THUNDERBOLT), MOVE(opponent, move); } + TURN { MOVE(player, MOVE_THUNDERBOLT), MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, player); + HP_BAR(opponent, captureDamage: &dmgBefore); + + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + HP_BAR(player); + if (move == MOVE_AIR_CUTTER) { + ABILITY_POPUP(player, ABILITY_WIND_POWER); + MESSAGE("Being hit by Air Cutter charged Wattrel with power!"); + } + + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, player); + HP_BAR(opponent, captureDamage: &dmgAfter); + + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + HP_BAR(player); + if (move == MOVE_AIR_CUTTER) { + ABILITY_POPUP(player, ABILITY_WIND_POWER); + MESSAGE("Being hit by Air Cutter charged Wattrel with power!"); + } + } + THEN { + if (move == MOVE_AIR_CUTTER) { + EXPECT_MUL_EQ(dmgBefore, Q_4_12(2.0), dmgAfter); + } + else { + EXPECT_EQ(dmgAfter, dmgBefore); + } + } +} + +SINGLE_BATTLE_TEST("Wind Power sets up Charge for only one attack when hit by a wind move (Traits)") +{ + s16 dmgCharged, dmgAfter; + u16 move; + + PARAMETRIZE {move = MOVE_SCRATCH; } + PARAMETRIZE {move = MOVE_AIR_CUTTER; } + + GIVEN { + PLAYER(SPECIES_WATTREL) { Ability(ABILITY_COMPETITIVE); Innates(ABILITY_WIND_POWER); Speed(5); } + OPPONENT(SPECIES_PERSIAN) {Ability(ABILITY_UNNERVE); Innates(ABILITY_LIMBER); Speed(10) ;} // Limber, so it doesn't get paralyzed. + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_NUZZLE); } + TURN { MOVE(player, MOVE_NUZZLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + HP_BAR(player); + if (move == MOVE_AIR_CUTTER) { + ABILITY_POPUP(player, ABILITY_WIND_POWER); + MESSAGE("Being hit by Air Cutter charged Wattrel with power!"); + } + + ANIMATION(ANIM_TYPE_MOVE, MOVE_NUZZLE, player); + HP_BAR(opponent, captureDamage: &dmgCharged); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_NUZZLE, player); + HP_BAR(opponent, captureDamage: &dmgAfter); + } + THEN { + if (move == MOVE_AIR_CUTTER) { + EXPECT_MUL_EQ(dmgAfter, Q_4_12(2.0), dmgCharged); + } + else { + EXPECT_EQ(dmgAfter, dmgCharged); + } + } +} + +DOUBLE_BATTLE_TEST("Wind Power activates correctly for every battler with the ability when hit by a 2/3 target move (Traits)") +{ + enum Ability abilityLeft, abilityRight; + + PARAMETRIZE {abilityLeft = ABILITY_NONE, abilityRight = ABILITY_WIND_POWER;} + PARAMETRIZE {abilityLeft = ABILITY_WIND_POWER, abilityRight = ABILITY_NONE; } + PARAMETRIZE {abilityLeft = ABILITY_WIND_POWER, abilityRight = ABILITY_WIND_POWER; } + + GIVEN { + PLAYER(SPECIES_WATTREL) { Ability(ABILITY_COMPETITIVE); Innates(abilityLeft); Speed(10); } + PLAYER(SPECIES_WATTREL) { Ability(ABILITY_COMPETITIVE); Innates(abilityRight); Speed(5); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_LIMBER); Speed(20); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_LIMBER); Speed(15); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_AIR_CUTTER); MOVE(opponentRight, MOVE_AIR_CUTTER);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AIR_CUTTER, opponentLeft); + + HP_BAR(playerLeft); + if (abilityLeft == ABILITY_WIND_POWER) { + ABILITY_POPUP(playerLeft, ABILITY_WIND_POWER); + MESSAGE("Being hit by Air Cutter charged Wattrel with power!"); + } + HP_BAR(playerRight); + if (abilityRight == ABILITY_WIND_POWER) { + ABILITY_POPUP(playerRight, ABILITY_WIND_POWER); + MESSAGE("Being hit by Air Cutter charged Wattrel with power!"); + } + NONE_OF { + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + } + } + THEN { + EXPECT_NE(playerLeft->hp, playerLeft->maxHP); + EXPECT_NE(playerRight->hp, playerRight->maxHP); + EXPECT_EQ(opponentRight->hp, opponentRight->maxHP); + EXPECT_EQ(opponentLeft->hp, opponentLeft->maxHP); + } +} + +DOUBLE_BATTLE_TEST("Wind Power activates correctly for every battler with the ability when hit by a 3 target move (Traits)") +{ + enum Ability abilityLeft, abilityRight; + + PARAMETRIZE {abilityLeft = ABILITY_NONE, abilityRight = ABILITY_WIND_POWER; } + PARAMETRIZE {abilityLeft = ABILITY_WIND_POWER, abilityRight = ABILITY_NONE; } + PARAMETRIZE {abilityLeft = ABILITY_WIND_POWER, abilityRight = ABILITY_WIND_POWER; } + + GIVEN { + PLAYER(SPECIES_WATTREL) { Ability(ABILITY_COMPETITIVE); Innates(abilityLeft); Speed(10); } + PLAYER(SPECIES_WATTREL) { Ability(ABILITY_COMPETITIVE); Innates(abilityRight); Speed(5); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_LIMBER); Speed(20); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_LIMBER); Speed(15); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_PETAL_BLIZZARD);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PETAL_BLIZZARD, opponentLeft); + + HP_BAR(playerLeft); + HP_BAR(playerRight); + HP_BAR(opponentRight); + NOT HP_BAR(opponentLeft); + if (abilityLeft == ABILITY_WIND_POWER) { + ABILITY_POPUP(playerLeft, ABILITY_WIND_POWER); + MESSAGE("Being hit by Petal Blizzard charged Wattrel with power!"); + } + if (abilityRight == ABILITY_WIND_POWER) { + ABILITY_POPUP(playerRight, ABILITY_WIND_POWER); + MESSAGE("Being hit by Petal Blizzard charged Wattrel with power!"); + } + } + THEN { + EXPECT_NE(playerLeft->hp, playerLeft->maxHP); + EXPECT_NE(playerRight->hp, playerRight->maxHP); + EXPECT_NE(opponentRight->hp, opponentRight->maxHP); + EXPECT_EQ(opponentLeft->hp, opponentLeft->maxHP); + } +} + +DOUBLE_BATTLE_TEST("Wind Power activates correctly when Tailwind is used (Traits)") +{ + bool8 opponentSide; + + PARAMETRIZE {opponentSide = TRUE;} + PARAMETRIZE {opponentSide = FALSE;} + + GIVEN { + ASSUME(GetMoveEffect(MOVE_TAILWIND) == EFFECT_TAILWIND); + PLAYER(SPECIES_WATTREL) { Ability(ABILITY_COMPETITIVE); Innates(ABILITY_WIND_POWER); Speed(10); } + PLAYER(SPECIES_WATTREL) { Ability(ABILITY_COMPETITIVE); Innates(ABILITY_WIND_POWER); Speed(5); } + OPPONENT(SPECIES_WATTREL) { Ability(ABILITY_COMPETITIVE); Innates(ABILITY_WIND_POWER); Speed(20); } + OPPONENT(SPECIES_WATTREL) { Ability(ABILITY_COMPETITIVE); Innates(ABILITY_WIND_POWER); Speed(15); } + } WHEN { + TURN { MOVE((opponentSide == TRUE) ? opponentLeft : playerLeft, MOVE_TAILWIND);} + } SCENE { + if (opponentSide) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAILWIND, opponentLeft); + + ABILITY_POPUP(opponentLeft, ABILITY_WIND_POWER); + MESSAGE("Being hit by Tailwind charged the opposing Wattrel with power!"); + + ABILITY_POPUP(opponentRight, ABILITY_WIND_POWER); + MESSAGE("Being hit by Tailwind charged the opposing Wattrel with power!"); + } + else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAILWIND, playerLeft); + + ABILITY_POPUP(playerLeft, ABILITY_WIND_POWER); + MESSAGE("Being hit by Tailwind charged Wattrel with power!"); + + ABILITY_POPUP(playerRight, ABILITY_WIND_POWER); + MESSAGE("Being hit by Tailwind charged Wattrel with power!"); + } + } +} +#endif diff --git a/test/battle/ability/wind_rider.c b/test/battle/ability/wind_rider.c index d68414d060b7..5c3f5c236d8b 100644 --- a/test/battle/ability/wind_rider.c +++ b/test/battle/ability/wind_rider.c @@ -125,3 +125,101 @@ SINGLE_BATTLE_TEST("Wind Rider absorbs Wind moves and raises Attack by one stage EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Wind Rider raises Attack by one stage if it sets up Tailwind (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRAMBLIN) { Ability(ABILITY_INFILTRATOR); Innates(ABILITY_WIND_RIDER); } + } WHEN { + TURN { MOVE(opponent, MOVE_TAILWIND); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAILWIND, opponent); + ABILITY_POPUP(opponent, ABILITY_WIND_RIDER); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Bramblin's Attack rose!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} + +DOUBLE_BATTLE_TEST("Wind Rider raises Attack by one stage if Tailwind is setup by its partner (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRAMBLIN) { Ability(ABILITY_INFILTRATOR); Innates(ABILITY_WIND_RIDER); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_TAILWIND); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAILWIND, opponentLeft); + ABILITY_POPUP(opponentRight, ABILITY_WIND_RIDER); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("The opposing Bramblin's Attack rose!"); + } THEN { + EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Wind Rider doesn't raise Attack if opponent sets up Tailwind (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRAMBLIN) { Ability(ABILITY_INFILTRATOR); Innates(ABILITY_WIND_RIDER); } + } WHEN { + TURN { MOVE(player, MOVE_TAILWIND); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAILWIND, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_WIND_RIDER); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Bramblin's Attack rose!"); + } + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Wind Rider raises Attack by one stage if switched into Tailwind on its side of the field (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_BRAMBLIN) { Ability(ABILITY_INFILTRATOR); Innates(ABILITY_WIND_RIDER); } + } WHEN { + TURN { MOVE(opponent, MOVE_TAILWIND); } + TURN { SWITCH(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAILWIND, opponent); + ABILITY_POPUP(opponent, ABILITY_WIND_RIDER); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Bramblin's Wind Rider raised its Attack!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Wind Rider absorbs Wind moves and raises Attack by one stage (Traits)") +{ + GIVEN { + ASSUME(IsWindMove(MOVE_GUST)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRAMBLIN) { Ability(ABILITY_INFILTRATOR); Innates(ABILITY_WIND_RIDER); } + } WHEN { + TURN { MOVE(player, MOVE_GUST); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GUST, player); + HP_BAR(opponent); + } + ABILITY_POPUP(opponent, ABILITY_WIND_RIDER); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Bramblin's Attack rose!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} +#endif diff --git a/test/battle/ability/zen_mode.c b/test/battle/ability/zen_mode.c index 96f47a3180a3..d4f5b663a854 100644 --- a/test/battle/ability/zen_mode.c +++ b/test/battle/ability/zen_mode.c @@ -88,3 +88,93 @@ SINGLE_BATTLE_TEST("Zen Mode switches Darmanitan's form when HP is healed above EXPECT_EQ(player->species, standardSpecies); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Zen Mode switches Darmanitan's form when HP is half or less at the end of the turn (Traits)") +{ + u16 standardSpecies, zenSpecies; + PARAMETRIZE { standardSpecies = SPECIES_DARMANITAN_STANDARD; zenSpecies = SPECIES_DARMANITAN_ZEN; } + PARAMETRIZE { standardSpecies = SPECIES_DARMANITAN_GALAR_STANDARD; zenSpecies = SPECIES_DARMANITAN_GALAR_ZEN; } + + GIVEN { + ASSUME(GetSpeciesBaseHP(standardSpecies) == 105); + ASSUME(GetSpeciesBaseHP(zenSpecies) == 105); + PLAYER(standardSpecies) + { + Ability(ABILITY_SHEER_FORCE); Innates(ABILITY_ZEN_MODE); + HP((GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP) / 2) + 1); + } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Darmanitan used Celebrate!"); + MESSAGE("The opposing Wobbuffet used Scratch!"); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_ZEN_MODE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + } THEN { + ASSUME(player->hp <= player->maxHP / 2); + EXPECT_EQ(player->species, zenSpecies); + } +} + +SINGLE_BATTLE_TEST("Zen Mode switches Darmanitan's form when HP is half or less before the first turn (Traits)") +{ + u16 standardSpecies, zenSpecies; + PARAMETRIZE { standardSpecies = SPECIES_DARMANITAN_STANDARD; zenSpecies = SPECIES_DARMANITAN_ZEN; } + PARAMETRIZE { standardSpecies = SPECIES_DARMANITAN_GALAR_STANDARD; zenSpecies = SPECIES_DARMANITAN_GALAR_ZEN; } + + GIVEN { + ASSUME(GetSpeciesBaseHP(standardSpecies) == 105); + ASSUME(GetSpeciesBaseHP(zenSpecies) == 105); + PLAYER(standardSpecies) + { + Ability(ABILITY_SHEER_FORCE); Innates(ABILITY_ZEN_MODE); + HP(GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP) / 2); + } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ZEN_MODE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Darmanitan used Celebrate!"); + MESSAGE("The opposing Wobbuffet used Celebrate!"); + } THEN { + EXPECT_LE(player->hp, player->maxHP / 2); + EXPECT_EQ(player->species, zenSpecies); + } +} + +SINGLE_BATTLE_TEST("Zen Mode switches Darmanitan's form when HP is healed above half (Traits)") +{ + u16 standardSpecies, zenSpecies; + PARAMETRIZE { standardSpecies = SPECIES_DARMANITAN_STANDARD; zenSpecies = SPECIES_DARMANITAN_ZEN; } + PARAMETRIZE { standardSpecies = SPECIES_DARMANITAN_GALAR_STANDARD; zenSpecies = SPECIES_DARMANITAN_GALAR_ZEN; } + + GIVEN { + ASSUME(GetSpeciesBaseHP(standardSpecies) == 105); + ASSUME(GetSpeciesBaseHP(zenSpecies) == 105); + PLAYER(standardSpecies) + { + Ability(ABILITY_SHEER_FORCE); Innates(ABILITY_ZEN_MODE); + HP(GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP) / 2); + } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_HEAL_PULSE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ZEN_MODE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Darmanitan used Celebrate!"); + MESSAGE("The opposing Wobbuffet used Heal Pulse!"); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_ZEN_MODE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + } THEN { + EXPECT_GT(player->hp, player->maxHP / 2); + EXPECT_EQ(player->species, standardSpecies); + } +} +#endif diff --git a/test/battle/ability/zero_to_hero.c b/test/battle/ability/zero_to_hero.c index 9b4b362d7597..d887a4f60cc0 100644 --- a/test/battle/ability/zero_to_hero.c +++ b/test/battle/ability/zero_to_hero.c @@ -197,3 +197,148 @@ SINGLE_BATTLE_TEST("Zero to Hero cannot be copied by Trace") } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Zero to Hero transforms Palafin when it switches out (Traits)") +{ + GIVEN { + PLAYER(SPECIES_PALAFIN_ZERO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ZERO_TO_HERO); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(player, 1); } + TURN { SWITCH(player, 0); } + } SCENE { + SWITCH_OUT_MESSAGE("Palafin"); + SEND_IN_MESSAGE("Wobbuffet"); + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Palafin"); + ABILITY_POPUP(player, ABILITY_ZERO_TO_HERO); + MESSAGE("Palafin underwent a heroic transformation!"); + } THEN { EXPECT_EQ(player->species, SPECIES_PALAFIN_HERO); } +} + +SINGLE_BATTLE_TEST("Zero to Hero transforms both player and opponent (Traits)") +{ + GIVEN { + PLAYER(SPECIES_PALAFIN_ZERO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ZERO_TO_HERO); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PALAFIN_ZERO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ZERO_TO_HERO); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(player, 1); SWITCH(opponent, 1); } + TURN { SWITCH(player, 0); SWITCH(opponent, 0); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ZERO_TO_HERO); + MESSAGE("Palafin underwent a heroic transformation!"); + ABILITY_POPUP(opponent, ABILITY_ZERO_TO_HERO); + MESSAGE("The opposing Palafin underwent a heroic transformation!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_PALAFIN_HERO); + EXPECT_EQ(opponent->species, SPECIES_PALAFIN_HERO); + } +} + +SINGLE_BATTLE_TEST("Zero to Hero will activate if a switch move is used (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FLIP_TURN) == EFFECT_HIT_ESCAPE); + PLAYER(SPECIES_PALAFIN_ZERO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ZERO_TO_HERO); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FLIP_TURN); SEND_OUT(player, 1); } + TURN { SWITCH(player, 0); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLIP_TURN, player); + ABILITY_POPUP(player, ABILITY_ZERO_TO_HERO); + MESSAGE("Palafin underwent a heroic transformation!"); + } THEN { EXPECT_EQ(player->species, SPECIES_PALAFIN_HERO); } +} + +SINGLE_BATTLE_TEST("Transform doesn't apply the heroic transformation message when copying Palafin (Traits)") +{ + GIVEN { + PLAYER(SPECIES_PALAFIN_ZERO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ZERO_TO_HERO); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(player, 1); } + TURN { SWITCH(player, 0); MOVE(opponent, MOVE_TRANSFORM); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ZERO_TO_HERO); + MESSAGE("Palafin underwent a heroic transformation!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRANSFORM, opponent); + MESSAGE("The opposing Wobbuffet transformed into Palafin!"); + NOT ABILITY_POPUP(opponent, ABILITY_ZERO_TO_HERO); + } THEN { EXPECT_EQ(player->species, SPECIES_PALAFIN_HERO); } +} + +SINGLE_BATTLE_TEST("Imposter doesn't apply the heroic transformation message when copying Palafin (Traits)") +{ + GIVEN { + PLAYER(SPECIES_PALAFIN_ZERO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ZERO_TO_HERO); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_DITTO) { Ability(ABILITY_IMPOSTER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(player, 1); SWITCH(opponent, 1); } + TURN { SWITCH(player, 0); SWITCH(opponent, 0); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ZERO_TO_HERO); + MESSAGE("Palafin underwent a heroic transformation!"); + ABILITY_POPUP(opponent, ABILITY_IMPOSTER); + MESSAGE("The opposing Ditto transformed into Palafin using Imposter!"); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_ZERO_TO_HERO); + MESSAGE("The opposing Ditto underwent a heroic transformation!"); + } + } THEN { EXPECT_EQ(player->species, SPECIES_PALAFIN_HERO); } +} + +SINGLE_BATTLE_TEST("Zero to Hero's message displays correctly after all battlers fainted - Player (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + PLAYER(SPECIES_PALAFIN_ZERO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ZERO_TO_HERO); } + PLAYER(SPECIES_WOBBUFFET) { HP(1);} + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FLIP_TURN); SEND_OUT(player, 1); } + TURN { MOVE(opponent, MOVE_EXPLOSION); SEND_OUT(player, 0); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + HP_BAR(opponent, hp: 0); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, opponent); + // Everyone faints. + SEND_IN_MESSAGE("Palafin"); + MESSAGE("2 sent out Wobbuffet!"); + ABILITY_POPUP(player, ABILITY_ZERO_TO_HERO); + MESSAGE("Palafin underwent a heroic transformation!"); + } +} + +SINGLE_BATTLE_TEST("Zero to Hero's message displays correctly after all battlers fainted - Opponent (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PALAFIN_ZERO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ZERO_TO_HERO); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1);} + } WHEN { + TURN { MOVE(opponent, MOVE_FLIP_TURN); SEND_OUT(opponent, 1); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_EXPLOSION); SEND_OUT(player, 1); SEND_OUT(opponent, 0); } + TURN { MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_SCRATCH); } + } SCENE { + HP_BAR(player, hp: 0); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, player); + // Everyone faints. + SEND_IN_MESSAGE("Wobbuffet"); + MESSAGE("2 sent out Palafin!"); + ABILITY_POPUP(opponent, ABILITY_ZERO_TO_HERO); + MESSAGE("The opposing Palafin underwent a heroic transformation!"); + } +} +#endif diff --git a/test/battle/ai/ai.c b/test/battle/ai/ai.c index 43c6740f487c..3f33562b9b9d 100644 --- a/test/battle/ai/ai.c +++ b/test/battle/ai/ai.c @@ -1038,3 +1038,713 @@ AI_DOUBLE_BATTLE_TEST("AI won't be confused by player's previous priority moves TURN { MOVE(playerLeft, MOVE_DETECT); MOVE(playerRight, MOVE_DETECT); EXPECT_MOVE(opponentLeft, MOVE_POWER_GEM, target:playerLeft); EXPECT_MOVE(opponentRight, MOVE_CELEBRATE); } } } + +#if MAX_MON_TRAITS > 1 +AI_SINGLE_BATTLE_TEST("AI prefers Water Gun over Bubble if it knows that foe has Contrary (Traits)") +{ + enum Ability abilityAI; + + PARAMETRIZE { abilityAI = ABILITY_MOXIE; } + PARAMETRIZE { abilityAI = ABILITY_MOLD_BREAKER; } // Mold Breaker ignores Contrary. + GIVEN { + ASSUME(GetMovePower(MOVE_BUBBLE) == GetMovePower(MOVE_WATER_GUN)); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_SHUCKLE) { Ability(ABILITY_GLUTTONY); Innates(ABILITY_CONTRARY); } + OPPONENT(SPECIES_PINSIR) { Moves(MOVE_WATER_GUN, MOVE_BUBBLE); Ability(ABILITY_HYPER_CUTTER); Innates(abilityAI); } + } WHEN { + TURN { MOVE(player, MOVE_DEFENSE_CURL); } + TURN { MOVE(player, MOVE_DEFENSE_CURL); + if (abilityAI == ABILITY_MOLD_BREAKER) { SCORE_EQ(opponent, MOVE_WATER_GUN, MOVE_BUBBLE); } + else { SCORE_GT(opponent, MOVE_WATER_GUN, MOVE_BUBBLE); }} + } SCENE { + MESSAGE("Shuckle's Defense fell!"); // Contrary activates + } +} + +AI_SINGLE_BATTLE_TEST("AI prefers moves with better accuracy, but only if they both require the same number of hits to ko (Traits)") +{ + u16 move1 = MOVE_NONE, move2 = MOVE_NONE, move3 = MOVE_NONE, move4 = MOVE_NONE; + u16 hp, expectedMove, turns, expectedMove2; + enum Ability abilityAtk; + + abilityAtk = ABILITY_NONE; + expectedMove2 = MOVE_NONE; + + // Here it's a simple test, both Slam and Strength deal the same damage, but Strength always hits, whereas Slam often misses. + PARAMETRIZE { move1 = MOVE_SLAM; move2 = MOVE_STRENGTH; move3 = MOVE_SCRATCH; hp = 490; expectedMove = MOVE_STRENGTH; turns = 4; } + PARAMETRIZE { move1 = MOVE_SLAM; move2 = MOVE_STRENGTH; move3 = MOVE_SWIFT; move4 = MOVE_SCRATCH; hp = 365; expectedMove = MOVE_STRENGTH; turns = 3; } + PARAMETRIZE { move1 = MOVE_SLAM; move2 = MOVE_STRENGTH; move3 = MOVE_SWIFT; move4 = MOVE_SCRATCH; hp = 245; expectedMove = MOVE_STRENGTH; turns = 2; } + PARAMETRIZE { move1 = MOVE_SLAM; move2 = MOVE_STRENGTH; move3 = MOVE_SWIFT; move4 = MOVE_SCRATCH; hp = 125; expectedMove = MOVE_STRENGTH; turns = 1; } + // Mega Kick deals more damage, but can miss more often. Here, AI should choose Mega Kick if it can faint target in less number of turns than Strength. Otherwise, it should use Strength. + PARAMETRIZE { move1 = MOVE_MEGA_KICK; move2 = MOVE_STRENGTH; move3 = MOVE_SWIFT; move4 = MOVE_SCRATCH; hp = 170; expectedMove = MOVE_MEGA_KICK; turns = 1; } + PARAMETRIZE { move1 = MOVE_MEGA_KICK; move2 = MOVE_STRENGTH; move3 = MOVE_SWIFT; move4 = MOVE_SCRATCH; hp = 245; expectedMove = MOVE_STRENGTH; turns = 2; } + // Swift always hits and Guts has accuracy of 100%. Hustle lowers accuracy of all physical moves. + PARAMETRIZE { abilityAtk = ABILITY_HUSTLE; move1 = MOVE_MEGA_KICK; move2 = MOVE_STRENGTH; move3 = MOVE_SWIFT; move4 = MOVE_SCRATCH; hp = 5; expectedMove = MOVE_SWIFT; turns = 1; } + PARAMETRIZE { abilityAtk = ABILITY_HUSTLE; move1 = MOVE_MEGA_KICK; move2 = MOVE_STRENGTH; move3 = MOVE_GUST; move4 = MOVE_SCRATCH; hp = 5; expectedMove = MOVE_GUST; turns = 1; } + // Mega Kick and Slam both have lower accuracy. Gust and Scratch both have 100, so AI can choose either of them. + PARAMETRIZE { move1 = MOVE_MEGA_KICK; move2 = MOVE_SLAM; move3 = MOVE_SCRATCH; move4 = MOVE_GUST; hp = 5; expectedMove = MOVE_GUST; expectedMove2 = MOVE_SCRATCH; turns = 1; } + // All moves hit with No guard ability + PARAMETRIZE { move1 = MOVE_MEGA_KICK; move2 = MOVE_GUST; hp = 5; expectedMove = MOVE_MEGA_KICK; expectedMove2 = MOVE_GUST; turns = 1; } + // Tests to compare move that always hits and a beneficial effect. A move with higher acc should be chosen in this case. + PARAMETRIZE { move1 = MOVE_SHOCK_WAVE; move2 = MOVE_ICY_WIND; hp = 5; expectedMove = MOVE_SHOCK_WAVE; turns = 1; } + PARAMETRIZE { move1 = MOVE_SHOCK_WAVE; move2 = MOVE_ICY_WIND; move3 = MOVE_THUNDERBOLT; hp = 5; expectedMove = MOVE_SHOCK_WAVE; expectedMove2 = MOVE_THUNDERBOLT; turns = 1; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { HP(hp); } + PLAYER(SPECIES_WOBBUFFET); + ASSUME(GetMoveAccuracy(MOVE_SWIFT) == 0); + ASSUME(GetMovePower(MOVE_SLAM) == GetMovePower(MOVE_STRENGTH)); + ASSUME(GetMovePower(MOVE_MEGA_KICK) > GetMovePower(MOVE_STRENGTH)); + ASSUME(GetMoveAccuracy(MOVE_SLAM) < GetMoveAccuracy(MOVE_STRENGTH)); + ASSUME(GetMoveAccuracy(MOVE_MEGA_KICK) < GetMoveAccuracy(MOVE_STRENGTH)); + ASSUME(GetMoveAccuracy(MOVE_SCRATCH) == 100); + ASSUME(GetMoveAccuracy(MOVE_GUST) == 100); + ASSUME(GetMoveAccuracy(MOVE_SHOCK_WAVE) == 0); + ASSUME(GetMoveAccuracy(MOVE_THUNDERBOLT) == 100); + ASSUME(GetMoveAccuracy(MOVE_ICY_WIND) != 100); + ASSUME(GetMoveCategory(MOVE_SLAM) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_STRENGTH) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_MEGA_KICK) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_SWIFT) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMoveCategory(MOVE_SHOCK_WAVE) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMoveCategory(MOVE_ICY_WIND) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMoveCategory(MOVE_THUNDERBOLT) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMoveCategory(MOVE_GUST) == DAMAGE_CATEGORY_SPECIAL); + OPPONENT(SPECIES_EXPLOUD) { Moves(move1, move2, move3, move4); Ability(ABILITY_SCRAPPY); Innates(abilityAtk); SpAttack(50); } // Low Sp.Atk, so Swift deals less damage than Strength. + } WHEN { + switch (turns) + { + case 1: + if (expectedMove2 != MOVE_NONE) { + TURN { EXPECT_MOVES(opponent, expectedMove, expectedMove2); SEND_OUT(player, 1); } + } + else { + TURN { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); } + } + break; + case 2: + TURN { EXPECT_MOVE(opponent, expectedMove); } + TURN { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); } + break; + case 3: + TURN { EXPECT_MOVE(opponent, expectedMove); } + TURN { EXPECT_MOVE(opponent, expectedMove); } + TURN { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); } + break; + case 4: + TURN { EXPECT_MOVE(opponent, expectedMove); } + TURN { EXPECT_MOVE(opponent, expectedMove); } + TURN { EXPECT_MOVE(opponent, expectedMove); } + TURN { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); } + break; + } + } SCENE { + MESSAGE("Wobbuffet fainted!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI prefers moves which deal more damage instead of moves which are super-effective but deal less damage (Traits)") +{ + u8 turns = 0; + u16 move1 = MOVE_NONE, move2 = MOVE_NONE, move3 = MOVE_NONE, move4 = MOVE_NONE; + u16 expectedMove; + enum Ability abilityAtk, abilityDef; + + abilityAtk = ABILITY_NONE; + + // Scald and Poison Jab take 3 hits, Waterfall takes 2. + PARAMETRIZE { move1 = MOVE_WATERFALL; move2 = MOVE_SCALD; move3 = MOVE_POISON_JAB; move4 = MOVE_WATER_GUN; expectedMove = MOVE_WATERFALL; turns = 2; } + // Poison Jab takes 3 hits, Water gun 5. Immunity so there's no poison chip damage. + PARAMETRIZE { move1 = MOVE_POISON_JAB; move2 = MOVE_WATER_GUN; expectedMove = MOVE_POISON_JAB; abilityDef = ABILITY_IMMUNITY; turns = 3; } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_WATERFALL) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_SCALD) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMoveCategory(MOVE_POISON_JAB) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_WATER_GUN) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetSpeciesBaseAttack(SPECIES_NIDOQUEEN) == 92); // Gen 5's 82 Base Attack causes the test to fail + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_TYPHLOSION) { Ability(ABILITY_BLAZE); Innates(abilityDef); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_NIDOQUEEN) { Moves(move1, move2, move3, move4); Ability(abilityAtk); } + } WHEN { + switch (turns) + { + case 2: + TURN { EXPECT_MOVE(opponent, expectedMove); } + TURN { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); } + break; + case 3: + TURN { EXPECT_MOVE(opponent, expectedMove); } + TURN { EXPECT_MOVE(opponent, expectedMove); } + TURN { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); } + break; + } + } SCENE { + MESSAGE("Typhlosion fainted!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI chooses the safest option to faint the target, taking into account accuracy and move effect (Traits)") +{ + u16 move1 = MOVE_NONE, move2 = MOVE_NONE, move3 = MOVE_NONE, move4 = MOVE_NONE; + u16 expectedMove, expectedMove2 = MOVE_NONE; + enum Ability abilityAtk = ABILITY_NONE; + u32 holdItemAtk = ITEM_NONE; + + // Psychic is not very effective, but always hits. Solarbeam requires a charging turn, Double Edge has recoil and Focus Blast can miss; + PARAMETRIZE { abilityAtk = ABILITY_STURDY; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SOLAR_BEAM; move3 = MOVE_PSYCHIC; move4 = MOVE_DOUBLE_EDGE; expectedMove = MOVE_PSYCHIC; } + // Same as above, but ai mon has rock head ability, so it can use Double Edge without taking recoil damage. Psychic can also lower Special Defense, + // but because it faints the target it doesn't matter. + PARAMETRIZE { abilityAtk = ABILITY_ROCK_HEAD; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SOLAR_BEAM; move3 = MOVE_PSYCHIC; move4 = MOVE_DOUBLE_EDGE; + expectedMove = MOVE_PSYCHIC; expectedMove2 = MOVE_DOUBLE_EDGE; } + // This time it's Solarbeam + Psychic, because the weather is sunny. + PARAMETRIZE { abilityAtk = ABILITY_DROUGHT; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SOLAR_BEAM; move3 = MOVE_PSYCHIC; move4 = MOVE_DOUBLE_EDGE; + expectedMove = MOVE_PSYCHIC; expectedMove2 = MOVE_SOLAR_BEAM; } + // Psychic and Solar Beam are chosen because user is holding Power Herb + PARAMETRIZE { abilityAtk = ABILITY_STURDY; holdItemAtk = ITEM_POWER_HERB; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SOLAR_BEAM; move3 = MOVE_PSYCHIC; move4 = MOVE_DOUBLE_EDGE; + expectedMove = MOVE_PSYCHIC; expectedMove2 = MOVE_SOLAR_BEAM; } + // Skull Bash is chosen because it's the most accurate and is holding Power Herb + PARAMETRIZE { abilityAtk = ABILITY_STURDY; holdItemAtk = ITEM_POWER_HERB; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SKULL_BASH; move3 = MOVE_SLAM; move4 = MOVE_DOUBLE_EDGE; + expectedMove = MOVE_SKULL_BASH; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { HP(5); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GEODUDE) { Moves(move1, move2, move3, move4); Ability(ABILITY_SAND_VEIL); Innates(abilityAtk); Item(holdItemAtk); } + } WHEN { + TURN { if (expectedMove2 == MOVE_NONE) { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); } + else {EXPECT_MOVES(opponent, expectedMove, expectedMove2); SCORE_EQ(opponent, expectedMove, expectedMove2); SEND_OUT(player, 1);} + } + } + SCENE { + MESSAGE("Wobbuffet fainted!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI chooses the safest option to faint the target, taking into account accuracy and move effect failing (Traits)") +{ + u16 move1 = MOVE_NONE, move2 = MOVE_NONE, move3 = MOVE_NONE, move4 = MOVE_NONE; + u16 expectedMove, expectedMove2 = MOVE_NONE; + enum Ability abilityAtk = ABILITY_NONE; + u32 holdItemAtk = ITEM_NONE; + + // Fiery Dance and Skull Bash are chosen because user is holding Power Herb + PARAMETRIZE { abilityAtk = ABILITY_STURDY; holdItemAtk = ITEM_POWER_HERB; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SKULL_BASH; move3 = MOVE_FIERY_DANCE; move4 = MOVE_DOUBLE_EDGE; + expectedMove = MOVE_FIERY_DANCE; expectedMove2 = MOVE_SKULL_BASH; } + // Crabhammer is chosen even if Skull Bash is more accurate, the user has no Power Herb + PARAMETRIZE { abilityAtk = ABILITY_STURDY; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SKULL_BASH; move3 = MOVE_SLAM; move4 = MOVE_CRABHAMMER; + expectedMove = MOVE_CRABHAMMER; } + + KNOWN_FAILING; + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { HP(5); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GEODUDE) { Moves(move1, move2, move3, move4); Ability(ABILITY_SAND_VEIL); Innates(abilityAtk); Item(holdItemAtk); } + } WHEN { + TURN { if (expectedMove2 == MOVE_NONE) { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); } + else {EXPECT_MOVES(opponent, expectedMove, expectedMove2); SCORE_EQ(opponent, expectedMove, expectedMove2); SEND_OUT(player, 1);} + } + } + SCENE { + MESSAGE("Wobbuffet fainted!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI won't use Solar Beam if there is no Sun up or the user is not holding Power Herb (Traits)") +{ + enum Ability abilityAtk = ABILITY_NONE; + u16 holdItemAtk = ITEM_NONE; + + PARAMETRIZE { abilityAtk = ABILITY_DROUGHT; } + PARAMETRIZE { holdItemAtk = ITEM_POWER_HERB; } + PARAMETRIZE { } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_SOLAR_BEAM) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMoveCategory(MOVE_GRASS_PLEDGE) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMovePower(MOVE_GRASS_PLEDGE) == 80); // Gen 5's 50 power causes the test to fail + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { HP(211); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TYPHLOSION) { Moves(MOVE_SOLAR_BEAM, MOVE_GRASS_PLEDGE); Ability(ABILITY_BLAZE); Innates(abilityAtk); Item(holdItemAtk); } + } WHEN { + if (abilityAtk == ABILITY_DROUGHT) { + TURN { EXPECT_MOVES(opponent, MOVE_SOLAR_BEAM, MOVE_GRASS_PLEDGE); } + TURN { EXPECT_MOVES(opponent, MOVE_SOLAR_BEAM, MOVE_GRASS_PLEDGE); SEND_OUT(player, 1); } + } else if (holdItemAtk == ITEM_POWER_HERB) { + TURN { EXPECT_MOVES(opponent, MOVE_SOLAR_BEAM, MOVE_GRASS_PLEDGE); MOVE(player, MOVE_KNOCK_OFF); } + TURN { EXPECT_MOVE(opponent, MOVE_GRASS_PLEDGE); SEND_OUT(player, 1); } + } else { + TURN { EXPECT_MOVE(opponent, MOVE_GRASS_PLEDGE); } + TURN { EXPECT_MOVE(opponent, MOVE_GRASS_PLEDGE); SEND_OUT(player, 1); } + } + } SCENE { + MESSAGE("Wobbuffet fainted!"); + } +} + +AI_SINGLE_BATTLE_TEST("First Impression is not chosen if it's blocked by certain abilities (Traits)") +{ + u16 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_BRUXISH; ability = ABILITY_DAZZLING; } + PARAMETRIZE { species = SPECIES_FARIGIRAF; ability = ABILITY_ARMOR_TAIL; } + PARAMETRIZE { species = SPECIES_TSAREENA; ability = ABILITY_QUEENLY_MAJESTY; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_FIRST_IMPRESSION) == EFFECT_FIRST_TURN_ONLY); + ASSUME(GetMovePower(MOVE_FIRST_IMPRESSION) == 90); + ASSUME(GetMovePower(MOVE_LUNGE) == 80); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_FIRST_IMPRESSION, MOVE_LUNGE); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_LUNGE); } + } +} + +// Prediction flags pull from natural Abilities, these tests are basically Ability only +TO_DO_BATTLE_TEST("AI will only choose Surf 1/3 times if the opposing mon has Volt Absorb (Traits)") +TO_DO_BATTLE_TEST("AI will choose Thunderbolt then Surf 2/3 times if the opposing mon has Volt Absorb (Traits)") + +AI_SINGLE_BATTLE_TEST("AI will choose Scratch over Power-up Punch with Contrary (Traits)") +{ + enum Ability ability; + + PARAMETRIZE {ability = ABILITY_SUCTION_CUPS; } + PARAMETRIZE {ability = ABILITY_CONTRARY; } + GIVEN { + ASSUME(GetMovePower(MOVE_SCRATCH) == 40); + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + ASSUME(GetMovePower(MOVE_POWER_UP_PUNCH) == 40); + ASSUME(GetMoveType(MOVE_POWER_UP_PUNCH) == TYPE_FIGHTING); + ASSUME(GetSpeciesType(SPECIES_SQUIRTLE, 0) == TYPE_WATER); + ASSUME(GetSpeciesType(SPECIES_SQUIRTLE, 1) == TYPE_WATER); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_SQUIRTLE) { }; + OPPONENT(SPECIES_MALAMAR) { Ability(ABILITY_SUCTION_CUPS); Innates(ability); Moves(MOVE_SCRATCH, MOVE_POWER_UP_PUNCH); } + } WHEN { + TURN { + if (ability != ABILITY_CONTRARY) + EXPECT_MOVE(opponent, MOVE_POWER_UP_PUNCH); + else + EXPECT_MOVE(opponent, MOVE_SCRATCH); + } + } +} + +AI_SINGLE_BATTLE_TEST("AI will choose Superpower over Outrage with Contrary (Traits)") +{ + enum Ability ability; + + PARAMETRIZE {ability = ABILITY_SUCTION_CUPS; } + PARAMETRIZE {ability = ABILITY_CONTRARY; } + GIVEN { + ASSUME(GetMovePower(MOVE_SUPERPOWER) == 120); + ASSUME(GetMoveType(MOVE_SUPERPOWER) == TYPE_FIGHTING); + ASSUME(GetMovePower(MOVE_OUTRAGE) == 120); + ASSUME(GetMoveType(MOVE_OUTRAGE) == TYPE_DRAGON); + ASSUME(GetSpeciesType(SPECIES_SQUIRTLE, 0) == TYPE_WATER); + ASSUME(GetSpeciesType(SPECIES_SQUIRTLE, 1) == TYPE_WATER); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_SQUIRTLE) { }; + OPPONENT(SPECIES_MALAMAR) { Ability(ABILITY_SUCTION_CUPS); Innates(ability); Moves(MOVE_OUTRAGE, MOVE_SUPERPOWER); } + } WHEN { + TURN { + if (ability != ABILITY_CONTRARY) + EXPECT_MOVE(opponent, MOVE_OUTRAGE); + else + EXPECT_MOVE(opponent, MOVE_SUPERPOWER); + } + } +} + +AI_SINGLE_BATTLE_TEST("AI calculates guaranteed criticals and detects critical immunity (Traits)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_SWIFT_SWIM; } + PARAMETRIZE { ability = ABILITY_SHELL_ARMOR; } + + GIVEN { + ASSUME(MoveAlwaysCrits(MOVE_STORM_THROW)); + ASSUME(GetMovePower(MOVE_STORM_THROW) == 60); + ASSUME(GetMovePower(MOVE_BRICK_BREAK) == 75); + ASSUME(GetMoveType(MOVE_STORM_THROW) == GetMoveType(MOVE_BRICK_BREAK)); + ASSUME(GetMoveCategory(MOVE_STORM_THROW) == GetMoveCategory(MOVE_BRICK_BREAK)); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_OMASTAR) { Ability(ABILITY_WEAK_ARMOR); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_STORM_THROW, MOVE_BRICK_BREAK); } + } WHEN { + if (ability == ABILITY_SHELL_ARMOR) + TURN { EXPECT_MOVE(opponent, MOVE_BRICK_BREAK); } + else + TURN { EXPECT_MOVE(opponent, MOVE_STORM_THROW); } + } +} + +AI_SINGLE_BATTLE_TEST("AI uses a guaranteed KO move instead of the move with the highest expected damage (Traits)") +{ + u32 flags; + + PARAMETRIZE { flags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY; } + PARAMETRIZE { flags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT; } + + GIVEN { + ASSUME(GetMoveCriticalHitStage(MOVE_SLASH) == 1); + ASSUME(GetMovePower(MOVE_SLASH) == 70); + ASSUME(GetMovePower(MOVE_STRENGTH) == 80); + ASSUME(GetMoveType(MOVE_SLASH) == GetMoveType(MOVE_STRENGTH)); + ASSUME(GetMoveCategory(MOVE_SLASH) == GetMoveCategory(MOVE_STRENGTH)); + AI_FLAGS(flags); + PLAYER(SPECIES_WOBBUFFET) { HP(225); } + OPPONENT(SPECIES_ABSOL) { Ability(ABILITY_PRESSURE); Innates(ABILITY_SUPER_LUCK); Moves(MOVE_SLASH, MOVE_STRENGTH); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_SLASH); } + if (flags & AI_FLAG_TRY_TO_FAINT) + TURN { EXPECT_MOVE(opponent, MOVE_STRENGTH); } + else + TURN { EXPECT_MOVE(opponent, MOVE_SLASH); } + } SCENE { + if (flags & AI_FLAG_TRY_TO_FAINT) + MESSAGE("Wobbuffet fainted!"); + else + NOT MESSAGE("Wobbuffet fainted!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI stays choice locked into moves in spite of the player's ability disabling them (Traits)") +{ + u32 playerMon, aiMove; + enum Ability ability; + PARAMETRIZE { ability = ABILITY_DAZZLING; playerMon = SPECIES_BRUXISH; aiMove = MOVE_QUICK_ATTACK; } + PARAMETRIZE { ability = ABILITY_QUEENLY_MAJESTY; playerMon = SPECIES_TSAREENA; aiMove = MOVE_QUICK_ATTACK; } + PARAMETRIZE { ability = ABILITY_ARMOR_TAIL; playerMon = SPECIES_FARIGIRAF; aiMove = MOVE_QUICK_ATTACK; } + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; playerMon = SPECIES_EXPLOUD; aiMove = MOVE_BOOMBURST; } + PARAMETRIZE { ability = ABILITY_BULLETPROOF; playerMon = SPECIES_CHESNAUGHT; aiMove = MOVE_BULLET_SEED; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_CHOICE_BAND].holdEffect == HOLD_EFFECT_CHOICE_BAND); + ASSUME(GetMovePriority(MOVE_QUICK_ATTACK) == 1); + ASSUME(IsSoundMove(MOVE_BOOMBURST)); + ASSUME(IsBallisticMove(MOVE_BULLET_SEED)); + ASSUME(GetMoveCategory(MOVE_TAIL_WHIP) == DAMAGE_CATEGORY_STATUS); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(playerMon) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_SMEARGLE) { Item(ITEM_CHOICE_BAND); Moves(aiMove, MOVE_SCRATCH); } + } WHEN { + TURN { SWITCH(player, 1); EXPECT_MOVE(opponent, aiMove); } + TURN { EXPECT_MOVE(opponent, aiMove); } + } +} + +AI_SINGLE_BATTLE_TEST("AI won't boost stats against opponent with Unaware") +{ + GIVEN { + MoveHasAdditionalEffectSelf(MOVE_SWORDS_DANCE, MOVE_EFFECT_ATK_PLUS_2); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY); + PLAYER(SPECIES_QUAGSIRE) { Ability(ABILITY_DAMP); Innates(ABILITY_UNAWARE); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_BODY_SLAM, MOVE_SWORDS_DANCE); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_BODY_SLAM); } + } +} + +AI_SINGLE_BATTLE_TEST("AI won't use status moves against opponents that would benefit (Traits)") +{ + u32 aiMove; + PARAMETRIZE { aiMove = MOVE_WILL_O_WISP; } + PARAMETRIZE { aiMove = MOVE_TOXIC; } + PARAMETRIZE { aiMove = MOVE_THUNDER_WAVE; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_WILL_O_WISP) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_WILL_O_WISP) == MOVE_EFFECT_BURN); + ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + ASSUME(GetMoveEffect(MOVE_THUNDER_WAVE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_THUNDER_WAVE) == MOVE_EFFECT_PARALYSIS); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_SWELLOW) { Ability(ABILITY_SCRAPPY); Innates(ABILITY_GUTS); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, aiMove); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_TACKLE); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI sees opposing drain ability (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_THUNDERBOLT) == TYPE_ELECTRIC); + ASSUME(GetMoveType(MOVE_RAZOR_LEAF) != TYPE_ELECTRIC); + ASSUME(GetMoveType(MOVE_METAL_CLAW) != TYPE_ELECTRIC); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_RAICHU) { Ability(ABILITY_STATIC); Innates(ABILITY_LIGHTNING_ROD); Moves(MOVE_CELEBRATE); } + PLAYER(SPECIES_KRABBY) { Ability(ABILITY_HYPER_CUTTER); Innates(ABILITY_VOLT_ABSORB); Moves(MOVE_CELEBRATE); } + OPPONENT(SPECIES_MAGNETON) { Moves(MOVE_THUNDERBOLT, MOVE_RAZOR_LEAF); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_THUNDERBOLT, MOVE_METAL_CLAW); } + } WHEN { + TURN { + NOT_EXPECT_MOVE(opponentLeft, MOVE_THUNDERBOLT); + NOT_EXPECT_MOVE(opponentRight, MOVE_THUNDERBOLT); } + } +} + +AI_SINGLE_BATTLE_TEST("AI will not set up Weather if it wont have any affect (Traits)") +{ + enum Ability ability; + + PARAMETRIZE { ability = ABILITY_CLOUD_NINE; } + PARAMETRIZE { ability = ABILITY_DAMP; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_RAIN_DANCE) == EFFECT_RAIN_DANCE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY); + PLAYER(SPECIES_GOLDUCK) { Ability(ABILITY_SWIFT_SWIM); Innates(ability); Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_KABUTOPS) { Ability(ABILITY_SHELL_ARMOR); Innates(ABILITY_SWIFT_SWIM); Moves(MOVE_RAIN_DANCE, MOVE_POUND); } + } WHEN { + if (ability == ABILITY_CLOUD_NINE) + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_POUND); } + else + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_RAIN_DANCE); } + } +} + +AI_SINGLE_BATTLE_TEST("AI won't setup if it can KO through Sturdy effect (Traits)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_SKARMORY) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_STURDY); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_MOLTRES) { Moves(MOVE_FIRE_BLAST, MOVE_AGILITY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_FIRE_BLAST); } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +AI_SINGLE_BATTLE_TEST("AI chooses the safest option to faint the target, taking into account accuracy and move effect (Multi)") +{ + u16 move1 = MOVE_NONE, move2 = MOVE_NONE, move3 = MOVE_NONE, move4 = MOVE_NONE; + u16 expectedMove, expectedMove2 = MOVE_NONE; + enum Ability abilityAtk = ABILITY_NONE; + u32 holdItemAtk = ITEM_NONE; + + // Psychic is not very effective, but always hits. Solarbeam requires a charging turn, Double Edge has recoil and Focus Blast can miss; + PARAMETRIZE { abilityAtk = ABILITY_STURDY; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SOLAR_BEAM; move3 = MOVE_PSYCHIC; move4 = MOVE_DOUBLE_EDGE; expectedMove = MOVE_PSYCHIC; } + // Same as above, but ai mon has rock head ability, so it can use Double Edge without taking recoil damage. Psychic can also lower Special Defense, + // but because it faints the target it doesn't matter. + PARAMETRIZE { abilityAtk = ABILITY_ROCK_HEAD; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SOLAR_BEAM; move3 = MOVE_PSYCHIC; move4 = MOVE_DOUBLE_EDGE; + expectedMove = MOVE_PSYCHIC; expectedMove2 = MOVE_DOUBLE_EDGE; } + // This time it's Solarbeam + Psychic, because the weather is sunny. + PARAMETRIZE { abilityAtk = ABILITY_DROUGHT; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SOLAR_BEAM; move3 = MOVE_PSYCHIC; move4 = MOVE_DOUBLE_EDGE; + expectedMove = MOVE_PSYCHIC; expectedMove2 = MOVE_SOLAR_BEAM; } + // Psychic and Solar Beam are chosen because user is holding Power Herb + PARAMETRIZE { abilityAtk = ABILITY_STURDY; holdItemAtk = ITEM_POWER_HERB; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SOLAR_BEAM; move3 = MOVE_PSYCHIC; move4 = MOVE_DOUBLE_EDGE; + expectedMove = MOVE_PSYCHIC; expectedMove2 = MOVE_SOLAR_BEAM; } + // Skull Bash is chosen because it's the most accurate and is holding Power Herb + PARAMETRIZE { abilityAtk = ABILITY_STURDY; holdItemAtk = ITEM_POWER_HERB; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SKULL_BASH; move3 = MOVE_SLAM; move4 = MOVE_DOUBLE_EDGE; + expectedMove = MOVE_SKULL_BASH; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { HP(5); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GEODUDE) { Moves(move1, move2, move3, move4); Ability(abilityAtk); Items(ITEM_PECHA_BERRY, holdItemAtk); } + } WHEN { + TURN { if (expectedMove2 == MOVE_NONE) { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); } + else {EXPECT_MOVES(opponent, expectedMove, expectedMove2); SCORE_EQ(opponent, expectedMove, expectedMove2); SEND_OUT(player, 1);} + } + } + SCENE { + MESSAGE("Wobbuffet fainted!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI chooses the safest option to faint the target, taking into account accuracy and move effect failing (Multi)") +{ + u16 move1 = MOVE_NONE, move2 = MOVE_NONE, move3 = MOVE_NONE, move4 = MOVE_NONE; + u16 expectedMove, expectedMove2 = MOVE_NONE; + enum Ability abilityAtk = ABILITY_NONE; + u32 holdItemAtk = ITEM_NONE; + + // Fiery Dance and Skull Bash are chosen because user is holding Power Herb + PARAMETRIZE { abilityAtk = ABILITY_STURDY; holdItemAtk = ITEM_POWER_HERB; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SKULL_BASH; move3 = MOVE_FIERY_DANCE; move4 = MOVE_DOUBLE_EDGE; + expectedMove = MOVE_FIERY_DANCE; expectedMove2 = MOVE_SKULL_BASH; } + // Crabhammer is chosen even if Skull Bash is more accurate, the user has no Power Herb + PARAMETRIZE { abilityAtk = ABILITY_STURDY; move1 = MOVE_FOCUS_BLAST; move2 = MOVE_SKULL_BASH; move3 = MOVE_SLAM; move4 = MOVE_CRABHAMMER; + expectedMove = MOVE_CRABHAMMER; } + + KNOWN_FAILING; + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { HP(5); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GEODUDE) { Moves(move1, move2, move3, move4); Ability(abilityAtk); Items(ITEM_PECHA_BERRY, holdItemAtk); } + } WHEN { + TURN { if (expectedMove2 == MOVE_NONE) { EXPECT_MOVE(opponent, expectedMove); SEND_OUT(player, 1); } + else {EXPECT_MOVES(opponent, expectedMove, expectedMove2); SCORE_EQ(opponent, expectedMove, expectedMove2); SEND_OUT(player, 1);} + } + } + SCENE { + MESSAGE("Wobbuffet fainted!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI won't use Solar Beam if there is no Sun up or the user is not holding Power Herb (Multi)") +{ + enum Ability abilityAtk = ABILITY_NONE; + u16 holdItemAtk = ITEM_NONE; + + PARAMETRIZE { abilityAtk = ABILITY_DROUGHT; } + PARAMETRIZE { holdItemAtk = ITEM_POWER_HERB; } + PARAMETRIZE { } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_SOLAR_BEAM) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMoveCategory(MOVE_GRASS_PLEDGE) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMovePower(MOVE_GRASS_PLEDGE) == 80); // Gen 5's 50 power causes the test to fail + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { HP(211); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TYPHLOSION) { Moves(MOVE_SOLAR_BEAM, MOVE_GRASS_PLEDGE); Ability(abilityAtk); Items(ITEM_PECHA_BERRY, holdItemAtk); } + } WHEN { + if (abilityAtk == ABILITY_DROUGHT) { + TURN { EXPECT_MOVES(opponent, MOVE_SOLAR_BEAM, MOVE_GRASS_PLEDGE); } + TURN { EXPECT_MOVES(opponent, MOVE_SOLAR_BEAM, MOVE_GRASS_PLEDGE); SEND_OUT(player, 1); } + } else if (holdItemAtk == ITEM_POWER_HERB) { + TURN { EXPECT_MOVES(opponent, MOVE_SOLAR_BEAM, MOVE_GRASS_PLEDGE); MOVE(player, MOVE_KNOCK_OFF); } + TURN { EXPECT_MOVE(opponent, MOVE_GRASS_PLEDGE); SEND_OUT(player, 1); } + } else { + TURN { EXPECT_MOVE(opponent, MOVE_GRASS_PLEDGE); } + TURN { EXPECT_MOVE(opponent, MOVE_GRASS_PLEDGE); SEND_OUT(player, 1); } + } + } SCENE { + MESSAGE("Wobbuffet fainted!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI avoids contact moves against rocky helmet (Multi)") +{ + u32 item; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_ROCKY_HELMET; } + + GIVEN { + ASSUME(MoveMakesContact(MOVE_BRANCH_POKE)); + ASSUME(!MoveMakesContact(MOVE_LEAFAGE)); + ASSUME(GetMovePower(MOVE_BRANCH_POKE) == GetMovePower(MOVE_LEAFAGE)); + ASSUME(GetMoveType(MOVE_BRANCH_POKE) == GetMoveType(MOVE_LEAFAGE)); + ASSUME(GetMoveCategory(MOVE_BRANCH_POKE) == GetMoveCategory(MOVE_LEAFAGE)); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_BRANCH_POKE, MOVE_LEAFAGE); } + } WHEN { + if (item == ITEM_ROCKY_HELMET) + TURN { EXPECT_MOVE(opponent, MOVE_LEAFAGE); } + else + TURN { EXPECT_MOVES(opponent, MOVE_LEAFAGE, MOVE_BRANCH_POKE); } + } +} + +AI_SINGLE_BATTLE_TEST("AI stays choice locked into moves in spite of the player's ability disabling them (Multi)") +{ + u32 playerMon, aiMove; + enum Ability ability; + PARAMETRIZE { ability = ABILITY_DAZZLING; playerMon = SPECIES_BRUXISH; aiMove = MOVE_QUICK_ATTACK; } + PARAMETRIZE { ability = ABILITY_QUEENLY_MAJESTY; playerMon = SPECIES_TSAREENA; aiMove = MOVE_QUICK_ATTACK; } + PARAMETRIZE { ability = ABILITY_ARMOR_TAIL; playerMon = SPECIES_FARIGIRAF; aiMove = MOVE_QUICK_ATTACK; } + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; playerMon = SPECIES_EXPLOUD; aiMove = MOVE_BOOMBURST; } + PARAMETRIZE { ability = ABILITY_BULLETPROOF; playerMon = SPECIES_CHESNAUGHT; aiMove = MOVE_BULLET_SEED; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_CHOICE_BAND].holdEffect == HOLD_EFFECT_CHOICE_BAND); + ASSUME(GetMovePriority(MOVE_QUICK_ATTACK) == 1); + ASSUME(IsSoundMove(MOVE_BOOMBURST)); + ASSUME(IsBallisticMove(MOVE_BULLET_SEED)); + ASSUME(GetMoveCategory(MOVE_TAIL_WHIP) == DAMAGE_CATEGORY_STATUS); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(playerMon) { Ability(ability); } + OPPONENT(SPECIES_SMEARGLE) { Items(ITEM_PECHA_BERRY, ITEM_CHOICE_BAND); Moves(aiMove, MOVE_SCRATCH); } + } WHEN { + TURN { SWITCH(player, 1); EXPECT_MOVE(opponent, aiMove); } + TURN { EXPECT_MOVE(opponent, aiMove); } + } +} + +AI_SINGLE_BATTLE_TEST("AI score for Mean Look will be decreased if target can escape (Multi)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_BULBASAUR) { Items(ITEM_PECHA_BERRY, ITEM_SHED_SHELL); } + OPPONENT(SPECIES_BULBASAUR) { Moves(MOVE_TACKLE, MOVE_MEAN_LOOK); } + } WHEN { + TURN { SCORE_EQ_VAL(opponent, MOVE_MEAN_LOOK, 90); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI considers Focus Sash when determining if it should switch out (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_FOCUS_SASH].holdEffect == HOLD_EFFECT_FOCUS_SASH); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_BEAUTIFLY) { Speed(10); Moves(MOVE_AIR_SLASH); } + OPPONENT(SPECIES_CACNEA) { Speed(1); Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_COMBUSKEN) { Speed(1); Moves(MOVE_FLAMETHROWER); Items(ITEM_PECHA_BERRY, ITEM_FOCUS_SASH); } + OPPONENT(SPECIES_CROBAT) { Speed(11); Moves(MOVE_SLUDGE); } + } WHEN { + TURN { MOVE(player, MOVE_AIR_SLASH); EXPECT_MOVE(opponent, MOVE_SCRATCH); EXPECT_SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_AIR_SLASH); EXPECT_MOVE(opponent, MOVE_FLAMETHROWER); } + } +} + +AI_SINGLE_BATTLE_TEST("AI sees popped Air Balloon (Multi)") +{ + GIVEN { + ASSUME(GetItemHoldEffect(ITEM_AIR_BALLOON) == HOLD_EFFECT_AIR_BALLOON); + ASSUME(GetMoveType(MOVE_EARTHQUAKE) == TYPE_GROUND); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_TORCHIC) { Items(ITEM_PECHA_BERRY, ITEM_AIR_BALLOON); Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_GEODUDE) { Moves(MOVE_SCRATCH, MOVE_EARTHQUAKE); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_EARTHQUAKE); } + } +} + +AI_SINGLE_BATTLE_TEST("AI sees popped Air Balloon after Air Balloon mon switches out and back in (Multi)") +{ + GIVEN { + ASSUME(GetItemHoldEffect(ITEM_AIR_BALLOON) == HOLD_EFFECT_AIR_BALLOON); + ASSUME(GetMoveType(MOVE_EARTHQUAKE) == TYPE_GROUND); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_TORCHIC) { Items(ITEM_PECHA_BERRY, ITEM_AIR_BALLOON); Moves(MOVE_SCRATCH); } + PLAYER(SPECIES_TORCHIC) { Items(ITEM_PECHA_BERRY, ITEM_AIR_BALLOON); Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_GEODUDE) { Moves(MOVE_SCRATCH, MOVE_EARTHQUAKE); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_SCRATCH); } + TURN { SWITCH(player, 1); EXPECT_MOVE(opponent, MOVE_EARTHQUAKE); } + TURN { SWITCH(player, 0); EXPECT_MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_EARTHQUAKE); SEND_OUT(player, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI sees that Primal weather can block a move by type (Multi)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_HYDRO_PUMP) == TYPE_WATER); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_BLASTOISE) { Moves(MOVE_HYDRO_PUMP, MOVE_POUND); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_POUND); } + } +} +#endif diff --git a/test/battle/ai/ai_calc_best_move_score.c b/test/battle/ai/ai_calc_best_move_score.c index 715d30c1f585..9c5d6bbb03e7 100644 --- a/test/battle/ai/ai_calc_best_move_score.c +++ b/test/battle/ai/ai_calc_best_move_score.c @@ -146,3 +146,39 @@ AI_SINGLE_BATTLE_TEST("AI_IsMoveEffectInPlus - AI should not see secondary effec TURN { MOVE(player, MOVE_GYRO_BALL); SCORE_EQ_VAL(opponent, MOVE_PSYCHIC, 101); SCORE_EQ_VAL(opponent, MOVE_NIGHT_SHADE, 101); } } } + +#if MAX_MON_ITEMS > 1 +AI_SINGLE_BATTLE_TEST("HasMoveThatChangesKOThreshold - AI should not see self-targeted speed drops as preventing setup moves in 2hko cases (Multi)") +{ + u16 move; + PARAMETRIZE { move = MOVE_EARTHQUAKE; } + PARAMETRIZE { move = MOVE_BULLDOZE; } + GIVEN { + ASSUME(MoveHasAdditionalEffectSelf(MOVE_HAMMER_ARM, MOVE_EFFECT_SPD_MINUS_1) == TRUE); + ASSUME(MoveHasAdditionalEffect(MOVE_BULLDOZE, MOVE_EFFECT_SPD_MINUS_1) == TRUE); + ASSUME(GetMoveEffect(MOVE_NASTY_PLOT) == EFFECT_SPECIAL_ATTACK_UP_2); + ASSUME(GetMovePower(MOVE_EARTHQUAKE) == 100); + ASSUME(GetMovePower(MOVE_HAMMER_ARM) == 100); + ASSUME(GetMovePower(MOVE_BULLDOZE) == 60); + ASSUME(GetMovePower(MOVE_AURA_SPHERE) == 80); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_RHYDON) { Level(100); Nature(NATURE_ADAMANT); Items(ITEM_PECHA_BERRY, ITEM_EVIOLITE); Speed(1); Ability(ABILITY_LIGHTNING_ROD); Moves(MOVE_HAMMER_ARM, move); } + OPPONENT(SPECIES_GRIMMSNARL) { Level(100); Nature(NATURE_JOLLY); Ability(ABILITY_INFILTRATOR); Speed(2); HP(300); Moves(MOVE_NASTY_PLOT, MOVE_AURA_SPHERE); } + } WHEN { + TURN { MOVE(player, MOVE_HAMMER_ARM); EXPECT_MOVE(opponent, move == MOVE_EARTHQUAKE ? MOVE_NASTY_PLOT : MOVE_AURA_SPHERE); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_IsMoveEffectInPlus - AI should not see secondary effect of Sheer Force boosted moves as beneficial (Multi)") +{ + GIVEN { + ASSUME(GetMovePower(MOVE_PSYCHIC) == 90); + ASSUME(MoveHasAdditionalEffect(MOVE_PSYCHIC, MOVE_EFFECT_SP_DEF_MINUS_1) == TRUE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_STEELIX) { Level(100); Nature(NATURE_SASSY); Items(ITEM_PECHA_BERRY, ITEM_STEELIXITE); Ability(ABILITY_STURDY); Speed(58); Moves(MOVE_GYRO_BALL); } + OPPONENT(SPECIES_BRAVIARY_HISUI) { Level(100); Nature(NATURE_TIMID); Ability(ABILITY_SHEER_FORCE); Speed(251); Moves(MOVE_PSYCHIC, MOVE_NIGHT_SHADE); } + } WHEN { + TURN { MOVE(player, MOVE_GYRO_BALL); SCORE_EQ_VAL(opponent, MOVE_PSYCHIC, 101); SCORE_EQ_VAL(opponent, MOVE_NIGHT_SHADE, 101); } + } +} +#endif diff --git a/test/battle/ai/ai_check_viability.c b/test/battle/ai/ai_check_viability.c index f5bd39b27bec..edb82af62020 100644 --- a/test/battle/ai/ai_check_viability.c +++ b/test/battle/ai/ai_check_viability.c @@ -502,3 +502,129 @@ AI_SINGLE_BATTLE_TEST("AI uses Sparkling Aria to cure an enemy with Guts") TURN { EXPECT_MOVE(opponent, MOVE_SCALD); } } } + +#if MAX_MON_TRAITS > 1 +AI_SINGLE_BATTLE_TEST("AI chooses moves with secondary effect that have a 100% chance to trigger (Traits)") +{ + enum Ability ability; + + PARAMETRIZE { ability = ABILITY_NONE; } + PARAMETRIZE { ability = ABILITY_SERENE_GRACE; } + + GIVEN { + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_SHADOW_BALL, MOVE_EFFECT_SP_DEF_MINUS_1, 20)); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_OCTAZOOKA, MOVE_EFFECT_ACC_MINUS_1, 50)); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_REGICE); + OPPONENT(SPECIES_REGIROCK) { Ability(ABILITY_LIGHT_METAL); Innates(ability); Moves(MOVE_SHADOW_BALL, MOVE_OCTAZOOKA); } + } WHEN { + if (ability == ABILITY_NONE) + TURN { EXPECT_MOVE(opponent, MOVE_SHADOW_BALL); } + else + TURN { EXPECT_MOVES(opponent, MOVE_OCTAZOOKA); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI chooses moves that cure self or partner (Traits)") +{ + u32 status1_0, status1_1, partnerAbility, move; + + PARAMETRIZE { status1_0 = STATUS1_NONE; status1_1 = STATUS1_NONE; + move = MOVE_HEAL_BELL; partnerAbility = ABILITY_SCRAPPY; } + PARAMETRIZE { status1_0 = STATUS1_TOXIC_POISON; status1_1 = STATUS1_NONE; + move = MOVE_HEAL_BELL; partnerAbility = ABILITY_SCRAPPY; } + PARAMETRIZE { status1_0 = STATUS1_NONE; status1_1 = STATUS1_PARALYSIS; + move = MOVE_HEAL_BELL; partnerAbility = ABILITY_SCRAPPY; } + PARAMETRIZE { status1_0 = STATUS1_NONE; status1_1 = STATUS1_PARALYSIS; + move = MOVE_HEAL_BELL; partnerAbility = ABILITY_SOUNDPROOF; } + + PARAMETRIZE { status1_0 = STATUS1_NONE; status1_1 = STATUS1_NONE; + move = MOVE_REFRESH; partnerAbility = ABILITY_SCRAPPY; } + PARAMETRIZE { status1_0 = STATUS1_TOXIC_POISON; status1_1 = STATUS1_NONE; + move = MOVE_REFRESH; partnerAbility = ABILITY_SCRAPPY; } + + + GIVEN { + ASSUME(GetMoveEffect(MOVE_HEAL_BELL) == EFFECT_HEAL_BELL); + WITH_CONFIG(CONFIG_HEAL_BELL_SOUNDPROOF, GEN_8); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_REGIROCK) { Moves(MOVE_ROCK_SLIDE, move, MOVE_ACID); Status1(status1_0); } + OPPONENT(SPECIES_EXPLOUD) { Status1(status1_1); Ability(ABILITY_LIGHT_METAL); Innates(partnerAbility); } + } WHEN { + if (status1_0 != STATUS1_NONE || (status1_1 != STATUS1_NONE && partnerAbility != ABILITY_SOUNDPROOF)) + TURN { EXPECT_MOVE(opponentLeft, move); } + else + TURN { EXPECT_MOVE(opponentLeft, MOVE_ROCK_SLIDE); } + } +} + +TO_DO_BATTLE_TEST("AI chooses moves that cure inactive party members (Traits)") // TODO: Innate parameters on reserve pokemon in Tests + +AI_SINGLE_BATTLE_TEST("AI sees Shield Dust immunity to additional effects (Traits)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_SHIELD_DUST; } + PARAMETRIZE { ability = ABILITY_TINTED_LENS; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_VENOMOTH) { Ability(ABILITY_LIGHT_METAL); Innates(ability); Moves(MOVE_CELEBRATE, MOVE_POUND); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CHILLING_WATER, MOVE_BRINE); } + } WHEN { + if (ability == ABILITY_SHIELD_DUST) + TURN { EXPECT_MOVE(opponent, MOVE_BRINE); } + else + TURN { EXPECT_MOVE(opponent, MOVE_CHILLING_WATER); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI sees type-changing moves as the correct type (Traits)") +{ + u32 species, fieldStatus, ability; + u64 aiFlags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT; + + PARAMETRIZE { fieldStatus = MOVE_RAIN_DANCE; species = SPECIES_PRIMARINA; ability = ABILITY_NONE; } + PARAMETRIZE { fieldStatus = MOVE_RAIN_DANCE; species = SPECIES_PRIMARINA; ability = ABILITY_LIQUID_VOICE; } + PARAMETRIZE { fieldStatus = MOVE_ELECTRIC_TERRAIN; species = SPECIES_GEODUDE_ALOLA; ability = ABILITY_GALVANIZE; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + fieldStatus = MOVE_RAIN_DANCE; species = SPECIES_PRIMARINA; ability = ABILITY_NONE; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + fieldStatus = MOVE_RAIN_DANCE; species = SPECIES_PRIMARINA; ability = ABILITY_LIQUID_VOICE; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + fieldStatus = MOVE_ELECTRIC_TERRAIN; species = SPECIES_GEODUDE_ALOLA; ability = ABILITY_GALVANIZE; } + + GIVEN { + AI_FLAGS(aiFlags); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(fieldStatus, MOVE_RETURN, MOVE_TAUNT); } + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); Moves(MOVE_HYPER_VOICE); } + } WHEN { + if (ability != ABILITY_NONE) + TURN { EXPECT_MOVE(opponentLeft, fieldStatus); } + else + TURN { NOT_EXPECT_MOVE(opponentLeft, fieldStatus); } + } +} + +AI_SINGLE_BATTLE_TEST("AI uses Sparkling Aria to cure an enemy with Guts (Traits)") +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_GUTS; } + PARAMETRIZE { ability = ABILITY_BULLETPROOF; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_URSALUNA) { Ability(ABILITY_LIGHT_METAL); Innates(ability); Moves(MOVE_HEADLONG_RUSH, MOVE_CELEBRATE); Status1(STATUS1_BURN); } + OPPONENT(SPECIES_PRIMARINA) { Moves(MOVE_SPARKLING_ARIA, MOVE_SCALD); } + } WHEN { + if (ability == ABILITY_GUTS) + TURN { EXPECT_MOVE(opponent, MOVE_SPARKLING_ARIA); } + else + TURN { EXPECT_MOVE(opponent, MOVE_SCALD); } + } +} +#endif diff --git a/test/battle/ai/ai_choice.c b/test/battle/ai/ai_choice.c index e8d3a8e42a51..64dd39a10660 100644 --- a/test/battle/ai/ai_choice.c +++ b/test/battle/ai/ai_choice.c @@ -252,3 +252,250 @@ AI_SINGLE_BATTLE_TEST("Choiced Pokémon will only see choiced moves when conside TURN { MOVE(player, MOVE_WATER_GUN); EXPECT_SWITCH(opponent, 1); } } } + +#if MAX_MON_ITEMS > 1 +AI_SINGLE_BATTLE_TEST("Choiced Pokémon switch out after using a status move once (Multi)") +{ + u32 j, heldItem = ITEM_NONE; + enum Ability ability = ABILITY_NONE; + + static const u32 choiceItems[] = { + ITEM_CHOICE_SPECS, + ITEM_CHOICE_BAND, + ITEM_CHOICE_SCARF, + }; + + for (j = 0; j < ARRAY_COUNT(choiceItems); j++) + { + PARAMETRIZE { ability = ABILITY_NONE; heldItem = choiceItems[j]; } + PARAMETRIZE { ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; } + } + + PASSES_RANDOMLY(SHOULD_SWITCH_CHOICE_LOCKED_PERCENTAGE, 100, RNG_AI_SWITCH_CHOICE_LOCKED); + + GIVEN { + ASSUME(GetMoveCategory(MOVE_YAWN) == DAMAGE_CATEGORY_STATUS); + ASSUME(GetMoveEffect(MOVE_YAWN) == EFFECT_YAWN); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_RHYDON) + OPPONENT(SPECIES_LOPUNNY) { Moves(MOVE_YAWN, MOVE_SCRATCH); Items(ITEM_PECHA_BERRY, heldItem); Ability(ability); } + OPPONENT(SPECIES_SWAMPERT) { Moves(MOVE_WATERFALL); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_YAWN); } + if (ability == ABILITY_KLUTZ) { // Klutz ignores item + TURN { EXPECT_MOVE(opponent, MOVE_SCRATCH); } + } + else { + TURN { EXPECT_SWITCH(opponent, 1); } + } + } +} + +AI_SINGLE_BATTLE_TEST("Choiced Pokémon only consider their own status moves when determining if they should switch (Multi)") +{ + GIVEN + { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_RISKY | AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_ZIGZAGOON) { Speed(4); Moves(MOVE_TAIL_WHIP, MOVE_SCRATCH); } + OPPONENT(SPECIES_ZIGZAGOON) { Speed(5); Moves(MOVE_SCRATCH); Items(ITEM_PECHA_BERRY, ITEM_CHOICE_BAND); } + OPPONENT(SPECIES_ZIGZAGOON) { Speed(5); Moves(MOVE_SCRATCH); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_TAIL_WHIP); } + TURN { EXPECT_MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_TAIL_WHIP); } + } +} + +AI_SINGLE_BATTLE_TEST("Choiced Pokémon won't use stat boosting moves (Multi)") +{ + // Moves defined by MOVE_TARGET_USER (with exceptions?) + u32 j, heldItem = ITEM_NONE; + enum Ability ability = ABILITY_NONE; + + static const u32 choiceItems[] = { + ITEM_CHOICE_SPECS, + ITEM_CHOICE_BAND, + ITEM_CHOICE_SCARF, + }; + + for (j = 0; j < ARRAY_COUNT(choiceItems); j++) + { + PARAMETRIZE { ability = ABILITY_NONE; heldItem = choiceItems[j]; } + PARAMETRIZE { ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; } + } + + GIVEN { + ASSUME(GetMoveTarget(MOVE_SWORDS_DANCE) == MOVE_TARGET_USER); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_RHYDON) + OPPONENT(SPECIES_LOPUNNY) { Moves(MOVE_SWORDS_DANCE, MOVE_SCRATCH); Items(ITEM_PECHA_BERRY, heldItem); Ability(ability); } + OPPONENT(SPECIES_SWAMPERT) { Moves(MOVE_WATERFALL); } + } WHEN { + if (ability == ABILITY_KLUTZ) { // Klutz ignores item + TURN { EXPECT_MOVE(opponent, MOVE_SWORDS_DANCE); } + } + else { + TURN { EXPECT_MOVE(opponent, MOVE_SCRATCH); } + } + } +} + +AI_SINGLE_BATTLE_TEST("Choiced Pokémon won't use status move if they are the only party member (Multi)") +{ + u32 j, isAlive = 0, heldItem = ITEM_NONE; + enum Ability ability = ABILITY_NONE; + static const u32 choiceItems[] = { + ITEM_CHOICE_SPECS, + ITEM_CHOICE_BAND, + ITEM_CHOICE_SCARF, + }; + + for (j = 0; j < ARRAY_COUNT(choiceItems); j++) + { + PARAMETRIZE { ability = ABILITY_NONE; heldItem = choiceItems[j]; isAlive = 0; } + PARAMETRIZE { ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; isAlive = 0; } + PARAMETRIZE { ability = ABILITY_NONE; heldItem = choiceItems[j]; isAlive = 1; } + PARAMETRIZE { ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; isAlive = 1; } + } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_YAWN) == DAMAGE_CATEGORY_STATUS); + ASSUME(GetMoveEffect(MOVE_YAWN) == EFFECT_YAWN); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_RHYDON) + OPPONENT(SPECIES_LOPUNNY) { Moves(MOVE_YAWN, MOVE_SCRATCH); Items(ITEM_PECHA_BERRY, heldItem); Ability(ability); } + OPPONENT(SPECIES_SWAMPERT) { HP(isAlive); Moves(MOVE_WATERFALL); } + } WHEN { + if (isAlive == 1 || ability == ABILITY_KLUTZ) { + TURN { EXPECT_MOVE(opponent, MOVE_YAWN); } + } + else { + TURN { EXPECT_MOVE(opponent, MOVE_SCRATCH); } + } + } +} + +AI_SINGLE_BATTLE_TEST("Choiced Pokémon won't use status move if they don't have a good switchin (Multi)") +{ + u32 j, move = MOVE_NONE, species = SPECIES_NONE, heldItem = ITEM_NONE; + enum Ability ability = ABILITY_NONE; + static const u32 choiceItems[] = { + ITEM_CHOICE_SPECS, + ITEM_CHOICE_BAND, + ITEM_CHOICE_SCARF, + }; + + for (j = 0; j < ARRAY_COUNT(choiceItems); j++) + { + PARAMETRIZE { ability = ABILITY_NONE; heldItem = choiceItems[j]; species = SPECIES_SWAMPERT; move = MOVE_WATERFALL; } + PARAMETRIZE { ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; species = SPECIES_SWAMPERT; move = MOVE_WATERFALL; } + PARAMETRIZE { ability = ABILITY_NONE; heldItem = choiceItems[j]; species = SPECIES_ELEKID; move = MOVE_THUNDER_WAVE; } + PARAMETRIZE { ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; species = SPECIES_ELEKID; move = MOVE_THUNDER_WAVE; } + } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_YAWN) == DAMAGE_CATEGORY_STATUS); + ASSUME(GetMoveEffect(MOVE_YAWN) == EFFECT_YAWN); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_RHYDON) + OPPONENT(SPECIES_LOPUNNY) { Moves(MOVE_YAWN, MOVE_SCRATCH); Items(ITEM_PECHA_BERRY, heldItem); Ability(ability); } + OPPONENT(species) { Moves(move); } + } WHEN { + if (species == SPECIES_SWAMPERT || ability == ABILITY_KLUTZ) { + TURN { EXPECT_MOVE(opponent, MOVE_YAWN); } + } + else { + TURN { EXPECT_MOVE(opponent, MOVE_SCRATCH); } + } + } +} + +AI_SINGLE_BATTLE_TEST("Choiced Pokémon won't use status move if they are trapped (Multi)") +{ + u32 j, aiAbility = ABILITY_NONE, playerAbility = MOVE_NONE, species = SPECIES_NONE, heldItem = ITEM_NONE; + + static const u32 choiceItems[] = { + ITEM_CHOICE_SPECS, + ITEM_CHOICE_BAND, + ITEM_CHOICE_SCARF, + }; + + for (j = 0; j < ARRAY_COUNT(choiceItems); j++) + { + PARAMETRIZE { aiAbility = ABILITY_NONE; heldItem = choiceItems[j]; species = SPECIES_RHYDON; playerAbility = ABILITY_LIGHTNING_ROD; } + PARAMETRIZE { aiAbility = ABILITY_KLUTZ; heldItem = choiceItems[j]; species = SPECIES_RHYDON; playerAbility = ABILITY_LIGHTNING_ROD; } + PARAMETRIZE { aiAbility = ABILITY_NONE; heldItem = choiceItems[j]; species = SPECIES_DUGTRIO; playerAbility = ABILITY_ARENA_TRAP; } + PARAMETRIZE { aiAbility = ABILITY_KLUTZ; heldItem = choiceItems[j]; species = SPECIES_DUGTRIO; playerAbility = ABILITY_ARENA_TRAP; } + } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_YAWN) == DAMAGE_CATEGORY_STATUS); + ASSUME(GetMoveEffect(MOVE_YAWN) == EFFECT_YAWN); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(species) { Ability(playerAbility); } + OPPONENT(SPECIES_LOPUNNY) { Moves(MOVE_YAWN, MOVE_SCRATCH); Items(ITEM_PECHA_BERRY, heldItem); Ability(aiAbility); } + OPPONENT(SPECIES_SWAMPERT) { Moves(MOVE_WATERFALL); } + } WHEN { + if (playerAbility != ABILITY_ARENA_TRAP || aiAbility == ABILITY_KLUTZ) { + TURN { EXPECT_MOVE(opponent, MOVE_YAWN); } + } + else { + TURN { EXPECT_MOVE(opponent, MOVE_SCRATCH); } + } + } +} + +AI_SINGLE_BATTLE_TEST("Choiced Pokémon will switch if locked into a move the player is immune to (Multi)") +{ + GIVEN { + ASSUME(GetSpeciesType(SPECIES_GASTLY, 0) == TYPE_GHOST); + ASSUME(GetMoveType(MOVE_SURF) == TYPE_WATER); + ASSUME(GetMoveType(MOVE_BODY_SLAM) == TYPE_NORMAL); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_GASTLY) { Level(1); Moves(MOVE_CELEBRATE); } + PLAYER(SPECIES_VAPOREON) { Ability(ABILITY_WATER_ABSORB); Moves(MOVE_SURF); } + OPPONENT(SPECIES_ZIGZAGOON) { Items(ITEM_PECHA_BERRY, ITEM_CHOICE_BAND); Moves(MOVE_SURF, MOVE_BODY_SLAM); } + OPPONENT(SPECIES_ZIGZAGOON) { Items(ITEM_PECHA_BERRY, ITEM_CHOICE_BAND); Moves(MOVE_SURF, MOVE_BODY_SLAM); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); EXPECT_MOVE(opponent, MOVE_SURF); SEND_OUT(player, 1); } + TURN { MOVE(player, MOVE_SURF); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("Choiced Pokémon will only see choiced moves when considering switching with ShouldSwitchIfHasBadOdds (Multi)") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_HASBADODDS_PERCENTAGE, 100, RNG_AI_SWITCH_HASBADODDS); + GIVEN { + ASSUME(GetSpeciesType(SPECIES_GASTLY, 0) == TYPE_GHOST); + ASSUME(GetMoveType(MOVE_SURF) == TYPE_WATER); + ASSUME(GetMoveType(MOVE_BODY_SLAM) == TYPE_NORMAL); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_GASTLY) { Level(1); Moves(MOVE_CELEBRATE); } + PLAYER(SPECIES_ZIGZAGOON) { Items(ITEM_PECHA_BERRY, ITEM_CHOICE_BAND); Moves(MOVE_CLOSE_COMBAT); } + OPPONENT(SPECIES_ZIGZAGOON) { Items(ITEM_PECHA_BERRY, ITEM_CHOICE_BAND); Moves(MOVE_SURF, MOVE_CLOSE_COMBAT); } + OPPONENT(SPECIES_BRONZONG) { Moves(MOVE_CLOSE_COMBAT); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); EXPECT_MOVE(opponent, MOVE_SURF); SEND_OUT(player, 1); } + TURN { MOVE(player, MOVE_CLOSE_COMBAT); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("Choiced Pokémon will only see choiced moves when considering switching with FindMonThatAbsorbsMove (Multi)") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_ABSORBS_MOVE_PERCENTAGE, 100, RNG_AI_SWITCH_ABSORBING); + GIVEN { + ASSUME(GetSpeciesType(SPECIES_SANDSHREW, 0) == TYPE_GROUND); + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + ASSUME(GetMoveType(MOVE_THUNDERBOLT) == TYPE_ELECTRIC); + ASSUME(GetMoveType(MOVE_WATER_GUN) == TYPE_WATER); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_WOOPER) { Ability(ABILITY_WATER_ABSORB); Moves(MOVE_CELEBRATE); } + PLAYER(SPECIES_DWEBBLE) { Moves(MOVE_WATER_GUN); } + OPPONENT(SPECIES_MUDKIP) { Items(ITEM_PECHA_BERRY, ITEM_CHOICE_SCARF); Moves(MOVE_SCRATCH, MOVE_WATER_GUN); } + OPPONENT(SPECIES_WOOPER) { Ability(ABILITY_WATER_ABSORB); Moves(MOVE_SCRATCH); } + } WHEN { + TURN { SWITCH(player, 1); EXPECT_MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_WATER_GUN); EXPECT_MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_WATER_GUN); EXPECT_SWITCH(opponent, 1); } + } +} +#endif diff --git a/test/battle/ai/ai_doubles.c b/test/battle/ai/ai_doubles.c index 955c82b9f924..0cc428c3c3c0 100644 --- a/test/battle/ai/ai_doubles.c +++ b/test/battle/ai/ai_doubles.c @@ -906,3 +906,441 @@ AI_DOUBLE_BATTLE_TEST("AI uses Magnetic Flux") TURN { EXPECT_MOVE(opponentLeft, MOVE_MAGNETIC_FLUX); } } } + +#if MAX_MON_TRAITS > 1 +AI_DOUBLE_BATTLE_TEST("AI recognizes its ally's Telepathy (Traits)") +{ + ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY); + + GIVEN { + ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PHANPY) { Moves(MOVE_EARTHQUAKE, MOVE_SCRATCH); } + OPPONENT(SPECIES_ELGYEM) { Level(1); Ability(ABILITY_SYNCHRONIZE); Innates(ABILITY_TELEPATHY); } + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, MOVE_EARTHQUAKE); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI will choose Bulldoze if it triggers its ally's ability but will not KO the ally needlessly (Traits)") +{ + ASSUME(GetMoveTarget(MOVE_BULLDOZE) == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(GetMoveType(MOVE_BULLDOZE) == TYPE_GROUND); + ASSUME(MoveHasAdditionalEffect(MOVE_BULLDOZE, MOVE_EFFECT_SPD_MINUS_1)); + + u32 species, currentHP; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_KINGAMBIT; ability = ABILITY_DEFIANT; currentHP = 400; } + PARAMETRIZE { species = SPECIES_SHUCKLE; ability = ABILITY_CONTRARY; currentHP = 400; } + PARAMETRIZE { species = SPECIES_PAWNIARD; ability = ABILITY_PRESSURE; currentHP = 1; } + PARAMETRIZE { species = SPECIES_PAWNIARD; ability = ABILITY_DEFIANT; currentHP = 1; } + PARAMETRIZE { species = SPECIES_SHUCKLE; ability = ABILITY_CONTRARY; currentHP = 1; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PHANPY) { Moves(MOVE_BULLDOZE, MOVE_HIGH_HORSEPOWER); } + OPPONENT(species) { Moves(MOVE_CELEBRATE, MOVE_POUND); HP(currentHP); Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + if (currentHP != 1) + TURN { EXPECT_MOVE(opponentLeft, MOVE_BULLDOZE); } + else + TURN { EXPECT_MOVE(opponentLeft, MOVE_HIGH_HORSEPOWER); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI will choose Beat Up on an ally with Justified if it will benefit the ally (Traits)") +{ + ASSUME(GetMoveEffect(MOVE_BEAT_UP) == EFFECT_BEAT_UP); + ASSUME(GetMoveType(MOVE_BEAT_UP) == TYPE_DARK); + + enum Ability defAbility, atkAbility, currentHP; + + PARAMETRIZE { defAbility = ABILITY_FLASH_FIRE; atkAbility = ABILITY_SCRAPPY; currentHP = 400; } + PARAMETRIZE { defAbility = ABILITY_JUSTIFIED; atkAbility = ABILITY_SCRAPPY; currentHP = 400; } + PARAMETRIZE { defAbility = ABILITY_JUSTIFIED; atkAbility = ABILITY_MOLD_BREAKER; currentHP = 400; } + PARAMETRIZE { defAbility = ABILITY_JUSTIFIED; atkAbility = ABILITY_SCRAPPY; currentHP = 1; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_CLEFABLE); + OPPONENT(SPECIES_PANGORO) { Ability(atkAbility); Moves(MOVE_BEAT_UP); } + OPPONENT(SPECIES_GROWLITHE) { Moves(MOVE_CELEBRATE, MOVE_TACKLE); HP(currentHP); Ability(ABILITY_FLASH_FIRE); Innates(defAbility); } + } WHEN { + if (!(currentHP == 1) && (defAbility == ABILITY_JUSTIFIED) && (atkAbility != ABILITY_MOLD_BREAKER)) + TURN { EXPECT_MOVE(opponentLeft, MOVE_BEAT_UP, target: opponentRight); } + else + TURN { EXPECT_MOVE(opponentLeft, MOVE_BEAT_UP, target: playerLeft); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI sees corresponding absorbing abilities on partners (Traits)") +{ + ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(GetMoveType(MOVE_DISCHARGE) == TYPE_ELECTRIC); + ASSUME(GetMoveTarget(MOVE_LAVA_PLUME) == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(GetMoveType(MOVE_LAVA_PLUME) == TYPE_FIRE); + ASSUME(GetMoveTarget(MOVE_SURF) == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(GetMoveType(MOVE_SURF) == TYPE_WATER); + ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(GetMoveType(MOVE_EARTHQUAKE) == TYPE_GROUND); + + enum Ability ability; + u32 move, species; + + PARAMETRIZE { species = SPECIES_PSYDUCK; ability = ABILITY_CLOUD_NINE; move = MOVE_DISCHARGE; } + PARAMETRIZE { species = SPECIES_PIKACHU; ability = ABILITY_LIGHTNING_ROD; move = MOVE_DISCHARGE; } + PARAMETRIZE { species = SPECIES_LANTURN; ability = ABILITY_VOLT_ABSORB; move = MOVE_DISCHARGE; } + PARAMETRIZE { species = SPECIES_EMOLGA; ability = ABILITY_MOTOR_DRIVE; move = MOVE_DISCHARGE; } + PARAMETRIZE { species = SPECIES_SEAKING; ability = ABILITY_LIGHTNING_ROD; move = MOVE_DISCHARGE; } + PARAMETRIZE { species = SPECIES_GROWLITHE; ability = ABILITY_FLASH_FIRE; move = MOVE_LAVA_PLUME; } + PARAMETRIZE { species = SPECIES_DACHSBUN; ability = ABILITY_WELL_BAKED_BODY; move = MOVE_LAVA_PLUME; } + PARAMETRIZE { species = SPECIES_QUAGSIRE; ability = ABILITY_WATER_ABSORB; move = MOVE_SURF; } + PARAMETRIZE { species = SPECIES_SHELLOS; ability = ABILITY_STORM_DRAIN; move = MOVE_SURF; } + PARAMETRIZE { species = SPECIES_UNOWN; ability = ABILITY_LEVITATE; move = MOVE_EARTHQUAKE; } + PARAMETRIZE { species = SPECIES_ORTHWORM; ability = ABILITY_EARTH_EATER; move = MOVE_EARTHQUAKE; } + + GIVEN { + ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_HP_AWARE); + PLAYER(SPECIES_ZIGZAGOON); + PLAYER(SPECIES_ZIGZAGOON); + OPPONENT(SPECIES_SLAKING) { Moves(move, MOVE_SCRATCH); } + OPPONENT(species) { HP(1); Ability(ABILITY_LIGHT_METAL); Innates(ability); Moves(MOVE_POUND, MOVE_EMBER, MOVE_ROUND); } + } WHEN { + if (ability != ABILITY_CLOUD_NINE) + TURN { EXPECT_MOVE(opponentLeft, move); } + else + TURN { EXPECT_MOVE(opponentLeft, MOVE_SCRATCH); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI treats an ally's redirection ability appropriately (gen 4) (Traits)") +{ + KNOWN_FAILING; + ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(GetMoveType(MOVE_DISCHARGE) == TYPE_ELECTRIC); + ASSUME(GetMoveTarget(MOVE_SURF) == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(GetMoveType(MOVE_SURF) == TYPE_WATER); + + enum Ability ability; + u32 move, species; + + PARAMETRIZE { species = SPECIES_SEAKING; ability = ABILITY_LIGHTNING_ROD; move = MOVE_DISCHARGE; } + PARAMETRIZE { species = SPECIES_SHELLOS; ability = ABILITY_STORM_DRAIN; move = MOVE_SURF; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_HP_AWARE); + WITH_CONFIG(B_REDIRECT_ABILITY_IMMUNITY, GEN_4); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(move, MOVE_HEADBUTT); } + OPPONENT(species) { HP(1); Ability(ABILITY_LIGHT_METAL); Innates(ability); Moves(MOVE_ROUND); } + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, MOVE_HEADBUTT); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI treats an ally's redirection ability appropriately (gen 5+) (Traits)") +{ + ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(GetMoveType(MOVE_DISCHARGE) == TYPE_ELECTRIC); + ASSUME(GetMoveTarget(MOVE_SURF) == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(GetMoveType(MOVE_SURF) == TYPE_WATER); + + enum Ability ability; + u32 move, species; + + PARAMETRIZE { species = SPECIES_SEAKING; ability = ABILITY_LIGHTNING_ROD; move = MOVE_DISCHARGE; } + PARAMETRIZE { species = SPECIES_SHELLOS; ability = ABILITY_STORM_DRAIN; move = MOVE_SURF; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_HP_AWARE); + WITH_CONFIG(B_REDIRECT_ABILITY_IMMUNITY, GEN_5); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(move, MOVE_HEADBUTT); } + OPPONENT(species) { HP(1); Ability(ABILITY_LIGHT_METAL); Innates(ability); Moves(MOVE_ROUND); } + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, move); } + } +} + +// Sandstorm is omitted on purpose. +// Tornadus is currently not willing to set up Sandstorm for its ally, but the actual purpose of this test is to demonstrate that Tornadus or Whimsicott will perform standard VGC openers. +// Rain Dance, Sunny Day, and Snowscape are the actually important ones; setting up a good Sandstorm test + functionality is less important and will be done in later PRs. +AI_DOUBLE_BATTLE_TEST("AI sets up weather for its ally (Traits)") +{ + u32 goodWeather, badWeather, weatherTrigger; + u64 aiFlags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT; + + PARAMETRIZE { goodWeather = MOVE_SUNNY_DAY; badWeather = MOVE_RAIN_DANCE; weatherTrigger = MOVE_SOLAR_BEAM; } + PARAMETRIZE { goodWeather = MOVE_RAIN_DANCE; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_THUNDER; } + PARAMETRIZE { goodWeather = MOVE_HAIL; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_BLIZZARD; } + PARAMETRIZE { goodWeather = MOVE_SNOWSCAPE; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_BLIZZARD; } + PARAMETRIZE { goodWeather = MOVE_SANDSTORM; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_SHORE_UP; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + goodWeather = MOVE_SUNNY_DAY; badWeather = MOVE_RAIN_DANCE; weatherTrigger = MOVE_SOLAR_BEAM; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + goodWeather = MOVE_RAIN_DANCE; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_THUNDER; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + goodWeather = MOVE_HAIL; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_BLIZZARD; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + goodWeather = MOVE_SNOWSCAPE; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_BLIZZARD; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + goodWeather = MOVE_SANDSTORM; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_SHORE_UP; } + + GIVEN { + AI_FLAGS(aiFlags); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TORNADUS) { Item(ITEM_SAFETY_GOGGLES); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PRANKSTER); Moves(goodWeather, badWeather, MOVE_RETURN, MOVE_TAUNT); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SAFETY_GOGGLES); Moves(weatherTrigger, MOVE_EARTH_POWER); } + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, goodWeather); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI uses After You to set up Trick Room (Traits)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_TRICK_ROOM; } + PARAMETRIZE { move = MOVE_MOONBLAST; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_AFTER_YOU) == EFFECT_AFTER_YOU); + ASSUME(GetMoveEffect(MOVE_TRICK_ROOM) == EFFECT_TRICK_ROOM); + ASSUME(IsHealingMove(MOVE_DRAINING_KISS)); // Doesn't have the Healing Move flag in Gen 5 + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_DOUBLE_BATTLE); + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + OPPONENT(SPECIES_COMFEY) { Ability(ABILITY_FLOWER_VEIL); Innates(ABILITY_TRIAGE); Speed(5); Moves(MOVE_AFTER_YOU, MOVE_DRAINING_KISS); } + OPPONENT(SPECIES_CLEFAIRY) { Speed(3); Moves(move, MOVE_PSYCHIC); } + } WHEN { + if (move == MOVE_TRICK_ROOM) + TURN { EXPECT_MOVE(opponentLeft, MOVE_AFTER_YOU, target:opponentRight); EXPECT_MOVE(opponentRight, MOVE_TRICK_ROOM); } + else + TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_AFTER_YOU); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI uses Trick Room intelligently (Traits)") +{ + u32 move, ability, speed; + + PARAMETRIZE { move = MOVE_DRAINING_KISS; ability = ABILITY_SYNCHRONIZE; speed = 4; } + PARAMETRIZE { move = MOVE_DAZZLING_GLEAM; ability = ABILITY_SYNCHRONIZE; speed = 4; } + PARAMETRIZE { move = MOVE_DRAINING_KISS; ability = ABILITY_PSYCHIC_SURGE; speed = 4; } + PARAMETRIZE { move = MOVE_DRAINING_KISS; ability = ABILITY_SYNCHRONIZE; speed = 2; } + PARAMETRIZE { move = MOVE_DAZZLING_GLEAM; ability = ABILITY_SYNCHRONIZE; speed = 2; } + PARAMETRIZE { move = MOVE_DRAINING_KISS; ability = ABILITY_PSYCHIC_SURGE; speed = 2; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_AFTER_YOU) == EFFECT_AFTER_YOU); + ASSUME(GetMoveEffect(MOVE_TRICK_ROOM) == EFFECT_TRICK_ROOM); + ASSUME(IsHealingMove(MOVE_DRAINING_KISS)); // Doesn't have the Healing Move flag in Gen 5 + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_DOUBLE_BATTLE); + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + PLAYER(SPECIES_WOBBUFFET) { Speed(speed); } + OPPONENT(SPECIES_COMFEY) { Ability(ABILITY_FLOWER_VEIL); Innates(ABILITY_TRIAGE); Speed(5); Moves(move); } + OPPONENT(SPECIES_INDEEDEE) { Ability(ABILITY_OWN_TEMPO); Innates(ability); Speed(3); Moves(MOVE_TRICK_ROOM, MOVE_PSYCHIC); } + } WHEN { + if (move == MOVE_DRAINING_KISS && ability != ABILITY_PSYCHIC_SURGE && speed > 3) + TURN { EXPECT_MOVE(opponentRight, MOVE_TRICK_ROOM); } + else + TURN { NOT_EXPECT_MOVE(opponentRight, MOVE_TRICK_ROOM); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI prefers to Fake Out the opponent vulnerable to flinching. (Traits)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_DOUBLE_BATTLE | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_ZUBAT) { Ability(ABILITY_INFILTRATOR); Innates(ABILITY_INNER_FOCUS); } + PLAYER(SPECIES_BRAIXEN) { Ability(ABILITY_MAGICIAN); Innates(ABILITY_BLAZE); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_FAKE_OUT, MOVE_BRANCH_POKE, MOVE_ROCK_SMASH); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, MOVE_FAKE_OUT, target:playerRight); } + } +} + + +AI_DOUBLE_BATTLE_TEST("AI uses Gear Up (Traits)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, MOVE_CELEBRATE); } + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, MOVE_CELEBRATE); } + OPPONENT(SPECIES_KLINKLANG) { Ability(ABILITY_MAGNET_PULL); Innates(ABILITY_PLUS); Moves(MOVE_GEAR_UP, MOVE_WATER_GUN, MOVE_POUND); } + OPPONENT(SPECIES_KLINKLANG) { Ability(ABILITY_MAGNET_PULL); Innates(ABILITY_PLUS); Moves(MOVE_GEAR_UP, MOVE_WATER_GUN, MOVE_POUND); } + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, MOVE_GEAR_UP); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI uses Magnetic Flux (Traits)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, MOVE_CELEBRATE); } + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, MOVE_CELEBRATE); } + OPPONENT(SPECIES_KLINK) { Ability(ABILITY_MAGNET_PULL); Innates(ABILITY_PLUS); Moves(MOVE_MAGNETIC_FLUX, MOVE_POUND); } + OPPONENT(SPECIES_KLINK) { Ability(ABILITY_MAGNET_PULL); Innates(ABILITY_PLUS); Moves(MOVE_MAGNETIC_FLUX, MOVE_POUND); } + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, MOVE_MAGNETIC_FLUX); } + } +} + +#endif + +#if MAX_MON_ITEMS > 1 +AI_DOUBLE_BATTLE_TEST("AI skips Trick/Bestow when items are missing or target already holds one (Multi)") +{ + u16 move = MOVE_NONE, atkItem = ITEM_NONE, targetItem = ITEM_NONE; + + PARAMETRIZE { move = MOVE_TRICK; atkItem = ITEM_NONE; targetItem = ITEM_NONE; } + PARAMETRIZE { move = MOVE_BESTOW; atkItem = ITEM_NONE; targetItem = ITEM_NONE; } + PARAMETRIZE { move = MOVE_BESTOW; atkItem = ITEM_ORAN_BERRY; targetItem = ITEM_LEFTOVERS; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, targetItem); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(move, MOVE_SCRATCH); Items(ITEM_PECHA_BERRY, atkItem); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE); } + } WHEN { + TURN { NOT_EXPECT_MOVE(opponentLeft, move); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI skips Trick/Bestow with unexchangeable items (Multi)") +{ + u16 move = MOVE_NONE, atkItem = ITEM_NONE, targetItem = ITEM_NONE; + + PARAMETRIZE { move = MOVE_TRICK; atkItem = ITEM_ORANGE_MAIL; targetItem = ITEM_NONE; } + PARAMETRIZE { move = MOVE_TRICK; atkItem = ITEM_ORAN_BERRY; targetItem = ITEM_ORANGE_MAIL; } + PARAMETRIZE { move = MOVE_BESTOW; atkItem = ITEM_ORANGE_MAIL; targetItem = ITEM_NONE; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, targetItem); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(move, MOVE_SCRATCH); Items(ITEM_PECHA_BERRY, atkItem); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE); } + } WHEN { + TURN { NOT_EXPECT_MOVE(opponentLeft, move); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI skips Trick/Bestow around Sticky Hold (Multi)") +{ + u16 move = MOVE_NONE, atkItem = ITEM_ORAN_BERRY, targetItem = ITEM_NONE; + enum Ability atkAbility = ABILITY_PRESSURE, targetAbility = ABILITY_PRESSURE; + + PARAMETRIZE { move = MOVE_TRICK; atkAbility = ABILITY_STICKY_HOLD; targetAbility = ABILITY_PRESSURE; targetItem = ITEM_LEFTOVERS; } + PARAMETRIZE { move = MOVE_TRICK; atkAbility = ABILITY_PRESSURE; targetAbility = ABILITY_STICKY_HOLD; targetItem = ITEM_LEFTOVERS; } + PARAMETRIZE { move = MOVE_BESTOW; atkAbility = ABILITY_STICKY_HOLD; targetAbility = ABILITY_PRESSURE; targetItem = ITEM_NONE; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { Ability(targetAbility); Items(ITEM_PECHA_BERRY, targetItem); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Ability(atkAbility); Items(ITEM_PECHA_BERRY, atkItem); Moves(move, MOVE_SCRATCH); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE); } + } WHEN { + TURN { NOT_EXPECT_MOVE(opponentLeft, move); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI skips Trick/Bestow if the target has a Substitute (Multi)") +{ + ASSUME(GetMoveEffect(MOVE_SUBSTITUTE) == EFFECT_SUBSTITUTE); + + u16 move = MOVE_NONE, atkItem = ITEM_NONE, targetItem = ITEM_NONE; + + PARAMETRIZE { move = MOVE_TRICK; atkItem = ITEM_ORAN_BERRY; targetItem = ITEM_LEFTOVERS; } + PARAMETRIZE { move = MOVE_BESTOW; atkItem = ITEM_ORAN_BERRY; targetItem = ITEM_NONE; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_SUBSTITUTE, MOVE_CELEBRATE); Items(ITEM_PECHA_BERRY, targetItem); Speed(20); } + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); Speed(20); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(move, MOVE_SCRATCH); Items(ITEM_PECHA_BERRY, atkItem); Speed(1); Attack(1); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); Speed(1); } + } WHEN { + TURN { + MOVE(playerLeft, MOVE_SUBSTITUTE); + MOVE(playerRight, MOVE_CELEBRATE); + } + TURN { NOT_EXPECT_MOVE(opponentLeft, move); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI will trigger its ally's Weakness Policy (Multi)") +{ + ASSUME(gItemsInfo[ITEM_WEAKNESS_POLICY].holdEffect == HOLD_EFFECT_WEAKNESS_POLICY); + ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(GetMoveType(MOVE_EARTHQUAKE) == TYPE_GROUND); + + u32 species; + PARAMETRIZE { species = SPECIES_INCINEROAR; } + PARAMETRIZE { species = SPECIES_CLEFFA; } + + GIVEN { + ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_EARTHQUAKE, MOVE_STOMPING_TANTRUM); } + OPPONENT(species) { Moves(MOVE_CELEBRATE); Items(ITEM_PECHA_BERRY, ITEM_WEAKNESS_POLICY); } + } WHEN { + if (species == SPECIES_INCINEROAR) + TURN { EXPECT_MOVE(opponentLeft, MOVE_EARTHQUAKE); } + else + TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_EARTHQUAKE); } + } +} + +// Sandstorm is omitted on purpose. +// Tornadus is currently not willing to set up Sandstorm for its ally, but the actual purpose of this test is to demonstrate that Tornadus or Whimsicott will perform standard VGC openers. +// Rain Dance, Sunny Day, and Snowscape are the actually important ones; setting up a good Sandstorm test + functionality is less important and will be done in later PRs. +AI_DOUBLE_BATTLE_TEST("AI sets up weather for its ally (Multi)") +{ + u32 goodWeather, badWeather, weatherTrigger; + u64 aiFlags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT; + + PARAMETRIZE { goodWeather = MOVE_SUNNY_DAY; badWeather = MOVE_RAIN_DANCE; weatherTrigger = MOVE_SOLAR_BEAM; } + PARAMETRIZE { goodWeather = MOVE_RAIN_DANCE; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_THUNDER; } + PARAMETRIZE { goodWeather = MOVE_HAIL; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_BLIZZARD; } + PARAMETRIZE { goodWeather = MOVE_SNOWSCAPE; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_BLIZZARD; } + PARAMETRIZE { goodWeather = MOVE_SANDSTORM; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_SHORE_UP; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + goodWeather = MOVE_SUNNY_DAY; badWeather = MOVE_RAIN_DANCE; weatherTrigger = MOVE_SOLAR_BEAM; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + goodWeather = MOVE_RAIN_DANCE; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_THUNDER; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + goodWeather = MOVE_HAIL; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_BLIZZARD; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + goodWeather = MOVE_SNOWSCAPE; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_BLIZZARD; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + goodWeather = MOVE_SANDSTORM; badWeather = MOVE_SUNNY_DAY; weatherTrigger = MOVE_SHORE_UP; } + + GIVEN { + AI_FLAGS(aiFlags); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TORNADUS) { Items(ITEM_PECHA_BERRY, ITEM_SAFETY_GOGGLES); Ability(ABILITY_PRANKSTER); Moves(goodWeather, badWeather, MOVE_RETURN, MOVE_TAUNT); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_SAFETY_GOGGLES); Moves(weatherTrigger, MOVE_EARTH_POWER); } + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, goodWeather); } + } +} + +#endif diff --git a/test/battle/ai/ai_flag_predict_move.c b/test/battle/ai/ai_flag_predict_move.c index 6a8554b73469..7ab9e68e7386 100644 --- a/test/battle/ai/ai_flag_predict_move.c +++ b/test/battle/ai/ai_flag_predict_move.c @@ -42,3 +42,32 @@ AI_SINGLE_BATTLE_TEST("AI won't use Sucker Punch if it expects a move of the sam TURN { MOVE(player, MOVE_QUICK_ATTACK); EXPECT_MOVE(opponent, MOVE_SCRATCH); } } } + +#if MAX_MON_TRAITS > 1 +AI_SINGLE_BATTLE_TEST("AI_FLAG_PREDICT_MOVE: AI will predict player's move (Traits)") +{ + PASSES_RANDOMLY(PREDICT_MOVE_CHANCE, 100, RNG_AI_PREDICT_MOVE); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PREDICT_MOVE); + PLAYER(SPECIES_VAPOREON) { Ability(ABILITY_HYDRATION); Innates(ABILITY_WATER_ABSORB); Moves(MOVE_SURF, MOVE_TACKLE); } + OPPONENT(SPECIES_NUMEL) { Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_VAPOREON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_WATER_ABSORB); Moves(MOVE_TACKLE); } + } WHEN { + TURN { MOVE(player, MOVE_SURF); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_PREDICT_MOVE: AI will still attack you when it should (Traits)") +{ + PASSES_RANDOMLY(PREDICT_MOVE_CHANCE, 100, RNG_AI_PREDICT_MOVE); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PREDICT_MOVE); + PLAYER(SPECIES_VAPOREON) { Ability(ABILITY_HYDRATION); Innates(ABILITY_WATER_ABSORB); Moves(MOVE_SURF, MOVE_TACKLE); } + OPPONENT(SPECIES_SCEPTILE) { Moves(MOVE_LEAF_BLADE); } + OPPONENT(SPECIES_RHYDON) { Ability(ABILITY_LIGHTNING_ROD); Innates(ABILITY_ROCK_HEAD); Moves(MOVE_EARTHQUAKE); } + } WHEN { + TURN { MOVE(player, MOVE_SURF); EXPECT_MOVE(opponent, MOVE_LEAF_BLADE); } + } +} + +#endif diff --git a/test/battle/ai/ai_flag_predict_switch.c b/test/battle/ai/ai_flag_predict_switch.c index 6f484b6378b3..88ba29c20f21 100644 --- a/test/battle/ai/ai_flag_predict_switch.c +++ b/test/battle/ai/ai_flag_predict_switch.c @@ -149,3 +149,8 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_PREDICT_SWITCH: AI would normally choose predicti TURN { MOVE(player, MOVE_CRUNCH); EXPECT_MOVE(opponent, MOVE_SPORE); } } } + +#if MAX_MON_TRAITS > 1 +TO_DO_BATTLE_TEST("AI_FLAG_PREDICT_SWITCH: Considers ShouldSwitch and GetMostSuitableMonToSwitchInto from player's perspective (Traits)") +TO_DO_BATTLE_TEST("AI_FLAG_PREDICT_SWITCH: AI would switch out in trapper-from-player's-perspective case (Traits)") +#endif diff --git a/test/battle/ai/ai_flag_risky.c b/test/battle/ai/ai_flag_risky.c index befefb814b58..da04bafdbeb5 100644 --- a/test/battle/ai/ai_flag_risky.c +++ b/test/battle/ai/ai_flag_risky.c @@ -91,3 +91,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY | AI_FLAG_PREFER_HIGHEST_DAMAGE_MOVE: AI pr TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, aiRiskyFlag ? MOVE_THUNDER : MOVE_THUNDERBOLT); } } } + +#if MAX_MON_TRAITS > 1 +TO_DO_BATTLE_TEST("AI_FLAG_RISKY: Mid-battle switches prioritize offensive options (Traits)") +#endif diff --git a/test/battle/ai/ai_multi.c b/test/battle/ai/ai_multi.c index ae07207ae85b..4098f3b59478 100644 --- a/test/battle/ai/ai_multi.c +++ b/test/battle/ai/ai_multi.c @@ -2,7 +2,7 @@ #include "test/battle.h" #include "battle_ai_util.h" -AI_MULTI_BATTLE_TEST("AI will only explode and kill everything on the field with Risky or Will Suicide (multi)") +AI_MULTI_BATTLE_TEST("AI will only explode and kill everything on the field with Risky or Will Suicide (multi battle)") { ASSUME(GetMoveTarget(MOVE_EXPLOSION) == MOVE_TARGET_FOES_AND_ALLY); ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); @@ -63,7 +63,7 @@ AI_ONE_VS_TWO_BATTLE_TEST("AI will only explode and kill everything on the field } // Used to test EXPECT_MOVE only on partner -AI_MULTI_BATTLE_TEST("AI partner makes sensible move selections in battle (multi)") +AI_MULTI_BATTLE_TEST("AI partner makes sensible move selections in battle (multi battle)") { GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); @@ -250,3 +250,45 @@ AI_MULTI_BATTLE_TEST("Pollen Puff: AI correctly scores moves with EFFECT_HIT_ENE } } } + +#if MAX_MON_TRAITS > 1 +AI_MULTI_BATTLE_TEST("AI opponents do not steal their partner pokemon in multi battle when forced out 2 (Traits)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + BATTLER_AI_FLAGS(B_POSITION_OPPONENT_LEFT, AI_FLAG_ACE_POKEMON); + MULTI_PLAYER(SPECIES_WOBBUFFET) { } + MULTI_PARTNER(SPECIES_WOBBUFFET) { } + MULTI_OPPONENT_A(SPECIES_GOLISOPOD) { Moves(MOVE_CELEBRATE); HP(101); MaxHP(200); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT);} + MULTI_OPPONENT_A(SPECIES_VENUSAUR) { Moves(MOVE_GIGA_DRAIN); } + MULTI_OPPONENT_B(SPECIES_WYNAUT) { Moves(MOVE_CELEBRATE); } + } WHEN { + TURN {MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft); } + } THEN { + EXPECT_EQ(SPECIES_VENUSAUR, opponentLeft->species); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +AI_MULTI_BATTLE_TEST("AI opponents do not steal their partner pokemon in multi battle when forced out (Multi)") +{ + u32 item, move; + PARAMETRIZE {item = ITEM_EJECT_BUTTON; move = MOVE_TACKLE;} + PARAMETRIZE {item = ITEM_EJECT_PACK; move = MOVE_TAIL_WHIP;} + PARAMETRIZE {item = ITEM_NONE; move = MOVE_ROAR;} + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + BATTLER_AI_FLAGS(B_POSITION_OPPONENT_LEFT, AI_FLAG_ACE_POKEMON); + MULTI_PLAYER(SPECIES_WOBBUFFET) { } + MULTI_PARTNER(SPECIES_WOBBUFFET) { } + MULTI_OPPONENT_A(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); Items(ITEM_PECHA_BERRY, item);} + MULTI_OPPONENT_A(SPECIES_VENUSAUR) { Moves(MOVE_GIGA_DRAIN); } + MULTI_OPPONENT_B(SPECIES_WYNAUT) { Moves(MOVE_CELEBRATE); } + } WHEN { + TURN {MOVE(playerLeft, move, target: opponentLeft); } + } THEN { + EXPECT_EQ(SPECIES_VENUSAUR, opponentLeft->species); + } +} +#endif diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index 36a859f62c94..8cf0fb5c95df 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -92,7 +92,7 @@ AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same Pokémon for 2 spo } // Used to test EXPECT_SWITCH only on partner -AI_MULTI_BATTLE_TEST("AI partner will not switch mid-turn into a player Pokémon (multi)") +AI_MULTI_BATTLE_TEST("AI partner will not switch mid-turn into a player Pokémon (multi battle)") { u32 flags; @@ -156,7 +156,7 @@ AI_TWO_VS_ONE_BATTLE_TEST("AI partner will not switch mid-turn into a player Pok } // Used to test EXPECT_SEND_OUT only on partner -AI_MULTI_BATTLE_TEST("AI partner will not switch into a player Pokémon after fainting (multi)") +AI_MULTI_BATTLE_TEST("AI partner will not switch into a player Pokémon after fainting (multi battle)") { u32 flags; @@ -216,7 +216,7 @@ AI_TWO_VS_ONE_BATTLE_TEST("AI partner will not switch into a player Pokémon aft } // Used to test EXPECT_SWITCH, EXPECT_SEND_OUT, and EXPECT_MOVE on partner -AI_MULTI_BATTLE_TEST("AI partner will not switch into a player Pokémon (multi)") +AI_MULTI_BATTLE_TEST("AI partner will not switch into a player Pokémon (multi battle)") { u32 flags; @@ -1748,3 +1748,1962 @@ AI_DOUBLE_BATTLE_TEST("AI will not choose to switch out Dondozo with Commander T TURN { MOVE(playerLeft, MOVE_CELEBRATE); MOVE(playerRight, MOVE_CELEBRATE); EXPECT_MOVE(opponentLeft, MOVE_WATER_GUN); } } } + +#if MAX_MON_ITEMS > 1 +// Used to test EXPECT_SWITCH only on partner +AI_MULTI_BATTLE_TEST("AI partner will not switch mid-turn into a player Pokémon (multi battle)") +{ + u32 flags; + + PARAMETRIZE {flags = AI_FLAG_SMART_SWITCHING; } + PARAMETRIZE {flags = 0; } + + PASSES_RANDOMLY(SHOULD_SWITCH_ALL_MOVES_BAD_PERCENTAGE, 100, RNG_AI_SWITCH_ALL_MOVES_BAD); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | flags); + MULTI_PLAYER(SPECIES_HAUNTER); + MULTI_PLAYER(SPECIES_RATTATA); + // No moves to damage opponents. + MULTI_PARTNER(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL); } + MULTI_PARTNER(SPECIES_GASTLY) { Moves(MOVE_LICK); } + MULTI_PARTNER(SPECIES_RATICATE) { Moves(MOVE_HEADBUTT); } + MULTI_OPPONENT_A(SPECIES_RATTATA) { Moves(MOVE_CELEBRATE); } + MULTI_OPPONENT_B(SPECIES_KANGASKHAN) { Moves(MOVE_CELEBRATE); } + + } WHEN { + TURN { EXPECT_SWITCH(playerRight, 5); }; + } SCENE { + MESSAGE(AI_PARTNER_NAME " withdrew Gengar!"); + MESSAGE(AI_PARTNER_NAME " sent out Raticate!"); + NONE_OF { + MESSAGE(AI_PARTNER_NAME " withdrew Gengar!"); + MESSAGE(AI_PARTNER_NAME " sent out Rattata!"); + } + } +} + +// Used to test EXPECT_SWITCH only on partner +AI_TWO_VS_ONE_BATTLE_TEST("AI partner will not switch mid-turn into a player Pokémon (2v1)") +{ + u32 flags; + + PARAMETRIZE {flags = AI_FLAG_SMART_SWITCHING; } + PARAMETRIZE {flags = 0; } + + PASSES_RANDOMLY(SHOULD_SWITCH_ALL_MOVES_BAD_PERCENTAGE, 100, RNG_AI_SWITCH_ALL_MOVES_BAD); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | flags); + MULTI_PLAYER(SPECIES_HAUNTER); + MULTI_PLAYER(SPECIES_RATTATA); + // No moves to damage opponents. + MULTI_PARTNER(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL); } + MULTI_PARTNER(SPECIES_GASTLY) { Moves(MOVE_LICK); } + MULTI_PARTNER(SPECIES_RATICATE) { Moves(MOVE_HEADBUTT); } + MULTI_OPPONENT_A(SPECIES_RATTATA) { Moves(MOVE_CELEBRATE); } + MULTI_OPPONENT_A(SPECIES_KANGASKHAN) { Moves(MOVE_CELEBRATE); } + + } WHEN { + TURN { EXPECT_SWITCH(playerRight, 5); }; + } SCENE { + MESSAGE(AI_PARTNER_NAME " withdrew Gengar!"); + MESSAGE(AI_PARTNER_NAME " sent out Raticate!"); + NONE_OF { + MESSAGE(AI_PARTNER_NAME " withdrew Gengar!"); + MESSAGE(AI_PARTNER_NAME " sent out Rattata!"); + } + } +} + +// Used to test EXPECT_SEND_OUT only on partner +AI_MULTI_BATTLE_TEST("AI partner will not switch into a player Pokémon after fainting (multi battle)") +{ + u32 flags; + + PARAMETRIZE {flags = AI_FLAG_SMART_SWITCHING; } + PARAMETRIZE {flags = 0; } + + PASSES_RANDOMLY(SHOULD_SWITCH_ALL_MOVES_BAD_PERCENTAGE, 100, RNG_AI_SWITCH_ALL_MOVES_BAD); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | flags); + MULTI_PLAYER(SPECIES_GENGAR); + MULTI_PLAYER(SPECIES_RATTATA); + // No moves to damage opponents. + MULTI_PARTNER(SPECIES_WOBBUFFET) { Status1(STATUS1_BURN); HP(1); } + MULTI_PARTNER(SPECIES_GASTLY); + MULTI_PARTNER(SPECIES_HAUNTER); + MULTI_OPPONENT_A(SPECIES_TRAPINCH) { Ability(ABILITY_ARENA_TRAP); Moves(MOVE_CELEBRATE); } + MULTI_OPPONENT_B(SPECIES_VIBRAVA) { Moves(MOVE_CELEBRATE); } + + } WHEN { + TURN { EXPECT_MOVE(playerRight, MOVE_CELEBRATE); EXPECT_SEND_OUT(playerRight, 5); }; + } SCENE { + MESSAGE(AI_PARTNER_NAME " sent out Haunter!"); + NONE_OF { + MESSAGE(AI_PARTNER_NAME " sent out Rattata!"); + } + } +} + +// Used to test EXPECT_SEND_OUT only on partner +AI_TWO_VS_ONE_BATTLE_TEST("AI partner will not switch into a player Pokémon after fainting (2v1)") +{ + u32 flags; + + PARAMETRIZE {flags = AI_FLAG_SMART_SWITCHING; } + PARAMETRIZE {flags = 0; } + + PASSES_RANDOMLY(SHOULD_SWITCH_ALL_MOVES_BAD_PERCENTAGE, 100, RNG_AI_SWITCH_ALL_MOVES_BAD); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | flags); + MULTI_PLAYER(SPECIES_GENGAR); + MULTI_PLAYER(SPECIES_RATTATA); + // No moves to damage opponents. + MULTI_PARTNER(SPECIES_WOBBUFFET) { Status1(STATUS1_BURN); HP(1); } + MULTI_PARTNER(SPECIES_GASTLY); + MULTI_PARTNER(SPECIES_HAUNTER); + MULTI_OPPONENT_A(SPECIES_TRAPINCH) { Ability(ABILITY_ARENA_TRAP); Moves(MOVE_CELEBRATE); } + MULTI_OPPONENT_A(SPECIES_VIBRAVA) { Moves(MOVE_CELEBRATE); } + + } WHEN { + TURN { EXPECT_MOVE(playerRight, MOVE_CELEBRATE); EXPECT_SEND_OUT(playerRight, 5); }; + } SCENE { + MESSAGE(AI_PARTNER_NAME " sent out Haunter!"); + NONE_OF { + MESSAGE(AI_PARTNER_NAME " sent out Rattata!"); + } + } +} + +// Used to test EXPECT_SWITCH, EXPECT_SEND_OUT, and EXPECT_MOVE on partner +AI_MULTI_BATTLE_TEST("AI partner will not switch into a player Pokémon (multi battle)") +{ + u32 flags; + + PARAMETRIZE {flags = AI_FLAG_SMART_SWITCHING; } + PARAMETRIZE {flags = 0; } + + PASSES_RANDOMLY(SHOULD_SWITCH_ALL_MOVES_BAD_PERCENTAGE, 100, RNG_AI_SWITCH_ALL_MOVES_BAD); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | flags); + MULTI_PLAYER(SPECIES_HAUNTER); + MULTI_PLAYER(SPECIES_RATTATA); + // No moves to damage opponents. + MULTI_PARTNER(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL); } + MULTI_PARTNER(SPECIES_RATICATE) { Moves(MOVE_HEADBUTT); HP(1); } + MULTI_OPPONENT_A(SPECIES_RATTATA) { Moves(MOVE_CELEBRATE); } + MULTI_OPPONENT_B(SPECIES_KANGASKHAN) { Moves(MOVE_CELEBRATE); } + + } WHEN { + TURN { MOVE(playerLeft, MOVE_AURA_SPHERE, target:playerRight); EXPECT_SWITCH(playerRight, 4); EXPECT_SEND_OUT(playerRight, 3); }; + TURN { EXPECT_MOVE(playerRight, MOVE_SHADOW_BALL, target:opponentLeft); }; + } SCENE { + MESSAGE(AI_PARTNER_NAME " sent out Raticate!"); + NONE_OF { + MESSAGE(AI_PARTNER_NAME " sent out Rattata!"); + } + } +} + +// Used to test EXPECT_SWITCH, EXPECT_SEND_OUT, and EXPECT_MOVE on partner +AI_TWO_VS_ONE_BATTLE_TEST("AI partner will not switch into a player Pokémon (2v1)") +{ + u32 flags; + + PARAMETRIZE {flags = AI_FLAG_SMART_SWITCHING; } + PARAMETRIZE {flags = 0; } + + PASSES_RANDOMLY(SHOULD_SWITCH_ALL_MOVES_BAD_PERCENTAGE, 100, RNG_AI_SWITCH_ALL_MOVES_BAD); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | flags); + MULTI_PLAYER(SPECIES_HAUNTER); + MULTI_PLAYER(SPECIES_RATTATA); + // No moves to damage opponents. + MULTI_PARTNER(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL); } + MULTI_PARTNER(SPECIES_RATICATE) { Moves(MOVE_HEADBUTT); HP(1); } + MULTI_OPPONENT_A(SPECIES_RATTATA) { Moves(MOVE_CELEBRATE); } + MULTI_OPPONENT_A(SPECIES_KANGASKHAN) { Moves(MOVE_CELEBRATE); } + + } WHEN { + TURN { MOVE(playerLeft, MOVE_AURA_SPHERE, target:playerRight); EXPECT_SWITCH(playerRight, 4); EXPECT_SEND_OUT(playerRight, 3); }; + TURN { EXPECT_MOVE(playerRight, MOVE_SHADOW_BALL, target:opponentLeft); }; + } SCENE { + MESSAGE(AI_PARTNER_NAME " sent out Raticate!"); + NONE_OF { + MESSAGE(AI_PARTNER_NAME " sent out Rattata!"); + } + } +} + +AI_TWO_VS_ONE_BATTLE_TEST("AI will not try to switch for the same pokemon for 2 spots in a 2v1 battle (all bad moves)") +{ + u32 flags; + + PARAMETRIZE {flags = AI_FLAG_SMART_SWITCHING; } + PARAMETRIZE {flags = 0; } + + PASSES_RANDOMLY(SHOULD_SWITCH_ALL_MOVES_BAD_PERCENTAGE, 100, RNG_AI_SWITCH_ALL_MOVES_BAD); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | flags); + MULTI_PLAYER(SPECIES_RATTATA); + MULTI_PLAYER(SPECIES_RATTATA); + MULTI_PARTNER(SPECIES_KANGASKHAN); + // No moves to damage player. + MULTI_OPPONENT_A(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL); } + MULTI_OPPONENT_A(SPECIES_HAUNTER) { Moves(MOVE_SHADOW_BALL); } + MULTI_OPPONENT_A(SPECIES_GASTLY) { Moves(MOVE_LICK); } + MULTI_OPPONENT_A(SPECIES_RATICATE) { Moves(MOVE_HEADBUTT); } + } WHEN { + TURN { EXPECT_SWITCH(opponentLeft, 3); }; + } SCENE { + MESSAGE(AI_TRAINER_NAME " withdrew Gengar!"); + MESSAGE(AI_TRAINER_NAME " sent out Raticate!"); + NONE_OF { + MESSAGE(AI_TRAINER_NAME " withdrew Haunter!"); + MESSAGE(AI_TRAINER_NAME " sent out Raticate!"); + } + } +} + +AI_ONE_VS_TWO_BATTLE_TEST("AI will not switch into a partner Pokémon in a 1v2 battle (all bad moves)") +{ + u32 flags; + + PARAMETRIZE {flags = AI_FLAG_SMART_SWITCHING; } + PARAMETRIZE {flags = 0; } + + PASSES_RANDOMLY(SHOULD_SWITCH_ALL_MOVES_BAD_PERCENTAGE, 100, RNG_AI_SWITCH_ALL_MOVES_BAD); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | flags); + MULTI_PLAYER(SPECIES_RATTATA); + MULTI_PLAYER(SPECIES_KANGASKHAN); + // No moves to damage player. + MULTI_OPPONENT_A(SPECIES_HAUNTER) { Moves(MOVE_SHADOW_BALL); } + MULTI_OPPONENT_B(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL); } + MULTI_OPPONENT_B(SPECIES_GASTLY) { Moves(MOVE_LICK); } + MULTI_OPPONENT_B(SPECIES_RATICATE) { Moves(MOVE_HEADBUTT); } + + } WHEN { + TURN { EXPECT_SWITCH(opponentRight, 5); }; + } SCENE { + MESSAGE(AI_TRAINER_2_NAME " withdrew Gengar!"); + MESSAGE(AI_TRAINER_2_NAME " sent out Raticate!"); + NONE_OF { + MESSAGE(AI_TRAINER_NAME " withdrew Haunter!"); + MESSAGE(AI_TRAINER_NAME " sent out Raticate!"); + } + } +} + +AI_SINGLE_BATTLE_TEST("AI will switch out if it has no move that affects the player") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_ALL_MOVES_BAD_PERCENTAGE, 100, RNG_AI_SWITCH_ALL_MOVES_BAD); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_RATTATA); + OPPONENT(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL); } + OPPONENT(SPECIES_RATTATA) { Moves(MOVE_SCRATCH); } + } WHEN { + TURN { EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("When AI switches out due to having no move that affects the player, AI will send in a mon that can hit the player, even if not ideal") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL, MOVE_CELEBRATE); } + OPPONENT(SPECIES_ABRA) { Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_ABRA) { Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_ABRA) { Level(5); Moves(MOVE_CONFUSION); } + OPPONENT(SPECIES_ABRA) { Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_ABRA) { Moves(MOVE_TACKLE); } + } WHEN { + TURN { MOVE(player, MOVE_SHADOW_BALL); EXPECT_SWITCH(opponent, 2); EXPECT_SEND_OUT(opponent, 0);} + TURN { MOVE(player, MOVE_SHADOW_BALL); EXPECT_MOVE(opponent, MOVE_TACKLE); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same Pokémon for 2 spots in a double battle (Wonder Guard)") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_WONDER_GUARD_PERCENTAGE, 100, RNG_AI_SWITCH_WONDER_GUARD); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_SHEDINJA); + PLAYER(SPECIES_SHEDINJA); + // No moves to damage player. + OPPONENT(SPECIES_LINOONE) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_LINOONE) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL); } + } WHEN { + TURN { EXPECT_SWITCH(opponentLeft, 3); }; + } SCENE { + MESSAGE(AI_TRAINER_NAME " withdrew Linoone!"); + MESSAGE(AI_TRAINER_NAME " sent out Gengar!"); + NONE_OF { + MESSAGE(AI_TRAINER_NAME " withdrew Zigzagoon!"); + MESSAGE(AI_TRAINER_NAME " sent out Gengar!"); + } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Switch effect moves will send out Ace Mon if it's the only one remaining") +{ + u32 aiMove = 0; + // Moves testing all effects in IsSwitchOutEffect + PARAMETRIZE { aiMove = MOVE_U_TURN; } + PARAMETRIZE { aiMove = MOVE_TELEPORT; } + PARAMETRIZE { aiMove = MOVE_PARTING_SHOT; } + PARAMETRIZE { aiMove = MOVE_BATON_PASS; } + PARAMETRIZE { aiMove = MOVE_CHILLY_RECEPTION; } + PARAMETRIZE { aiMove = MOVE_SHED_TAIL; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_U_TURN) == EFFECT_HIT_ESCAPE); + ASSUME(GetMoveEffect(MOVE_TELEPORT) == EFFECT_TELEPORT); + ASSUME(GetMoveEffect(MOVE_PARTING_SHOT) == EFFECT_PARTING_SHOT); + ASSUME(GetMoveEffect(MOVE_BATON_PASS) == EFFECT_BATON_PASS); + ASSUME(GetMoveEffect(MOVE_CHILLY_RECEPTION) == EFFECT_CHILLY_RECEPTION); + ASSUME(GetMoveEffect(MOVE_SHED_TAIL) == EFFECT_SHED_TAIL); + WITH_CONFIG(CONFIG_TELEPORT_BEHAVIOR, GEN_8); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_ACE_POKEMON); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(aiMove); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { EXPECT_MOVE(opponent, aiMove); EXPECT_SEND_OUT(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Eject Button will send out Ace Mon if it's the only one remaining") +{ + u32 aiSmartMonChoicesFlag; + PARAMETRIZE { aiSmartMonChoicesFlag = 0; } + PARAMETRIZE { aiSmartMonChoicesFlag = AI_FLAG_SMART_MON_CHOICES; } + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSmartMonChoicesFlag | AI_FLAG_ACE_POKEMON); + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_ZIGZAGOON) { Items(ITEM_EJECT_BUTTON); }; + OPPONENT(SPECIES_LINOONE); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_SCRATCH); EXPECT_SEND_OUT(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Eject Pack will send out Ace Mon if it's the only one remaining") +{ + u32 aiSmartMonChoicesFlag; + PARAMETRIZE { aiSmartMonChoicesFlag = 0; } + PARAMETRIZE { aiSmartMonChoicesFlag = AI_FLAG_SMART_MON_CHOICES; } + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSmartMonChoicesFlag | AI_FLAG_ACE_POKEMON); + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + PLAYER(SPECIES_ARCANINE) { Ability(ABILITY_INTIMIDATE); Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_ZIGZAGOON) { Items(ITEM_EJECT_PACK); Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_LINOONE) { Moves(MOVE_HEADBUTT); } + } WHEN { + TURN { SWITCH(player, 1); EXPECT_MOVE(opponent, MOVE_SCRATCH); EXPECT_SEND_OUT(opponent, 1); } + } +} + +// General AI_FLAG_SMART_MON_CHOICES behaviour +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Number of hits to KO calculation checks whether incoming damage is less than recurring healing to avoid an infinite loop") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_VENUSAUR) { Level(30); Moves(MOVE_SCRATCH); } + // Opponent party courtesy of Skolgrahd, who triggered the bug in the first place + OPPONENT(SPECIES_PIKACHU) { Level(100); Moves(MOVE_ZIPPY_ZAP, MOVE_EXTREME_SPEED, MOVE_IRON_TAIL, MOVE_KNOCK_OFF); } + OPPONENT(SPECIES_NINETALES_ALOLA) { Level(100); Moves(MOVE_AURORA_VEIL, MOVE_BLIZZARD, MOVE_MOONBLAST, MOVE_DISABLE); } + OPPONENT(SPECIES_WEAVILE) { Level(100); Moves(MOVE_NIGHT_SLASH, MOVE_TRIPLE_AXEL, MOVE_ICE_SHARD, MOVE_FAKE_OUT); } + OPPONENT(SPECIES_DITTO) { Level(100); Moves(MOVE_TRANSFORM); } + OPPONENT(SPECIES_TYPHLOSION) { Level(100); Moves(MOVE_ERUPTION, MOVE_HEAT_WAVE, MOVE_FOCUS_BLAST, MOVE_EXTRASENSORY); } + OPPONENT(SPECIES_UMBREON) { Level(100); Items(ITEM_LEFTOVERS); Moves(MOVE_FOUL_PLAY, MOVE_SNARL, MOVE_HELPING_HAND, MOVE_THUNDER_WAVE); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVES(opponent, MOVE_ZIPPY_ZAP, MOVE_EXTREME_SPEED, MOVE_IRON_TAIL, MOVE_KNOCK_OFF); } + } SCENE { + MESSAGE("Venusaur fainted!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Number of hits to KO calculation checks whether incoming damage is zero to avoid an infinite loop") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LEFTOVERS].holdEffect == HOLD_EFFECT_LEFTOVERS); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_BULBASAUR) { Level(5); Moves(MOVE_SWORDS_DANCE, MOVE_WHIRLWIND, MOVE_SAND_ATTACK, MOVE_TAIL_WHIP); } + // Scenario courtesy of Duke, who triggered the bug in the first place + OPPONENT(SPECIES_GEODUDE) { Level(100); Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_GEODUDE) { Level(100); Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_NOSEPASS) { Level(100); Moves(MOVE_SCRATCH); } + } WHEN { + TURN { MOVE(player, MOVE_SWORDS_DANCE); EXPECT_MOVES(opponent, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Bulbasaur fainted!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Avoid infinite loop if damage taken is equal to recurring healing") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LEFTOVERS].holdEffect == HOLD_EFFECT_LEFTOVERS); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_MEOWTH_GALAR) { Level(100); Moves(MOVE_GROWL, MOVE_FAKE_OUT, MOVE_HONE_CLAWS); } + // Scenario courtesy of Duke, who triggered the bug in the first place + OPPONENT(SPECIES_MEOWTH_GALAR) { Level(5); Moves(MOVE_GROWL, MOVE_FAKE_OUT, MOVE_HONE_CLAWS); } + OPPONENT(SPECIES_GEODUDE) { Level(5); Moves(MOVE_DOUBLE_EDGE); } + OPPONENT(SPECIES_GEODUDE) { Level(5); Moves(MOVE_DOUBLE_EDGE); } + OPPONENT(SPECIES_NOSEPASS) { Level(5); Moves(MOVE_DOUBLE_EDGE); } + OPPONENT(SPECIES_HOUNDSTONE) { Level(5); Moves(MOVE_NIGHT_SHADE, MOVE_BODY_PRESS, MOVE_WILL_O_WISP, MOVE_PROTECT); Items(ITEM_LEFTOVERS); } + } WHEN { + TURN { MOVE(player, MOVE_FAKE_OUT); EXPECT_MOVES(opponent, MOVE_FAKE_OUT); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will not switch in a Pokemon which is slower and gets 1HKOed after fainting") +{ + bool32 alakazamFirst; + u32 speedAlakazm; + u32 aiSmartSwitchFlags = 0; + + PARAMETRIZE { speedAlakazm = 200; alakazamFirst = TRUE; } // AI will always send out Alakazan as it sees a KO with Focus Blast, even if Alakazam dies before it can get it off + PARAMETRIZE { speedAlakazm = 200; alakazamFirst = FALSE; aiSmartSwitchFlags = AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES; } // AI_FLAG_SMART_MON_CHOICES lets AI see that Alakazam would be KO'd before it can KO, and won't switch it in + PARAMETRIZE { speedAlakazm = 400; alakazamFirst = TRUE; aiSmartSwitchFlags = AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES; } // AI_FLAG_SMART_MON_CHOICES recognizes that Alakazam is faster and can KO, and will switch it in + + GIVEN { + ASSUME(GetMoveCategory(MOVE_PSYCHIC) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMoveCategory(MOVE_FOCUS_BLAST) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMoveCategory(MOVE_BUBBLE_BEAM) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMoveCategory(MOVE_WATER_GUN) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMoveCategory(MOVE_STRENGTH) == DAMAGE_CATEGORY_PHYSICAL); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSmartSwitchFlags); + PLAYER(SPECIES_WEAVILE) { Speed(300); Ability(ABILITY_SHADOW_TAG); } // Weavile has Shadow Tag, so AI can't switch on the first turn, but has to do it after fainting. + OPPONENT(SPECIES_KADABRA) { Speed(200); Moves(MOVE_PSYCHIC, MOVE_DISABLE, MOVE_TAUNT, MOVE_CALM_MIND); } + OPPONENT(SPECIES_ALAKAZAM) { Speed(speedAlakazm); Moves(MOVE_FOCUS_BLAST, MOVE_PSYCHIC); } // Alakazam has a move which OHKOes Weavile, but it doesn't matter if he's getting KO-ed first. + OPPONENT(SPECIES_BLASTOISE) { Speed(200); Moves(MOVE_BUBBLE_BEAM, MOVE_WATER_GUN, MOVE_LEER, MOVE_STRENGTH); } // Can't OHKO, but survives a hit from Weavile's Night Slash. + } WHEN { + TURN { MOVE(player, MOVE_NIGHT_SLASH) ; EXPECT_SEND_OUT(opponent, alakazamFirst ? 1 : 2); } // AI doesn't send out Alakazam if it gets outsped + } SCENE { + MESSAGE("The opposing Kadabra fainted!"); + if (alakazamFirst) { + MESSAGE(AI_TRAINER_NAME " sent out Alakazam!"); + } else { + MESSAGE(AI_TRAINER_NAME " sent out Blastoise!"); + } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI considers hazard damage when choosing which Pokemon to switch in") +{ + u32 aiIsSmart = 0; + u32 aiSmartSwitchFlags = 0; + + PARAMETRIZE { aiIsSmart = 0; aiSmartSwitchFlags = 0; } // AI doesn't care about hazard damage resulting in Pokemon being KO'd + PARAMETRIZE { aiIsSmart = 1; aiSmartSwitchFlags = AI_FLAG_SMART_MON_CHOICES; } // AI_FLAG_SMART_MON_CHOICES avoids being KO'd as a result of hazards damage + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSmartSwitchFlags); + PLAYER(SPECIES_MEGANIUM) { Speed(100); SpDefense(328); SpAttack(265); Moves(MOVE_STEALTH_ROCK, MOVE_SURF); } // Meganium does ~56% minimum ~66% maximum, enough to KO Charizard after rocks and never KO Typhlosion after rocks + OPPONENT(SPECIES_PONYTA) { Level(5); Speed(5); Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_CHARIZARD) { Speed(200); Moves(MOVE_FLAMETHROWER); SpAttack(317); SpDefense(207); MaxHP(297); } // Outspeends and 2HKOs Meganium + OPPONENT(SPECIES_TYPHLOSION) { Speed(200); Moves(MOVE_FLAMETHROWER); SpAttack(317); SpDefense(207); MaxHP(297); } // Outspeends and 2HKOs Meganium + } WHEN { + TURN { MOVE(player, MOVE_STEALTH_ROCK) ;} + TURN { MOVE(player, MOVE_SURF); EXPECT_SEND_OUT(opponent, aiIsSmart ? 2 : 1); } // AI sends out Typhlosion to get the KO with the flag rather than Charizard + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize type matchup + SE move, then type matchup") +{ + u32 aiSmartSwitchFlags = 0; + u32 move1; + u32 move2; + u32 expectedIndex; + + PARAMETRIZE { expectedIndex = 3; move1 = MOVE_SCRATCH; move2 = MOVE_SCRATCH; aiSmartSwitchFlags = 0; } // When not smart, AI will only switch in a defensive mon if it has a SE move, otherwise will just default to damage + PARAMETRIZE { expectedIndex = 1; move1 = MOVE_GIGA_DRAIN; move2 = MOVE_SCRATCH; aiSmartSwitchFlags = 0; } + PARAMETRIZE { expectedIndex = 2; move1 = MOVE_SCRATCH; move2 = MOVE_WATER_PULSE; aiSmartSwitchFlags = AI_FLAG_SMART_MON_CHOICES; } // When smart, AI will prioritize SE move, but still switch in good type matchup without SE move + PARAMETRIZE { expectedIndex = 1; move1 = MOVE_GIGA_DRAIN; move2 = MOVE_SCRATCH; aiSmartSwitchFlags = AI_FLAG_SMART_MON_CHOICES; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSmartSwitchFlags); + PLAYER(SPECIES_MARSHTOMP) { Level(30); Moves(MOVE_MUD_BOMB, MOVE_WATER_GUN, MOVE_GROWL, MOVE_MUD_SHOT); Speed(5); } + OPPONENT(SPECIES_PONYTA) { Level(1); Moves(MOVE_NONE); Speed(6); } // Forces switchout + OPPONENT(SPECIES_TANGELA) { Level(30); Moves(move1); Speed(4); } + OPPONENT(SPECIES_LOMBRE) { Level(30); Moves(move2); Speed(4); } + OPPONENT(SPECIES_HARIYAMA) { Level(30); Moves(MOVE_VITAL_THROW); Speed(4); } + } WHEN { + TURN { MOVE(player, MOVE_GROWL); EXPECT_SWITCH(opponent, expectedIndex); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize defensive options") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_WING_ATTACK, MOVE_BOOMBURST); Speed(5); SpAttack(50); } + OPPONENT(SPECIES_PONYTA) { Level(1); Moves(MOVE_NONE); Speed(4); } // Forces switchout + OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_IRON_HEAD); Speed(4); SpDefense(50); } // Mid battle, AI sends out Aron + OPPONENT(SPECIES_ELECTRODE) { Level(30); Ability(ABILITY_STATIC); Moves(MOVE_CHARGE_BEAM); Speed(6); SpDefense(53);} + } WHEN { + TURN { MOVE(player, MOVE_WING_ATTACK); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize offensive options after slow U-Turn") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FALSE_SWIPE) == EFFECT_FALSE_SWIPE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_FALSE_SWIPE, MOVE_BOOMBURST); Speed(5); SpAttack(50); } + OPPONENT(SPECIES_PONYTA) { Level(1); Moves(MOVE_U_TURN); Speed(4); } // Forces switchout + OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_IRON_HEAD); Speed(4); SpDefense(50); } + OPPONENT(SPECIES_ELECTRODE) { Level(30); Ability(ABILITY_STATIC); Moves(MOVE_CHARGE_BEAM); Speed(6); SpDefense(53); } + } WHEN { + TURN { MOVE(player, MOVE_FALSE_SWIPE); EXPECT_SEND_OUT(opponent, 2); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize offensive options after Eject Button") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_EJECT_BUTTON].holdEffect == HOLD_EFFECT_EJECT_BUTTON); + ASSUME(GetMoveEffect(MOVE_FALSE_SWIPE) == EFFECT_FALSE_SWIPE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_FALSE_SWIPE, MOVE_BOOMBURST); Speed(5); SpAttack(50); } + OPPONENT(SPECIES_PONYTA) { Level(1); Items(ITEM_EJECT_BUTTON); Moves(MOVE_SCRATCH); Speed(4); } // Forces switchout + OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_IRON_HEAD); Speed(4); SpDefense(50); } + OPPONENT(SPECIES_ELECTRODE) { Level(30); Ability(ABILITY_STATIC); Moves(MOVE_CHARGE_BEAM); Speed(6); SpDefense(53); } + } WHEN { + TURN { MOVE(player, MOVE_FALSE_SWIPE); EXPECT_SEND_OUT(opponent, 2); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize offensive options after Eject Pack") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_EJECT_PACK].holdEffect == HOLD_EFFECT_EJECT_PACK); + ASSUME(GetMoveEffect(MOVE_GROWL) == EFFECT_ATTACK_DOWN); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_GROWL, MOVE_BOOMBURST); Speed(5); SpAttack(50); } + OPPONENT(SPECIES_PONYTA) { Level(1); Items(ITEM_EJECT_PACK); Moves(MOVE_SCRATCH); Speed(4); } // Forces switchout + OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_IRON_HEAD); Speed(4); SpDefense(50); } + OPPONENT(SPECIES_ELECTRODE) { Level(30); Ability(ABILITY_STATIC); Moves(MOVE_CHARGE_BEAM); Speed(6); SpDefense(53); } + } WHEN { + TURN { MOVE(player, MOVE_GROWL); EXPECT_SEND_OUT(opponent, 2); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize defensive options after Eject Pack if mon outspeeds") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_EJECT_PACK].holdEffect == HOLD_EFFECT_EJECT_PACK); + ASSUME(MoveHasAdditionalEffectSelf(MOVE_OVERHEAT, MOVE_EFFECT_SP_ATK_MINUS_2) == TRUE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_WING_ATTACK, MOVE_BOOMBURST); Speed(5); SpAttack(50); } + OPPONENT(SPECIES_PONYTA) { Level(1); Items(ITEM_EJECT_PACK); Moves(MOVE_OVERHEAT); Speed(6); } // Forces switchout + OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_IRON_HEAD); Speed(4); SpDefense(50); } + OPPONENT(SPECIES_ELECTRODE) { Level(30); Ability(ABILITY_STATIC); Moves(MOVE_CHARGE_BEAM); Speed(6); SpDefense(53); } + } WHEN { + TURN { MOVE(player, MOVE_WING_ATTACK); EXPECT_SEND_OUT(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize offensive options after Eject Pack if mon outspeeds but was Intimidate'd") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_EJECT_PACK].holdEffect == HOLD_EFFECT_EJECT_PACK); + ASSUME(MoveHasAdditionalEffectSelf(MOVE_OVERHEAT, MOVE_EFFECT_SP_ATK_MINUS_2) == TRUE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_STARAPTOR) { Level(30); Ability(ABILITY_INTIMIDATE); Moves(MOVE_WING_ATTACK, MOVE_BOOMBURST); Speed(5); SpAttack(50); } + OPPONENT(SPECIES_PONYTA) { Level(1); Items(ITEM_EJECT_PACK); Moves(MOVE_OVERHEAT); Speed(6); } // Forces switchout + OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_IRON_HEAD); Speed(4); SpDefense(50); } + OPPONENT(SPECIES_ELECTRODE) { Level(30); Ability(ABILITY_STATIC); Moves(MOVE_CHARGE_BEAM); Speed(6); SpDefense(53); } + } WHEN { + TURN { MOVE(player, MOVE_WING_ATTACK); EXPECT_SWITCH(opponent, 2); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Post-KO switches prioritize offensive options") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_WING_ATTACK, MOVE_BOOMBURST); Speed(5); } + OPPONENT(SPECIES_PONYTA) { Level(1); Moves(MOVE_SCRATCH); Speed(4); } + OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_IRON_HEAD); Speed(4); } // Mid battle, AI sends out Aron + OPPONENT(SPECIES_ELECTRODE) { Level(30); Ability(ABILITY_STATIC); Moves(MOVE_CHARGE_BEAM); Speed(6); } + } WHEN { + TURN { MOVE(player, MOVE_WING_ATTACK); EXPECT_SEND_OUT(opponent, 2); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Post-KO switches factor in Trick Room for revenge killing") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TRICK_ROOM) == EFFECT_TRICK_ROOM); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_SWELLOW) { Level(30); Speed(10); Moves(MOVE_WING_ATTACK, MOVE_GROWL); } + OPPONENT(SPECIES_BALTOY) { Level(1); Speed(10); Moves(MOVE_TRICK_ROOM); } + OPPONENT(SPECIES_ELECTRODE) { Level(30); Speed(5); Moves(MOVE_THUNDERBOLT); } + OPPONENT(SPECIES_ELECTRODE) { Level(30); Speed(15); Moves(MOVE_THUNDERBOLT); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_TRICK_ROOM); MOVE(player, MOVE_GROWL); } + TURN { MOVE(player, MOVE_WING_ATTACK); EXPECT_SEND_OUT(opponent, 1); } + } +} + +// General AI_FLAG_SMART_SWITCHING behaviour +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI switches out after sufficient stat drops") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_HITMONTOP) { Level(30); Moves(MOVE_CHARM, MOVE_SCRATCH); Ability(ABILITY_INTIMIDATE); Speed(5); } + OPPONENT(SPECIES_GRIMER) { Level(30); Moves(MOVE_SCRATCH); Speed(4); } + OPPONENT(SPECIES_PONYTA) { Level(30); Moves(MOVE_HEADBUTT); Speed(4); } + } WHEN { + TURN { MOVE(player, MOVE_CHARM); } + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will not switch out if Pokemon would faint to hazards unless party member can clear them") +{ + u32 move1; + + PARAMETRIZE { move1 = MOVE_SCRATCH; } + PARAMETRIZE { move1 = MOVE_RAPID_SPIN; } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_RAPID_SPIN) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_EARTHQUAKE) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_HEADBUTT) == DAMAGE_CATEGORY_PHYSICAL); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_HITMONTOP) { Level(30); Moves(MOVE_CHARM, MOVE_SCRATCH, MOVE_STEALTH_ROCK, MOVE_EARTHQUAKE); Ability(ABILITY_INTIMIDATE); Speed(5); } + OPPONENT(SPECIES_GRIMER) { Level(30); Moves(MOVE_SCRATCH); Items(ITEM_FOCUS_SASH); Speed(4); } + OPPONENT(SPECIES_PONYTA) { Level(30); Moves(MOVE_HEADBUTT, move1); Speed(4); } + } WHEN { + TURN { MOVE(player, MOVE_STEALTH_ROCK); } + TURN { MOVE(player, MOVE_EARTHQUAKE); } + TURN { MOVE(player, MOVE_CHARM); } + TURN { // If the AI has a mon that can remove hazards, don't prevent them switching out + MOVE(player, MOVE_CHARM); + if (move1 == MOVE_RAPID_SPIN) + EXPECT_SWITCH(opponent, 1); + else if (move1 == MOVE_SCRATCH) + EXPECT_MOVE(opponent, MOVE_SCRATCH); + } + } +} + +// Trapping behaviour +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch in trapping mon mid battle") +{ + u32 aiSmartSwitchingFlag = 0; + PARAMETRIZE { aiSmartSwitchingFlag = 0; } + PARAMETRIZE { aiSmartSwitchingFlag = AI_FLAG_SMART_SWITCHING; } + PASSES_RANDOMLY(SHOULD_SWITCH_TRAPPER_PERCENTAGE, 100, RNG_AI_SWITCH_TRAPPER); + GIVEN { + ASSUME(GetSpeciesType(SPECIES_GOLURK, 0) == TYPE_GROUND); + ASSUME(GetSpeciesType(SPECIES_GOLURK, 1) == TYPE_GHOST); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSmartSwitchingFlag); + PLAYER(SPECIES_ELECTRODE) { Speed(4); Moves(MOVE_THUNDERBOLT, MOVE_AURA_SPHERE, MOVE_PROTECT); } + PLAYER(SPECIES_WOBBUFFET) { Speed(1); }; + OPPONENT(SPECIES_SNORLAX) { Speed(1); Moves(MOVE_HEADBUTT); } + OPPONENT(SPECIES_DUGTRIO) { Speed(3); Ability(ABILITY_ARENA_TRAP); Moves(MOVE_EARTHQUAKE); } + OPPONENT(SPECIES_GOLURK) { Speed(5); Moves(MOVE_EARTHQUAKE); } + } WHEN { + if (aiSmartSwitchingFlag == AI_FLAG_SMART_SWITCHING) + TURN { MOVE(player, MOVE_AURA_SPHERE) ; EXPECT_SWITCH(opponent, 1); } + else + TURN { MOVE(player, MOVE_AURA_SPHERE) ; EXPECT_MOVE(opponent, MOVE_HEADBUTT); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will switch in trapping mon after KO") +{ + u32 aiSmartMonChoicesFlag = 0; // Enables trapping behaviour after KOs + PARAMETRIZE { aiSmartMonChoicesFlag = 0; } // No trapping behaviour + PARAMETRIZE { aiSmartMonChoicesFlag = AI_FLAG_SMART_MON_CHOICES; } // Traps with mid battle switches + GIVEN { + ASSUME(GetSpeciesType(SPECIES_MAWILE, 0) == TYPE_STEEL); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSmartMonChoicesFlag); + PLAYER(SPECIES_MAWILE) { Speed(2); Moves(MOVE_PROTECT, MOVE_SCRATCH); } + PLAYER(SPECIES_WOBBUFFET) { Speed(1); } + OPPONENT(SPECIES_SNORLAX) { Speed(3); Moves(MOVE_SELF_DESTRUCT); } + OPPONENT(SPECIES_MAGNEZONE) { Speed(1); Ability(ABILITY_MAGNET_PULL); Moves(MOVE_SHOCK_WAVE); } + OPPONENT(SPECIES_MEGANIUM) { Speed(3); Moves(MOVE_EARTH_POWER); } + } WHEN { + if (aiSmartMonChoicesFlag == AI_FLAG_SMART_MON_CHOICES) + TURN{ MOVE(player, MOVE_PROTECT); EXPECT_MOVE(opponent, MOVE_SELF_DESTRUCT); EXPECT_SEND_OUT(opponent, 1); } + else + TURN{ MOVE(player, MOVE_PROTECT); EXPECT_MOVE(opponent, MOVE_SELF_DESTRUCT); EXPECT_SEND_OUT(opponent, 2); } + } +} + +AI_SINGLE_BATTLE_TEST("AI won't use trapping behaviour if player only has 1 mon left") +{ + u32 aiSmartMonChoicesFlag = 0; // Enables trapping behaviour after KOs + PARAMETRIZE { aiSmartMonChoicesFlag = 0; } // No trapping behaviour + PARAMETRIZE { aiSmartMonChoicesFlag = AI_FLAG_SMART_MON_CHOICES; } // Traps with mid battle switches + GIVEN { + ASSUME(GetSpeciesType(SPECIES_MAWILE, 0) == TYPE_STEEL); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSmartMonChoicesFlag); + PLAYER(SPECIES_MAWILE) { Speed(2); Moves(MOVE_PROTECT, MOVE_SCRATCH); } + OPPONENT(SPECIES_SNORLAX) { Speed(3); Moves(MOVE_SELF_DESTRUCT); } + OPPONENT(SPECIES_MAGNEZONE) { Speed(1); Ability(ABILITY_MAGNET_PULL); Moves(MOVE_SHOCK_WAVE); } + OPPONENT(SPECIES_MEGANIUM) { Speed(3); Moves(MOVE_EARTH_POWER); } + } WHEN { + TURN{ MOVE(player, MOVE_PROTECT); EXPECT_MOVE(opponent, MOVE_SELF_DESTRUCT); EXPECT_SEND_OUT(opponent, 2); } + } +} + +AI_SINGLE_BATTLE_TEST("AI will trap player using Trace if player has a trapper") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_TRAPPER_PERCENTAGE, 100, RNG_AI_SWITCH_TRAPPER); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_DUGTRIO) { Ability(ABILITY_ARENA_TRAP); Moves(MOVE_ROCK_TOMB); } + PLAYER(SPECIES_DUGTRIO); + OPPONENT(SPECIES_GENGAR); + OPPONENT(SPECIES_PORYGON2) { Ability(ABILITY_TRACE); Items(ITEM_EVIOLITE); Moves(MOVE_ICE_BEAM); } + } WHEN { + TURN { MOVE(player, MOVE_ROCK_TOMB); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if mon would be OKHO'd and they have a good switchin 50% of the time") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_HASBADODDS_PERCENTAGE, 100, RNG_AI_SWITCH_HASBADODDS); + GIVEN { + ASSUME(GetSpeciesType(SPECIES_RHYDON, 0) == TYPE_GROUND); + ASSUME(GetSpeciesType(SPECIES_PELIPPER, 0) == TYPE_WATER); + ASSUME(GetSpeciesType(SPECIES_PELIPPER, 1) == TYPE_FLYING); + ASSUME(GetMoveType(MOVE_THUNDERBOLT) == TYPE_ELECTRIC); + ASSUME(GetMoveType(MOVE_EARTHQUAKE) == TYPE_GROUND); + + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_ELECTRODE) { Moves(MOVE_THUNDERBOLT, MOVE_THUNDER_WAVE, MOVE_THUNDER_SHOCK); } + OPPONENT(SPECIES_PELIPPER) { Moves(MOVE_EARTHQUAKE); }; + OPPONENT(SPECIES_RHYDON) { Moves(MOVE_EARTHQUAKE); Ability(ABILITY_ROCK_HEAD); } + } WHEN { + TURN { MOVE(player, MOVE_THUNDERBOLT) ; EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("Switch AI: AI will switch out if it can't deal damage to a mon with Wonder Guard") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_WONDER_GUARD_PERCENTAGE, 100, RNG_AI_SWITCH_WONDER_GUARD); + GIVEN { + ASSUME(GetSpeciesType(SPECIES_SHEDINJA, 0) == TYPE_BUG); + ASSUME(GetSpeciesType(SPECIES_SHEDINJA, 1) == TYPE_GHOST); + ASSUME(GetSpeciesAbility(SPECIES_SHEDINJA, 0) == ABILITY_WONDER_GUARD); + ASSUME(GetSpeciesAbility(SPECIES_SHEDINJA, 1) == ABILITY_NONE); + ASSUME(GetSpeciesAbility(SPECIES_SHEDINJA, 2) == ABILITY_NONE); + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + ASSUME(GetMoveType(MOVE_SHADOW_BALL) == TYPE_GHOST); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_SHEDINJA) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SHADOW_BALL); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH) ; EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it can't deal damage to a mon with Wonder Guard") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_WONDER_GUARD_PERCENTAGE, 100, RNG_AI_SWITCH_WONDER_GUARD); + GIVEN { + ASSUME(GetSpeciesType(SPECIES_SHEDINJA, 0) == TYPE_BUG); + ASSUME(GetSpeciesType(SPECIES_SHEDINJA, 1) == TYPE_GHOST); + ASSUME(GetSpeciesAbility(SPECIES_SHEDINJA, 0) == ABILITY_WONDER_GUARD); + ASSUME(GetSpeciesAbility(SPECIES_SHEDINJA, 1) == ABILITY_NONE); + ASSUME(GetSpeciesAbility(SPECIES_SHEDINJA, 2) == ABILITY_NONE); + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + ASSUME(GetMoveType(MOVE_SHADOW_BALL) == TYPE_GHOST); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_SHEDINJA) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SHADOW_BALL); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH) ; EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has been Toxic'd for at least two turns 50% of the time with more than 1/3 HP remaining with good switchin") +{ + u32 species = SPECIES_NONE, odds = 0; + PARAMETRIZE { species = SPECIES_ZIGZAGOON, odds = 0; } + PARAMETRIZE { species = SPECIES_HARIYAMA, odds = SHOULD_SWITCH_BADLY_POISONED_PERCENTAGE; } + PASSES_RANDOMLY(odds, 100, RNG_AI_SWITCH_BADLY_POISONED); + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + ASSUME(GetMovePower(MOVE_AURA_SPHERE) == 80); // Gen 5's 90 power causes too much damage + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH, MOVE_CELEBRATE, MOVE_TOXIC, MOVE_AURA_SPHERE); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + OPPONENT(species) { Moves(MOVE_ROCK_SMASH); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC); EXPECT_MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_AURA_SPHERE); EXPECT_MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_AURA_SPHERE); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has been Curse'd 50% of the time") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_CURSED_PERCENTAGE, 100, RNG_AI_SWITCH_CURSED); + GIVEN { + ASSUME(GetMoveEffect(MOVE_CURSE) == EFFECT_CURSE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_DUSCLOPS) { Moves(MOVE_FIRE_PUNCH, MOVE_CURSE); } + PLAYER(SPECIES_MILOTIC) { Moves(MOVE_WATER_GUN); } + OPPONENT(SPECIES_DUSCLOPS) { Moves(MOVE_SHADOW_BALL); } + OPPONENT(SPECIES_DUSCLOPS) { Moves(MOVE_SHADOW_BALL); } + } WHEN { + TURN { MOVE(player, MOVE_CURSE) ; EXPECT_MOVE(opponent, MOVE_SHADOW_BALL); } + TURN { MOVE(player, MOVE_FIRE_PUNCH); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has been Nightmare'd 33% of the time") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_NIGHTMARE_PERCENTAGE, 100, RNG_AI_SWITCH_NIGHTMARE); + GIVEN { + ASSUME(GetMoveEffect(MOVE_NIGHTMARE) == EFFECT_NIGHTMARE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_GENGAR) { Moves(MOVE_NIGHTMARE); } + OPPONENT(SPECIES_DUSCLOPS) { Moves(MOVE_SHADOW_BALL); Status1(STATUS1_SLEEP); } + OPPONENT(SPECIES_DUSCLOPS) { Moves(MOVE_SHADOW_BALL); } + } WHEN { + TURN { MOVE(player, MOVE_NIGHTMARE) ; EXPECT_MOVE(opponent, MOVE_SHADOW_BALL); } + TURN { MOVE(player, MOVE_NIGHTMARE) ; EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has been Leech Seed'd 25% of the time") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_SEEDED_PERCENTAGE, 100, RNG_AI_SWITCH_SEEDED); + GIVEN { + ASSUME(GetMoveEffect(MOVE_LEECH_SEED) == EFFECT_LEECH_SEED); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_WHIMSICOTT) { Moves(MOVE_LEECH_SEED); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + } WHEN { + TURN { MOVE(player, MOVE_LEECH_SEED) ; EXPECT_MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_LEECH_SEED); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has been infatuated") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_INFATUATION_PERCENTAGE, 100, RNG_AI_SWITCH_INFATUATION); + GIVEN { + ASSUME(GetMoveEffect(MOVE_ATTRACT) == EFFECT_ATTRACT); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_LUVDISC) { Moves(MOVE_ATTRACT); Gender(MON_FEMALE); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); Gender(MON_MALE); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); Gender(MON_MALE); } + } WHEN { + TURN { MOVE(player, MOVE_ATTRACT) ; EXPECT_MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_ATTRACT) ; EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has been Yawn'd with more than 1/3 HP remaining and it has a good switchin") +{ + u32 hp; + PARAMETRIZE { hp = 30; } + PARAMETRIZE { hp = 10; } + PASSES_RANDOMLY(SHOULD_SWITCH_YAWN_PERCENTAGE, 100, RNG_AI_SWITCH_YAWN); + GIVEN { + ASSUME(GetMoveEffect(MOVE_YAWN) == EFFECT_YAWN); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_SLAKOTH) { Moves(MOVE_SCRATCH, MOVE_YAWN); } + OPPONENT(SPECIES_SLAKOTH) { Moves(MOVE_SCRATCH); HP(hp); MaxHP(30); } + OPPONENT(SPECIES_SLAKOTH) { Moves(MOVE_HEADBUTT); } + } WHEN { + TURN { MOVE(player, MOVE_YAWN) ; EXPECT_MOVE(opponent, MOVE_SCRATCH); } + if (hp == 30) + TURN { MOVE(player, MOVE_YAWN) ; EXPECT_SWITCH(opponent, 1); } + else + TURN { MOVE(player, MOVE_YAWN) ; EXPECT_MOVE(opponent, MOVE_SCRATCH); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will not switch out if it has been Yawn'd with more than 1/3 HP remaining and it does not have a good switchin") +{ + u32 hp; + PARAMETRIZE { hp = 30; } + PARAMETRIZE { hp = 10; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_YAWN) == EFFECT_YAWN); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_SLAKOTH) { Moves(MOVE_SCRATCH, MOVE_YAWN); } + OPPONENT(SPECIES_SLAKOTH) { Moves(MOVE_SCRATCH); HP(hp); MaxHP(30); } + OPPONENT(SPECIES_SLAKOTH) { Level(1); Moves(MOVE_HEADBUTT); } + } WHEN { + TURN { MOVE(player, MOVE_YAWN) ; EXPECT_MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH) ; EXPECT_MOVE(opponent, MOVE_SCRATCH); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has been Yawn'd with more than 1/3 HP remaining (Doubles)") +{ + u32 hp; + PARAMETRIZE { hp = 30; } + PARAMETRIZE { hp = 10; } + PASSES_RANDOMLY(SHOULD_SWITCH_YAWN_PERCENTAGE, 100, RNG_AI_SWITCH_YAWN); + GIVEN { + ASSUME(GetMoveEffect(MOVE_YAWN) == EFFECT_YAWN); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_SLAKOTH) { Moves(MOVE_SCRATCH, MOVE_CELEBRATE, MOVE_YAWN); } + PLAYER(SPECIES_SLAKOTH) { Moves(MOVE_SCRATCH, MOVE_CELEBRATE, MOVE_YAWN); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); HP(hp); MaxHP(30); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_YAWN, target: opponentLeft); MOVE(playerRight, MOVE_CELEBRATE, target: opponentLeft); } + if (hp == 30) + TURN { MOVE(playerLeft, MOVE_YAWN, target: opponentLeft); MOVE(playerRight, MOVE_CELEBRATE, target: opponentLeft); EXPECT_SWITCH(opponentLeft, 2); } + else + TURN { MOVE(playerLeft, MOVE_YAWN, target: opponentLeft); MOVE(playerRight, MOVE_CELEBRATE, target: opponentLeft); EXPECT_MOVE(opponentLeft, MOVE_SCRATCH); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if player's mon is semi-invulnerable and it has a good switchin") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_FREE_TURN_PERCENTAGE, 100, RNG_AI_SWITCH_FREE_TURN); + GIVEN { + ASSUME(GetMoveType(MOVE_DIVE) == TYPE_WATER); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_LUVDISC) { Level(1); Moves(MOVE_DIVE); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_PIKACHU) { Moves(MOVE_THUNDERBOLT); } + } WHEN { + TURN { MOVE(player, MOVE_DIVE) ; EXPECT_MOVE(opponent, MOVE_SCRATCH); } + TURN { SKIP_TURN(player); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has an absorber but current mon has SE move 33% of the time") +{ + PASSES_RANDOMLY(33, 100, RNG_AI_SWITCH_ABSORBING_STAY_IN); + GIVEN { + ASSUME(GetMoveType(MOVE_WATER_GUN) == TYPE_WATER); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_LUVDISC) { Moves(MOVE_WATER_GUN); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SHOCK_WAVE); } + OPPONENT(SPECIES_MANTINE) { Moves(MOVE_SCRATCH); Ability(ABILITY_WATER_ABSORB); } + } WHEN { + TURN { MOVE(player, MOVE_WATER_GUN) ; EXPECT_MOVE(opponent, MOVE_SHOCK_WAVE); } + TURN { MOVE(player, MOVE_WATER_GUN) ; EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if player's mon is charging and it has an absorber") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_ABSORBS_MOVE_PERCENTAGE, 100, RNG_AI_SWITCH_ABSORBING); + GIVEN { + ASSUME(GetMoveType(MOVE_SOLAR_BEAM) == TYPE_GRASS); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_BELLOSSOM) { Moves(MOVE_SOLAR_BEAM, MOVE_THUNDERBOLT); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_AZUMARILL) { Moves(MOVE_PLAY_ROUGH); Ability(ABILITY_SAP_SIPPER); } + } WHEN { + TURN { MOVE(player, MOVE_SOLAR_BEAM) ; EXPECT_MOVE(opponent, MOVE_SCRATCH); } + TURN { SKIP_TURN(player); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if player's mon is charging and it has a good switchin immunity (type)") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_FREE_TURN_PERCENTAGE, 100, RNG_AI_SWITCH_FREE_TURN); + GIVEN { + ASSUME(GetMoveType(MOVE_DIG) == TYPE_GROUND); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_SANDSHREW) { Moves(MOVE_DIG); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_SWELLOW) { Moves(MOVE_WING_ATTACK); } + } WHEN { + TURN { MOVE(player, MOVE_DIG) ; EXPECT_MOVE(opponent, MOVE_SCRATCH); } + TURN { SKIP_TURN(player); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if player's mon is charging and it has a good switchin immunity (ability)") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_ABSORBS_MOVE_PERCENTAGE, 100, RNG_AI_SWITCH_ABSORBING); + GIVEN { + ASSUME(GetMoveType(MOVE_DIG) == TYPE_GROUND); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_SANDSHREW) { Moves(MOVE_DIG); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_BRONZONG) { Moves(MOVE_PSYCHIC); Ability(ABILITY_LEVITATE); } + } WHEN { + TURN { MOVE(player, MOVE_DIG) ; EXPECT_MOVE(opponent, MOVE_SCRATCH); } + TURN { SKIP_TURN(player); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has an absorber") +{ + u32 aiMon; u32 move; + enum Ability absorbingAbility; + PARAMETRIZE { aiMon = SPECIES_NINETALES; absorbingAbility = ABILITY_FLASH_FIRE; move = MOVE_FLAMETHROWER;} + PARAMETRIZE { aiMon = SPECIES_MANTINE; absorbingAbility = ABILITY_WATER_ABSORB; move = MOVE_SURF;} + PARAMETRIZE { aiMon = SPECIES_TOXICROAK; absorbingAbility = ABILITY_DRY_SKIN; move = MOVE_SURF;} + PARAMETRIZE { aiMon = SPECIES_GASTRODON; absorbingAbility = ABILITY_STORM_DRAIN; move = MOVE_SURF;} + PARAMETRIZE { aiMon = SPECIES_JOLTEON; absorbingAbility = ABILITY_VOLT_ABSORB; move = MOVE_THUNDERBOLT;} + PARAMETRIZE { aiMon = SPECIES_ELECTIVIRE; absorbingAbility = ABILITY_MOTOR_DRIVE; move = MOVE_THUNDERBOLT;} + PARAMETRIZE { aiMon = SPECIES_MANECTRIC; absorbingAbility = ABILITY_LIGHTNING_ROD; move = MOVE_THUNDERBOLT;} + PARAMETRIZE { aiMon = SPECIES_ELECTIVIRE; absorbingAbility = ABILITY_MOTOR_DRIVE; move = MOVE_THUNDERBOLT;} + PARAMETRIZE { aiMon = SPECIES_AZUMARILL; absorbingAbility = ABILITY_SAP_SIPPER; move = MOVE_GIGA_DRAIN;} + PARAMETRIZE { aiMon = SPECIES_ORTHWORM; absorbingAbility = ABILITY_EARTH_EATER; move = MOVE_EARTHQUAKE;} + PARAMETRIZE { aiMon = SPECIES_BRONZONG; absorbingAbility = ABILITY_LEVITATE; move = MOVE_EARTHQUAKE;} + PARAMETRIZE { aiMon = SPECIES_ELECTRODE; absorbingAbility = ABILITY_SOUNDPROOF; move = MOVE_HYPER_VOICE;} + PARAMETRIZE { aiMon = SPECIES_CHESNAUGHT; absorbingAbility = ABILITY_BULLETPROOF; move = MOVE_SLUDGE_BOMB;} + PARAMETRIZE { aiMon = SPECIES_BRAMBLEGHAST; absorbingAbility = ABILITY_WIND_RIDER; move = MOVE_HURRICANE;} + GIVEN { + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, GEN_5); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_ZIGZAGOON) { Moves(move); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + OPPONENT(aiMon) { Moves(MOVE_SCRATCH); Ability(absorbingAbility); } + } WHEN { + TURN { MOVE(player, move); EXPECT_MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, move); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if opponent uses two-turn move and it has a switchin that wins 1v1") +{ + u32 move; + PARAMETRIZE { move = MOVE_SKY_ATTACK; } + PARAMETRIZE { move = MOVE_FLY; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_FLY) == EFFECT_SEMI_INVULNERABLE); + ASSUME(GetMoveEffect(MOVE_SKY_ATTACK) == EFFECT_TWO_TURNS_ATTACK); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_SWELLOW) { Moves(move); } + OPPONENT(SPECIES_MILOTIC) { Moves(MOVE_SURF); } + OPPONENT(SPECIES_LAIRON) { Moves(MOVE_ROCK_SLIDE); } + } WHEN { + TURN { MOVE(player, move); EXPECT_MOVE(opponent, MOVE_SURF); } + TURN { SKIP_TURN(player); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("Switch AI: AI will switch out if badly statused with >= 50% HP remaining and has Natural Cure and a good switchin 66% of the time") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_NATURAL_CURE_STRONG_PERCENTAGE, 100, RNG_AI_SWITCH_NATURAL_CURE); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_ODDISH) { Moves(MOVE_TOXIC, MOVE_SCRATCH); } + OPPONENT(SPECIES_SWABLU) { Ability(ABILITY_NATURAL_CURE); Moves(MOVE_SCRATCH, MOVE_PECK); } + OPPONENT(SPECIES_SWABLU) { Ability(ABILITY_NATURAL_CURE); Moves(MOVE_SCRATCH); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC); EXPECT_MOVE(opponent, MOVE_PECK); } + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("Switch AI: AI will switch out if it has <= 66% HP remaining and has Regenerator and a good switchin 50% of the time") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_REGENERATOR_PERCENTAGE, 100, RNG_AI_SWITCH_REGENERATOR); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_SLOWBRO) { MaxHP(100); HP(65); Ability(ABILITY_REGENERATOR); Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_SLOWBRO) { Ability(ABILITY_REGENERATOR); Moves(MOVE_SCRATCH); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has been Encore'd into a status move") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_ENCORE_STATUS_PERCENTAGE, 100, RNG_AI_SWITCH_ENCORE); + GIVEN { + ASSUME(GetMoveEffect(MOVE_ENCORE) == EFFECT_ENCORE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_AZURILL) { Moves(MOVE_SCRATCH, MOVE_ENCORE); } + OPPONENT(SPECIES_ODDISH) { Moves(MOVE_TOXIC, MOVE_SWEET_SCENT, MOVE_INGRAIN, MOVE_SCRATCH); } + OPPONENT(SPECIES_ARON) { Moves(MOVE_METAL_CLAW); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_TOXIC); MOVE(player, MOVE_ENCORE); } + TURN { MOVE(player, MOVE_ENCORE); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will stay in if Encore'd into super effective move") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ENCORE) == EFFECT_ENCORE); + ASSUME(GetSpeciesType(SPECIES_AZURILL, 0) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_AZURILL, 1) == TYPE_FAIRY); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_AZURILL) { Moves(MOVE_SCRATCH, MOVE_ENCORE); } + OPPONENT(SPECIES_ODDISH) { Moves(MOVE_ACID); } + OPPONENT(SPECIES_ARON) { Moves(MOVE_METAL_CLAW); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_ACID); MOVE(player, MOVE_ENCORE); } + TURN { EXPECT_MOVE(opponent, MOVE_ACID); MOVE(player, MOVE_SCRATCH); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if Encore'd into neutral move with good switchin 50% of the time") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_ENCORE_DAMAGE_PERCENTAGE, 100, RNG_AI_SWITCH_ENCORE); + GIVEN { + WITH_CONFIG(CONFIG_ENCORE_TARGET, GEN_3); + ASSUME(GetMoveEffect(MOVE_ENCORE) == EFFECT_ENCORE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_AZURILL) { Moves(MOVE_SCRATCH, MOVE_ENCORE); } + OPPONENT(SPECIES_ODDISH) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_ARON) { Moves(MOVE_METAL_CLAW); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_ENCORE); } + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if Encore'd into neutral move with good switchin 50% of the time (Gen 5+)") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_ENCORE_DAMAGE_PERCENTAGE, 100, RNG_AI_SWITCH_ENCORE); + GIVEN { + ASSUME(GetMoveEffect(MOVE_ENCORE) == EFFECT_ENCORE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_AZURILL) { Moves(MOVE_SCRATCH, MOVE_ENCORE); } + OPPONENT(SPECIES_ODDISH) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_ARON) { Moves(MOVE_METAL_CLAW); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_ENCORE); } + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("Switch AI: AI will switch out if mon has Truant and opponent has Protect") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_TRUANT_PERCENTAGE, 100, RNG_AI_SWITCH_TRUANT); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_ARON) { Moves(MOVE_SCRATCH, MOVE_PROTECT); } + OPPONENT(SPECIES_SLAKING) { Ability(ABILITY_TRUANT); Moves(MOVE_BRICK_BREAK); } + OPPONENT(SPECIES_ARON) { Moves(MOVE_SCRATCH); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_BRICK_BREAK); MOVE(player, MOVE_PROTECT); } + TURN { EXPECT_SWITCH(opponent, 1); MOVE(player, MOVE_SCRATCH); } + } +} + +AI_SINGLE_BATTLE_TEST("Switch AI: AI will switch out if mon has Truant and opponent has invulnerability move and is faster") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_TRUANT_PERCENTAGE, 100, RNG_AI_SWITCH_TRUANT); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_SWELLOW) { Speed(5); Moves(MOVE_FLY); } + OPPONENT(SPECIES_SLAKING) { Speed(4); Ability(ABILITY_TRUANT); Moves(MOVE_ROCK_SLIDE); } + OPPONENT(SPECIES_ARON) { Speed(4); Moves(MOVE_SCRATCH); } + } WHEN { + TURN { MOVE(player, MOVE_FLY); EXPECT_MOVE(opponent, MOVE_ROCK_SLIDE); } + TURN { SKIP_TURN(player); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if main attacking stat lowered by 2 stages with good switchin candidate 50% of the time") +{ + u32 aiSpecies = SPECIES_NONE, aiMove = MOVE_NONE, move = MOVE_NONE; + + PASSES_RANDOMLY(SHOULD_SWITCH_ATTACKING_STAT_MINUS_TWO_PERCENTAGE, 100, RNG_AI_SWITCH_STATS_LOWERED); + PARAMETRIZE {move = MOVE_CHARM; aiSpecies = SPECIES_FLAREON; aiMove = MOVE_FIRE_FANG; }; + PARAMETRIZE {move = MOVE_EERIE_IMPULSE; aiSpecies = SPECIES_ESPEON; aiMove = MOVE_CONFUSION; }; + + GIVEN { + ASSUME(GetMoveEffect(MOVE_CHARM) == EFFECT_ATTACK_DOWN_2); + ASSUME(GetMoveEffect(MOVE_EERIE_IMPULSE) == EFFECT_SPECIAL_ATTACK_DOWN_2); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_ARON) { Moves(move, MOVE_SCRATCH); } + OPPONENT(aiSpecies) { Moves(aiMove); } + OPPONENT(SPECIES_MILOTIC) { Moves(MOVE_SURF); } + } WHEN { + TURN { MOVE(player, move); EXPECT_MOVE(opponent, aiMove); } + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if main attacking stat lowered by 3+ stages") +{ + u32 aiSpecies = SPECIES_NONE, aiMove = MOVE_NONE, move = MOVE_NONE, move2 = MOVE_NONE; + + PASSES_RANDOMLY(SHOULD_SWITCH_ATTACKING_STAT_MINUS_THREE_PLUS_PERCENTAGE, 100, RNG_AI_SWITCH_STATS_LOWERED); + PARAMETRIZE {move = MOVE_GROWL; move2 = MOVE_CHARM; aiSpecies = SPECIES_FLAREON; aiMove = MOVE_FIRE_FANG; }; + PARAMETRIZE {move = MOVE_CONFIDE; move2 = MOVE_EERIE_IMPULSE; aiSpecies = SPECIES_ESPEON; aiMove = MOVE_STORED_POWER; }; + + GIVEN { + ASSUME(GetMoveEffect(MOVE_CHARM) == EFFECT_ATTACK_DOWN_2); + ASSUME(GetMoveEffect(MOVE_EERIE_IMPULSE) == EFFECT_SPECIAL_ATTACK_DOWN_2); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_ARON) { Moves(move, move2, MOVE_SCRATCH); } + OPPONENT(aiSpecies) { Moves(aiMove); } + OPPONENT(SPECIES_MILOTIC) { Moves(MOVE_SURF); } + } WHEN { + TURN { MOVE(player, move); EXPECT_MOVE(opponent, aiMove); } + TURN { MOVE(player, move2); EXPECT_MOVE(opponent, aiMove); } + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("Switch AI: AI will switch into mon with good type matchup and SE move if current mon has no SE move and no stats raised") +{ + u32 odds = 0, species = SPECIES_NONE, move = MOVE_NONE; + PARAMETRIZE { odds = 33; species = SPECIES_SCIZOR; move = MOVE_X_SCISSOR; } + PARAMETRIZE { odds = 50; species = SPECIES_DUSCLOPS; move = MOVE_SHADOW_BALL; } + PASSES_RANDOMLY(odds, 100, RNG_AI_SWITCH_SE_DEFENSIVE); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_MUNNA) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_MUNNA) { Moves(MOVE_SCRATCH); } + OPPONENT(species) { Moves(move); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI correctly handles abilities when scoring moves") +{ + GIVEN { + WITH_CONFIG(CONFIG_PRANKSTER_DARK_TYPES, GEN_7); + ASSUME(GetSpeciesType(SPECIES_GRENINJA, 1) == TYPE_DARK); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_GRENINJA) { Moves(MOVE_WATER_GUN); } + OPPONENT(SPECIES_WHIMSICOTT) { Ability(ABILITY_PRANKSTER); Moves(MOVE_LEECH_SEED, MOVE_STUN_SPORE, MOVE_ABSORB); } + OPPONENT(SPECIES_WHIMSICOTT) { Ability(ABILITY_INFILTRATOR); } + } WHEN { + TURN { MOVE(player, MOVE_WATER_GUN); EXPECT_MOVE(opponent, MOVE_ABSORB); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI won't switch out if Yawn'd with only Ace mon remaining") +{ + u32 aceFlag; + PARAMETRIZE{ aceFlag = 0; } + PARAMETRIZE{ aceFlag = AI_FLAG_ACE_POKEMON; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_YAWN) == EFFECT_YAWN); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | aceFlag | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_SLAKOTH) { Moves(MOVE_SCRATCH, MOVE_YAWN); } + OPPONENT(SPECIES_SLAKOTH) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_SLAKOTH) { Moves(MOVE_HEADBUTT); } + } WHEN { + TURN { MOVE(player, MOVE_YAWN); EXPECT_MOVE(opponent, MOVE_SCRATCH); } + if (aceFlag) + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_SCRATCH); } + else + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI won't switch in ace mon after U-Turn if other options available") +{ + u32 aceFlag; + PARAMETRIZE{ aceFlag = 0; } + PARAMETRIZE{ aceFlag = AI_FLAG_ACE_POKEMON; } + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | aceFlag | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_SURF); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_U_TURN); } + OPPONENT(SPECIES_NUMEL) { Level(5); Moves(MOVE_SPLASH); } + OPPONENT(SPECIES_SCIZOR) { Moves(MOVE_BUG_BITE); } + } WHEN { + if (aceFlag) + TURN { EXPECT_MOVE(opponent, MOVE_U_TURN); EXPECT_SEND_OUT(opponent, 1); MOVE(player, MOVE_SURF); } + else + TURN { EXPECT_MOVE(opponent, MOVE_U_TURN); EXPECT_SEND_OUT(opponent, 2); MOVE(player, MOVE_SURF); } + } +} + +AI_SINGLE_BATTLE_TEST("Switch AI: AI won't switch in ace mon after U-Turn if other options available") +{ + u32 aceFlag; + PARAMETRIZE{ aceFlag = 0; } + PARAMETRIZE{ aceFlag = AI_FLAG_ACE_POKEMON; } + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | aceFlag | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_SURF); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_U_TURN); } + OPPONENT(SPECIES_NUMEL) { Level(5); Moves(MOVE_SPLASH); } + OPPONENT(SPECIES_SCIZOR) { Moves(MOVE_BUG_BITE); } + } WHEN { + if (aceFlag) + TURN { EXPECT_MOVE(opponent, MOVE_U_TURN); EXPECT_SEND_OUT(opponent, 1); MOVE(player, MOVE_SURF); } + else + TURN { EXPECT_MOVE(opponent, MOVE_U_TURN); EXPECT_SEND_OUT(opponent, 2); MOVE(player, MOVE_SURF); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI won't switch in absorbing mon immediately after sending out new mon") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_BLAZIKEN) { Moves(MOVE_FLAMETHROWER, MOVE_CLOSE_COMBAT); } + OPPONENT(SPECIES_FERROTHORN) { Moves(MOVE_GYRO_BALL); } + OPPONENT(SPECIES_DIALGA) { Moves(MOVE_DRACO_METEOR); } + OPPONENT(SPECIES_HEATRAN) { Moves(MOVE_EARTH_POWER, MOVE_FLAMETHROWER); } + } WHEN { + TURN { MOVE(player, MOVE_FLAMETHROWER); EXPECT_SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_CLOSE_COMBAT); EXPECT_MOVE(opponent, MOVE_DRACO_METEOR); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will consider choice-locked player when determining which mon to send out") +{ + u32 item; + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_CHOICE_BAND; } + GIVEN { + ASSUME(gItemsInfo[ITEM_CHOICE_BAND].holdEffect == HOLD_EFFECT_CHOICE_BAND); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_JOLTEON) { Speed(5); Moves(MOVE_EARTHQUAKE, MOVE_THUNDERBOLT); Items(item); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(4); HP(1); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_MACHAMP) { Speed(4); Moves(MOVE_REVENGE); } + OPPONENT(SPECIES_GYARADOS) { Speed(4); Moves(MOVE_EARTHQUAKE); } + } WHEN { + TURN { MOVE(player, MOVE_EARTHQUAKE); EXPECT_MOVE(opponent, MOVE_TACKLE); item == ITEM_NONE ? EXPECT_SEND_OUT(opponent, 1) : EXPECT_SEND_OUT(opponent, 2); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if all moves deal zero damage") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_ALL_SCORES_BAD_PERCENTAGE, 100, RNG_AI_SWITCH_ALL_SCORES_BAD); + GIVEN { + ASSUME(GetMoveEffect(MOVE_WILL_O_WISP) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_WILL_O_WISP) == MOVE_EFFECT_BURN); + ASSUME(GetMoveEffect(MOVE_POLTERGEIST) == EFFECT_POLTERGEIST); + ASSUME(GetMoveType(MOVE_SCALD) == TYPE_WATER); + ASSUME(GetMoveType(MOVE_EARTHQUAKE) == TYPE_GROUND); + ASSUME(GetSpeciesType(SPECIES_MANTINE, 1) == TYPE_FLYING); + ASSUME(GetItemHoldEffect(ITEM_WATER_GEM) == HOLD_EFFECT_GEMS); + ASSUME(GetItemSecondaryId(ITEM_WATER_GEM) == TYPE_WATER); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_MANTINE) { Speed(5); Moves(MOVE_ROOST, MOVE_SCALD); Ability(ABILITY_WATER_VEIL); Items(ITEM_WATER_GEM); } + OPPONENT(SPECIES_DUSKNOIR) { Speed(6); Moves(MOVE_WILL_O_WISP, MOVE_POLTERGEIST, MOVE_EARTHQUAKE); } + OPPONENT(SPECIES_ZIGZAGOON) { Speed(6); Moves(MOVE_TACKLE); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_POLTERGEIST); MOVE(player, MOVE_SCALD); } + TURN { EXPECT_SWITCH(opponent, 1); MOVE(player, MOVE_ROOST); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if all moves deal zero damage (absorbing ability)") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_ALL_SCORES_BAD_PERCENTAGE, 100, RNG_AI_SWITCH_ALL_SCORES_BAD); + GIVEN { + ASSUME(GetMoveType(MOVE_THUNDER_PUNCH) == TYPE_ELECTRIC); + ASSUME(GetMoveType(MOVE_FAKE_OUT) == TYPE_NORMAL); + ASSUME(GetMoveType(MOVE_RETURN) == TYPE_NORMAL); + ASSUME(GetMoveType(MOVE_DRAIN_PUNCH) == TYPE_FIGHTING); + ASSUME(gSpeciesInfo[SPECIES_MAROWAK_ALOLA].types[1] == TYPE_GHOST); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_MAROWAK_ALOLA) { Ability(ABILITY_LIGHTNING_ROD); Moves(MOVE_SHADOW_BONE); } + OPPONENT(SPECIES_LOPUNNY) { Moves(MOVE_FAKE_OUT, MOVE_RETURN, MOVE_DRAIN_PUNCH, MOVE_THUNDER_PUNCH); Ability(ABILITY_LIMBER); } + OPPONENT(SPECIES_CHANDELURE) { Moves(MOVE_SHADOW_BALL); } + } WHEN { + TURN { MOVE(player, MOVE_SHADOW_BONE); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("Switch AI: AI will switch out if Palafin-Zero isn't transformed yet") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_FINIZEN); + OPPONENT(SPECIES_PALAFIN_ZERO); + OPPONENT(SPECIES_FINIZEN); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("Switch AI: AI will use pivot move to activate Palafin's Zero to Hero rather than hard switching") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_FINIZEN); + OPPONENT(SPECIES_PALAFIN_ZERO) { Moves(MOVE_FLIP_TURN); } + OPPONENT(SPECIES_FINIZEN); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); EXPECT_MOVE(opponent, MOVE_FLIP_TURN); EXPECT_SEND_OUT(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI won't send out defensive mon that can lose 1v1, or switch out a mon that can win 1v1 even with bad type matchup") +{ + PASSES_RANDOMLY(100, 100, RNG_AI_SWITCH_HASBADODDS); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_PANPOUR) { + Level(15); + Moves(MOVE_WATER_PULSE, MOVE_PLAY_NICE, MOVE_FURY_SWIPES, MOVE_LICK); + Items(ITEM_MYSTIC_WATER); + Ability(ABILITY_GLUTTONY); + Nature(NATURE_MODEST); + HPIV(31); + AttackIV(31); + DefenseIV(31); + SpAttackIV(31); + SpDefenseIV(31); + SpeedIV(31); } + OPPONENT(SPECIES_RHYHORN) { + Level(14); + Moves(MOVE_ROCK_TOMB, MOVE_HORN_ATTACK, MOVE_BULLDOZE, MOVE_ROCK_SMASH); + Items(ITEM_RINDO_BERRY); + Ability(ABILITY_LIGHTNING_ROD); + Nature(NATURE_ADAMANT); + HPIV(31); + AttackIV(31); + DefenseIV(31); + SpAttackIV(31); + SpDefenseIV(31); + SpeedIV(31); } + OPPONENT(SPECIES_GLIGAR) { + Level(15); + Moves(MOVE_WING_ATTACK, MOVE_QUICK_ATTACK, MOVE_BULLDOZE); + Items(ITEM_ORAN_BERRY); + Ability(ABILITY_SAND_VEIL); + Nature(NATURE_ADAMANT); + HPIV(31); + AttackIV(31); + DefenseIV(31); + SpAttackIV(31); + SpDefenseIV(31); + SpeedIV(31); } + OPPONENT(SPECIES_WOOPER_PALDEA) { + Level(15); + Moves(MOVE_MUD_SHOT, MOVE_ACID_SPRAY, MOVE_YAWN, MOVE_SANDSTORM); + Items(ITEM_ORAN_BERRY); + Ability(ABILITY_WATER_ABSORB); + Nature(NATURE_MODEST); + HPIV(31); + AttackIV(31); + DefenseIV(31); + SpAttackIV(31); + SpDefenseIV(31); + SpeedIV(31); } + } WHEN { + TURN { MOVE(player, MOVE_WATER_PULSE); EXPECT_MOVE(opponent, MOVE_BULLDOZE); EXPECT_SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_WATER_PULSE); EXPECT_MOVE(opponent, MOVE_BULLDOZE); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI considers 0 hits to KO as losing a 1v1") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_JOLTEON) { Level(100); Ability(ABILITY_VOLT_ABSORB); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_ZIGZAGOON) { Level(1); HP(1); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_TANGELA) { Level(100); Moves(MOVE_THUNDERBOLT); } + OPPONENT(SPECIES_TANGELA) { Level(100); Moves(MOVE_GIGA_DRAIN); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); EXPECT_SEND_OUT(opponent, 2); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI sees Echoed Voice damage correctly") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Speed(5); Moves(MOVE_SCRATCH, MOVE_ECHOED_VOICE); } + OPPONENT(SPECIES_ZIGZAGOON) { Speed(4); Level(55); Moves(MOVE_CELEBRATE); } + OPPONENT(SPECIES_DRAPION) { Speed(4); SpDefense(25); Moves(MOVE_WICKED_BLOW); Ability(ABILITY_SNIPER); } + OPPONENT(SPECIES_GASTLY) { Speed(4); Level(1); Moves(MOVE_TACKLE); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_CELEBRATE); EXPECT_SEND_OUT(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_SMART_MON_CHOICES: AI sees its own weather setting ability when considering switchin candidates") +{ + enum Ability ability = ABILITY_NONE; + PARAMETRIZE { ability = ABILITY_WATER_ABSORB; } + PARAMETRIZE { ability = ABILITY_DRIZZLE; } + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_ZIGZAGOON) { Speed(2); Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_ZIGZAGOON) { Speed(1); Level(1); Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_POLITOED) { Speed(5); Ability(ability); Moves(MOVE_BUBBLE_BEAM); } + OPPONENT(SPECIES_CONKELDURR) { Speed(1); Ability(ABILITY_GUTS); Moves(MOVE_SUPERPOWER); } + } WHEN { + if (ability == ABILITY_DRIZZLE) + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_SCRATCH); EXPECT_SEND_OUT(opponent, 1); } + else + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_SCRATCH); EXPECT_SEND_OUT(opponent, 2); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will properly consider immunities when determining switchin type matchup") +{ + GIVEN { + ASSUME(GetSpeciesType(SPECIES_WHIMSICOTT, 0) == TYPE_GRASS); + ASSUME(GetSpeciesType(SPECIES_WHIMSICOTT, 1) == TYPE_FAIRY); // Gen 5's pure Grass type makes the test fail + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_POLIWRATH) { Moves(MOVE_WATER_GUN, MOVE_KARATE_CHOP); } + OPPONENT(SPECIES_ZIGZAGOON) { Level(1); Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_CERULEDGE) { Moves(MOVE_SPARK); } + OPPONENT(SPECIES_WHIMSICOTT) { Moves(MOVE_MEGA_DRAIN); } + } WHEN { + TURN { MOVE(player, MOVE_KARATE_CHOP); EXPECT_MOVE(opponent, MOVE_SCRATCH); EXPECT_SEND_OUT(opponent, 2); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will properly consider immunities when determining switchin type matchup (Doubles)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_POLIWRATH) { Moves(MOVE_WATER_GUN, MOVE_KARATE_CHOP); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_CELEBRATE); } + OPPONENT(SPECIES_ZIGZAGOON) { Level(1); Moves(MOVE_CELEBRATE); } + OPPONENT(SPECIES_ZIGZAGOON) { Level(1); Moves(MOVE_CELEBRATE); } + OPPONENT(SPECIES_CERULEDGE) { Moves(MOVE_SPARK); } + OPPONENT(SPECIES_WHIMSICOTT) { Moves(MOVE_MEGA_DRAIN); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_KARATE_CHOP, target:opponentLeft); MOVE(playerRight, MOVE_CELEBRATE); EXPECT_MOVE(opponentLeft, MOVE_CELEBRATE); EXPECT_MOVE(opponentRight, MOVE_CELEBRATE); EXPECT_SEND_OUT(opponentLeft, 3); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI won't switch out due to bad odds if it can OHKO with a priority move") +{ + PASSES_RANDOMLY(100, 100, RNG_AI_SWITCH_HASBADODDS); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + OPPONENT(SPECIES_CETODDLE) { Level(14); HP(30); Speed(1); Moves(MOVE_ICE_FANG, MOVE_ROCK_SMASH, MOVE_BULLDOZE, MOVE_ICE_SHARD); } + OPPONENT(SPECIES_SPHEAL) { Level(14); Speed(1); Ability(ABILITY_THICK_FAT); Moves(MOVE_ICY_WIND, MOVE_BRINE, MOVE_HIDDEN_POWER, MOVE_SIGNAL_BEAM); } + PLAYER(SPECIES_LITTEN) { Level(15); HP(1); Speed(2); Ability(ABILITY_BLAZE); Moves(MOVE_FIRE_FANG, MOVE_EMBER, MOVE_LICK, MOVE_FAKE_OUT); } + } WHEN { + TURN { MOVE(player, MOVE_FIRE_FANG); EXPECT_MOVE(opponent, MOVE_ICE_SHARD); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will consider player's priority when evaluating switchin candidates") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + OPPONENT(SPECIES_ZIGZAGOON) { Speed(1); HP(1); Moves(MOVE_HEADBUTT); } + OPPONENT(SPECIES_ANNIHILAPE) { Speed(5); Moves(MOVE_DRAIN_PUNCH); } + OPPONENT(SPECIES_GENGAR) { Speed(10); Moves(MOVE_FOCUS_BLAST); } + PLAYER(SPECIES_KINGAMBIT) { Speed(2); Moves(MOVE_SUCKER_PUNCH, MOVE_KNOCK_OFF); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); EXPECT_MOVE(opponent, MOVE_HEADBUTT); EXPECT_SEND_OUT(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will consider player's priority when evaluating Bad Odds 1v1") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_HASBADODDS_PERCENTAGE, 100, RNG_AI_SWITCH_HASBADODDS); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + OPPONENT(SPECIES_GENGAR) { Speed(10); Moves(MOVE_FOCUS_BLAST); } + OPPONENT(SPECIES_SCRAFTY) { Speed(5); Moves(MOVE_DRAIN_PUNCH); } + PLAYER(SPECIES_KINGAMBIT) { Speed(2); Moves(MOVE_SUCKER_PUNCH, MOVE_KNOCK_OFF); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will consider Hidden Power when triggering absorbing switches") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_ABSORBS_HIDDEN_POWER_PERCENTAGE, 100, RNG_AI_SWITCH_ABSORBING_HIDDEN_POWER); + GIVEN { + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, GEN_5); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_HIDDEN_POWER); HPIV(31); AttackIV(30); DefenseIV(31); SpAttackIV(30); SpDefenseIV(31); SpeedIV(30); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_NINETALES) { Moves(MOVE_SCRATCH); Ability(ABILITY_FLASH_FIRE); } + } WHEN { + TURN { MOVE(player, MOVE_HIDDEN_POWER); EXPECT_MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_HIDDEN_POWER); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: Fake Out style moves won't confuse choiced AI into thinking it does no damage") +{ + + GIVEN { + ASSUME(gItemsInfo[ITEM_CHOICE_SCARF].holdEffect == HOLD_EFFECT_CHOICE_SCARF); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_FAKE_OUT, MOVE_SCRATCH); } + OPPONENT(SPECIES_INFERNAPE) { Items(ITEM_CHOICE_SCARF); Moves(MOVE_CLOSE_COMBAT); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + } WHEN { + TURN { MOVE(player, MOVE_FAKE_OUT); EXPECT_MOVE(opponent, MOVE_CLOSE_COMBAT); } + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_CLOSE_COMBAT); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will consider choice-locked player priority when determining which mon to send out") +{ + u32 item; + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_CHOICE_BAND; } + GIVEN { + ASSUME(gItemsInfo[ITEM_CHOICE_BAND].holdEffect == HOLD_EFFECT_CHOICE_BAND); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_LYCANROC) { Speed(5); Moves(MOVE_ACCELEROCK, MOVE_MIGHTY_CLEAVE); Items(item); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(4); HP(1); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_DECIDUEYE_HISUI) { Speed(4); Moves(MOVE_LEAF_BLADE); } + OPPONENT(SPECIES_PHEROMOSA) { Speed(6); HP(1); Moves(MOVE_EARTHQUAKE); } + } WHEN { + TURN { MOVE(player, MOVE_MIGHTY_CLEAVE); EXPECT_MOVE(opponent, MOVE_TACKLE); item == ITEM_NONE ? EXPECT_SEND_OUT(opponent, 1) : EXPECT_SEND_OUT(opponent, 2); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI considers both meeting and exceeding KO thresholds correctly") +{ + u32 hp; + PARAMETRIZE { hp = 40; } + PARAMETRIZE { hp = 80; } + PARAMETRIZE { hp = 79; } + PARAMETRIZE { hp = 81; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_DRAGON_RAGE) == EFFECT_FIXED_HP_DAMAGE); + ASSUME(GetMoveFixedHPDamage(MOVE_DRAGON_RAGE) == 40); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_ZIGZAGOON) { Speed(5); HP(hp); Moves(MOVE_PROTECT, MOVE_TACKLE); } + OPPONENT(SPECIES_ZIGZAGOON) { Speed(6); Moves(MOVE_EXPLOSION); } + OPPONENT(SPECIES_ZIGZAGOON) { Speed(6); Moves(MOVE_DRAGON_RAGE); } + OPPONENT(SPECIES_BELDUM) { Speed(4); Moves(MOVE_TACKLE); } + } WHEN { + TURN { MOVE(player, MOVE_PROTECT); EXPECT_MOVE(opponent, MOVE_EXPLOSION); hp > 80 ? EXPECT_SEND_OUT(opponent, 2) : EXPECT_SEND_OUT(opponent, 1); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI will not choose to switch out Dondozo with Commander Tatsugiri") +{ + PASSES_RANDOMLY(100, 100); + GIVEN { + ASSUME(GetMoveEffect(MOVE_PERISH_SONG) == EFFECT_PERISH_SONG); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES); + OPPONENT(SPECIES_DONDOZO) { Level(50); Moves(MOVE_WATER_GUN); } + OPPONENT(SPECIES_TATSUGIRI) { Moves(MOVE_WATER_GUN); Ability(ABILITY_COMMANDER); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_HEADBUTT); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_CELEBRATE, MOVE_SCRATCH); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_CELEBRATE, MOVE_PERISH_SONG); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_CELEBRATE); } + PLAYER(SPECIES_ZIGZAGOON) { Moves (MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_CELEBRATE); MOVE(playerRight, MOVE_PERISH_SONG); } + TURN { MOVE(playerLeft, MOVE_CELEBRATE); MOVE(playerRight, MOVE_CELEBRATE); } + TURN { SWITCH(playerLeft, 2); SWITCH(playerRight, 3); } + TURN { MOVE(playerLeft, MOVE_CELEBRATE); MOVE(playerRight, MOVE_CELEBRATE); EXPECT_MOVE(opponentLeft, MOVE_WATER_GUN); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Eject Button will send out Ace Mon if it's the only one remaining (Multi)") +{ + u32 aiSmartMonChoicesFlag; + PARAMETRIZE { aiSmartMonChoicesFlag = 0; } + PARAMETRIZE { aiSmartMonChoicesFlag = AI_FLAG_SMART_MON_CHOICES; } + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSmartMonChoicesFlag | AI_FLAG_ACE_POKEMON); + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_ZIGZAGOON) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); }; + OPPONENT(SPECIES_LINOONE); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_SCRATCH); EXPECT_SEND_OUT(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Eject Pack will send out Ace Mon if it's the only one remaining (Multi)") +{ + u32 aiSmartMonChoicesFlag; + PARAMETRIZE { aiSmartMonChoicesFlag = 0; } + PARAMETRIZE { aiSmartMonChoicesFlag = AI_FLAG_SMART_MON_CHOICES; } + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSmartMonChoicesFlag | AI_FLAG_ACE_POKEMON); + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + PLAYER(SPECIES_ARCANINE) { Ability(ABILITY_INTIMIDATE); Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_ZIGZAGOON) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_LINOONE) { Moves(MOVE_HEADBUTT); } + } WHEN { + TURN { SWITCH(player, 1); EXPECT_MOVE(opponent, MOVE_SCRATCH); EXPECT_SEND_OUT(opponent, 1); } + } +} + +// General AI_FLAG_SMART_MON_CHOICES behaviour +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Number of hits to KO calculation checks whether incoming damage is less than recurring healing to avoid an infinite loop (Multi)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_VENUSAUR) { Level(30); Moves(MOVE_SCRATCH); } + // Opponent party courtesy of Skolgrahd, who triggered the bug in the first place + OPPONENT(SPECIES_PIKACHU) { Level(100); Moves(MOVE_ZIPPY_ZAP, MOVE_EXTREME_SPEED, MOVE_IRON_TAIL, MOVE_KNOCK_OFF); } + OPPONENT(SPECIES_NINETALES_ALOLA) { Level(100); Moves(MOVE_AURORA_VEIL, MOVE_BLIZZARD, MOVE_MOONBLAST, MOVE_DISABLE); } + OPPONENT(SPECIES_WEAVILE) { Level(100); Moves(MOVE_NIGHT_SLASH, MOVE_TRIPLE_AXEL, MOVE_ICE_SHARD, MOVE_FAKE_OUT); } + OPPONENT(SPECIES_DITTO) { Level(100); Moves(MOVE_TRANSFORM); } + OPPONENT(SPECIES_TYPHLOSION) { Level(100); Moves(MOVE_ERUPTION, MOVE_HEAT_WAVE, MOVE_FOCUS_BLAST, MOVE_EXTRASENSORY); } + OPPONENT(SPECIES_UMBREON) { Level(100); Items(ITEM_PECHA_BERRY, ITEM_LEFTOVERS); Moves(MOVE_FOUL_PLAY, MOVE_SNARL, MOVE_HELPING_HAND, MOVE_THUNDER_WAVE); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVES(opponent, MOVE_ZIPPY_ZAP, MOVE_EXTREME_SPEED, MOVE_IRON_TAIL, MOVE_KNOCK_OFF); } + } SCENE { + MESSAGE("Venusaur fainted!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Avoid infinite loop if damage taken is equal to recurring healing (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LEFTOVERS].holdEffect == HOLD_EFFECT_LEFTOVERS); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_MEOWTH_GALAR) { Level(100); Moves(MOVE_GROWL, MOVE_FAKE_OUT, MOVE_HONE_CLAWS); } + // Scenario courtesy of Duke, who triggered the bug in the first place + OPPONENT(SPECIES_MEOWTH_GALAR) { Level(5); Moves(MOVE_GROWL, MOVE_FAKE_OUT, MOVE_HONE_CLAWS); } + OPPONENT(SPECIES_GEODUDE) { Level(5); Moves(MOVE_DOUBLE_EDGE); } + OPPONENT(SPECIES_GEODUDE) { Level(5); Moves(MOVE_DOUBLE_EDGE); } + OPPONENT(SPECIES_NOSEPASS) { Level(5); Moves(MOVE_DOUBLE_EDGE); } + OPPONENT(SPECIES_HOUNDSTONE) { Level(5); Moves(MOVE_NIGHT_SHADE, MOVE_BODY_PRESS, MOVE_WILL_O_WISP, MOVE_PROTECT); Items(ITEM_PECHA_BERRY, ITEM_LEFTOVERS); } + } WHEN { + TURN { MOVE(player, MOVE_FAKE_OUT); EXPECT_MOVES(opponent, MOVE_FAKE_OUT); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize offensive options after Eject Button (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_EJECT_BUTTON].holdEffect == HOLD_EFFECT_EJECT_BUTTON); + ASSUME(GetMoveEffect(MOVE_FALSE_SWIPE) == EFFECT_FALSE_SWIPE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_FALSE_SWIPE, MOVE_BOOMBURST); Speed(5); SpAttack(50); } + OPPONENT(SPECIES_PONYTA) { Level(1); Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); Moves(MOVE_SCRATCH); Speed(4); } // Forces switchout + OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_IRON_HEAD); Speed(4); SpDefense(50); } + OPPONENT(SPECIES_ELECTRODE) { Level(30); Ability(ABILITY_STATIC); Moves(MOVE_CHARGE_BEAM); Speed(6); SpDefense(53); } + } WHEN { + TURN { MOVE(player, MOVE_FALSE_SWIPE); EXPECT_SEND_OUT(opponent, 2); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize offensive options after Eject Pack (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_EJECT_PACK].holdEffect == HOLD_EFFECT_EJECT_PACK); + ASSUME(GetMoveEffect(MOVE_GROWL) == EFFECT_ATTACK_DOWN); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_GROWL, MOVE_BOOMBURST); Speed(5); SpAttack(50); } + OPPONENT(SPECIES_PONYTA) { Level(1); Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); Moves(MOVE_SCRATCH); Speed(4); } // Forces switchout + OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_IRON_HEAD); Speed(4); SpDefense(50); } + OPPONENT(SPECIES_ELECTRODE) { Level(30); Ability(ABILITY_STATIC); Moves(MOVE_CHARGE_BEAM); Speed(6); SpDefense(53); } + } WHEN { + TURN { MOVE(player, MOVE_GROWL); EXPECT_SEND_OUT(opponent, 2); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize defensive options after Eject Pack if mon outspeeds (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_EJECT_PACK].holdEffect == HOLD_EFFECT_EJECT_PACK); + ASSUME(MoveHasAdditionalEffectSelf(MOVE_OVERHEAT, MOVE_EFFECT_SP_ATK_MINUS_2) == TRUE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_WING_ATTACK, MOVE_BOOMBURST); Speed(5); SpAttack(50); } + OPPONENT(SPECIES_PONYTA) { Level(1); Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); Moves(MOVE_OVERHEAT); Speed(6); } // Forces switchout + OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_IRON_HEAD); Speed(4); SpDefense(50); } + OPPONENT(SPECIES_ELECTRODE) { Level(30); Ability(ABILITY_STATIC); Moves(MOVE_CHARGE_BEAM); Speed(6); SpDefense(53); } + } WHEN { + TURN { MOVE(player, MOVE_WING_ATTACK); EXPECT_SEND_OUT(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize offensive options after Eject Pack if mon outspeeds but was Intimidate'd (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_EJECT_PACK].holdEffect == HOLD_EFFECT_EJECT_PACK); + ASSUME(MoveHasAdditionalEffectSelf(MOVE_OVERHEAT, MOVE_EFFECT_SP_ATK_MINUS_2) == TRUE); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_STARAPTOR) { Level(30); Ability(ABILITY_INTIMIDATE); Moves(MOVE_WING_ATTACK, MOVE_BOOMBURST); Speed(5); SpAttack(50); } + OPPONENT(SPECIES_PONYTA) { Level(1); Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); Moves(MOVE_OVERHEAT); Speed(6); } // Forces switchout + OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_IRON_HEAD); Speed(4); SpDefense(50); } + OPPONENT(SPECIES_ELECTRODE) { Level(30); Ability(ABILITY_STATIC); Moves(MOVE_CHARGE_BEAM); Speed(6); SpDefense(53); } + } WHEN { + TURN { MOVE(player, MOVE_WING_ATTACK); EXPECT_SWITCH(opponent, 2); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will not switch out if Pokemon would faint to hazards unless party member can clear them (Multi)") +{ + u32 move1; + + PARAMETRIZE { move1 = MOVE_SCRATCH; } + PARAMETRIZE { move1 = MOVE_RAPID_SPIN; } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_RAPID_SPIN) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_EARTHQUAKE) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_HEADBUTT) == DAMAGE_CATEGORY_PHYSICAL); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_HITMONTOP) { Level(30); Moves(MOVE_CHARM, MOVE_SCRATCH, MOVE_STEALTH_ROCK, MOVE_EARTHQUAKE); Ability(ABILITY_INTIMIDATE); Speed(5); } + OPPONENT(SPECIES_GRIMER) { Level(30); Moves(MOVE_SCRATCH); Items(ITEM_PECHA_BERRY, ITEM_FOCUS_SASH); Speed(4); } + OPPONENT(SPECIES_PONYTA) { Level(30); Moves(MOVE_HEADBUTT, move1); Speed(4); } + } WHEN { + TURN { MOVE(player, MOVE_STEALTH_ROCK); } + TURN { MOVE(player, MOVE_EARTHQUAKE); } + TURN { MOVE(player, MOVE_CHARM); } + TURN { // If the AI has a mon that can remove hazards, don't prevent them switching out + MOVE(player, MOVE_CHARM); + if (move1 == MOVE_RAPID_SPIN) + EXPECT_SWITCH(opponent, 1); + else if (move1 == MOVE_SCRATCH) + EXPECT_MOVE(opponent, MOVE_SCRATCH); + } + } +} + +AI_SINGLE_BATTLE_TEST("AI will trap player using Trace if player has a trapper (Multi)") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_TRAPPER_PERCENTAGE, 100, RNG_AI_SWITCH_TRAPPER); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING); + PLAYER(SPECIES_DUGTRIO) { Ability(ABILITY_ARENA_TRAP); Moves(MOVE_ROCK_TOMB); } + PLAYER(SPECIES_DUGTRIO); + OPPONENT(SPECIES_GENGAR); + OPPONENT(SPECIES_PORYGON2) { Ability(ABILITY_TRACE); Items(ITEM_PECHA_BERRY, ITEM_EVIOLITE); Moves(MOVE_ICE_BEAM); } + } WHEN { + TURN { MOVE(player, MOVE_ROCK_TOMB); EXPECT_SWITCH(opponent, 1); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will consider choice-locked player when determining which mon to send out (Multi)") +{ + u32 item; + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_CHOICE_BAND; } + GIVEN { + ASSUME(gItemsInfo[ITEM_CHOICE_BAND].holdEffect == HOLD_EFFECT_CHOICE_BAND); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_JOLTEON) { Speed(5); Moves(MOVE_EARTHQUAKE, MOVE_THUNDERBOLT); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(4); HP(1); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_MACHAMP) { Speed(4); Moves(MOVE_REVENGE); } + OPPONENT(SPECIES_GYARADOS) { Speed(4); Moves(MOVE_EARTHQUAKE); } + } WHEN { + TURN { MOVE(player, MOVE_EARTHQUAKE); EXPECT_MOVE(opponent, MOVE_TACKLE); item == ITEM_NONE ? EXPECT_SEND_OUT(opponent, 1) : EXPECT_SEND_OUT(opponent, 2); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if all moves deal zero damage (Multi)") +{ + PASSES_RANDOMLY(SHOULD_SWITCH_ALL_SCORES_BAD_PERCENTAGE, 100, RNG_AI_SWITCH_ALL_SCORES_BAD); + GIVEN { + ASSUME(GetMoveEffect(MOVE_WILL_O_WISP) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_WILL_O_WISP) == MOVE_EFFECT_BURN); + ASSUME(GetMoveEffect(MOVE_POLTERGEIST) == EFFECT_POLTERGEIST); + ASSUME(GetMoveType(MOVE_SCALD) == TYPE_WATER); + ASSUME(GetMoveType(MOVE_EARTHQUAKE) == TYPE_GROUND); + ASSUME(GetSpeciesType(SPECIES_MANTINE, 1) == TYPE_FLYING); + ASSUME(GetItemHoldEffect(ITEM_WATER_GEM) == HOLD_EFFECT_GEMS); + ASSUME(GetItemSecondaryId(ITEM_WATER_GEM) == TYPE_WATER); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_MANTINE) { Speed(5); Moves(MOVE_ROOST, MOVE_SCALD); Ability(ABILITY_WATER_VEIL); Items(ITEM_NONE, ITEM_WATER_GEM); } + OPPONENT(SPECIES_DUSKNOIR) { Speed(6); Moves(MOVE_WILL_O_WISP, MOVE_POLTERGEIST, MOVE_EARTHQUAKE); } + OPPONENT(SPECIES_ZIGZAGOON) { Speed(6); Moves(MOVE_TACKLE); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_POLTERGEIST); MOVE(player, MOVE_SCALD); } + TURN { EXPECT_SWITCH(opponent, 1); MOVE(player, MOVE_ROOST); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI won't send out defensive mon that can lose 1v1, or switch out a mon that can win 1v1 even with bad type matchup (Multi)") +{ + PASSES_RANDOMLY(100, 100, RNG_AI_SWITCH_HASBADODDS); + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_PANPOUR) { + Level(15); + Moves(MOVE_WATER_PULSE, MOVE_PLAY_NICE, MOVE_FURY_SWIPES, MOVE_LICK); + Items(ITEM_PECHA_BERRY, ITEM_MYSTIC_WATER); + Ability(ABILITY_GLUTTONY); + Nature(NATURE_MODEST); + HPIV(31); + AttackIV(31); + DefenseIV(31); + SpAttackIV(31); + SpDefenseIV(31); + SpeedIV(31); } + OPPONENT(SPECIES_RHYHORN) { + Level(14); + Moves(MOVE_ROCK_TOMB, MOVE_HORN_ATTACK, MOVE_BULLDOZE, MOVE_ROCK_SMASH); + Items(ITEM_PECHA_BERRY, ITEM_RINDO_BERRY); + Ability(ABILITY_LIGHTNING_ROD); + Nature(NATURE_ADAMANT); + HPIV(31); + AttackIV(31); + DefenseIV(31); + SpAttackIV(31); + SpDefenseIV(31); + SpeedIV(31); } + OPPONENT(SPECIES_GLIGAR) { + Level(15); + Moves(MOVE_WING_ATTACK, MOVE_QUICK_ATTACK, MOVE_BULLDOZE); + Items(ITEM_PECHA_BERRY, ITEM_ORAN_BERRY); + Ability(ABILITY_SAND_VEIL); + Nature(NATURE_ADAMANT); + HPIV(31); + AttackIV(31); + DefenseIV(31); + SpAttackIV(31); + SpDefenseIV(31); + SpeedIV(31); } + OPPONENT(SPECIES_WOOPER_PALDEA) { + Level(15); + Moves(MOVE_MUD_SHOT, MOVE_ACID_SPRAY, MOVE_YAWN, MOVE_SANDSTORM); + Items(ITEM_PECHA_BERRY, ITEM_ORAN_BERRY); + Ability(ABILITY_WATER_ABSORB); + Nature(NATURE_MODEST); + HPIV(31); + AttackIV(31); + DefenseIV(31); + SpAttackIV(31); + SpDefenseIV(31); + SpeedIV(31); } + } WHEN { + TURN { MOVE(player, MOVE_WATER_PULSE); EXPECT_MOVE(opponent, MOVE_BULLDOZE); EXPECT_SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_WATER_PULSE); EXPECT_MOVE(opponent, MOVE_BULLDOZE); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: Fake Out style moves won't confuse choiced AI into thinking it does no damage (Multi)") +{ + + GIVEN { + ASSUME(gItemsInfo[ITEM_CHOICE_SCARF].holdEffect == HOLD_EFFECT_CHOICE_SCARF); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_FAKE_OUT, MOVE_SCRATCH); } + OPPONENT(SPECIES_INFERNAPE) { Items(ITEM_PECHA_BERRY, ITEM_CHOICE_SCARF); Moves(MOVE_CLOSE_COMBAT); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + } WHEN { + TURN { MOVE(player, MOVE_FAKE_OUT); EXPECT_MOVE(opponent, MOVE_CLOSE_COMBAT); } + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_CLOSE_COMBAT); } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will consider choice-locked player priority when determining which mon to send out (Multi)") +{ + u32 item; + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_CHOICE_BAND; } + GIVEN { + ASSUME(gItemsInfo[ITEM_CHOICE_BAND].holdEffect == HOLD_EFFECT_CHOICE_BAND); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_LYCANROC) { Speed(5); Moves(MOVE_ACCELEROCK, MOVE_MIGHTY_CLEAVE); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(4); HP(1); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_DECIDUEYE_HISUI) { Speed(4); Moves(MOVE_LEAF_BLADE); } + OPPONENT(SPECIES_PHEROMOSA) { Speed(6); HP(1); Moves(MOVE_EARTHQUAKE); } + } WHEN { + TURN { MOVE(player, MOVE_MIGHTY_CLEAVE); EXPECT_MOVE(opponent, MOVE_TACKLE); item == ITEM_NONE ? EXPECT_SEND_OUT(opponent, 1) : EXPECT_SEND_OUT(opponent, 2); } + } +} +#endif diff --git a/test/battle/ai/ai_trytofaint.c b/test/battle/ai/ai_trytofaint.c index f8bdded933d4..b48091b14df4 100644 --- a/test/battle/ai/ai_trytofaint.c +++ b/test/battle/ai/ai_trytofaint.c @@ -74,3 +74,30 @@ AI_SINGLE_BATTLE_TEST("AI sees Parental Bond killing through sturdy") } } +AI_SINGLE_BATTLE_TEST("AI sees Parental Bond killing through sturdy") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY); + PLAYER(SPECIES_MAGNEZONE){Level(64); Ability(ABILITY_MAGNET_PULL); Innates(ABILITY_STURDY); Moves(MOVE_TACKLE, MOVE_LIGHT_SCREEN); } + OPPONENT(SPECIES_KANGASKHAN_MEGA){Level(64); Moves(MOVE_DRAIN_PUNCH, MOVE_TAUNT); } + } WHEN { + TURN{ MOVE(player, MOVE_TACKLE); + EXPECT_MOVE(opponent, MOVE_DRAIN_PUNCH); // AI should see drain punch as a kill due to multi hit, outscoring taunt + } + } +} + +#if MAX_MON_ITEMS > 1 +AI_SINGLE_BATTLE_TEST("AI sees Loaded Dice damage increase from multi hit moves (Multi)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { HP(44); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LOADED_DICE); Moves(MOVE_SEED_BOMB, MOVE_BULLET_SEED); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_BULLET_SEED); } + } SCENE { + MESSAGE("Wobbuffet fainted!"); + } +} +#endif diff --git a/test/battle/ai/can_use_all_moves.c b/test/battle/ai/can_use_all_moves.c index 89fea681786e..25046bb9d6bf 100644 --- a/test/battle/ai/can_use_all_moves.c +++ b/test/battle/ai/can_use_all_moves.c @@ -704,3 +704,655 @@ AI_DOUBLE_BATTLE_TEST("AI can use all moves, 801-900") TURN { EXPECT_MOVE(opponentLeft, move); } } } + +#if MAX_MON_ITEMS > 1 +AI_DOUBLE_BATTLE_TEST("AI can use all moves, 1-100 (Multi)") +{ + u32 moveStart = 0; + u32 moveCap = 100; + + if (moveCap > MOVES_COUNT) + moveCap = MOVES_COUNT - 1; + + s32 j; + u32 move = MOVE_NONE; + + enum BattleMoveEffects effect; + + for (j = moveStart + 1; j <= moveCap; j++) + { + effect = GetMoveEffect(j); + + // Stat raising effects are not meant to be used when you have only Splash. + if (IsStatRaisingEffect(effect)) + continue; + + switch (effect) + { + //TODO: AI HANDLING + case EFFECT_OHKO: // Guillotine is crashing the test entirely. + case EFFECT_MIST: + case EFFECT_TELEPORT: + + //TODO: AI TESTS + case EFFECT_RESTORE_HP: + case EFFECT_MIMIC: + case EFFECT_DISABLE: + + // tests exist elsewhere + + // Skipped on purpose. + case EFFECT_PROTECT: + case EFFECT_NON_VOLATILE_STATUS: + case EFFECT_DO_NOTHING: + case EFFECT_HOLD_HANDS: + case EFFECT_CELEBRATE: + case EFFECT_HAPPY_HOUR: + break; + default: + PARAMETRIZE { move = j; } + } + } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_HP_AWARE | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SPLASH, move); Status1(STATUS1_BURN); Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, move); Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, move); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI can use all moves, 101-200 (Multi)") +{ + u32 moveStart = 100; + u32 moveCap = 200; + + if (moveCap > MOVES_COUNT) + moveCap = MOVES_COUNT - 1; + + s32 j; + u32 move = MOVE_NONE; + + enum BattleMoveEffects effect; + + for (j = moveStart + 1; j <= moveCap; j++) + { + effect = GetMoveEffect(j); + + // Stat raising effects are not meant to be used when you have only Splash. + if (IsStatRaisingEffect(effect)) + continue; + + switch (effect) + { + //TODO: AI HANDLING + case EFFECT_LIGHT_SCREEN: + case EFFECT_REFLECT: + case EFFECT_BIDE: + case EFFECT_NIGHTMARE: + case EFFECT_SNORE: + case EFFECT_SKETCH: + case EFFECT_BELLY_DRUM: + case EFFECT_DESTINY_BOND: + case EFFECT_MIRROR_MOVE: + case EFFECT_REST: + case EFFECT_SUBSTITUTE: + + //TODO: AI TESTS + case EFFECT_RESTORE_HP: + case EFFECT_MIMIC: + case EFFECT_SOFTBOILED: + case EFFECT_DREAM_EATER: + case EFFECT_CONVERSION: + case EFFECT_PERISH_SONG: + case EFFECT_FOCUS_ENERGY: + case EFFECT_SPITE: + + // tests exist elsewhere + case EFFECT_HAZE: + + // Skipped on purpose. + case EFFECT_PROTECT: + case EFFECT_NON_VOLATILE_STATUS: + case EFFECT_DO_NOTHING: + case EFFECT_HOLD_HANDS: + case EFFECT_CELEBRATE: + case EFFECT_HAPPY_HOUR: + break; + default: + PARAMETRIZE { move = j; } + } + } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_HP_AWARE | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SPLASH, move); Status1(STATUS1_BURN); Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, move); Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, move); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI can use all moves, 201-300 (Multi)") +{ + u32 moveStart = 200; + u32 moveCap = 300; + + if (moveCap > MOVES_COUNT) + moveCap = MOVES_COUNT - 1; + + s32 j; + u32 move = MOVE_NONE; + + enum BattleMoveEffects effect; + + for (j = moveStart + 1; j <= moveCap; j++) + { + effect = GetMoveEffect(j); + + // Stat raising effects are not meant to be used when you have only Splash. + if (IsStatRaisingEffect(effect)) + continue; + + switch (effect) + { + //TODO: AI HANDLING + case EFFECT_SLEEP_TALK: // logic exists but does not account for Rest correctly + case EFFECT_SAFEGUARD: // logic exists but does not account for Rest correctly + case EFFECT_FOLLOW_ME: + case EFFECT_SNATCH: + case EFFECT_GRUDGE: + case EFFECT_CAMOUFLAGE: + case EFFECT_IMPRISON: + case EFFECT_INGRAIN: + case EFFECT_MAGIC_COAT: + case EFFECT_MUD_SPORT: + + //TODO: AI TESTS + case EFFECT_RESTORE_HP: + case EFFECT_SOFTBOILED: + case EFFECT_ENDURE: + case EFFECT_BATON_PASS: + case EFFECT_ENCORE: + case EFFECT_MORNING_SUN: + case EFFECT_MOONLIGHT: + case EFFECT_SYNTHESIS: + case EFFECT_SPIT_UP: + case EFFECT_SWALLOW: + case EFFECT_WISH: + case EFFECT_RECYCLE: + + // tests exist elsewhere + case EFFECT_HEAL_BELL: + case EFFECT_SUNNY_DAY: + case EFFECT_RAIN_DANCE: + #if B_PREFERRED_ICE_WEATHER == B_ICE_WEATHER_SNOW + case EFFECT_SNOWSCAPE: + #else + case EFFECT_HAIL: + #endif + case EFFECT_ROLE_PLAY: + case EFFECT_REFRESH: + + // Skipped on purpose. + case EFFECT_PROTECT: + case EFFECT_NON_VOLATILE_STATUS: + case EFFECT_SANDSTORM: + case EFFECT_DO_NOTHING: + case EFFECT_HOLD_HANDS: + case EFFECT_CELEBRATE: + case EFFECT_HAPPY_HOUR: + break; + default: + PARAMETRIZE { move = j; } + } + } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_HP_AWARE | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SPLASH, move); Status1(STATUS1_BURN); Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, move); Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, move); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI can use all moves, 301-400 (Multi)") +{ + u32 moveStart = 300; + u32 moveCap = 400; + + if (moveCap > MOVES_COUNT) + moveCap = MOVES_COUNT - 1; + + s32 j; + u32 move = MOVE_NONE; + + enum BattleMoveEffects effect; + + for (j = moveStart + 1; j <= moveCap; j++) + { + effect = GetMoveEffect(j); + + // Stat raising effects are not meant to be used when you have only Splash. + if (IsStatRaisingEffect(effect)) + continue; + + switch (effect) + { + //TODO: AI HANDLING + case EFFECT_SHEER_COLD: // Guillotine is crashing the test entirely. + case EFFECT_WATER_SPORT: + case EFFECT_LUCKY_CHANT: + case EFFECT_ME_FIRST: + case EFFECT_PSYCHO_SHIFT: + case EFFECT_COPYCAT: + case EFFECT_LAST_RESORT: + case EFFECT_AQUA_RING: + case EFFECT_HEALING_WISH: + case EFFECT_LUNAR_DANCE: + + //TODO: AI TESTS + case EFFECT_RESTORE_HP: + case EFFECT_ROOST: + case EFFECT_GUARD_SWAP: + case EFFECT_POWER_SWAP: + case EFFECT_HEART_SWAP: + case EFFECT_TAILWIND: + case EFFECT_POWER_TRICK: + case EFFECT_MAGNET_RISE: + + // tests exist elsewhere + case EFFECT_GRAVITY: + case EFFECT_HEAL_BELL: + case EFFECT_ATTACK_UP_USER_ALLY: + + // Skipped on purpose. + case EFFECT_PROTECT: + case EFFECT_NON_VOLATILE_STATUS: + case EFFECT_DO_NOTHING: + case EFFECT_HOLD_HANDS: + case EFFECT_CELEBRATE: + case EFFECT_HAPPY_HOUR: + break; + default: + PARAMETRIZE { move = j; } + } + } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_HP_AWARE | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SPLASH, move); Status1(STATUS1_BURN); Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, move); Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, move); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI can use all moves, 401-500 (Multi)") +{ + u32 moveStart = 400; + u32 moveCap = 500; + + if (moveCap > MOVES_COUNT) + moveCap = MOVES_COUNT - 1; + + s32 j; + u32 move = MOVE_NONE; + + enum BattleMoveEffects effect; + + for (j = moveStart + 1; j <= moveCap; j++) + { + effect = GetMoveEffect(j); + + // Stat raising effects are not meant to be used when you have only Splash. + if (IsStatRaisingEffect(effect)) + continue; + + switch (effect) + { + //TODO: AI HANDLING + case EFFECT_HEALING_WISH: + case EFFECT_LUNAR_DANCE: + case EFFECT_WONDER_ROOM: + case EFFECT_FOLLOW_ME: + case EFFECT_MAGIC_ROOM: + case EFFECT_AFTER_YOU: + + //TODO: AI TESTS + case EFFECT_RESTORE_HP: + case EFFECT_CAPTIVATE: + case EFFECT_DARK_VOID: // Gen 4-6's case is not being handled + + // tests exist elsewhere + case EFFECT_TRICK_ROOM: + case EFFECT_GUARD_SPLIT: + case EFFECT_POWER_SPLIT: + + // Skipped on purpose. + case EFFECT_PROTECT: + case EFFECT_NON_VOLATILE_STATUS: + case EFFECT_DO_NOTHING: + case EFFECT_HOLD_HANDS: + case EFFECT_CELEBRATE: + case EFFECT_HAPPY_HOUR: + break; + default: + PARAMETRIZE { move = j; } + } + } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_HP_AWARE | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SPLASH, move); Status1(STATUS1_BURN); Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, move); Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, move); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI can use all moves, 501-600 (Multi)") +{ + u32 moveStart = 515; + u32 moveCap = 600; + + if (moveCap > MOVES_COUNT) + moveCap = MOVES_COUNT - 1; + + s32 j; + u32 move = MOVE_NONE; + + enum BattleMoveEffects effect; + + for (j = moveStart + 1; j <= moveCap; j++) + { + effect = GetMoveEffect(j); + + // Stat raising effects are not meant to be used when you have only Splash. + if (IsStatRaisingEffect(effect)) + continue; + + switch (effect) + { + case EFFECT_FINAL_GAMBIT: + //TODO: AI HANDLING + case EFFECT_ALLY_SWITCH: + case EFFECT_QUASH: + case EFFECT_REFLECT_TYPE: + case EFFECT_SKY_DROP: + case EFFECT_MAT_BLOCK: + case EFFECT_ION_DELUGE: + case EFFECT_AROMATIC_MIST: + case EFFECT_POWDER: + case EFFECT_ELECTRIFY: + + //TODO: AI TESTS + case EFFECT_RESTORE_HP: + case EFFECT_HEAL_PULSE: + case EFFECT_BELCH: + case EFFECT_TOPSY_TURVY: + case EFFECT_FAIRY_LOCK: + + // tests exist elsewhere + case EFFECT_FLOWER_SHIELD: + case EFFECT_ROTOTILLER: + case EFFECT_GRASSY_TERRAIN: + case EFFECT_MISTY_TERRAIN: + + // Skipped on purpose. + case EFFECT_PROTECT: + case EFFECT_NON_VOLATILE_STATUS: + case EFFECT_DO_NOTHING: + case EFFECT_HOLD_HANDS: + case EFFECT_CELEBRATE: + case EFFECT_HAPPY_HOUR: + break; + default: + PARAMETRIZE { move = j; } + } + } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_HP_AWARE | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SPLASH, move); Status1(STATUS1_BURN); Items(ITEM_NONE, ITEM_STARF_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, move); Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, move); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI can use all moves, 601-700 (Multi)") +{ + u32 moveStart = 600; + u32 moveCap = 700; + + if (moveCap > MOVES_COUNT) + moveCap = MOVES_COUNT - 1; + + s32 j; + u32 move = MOVE_NONE; + + enum BattleMoveEffects effect; + + for (j = moveStart + 1; j <= moveCap; j++) + { + effect = GetMoveEffect(j); + + // Stat raising effects are not meant to be used when you have only Splash. + if (IsStatRaisingEffect(effect)) + continue; + + switch (effect) + { + //TODO: AI HANDLING + case EFFECT_FAIL_IF_NOT_ARG_TYPE: + case EFFECT_STUFF_CHEEKS: + case EFFECT_NO_RETREAT: + case EFFECT_TEATIME: + + //TODO: AI TESTS + case EFFECT_RESTORE_HP: + case EFFECT_SHORE_UP: + case EFFECT_HEAL_PULSE: + case EFFECT_LASER_FOCUS: + case EFFECT_PURIFY: + case EFFECT_INSTRUCT: + case EFFECT_SOAK: + + // tests exist elsewhere + case EFFECT_ELECTRIC_TERRAIN: + case EFFECT_PSYCHIC_TERRAIN: + case EFFECT_AURORA_VEIL: + case EFFECT_GEAR_UP: + case EFFECT_MAGNETIC_FLUX: + + // Skipped on purpose. + case EFFECT_PROTECT: + case EFFECT_NON_VOLATILE_STATUS: + case EFFECT_DO_NOTHING: + case EFFECT_HOLD_HANDS: + case EFFECT_CELEBRATE: + case EFFECT_HAPPY_HOUR: + break; + default: + PARAMETRIZE { move = j; } + } + } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_HP_AWARE | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SPLASH, move); Status1(STATUS1_BURN); Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, move); Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, move); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI can use all moves, 701-800 (Multi)") +{ + u32 moveStart = 700; + u32 moveCap = 800; + + if (moveCap > MOVES_COUNT) + moveCap = MOVES_COUNT - 1; + + s32 j; + u32 move = MOVE_NONE; + + enum BattleMoveEffects effect; + + for (j = moveStart + 1; j <= moveCap; j++) + { + effect = GetMoveEffect(j); + + // Stat raising effects are not meant to be used when you have only Splash. + if (IsStatRaisingEffect(effect)) + continue; + + switch (effect) + { + //TODO: AI HANDLING + case EFFECT_CLANGOROUS_SOUL: + case EFFECT_POLTERGEIST: + case EFFECT_COACHING: + case EFFECT_REVIVAL_BLESSING: + case EFFECT_FILLET_AWAY: + + //TODO: AI TESTS + case EFFECT_RESTORE_HP: + case EFFECT_STEEL_ROLLER: + case EFFECT_JUNGLE_HEALING: + case EFFECT_POWER_TRICK: + case EFFECT_TAKE_HEART: + + // tests exist elsewhere + case EFFECT_COURT_CHANGE: + case EFFECT_DOODLE: + case EFFECT_LIFE_DEW: + + // Skipped on purpose. + case EFFECT_PROTECT: + case EFFECT_NON_VOLATILE_STATUS: + case EFFECT_DO_NOTHING: + case EFFECT_HOLD_HANDS: + case EFFECT_CELEBRATE: + case EFFECT_HAPPY_HOUR: + break; + default: + PARAMETRIZE { move = j; } + } + } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_HP_AWARE | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SPLASH, move); Status1(STATUS1_BURN); Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, move); Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, move); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI can use all moves, 801-900 (Multi)") +{ + u32 moveStart = 800; + u32 moveCap = 900; + + if (moveCap > MOVES_COUNT) + moveCap = MOVES_COUNT - 1; + + s32 j; + u32 move = MOVE_NONE; + + enum BattleMoveEffects effect; + + for (j = moveStart + 1; j <= moveCap; j++) + { + effect = GetMoveEffect(j); + + // Stat raising effects are not meant to be used when you have only Splash. + if (IsStatRaisingEffect(effect)) + continue; + + switch (effect) + { + //TODO: AI HANDLING + case EFFECT_SHED_TAIL: + case EFFECT_FAIL_IF_NOT_ARG_TYPE: + + //TODO: AI TESTS + case EFFECT_CHILLY_RECEPTION: + case EFFECT_TIDY_UP: + + // tests exist elsewhere + case EFFECT_SNOWSCAPE: + case EFFECT_DRAGON_CHEER: + + // Skipped on purpose. + case EFFECT_PROTECT: + case EFFECT_NON_VOLATILE_STATUS: + case EFFECT_DO_NOTHING: + case EFFECT_HOLD_HANDS: + case EFFECT_CELEBRATE: + case EFFECT_HAPPY_HOUR: + break; + default: + PARAMETRIZE { move = j; } + } + } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_HP_AWARE | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SPLASH, move); Status1(STATUS1_BURN); Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, move); Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, move); } + } +} +#endif diff --git a/test/battle/ai/check_bad_move.c b/test/battle/ai/check_bad_move.c index c5a34f0df4f5..a29c22197316 100644 --- a/test/battle/ai/check_bad_move.c +++ b/test/battle/ai/check_bad_move.c @@ -48,3 +48,53 @@ AI_DOUBLE_BATTLE_TEST("AI will not try to lower opposing stats if target is prot TURN { SCORE_LT_VAL(opponentLeft, move, AI_SCORE_DEFAULT, target: playerRight); } } } + + +#if MAX_MON_TRAITS > 1 +AI_SINGLE_BATTLE_TEST("AI will not try to lower opposing stats if target is protected by it's ability (Traits)") +{ + enum Ability ability; + u32 species, move; + + PARAMETRIZE { ability = ABILITY_SPEED_BOOST; species = SPECIES_TORCHIC; move = MOVE_SCARY_FACE; } + PARAMETRIZE { ability = ABILITY_HYPER_CUTTER; species = SPECIES_KRABBY; move = MOVE_GROWL; } + PARAMETRIZE { ability = ABILITY_BIG_PECKS; species = SPECIES_PIDGEY; move = MOVE_SCREECH; } + PARAMETRIZE { ability = ABILITY_ILLUMINATE; species = SPECIES_STARYU; move = MOVE_SAND_ATTACK; } + PARAMETRIZE { ability = ABILITY_KEEN_EYE; species = SPECIES_PIDGEY; move = MOVE_SAND_ATTACK; } + PARAMETRIZE { ability = ABILITY_CONTRARY; species = SPECIES_SNIVY; move = MOVE_NOBLE_ROAR; } + PARAMETRIZE { ability = ABILITY_CLEAR_BODY; species = SPECIES_BELDUM; move = MOVE_NOBLE_ROAR; } + + GIVEN { + WITH_CONFIG(CONFIG_ILLUMINATE_EFFECT, GEN_9); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_OMNISCIENT); + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, move); } + } WHEN { + TURN { SCORE_LT_VAL(opponent, move, AI_SCORE_DEFAULT); } + } +} + + +AI_DOUBLE_BATTLE_TEST("AI will not try to lower opposing stats if target is protected by Flower Veil (Traits)") +{ + u16 move; + + PARAMETRIZE { move = MOVE_SCARY_FACE; } + PARAMETRIZE { move = MOVE_GROWL; } + PARAMETRIZE { move = MOVE_SCREECH; } + PARAMETRIZE { move = MOVE_SAND_ATTACK; } + PARAMETRIZE { move = MOVE_SAND_ATTACK; } + PARAMETRIZE { move = MOVE_NOBLE_ROAR; } + PARAMETRIZE { move = MOVE_NOBLE_ROAR; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_COMFEY) { Ability(ABILITY_TRIAGE); Innates(ABILITY_FLOWER_VEIL); } + PLAYER(SPECIES_BULBASAUR); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, move); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SCORE_LT_VAL(opponentLeft, move, AI_SCORE_DEFAULT, target: playerRight); } + } +} +#endif diff --git a/test/battle/ai/gimmick_mega.c b/test/battle/ai/gimmick_mega.c index ef94223122e5..55ccbd4c7b74 100644 --- a/test/battle/ai/gimmick_mega.c +++ b/test/battle/ai/gimmick_mega.c @@ -17,3 +17,19 @@ AI_SINGLE_BATTLE_TEST("AI uses Mega Evolution") } } +#if MAX_MON_ITEMS > 1 +AI_SINGLE_BATTLE_TEST("AI uses Mega Evolution (Multi)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT ); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_VENUSAUR) { Items(ITEM_PECHA_BERRY, ITEM_VENUSAURITE); Moves(MOVE_SLUDGE_BOMB); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_SLUDGE_BOMB, gimmick: GIMMICK_MEGA); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponent); + } THEN { + EXPECT_EQ(opponent->species, SPECIES_VENUSAUR_MEGA); + } +} +#endif diff --git a/test/battle/ai/gimmick_z_move.c b/test/battle/ai/gimmick_z_move.c index b6432987b776..5a489692e147 100644 --- a/test/battle/ai/gimmick_z_move.c +++ b/test/battle/ai/gimmick_z_move.c @@ -210,4 +210,234 @@ AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Z-Transform") TO_DO_BATTLE_TEST("TODO: AI uses Z-Moves -- Z-Trick Room") +#if MAX_MON_TRAITS > 1 +AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Z-Conversion (Traits)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_NONE; } + PARAMETRIZE { ability = ABILITY_OPPORTUNIST; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT ); + ASSUME(GetMoveType(MOVE_CONVERSION) == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_ADAPTABILITY); Moves(MOVE_THUNDERBOLT, MOVE_CONVERSION); } + } WHEN { + if (ability == ABILITY_OPPORTUNIST) + TURN { EXPECT_MOVE(opponent, MOVE_CONVERSION, gimmick: GIMMICK_NONE); } + else + TURN { EXPECT_MOVE(opponent, MOVE_CONVERSION, gimmick: GIMMICK_Z_MOVE); } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +AI_SINGLE_BATTLE_TEST("AI uses Z-moves. (Multi)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT ); + ASSUME(GetMoveType(MOVE_QUICK_ATTACK) == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); Moves(MOVE_QUICK_ATTACK); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_QUICK_ATTACK, gimmick: GIMMICK_Z_MOVE); } + } +} + +AI_SINGLE_BATTLE_TEST("AI does not use damaging Z-moves if the player would faint anyway. (Multi)") +{ + u32 currentHP; + PARAMETRIZE { currentHP = 1; } + PARAMETRIZE { currentHP = 500; } + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT ); + ASSUME(GetMoveType(MOVE_QUICK_ATTACK) == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { HP(currentHP); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); Moves(MOVE_QUICK_ATTACK); } + } WHEN { + if (currentHP != 1) + TURN { EXPECT_MOVE(opponent, MOVE_QUICK_ATTACK, gimmick: GIMMICK_Z_MOVE); } + else + TURN { EXPECT_MOVE(opponent, MOVE_QUICK_ATTACK, gimmick: GIMMICK_NONE); } + } +} + +AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Extreme Evoboost (Multi)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT ); + ASSUME(GetMoveType(MOVE_QUICK_ATTACK) == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EEVEE) { Items(ITEM_PECHA_BERRY, ITEM_EEVIUM_Z); Moves(MOVE_POUND, MOVE_LAST_RESORT); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_LAST_RESORT, gimmick: GIMMICK_Z_MOVE); } + TURN { EXPECT_MOVE(opponent, MOVE_POUND, gimmick: GIMMICK_NONE); + SCORE_LT_VAL(opponent, MOVE_LAST_RESORT, AI_SCORE_DEFAULT); } + TURN { EXPECT_MOVE(opponent, MOVE_LAST_RESORT, gimmick: GIMMICK_NONE); } + } +} + +AI_SINGLE_BATTLE_TEST("AI uses Z-Moves to bypass move limitations (Multi)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT ); + ASSUME(GetMoveType(MOVE_QUICK_ATTACK) == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); Moves(MOVE_POUND, MOVE_LAST_RESORT); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_LAST_RESORT, gimmick: GIMMICK_Z_MOVE); } + } +} + +AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- 10,000,000 Volt Thunderbolt (Multi)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT ); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIKACHU_PARTNER) { Items(ITEM_PECHA_BERRY, ITEM_PIKASHUNIUM_Z); Moves(MOVE_THUNDERBOLT); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_THUNDERBOLT, gimmick: GIMMICK_Z_MOVE); } + } +} + +AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Z-Conversion (Multi)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_NONE; } + PARAMETRIZE { ability = ABILITY_OPPORTUNIST; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT ); + ASSUME(GetMoveType(MOVE_CONVERSION) == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); Ability(ABILITY_ADAPTABILITY); Moves(MOVE_THUNDERBOLT, MOVE_CONVERSION); } + } WHEN { + if (ability == ABILITY_OPPORTUNIST) + TURN { EXPECT_MOVE(opponent, MOVE_CONVERSION, gimmick: GIMMICK_NONE); } + else + TURN { EXPECT_MOVE(opponent, MOVE_CONVERSION, gimmick: GIMMICK_Z_MOVE); } + } +} + + +AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Z-Destiny Bond is not used in singles (Multi)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT ); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_GHOSTIUM_Z); Moves(MOVE_DESTINY_BOND); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_DESTINY_BOND, gimmick: GIMMICK_NONE); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI uses Z-Moves -- Z-Destiny Bond is used when about to die (Multi)") +{ + u32 currentHP; + PARAMETRIZE { currentHP = 1; } + PARAMETRIZE { currentHP = 500; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT ); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_POUND); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(currentHP); Items(ITEM_PECHA_BERRY, ITEM_GHOSTIUM_Z); Moves(MOVE_DESTINY_BOND); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (currentHP == 1) + TURN { EXPECT_MOVE(opponentLeft, MOVE_DESTINY_BOND, gimmick: GIMMICK_Z_MOVE); } + else + TURN { EXPECT_MOVE(opponentLeft, MOVE_DESTINY_BOND, gimmick: GIMMICK_NONE); } + } +} + +AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Z-Detect (Multi)") +{ + u32 move; + PARAMETRIZE { move = MOVE_THUNDERBOLT; } + PARAMETRIZE { move = MOVE_CLOSE_COMBAT; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT | AI_FLAG_PREDICT_MOVE ); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_FAKE_OUT); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_FIGHTINIUM_Z); Moves(MOVE_DETECT, move); } + } WHEN { + if (move == MOVE_CLOSE_COMBAT) + TURN { EXPECT_MOVE(opponent, MOVE_DETECT, gimmick: GIMMICK_NONE); } + else + TURN { EXPECT_MOVE(opponent, MOVE_DETECT, gimmick: GIMMICK_Z_MOVE); } + } +} + +AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Z-Happy Hour (Multi)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT ); + ASSUME(GetMoveType(MOVE_QUICK_ATTACK) == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); Moves(MOVE_POUND, MOVE_HAPPY_HOUR); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_HAPPY_HOUR, gimmick: GIMMICK_Z_MOVE); + SCORE_GT_VAL(opponent, MOVE_HAPPY_HOUR, AI_SCORE_DEFAULT); } + TURN { EXPECT_MOVE(opponent, MOVE_POUND, gimmick: GIMMICK_NONE); + SCORE_EQ_VAL(opponent, MOVE_HAPPY_HOUR, 90); } + } +} + +TO_DO_BATTLE_TEST("TODO: AI uses Z-Moves -- Z-Haze (Multi)") + +TO_DO_BATTLE_TEST("TODO: AI uses Z-Moves -- Z-Mirror Move (Multi)") + +AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Z-Nature Power (Multi)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT | AI_FLAG_PREDICT_MOVE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); Moves(MOVE_NATURE_POWER, MOVE_HEADBUTT); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_NATURE_POWER, gimmick: GIMMICK_Z_MOVE); } + } +} + +// Requires handling for Wish passing/Healing Wish/other ways to determine what pokemon to heal via switching into. +TO_DO_BATTLE_TEST("TODO: AI uses Z-Moves -- Z-Parting Shot (Multi)") + +AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Z-Splash (Multi)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT ); + ASSUME(GetMoveType(MOVE_QUICK_ATTACK) == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); Moves(MOVE_POUND, MOVE_SPLASH); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_SPLASH, gimmick: GIMMICK_Z_MOVE); + SCORE_GT_VAL(opponent, MOVE_SPLASH, AI_SCORE_DEFAULT); } + TURN { EXPECT_MOVE(opponent, MOVE_POUND, gimmick: GIMMICK_NONE); + SCORE_EQ_VAL(opponent, MOVE_SPLASH, 90); } + } +} + +TO_DO_BATTLE_TEST("TODO: AI uses Z-Moves -- Z-Tailwind (Multi)") + +AI_SINGLE_BATTLE_TEST("AI uses Z-Moves -- Z-Transform (Multi)") +{ + u32 currentHP, move; + PARAMETRIZE { currentHP = 1; move = MOVE_HEADBUTT; } + PARAMETRIZE { currentHP = 1; move = MOVE_THUNDERBOLT; } + PARAMETRIZE { currentHP = 500; move = MOVE_HEADBUTT; } + PARAMETRIZE { currentHP = 500; move = MOVE_THUNDERBOLT; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT ); + PLAYER(SPECIES_WOBBUFFET) { Moves(move, MOVE_CELEBRATE); } + OPPONENT(SPECIES_WOBBUFFET) { HP(currentHP); Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); Moves(MOVE_TRANSFORM); } + } WHEN { + if (currentHP == 1 || move == MOVE_THUNDERBOLT) + TURN { EXPECT_MOVE(opponent, MOVE_TRANSFORM, gimmick: GIMMICK_Z_MOVE); } + else + TURN { EXPECT_MOVE(opponent, MOVE_TRANSFORM, gimmick: GIMMICK_NONE); } + } +} +TO_DO_BATTLE_TEST("TODO: AI uses Z-Moves -- Z-Trick Room (Multi)") +#endif diff --git a/test/battle/crit_chance.c b/test/battle/crit_chance.c index 58650f7f5dbb..5d961513ce72 100644 --- a/test/battle/crit_chance.c +++ b/test/battle/crit_chance.c @@ -47,3 +47,55 @@ SINGLE_BATTLE_TEST("Crit Chance: Raising critical hit rate to 3 guarantees a cri MESSAGE("A critical hit!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Crit Chance: Raising critical hit rate to 3 guarantees a critical hit (Gen 6+) (Traits)") +{ + u32 genConfig = 0, passes, trials; + PARAMETRIZE { genConfig = GEN_1; passes = 255; trials = 256; } // ~99.6% + PARAMETRIZE { genConfig = GEN_2; passes = 85; trials = 256; } // ~33.2% + for (u32 j = GEN_3; j <= GEN_5; j++) + PARAMETRIZE { genConfig = j; passes = 1, trials = 3; } // ~33.3% + for (u32 j = GEN_6; j <= GEN_9; j++) + PARAMETRIZE { genConfig = j; passes = 1, trials = 1; } // 100% + PASSES_RANDOMLY(passes, trials, RNG_CRITICAL_HIT); + GIVEN { + WITH_CONFIG(CONFIG_CRIT_CHANCE, genConfig); + ASSUME(GetMoveCriticalHitStage(MOVE_SLASH) == 1); + ASSUME(gItemsInfo[ITEM_SCOPE_LENS].holdEffect == HOLD_EFFECT_SCOPE_LENS); + PLAYER(SPECIES_TOGEKISS) { Ability(ABILITY_SERENE_GRACE); Innates(ABILITY_SUPER_LUCK); Item(ITEM_SCOPE_LENS); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SLASH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SLASH, player); + MESSAGE("A critical hit!"); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Crit Chance: Raising critical hit rate to 3 guarantees a critical hit (Gen 6+) (Multi)") +{ + u32 genConfig = 0, passes, trials; + PARAMETRIZE { genConfig = GEN_1; passes = 255; trials = 256; } // ~99.6% + PARAMETRIZE { genConfig = GEN_2; passes = 85; trials = 256; } // ~33.2% + for (u32 j = GEN_3; j <= GEN_5; j++) + PARAMETRIZE { genConfig = j; passes = 1, trials = 3; } // ~33.3% + for (u32 j = GEN_6; j <= GEN_9; j++) + PARAMETRIZE { genConfig = j; passes = 1, trials = 1; } // 100% + PASSES_RANDOMLY(passes, trials, RNG_CRITICAL_HIT); + GIVEN { + WITH_CONFIG(CONFIG_CRIT_CHANCE, genConfig); + ASSUME(GetMoveCriticalHitStage(MOVE_SLASH) == 1); + ASSUME(gItemsInfo[ITEM_SCOPE_LENS].holdEffect == HOLD_EFFECT_SCOPE_LENS); + PLAYER(SPECIES_TOGEKISS) { Ability(ABILITY_SUPER_LUCK); Items(ITEM_ORAN_BERRY, ITEM_SCOPE_LENS); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SLASH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SLASH, player); + MESSAGE("A critical hit!"); + } +} +#endif diff --git a/test/battle/damage_formula.c b/test/battle/damage_formula.c index b62ed36ad927..0818721a9db2 100644 --- a/test/battle/damage_formula.c +++ b/test/battle/damage_formula.c @@ -418,3 +418,201 @@ DOUBLE_BATTLE_TEST("Transistor Damage calculation", s16 damage) EXPECT_EQ(damagePlayerRight, expectedDamageTransistorPhys); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Transistor Damage calculation (Traits)", s16 damage) +{ + s16 expectedDamageTransistorSpec = 0, expectedDamageRegularPhys = 0, expectedDamageRegularSpec = 0, expectedDamageTransistorPhys = 0; + s16 damagePlayerLeft, damagePlayerRight, damageOpponentLeft, damageOpponentRight; + u32 gen = 0; + for (u32 spread = 0; spread < 16; ++spread) { + PARAMETRIZE { gen = GEN_9, + expectedDamageTransistorSpec = sThunderShockTransistorSpreadGen9[spread], + expectedDamageRegularSpec = sThunderShockRegularSpread[spread]; + expectedDamageTransistorPhys = sWildChargeTransistorSpreadGen9[spread], + expectedDamageRegularPhys = sWildChargeRegularSpread[spread]; + } + } + for (u32 spread = 0; spread < 16; ++spread) { + PARAMETRIZE { gen = GEN_8, + expectedDamageTransistorSpec = sThunderShockTransistorSpreadGen8[spread], + expectedDamageRegularSpec = sThunderShockRegularSpread[spread], + expectedDamageTransistorPhys = sWildChargeTransistorSpreadGen8[spread], + expectedDamageRegularPhys = sWildChargeRegularSpread[spread]; + } + } + GIVEN { + WITH_CONFIG(CONFIG_TRANSISTOR_BOOST, gen); + ASSUME(GetMoveType(MOVE_WILD_CHARGE) == TYPE_ELECTRIC); + ASSUME(GetMoveType(MOVE_THUNDER_SHOCK) == TYPE_ELECTRIC); + ASSUME(GetMoveCategory(MOVE_WILD_CHARGE) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_THUNDER_SHOCK) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(NUM_DAMAGE_SPREADS == 16); + + PLAYER(SPECIES_REGIELEKI) { Ability(ABILITY_KLUTZ); } + PLAYER(SPECIES_REGIELEKI) { Ability(ABILITY_KLUTZ); Innates(ABILITY_TRANSISTOR); } + OPPONENT(SPECIES_REGIELEKI) { Ability(ABILITY_KLUTZ); } + OPPONENT(SPECIES_REGIELEKI) { Ability(ABILITY_KLUTZ); Innates(ABILITY_TRANSISTOR); } + } WHEN { + TURN { + MOVE(playerLeft, MOVE_THUNDER_SHOCK, target: opponentLeft, WITH_RNG(RNG_DAMAGE_MODIFIER, 15 - (i % 16))); + MOVE(playerRight, MOVE_THUNDER_SHOCK, target: opponentRight, WITH_RNG(RNG_DAMAGE_MODIFIER, 15 - (i % 16))); + MOVE(opponentLeft, MOVE_WILD_CHARGE, target: playerLeft, WITH_RNG(RNG_DAMAGE_MODIFIER, 15 - (i % 16))); + MOVE(opponentRight, MOVE_WILD_CHARGE, target: playerRight, WITH_RNG(RNG_DAMAGE_MODIFIER, 15 - (i % 16))); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_SHOCK, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damageOpponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_SHOCK, playerRight); + HP_BAR(opponentRight, captureDamage: &damageOpponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WILD_CHARGE, opponentLeft); + HP_BAR(playerLeft, captureDamage: &damagePlayerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WILD_CHARGE, opponentRight); + HP_BAR(playerRight, captureDamage: &damagePlayerRight); + } THEN { + EXPECT_EQ(damageOpponentLeft, expectedDamageRegularSpec); + EXPECT_EQ(damageOpponentRight, expectedDamageTransistorSpec); + EXPECT_EQ(damagePlayerLeft, expectedDamageRegularPhys); + EXPECT_EQ(damagePlayerRight, expectedDamageTransistorPhys); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Damage calculation matches Gen6+ (Muscle Band, crit) (Multi)") +{ + s16 dmg; + s16 expectedDamage; + PARAMETRIZE { expectedDamage = 324; } + PARAMETRIZE { expectedDamage = 316; } + PARAMETRIZE { expectedDamage = 312; } + PARAMETRIZE { expectedDamage = 312; } + PARAMETRIZE { expectedDamage = 304; } + PARAMETRIZE { expectedDamage = 304; } + PARAMETRIZE { expectedDamage = 300; } + PARAMETRIZE { expectedDamage = 300; } + PARAMETRIZE { expectedDamage = 292; } + PARAMETRIZE { expectedDamage = 292; } + PARAMETRIZE { expectedDamage = 288; } + PARAMETRIZE { expectedDamage = 288; } + PARAMETRIZE { expectedDamage = 280; } + PARAMETRIZE { expectedDamage = 276; } + PARAMETRIZE { expectedDamage = 276; } + PARAMETRIZE { expectedDamage = 268; } + GIVEN { + WITH_CONFIG(CONFIG_CRIT_MULTIPLIER, GEN_6); + ASSUME(GetMoveCategory(MOVE_ICE_FANG) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_GLACEON) { Level(75); Attack(123); Items(ITEM_ORAN_BERRY, ITEM_MUSCLE_BAND); } + OPPONENT(SPECIES_GARCHOMP) { Defense(163); } + } WHEN { + TURN { + MOVE(player, MOVE_ICE_FANG, WITH_RNG(RNG_DAMAGE_MODIFIER, i), criticalHit: TRUE); + } + } + SCENE { + MESSAGE("Glaceon used Ice Fang!"); + HP_BAR(opponent, captureDamage: &dmg); + } + THEN { + EXPECT_EQ(expectedDamage, dmg); + } +} + +SINGLE_BATTLE_TEST("Punching Glove vs Muscle Band Damage calculation (Multi)") +{ + s16 dmgPlayer, dmgOpponent; + s16 expectedDamagePlayer, expectedDamageOpponent; + PARAMETRIZE { expectedDamagePlayer = 204, expectedDamageOpponent = 201; } + PARAMETRIZE { expectedDamagePlayer = 201, expectedDamageOpponent = 198; } + PARAMETRIZE { expectedDamagePlayer = 199, expectedDamageOpponent = 196; } + PARAMETRIZE { expectedDamagePlayer = 196, expectedDamageOpponent = 193; } + PARAMETRIZE { expectedDamagePlayer = 195, expectedDamageOpponent = 192; } + PARAMETRIZE { expectedDamagePlayer = 193, expectedDamageOpponent = 190; } + PARAMETRIZE { expectedDamagePlayer = 190, expectedDamageOpponent = 187; } + PARAMETRIZE { expectedDamagePlayer = 189, expectedDamageOpponent = 186; } + PARAMETRIZE { expectedDamagePlayer = 187, expectedDamageOpponent = 184; } + PARAMETRIZE { expectedDamagePlayer = 184, expectedDamageOpponent = 181; } + PARAMETRIZE { expectedDamagePlayer = 183, expectedDamageOpponent = 180; } + PARAMETRIZE { expectedDamagePlayer = 181, expectedDamageOpponent = 178; } + PARAMETRIZE { expectedDamagePlayer = 178, expectedDamageOpponent = 175; } + PARAMETRIZE { expectedDamagePlayer = 177, expectedDamageOpponent = 174; } + PARAMETRIZE { expectedDamagePlayer = 174, expectedDamageOpponent = 172; } + PARAMETRIZE { expectedDamagePlayer = 172, expectedDamageOpponent = 169; } + GIVEN { + PLAYER(SPECIES_MAKUHITA) { Items(ITEM_ORAN_BERRY, ITEM_PUNCHING_GLOVE); } + OPPONENT(SPECIES_MAKUHITA) { Items(ITEM_ORAN_BERRY, ITEM_MUSCLE_BAND); } + } WHEN { + TURN { + MOVE(player, MOVE_DRAIN_PUNCH, WITH_RNG(RNG_DAMAGE_MODIFIER, i)); + MOVE(opponent, MOVE_DRAIN_PUNCH, WITH_RNG(RNG_DAMAGE_MODIFIER, i)); + } + } + SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAIN_PUNCH, player); + HP_BAR(opponent, captureDamage: &dmgPlayer); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAIN_PUNCH, opponent); + HP_BAR(player, captureDamage: &dmgOpponent); + } + THEN { + EXPECT_EQ(expectedDamagePlayer, dmgPlayer); + EXPECT_EQ(expectedDamageOpponent, dmgOpponent); + } +} + +SINGLE_BATTLE_TEST("Gem boosted Damage calculation (Multi)") +{ + s16 dmg; + s16 expectedDamage; +#if I_GEM_BOOST_POWER >= GEN_6 + PARAMETRIZE { expectedDamage = 240; } + PARAMETRIZE { expectedDamage = 237; } + PARAMETRIZE { expectedDamage = 234; } + PARAMETRIZE { expectedDamage = 232; } + PARAMETRIZE { expectedDamage = 229; } + PARAMETRIZE { expectedDamage = 228; } + PARAMETRIZE { expectedDamage = 225; } + PARAMETRIZE { expectedDamage = 222; } + PARAMETRIZE { expectedDamage = 220; } + PARAMETRIZE { expectedDamage = 217; } + PARAMETRIZE { expectedDamage = 216; } + PARAMETRIZE { expectedDamage = 213; } + PARAMETRIZE { expectedDamage = 210; } + PARAMETRIZE { expectedDamage = 208; } + PARAMETRIZE { expectedDamage = 205; } + PARAMETRIZE { expectedDamage = 204; } +#else + KNOWN_FAILING; + PARAMETRIZE { expectedDamage = 273; } + PARAMETRIZE { expectedDamage = 270; } + PARAMETRIZE { expectedDamage = 267; } + PARAMETRIZE { expectedDamage = 264; } + PARAMETRIZE { expectedDamage = 261; } + PARAMETRIZE { expectedDamage = 258; } + PARAMETRIZE { expectedDamage = 256; } + PARAMETRIZE { expectedDamage = 253; } + PARAMETRIZE { expectedDamage = 250; } + PARAMETRIZE { expectedDamage = 247; } + PARAMETRIZE { expectedDamage = 244; } + PARAMETRIZE { expectedDamage = 241; } + PARAMETRIZE { expectedDamage = 240; } + PARAMETRIZE { expectedDamage = 237; } + PARAMETRIZE { expectedDamage = 234; } + PARAMETRIZE { expectedDamage = 231; } +#endif + GIVEN { + PLAYER(SPECIES_MAKUHITA) { Items(ITEM_ORAN_BERRY, ITEM_FIGHTING_GEM); } + OPPONENT(SPECIES_MAKUHITA); + } WHEN { + TURN { + MOVE(player, MOVE_DRAIN_PUNCH, WITH_RNG(RNG_DAMAGE_MODIFIER, i)); + } + } + SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAIN_PUNCH, player); + HP_BAR(opponent, captureDamage: &dmg); + } + THEN { + EXPECT_EQ(expectedDamage, dmg); + } +} +#endif diff --git a/test/battle/end_turn_effects.c b/test/battle/end_turn_effects.c index cf65a50c6b2b..7263eb3c931d 100644 --- a/test/battle/end_turn_effects.c +++ b/test/battle/end_turn_effects.c @@ -139,3 +139,259 @@ ONE_VS_TWO_BATTLE_TEST("End Turn Effects: First Event Block is executed correctl EXPECT_GT(damage, 0); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("End Turn Effects: First Event Block is executed correctly (double battle) (Traits)") +{ + s16 healed; + s16 damage; + + GIVEN { + PLAYER(SPECIES_WYNAUT) { HP(100); Speed(1); } + PLAYER(SPECIES_EKANS) { HP(100); Ability(ABILITY_INTIMIDATE); Innates(ABILITY_SHED_SKIN); Status1(STATUS1_BURN); Speed(2); } + OPPONENT(SPECIES_WYNAUT) { HP(100); Item(ITEM_LEFTOVERS); Speed(3); } + OPPONENT(SPECIES_WOBBUFFET) { HP(100); Item(ITEM_BLACK_SLUDGE); Speed(4); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_GRASSY_TERRAIN); } + } SCENE { + MESSAGE("The opposing Wobbuffet is healed by the grassy terrain!"); + HP_BAR(opponentRight, captureDamage: &healed); + HP_BAR(opponentRight, captureDamage: &damage); + MESSAGE("The opposing Wobbuffet was hurt by the Black Sludge!"); + MESSAGE("The opposing Wynaut is healed by the grassy terrain!"); + MESSAGE("The opposing Wynaut restored a little HP using its Leftovers!"); + MESSAGE("Ekans is healed by the grassy terrain!"); + MESSAGE("Ekans's Shed Skin cured its burn problem!"); + MESSAGE("Wynaut is healed by the grassy terrain!"); + } THEN { + EXPECT_GT(0, healed); + EXPECT_GT(damage, 0); + } +} + +DOUBLE_BATTLE_TEST("End Turn Effects: Effects are applied by Speed Order (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT) { MaxHP(200); HP(100); Speed(3); } + PLAYER(SPECIES_RILLABOOM) { MaxHP(200); HP(100); Speed(1); Ability(ABILITY_OVERGROW); Innates(ABILITY_GRASSY_SURGE); } + OPPONENT(SPECIES_MEWTWO) { MaxHP(200); HP(100); Speed(2); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(200); HP(100); Speed(4); } + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_FAKE_OUT, target: playerLeft); + MOVE(playerRight, MOVE_FAKE_OUT, target: opponentRight); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FAKE_OUT, opponentLeft); + HP_BAR(playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FAKE_OUT, playerRight); + HP_BAR(opponentRight); + + HP_BAR(opponentRight); + HP_BAR(playerLeft); + HP_BAR(opponentLeft); + HP_BAR(playerRight); + } +} + +MULTI_BATTLE_TEST("End Turn Effects: First Event Block is executed correctly (multibattle) (Traits)") +{ + s16 healed; + s16 damage; + + GIVEN { + MULTI_PLAYER(SPECIES_WYNAUT) { HP(100); Speed(1); } + MULTI_PARTNER(SPECIES_EKANS) { HP(100); Ability(ABILITY_INTIMIDATE); Innates(ABILITY_SHED_SKIN); Status1(STATUS1_BURN); Speed(2); } + MULTI_OPPONENT_A(SPECIES_WYNAUT) { HP(100); Item(ITEM_LEFTOVERS); Speed(3); } + MULTI_OPPONENT_B(SPECIES_WOBBUFFET) { HP(100); Item(ITEM_BLACK_SLUDGE); Speed(4); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_GRASSY_TERRAIN); } + } SCENE { + MESSAGE("The opposing Wobbuffet is healed by the grassy terrain!"); + HP_BAR(opponentRight, captureDamage: &healed); + HP_BAR(opponentRight, captureDamage: &damage); + MESSAGE("The opposing Wobbuffet was hurt by the Black Sludge!"); + MESSAGE("The opposing Wynaut is healed by the grassy terrain!"); + MESSAGE("The opposing Wynaut restored a little HP using its Leftovers!"); + MESSAGE("Ekans is healed by the grassy terrain!"); + MESSAGE("Ekans's Shed Skin cured its burn problem!"); + MESSAGE("Wynaut is healed by the grassy terrain!"); + } THEN { + EXPECT_GT(0, healed); + EXPECT_GT(damage, 0); + } +} + + +TWO_VS_ONE_BATTLE_TEST("End Turn Effects: First Event Block is executed correctly (2v1) (Traits)") +{ + s16 healed; + s16 damage; + + GIVEN { + MULTI_PLAYER(SPECIES_WYNAUT) { HP(100); Speed(1);} + MULTI_PARTNER(SPECIES_EKANS) { HP(100); Ability(ABILITY_INTIMIDATE); Innates(ABILITY_SHED_SKIN); Status1(STATUS1_BURN); Speed(2); } + MULTI_OPPONENT_A(SPECIES_WYNAUT) { HP(100); Item(ITEM_LEFTOVERS); Speed(3); } + MULTI_OPPONENT_A(SPECIES_WOBBUFFET) { HP(100); Item(ITEM_BLACK_SLUDGE); Speed(4); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_GRASSY_TERRAIN); } + } SCENE { + MESSAGE("The opposing Wobbuffet is healed by the grassy terrain!"); + HP_BAR(opponentRight, captureDamage: &healed); + HP_BAR(opponentRight, captureDamage: &damage); + MESSAGE("The opposing Wobbuffet was hurt by the Black Sludge!"); + MESSAGE("The opposing Wynaut is healed by the grassy terrain!"); + MESSAGE("The opposing Wynaut restored a little HP using its Leftovers!"); + MESSAGE("Ekans is healed by the grassy terrain!"); + MESSAGE("Ekans's Shed Skin cured its burn problem!"); + MESSAGE("Wynaut is healed by the grassy terrain!"); + } THEN { + EXPECT_GT(0, healed); + EXPECT_GT(damage, 0); + } +} + +ONE_VS_TWO_BATTLE_TEST("End Turn Effects: First Event Block is executed correctly (1v2) (Traits)") +{ + s16 healed; + s16 damage; + + GIVEN { + MULTI_PLAYER(SPECIES_WYNAUT) { HP(100); Speed(1);} + MULTI_PLAYER(SPECIES_EKANS) { HP(100); Ability(ABILITY_INTIMIDATE); Innates(ABILITY_SHED_SKIN); Status1(STATUS1_BURN); Speed(2); } + MULTI_OPPONENT_A(SPECIES_WYNAUT) { HP(100); Item(ITEM_LEFTOVERS); Speed(3); } + MULTI_OPPONENT_B(SPECIES_WOBBUFFET) { HP(100); Item(ITEM_BLACK_SLUDGE); Speed(4); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_GRASSY_TERRAIN); } + } SCENE { + MESSAGE("The opposing Wobbuffet is healed by the grassy terrain!"); + HP_BAR(opponentRight, captureDamage: &healed); + HP_BAR(opponentRight, captureDamage: &damage); + MESSAGE("The opposing Wobbuffet was hurt by the Black Sludge!"); + MESSAGE("The opposing Wynaut is healed by the grassy terrain!"); + MESSAGE("The opposing Wynaut restored a little HP using its Leftovers!"); + MESSAGE("Ekans is healed by the grassy terrain!"); + MESSAGE("Ekans's Shed Skin cured its burn problem!"); + MESSAGE("Wynaut is healed by the grassy terrain!"); + } THEN { + EXPECT_GT(0, healed); + EXPECT_GT(damage, 0); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +DOUBLE_BATTLE_TEST("End Turn Effects: First Event Block is executed correctly (double battle) (Multi)") +{ + s16 healed; + s16 damage; + + GIVEN { + PLAYER(SPECIES_WYNAUT) { HP(100); Speed(1); } + PLAYER(SPECIES_EKANS) { HP(100); Ability(ABILITY_SHED_SKIN); Status1(STATUS1_BURN); Speed(2); } + OPPONENT(SPECIES_WYNAUT) { HP(100); Items(ITEM_ORAN_BERRY, ITEM_LEFTOVERS); Speed(3); } + OPPONENT(SPECIES_WOBBUFFET) { HP(100); Items(ITEM_ORAN_BERRY, ITEM_BLACK_SLUDGE); Speed(4); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_GRASSY_TERRAIN); } + } SCENE { + MESSAGE("The opposing Wobbuffet is healed by the grassy terrain!"); + HP_BAR(opponentRight, captureDamage: &healed); + HP_BAR(opponentRight, captureDamage: &damage); + MESSAGE("The opposing Wobbuffet was hurt by the Black Sludge!"); + MESSAGE("The opposing Wynaut is healed by the grassy terrain!"); + MESSAGE("The opposing Wynaut restored a little HP using its Leftovers!"); + MESSAGE("Ekans is healed by the grassy terrain!"); + MESSAGE("Ekans's Shed Skin cured its burn problem!"); + MESSAGE("Wynaut is healed by the grassy terrain!"); + } THEN { + EXPECT_GT(0, healed); + EXPECT_GT(damage, 0); + } +} + +MULTI_BATTLE_TEST("End Turn Effects: First Event Block is executed correctly (multibattle) (Multi)") +{ + s16 healed; + s16 damage; + + GIVEN { + MULTI_PLAYER(SPECIES_WYNAUT) { HP(100); Speed(1); } + MULTI_PARTNER(SPECIES_EKANS) { HP(100); Ability(ABILITY_SHED_SKIN); Status1(STATUS1_BURN); Speed(2); } + MULTI_OPPONENT_A(SPECIES_WYNAUT) { HP(100); Items(ITEM_ORAN_BERRY, ITEM_LEFTOVERS); Speed(3); } + MULTI_OPPONENT_B(SPECIES_WOBBUFFET) { HP(100); Items(ITEM_ORAN_BERRY, ITEM_BLACK_SLUDGE); Speed(4); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_GRASSY_TERRAIN); } + } SCENE { + MESSAGE("The opposing Wobbuffet is healed by the grassy terrain!"); + HP_BAR(opponentRight, captureDamage: &healed); + HP_BAR(opponentRight, captureDamage: &damage); + MESSAGE("The opposing Wobbuffet was hurt by the Black Sludge!"); + MESSAGE("The opposing Wynaut is healed by the grassy terrain!"); + MESSAGE("The opposing Wynaut restored a little HP using its Leftovers!"); + MESSAGE("Ekans is healed by the grassy terrain!"); + MESSAGE("Ekans's Shed Skin cured its burn problem!"); + MESSAGE("Wynaut is healed by the grassy terrain!"); + } THEN { + EXPECT_GT(0, healed); + EXPECT_GT(damage, 0); + } +} + + +TWO_VS_ONE_BATTLE_TEST("End Turn Effects: First Event Block is executed correctly (2v1) (Multi)") +{ + s16 healed; + s16 damage; + + GIVEN { + MULTI_PLAYER(SPECIES_WYNAUT) { HP(100); Speed(1);} + MULTI_PARTNER(SPECIES_EKANS) { HP(100); Ability(ABILITY_SHED_SKIN); Status1(STATUS1_BURN); Speed(2); } + MULTI_OPPONENT_A(SPECIES_WYNAUT) { HP(100); Items(ITEM_ORAN_BERRY, ITEM_LEFTOVERS); Speed(3); } + MULTI_OPPONENT_A(SPECIES_WOBBUFFET) { HP(100); Items(ITEM_ORAN_BERRY, ITEM_BLACK_SLUDGE); Speed(4); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_GRASSY_TERRAIN); } + } SCENE { + MESSAGE("The opposing Wobbuffet is healed by the grassy terrain!"); + HP_BAR(opponentRight, captureDamage: &healed); + HP_BAR(opponentRight, captureDamage: &damage); + MESSAGE("The opposing Wobbuffet was hurt by the Black Sludge!"); + MESSAGE("The opposing Wynaut is healed by the grassy terrain!"); + MESSAGE("The opposing Wynaut restored a little HP using its Leftovers!"); + MESSAGE("Ekans is healed by the grassy terrain!"); + MESSAGE("Ekans's Shed Skin cured its burn problem!"); + MESSAGE("Wynaut is healed by the grassy terrain!"); + } THEN { + EXPECT_GT(0, healed); + EXPECT_GT(damage, 0); + } +} + + +ONE_VS_TWO_BATTLE_TEST("End Turn Effects: First Event Block is executed correctly (1v2) (Multi)") +{ + s16 healed; + s16 damage; + + GIVEN { + MULTI_PLAYER(SPECIES_WYNAUT) { HP(100); Speed(1);} + MULTI_PLAYER(SPECIES_EKANS) { HP(100); Ability(ABILITY_SHED_SKIN); Status1(STATUS1_BURN); Speed(2); } + MULTI_OPPONENT_A(SPECIES_WYNAUT) { HP(100); Items(ITEM_ORAN_BERRY, ITEM_LEFTOVERS); Speed(3); } + MULTI_OPPONENT_B(SPECIES_WOBBUFFET) { HP(100); Items(ITEM_ORAN_BERRY, ITEM_BLACK_SLUDGE); Speed(4); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_GRASSY_TERRAIN); } + } SCENE { + MESSAGE("The opposing Wobbuffet is healed by the grassy terrain!"); + HP_BAR(opponentRight, captureDamage: &healed); + HP_BAR(opponentRight, captureDamage: &damage); + MESSAGE("The opposing Wobbuffet was hurt by the Black Sludge!"); + MESSAGE("The opposing Wynaut is healed by the grassy terrain!"); + MESSAGE("The opposing Wynaut restored a little HP using its Leftovers!"); + MESSAGE("Ekans is healed by the grassy terrain!"); + MESSAGE("Ekans's Shed Skin cured its burn problem!"); + MESSAGE("Wynaut is healed by the grassy terrain!"); + } THEN { + EXPECT_GT(0, healed); + EXPECT_GT(damage, 0); + } +} +#endif diff --git a/test/battle/evolution_tracker.c b/test/battle/evolution_tracker.c index ebc83132fc6b..f205be8e743e 100644 --- a/test/battle/evolution_tracker.c +++ b/test/battle/evolution_tracker.c @@ -135,3 +135,133 @@ DOUBLE_BATTLE_TEST("Evolution Tracker: Bisharp KO-ing eligible battler with burs EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_EVOLUTION_TRACKER), 0); } } + +#if MAX_MON_ITEMS > 1 +WILD_BATTLE_TEST("Evolution Tracker: Bisharp KO-ing a Bisharp that holds Leader's Crest increases tracker (Multi)") +{ + GIVEN { + PLAYER(SPECIES_BISHARP); + OPPONENT(SPECIES_BISHARP) { Items(ITEM_PECHA_BERRY, ITEM_LEADERS_CREST); HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + HP_BAR(opponent, hp: 0); + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_EVOLUTION_TRACKER), 1); + } +} + +// To be replaced with WILD_DOUBLE_BATTLE_TEST when that is made possible (also see TryUpdateEvolutionTracker) +DOUBLE_BATTLE_TEST("Evolution Tracker: Bisharp KO-ing multiple Bisharps holding Leader's Crest increases tracker multiple times (Multi)") +{ + GIVEN { + ASSUME(GetMoveTarget(MOVE_LAVA_PLUME) == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(SPECIES_BISHARP); + PLAYER(SPECIES_BISHARP) { Items(ITEM_PECHA_BERRY, ITEM_LEADERS_CREST); HP(1); } + OPPONENT(SPECIES_BISHARP) { Items(ITEM_PECHA_BERRY, ITEM_LEADERS_CREST); HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_LAVA_PLUME); } + } SCENE { + HP_BAR(opponentLeft, hp: 0); + HP_BAR(playerRight, hp: 0); + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_EVOLUTION_TRACKER), 2); + } +} + +WILD_BATTLE_TEST("Evolution Tracker: Bisharp KO-ing a Bisharp that doesn't hold Leader's Crest doesn't increase tracker (Multi)") +{ + GIVEN { + PLAYER(SPECIES_BISHARP); + OPPONENT(SPECIES_BISHARP) { HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + HP_BAR(opponent, hp: 0); + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_EVOLUTION_TRACKER), 0); + } +} + +WILD_BATTLE_TEST("Evolution Tracker: Bisharp KO-ing a non-Bisharp that holds Leader's Crest doesn't increase tracker (Multi)") +{ + u32 species; + + PARAMETRIZE { species = SPECIES_WOBBUFFET; } + PARAMETRIZE { species = SPECIES_PAWNIARD; } + GIVEN { + PLAYER(SPECIES_BISHARP); + OPPONENT(species) { Items(ITEM_PECHA_BERRY, ITEM_LEADERS_CREST); HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + HP_BAR(opponent, hp: 0); + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_EVOLUTION_TRACKER), 0); + } +} + +WILD_BATTLE_TEST("Evolution Tracker: Pawniard KO-ing a Bisharp that holds Leader's Crest doesn't increase tracker (Multi)") +{ + GIVEN { + PLAYER(SPECIES_PAWNIARD); + OPPONENT(SPECIES_BISHARP) { Items(ITEM_PECHA_BERRY, ITEM_LEADERS_CREST); HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + HP_BAR(opponent, hp: 0); + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_EVOLUTION_TRACKER), 0); + } +} + +WILD_BATTLE_TEST("Evolution Tracker: Bisharp KO-ing eligible battler from contact effects doesn't increase tracker (Multi)") +{ + GIVEN { + ASSUME(GetItemHoldEffect(ITEM_ROCKY_HELMET) == HOLD_EFFECT_ROCKY_HELMET); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_BISHARP) { Items(ITEM_PECHA_BERRY,ITEM_ROCKY_HELMET); } + OPPONENT(SPECIES_BISHARP) { Items(ITEM_PECHA_BERRY, ITEM_LEADERS_CREST); HP(1); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + HP_BAR(opponent, hp: 0); + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_EVOLUTION_TRACKER), 0); + } +} + +WILD_BATTLE_TEST("Evolution Tracker: Bisharp KO-ing eligible battler with passive damage doesn't increase tracker (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_LEECH_SEED) == EFFECT_LEECH_SEED); + PLAYER(SPECIES_BISHARP); + OPPONENT(SPECIES_BISHARP) { Items(ITEM_PECHA_BERRY, ITEM_LEADERS_CREST); HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_LEECH_SEED); } + } SCENE { + HP_BAR(opponent, hp: 0); + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_EVOLUTION_TRACKER), 0); + } +} + +// To be replaced with WILD_DOUBLE_BATTLE_TEST when that is made possible (also see TryUpdateEvolutionTracker) +DOUBLE_BATTLE_TEST("Evolution Tracker: Bisharp KO-ing eligible battler with bursting flames doesn't increase tracker (Multi)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_FLAME_BURST, MOVE_EFFECT_FLAME_BURST)); + PLAYER(SPECIES_BISHARP); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BISHARP) { Items(ITEM_PECHA_BERRY, ITEM_LEADERS_CREST); HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_FLAME_BURST, target: opponentRight); } + } SCENE { + HP_BAR(opponentLeft, hp: 0); + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_EVOLUTION_TRACKER), 0); + } +} +#endif diff --git a/test/battle/exp.c b/test/battle/exp.c index c22b7fa2f55f..425a5466602d 100644 --- a/test/battle/exp.c +++ b/test/battle/exp.c @@ -147,5 +147,83 @@ WILD_BATTLE_TEST("Exp Share(held) gives Experience to mons which did not partici EXPECT_EQ(GetMonData(&gPlayerParty[1], MON_DATA_EXP), gExperienceTables[gSpeciesInfo[SPECIES_WYNAUT].growthRate][40]); } } +#endif // I_EXP_SHARE_ITEM + +#if MAX_MON_ITEMS > 1 +WILD_BATTLE_TEST("Lucky Egg boosts gained exp points by 50% (Multi)", s32 exp) +{ + u32 item = 0; + + PARAMETRIZE { item = ITEM_LUCKY_EGG; } + PARAMETRIZE { item = ITEM_NONE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Level(20); Items(ITEM_ORAN_BERRY, item); } + OPPONENT(SPECIES_CATERPIE) { Level(10); HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet used Scratch!"); + MESSAGE("The wild Caterpie fainted!"); + EXPERIENCE_BAR(player, captureGainedExp: &results[i].exp); + } FINALLY { + EXPECT_MUL_EQ(results[1].exp, Q_4_12(1.5), results[0].exp); + } +} + +WILD_BATTLE_TEST("Large exp gains are supported (Multi)", s32 exp) // #1455 +{ + u8 level = 0; + + PARAMETRIZE { level = 10; } + PARAMETRIZE { level = 50; } + PARAMETRIZE { level = MAX_LEVEL; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Level(1); Items(ITEM_ORAN_BERRY, ITEM_LUCKY_EGG); OTName("Test"); } // OT Name is different so it gets more exp as a traded mon + OPPONENT(SPECIES_BLISSEY) { Level(level); HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet used Scratch!"); + MESSAGE("The wild Blissey fainted!"); + EXPERIENCE_BAR(player, captureGainedExp: &results[i].exp); + } THEN { + EXPECT(GetMonData(&gPlayerParty[0], MON_DATA_LEVEL) > 1); + EXPECT(GetMonData(&gPlayerParty[0], MON_DATA_EXP) > 1); + } FINALLY { + EXPECT_GT(results[1].exp, results[0].exp); + EXPECT_GT(results[2].exp, results[1].exp); + } +} + +#if I_EXP_SHARE_ITEM < GEN_6 + +WILD_BATTLE_TEST("Exp Share(held) gives Experience to mons which did not participate in battle (Multi)") +{ + u32 item = 0; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_EXP_SHARE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { Level(40); Items(ITEM_ORAN_BERRY, item); } + OPPONENT(SPECIES_CATERPIE) { Level(10); HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet used Scratch!"); + MESSAGE("The wild Caterpie fainted!"); + // This message should appear only for gen6> exp share. + NOT MESSAGE("The rest of your team gained EXP. Points thanks to the Exp. Share!"); + } THEN { + if (item == ITEM_EXP_SHARE) + EXPECT_GT(GetMonData(&gPlayerParty[1], MON_DATA_EXP), gExperienceTables[gSpeciesInfo[SPECIES_WYNAUT].growthRate][40]); + else + EXPECT_EQ(GetMonData(&gPlayerParty[1], MON_DATA_EXP), gExperienceTables[gSpeciesInfo[SPECIES_WYNAUT].growthRate][40]); + } +} #endif // I_EXP_SHARE_ITEM +#endif diff --git a/test/battle/form_change/battle_after_move.c b/test/battle/form_change/battle_after_move.c index 2dc9d95a15eb..d6f4c5d1902b 100644 --- a/test/battle/form_change/battle_after_move.c +++ b/test/battle/form_change/battle_after_move.c @@ -129,3 +129,81 @@ SINGLE_BATTLE_TEST("Relic Song transforms Meloetta after Magician was activated" EXPECT_EQ(player->species, SPECIES_MELOETTA_PIROUETTE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Relic Song transformation is the last thing that happens after it hits (Traits)") +{ + GIVEN { + PLAYER(SPECIES_MELOETTA_ARIA); + OPPONENT(SPECIES_GOSSIFLEUR) { HP(1); Ability(ABILITY_REGENERATOR); Innates(ABILITY_COTTON_DOWN); } + } WHEN { + TURN { MOVE(player, MOVE_RELIC_SONG); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_RELIC_SONG, player); + HP_BAR(opponent); + MESSAGE("The opposing Gossifleur fainted!"); + ABILITY_POPUP(opponent, ABILITY_COTTON_DOWN); + MESSAGE("Meloetta's Speed fell!"); + MESSAGE("Meloetta transformed!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_MELOETTA_PIROUETTE); + } +} + +SINGLE_BATTLE_TEST("Relic Song loses the form-changing effect with Sheer Force (Traits)") +{ + GIVEN { + PLAYER(SPECIES_MELOETTA_ARIA){ Ability(ABILITY_SERENE_GRACE); Innates(ABILITY_SHEER_FORCE); } + OPPONENT(SPECIES_NIDOKING) { Ability(ABILITY_RIVALRY); Innates(ABILITY_SHEER_FORCE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SKILL_SWAP); MOVE(player, MOVE_RELIC_SONG); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RELIC_SONG, player); + HP_BAR(opponent); + NOT MESSAGE("Meloetta transformed!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_MELOETTA_ARIA); + } +} + +SINGLE_BATTLE_TEST("Relic Song transforms Meloetta after Magician was activated (Traits)") +{ + GIVEN { + PLAYER(SPECIES_MELOETTA_ARIA){ Ability(ABILITY_SERENE_GRACE); Innates(ABILITY_MAGICIAN); } + OPPONENT(SPECIES_DELPHOX) { Ability(ABILITY_BLAZE); Item(ITEM_POTION); } + } WHEN { + TURN { MOVE(opponent, MOVE_SKILL_SWAP); MOVE(player, MOVE_RELIC_SONG); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RELIC_SONG, player); + HP_BAR(opponent); + ABILITY_POPUP(player, ABILITY_MAGICIAN); + MESSAGE("Meloetta stole the opposing Delphox's Potion!"); + MESSAGE("Meloetta transformed!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_MELOETTA_PIROUETTE); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Relic Song transforms Meloetta after Magician was activated (Multi)") +{ + GIVEN { + PLAYER(SPECIES_MELOETTA_ARIA); + OPPONENT(SPECIES_DELPHOX) { Ability(ABILITY_MAGICIAN); Items(ITEM_PECHA_BERRY, ITEM_POTION); } + } WHEN { + TURN { MOVE(opponent, MOVE_SKILL_SWAP); MOVE(player, MOVE_RELIC_SONG); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RELIC_SONG, player); + HP_BAR(opponent); + ABILITY_POPUP(player, ABILITY_MAGICIAN); + MESSAGE("Meloetta stole the opposing Delphox's Potion!"); + MESSAGE("Meloetta transformed!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_MELOETTA_PIROUETTE); + } +} +#endif diff --git a/test/battle/form_change/battle_switch.c b/test/battle/form_change/battle_switch.c index 92e943d03345..1e46ede64a6a 100644 --- a/test/battle/form_change/battle_switch.c +++ b/test/battle/form_change/battle_switch.c @@ -20,3 +20,25 @@ SINGLE_BATTLE_TEST("Aegislash reverts to Shield Form upon switching out") EXPECT_EQ(player->species, SPECIES_AEGISLASH_SHIELD); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Aegislash reverts to Shield Form upon switching out (Traits)") +{ + GIVEN { + PLAYER(SPECIES_AEGISLASH_SHIELD) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_STANCE_CHANGE); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { SWITCH(player, 1); } + TURN { SWITCH(player, 0); } + } SCENE { + ABILITY_POPUP(player, ABILITY_STANCE_CHANGE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Aegislash used Scratch!"); + MESSAGE("The opposing Wobbuffet used Celebrate!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_AEGISLASH_SHIELD); + } +} +#endif diff --git a/test/battle/form_change/begin_battle.c b/test/battle/form_change/begin_battle.c index a133d321a3b3..98b08c4f3d8a 100644 --- a/test/battle/form_change/begin_battle.c +++ b/test/battle/form_change/begin_battle.c @@ -74,3 +74,67 @@ SINGLE_BATTLE_TEST("Zamazenta's Iron Head becomes Behemoth Bash upon form change EXPECT_EQ(player->moves[0], MOVE_BEHEMOTH_BASH); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Zacian changes into its Crowned Form when holding the Rusted Sword upon battle start (Multi)") +{ + u16 item; + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_RUSTED_SWORD; } + GIVEN { + PLAYER(SPECIES_ZACIAN_HERO) { Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); } + } THEN { + if (item == ITEM_NONE) + EXPECT_EQ(player->species, SPECIES_ZACIAN_HERO); + else + EXPECT_EQ(player->species, SPECIES_ZACIAN_CROWNED); + } +} + +SINGLE_BATTLE_TEST("Zacian's Iron Head becomes Behemoth Blade upon form change (Multi)") +{ + GIVEN { + PLAYER(SPECIES_ZACIAN_HERO) { Items(ITEM_PECHA_BERRY, ITEM_RUSTED_SWORD); Moves(MOVE_IRON_HEAD, MOVE_CELEBRATE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); } + } THEN { + ASSUME(player->species == SPECIES_ZACIAN_CROWNED); // Assumes form change worked. + EXPECT_EQ(player->moves[0], MOVE_BEHEMOTH_BLADE); + } +} + +SINGLE_BATTLE_TEST("Zamazenta changes into its Crowned Form when holding the Rusted Shield upon battle start (Multi)") +{ + u16 item; + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_RUSTED_SHIELD; } + GIVEN { + PLAYER(SPECIES_ZAMAZENTA_HERO) { Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); } + } THEN { + if (item == ITEM_NONE) + EXPECT_EQ(player->species, SPECIES_ZAMAZENTA_HERO); + else + EXPECT_EQ(player->species, SPECIES_ZAMAZENTA_CROWNED); + } +} + +SINGLE_BATTLE_TEST("Zamazenta's Iron Head becomes Behemoth Bash upon form change (Multi)") +{ + GIVEN { + PLAYER(SPECIES_ZAMAZENTA_HERO) { Items(ITEM_PECHA_BERRY, ITEM_RUSTED_SHIELD); Moves(MOVE_IRON_HEAD, MOVE_CELEBRATE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); } + } THEN { + ASSUME(player->species == SPECIES_ZAMAZENTA_CROWNED); // Assumes form change worked. + EXPECT_EQ(player->moves[0], MOVE_BEHEMOTH_BASH); + } +} +#endif diff --git a/test/battle/form_change/faint.c b/test/battle/form_change/faint.c index b0566d1e5665..9b9b8f7e25fa 100644 --- a/test/battle/form_change/faint.c +++ b/test/battle/form_change/faint.c @@ -112,3 +112,55 @@ SINGLE_BATTLE_TEST("Terapagos reverts to the correct form upon fainting after te EXPECT_EQ(player->species, SPECIES_TERAPAGOS_TERASTAL); // Not Normal form due to Tera Shift } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Causing a Forecast or Flower Gift Pokémon to faint should not cause a message (Traits)") // issue 7795 +{ + u32 species; + PARAMETRIZE { species = SPECIES_CASTFORM; } + PARAMETRIZE { species = SPECIES_CHERRIM; } + GIVEN { + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_VULPIX) { Ability(ABILITY_FLASH_FIRE); Innates(ABILITY_DROUGHT); } + OPPONENT(species) { HP(1); } + } WHEN { + TURN { MOVE(playerRight, MOVE_GYRO_BALL, target: opponentRight); } + } SCENE { + if (species == SPECIES_CASTFORM) { + MESSAGE("The opposing Castform fainted!"); + NOT MESSAGE("The opposing Castform transformed!"); + } else { + MESSAGE("The opposing Cherrim fainted!"); + NOT MESSAGE("The opposing Cherrim transformed!"); + } + } +} + +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Ogerpon reverts to the correct form upon fainting after terastallizing (Multi)") +{ + u32 species, item; + PARAMETRIZE { species = SPECIES_OGERPON_TEAL; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_OGERPON_WELLSPRING; item = ITEM_WELLSPRING_MASK; } + PARAMETRIZE { species = SPECIES_OGERPON_HEARTHFLAME; item = ITEM_HEARTHFLAME_MASK; } + PARAMETRIZE { species = SPECIES_OGERPON_CORNERSTONE; item = ITEM_CORNERSTONE_MASK; } + GIVEN { + PLAYER(species) { HP(1); Items(ITEM_NUGGET, item); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); + MOVE(opponent, MOVE_SCRATCH); + SEND_OUT(player, 1); + } + TURN { USE_ITEM(player, ITEM_REVIVE, 0); } + TURN { SWITCH(player, 0); } + } THEN { + EXPECT_EQ(player->species, species); + } +} +#endif diff --git a/test/battle/form_change/mega_evolution.c b/test/battle/form_change/mega_evolution.c index 8e1945c55845..99a865365a84 100644 --- a/test/battle/form_change/mega_evolution.c +++ b/test/battle/form_change/mega_evolution.c @@ -254,3 +254,213 @@ SINGLE_BATTLE_TEST("Rayquaza returns its base Form upon fainting end after Mega EXPECT_EQ(player->species, SPECIES_RAYQUAZA); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Venusaur can Mega Evolve holding Venusaurite (Multi)") +{ + GIVEN { + PLAYER(SPECIES_VENUSAUR) { Items(ITEM_PECHA_BERRY, ITEM_VENUSAURITE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + } SCENE { + MESSAGE("Venusaur's Venusaurite is reacting to 1's Mega Ring!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, player); + MESSAGE("Venusaur has Mega Evolved into Mega Venusaur!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_VENUSAUR_MEGA); + } +} + +DOUBLE_BATTLE_TEST("Mega Evolution's order is determined by Speed - opponent faster (Multi)") +{ + GIVEN { + PLAYER(SPECIES_VENUSAUR) { Items(ITEM_PECHA_BERRY, ITEM_VENUSAURITE); Speed(1); } + PLAYER(SPECIES_WOBBUFFET) { Speed(3); } + OPPONENT(SPECIES_GARDEVOIR) { Items(ITEM_PECHA_BERRY, ITEM_GARDEVOIRITE); Speed(3); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(4); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + } SCENE { + MESSAGE("The opposing Gardevoir's Gardevoirite is reacting to 2's Mega Ring!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponentLeft); + MESSAGE("The opposing Gardevoir has Mega Evolved into Mega Gardevoir!"); + MESSAGE("Venusaur's Venusaurite is reacting to 1's Mega Ring!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, playerLeft); + MESSAGE("Venusaur has Mega Evolved into Mega Venusaur!"); + } +} + +DOUBLE_BATTLE_TEST("Mega Evolution's order is determined by Speed - player faster (Multi)") +{ + GIVEN { + PLAYER(SPECIES_VENUSAUR) { Items(ITEM_PECHA_BERRY, ITEM_VENUSAURITE); Speed(5); } + PLAYER(SPECIES_WOBBUFFET) { Speed(3); } + OPPONENT(SPECIES_GARDEVOIR) { Items(ITEM_PECHA_BERRY, ITEM_GARDEVOIRITE); Speed(2); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(4); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + } SCENE { + MESSAGE("Venusaur's Venusaurite is reacting to 1's Mega Ring!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, playerLeft); + MESSAGE("Venusaur has Mega Evolved into Mega Venusaur!"); + MESSAGE("The opposing Gardevoir's Gardevoirite is reacting to 2's Mega Ring!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponentLeft); + MESSAGE("The opposing Gardevoir has Mega Evolved into Mega Gardevoir!"); + } +} + +SINGLE_BATTLE_TEST("Mega Evolution doesn't affect turn order (Gen6) (Multi)") +{ + GIVEN { + WITH_CONFIG(CONFIG_MEGA_EVO_TURN_ORDER, GEN_6); + PLAYER(SPECIES_GARDEVOIR) { Items(ITEM_PECHA_BERRY, ITEM_GARDEVOIRITE); } + OPPONENT(SPECIES_WOBBUFFET) {} + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Celebrate!"); + MESSAGE("Gardevoir used Celebrate!"); + } THEN { + ASSUME(player->speed == 205); + } +} + +SINGLE_BATTLE_TEST("Mega Evolution affects turn order (Gen7+) (Multi)") +{ + GIVEN { + WITH_CONFIG(CONFIG_MEGA_EVO_TURN_ORDER, GEN_7); + PLAYER(SPECIES_GARDEVOIR) { Items(ITEM_PECHA_BERRY, ITEM_GARDEVOIRITE);} + OPPONENT(SPECIES_WOBBUFFET) {} + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + } SCENE { + MESSAGE("Gardevoir used Celebrate!"); + MESSAGE("The opposing Wobbuffet used Celebrate!"); + } THEN { + ASSUME(player->speed == 205); + } +} + +SINGLE_BATTLE_TEST("Abilities replaced by Mega Evolution do not affect turn order (Multi)") +{ + GIVEN { + WITH_CONFIG(CONFIG_MEGA_EVO_TURN_ORDER, GEN_7); + ASSUME(GetSpeciesAbility(SPECIES_SABLEYE_MEGA, 0) != ABILITY_STALL + && GetSpeciesAbility(SPECIES_SABLEYE_MEGA, 1) != ABILITY_STALL); + PLAYER(SPECIES_SABLEYE) { Items(ITEM_PECHA_BERRY, ITEM_SABLENITE); Ability(ABILITY_STALL); Speed(105); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(44); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + } SCENE { + MESSAGE("Sableye used Celebrate!"); + MESSAGE("The opposing Wobbuffet used Celebrate!"); + } THEN { + ASSUME(player->speed == 105); + } +} + +DOUBLE_BATTLE_TEST("Mega Evolution happens after switching, but before Focus Punch-like Moves (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FOCUS_PUNCH) == EFFECT_FOCUS_PUNCH); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_VENUSAUR) { Items(ITEM_PECHA_BERRY, ITEM_VENUSAURITE); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(opponentRight, 2); MOVE(playerRight, MOVE_FOCUS_PUNCH, gimmick: GIMMICK_MEGA, target: opponentLeft); MOVE(playerLeft, MOVE_FOCUS_PUNCH, target: opponentLeft); } + TURN {} + } SCENE { + MESSAGE("2 withdrew Wobbuffet!"); + MESSAGE("2 sent out Wobbuffet!"); + + MESSAGE("Venusaur's Venusaurite is reacting to 1's Mega Ring!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, playerRight); + MESSAGE("Venusaur has Mega Evolved into Mega Venusaur!"); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FOCUS_PUNCH_SETUP, playerRight); + MESSAGE("Venusaur is tightening its focus!"); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FOCUS_PUNCH_SETUP, playerLeft); + MESSAGE("Wobbuffet is tightening its focus!"); + } +} + +SINGLE_BATTLE_TEST("Regular Mega Evolution and Fervent Wish Mega Evolution can happen on the same turn (Multi)") +{ + GIVEN { + PLAYER(SPECIES_RAYQUAZA) { Moves(MOVE_DRAGON_ASCENT, MOVE_CELEBRATE); Speed(3); } + OPPONENT(SPECIES_GARDEVOIR) { Items(ITEM_PECHA_BERRY, ITEM_GARDEVOIRITE); Speed(2); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); MOVE(opponent, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + } SCENE { + MESSAGE("1's fervent wish has reached Rayquaza!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, player); + MESSAGE("Rayquaza has Mega Evolved into Mega Rayquaza!"); + + MESSAGE("The opposing Gardevoir's Gardevoirite is reacting to 2's Mega Ring!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponent); + MESSAGE("The opposing Gardevoir has Mega Evolved into Mega Gardevoir!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_RAYQUAZA_MEGA); + EXPECT_EQ(opponent->species, SPECIES_GARDEVOIR_MEGA); + } +} + +SINGLE_BATTLE_TEST("Mega Evolved Pokemon do not change abilities after fainting (Multi)") +{ + GIVEN { + ASSUME(MoveMakesContact(MOVE_CRUNCH) == TRUE); + ASSUME(GetSpeciesAbility(SPECIES_GARCHOMP_MEGA, 0) != ABILITY_ROUGH_SKIN); + ASSUME(GetSpeciesAbility(SPECIES_GARCHOMP_MEGA, 1) != ABILITY_ROUGH_SKIN); + ASSUME(GetSpeciesAbility(SPECIES_GARCHOMP_MEGA, 2) != ABILITY_ROUGH_SKIN); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GARCHOMP) { Ability(ABILITY_ROUGH_SKIN); Items(ITEM_PECHA_BERRY, ITEM_GARCHOMPITE); HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_CRUNCH); MOVE(opponent, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CRUNCH, player); + MESSAGE("The opposing Garchomp fainted!"); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_ROUGH_SKIN); + MESSAGE("Wobbuffet was hurt by the opposing Garchomp's Rough Skin!"); + HP_BAR(player); + } + } +} + +SINGLE_BATTLE_TEST("Venusaur returns its base Form upon battle end after Mega Evolving (Multi)") +{ + GIVEN { + PLAYER(SPECIES_VENUSAUR) { Items(ITEM_ORAN_BERRY, ITEM_VENUSAURITE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), SPECIES_VENUSAUR); + } +} + +SINGLE_BATTLE_TEST("Venusaur returns its base Form upon fainting end after Mega Evolving (Multi)") +{ + GIVEN { + PLAYER(SPECIES_VENUSAUR) { HP(1); Items(ITEM_GREAT_BALL, ITEM_VENUSAURITE); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); + MOVE(opponent, MOVE_SCRATCH); + SEND_OUT(player, 1); + } + TURN { USE_ITEM(player, ITEM_REVIVE, 0); } + TURN { SWITCH(player, 0); } + } THEN { + EXPECT_EQ(player->species, SPECIES_VENUSAUR); + } +} + +#endif diff --git a/test/battle/form_change/primal_reversion.c b/test/battle/form_change/primal_reversion.c index 445420a55d52..300d03f97eb2 100644 --- a/test/battle/form_change/primal_reversion.c +++ b/test/battle/form_change/primal_reversion.c @@ -369,3 +369,375 @@ SINGLE_BATTLE_TEST("Primal Reversion is NOT reverted upon fainting") EXPECT_EQ(player->species, targetSpecies); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Primal reversion happens for Groudon only when holding Red Orb (Multi)") +{ + u16 heldItem; + PARAMETRIZE { heldItem = ITEM_NONE;} + PARAMETRIZE { heldItem = ITEM_RED_ORB;} + PARAMETRIZE { heldItem = ITEM_BLUE_ORB;} + GIVEN { + PLAYER(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, heldItem); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); } + } SCENE { + if (heldItem == ITEM_RED_ORB) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, player); + MESSAGE("Groudon's Primal Reversion! It reverted to its primal state!"); + } + else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, player); + MESSAGE("Groudon's Primal Reversion! It reverted to its primal state!"); + } + } + } THEN { + if (heldItem == ITEM_RED_ORB) { + EXPECT_EQ(player->species, SPECIES_GROUDON_PRIMAL); + } + else { + EXPECT_EQ(player->species, SPECIES_GROUDON); + } + } +} + +SINGLE_BATTLE_TEST("Primal reversion happens for Kyogre only when holding Blue Orb (Multi)") +{ + u16 heldItem; + PARAMETRIZE { heldItem = ITEM_NONE;} + PARAMETRIZE { heldItem = ITEM_RED_ORB;} + PARAMETRIZE { heldItem = ITEM_BLUE_ORB;} + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KYOGRE) { Items(ITEM_PECHA_BERRY, heldItem); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + if (heldItem == ITEM_BLUE_ORB) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, opponent); + MESSAGE("The opposing Kyogre's Primal Reversion! It reverted to its primal state!"); + } + else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, opponent); + MESSAGE("The opposing Kyogre's Primal Reversion! It reverted to its primal state!"); + } + } + } THEN { + if (heldItem == ITEM_BLUE_ORB) { + EXPECT_EQ(opponent->species, SPECIES_KYOGRE_PRIMAL); + } + else { + EXPECT_EQ(opponent->species, SPECIES_KYOGRE); + } + } +} + +DOUBLE_BATTLE_TEST("Primal reversion's order is determined by Speed - opponent faster (Multi)") +{ + GIVEN { + PLAYER(SPECIES_KYOGRE) { Items(ITEM_PECHA_BERRY, ITEM_BLUE_ORB); Speed(5); }; + PLAYER(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); Speed(15); }; + OPPONENT(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); Speed(10); } + OPPONENT(SPECIES_KYOGRE) { Items(ITEM_PECHA_BERRY, ITEM_BLUE_ORB); Speed(20); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, opponentRight); + MESSAGE("The opposing Kyogre's Primal Reversion! It reverted to its primal state!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, playerRight); + MESSAGE("Groudon's Primal Reversion! It reverted to its primal state!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, opponentLeft); + MESSAGE("The opposing Groudon's Primal Reversion! It reverted to its primal state!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, playerLeft); + MESSAGE("Kyogre's Primal Reversion! It reverted to its primal state!"); + } THEN { + EXPECT_EQ(playerLeft->species, SPECIES_KYOGRE_PRIMAL); + EXPECT_EQ(opponentLeft->species, SPECIES_GROUDON_PRIMAL); + EXPECT_EQ(opponentRight->species, SPECIES_KYOGRE_PRIMAL); + EXPECT_EQ(playerRight->species, SPECIES_GROUDON_PRIMAL); + } +} + +DOUBLE_BATTLE_TEST("Primal reversion's order is determined by Speed - player faster (Multi)") +{ + GIVEN { + PLAYER(SPECIES_KYOGRE) { Items(ITEM_PECHA_BERRY, ITEM_BLUE_ORB); Speed(20); }; + PLAYER(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); Speed(30); }; + OPPONENT(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); Speed(10); } + OPPONENT(SPECIES_KYOGRE) { Items(ITEM_PECHA_BERRY, ITEM_BLUE_ORB); Speed(2); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, playerRight); + MESSAGE("Groudon's Primal Reversion! It reverted to its primal state!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, playerLeft); + MESSAGE("Kyogre's Primal Reversion! It reverted to its primal state!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, opponentLeft); + MESSAGE("The opposing Groudon's Primal Reversion! It reverted to its primal state!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, opponentRight); + MESSAGE("The opposing Kyogre's Primal Reversion! It reverted to its primal state!"); + } THEN { + EXPECT_EQ(playerLeft->species, SPECIES_KYOGRE_PRIMAL); + EXPECT_EQ(opponentLeft->species, SPECIES_GROUDON_PRIMAL); + EXPECT_EQ(opponentRight->species, SPECIES_KYOGRE_PRIMAL); + EXPECT_EQ(playerRight->species, SPECIES_GROUDON_PRIMAL); + } +} + +SINGLE_BATTLE_TEST("Primal reversion happens after a mon is sent out after a mon is fainted (Multi)") +{ + GIVEN { + ASSUME(!IsBattleMoveStatus(MOVE_SCRATCH)); + PLAYER(SPECIES_WOBBUFFET) {HP(1); } + PLAYER(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); SEND_OUT(player, 1); } + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet fainted!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, player); + MESSAGE("Groudon's Primal Reversion! It reverted to its primal state!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_GROUDON_PRIMAL); + } +} + +SINGLE_BATTLE_TEST("Primal reversion happens after a mon is switched in (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, player); + MESSAGE("Groudon's Primal Reversion! It reverted to its primal state!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_GROUDON_PRIMAL); + } +} + +SINGLE_BATTLE_TEST("Primal reversion happens after a switch-in caused by Eject Button (Multi)") +{ + GIVEN { + ASSUME(!IsBattleMoveStatus(MOVE_SCRATCH)); + ASSUME(gItemsInfo[ITEM_EJECT_BUTTON].holdEffect == HOLD_EFFECT_EJECT_BUTTON); + PLAYER(SPECIES_WOBBUFFET) {Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + PLAYER(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); SEND_OUT(player, 1); } + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet is switched out with the Eject Button!"); + SEND_IN_MESSAGE("Groudon"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, player); + MESSAGE("Groudon's Primal Reversion! It reverted to its primal state!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_GROUDON_PRIMAL); + } +} + +SINGLE_BATTLE_TEST("Primal reversion happens after a switch-in caused by Red Card (Multi)") +{ + GIVEN { + ASSUME(!IsBattleMoveStatus(MOVE_SCRATCH)); + ASSUME(gItemsInfo[ITEM_RED_CARD].holdEffect == HOLD_EFFECT_RED_CARD); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); } + OPPONENT(SPECIES_WOBBUFFET) {Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("The opposing Wobbuffet held up its Red Card against Wobbuffet!"); + MESSAGE("Groudon was dragged out!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, player); + MESSAGE("Groudon's Primal Reversion! It reverted to its primal state!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_GROUDON_PRIMAL); + } +} + +SINGLE_BATTLE_TEST("Primal reversion happens after the entry hazards damage (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SPIKES) == EFFECT_SPIKES); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SPIKES); } + TURN { MOVE(opponent, MOVE_SPIKES); SWITCH(player, 1);} + } SCENE { + SEND_IN_MESSAGE("Groudon"); + HP_BAR(player); + MESSAGE("Groudon was hurt by the spikes!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, player); + MESSAGE("Groudon's Primal Reversion! It reverted to its primal state!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_GROUDON_PRIMAL); + } +} + +SINGLE_BATTLE_TEST("Primal reversion happens immediately if it was brought in by U-turn (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); } + OPPONENT(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, player); + MESSAGE("Groudon's Primal Reversion! It reverted to its primal state!"); + MESSAGE("2 sent out Wynaut!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_GROUDON_PRIMAL); + } +} + + +DOUBLE_BATTLE_TEST("Primal reversion triggers for multiple battlers if multiple fainted the previous turn (Multi)") +{ + GIVEN { + ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_CATERPIE) { HP(1); } + PLAYER(SPECIES_RESHIRAM); + OPPONENT(SPECIES_CATERPIE) { HP(1); } + OPPONENT(SPECIES_CATERPIE) { HP(1); } + OPPONENT(SPECIES_KYOGRE) { Items(ITEM_PECHA_BERRY, ITEM_BLUE_ORB); } + OPPONENT(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_EARTHQUAKE); + SEND_OUT(opponentRight, 3); + SEND_OUT(opponentLeft, 2); + SEND_OUT(playerRight, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_PRIMORDIAL_SEA); + ABILITY_POPUP(opponentRight, ABILITY_DESOLATE_LAND); + } +} + +DOUBLE_BATTLE_TEST("Primal reversion triggers for all battlers if multiple fainted the previous turn (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + ASSUME(GetMoveTarget(MOVE_EXPLOSION) == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_CATERPIE) { HP(1); } + PLAYER(SPECIES_KYOGRE) { Items(ITEM_PECHA_BERRY, ITEM_BLUE_ORB); } + PLAYER(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); } + OPPONENT(SPECIES_CATERPIE) { HP(1); } + OPPONENT(SPECIES_CATERPIE) { HP(1); } + OPPONENT(SPECIES_KYOGRE) { Items(ITEM_PECHA_BERRY, ITEM_BLUE_ORB); } + OPPONENT(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_EXPLOSION); + SEND_OUT(opponentRight, 3); + SEND_OUT(opponentLeft, 2); + SEND_OUT(playerRight, 3); + SEND_OUT(playerLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, playerLeft); + ABILITY_POPUP(playerLeft, ABILITY_PRIMORDIAL_SEA); + ABILITY_POPUP(playerRight, ABILITY_DESOLATE_LAND); + ABILITY_POPUP(opponentLeft, ABILITY_PRIMORDIAL_SEA); + ABILITY_POPUP(opponentRight, ABILITY_DESOLATE_LAND); + } +} + +DOUBLE_BATTLE_TEST("Primal reversion and other switch-in effects trigger for all battlers if multiple fainted the previous turn (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + ASSUME(GetMoveTarget(MOVE_EXPLOSION) == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(GetMoveEffect(MOVE_STICKY_WEB) == EFFECT_STICKY_WEB); + ASSUME(GetMoveEffect(MOVE_SPIKES) == EFFECT_SPIKES); + ASSUME(GetMoveEffect(MOVE_TOXIC_SPIKES) == EFFECT_TOXIC_SPIKES); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_CATERPIE) { HP(1); } + PLAYER(SPECIES_SCRAFTY) { Ability(ABILITY_INTIMIDATE); } + PLAYER(SPECIES_RESHIRAM); + OPPONENT(SPECIES_CATERPIE) { HP(1); } + OPPONENT(SPECIES_CATERPIE) { HP(1); } + OPPONENT(SPECIES_KYOGRE) { Items(ITEM_ORAN_BERRY, ITEM_BLUE_ORB); } + OPPONENT(SPECIES_GROUDON) { Items(ITEM_ORAN_BERRY, ITEM_RED_ORB); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_STICKY_WEB); + MOVE(opponentLeft, MOVE_SPIKES); + MOVE(playerRight, MOVE_TOXIC_SPIKES); } + TURN { MOVE(playerLeft, MOVE_EXPLOSION); + SEND_OUT(opponentRight, 3); + SEND_OUT(opponentLeft, 2); + SEND_OUT(playerRight, 3); + SEND_OUT(playerLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKES, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, playerLeft); + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ABILITY_POPUP(playerRight, ABILITY_TURBOBLAZE); + ABILITY_POPUP(opponentLeft, ABILITY_PRIMORDIAL_SEA); + ABILITY_POPUP(opponentRight, ABILITY_DESOLATE_LAND); + } THEN { + EXPECT_NE(playerLeft->hp, playerLeft->maxHP); + EXPECT_NE(playerRight->hp, playerRight->maxHP); + EXPECT_EQ(opponentLeft->status1, STATUS1_POISON); + EXPECT_EQ(opponentRight->status1, STATUS1_POISON); + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(opponentLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Primal Reversion is reverted upon battle end (Multi)") +{ + u32 species, item; + PARAMETRIZE { species = SPECIES_GROUDON; item = ITEM_RED_ORB; } + PARAMETRIZE { species = SPECIES_KYOGRE; item = ITEM_BLUE_ORB; } + GIVEN { + PLAYER(species) { Items(ITEM_ORAN_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); } + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), species); + } +} + +SINGLE_BATTLE_TEST("Primal Reversion is NOT reverted upon fainting (Multi)") +{ + u32 species, item, targetSpecies; + PARAMETRIZE { species = SPECIES_GROUDON; item = ITEM_RED_ORB; targetSpecies = SPECIES_GROUDON_PRIMAL; } + PARAMETRIZE { species = SPECIES_KYOGRE; item = ITEM_BLUE_ORB; targetSpecies = SPECIES_KYOGRE_PRIMAL; } + GIVEN { + PLAYER(species) { HP(1); Items(ITEM_GREAT_BALL, item); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_CELEBRATE); + MOVE(opponent, MOVE_SCRATCH); + SEND_OUT(player, 1); + } + TURN { USE_ITEM(player, ITEM_REVIVE, 0); } + TURN { SWITCH(player, 0); } + } THEN { + EXPECT_EQ(player->species, targetSpecies); + } +} + +#endif diff --git a/test/battle/form_change/ultra_burst.c b/test/battle/form_change/ultra_burst.c index 1db0bf7fd8bc..0ba00355122a 100644 --- a/test/battle/form_change/ultra_burst.c +++ b/test/battle/form_change/ultra_burst.c @@ -157,3 +157,164 @@ SINGLE_BATTLE_TEST("Necrozma returns its proper Form upon fainting after Ultra B EXPECT_EQ(player->species, species); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Dusk Mane Necrozma can Ultra Burst holding Ultranecrozium Z (Multi)") +{ + GIVEN { + PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Items(ITEM_PECHA_BERRY, ITEM_ULTRANECROZIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } + } SCENE { + MESSAGE("Bright light is about to burst out of Necrozma!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ULTRA_BURST, player); + MESSAGE("Necrozma regained its true power through Ultra Burst!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_NECROZMA_ULTRA); + } +} + +DOUBLE_BATTLE_TEST("Ultra Burst's order is determined by Speed - opponent faster (Multi)") +{ + GIVEN { + PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Items(ITEM_PECHA_BERRY, ITEM_ULTRANECROZIUM_Z); Speed(1); } + PLAYER(SPECIES_WOBBUFFET) { Speed(3); } + OPPONENT(SPECIES_NECROZMA_DAWN_WINGS) { Items(ITEM_PECHA_BERRY, ITEM_ULTRANECROZIUM_Z); Speed(3); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(4); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } + } SCENE { + MESSAGE("Bright light is about to burst out of the opposing Necrozma!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ULTRA_BURST, opponentLeft); + MESSAGE("The opposing Necrozma regained its true power through Ultra Burst!"); + MESSAGE("Bright light is about to burst out of Necrozma!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ULTRA_BURST, playerLeft); + MESSAGE("Necrozma regained its true power through Ultra Burst!"); + } +} + +DOUBLE_BATTLE_TEST("Ultra Burst's order is determined by Speed - player faster (Multi)") +{ + GIVEN { + PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Items(ITEM_PECHA_BERRY, ITEM_ULTRANECROZIUM_Z); Speed(5); } + PLAYER(SPECIES_WOBBUFFET) { Speed(3); } + OPPONENT(SPECIES_NECROZMA_DAWN_WINGS) { Items(ITEM_PECHA_BERRY, ITEM_ULTRANECROZIUM_Z); Speed(2); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(4); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } + } SCENE { + MESSAGE("Bright light is about to burst out of Necrozma!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ULTRA_BURST, playerLeft); + MESSAGE("Necrozma regained its true power through Ultra Burst!"); + MESSAGE("Bright light is about to burst out of the opposing Necrozma!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ULTRA_BURST, opponentLeft); + MESSAGE("The opposing Necrozma regained its true power through Ultra Burst!"); + } +} + +SINGLE_BATTLE_TEST("Ultra Burst affects turn order (Multi)") +{ + GIVEN { + WITH_CONFIG(CONFIG_MEGA_EVO_TURN_ORDER, GEN_7); + PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Items(ITEM_PECHA_BERRY, ITEM_ULTRANECROZIUM_Z);} + OPPONENT(SPECIES_WOBBUFFET) {} + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } + } SCENE { + MESSAGE("Necrozma used Celebrate!"); + MESSAGE("The opposing Wobbuffet used Celebrate!"); + } THEN { + ASSUME(player->speed == 263); + } +} + +DOUBLE_BATTLE_TEST("Ultra Burst happens after switching, but before Focus Punch-like Moves (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FOCUS_PUNCH) == EFFECT_FOCUS_PUNCH); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Items(ITEM_PECHA_BERRY, ITEM_ULTRANECROZIUM_Z); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(opponentRight, 2); MOVE(playerRight, MOVE_FOCUS_PUNCH, gimmick: GIMMICK_ULTRA_BURST, target: opponentLeft); MOVE(playerLeft, MOVE_FOCUS_PUNCH, target: opponentLeft); } + TURN {} + } SCENE { + MESSAGE("2 withdrew Wobbuffet!"); + MESSAGE("2 sent out Wobbuffet!"); + + MESSAGE("Bright light is about to burst out of Necrozma!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ULTRA_BURST, playerRight); + MESSAGE("Necrozma regained its true power through Ultra Burst!"); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FOCUS_PUNCH_SETUP, playerRight); + MESSAGE("Necrozma is tightening its focus!"); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FOCUS_PUNCH_SETUP, playerLeft); + MESSAGE("Wobbuffet is tightening its focus!"); + } +} + +SINGLE_BATTLE_TEST("Ultra Burst and Mega Evolution can happen on the same turn (Multi)") +{ + GIVEN { + PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Items(ITEM_PECHA_BERRY, ITEM_ULTRANECROZIUM_Z); Speed(3); } + OPPONENT(SPECIES_GARDEVOIR) { Items(ITEM_PECHA_BERRY, ITEM_GARDEVOIRITE); Speed(2); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); MOVE(opponent, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + } SCENE { + MESSAGE("Bright light is about to burst out of Necrozma!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ULTRA_BURST, player); + MESSAGE("Necrozma regained its true power through Ultra Burst!"); + + MESSAGE("The opposing Gardevoir's Gardevoirite is reacting to 2's Mega Ring!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponent); + MESSAGE("The opposing Gardevoir has Mega Evolved into Mega Gardevoir!"); + } THEN { + EXPECT_EQ(player->species, SPECIES_NECROZMA_ULTRA); + EXPECT_EQ(opponent->species, SPECIES_GARDEVOIR_MEGA); + } +} + + +SINGLE_BATTLE_TEST("Necrozma returns its proper Form upon battle end after Ultra Bursting (Multi)") +{ + u32 species; + PARAMETRIZE { species = SPECIES_NECROZMA_DUSK_MANE; } + PARAMETRIZE { species = SPECIES_NECROZMA_DAWN_WINGS; } + GIVEN { + PLAYER(species) { Items(ITEM_NUGGET, ITEM_ULTRANECROZIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } + } THEN { + EXPECT_EQ(player->species, SPECIES_NECROZMA_ULTRA); + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), species); + } +} + +SINGLE_BATTLE_TEST("Necrozma returns its proper Form upon fainting after Ultra Bursting (Multi)") +{ + u32 species; + PARAMETRIZE { species = SPECIES_NECROZMA_DUSK_MANE; } + PARAMETRIZE { species = SPECIES_NECROZMA_DAWN_WINGS; } + GIVEN { + PLAYER(species) { HP(1); Items(ITEM_NUGGET, ITEM_ULTRANECROZIUM_Z); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); + MOVE(opponent, MOVE_SCRATCH); + SEND_OUT(player, 1); + } + TURN { USE_ITEM(player, ITEM_REVIVE, 0); } + TURN { SWITCH(player, 0); } + } THEN { + EXPECT_EQ(player->species, species); + } +} + +#endif diff --git a/test/battle/gimmick/dynamax.c b/test/battle/gimmick/dynamax.c index e47cd2ca866e..e393d796f523 100644 --- a/test/battle/gimmick/dynamax.c +++ b/test/battle/gimmick/dynamax.c @@ -1712,3 +1712,142 @@ DOUBLE_BATTLE_TEST("Dynamax stat raising moves don't make stat-changing abilitie TO_DO_BATTLE_TEST("Dynamax: Contrary inverts stat-lowering Max Moves, without showing a message") TO_DO_BATTLE_TEST("Dynamax: Contrary inverts stat-increasing Max Moves, without showing a message") + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Dynamax: Dynamaxed Pokemon can be switched out by Eject Button (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_EJECT_BUTTON].holdEffect == HOLD_EFFECT_EJECT_BUTTON); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_SCRATCH); SEND_OUT(player, 1); } + } SCENE { + MESSAGE("Wobbuffet used Max Strike!"); + MESSAGE("The opposing Wobbuffet used Scratch!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet is switched out with the Eject Button!"); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +// This is true for all item-removing moves. +SINGLE_BATTLE_TEST("Dynamax: Dynamaxed Pokemon are not immune to Knock Off (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_POTION); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_KNOCK_OFF); } + TURN { MOVE(opponent, MOVE_KNOCK_OFF); } + } SCENE { + MESSAGE("Wobbuffet used Max Strike!"); + MESSAGE("The opposing Wobbuffet used Knock Off!"); + MESSAGE("The opposing Wobbuffet knocked off Wobbuffet's Potion!"); + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Dynamax: Dynamaxed Pokemon are not affected by Choice items (Multi)", s16 damage) +{ + u16 item; + PARAMETRIZE { item = ITEM_CHOICE_BAND; } + PARAMETRIZE { item = ITEM_NONE; } + GIVEN { + ASSUME(gItemsInfo[ITEM_CHOICE_BAND].holdEffect == HOLD_EFFECT_CHOICE_BAND); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, item); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_DYNAMAX); } + TURN { MOVE(player, MOVE_ARM_THRUST); } + } SCENE { + MESSAGE("Wobbuffet used Max Strike!"); + HP_BAR(opponent, captureDamage: &results[i].damage); + MESSAGE("Wobbuffet used Max Knuckle!"); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Dynamax: Dynamaxed Pokemon cannot use Max Guard while holding Assault Vest (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_ASSAULT_VEST].holdEffect == HOLD_EFFECT_ASSAULT_VEST); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_ASSAULT_VEST); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_DYNAMAX); } + TURN { MOVE(player, MOVE_PROTECT, allowed: FALSE); MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet used Max Strike!"); + MESSAGE("Wobbuffet used Max Strike!"); + } +} + +SINGLE_BATTLE_TEST("Dynamax: Sitrus Berries heal based on a Pokemon's non-Dynamax HP (Multi)", s16 damage) +{ + u32 dynamax; + PARAMETRIZE { dynamax = GIMMICK_NONE; } + PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; } + GIVEN { + ASSUME(I_SITRUS_BERRY_HEAL >= GEN_4); + ASSUME(gItemsInfo[ITEM_SITRUS_BERRY].holdEffect == HOLD_EFFECT_RESTORE_PCT_HP); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_FLING); MOVE(player, MOVE_SCRATCH, gimmick: dynamax); } + } SCENE { + MESSAGE("Wobbuffet restored its health using its Sitrus Berry!"); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} + +DOUBLE_BATTLE_TEST("Dynamax: G-Max Replenish recycles allies' berries 50\% of the time (Multi)") +{ + PASSES_RANDOMLY(1, 2, RNG_G_MAX_REPLENISH); + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_REPLENISH, MOVE_EFFECT_RECYCLE_BERRIES)); + PLAYER(SPECIES_SNORLAX) { Items(ITEM_PECHA_BERRY, ITEM_APICOT_BERRY); GigantamaxFactor(TRUE); } + PLAYER(SPECIES_MUNCHLAX) { Items(ITEM_PECHA_BERRY, ITEM_APICOT_BERRY); Ability(ABILITY_THICK_FAT); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_APICOT_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_APICOT_BERRY); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_STUFF_CHEEKS); \ + MOVE(playerRight, MOVE_STUFF_CHEEKS); \ + MOVE(opponentLeft, MOVE_STUFF_CHEEKS); \ + MOVE(opponentRight, MOVE_STUFF_CHEEKS); } + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft, gimmick: GIMMICK_DYNAMAX); } + } SCENE { + // turn 1 + MESSAGE("Using Apicot Berry, the Sp. Def of Snorlax rose!"); + MESSAGE("Using Apicot Berry, the Sp. Def of Munchlax rose!"); + MESSAGE("Using Apicot Berry, the Sp. Def of the opposing Wobbuffet rose!"); + MESSAGE("Using Apicot Berry, the Sp. Def of the opposing Wobbuffet rose!"); + // turn 2 + MESSAGE("Snorlax used G-Max Replenish!"); + MESSAGE("Snorlax found one Apicot Berry!"); + MESSAGE("Munchlax found one Apicot Berry!"); + } +} + +SINGLE_BATTLE_TEST("Dynamax: Dynamax is reverted before switch out (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_SCRATCH); SEND_OUT(player, 1); } + TURN { SWITCH(player, 0); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet used Scratch!"); + } +} +#endif diff --git a/test/battle/gimmick/terastal.c b/test/battle/gimmick/terastal.c index 6cd9e290be4c..9a2c0ca89036 100644 --- a/test/battle/gimmick/terastal.c +++ b/test/battle/gimmick/terastal.c @@ -840,3 +840,105 @@ SINGLE_BATTLE_TEST("(TERA) All type indicators function correctly - Opponent") TURN { MOVE(opponent, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("(TERA) Terastallization's 60 BP floor occurs after Technician (Traits)", s16 damage) +{ + bool32 tera; + PARAMETRIZE { tera = GIMMICK_NONE; } + PARAMETRIZE { tera = GIMMICK_TERA; } + GIVEN { + ASSUME(GetMovePower(MOVE_MEGA_DRAIN) == 40); + PLAYER(SPECIES_MR_MIME) { Ability(ABILITY_SOUNDPROOF); Innates(ABILITY_TECHNICIAN); TeraType(TYPE_GRASS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_MEGA_DRAIN, gimmick: tera); } + } SCENE { + MESSAGE("Mr. Mime used Mega Drain!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MEGA_DRAIN, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + // This should be the same as a normal Tera boost. + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("(TERA) Terastallization's 60 BP floor occurs after Technician (Traits)", s16 damage) +{ + bool32 tera; + PARAMETRIZE { tera = GIMMICK_NONE; } + PARAMETRIZE { tera = GIMMICK_TERA; } + GIVEN { + PLAYER(SPECIES_MR_MIME) { Ability(ABILITY_SOUNDPROOF); Innates(ABILITY_TECHNICIAN); TeraType(TYPE_PSYCHIC); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STORED_POWER, gimmick: tera); } + } SCENE { + MESSAGE("Mr. Mime used Stored Power!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STORED_POWER, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + // The jump from 45 BP (20 * 1.5x * 1.5x) to 120 BP (60 * 2.0x) is a 2.667x boost. + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.667), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("(TERA) Protean/Libero cannot change the type of a Terastallized Pokemon (Traits)") +{ + u32 ability, species; + PARAMETRIZE { ability = ABILITY_PROTEAN; species = SPECIES_GRENINJA; } + PARAMETRIZE { ability = ABILITY_LIBERO; species = SPECIES_RABOOT; } + GIVEN { + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); TeraType(TYPE_GRASS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BUBBLE, gimmick: GIMMICK_TERA); + MOVE(opponent, MOVE_EMBER); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BUBBLE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("(TERA) Stellar type's one-time boost factors in dynamically-typed moves (Traits)") +{ + s16 damage[4]; + GIVEN { + ASSUME(GetMoveType(MOVE_WEATHER_BALL) == TYPE_NORMAL); + PLAYER(SPECIES_PELIPPER) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_DRIZZLE); TeraType(TYPE_STELLAR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_WEATHER_BALL, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_RECOVER); } + TURN { MOVE(player, MOVE_TAKE_DOWN); MOVE(opponent, MOVE_RECOVER); } + TURN { MOVE(player, MOVE_TAKE_DOWN); MOVE(opponent, MOVE_RECOVER); } + TURN { MOVE(player, MOVE_WATER_PULSE); MOVE(opponent, MOVE_RECOVER); } + TURN { MOVE(player, MOVE_WATER_PULSE); MOVE(opponent, MOVE_RECOVER); } + } SCENE { + MESSAGE("Pelipper used Weather Ball!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WEATHER_BALL, player); + // turn 2 + MESSAGE("Pelipper used Take Down!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAKE_DOWN, player); + HP_BAR(opponent, captureDamage: &damage[0]); + // turn 3 + MESSAGE("Pelipper used Take Down!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAKE_DOWN, player); + HP_BAR(opponent, captureDamage: &damage[1]); + // turn 4 + MESSAGE("Pelipper used Water Pulse!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PULSE, player); + HP_BAR(opponent, captureDamage: &damage[2]); + // turn 5 + MESSAGE("Pelipper used Water Pulse!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PULSE, player); + HP_BAR(opponent, captureDamage: &damage[3]); + } THEN { + // Take Down should have a Normal type boost applied + EXPECT_MUL_EQ(damage[1], UQ_4_12(1.20), damage[0]); + // Water Pulse should not have a Water type boost applied + EXPECT_EQ(damage[3], damage[2]); + } +} +#endif diff --git a/test/battle/gimmick/zmove.c b/test/battle/gimmick/zmove.c index 74f4ea3675ba..eca1a19ec20a 100644 --- a/test/battle/gimmick/zmove.c +++ b/test/battle/gimmick/zmove.c @@ -690,3 +690,770 @@ SINGLE_BATTLE_TEST("(Z-MOVE) Z-Revelation Dance always transforms into Breakneck TO_DO_BATTLE_TEST("(Z-MOVE) Stat changes from status Z-Moves are not inverted by Contrary") TO_DO_BATTLE_TEST("(Z-MOVE) Stat changes from Extreme Evoboost are inverted by Contrary") TO_DO_BATTLE_TEST("(Z-MOVE) Stat changes from Clangorous Soulblaze are inverted by Contrary") + + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Moves are not affected by -ate abilities") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_SWELLOW, 1) == TYPE_FLYING); + PLAYER(SPECIES_AURORUS) { Ability(ABILITY_SNOW_WARNING); Innates(ABILITY_REFRIGERATE); Item(ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_SWELLOW); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + NOT { MESSAGE("It's super effective!"); } + } +} + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("(Z-MOVE) Dancer does not use a Z-Move if the battler has used a Z-Move the same turn (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_DANCER); Item(ITEM_NORMALIUM_Z); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft, gimmick: GIMMICK_Z_MOVE); + MOVE(playerRight, MOVE_FIERY_DANCE, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIERY_DANCE, playerRight); + ABILITY_POPUP(playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIERY_DANCE, playerLeft); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Splintered Stormshards removes terrain (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SPLINTERED_STORMSHARDS) == EFFECT_ICE_SPINNER); + PLAYER(SPECIES_LYCANROC_DUSK) { Item(ITEM_LYCANIUM_Z); } + OPPONENT(SPECIES_TAPU_LELE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PSYCHIC_SURGE); HP(1000); MaxHP(1000); } + } WHEN { + TURN { MOVE(player, MOVE_STONE_EDGE, gimmick: GIMMICK_Z_MOVE); } + TURN { MOVE(player, MOVE_QUICK_ATTACK); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLINTERED_STORMSHARDS, player); + MESSAGE("The weirdness disappeared from the battlefield!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Searing Sunraze Smash ignores the target's abilities (Traits)") +{ + GIVEN { + PLAYER(SPECIES_SOLGALEO) { Item(ITEM_SOLGANIUM_Z); } + OPPONENT(SPECIES_LAPRAS) { Ability(ABILITY_WATER_ABSORB); Innates(ABILITY_BATTLE_ARMOR); } + } WHEN { + TURN { MOVE(player, MOVE_SUNSTEEL_STRIKE, gimmick: GIMMICK_Z_MOVE, criticalHit: TRUE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SEARING_SUNRAZE_SMASH, player); + HP_BAR(opponent); + MESSAGE("A critical hit!"); + } +} + +TO_DO_BATTLE_TEST("(Z-MOVE) Stat changes from status Z-Moves are not inverted by Contrary (Traits)") +TO_DO_BATTLE_TEST("(Z-MOVE) Stat changes from Extreme Evoboost are inverted by Contrary (Traits)") +TO_DO_BATTLE_TEST("(Z-MOVE) Stat changes from Clangorous Soulblaze are inverted by Contrary (Traits)") +#endif + +#if MAX_MON_ITEMS > 1 +// Basic Functionality +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Moves do not retain priority (Multi)") +{ + GIVEN { + WITH_CONFIG(CONFIG_MEGA_EVO_TURN_ORDER, GEN_7); // TODO: Decouple this config from other gimmicks + ASSUME(GetMoveType(MOVE_QUICK_ATTACK) == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(2); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); + MOVE(player, MOVE_QUICK_ATTACK, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Moves are not affected by -ate abilities (Multi)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_SWELLOW, 1) == TYPE_FLYING); + PLAYER(SPECIES_AURORUS) { Ability(ABILITY_REFRIGERATE); Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_SWELLOW); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + NOT { MESSAGE("It's super effective!"); } + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Moves are affected by Ion Deluge (Multi)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + ASSUME(GetMoveEffect(MOVE_ION_DELUGE) == EFFECT_ION_DELUGE); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_SWELLOW); + } WHEN { + TURN { MOVE(opponent, MOVE_ION_DELUGE); MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Moves deal 1/4 damage through protect (Multi)", s16 damage) +{ + bool32 protected; + PARAMETRIZE { protected = TRUE; } + PARAMETRIZE { protected = FALSE; } + GIVEN { + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + ASSUME(GetMoveEffect(MOVE_PROTECT) == EFFECT_PROTECT); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (protected) + TURN { MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_Z_MOVE); MOVE(opponent, MOVE_PROTECT); } + else + TURN { MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(4), results[1].damage); + } +} + +// Status Z-Effects +SINGLE_BATTLE_TEST("(Z-MOVE) Z_EFFECT_RESET_STATS clears a battler's negative stat stages (Multi)") +{ + GIVEN { + ASSUME(GetMoveZEffect(MOVE_LEECH_SEED) == Z_EFFECT_RESET_STATS); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_GRASSIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SWORDS_DANCE); MOVE(opponent, MOVE_SCREECH); } + TURN { MOVE(player, MOVE_LEECH_SEED, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_LEECH_SEED, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z_EFFECT_ALL_STATS_UP raises all of a battler's stat stages by one (Multi)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_CELEBRATE) == TYPE_NORMAL); + ASSUME(GetMoveZEffect(MOVE_CELEBRATE) == Z_EFFECT_ALL_STATS_UP_1); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z_EFFECT_BOOST_CRITS raises a battler's critical hit ratio by 2 stages (Multi)") +{ + u32 genConfig = 0, chance; + for (u32 j = GEN_1; j <= GEN_5; j++) + PARAMETRIZE { genConfig = j; chance = 4; } // 25% + for (u32 j = GEN_6; j <= GEN_9; j++) + PARAMETRIZE { genConfig = j; chance = 2; } // 50% + PASSES_RANDOMLY(1, chance, RNG_CRITICAL_HIT); + GIVEN { + WITH_CONFIG(CONFIG_CRIT_CHANCE, genConfig); + ASSUME(GetMoveType(MOVE_FORESIGHT) == TYPE_NORMAL); + ASSUME(GetMoveZEffect(MOVE_FORESIGHT) == Z_EFFECT_BOOST_CRITS); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FORESIGHT, gimmick: GIMMICK_Z_MOVE); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FORESIGHT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("A critical hit!"); + } +} + +DOUBLE_BATTLE_TEST("(Z-MOVE) Z_EFFECT_FOLLOW_ME redirects attacks to the user (Multi)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_DESTINY_BOND) == TYPE_GHOST); + ASSUME(GetMoveZEffect(MOVE_DESTINY_BOND) == Z_EFFECT_FOLLOW_ME); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_GHOSTIUM_Z); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_DESTINY_BOND, gimmick: GIMMICK_Z_MOVE); + MOVE(opponentLeft, MOVE_SCRATCH, target: playerRight); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DESTINY_BOND, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); + NOT { HP_BAR(playerRight); } + HP_BAR(playerLeft); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z_EFFECT_RESTORE_REPLACEMENT_HP fully heals the replacement battler's HP (Multi)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_PARTING_SHOT) == TYPE_DARK); + ASSUME(GetMoveZEffect(MOVE_PARTING_SHOT) == Z_EFFECT_RESTORE_REPLACEMENT_HP); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_DARKINIUM_Z); } + PLAYER(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PARTING_SHOT, gimmick: GIMMICK_Z_MOVE); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, player); + HP_BAR(player); + } THEN { + EXPECT_EQ(player->species, SPECIES_WYNAUT); + EXPECT_EQ(player->hp, player->maxHP); + } +} + +// This tests the functionality of Z_EFFECT_RECOVER_HP and Z_EFFECT_ATK_UP_1 (and thus by extension all stat-up Z-effects) +SINGLE_BATTLE_TEST("(Z-MOVE) Z_EFFECT_CURSE activates Z_EFFECT_RECOVER_HP or Z_EFFECT_ATK_UP_1 depending on the type of the battler (Multi)") +{ + u32 species; + PARAMETRIZE { species = SPECIES_WOBBUFFET; } + PARAMETRIZE { species = SPECIES_DUSCLOPS; } + GIVEN { + ASSUME(GetMoveType(MOVE_CURSE) == TYPE_GHOST); + ASSUME(GetSpeciesType(SPECIES_WOBBUFFET, 0) != TYPE_GHOST); + ASSUME(GetSpeciesType(SPECIES_WOBBUFFET, 1) != TYPE_GHOST); + ASSUME(GetSpeciesType(SPECIES_DUSCLOPS, 0) == TYPE_GHOST); + ASSUME(GetMoveZEffect(MOVE_CURSE) == Z_EFFECT_CURSE); + PLAYER(species) { Items(ITEM_PECHA_BERRY, ITEM_GHOSTIUM_Z); HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CURSE, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + if (species == SPECIES_DUSCLOPS) { + HP_BAR(player); + NOT { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); } + ANIMATION(ANIM_TYPE_MOVE, MOVE_CURSE, player); + HP_BAR(player); + } else { + NOT { HP_BAR(player); } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CURSE, player); + NOT { HP_BAR(player); } + } + } THEN { + if (species == SPECIES_DUSCLOPS) { + EXPECT_MUL_EQ(player->maxHP, UQ_4_12(0.50), player->hp); // heal to full HP then cut by half + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } else { + EXPECT_EQ(player->hp, 1); + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); // +1 from Curse, +1 from Z-Effect + } + } +} + +// Specific Z-Move Interactions +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Mirror Move raises the user's attack by two stages and copies the last used non-status move as a Z-Move (Multi)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_MIRROR_MOVE) == TYPE_FLYING); + ASSUME(GetMoveZEffect(MOVE_MIRROR_MOVE) == Z_EFFECT_ATK_UP_2); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_FLYINIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_MIRROR_MOVE, gimmick: GIMMICK_Z_MOVE); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + // extra turn to make sure that everything resets properly + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Mirror Move raises the user's attack by two stages and copies the last used status move regularly (Multi)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_MIRROR_MOVE) == TYPE_FLYING); + ASSUME(GetMoveZEffect(MOVE_MIRROR_MOVE) == Z_EFFECT_ATK_UP_2); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_FLYINIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCREECH); MOVE(player, MOVE_MIRROR_MOVE, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCREECH, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCREECH, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); // Z-Screech would cause an additional attack stat stage (reaching +3) + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Copycat raises the user's accuracy by one stage and copies the last used non-status move as a Z-Move (Multi)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_COPYCAT) == TYPE_NORMAL); + ASSUME(GetMoveZEffect(MOVE_COPYCAT) == Z_EFFECT_ACC_UP_1); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_COPYCAT, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ACC], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Me First raises the user's speed by two stages and copies the last used non-status move as a Z-Move with boosted damage (Multi)", s16 damage) +{ + u32 meFirst; + PARAMETRIZE { meFirst = TRUE; } + PARAMETRIZE { meFirst = FALSE; } + GIVEN { + ASSUME(GetMoveType(MOVE_ME_FIRST) == TYPE_NORMAL); + ASSUME(GetMoveZEffect(MOVE_ME_FIRST) == Z_EFFECT_SPD_UP_2); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (meFirst) + TURN { MOVE(player, MOVE_ME_FIRST, gimmick: GIMMICK_Z_MOVE); MOVE(opponent, MOVE_SCRATCH); } + else + TURN { MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + if (meFirst) + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (meFirst) + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 2); + } FINALLY { + EXPECT_MUL_EQ(results[1].damage, UQ_4_12(1.50), results[0].damage); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Nature Power transforms into different Z-Moves based on the current terrain (Multi)") +{ + u32 terrainMove = MOVE_NONE; + u32 zMove = MOVE_NONE; + PARAMETRIZE { terrainMove = MOVE_ELECTRIC_TERRAIN; zMove = gTypesInfo[TYPE_ELECTRIC].zMove; } + PARAMETRIZE { terrainMove = MOVE_PSYCHIC_TERRAIN; zMove = gTypesInfo[TYPE_PSYCHIC].zMove; } + PARAMETRIZE { terrainMove = MOVE_GRASSY_TERRAIN; zMove = gTypesInfo[TYPE_GRASS].zMove; } + PARAMETRIZE { terrainMove = MOVE_MISTY_TERRAIN; zMove = gTypesInfo[TYPE_FAIRY].zMove; } + GIVEN { + ASSUME(GetMoveType(MOVE_NATURE_POWER) == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, terrainMove); } + TURN { MOVE(player, MOVE_NATURE_POWER, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, terrainMove, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, zMove, player); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Hidden Power always transforms into Breakneck Blitz (Multi)") +{ + u8 iv; + PARAMETRIZE { iv = 0; } // test different Hidden Power types + PARAMETRIZE { iv = 10; } + PARAMETRIZE { iv = 21; } + PARAMETRIZE { iv = 31; } + GIVEN { + ASSUME(GetMoveType(MOVE_HIDDEN_POWER) == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); AttackIV(iv); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HIDDEN_POWER, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Weather Ball transforms into different Z-Moves based on current weather (Multi)") +{ + u32 weatherMove = MOVE_NONE; + u32 zMove = MOVE_NONE; + PARAMETRIZE { weatherMove = MOVE_RAIN_DANCE; zMove = gTypesInfo[TYPE_WATER].zMove; } + PARAMETRIZE { weatherMove = MOVE_SUNNY_DAY; zMove = gTypesInfo[TYPE_FIRE].zMove; } + PARAMETRIZE { weatherMove = MOVE_SANDSTORM; zMove = gTypesInfo[TYPE_ROCK].zMove; } + PARAMETRIZE { weatherMove = MOVE_HAIL; zMove = gTypesInfo[TYPE_ICE].zMove; } + GIVEN { + ASSUME(GetMoveType(MOVE_WEATHER_BALL) == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, weatherMove); } + TURN { MOVE(player, MOVE_WEATHER_BALL, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, weatherMove, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, zMove, player); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Sleep Talk transforms a used non-status move into a Z-Move (Multi)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SLEEP_TALK) == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); Status1(STATUS1_SLEEP); Moves(MOVE_SLEEP_TALK, MOVE_ABSORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SLEEP_TALK, gimmick: GIMMICK_Z_MOVE); } + TURN { MOVE(player, MOVE_SLEEP_TALK); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BLOOM_DOOM, player); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Sleep Talk turns Weather Ball into Breakneck Blitz even under rain (Multi)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SLEEP_TALK) == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); Status1(STATUS1_SLEEP); Moves(MOVE_SLEEP_TALK, MOVE_WEATHER_BALL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_RAIN_DANCE); MOVE(player, MOVE_SLEEP_TALK, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAIN_DANCE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Powder blocks Fire type Z-Moves and deals 25% of maximum HP to the user (Multi)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_EMBER) == TYPE_FIRE); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_FIRIUM_Z); } + OPPONENT(SPECIES_VIVILLON); + } WHEN { + TURN { MOVE(opponent, MOVE_POWDER); MOVE(player, MOVE_EMBER, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POWDER, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_INFERNO_OVERDRIVE, player); + } THEN { + EXPECT_MUL_EQ(player->maxHP, UQ_4_12(0.75), player->hp); + } +} + +DOUBLE_BATTLE_TEST("(Z-MOVE) Powder blocks Fire type Z-Moves (from Z-Mirror Move) (Multi)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_EMBER) == TYPE_FIRE); + ASSUME(GetMoveType(MOVE_MIRROR_MOVE) == TYPE_FLYING); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_FLYINIUM_Z); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_VIVILLON); + } WHEN { + TURN { MOVE(opponentRight, MOVE_POWDER, target: playerLeft); MOVE(opponentLeft, MOVE_EMBER, target: playerLeft); MOVE(playerLeft, MOVE_MIRROR_MOVE, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POWDER, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, playerLeft); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_INFERNO_OVERDRIVE, playerLeft); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Powder blocks Fire type Z-Moves but not boosts granted (Multi)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_WILL_O_WISP) == TYPE_FIRE); + ASSUME(GetMoveZEffect(MOVE_WILL_O_WISP) == Z_EFFECT_ATK_UP_1); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_FIRIUM_Z); } + OPPONENT(SPECIES_VIVILLON); + } WHEN { + TURN { MOVE(opponent, MOVE_POWDER); MOVE(player, MOVE_WILL_O_WISP, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POWDER, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_WILL_O_WISP, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} + +// Miscellaneous Interactions +DOUBLE_BATTLE_TEST("(Z-MOVE) Instruct fails if the target last used a Z-Move (Multi)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft, gimmick: GIMMICK_Z_MOVE); + MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, playerLeft); + MESSAGE("Wynaut used Instruct!"); + MESSAGE("But it failed!"); + } +} + +DOUBLE_BATTLE_TEST("(Z-MOVE) Dancer does not use a Z-Move if the battler has used a Z-Move the same turn (Multi)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_DANCER); Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft, gimmick: GIMMICK_Z_MOVE); + MOVE(playerRight, MOVE_FIERY_DANCE, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIERY_DANCE, playerRight); + ABILITY_POPUP(playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIERY_DANCE, playerLeft); + } +} + +// Signature Z-Moves +SINGLE_BATTLE_TEST("(Z-MOVE) Light That Burns the Sky uses the battler's highest attacking stat (Multi)", s16 damage) +{ + bool32 useSwordsDance; + PARAMETRIZE { useSwordsDance = FALSE; } + PARAMETRIZE { useSwordsDance = TRUE; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_SWORDS_DANCE) == EFFECT_ATTACK_UP_2); + PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Items(ITEM_PECHA_BERRY, ITEM_ULTRANECROZIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1000); MaxHP(1000); }; // hits hard lol + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_ULTRA_BURST); } + if (useSwordsDance) + TURN { MOVE(player, MOVE_SWORDS_DANCE); } + TURN { MOVE(player, MOVE_PHOTON_GEYSER, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ULTRA_BURST, player); // implicitly testing double gimmicks :^) + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_LIGHT_THAT_BURNS_THE_SKY, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) 10,000,000 Volt Thunderbolt has an increased critical hit ratio (Multi)") +{ + u32 genConfig, chance; + PARAMETRIZE { genConfig = GEN_1; chance = 1; } + for (u32 j = GEN_2; j <= GEN_5; j++) + PARAMETRIZE { genConfig = j; chance = 4; } + for (u32 j = GEN_6; j <= GEN_9; j++) + PARAMETRIZE { genConfig = j; chance = 2; } + PASSES_RANDOMLY(1, chance, RNG_CRITICAL_HIT); + GIVEN { + WITH_CONFIG(CONFIG_CRIT_CHANCE, genConfig); + ASSUME(GetMoveCriticalHitStage(MOVE_10_000_000_VOLT_THUNDERBOLT) == 2); + ASSUME(GetSpeciesBaseSpeed(SPECIES_PIKACHU_PARTNER) == 90); + PLAYER(SPECIES_PIKACHU_PARTNER) { Items(ITEM_PECHA_BERRY, ITEM_PIKASHUNIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_THUNDERBOLT, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_10_000_000_VOLT_THUNDERBOLT, player); + MESSAGE("A critical hit!"); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Stoked Sparksurfer paralyzes the target (Multi)") +{ + GIVEN { + ASSUME(GetMoveAdditionalEffectById(MOVE_STOKED_SPARKSURFER, 0)->moveEffect == MOVE_EFFECT_PARALYSIS); + PLAYER(SPECIES_RAICHU_ALOLA) { Items(ITEM_PECHA_BERRY, ITEM_ALORAICHIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_THUNDERBOLT, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STOKED_SPARKSURFER, player); + STATUS_ICON(opponent, STATUS1_PARALYSIS); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Extreme Evoboost boosts all the user's stats by two stages (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_EXTREME_EVOBOOST) == EFFECT_EXTREME_EVOBOOST); + PLAYER(SPECIES_EEVEE) { Items(ITEM_PECHA_BERRY, ITEM_EEVIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_LAST_RESORT, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXTREME_EVOBOOST, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 2); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 2); + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 2); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Genesis Supernova sets up psychic terrain (Multi)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_GENESIS_SUPERNOVA, MOVE_EFFECT_PSYCHIC_TERRAIN)); + PLAYER(SPECIES_MEW) { Items(ITEM_PECHA_BERRY, ITEM_MEWNIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PSYCHIC, gimmick: GIMMICK_Z_MOVE); } + TURN { MOVE(player, MOVE_QUICK_ATTACK); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GENESIS_SUPERNOVA, player); + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player); } + MESSAGE("The opposing Wobbuffet is protected by the Psychic Terrain!"); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Splintered Stormshards removes terrain (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SPLINTERED_STORMSHARDS) == EFFECT_ICE_SPINNER); + PLAYER(SPECIES_LYCANROC_DUSK) { Items(ITEM_PECHA_BERRY, ITEM_LYCANIUM_Z); } + OPPONENT(SPECIES_TAPU_LELE) { Ability(ABILITY_PSYCHIC_SURGE); HP(1000); MaxHP(1000); } + } WHEN { + TURN { MOVE(player, MOVE_STONE_EDGE, gimmick: GIMMICK_Z_MOVE); } + TURN { MOVE(player, MOVE_QUICK_ATTACK); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLINTERED_STORMSHARDS, player); + MESSAGE("The weirdness disappeared from the battlefield!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Clangorous Soulblaze boosts all the user's stats by one stage (Multi)") +{ + GIVEN { + ASSUME(GetMoveAdditionalEffectById(MOVE_CLANGOROUS_SOULBLAZE, 0)->moveEffect == MOVE_EFFECT_ALL_STATS_UP); + PLAYER(SPECIES_KOMMO_O) { Items(ITEM_PECHA_BERRY, ITEM_KOMMONIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CLANGING_SCALES, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CLANGOROUS_SOULBLAZE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Guardian of Alola deals 75% of the target's current HP (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_GUARDIAN_OF_ALOLA) == EFFECT_FIXED_PERCENT_DAMAGE); + PLAYER(SPECIES_TAPU_FINI) { Items(ITEM_PECHA_BERRY, ITEM_TAPUNIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_NATURES_MADNESS, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GUARDIAN_OF_ALOLA, player); + } THEN { + EXPECT_MUL_EQ(opponent->maxHP, UQ_4_12(0.25), opponent->hp); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Searing Sunraze Smash ignores the target's abilities (Multi)") +{ + GIVEN { + PLAYER(SPECIES_SOLGALEO) { Items(ITEM_PECHA_BERRY, ITEM_SOLGANIUM_Z); } + OPPONENT(SPECIES_LAPRAS) { Ability(ABILITY_BATTLE_ARMOR); } + } WHEN { + TURN { MOVE(player, MOVE_SUNSTEEL_STRIKE, gimmick: GIMMICK_Z_MOVE, criticalHit: TRUE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SEARING_SUNRAZE_SMASH, player); + HP_BAR(opponent); + MESSAGE("A critical hit!"); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Revelation Dance always transforms into Breakneck Blitz (Multi)") +{ + u16 species; + PARAMETRIZE { species = SPECIES_ORICORIO_BAILE; } + PARAMETRIZE { species = SPECIES_ORICORIO_PAU; } + PARAMETRIZE { species = SPECIES_ORICORIO_POM_POM; } + PARAMETRIZE { species = SPECIES_ORICORIO_SENSU; } + GIVEN { + ASSUME(GetMoveType(MOVE_REVELATION_DANCE) == TYPE_NORMAL); + PLAYER(species) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_REVELATION_DANCE, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + } +} + +TO_DO_BATTLE_TEST("(Z-MOVE) Stat changes from status Z-Moves are not inverted by Contrary (Multi)") +TO_DO_BATTLE_TEST("(Z-MOVE) Stat changes from Extreme Evoboost are inverted by Contrary (Multi)") +TO_DO_BATTLE_TEST("(Z-MOVE) Stat changes from Clangorous Soulblaze are inverted by Contrary (Multi)") +#endif diff --git a/test/battle/hazards.c b/test/battle/hazards.c index fb16a9e1f219..581ebeef0b47 100644 --- a/test/battle/hazards.c +++ b/test/battle/hazards.c @@ -169,3 +169,79 @@ DOUBLE_BATTLE_TEST("Hazards can trigger Emergency Exit and hazards still activat MESSAGE("Wynaut was hurt by the spikes!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Hazards can trigger Emergency Exit and other hazards don't activate (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_GOLISOPOD) { HP(105); MaxHP(200); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_STEALTH_ROCK); } + TURN { MOVE(opponent, MOVE_TOXIC_SPIKES); } + TURN { MOVE(opponent, MOVE_STICKY_WEB); } + TURN { MOVE(opponent, MOVE_SPIKES); } + TURN { MOVE(opponent, MOVE_STEALTH_ROCK); SWITCH(player, 1); SEND_OUT(player, 0); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEALTH_ROCK, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKES, opponent); + MESSAGE("Pointed stones dug into Golisopod!"); + ABILITY_POPUP(player, ABILITY_EMERGENCY_EXIT); + NONE_OF { + MESSAGE("Golisopod was poisoned!"); + MESSAGE("Golisopod was caught in a sticky web!"); + MESSAGE("Golisopod was hurt by the spikes!"); + } + MESSAGE("Pointed stones dug into Wobbuffet!"); + MESSAGE("Wobbuffet was poisoned!"); + MESSAGE("Wobbuffet was caught in a sticky web!"); + MESSAGE("Wobbuffet was hurt by the spikes!"); + NOT MESSAGE("Pointed stones dug into Wobbuffet!"); // Because the previous switch in effects instruction is still kept + } +} + +DOUBLE_BATTLE_TEST("Hazards can trigger Emergency Exit and hazards still activate for other battlers (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FINAL_GAMBIT) == EFFECT_FINAL_GAMBIT); + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_GOLISOPOD) { HP(105); MaxHP(200); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); } + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_STEALTH_ROCK); MOVE(opponentRight, MOVE_TOXIC_SPIKES); } + TURN { MOVE(opponentLeft, MOVE_STICKY_WEB); MOVE(opponentRight, MOVE_SPIKES); } + TURN { MOVE(playerLeft, MOVE_FINAL_GAMBIT, target: opponentRight); + MOVE(playerRight, MOVE_FINAL_GAMBIT, target: opponentRight); + SEND_OUT(playerLeft, 2); + SEND_OUT(playerRight, 3); + SEND_OUT(playerLeft, 4); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEALTH_ROCK, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKES, opponentRight); + MESSAGE("Pointed stones dug into Golisopod!"); + ABILITY_POPUP(playerLeft, ABILITY_EMERGENCY_EXIT); + NONE_OF { + MESSAGE("Golisopod was poisoned!"); + MESSAGE("Golisopod was caught in a sticky web!"); + MESSAGE("Golisopod was hurt by the spikes!"); + } + MESSAGE("Pointed stones dug into Wobbuffet!"); + MESSAGE("Wobbuffet was poisoned!"); + MESSAGE("Wobbuffet was caught in a sticky web!"); + MESSAGE("Wobbuffet was hurt by the spikes!"); + MESSAGE("Pointed stones dug into Wynaut!"); + MESSAGE("Wynaut was poisoned!"); + MESSAGE("Wynaut was caught in a sticky web!"); + MESSAGE("Wynaut was hurt by the spikes!"); + } +} +#endif diff --git a/test/battle/hold_effect/ability_shield.c b/test/battle/hold_effect/ability_shield.c index 6e5a9884b8eb..ae952ebf1ac1 100644 --- a/test/battle/hold_effect/ability_shield.c +++ b/test/battle/hold_effect/ability_shield.c @@ -203,3 +203,264 @@ TO_DO_BATTLE_TEST("Ability Shield prevents the user's Trace from changing its ab TO_DO_BATTLE_TEST("Ability Shield prevents the user's Receiver from changing its ability"); TO_DO_BATTLE_TEST("Ability Shield protects against Wandering Spirit"); TO_DO_BATTLE_TEST("Ability Shield protects against Mummy/Lingering Aroma"); + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Ability Shield protects against Mold Breaker (no message) (Traits)") +{ + u32 item; + + PARAMETRIZE { item = ITEM_ABILITY_SHIELD; } + PARAMETRIZE { item = ITEM_NONE; } + + GIVEN { + ASSUME(GetMoveType(MOVE_EARTHQUAKE) == TYPE_GROUND); + PLAYER(SPECIES_FLYGON) { Ability(ABILITY_LEVITATE); Item(item); } + OPPONENT(SPECIES_EXCADRILL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_MOLD_BREAKER); } + } WHEN { + TURN { MOVE(opponent, MOVE_EARTHQUAKE); } + } SCENE { + if (item == ITEM_ABILITY_SHIELD) { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Flygon's Ability is protected by the effects of its Ability Shield!"); + HP_BAR(player); + } + ABILITY_POPUP(player, ABILITY_LEVITATE); + } else { + HP_BAR(player); + NOT ABILITY_POPUP(player, ABILITY_LEVITATE); + } + } +} + +SINGLE_BATTLE_TEST("Ability Shield protects against Mycelium Might (no message) (Traits)") +{ + u32 item; + + PARAMETRIZE { item = ITEM_ABILITY_SHIELD; } + PARAMETRIZE { item = ITEM_NONE; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + PLAYER(SPECIES_VIGOROTH) { Ability(ABILITY_VITAL_SPIRIT); Item(item); } + OPPONENT(SPECIES_TOEDSCOOL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_MYCELIUM_MIGHT); } + } WHEN { + TURN { MOVE(opponent, MOVE_SPORE); } + } SCENE { + + if (item == ITEM_ABILITY_SHIELD) { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponent); + STATUS_ICON(player, sleep: TRUE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Vigoroth's Ability is protected by the effects of its Ability Shield!"); + } + ABILITY_POPUP(player, ABILITY_VITAL_SPIRIT); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponent); + STATUS_ICON(player, sleep: TRUE); + } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Ability Shield protects against Neutralizing Gas (Multi)") +{ + u32 item; + + PARAMETRIZE { item = ITEM_ABILITY_SHIELD; } + PARAMETRIZE { item = ITEM_NONE; } + + GIVEN { + PLAYER(SPECIES_TORKOAL) { Ability(ABILITY_DROUGHT); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_KOFFING) { Ability(ABILITY_NEUTRALIZING_GAS); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_NEUTRALIZING_GAS); + MESSAGE("Neutralizing gas filled the area!"); + if (item == ITEM_ABILITY_SHIELD) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Torkoal's Ability is protected by the effects of its Ability Shield!"); + ABILITY_POPUP(player, ABILITY_DROUGHT); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Torkoal's Ability is protected by the effects of its Ability Shield!"); + ABILITY_POPUP(player, ABILITY_DROUGHT); + } + } + } +} + +SINGLE_BATTLE_TEST("Ability Shield protects against Mold Breaker (no message) (Multi)") +{ + u32 item; + + PARAMETRIZE { item = ITEM_ABILITY_SHIELD; } + PARAMETRIZE { item = ITEM_NONE; } + + GIVEN { + ASSUME(GetMoveType(MOVE_EARTHQUAKE) == TYPE_GROUND); + PLAYER(SPECIES_FLYGON) { Ability(ABILITY_LEVITATE); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_EXCADRILL) { Ability(ABILITY_MOLD_BREAKER); } + } WHEN { + TURN { MOVE(opponent, MOVE_EARTHQUAKE); } + } SCENE { + if (item == ITEM_ABILITY_SHIELD) { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Flygon's Ability is protected by the effects of its Ability Shield!"); + HP_BAR(player); + } + ABILITY_POPUP(player, ABILITY_LEVITATE); + } else { + HP_BAR(player); + NOT ABILITY_POPUP(player, ABILITY_LEVITATE); + } + } +} + +SINGLE_BATTLE_TEST("Ability Shield protects against Mycelium Might (no message) (Multi)") +{ + u32 item; + + PARAMETRIZE { item = ITEM_ABILITY_SHIELD; } + PARAMETRIZE { item = ITEM_NONE; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + PLAYER(SPECIES_VIGOROTH) { Ability(ABILITY_VITAL_SPIRIT); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_TOEDSCOOL) { Ability(ABILITY_MYCELIUM_MIGHT); } + } WHEN { + TURN { MOVE(opponent, MOVE_SPORE); } + } SCENE { + + if (item == ITEM_ABILITY_SHIELD) { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponent); + STATUS_ICON(player, sleep: TRUE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Vigoroth's Ability is protected by the effects of its Ability Shield!"); + } + ABILITY_POPUP(player, ABILITY_VITAL_SPIRIT); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponent); + STATUS_ICON(player, sleep: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Ability Shield protects against Sunsteel Strike (no message) (Multi)") +{ + u32 item; + + PARAMETRIZE { item = ITEM_ABILITY_SHIELD; } + PARAMETRIZE { item = ITEM_NONE; } + + GIVEN { + ASSUME(MoveIgnoresTargetAbility(MOVE_SUNSTEEL_STRIKE)); + PLAYER(SPECIES_SHEDINJA) { Ability(ABILITY_WONDER_GUARD); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUNSTEEL_STRIKE); } + } SCENE { + if (item == ITEM_ABILITY_SHIELD) { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Shedinja's Ability is protected by the effects of its Ability Shield!"); + MESSAGE("Shedinja fainted!"); + } + ABILITY_POPUP(player, ABILITY_WONDER_GUARD); + } else { + MESSAGE("Shedinja fainted!"); + NOT ABILITY_POPUP(player, ABILITY_WONDER_GUARD); + } + } +} + +SINGLE_BATTLE_TEST("Ability Shield protects the user's ability from being suppressed by Gastro Acid (Multi)") +{ + u32 item; + + PARAMETRIZE { item = ITEM_ABILITY_SHIELD; } + PARAMETRIZE { item = ITEM_NONE; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_GASTRO_ACID) == EFFECT_GASTRO_ACID); + PLAYER(SPECIES_BLAZIKEN) { Ability(ABILITY_SPEED_BOOST); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_GASTRO_ACID); } + } SCENE { + if (item == ITEM_ABILITY_SHIELD) { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_GASTRO_ACID, opponent); + ABILITY_POPUP(player, ABILITY_SPEED_BOOST); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GASTRO_ACID, opponent); + NOT ABILITY_POPUP(player, ABILITY_SPEED_BOOST); + } + } +} + +SINGLE_BATTLE_TEST("Ability Shield protects against Skill Swap (Multi)") +{ + u32 item; + + PARAMETRIZE { item = ITEM_ABILITY_SHIELD; } + PARAMETRIZE { item = ITEM_NONE; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SKILL_SWAP) == EFFECT_SKILL_SWAP); + PLAYER(SPECIES_GYARADOS) { Ability(ABILITY_INTIMIDATE); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SKILL_SWAP); } + } SCENE { + if (item == ITEM_ABILITY_SHIELD) { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Gyarados's Ability is protected by the effects of its Ability Shield!"); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + } + } +} + +SINGLE_BATTLE_TEST("Ability Shield protects against Skill Swap even if user has Klutz (Multi)") +{ + u32 item; + + PARAMETRIZE { item = ITEM_ABILITY_SHIELD; } + PARAMETRIZE { item = ITEM_NONE; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SKILL_SWAP) == EFFECT_SKILL_SWAP); + PLAYER(SPECIES_LOPUNNY) { Ability(ABILITY_KLUTZ); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SKILL_SWAP); } + } SCENE { + if (item == ITEM_ABILITY_SHIELD) { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Lopunny's Ability is protected by the effects of its Ability Shield!"); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); + } + } +} + +// These currently do not activate, but probably should do held item animation + message +TO_DO_BATTLE_TEST("Ability Shield prevents the user's Trace from changing its ability (Multi)"); +TO_DO_BATTLE_TEST("Ability Shield prevents the user's Receiver from changing its ability (Multi)"); +TO_DO_BATTLE_TEST("Ability Shield protects against Wandering Spirit (Multi)"); +TO_DO_BATTLE_TEST("Ability Shield protects against Mummy/Lingering Aroma (Multi)"); +#endif diff --git a/test/battle/hold_effect/air_balloon.c b/test/battle/hold_effect/air_balloon.c index 3493012e9cbc..c12f878e5119 100644 --- a/test/battle/hold_effect/air_balloon.c +++ b/test/battle/hold_effect/air_balloon.c @@ -116,3 +116,129 @@ SINGLE_BATTLE_TEST("Air Balloon pops before it can be stolen by Thief") NOT MESSAGE("The opposing Wobbuffet stole Wobbuffet's Air Balloon!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Air Balloon pops before it can be stolen with Magician (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_AIR_BALLOON); }; + OPPONENT(SPECIES_DELPHOX) { Ability(ABILITY_BLAZE); Innates(ABILITY_MAGICIAN); }; + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet floats in the air with its Air Balloon!"); + MESSAGE("Wobbuffet's Air Balloon popped!"); + NOT ABILITY_POPUP(opponent, ABILITY_MAGICIAN); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Air Balloon prevents the holder from taking damage from ground type moves (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_AIR_BALLOON); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_EARTHQUAKE); } + } SCENE { + MESSAGE("Wobbuffet floats in the air with its Air Balloon!"); + MESSAGE("The opposing Wobbuffet used Earthquake!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, opponent); + MESSAGE("It doesn't affect Wobbuffet…"); + } +} + +SINGLE_BATTLE_TEST("Air Balloon pops when the holder is hit by a move that is not ground type (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_AIR_BALLOON); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet floats in the air with its Air Balloon!"); + MESSAGE("The opposing Wobbuffet used Scratch!"); + MESSAGE("Wobbuffet's Air Balloon popped!"); + } +} + +SINGLE_BATTLE_TEST("Air Balloon no longer prevents the holder from taking damage from ground type moves once it has been popped (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_AIR_BALLOON); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(opponent, MOVE_EARTHQUAKE); } + } SCENE { + MESSAGE("Wobbuffet floats in the air with its Air Balloon!"); + MESSAGE("The opposing Wobbuffet used Scratch!"); + MESSAGE("Wobbuffet's Air Balloon popped!"); + MESSAGE("The opposing Wobbuffet used Earthquake!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, opponent); + NOT MESSAGE("It doesn't affect Wobbuffet…"); + } +} + +SINGLE_BATTLE_TEST("Air Balloon can not be restored with Recycle after it has been popped (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_AIR_BALLOON); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(opponent, MOVE_SCRATCH); + MOVE(player, MOVE_RECYCLE); + } + } SCENE { + MESSAGE("Wobbuffet floats in the air with its Air Balloon!"); + MESSAGE("The opposing Wobbuffet used Scratch!"); + MESSAGE("Wobbuffet's Air Balloon popped!"); + MESSAGE("Wobbuffet used Recycle!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Air Balloon prevents the user from being healed by Grassy Terrain (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_AIR_BALLOON); MaxHP(100); HP(1); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_GRASSY_TERRAIN); } + } SCENE { + MESSAGE("Wobbuffet floats in the air with its Air Balloon!"); + NOT MESSAGE("Wobbuffet is healed by the Grassy Terrain!"); + } +} + +SINGLE_BATTLE_TEST("Air Balloon pops before it can be stolen with Magician (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_AIR_BALLOON); }; + OPPONENT(SPECIES_DELPHOX) { Ability(ABILITY_MAGICIAN); }; + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet floats in the air with its Air Balloon!"); + MESSAGE("Wobbuffet's Air Balloon popped!"); + NOT ABILITY_POPUP(opponent, ABILITY_MAGICIAN); + } +} + +SINGLE_BATTLE_TEST("Air Balloon pops before it can be stolen by Thief (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_THIEF) == EFFECT_STEAL_ITEM); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_AIR_BALLOON); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_THIEF); } + } SCENE { + MESSAGE("Wobbuffet floats in the air with its Air Balloon!"); + MESSAGE("Wobbuffet's Air Balloon popped!"); + NOT MESSAGE("The opposing Wobbuffet stole Wobbuffet's Air Balloon!"); + } +} +#endif diff --git a/test/battle/hold_effect/attack_up.c b/test/battle/hold_effect/attack_up.c index e9efef9ad541..21b7f07a20da 100644 --- a/test/battle/hold_effect/attack_up.c +++ b/test/battle/hold_effect/attack_up.c @@ -68,3 +68,100 @@ SINGLE_BATTLE_TEST("Liechi Berry raises Attack by one stage when HP drops to 1/4 EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Liechi Berry raises Attack by one stage when HP drops to 1/2 or below if holder has Gluttony (Traits)") +{ + GIVEN { + PLAYER(SPECIES_BELLSPROUT) { MaxHP(80); HP(80); Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_GLUTTONY); Item(ITEM_LIECHI_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Liechi Berry, the Attack of Bellsprout rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Liechi Berry raises Attack by one stage when HP drops to 1/4 or below if holder has Ripen (Traits)") +{ + GIVEN { + PLAYER(SPECIES_APPLIN) { MaxHP(160); HP(80); Ability(ABILITY_BULLETPROOF); Innates(ABILITY_RIPEN); Item(ITEM_LIECHI_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Liechi Berry, the Attack of Applin sharply rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Liechi Berry raises the holder's Attack by one stage when HP drops to 1/4 or below (Multi)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_DRAGON_RAGE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { MaxHP(160); HP(80); Items(ITEM_PECHA_BERRY, ITEM_LIECHI_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + if (move == MOVE_SCRATCH) { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Liechi Berry, the Attack of Wobbuffet rose!"); + } + } else { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Liechi Berry, the Attack of Wobbuffet rose!"); + } + } THEN { + if (move == MOVE_DRAGON_RAGE) + EXPECT_EQ(player->statStages[STAT_ATK], 7); + } +} + +SINGLE_BATTLE_TEST("Liechi Berry raises Attack by one stage when HP drops to 1/2 or below if holder has Gluttony (Multi)") +{ + GIVEN { + PLAYER(SPECIES_BELLSPROUT) { MaxHP(80); HP(80); Ability(ABILITY_GLUTTONY); Items(ITEM_PECHA_BERRY, ITEM_LIECHI_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Liechi Berry, the Attack of Bellsprout rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Liechi Berry raises Attack by one stage when HP drops to 1/4 or below if holder has Ripen (Multi)") +{ + GIVEN { + PLAYER(SPECIES_APPLIN) { MaxHP(160); HP(80); Ability(ABILITY_RIPEN); Items(ITEM_PECHA_BERRY, ITEM_LIECHI_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Liechi Berry, the Attack of Applin sharply rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} +#endif diff --git a/test/battle/hold_effect/berserk_gene.c b/test/battle/hold_effect/berserk_gene.c index 002d164d1eaa..56f62eac91e4 100644 --- a/test/battle/hold_effect/berserk_gene.c +++ b/test/battle/hold_effect/berserk_gene.c @@ -303,3 +303,406 @@ SINGLE_BATTLE_TEST("Berserker Gene confusion can be healed with used held items" EXPECT(player->volatiles.infiniteConfusion == 0); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Berserk Gene does not confuse a Pokemon with Own Tempo but still raises attack sharply in a single battle (Traits)", s16 damage) +{ + u16 item; + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_BERSERK_GENE; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_SLOWBRO) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_OWN_TEMPO); Item(item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_SCRATCH); + } + } SCENE { + if (item == ITEM_BERSERK_GENE) + { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Berserk Gene, the Attack of Slowbro sharply rose!"); + ABILITY_POPUP(player, ABILITY_OWN_TEMPO); + MESSAGE("Slowbro's Own Tempo prevents confusion!"); + } + HP_BAR(opponent, captureDamage: &results[i].damage); + NOT MESSAGE("Slowbro became confused!"); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[1].damage); + } +} + +DOUBLE_BATTLE_TEST("Berserk Gene does not confuse a Pokemon with Own Tempo but still raises attack sharply in a double battle", s16 damage) +{ + u16 item; + bool8 positionLeft = FALSE; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_BERSERK_GENE; positionLeft = TRUE; } + PARAMETRIZE { item = ITEM_BERSERK_GENE; positionLeft = FALSE; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + if (positionLeft) { + PLAYER(SPECIES_SLOWBRO) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_OWN_TEMPO); Item(item); } + PLAYER(SPECIES_WOBBUFFET); + } else { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_SLOWBRO) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_OWN_TEMPO); Item(item); } + } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE((positionLeft != 0) ? playerLeft : playerRight, MOVE_SCRATCH, target: opponentLeft); + } + } SCENE { + if (item == ITEM_BERSERK_GENE) + { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, (positionLeft != 0) ? playerLeft : playerRight); + MESSAGE("Using Berserk Gene, the Attack of Slowbro sharply rose!"); + ABILITY_POPUP((positionLeft != 0) ? playerLeft : playerRight, ABILITY_OWN_TEMPO); + MESSAGE("Slowbro's Own Tempo prevents confusion!"); + } + HP_BAR(opponentLeft, captureDamage: &results[i].damage); + NOT MESSAGE("Slowbro became confused!"); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[1].damage); + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[2].damage); + EXPECT_EQ(((positionLeft != 0) ? playerLeft : playerRight)->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Berserk Gene does not confuse on Misty Terrain but still raises attack sharply (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_TAPU_FINI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_MISTY_SURGE); Item(ITEM_BERSERK_GENE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_SCRATCH); + } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Berserk Gene, the Attack of Tapu Fini sharply rose!"); + NOT MESSAGE("Tapu Fini became confused!"); + } +} + +SINGLE_BATTLE_TEST("Berserk Gene does not cause an infinite loop (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_BESTOW) == EFFECT_BESTOW); + PLAYER(SPECIES_TOXEL) { Item(ITEM_BERSERK_GENE); Ability(ABILITY_RATTLED); Innates(ABILITY_KLUTZ); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BESTOW); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Using Berserk Gene, the Attack of the opposing Wobbuffet sharply rose!"); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Berserk Gene sharply raises attack at the start of a single battle (Multi)", s16 damage) +{ + u16 item; + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_BERSERK_GENE; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH, WITH_RNG(RNG_CONFUSION, FALSE)); } + } SCENE { + if (item == ITEM_BERSERK_GENE) + { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Berserk Gene, the Attack of Wobbuffet sharply rose!"); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, player); + MESSAGE("Wobbuffet became confused!"); + } + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[1].damage); + } +} + +DOUBLE_BATTLE_TEST("Berserk Gene sharply raises attack at the start of a double battle (Multi)", s16 damage) +{ + u16 item; + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_BERSERK_GENE; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, MOVE_SCRATCH, target:opponentLeft, WITH_RNG(RNG_CONFUSION, FALSE)); } + } SCENE { + if (item == ITEM_BERSERK_GENE) + { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Using Berserk Gene, the Attack of Wobbuffet sharply rose!"); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, playerRight); + MESSAGE("Wobbuffet became confused!"); + } + HP_BAR(opponentLeft, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Berserk Gene activates on switch in (Multi)", s16 damage) +{ + u16 item; + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_BERSERK_GENE; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(player, 1); } + TURN { MOVE(player, MOVE_SCRATCH, WITH_RNG(RNG_CONFUSION, FALSE)); } + } SCENE { + if (item == ITEM_BERSERK_GENE) + { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Berserk Gene, the Attack of Wobbuffet sharply rose!"); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, player); + MESSAGE("Wobbuffet became confused!"); + } + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Berserk Gene does not confuse a Pokemon with Own Tempo but still raises attack sharply in a single battle (Multi)", s16 damage) +{ + u16 item; + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_BERSERK_GENE; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_SLOWBRO) { Ability(ABILITY_OWN_TEMPO); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_SCRATCH); + } + } SCENE { + if (item == ITEM_BERSERK_GENE) + { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Berserk Gene, the Attack of Slowbro sharply rose!"); + ABILITY_POPUP(player, ABILITY_OWN_TEMPO); + MESSAGE("Slowbro's Own Tempo prevents confusion!"); + } + HP_BAR(opponent, captureDamage: &results[i].damage); + NOT MESSAGE("Slowbro became confused!"); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[1].damage); + } +} + +DOUBLE_BATTLE_TEST("Berserk Gene does not confuse a Pokemon with Own Tempo but still raises attack sharply in a double battle (Multi)", s16 damage) +{ + u16 item; + bool8 positionLeft = FALSE; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_BERSERK_GENE; positionLeft = TRUE; } + PARAMETRIZE { item = ITEM_BERSERK_GENE; positionLeft = FALSE; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + if (positionLeft) { + PLAYER(SPECIES_SLOWBRO) { Ability(ABILITY_OWN_TEMPO); Items(ITEM_PECHA_BERRY, item); } + PLAYER(SPECIES_WOBBUFFET); + } else { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_SLOWBRO) { Ability(ABILITY_OWN_TEMPO); Items(ITEM_PECHA_BERRY, item); } + } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE((positionLeft != 0) ? playerLeft : playerRight, MOVE_SCRATCH, target: opponentLeft); + } + } SCENE { + if (item == ITEM_BERSERK_GENE) + { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, (positionLeft != 0) ? playerLeft : playerRight); + MESSAGE("Using Berserk Gene, the Attack of Slowbro sharply rose!"); + ABILITY_POPUP((positionLeft != 0) ? playerLeft : playerRight, ABILITY_OWN_TEMPO); + MESSAGE("Slowbro's Own Tempo prevents confusion!"); + } + HP_BAR(opponentLeft, captureDamage: &results[i].damage); + NOT MESSAGE("Slowbro became confused!"); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[1].damage); + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[2].damage); + EXPECT_EQ(((positionLeft != 0) ? playerLeft : playerRight)->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Berserk Gene does not confuse on Misty Terrain but still raises attack sharply (Multi)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_TAPU_FINI) { Ability(ABILITY_MISTY_SURGE); Items(ITEM_PECHA_BERRY, ITEM_BERSERK_GENE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_SCRATCH); + } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Berserk Gene, the Attack of Tapu Fini sharply rose!"); + NOT MESSAGE("Tapu Fini became confused!"); + } +} + +SINGLE_BATTLE_TEST("Berserk Gene does not confuse when Safeguard is active (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_BERSERK_GENE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SAFEGUARD); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Berserk Gene, the Attack of Wobbuffet sharply rose!"); + MESSAGE("Wobbuffet is protected by Safeguard!"); + NOT MESSAGE("Wobbuffet became confused!"); + } +} + +SINGLE_BATTLE_TEST("Berserk Gene causes confusion for more than 5 turns (Multi)") // how else would be check for infinite? +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_BERSERK_GENE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN {} + TURN {} + TURN {} + TURN {} + TURN {} + TURN {} + } SCENE { + NOT MESSAGE("Wobbuffet snapped out of confusion!"); + } +} + +SINGLE_BATTLE_TEST("Berserk Gene causes infinite confusion (Multi)") // check if bit is set +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_BERSERK_GENE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN {} + } SCENE { + } THEN { + EXPECT(gBattleMons[GetBattlerAtPosition(B_POSITION_PLAYER_LEFT)].volatiles.infiniteConfusion); + } +} + +SINGLE_BATTLE_TEST("Berserk Gene causes confusion timer to not tick down (Multi)", u32 confusionTurns) +{ + u32 turns; + PARAMETRIZE { turns = 1; } + PARAMETRIZE { turns = 2; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_BERSERK_GENE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + u32 count; + for (count = 0; count < turns; count++) { + TURN {} + } + } THEN { + results[i].confusionTurns = player->volatiles.confusionTurns; + } FINALLY { + EXPECT_EQ(results[0].confusionTurns, results[1].confusionTurns); + } +} + +SINGLE_BATTLE_TEST("Berserk Gene does not cause an infinite loop (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_BESTOW) == EFFECT_BESTOW); + PLAYER(SPECIES_TOXEL) { Items(ITEM_PECHA_BERRY, ITEM_BERSERK_GENE); Ability(ABILITY_KLUTZ); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BESTOW); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Using Berserk Gene, the Attack of the opposing Wobbuffet sharply rose!"); + } +} + +SINGLE_BATTLE_TEST("Berserker Gene confusion can be healed with bag items (Multi)") +{ + u16 item; + PARAMETRIZE { item = ITEM_FULL_HEAL; } + PARAMETRIZE { item = ITEM_HEAL_POWDER; } + PARAMETRIZE { item = ITEM_PEWTER_CRUNCHIES; } + PARAMETRIZE { item = ITEM_LAVA_COOKIE; } + PARAMETRIZE { item = ITEM_RAGE_CANDY_BAR; } + PARAMETRIZE { item = ITEM_OLD_GATEAU; } + PARAMETRIZE { item = ITEM_CASTELIACONE; } + PARAMETRIZE { item = ITEM_LUMIOSE_GALETTE; } + PARAMETRIZE { item = ITEM_SHALOUR_SABLE; } + PARAMETRIZE { item = ITEM_BIG_MALASADA; } + PARAMETRIZE { item = ITEM_JUBILIFE_MUFFIN; } + GIVEN { + ASSUME(gItemsInfo[item].battleUsage == EFFECT_ITEM_CURE_STATUS); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_BERSERK_GENE);}; + OPPONENT(SPECIES_GENGAR); + } WHEN { + TURN { USE_ITEM(player, item, partyIndex: 0); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, player); + MESSAGE("Wobbuffet had its status healed!"); + } THEN { + EXPECT(player->volatiles.infiniteConfusion == 0); + } +} + +SINGLE_BATTLE_TEST("Berserker Gene confusion can be healed with used held items (Multi)") +{ + u16 item; + PARAMETRIZE { item = ITEM_PERSIM_BERRY; } + PARAMETRIZE { item = ITEM_LUM_BERRY; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_PERSIM_BERRY].holdEffect == HOLD_EFFECT_CURE_CONFUSION); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_BERSERK_GENE);}; + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, item);}; + } WHEN { + TURN { MOVE(player, MOVE_COVET, WITH_RNG(RNG_CONFUSION, FALSE)); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } THEN { + EXPECT(player->volatiles.infiniteConfusion == 0); + } +} +#endif diff --git a/test/battle/hold_effect/big_root.c b/test/battle/hold_effect/big_root.c index 3d2256ea34d4..0a34b7cdb511 100644 --- a/test/battle/hold_effect/big_root.c +++ b/test/battle/hold_effect/big_root.c @@ -76,3 +76,98 @@ SINGLE_BATTLE_TEST("Big Root increases damage from absorbing Liquid Ooze", s16 d EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.3), results[1].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Big Root increases damage from absorbing Liquid Ooze (Traits)", s16 damage) +{ + u32 item; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_BIG_ROOT; } + + GIVEN { + PLAYER(SPECIES_XURKITREE) { HP(200); Item(item); } + OPPONENT(SPECIES_TENTACOOL) { Ability(ABILITY_RAIN_DISH); Innates(ABILITY_LIQUID_OOZE); } + } WHEN { + TURN { MOVE(player, MOVE_ABSORB); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ABSORB, player); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.3), results[1].damage); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Big Root increases healing from absorbing moves (Multi)", s16 damage, s16 heal) +{ + u32 item; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_BIG_ROOT; } + + GIVEN { + PLAYER(SPECIES_XURKITREE) { HP(200); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ABSORB); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ABSORB, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + HP_BAR(player, captureDamage: &results[i].heal); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); // Damage is unaffected + EXPECT_MUL_EQ(results[0].heal, Q_4_12(1.3), results[1].heal); + } +} + +SINGLE_BATTLE_TEST("Big Root increases the damage restored from Leech Seed, Ingrain and Aqua Ring (Multi)", s16 heal, s16 damage) +{ + u32 item; + u32 move; + + PARAMETRIZE { item = ITEM_NONE; move = MOVE_LEECH_SEED; } + PARAMETRIZE { item = ITEM_BIG_ROOT; move = MOVE_LEECH_SEED; } + PARAMETRIZE { item = ITEM_NONE; move = MOVE_INGRAIN; } + PARAMETRIZE { item = ITEM_BIG_ROOT; move = MOVE_INGRAIN; } + PARAMETRIZE { item = ITEM_NONE; move = MOVE_AQUA_RING; } + PARAMETRIZE { item = ITEM_BIG_ROOT; move = MOVE_AQUA_RING; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(200); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + if (move == MOVE_LEECH_SEED) + HP_BAR(opponent, captureDamage: &results[i].damage); + HP_BAR(player, captureDamage: &results[i].heal); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); // Damage is unaffected + EXPECT_MUL_EQ(results[0].heal, Q_4_12(1.3), results[1].heal); + EXPECT_MUL_EQ(results[2].heal, Q_4_12(1.3), results[3].heal); + EXPECT_MUL_EQ(results[4].heal, Q_4_12(1.3), results[5].heal); + } +} + +SINGLE_BATTLE_TEST("Big Root increases damage from absorbing Liquid Ooze (Multi)", s16 damage) +{ + u32 item; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_BIG_ROOT; } + + GIVEN { + PLAYER(SPECIES_XURKITREE) { HP(200); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_TENTACOOL) { Ability(ABILITY_LIQUID_OOZE); } + } WHEN { + TURN { MOVE(player, MOVE_ABSORB); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ABSORB, player); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.3), results[1].damage); + } +} +#endif diff --git a/test/battle/hold_effect/blunder_policy.c b/test/battle/hold_effect/blunder_policy.c index e9215b3eb2f6..a5605a998e45 100644 --- a/test/battle/hold_effect/blunder_policy.c +++ b/test/battle/hold_effect/blunder_policy.c @@ -65,3 +65,65 @@ SINGLE_BATTLE_TEST("Blunder Policy will never trigger if the move fails due to P EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Blunder Policy raises the users speed by 2 stages if the user misses (Multi)") +{ + PASSES_RANDOMLY(3, 10, RNG_ACCURACY); + GIVEN { + ASSUME(GetMoveAccuracy(MOVE_FOCUS_BLAST) == 70); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_BLUNDER_POLICY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FOCUS_BLAST); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_FOCUS_BLAST, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } THEN { + EXPECT(player->item == ITEM_NONE); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 2); + } +} + + +SINGLE_BATTLE_TEST("Blunder Policy will never trigger if the move fails due to an immunity (Multi)") +{ + PASSES_RANDOMLY(10, 10, RNG_ACCURACY); + GIVEN { + ASSUME(GetMoveAccuracy(MOVE_FOCUS_BLAST) == 70); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_BLUNDER_POLICY); } + OPPONENT(SPECIES_GASTLY); + } WHEN { + TURN { MOVE(player, MOVE_FOCUS_BLAST); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FOCUS_BLAST, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } + } THEN { + EXPECT(player->item == ITEM_BLUNDER_POLICY); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Blunder Policy will never trigger if the move fails due to Protect (Multi)") +{ + PASSES_RANDOMLY(10, 10, RNG_ACCURACY); + GIVEN { + ASSUME(GetMoveAccuracy(MOVE_FOCUS_BLAST) == 70); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_BLUNDER_POLICY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_PROTECT); MOVE(player, MOVE_FOCUS_BLAST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FOCUS_BLAST, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } + } THEN { + EXPECT(player->item == ITEM_BLUNDER_POLICY); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} +#endif diff --git a/test/battle/hold_effect/booster_energy.c b/test/battle/hold_effect/booster_energy.c index 9916fe5792af..444f39337565 100644 --- a/test/battle/hold_effect/booster_energy.c +++ b/test/battle/hold_effect/booster_energy.c @@ -332,3 +332,659 @@ DOUBLE_BATTLE_TEST("Booster Energy will not activate on terrain if user has Prot ABILITY_POPUP(playerLeft, ABILITY_PROTOSYNTHESIS); // Activation after all terrains } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Booster Energy will activate Quark Drive after Electric Terrain ends (Traits)") +{ + GIVEN { + PLAYER(SPECIES_IRON_MOTH) { Attack(100); Defense(100); Speed(100); SpAttack(110); SpDefense(100); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_QUARK_DRIVE); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_TAPU_KOKO) { Speed(100); Ability(ABILITY_ELECTRIC_SURGE); }; + } WHEN { + TURN {} + TURN {} + TURN {} + TURN {} + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Iron Moth used its Booster Energy to activate Quark Drive!"); + MESSAGE("Iron Moth's Sp. Atk was heightened!"); + } + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + MESSAGE("The Electric Terrain activated Iron Moth's Quark Drive!"); + MESSAGE("Iron Moth's Sp. Atk was heightened!"); + MESSAGE("The electricity disappeared from the battlefield."); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + MESSAGE("Iron Moth used its Booster Energy to activate Quark Drive!"); + MESSAGE("Iron Moth's Sp. Atk was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Booster Energy will activate Protosynthesis after harsh sunlight ends (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_ABILITY_WEATHER, GEN_6); + PLAYER(SPECIES_RAGING_BOLT) { Attack(100); Defense(100); Speed(100); SpAttack(110); SpDefense(100); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTOSYNTHESIS); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_TORKOAL) { Speed(100); Ability(ABILITY_WHITE_SMOKE); Innates(ABILITY_DROUGHT); }; + } WHEN { + TURN {} + TURN {} + TURN {} + TURN {} + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Raging Bolt used its Booster Energy to activate Protosynthesis!"); + MESSAGE("Raging Bolt's Sp. Atk was heightened!"); + } + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("The harsh sunlight activated Raging Bolt's Protosynthesis!"); + MESSAGE("Raging Bolt's Sp. Atk was heightened!"); + MESSAGE("The sunlight faded."); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("Raging Bolt used its Booster Energy to activate Protosynthesis!"); + MESSAGE("Raging Bolt's Sp. Atk was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Booster Energy's Protosynthesis boost is preserved when weather changes (Traits)") +{ + GIVEN { + PLAYER(SPECIES_RAGING_BOLT) { Attack(110); Defense(100); Speed(100); SpAttack(100); SpDefense(100); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTOSYNTHESIS); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); Moves(MOVE_SUNNY_DAY, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); } + TURN { } + TURN { } + TURN { } + TURN { } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Raging Bolt used its Booster Energy to activate Protosynthesis!"); + MESSAGE("Raging Bolt's Attack was heightened!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + MESSAGE("The sunlight faded."); + } THEN { + EXPECT(gDisableStructs[B_POSITION_PLAYER_LEFT].paradoxBoostedStat == STAT_ATK); + } +} + +SINGLE_BATTLE_TEST("Booster Energy activates Protosynthesis and increases highest stat (Traits)") +{ + u32 attack, defense, speed, spAttack, spDefense; + + PARAMETRIZE { attack = 110; defense = 100; speed = 100; spAttack = 100; spDefense = 100; } + PARAMETRIZE { attack = 100; defense = 110; speed = 100; spAttack = 100; spDefense = 100; } + PARAMETRIZE { attack = 100; defense = 100; speed = 110; spAttack = 100; spDefense = 100; } + PARAMETRIZE { attack = 100; defense = 100; speed = 100; spAttack = 110; spDefense = 100; } + PARAMETRIZE { attack = 100; defense = 100; speed = 100; spAttack = 100; spDefense = 110; } + + GIVEN { + PLAYER(SPECIES_RAGING_BOLT) { Attack(attack); Defense(defense); Speed(speed); SpAttack(spAttack); SpDefense(spDefense); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTOSYNTHESIS); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); }; + } WHEN { + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("Raging Bolt used its Booster Energy to activate Protosynthesis!"); + if (attack == 110) + MESSAGE("Raging Bolt's Attack was heightened!"); + else if (defense == 110) + MESSAGE("Raging Bolt's Defense was heightened!"); + else if (speed == 110) + MESSAGE("Raging Bolt's Speed was heightened!"); + else if (spAttack == 110) + MESSAGE("Raging Bolt's Sp. Atk was heightened!"); + else if (spDefense == 110) + MESSAGE("Raging Bolt's Sp. Def was heightened!"); + } THEN { + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Booster Energy activates Quark Drive and increases highest stat (Traits)") +{ + u32 attack, defense, speed, spAttack, spDefense; + + PARAMETRIZE { attack = 110; defense = 100; speed = 100; spAttack = 100; spDefense = 100; } + PARAMETRIZE { attack = 100; defense = 110; speed = 100; spAttack = 100; spDefense = 100; } + PARAMETRIZE { attack = 100; defense = 100; speed = 110; spAttack = 100; spDefense = 100; } + PARAMETRIZE { attack = 100; defense = 100; speed = 100; spAttack = 110; spDefense = 100; } + PARAMETRIZE { attack = 100; defense = 100; speed = 100; spAttack = 100; spDefense = 110; } + + GIVEN { + PLAYER(SPECIES_IRON_MOTH) { Attack(attack); Defense(defense); Speed(speed); SpAttack(spAttack); SpDefense(spDefense); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_QUARK_DRIVE); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); }; + } WHEN { + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + if (attack == 110) + MESSAGE("Iron Moth's Attack was heightened!"); + else if (defense == 110) + MESSAGE("Iron Moth's Defense was heightened!"); + else if (speed == 110) + MESSAGE("Iron Moth's Speed was heightened!"); + else if (spAttack == 110) + MESSAGE("Iron Moth's Sp. Atk was heightened!"); + else if (spDefense == 110) + MESSAGE("Iron Moth's Sp. Def was heightened!"); + } THEN { + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Booster Energy's Quark Drive boost is preserved when terrain changes (Traits)") +{ + GIVEN { + PLAYER(SPECIES_IRON_MOTH) { Attack(110); Defense(100); Speed(100); SpAttack(100); SpDefense(100); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_QUARK_DRIVE); Item(ITEM_BOOSTER_ENERGY); Moves(MOVE_CELEBRATE); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); Moves(MOVE_GRASSY_TERRAIN, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_GRASSY_TERRAIN); } + TURN { } + TURN { } + TURN { } + TURN { } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Iron Moth used its Booster Energy to activate Quark Drive!"); + MESSAGE("Iron Moth's Attack was heightened!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASSY_TERRAIN, opponent); + MESSAGE("The grass disappeared from the battlefield."); + } THEN { + EXPECT(gDisableStructs[B_POSITION_PLAYER_LEFT].paradoxBoostedStat == STAT_ATK); + } +} + +SINGLE_BATTLE_TEST("Booster Energy increases special attack by 30% if it is the highest stat (Traits)", s16 damage) +{ + u32 species; + enum Ability ability; + u32 item; + + PARAMETRIZE { species = SPECIES_RAGING_BOLT; ability = ABILITY_PROTOSYNTHESIS; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_RAGING_BOLT; ability = ABILITY_PROTOSYNTHESIS; item = ITEM_BOOSTER_ENERGY; } + + PARAMETRIZE { species = SPECIES_IRON_MOTH; ability = ABILITY_QUARK_DRIVE; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_IRON_MOTH; ability = ABILITY_QUARK_DRIVE; item = ITEM_BOOSTER_ENERGY; } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_ROUND) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(species) { Attack(100); Defense(100); Speed(100); SpAttack(110); SpDefense(100); Ability(ABILITY_LIGHT_METAL); Innates(ability); Item(item); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(100); }; + } WHEN { + TURN { MOVE(player, MOVE_ROUND); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.3), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Booster Energy increases special defense by 30% if it is the highest stat (Traits)", s16 damage) +{ + u32 species; + enum Ability ability; + u32 item; + + PARAMETRIZE { species = SPECIES_RAGING_BOLT; ability = ABILITY_PROTOSYNTHESIS; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_RAGING_BOLT; ability = ABILITY_PROTOSYNTHESIS; item = ITEM_BOOSTER_ENERGY; } + + PARAMETRIZE { species = SPECIES_IRON_MOTH; ability = ABILITY_QUARK_DRIVE; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_IRON_MOTH; ability = ABILITY_QUARK_DRIVE; item = ITEM_BOOSTER_ENERGY; } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_ROUND) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(species) { Attack(100); Defense(100); Speed(100); SpAttack(100); SpDefense(110); Ability(ABILITY_LIGHT_METAL); Innates(ability); Item(item); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(100); }; + } WHEN { + TURN { MOVE(opponent, MOVE_ROUND); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.77), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Booster Energy can't be flung if a Paradox species is involved (Traits)") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_IRON_MOTH].isParadox == TRUE); + PLAYER(SPECIES_IRON_MOTH) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_QUARK_DRIVE); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_BOOSTER_ENERGY); } + } WHEN { + TURN { MOVE(opponent, MOVE_FLING); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, opponent); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Booster Energy can't be tricked if a Paradox species is involved (Traits)") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_IRON_MOTH].isParadox == TRUE); + PLAYER(SPECIES_IRON_MOTH) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_QUARK_DRIVE); Item(ITEM_BERRY_JUICE); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_BOOSTER_ENERGY); } + } WHEN { + TURN { MOVE(opponent, MOVE_TRICK); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponent); + MESSAGE("But it failed!"); + } +} + +DOUBLE_BATTLE_TEST("Booster Energy triggers correctly for all battlers if multiple fainted the previous turn (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_CATERPIE) { HP(1); } + PLAYER(SPECIES_GOUGING_FIRE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTOSYNTHESIS); Item(ITEM_BOOSTER_ENERGY); } + PLAYER(SPECIES_IRON_MOTH) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_QUARK_DRIVE); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_CATERPIE) { HP(1); } + OPPONENT(SPECIES_CATERPIE) { HP(1); } + OPPONENT(SPECIES_FLUTTER_MANE) { Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_CATERPIE); + } WHEN { + TURN { MOVE(playerLeft, MOVE_EXPLOSION); + SEND_OUT(opponentRight, 3); + SEND_OUT(opponentLeft, 2); + SEND_OUT(playerRight, 3); + SEND_OUT(playerLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, playerLeft); + ABILITY_POPUP(playerLeft, ABILITY_PROTOSYNTHESIS); + ABILITY_POPUP(playerRight, ABILITY_QUARK_DRIVE); + ABILITY_POPUP(opponentLeft, ABILITY_PROTOSYNTHESIS); + } +} + +DOUBLE_BATTLE_TEST("Booster Energy activates on any terrain (Traits)") +{ + GIVEN { + PLAYER(SPECIES_IRON_MOTH) { Speed(110); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_QUARK_DRIVE); Item(ITEM_BOOSTER_ENERGY); } + PLAYER(SPECIES_WOBBUFFET) { Speed(80); } + OPPONENT(SPECIES_TAPU_BULU) { Speed(100); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GRASSY_SURGE); } + OPPONENT(SPECIES_TAPU_KOKO) { Speed(10); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ELECTRIC_SURGE); }; + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_GRASSY_SURGE); + ABILITY_POPUP(playerLeft, ABILITY_QUARK_DRIVE); + ABILITY_POPUP(opponentRight, ABILITY_ELECTRIC_SURGE); + } +} + +DOUBLE_BATTLE_TEST("Booster Energy activates on air locked sun (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_RAGING_BOLT) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTOSYNTHESIS); Item(ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_PSYDUCK) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_CLOUD_NINE); } + OPPONENT(SPECIES_TORKOAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DROUGHT); }; + } WHEN { + TURN { SWITCH(playerLeft, 2); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_CLOUD_NINE); + ABILITY_POPUP(opponentRight, ABILITY_DROUGHT); + ABILITY_POPUP(playerLeft, ABILITY_PROTOSYNTHESIS); + } +} + +DOUBLE_BATTLE_TEST("Booster Energy will not activate on terrain if user has Protosynthesis instead of Quark Drive (Traits)") +{ + GIVEN { + PLAYER(SPECIES_RAGING_BOLT) { Speed(110); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTOSYNTHESIS); Item(ITEM_BOOSTER_ENERGY); } + PLAYER(SPECIES_WOBBUFFET) { Speed(80); } + OPPONENT(SPECIES_TAPU_BULU) { Speed(100); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GRASSY_SURGE); } + OPPONENT(SPECIES_TAPU_KOKO) { Speed(10); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ELECTRIC_SURGE); }; + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_GRASSY_SURGE); + NOT ABILITY_POPUP(playerLeft, ABILITY_PROTOSYNTHESIS); + ABILITY_POPUP(opponentRight, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(playerLeft, ABILITY_PROTOSYNTHESIS); // Activation after all terrains + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Booster Energy will activate Quark Drive after Electric Terrain ends (Multi)") +{ + GIVEN { + PLAYER(SPECIES_IRON_MOTH) { Attack(100); Defense(100); Speed(100); SpAttack(110); SpDefense(100); Ability(ABILITY_QUARK_DRIVE); Items(ITEM_PECHA_BERRY, ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_TAPU_KOKO) { Speed(100); Ability(ABILITY_ELECTRIC_SURGE); }; + } WHEN { + TURN {} + TURN {} + TURN {} + TURN {} + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Iron Moth used its Booster Energy to activate Quark Drive!"); + MESSAGE("Iron Moth's Sp. Atk was heightened!"); + } + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + MESSAGE("The Electric Terrain activated Iron Moth's Quark Drive!"); + MESSAGE("Iron Moth's Sp. Atk was heightened!"); + MESSAGE("The electricity disappeared from the battlefield."); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + MESSAGE("Iron Moth used its Booster Energy to activate Quark Drive!"); + MESSAGE("Iron Moth's Sp. Atk was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Booster Energy will activate Protosynthesis after harsh sunlight ends (Multi)") +{ + GIVEN { + WITH_CONFIG(CONFIG_ABILITY_WEATHER, GEN_6); + PLAYER(SPECIES_RAGING_BOLT) { Attack(100); Defense(100); Speed(100); SpAttack(110); SpDefense(100); Ability(ABILITY_PROTOSYNTHESIS); Items(ITEM_PECHA_BERRY, ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_TORKOAL) { Speed(100); Ability(ABILITY_DROUGHT); }; + } WHEN { + TURN {} + TURN {} + TURN {} + TURN {} + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Raging Bolt used its Booster Energy to activate Protosynthesis!"); + MESSAGE("Raging Bolt's Sp. Atk was heightened!"); + } + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("The harsh sunlight activated Raging Bolt's Protosynthesis!"); + MESSAGE("Raging Bolt's Sp. Atk was heightened!"); + MESSAGE("The sunlight faded."); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("Raging Bolt used its Booster Energy to activate Protosynthesis!"); + MESSAGE("Raging Bolt's Sp. Atk was heightened!"); + } +} + +SINGLE_BATTLE_TEST("Booster Energy's Protosynthesis boost is preserved when weather changes (Multi)") +{ + GIVEN { + PLAYER(SPECIES_RAGING_BOLT) { Attack(110); Defense(100); Speed(100); SpAttack(100); SpDefense(100); Ability(ABILITY_PROTOSYNTHESIS); Items(ITEM_PECHA_BERRY, ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); Moves(MOVE_SUNNY_DAY, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); } + TURN { } + TURN { } + TURN { } + TURN { } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Raging Bolt used its Booster Energy to activate Protosynthesis!"); + MESSAGE("Raging Bolt's Attack was heightened!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + MESSAGE("The sunlight faded."); + } THEN { + EXPECT(gDisableStructs[B_POSITION_PLAYER_LEFT].paradoxBoostedStat == STAT_ATK); + } +} + +SINGLE_BATTLE_TEST("Booster Energy activates Protosynthesis and increases highest stat (Multi)") +{ + u32 attack, defense, speed, spAttack, spDefense; + + PARAMETRIZE { attack = 110; defense = 100; speed = 100; spAttack = 100; spDefense = 100; } + PARAMETRIZE { attack = 100; defense = 110; speed = 100; spAttack = 100; spDefense = 100; } + PARAMETRIZE { attack = 100; defense = 100; speed = 110; spAttack = 100; spDefense = 100; } + PARAMETRIZE { attack = 100; defense = 100; speed = 100; spAttack = 110; spDefense = 100; } + PARAMETRIZE { attack = 100; defense = 100; speed = 100; spAttack = 100; spDefense = 110; } + + GIVEN { + PLAYER(SPECIES_RAGING_BOLT) { Attack(attack); Defense(defense); Speed(speed); SpAttack(spAttack); SpDefense(spDefense); Ability(ABILITY_PROTOSYNTHESIS); Items(ITEM_PECHA_BERRY, ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); }; + } WHEN { + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("Raging Bolt used its Booster Energy to activate Protosynthesis!"); + if (attack == 110) + MESSAGE("Raging Bolt's Attack was heightened!"); + else if (defense == 110) + MESSAGE("Raging Bolt's Defense was heightened!"); + else if (speed == 110) + MESSAGE("Raging Bolt's Speed was heightened!"); + else if (spAttack == 110) + MESSAGE("Raging Bolt's Sp. Atk was heightened!"); + else if (spDefense == 110) + MESSAGE("Raging Bolt's Sp. Def was heightened!"); + } THEN { + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Booster Energy activates Quark Drive and increases highest stat (Multi)") +{ + u32 attack, defense, speed, spAttack, spDefense; + + PARAMETRIZE { attack = 110; defense = 100; speed = 100; spAttack = 100; spDefense = 100; } + PARAMETRIZE { attack = 100; defense = 110; speed = 100; spAttack = 100; spDefense = 100; } + PARAMETRIZE { attack = 100; defense = 100; speed = 110; spAttack = 100; spDefense = 100; } + PARAMETRIZE { attack = 100; defense = 100; speed = 100; spAttack = 110; spDefense = 100; } + PARAMETRIZE { attack = 100; defense = 100; speed = 100; spAttack = 100; spDefense = 110; } + + GIVEN { + PLAYER(SPECIES_IRON_MOTH) { Attack(attack); Defense(defense); Speed(speed); SpAttack(spAttack); SpDefense(spDefense); Ability(ABILITY_QUARK_DRIVE); Items(ITEM_PECHA_BERRY, ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); }; + } WHEN { + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + if (attack == 110) + MESSAGE("Iron Moth's Attack was heightened!"); + else if (defense == 110) + MESSAGE("Iron Moth's Defense was heightened!"); + else if (speed == 110) + MESSAGE("Iron Moth's Speed was heightened!"); + else if (spAttack == 110) + MESSAGE("Iron Moth's Sp. Atk was heightened!"); + else if (spDefense == 110) + MESSAGE("Iron Moth's Sp. Def was heightened!"); + } THEN { + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Booster Energy's Quark Drive boost is preserved when terrain changes (Multi)") +{ + GIVEN { + PLAYER(SPECIES_IRON_MOTH) { Attack(110); Defense(100); Speed(100); SpAttack(100); SpDefense(100); Ability(ABILITY_QUARK_DRIVE); Items(ITEM_PECHA_BERRY, ITEM_BOOSTER_ENERGY); Moves(MOVE_CELEBRATE); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); Moves(MOVE_GRASSY_TERRAIN, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_GRASSY_TERRAIN); } + TURN { } + TURN { } + TURN { } + TURN { } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Iron Moth used its Booster Energy to activate Quark Drive!"); + MESSAGE("Iron Moth's Attack was heightened!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASSY_TERRAIN, opponent); + MESSAGE("The grass disappeared from the battlefield."); + } THEN { + EXPECT(gDisableStructs[B_POSITION_PLAYER_LEFT].paradoxBoostedStat == STAT_ATK); + } +} + +SINGLE_BATTLE_TEST("Booster Energy increases special attack by 30% if it is the highest stat (Multi)", s16 damage) +{ + u32 species; + enum Ability ability; + u32 item; + + PARAMETRIZE { species = SPECIES_RAGING_BOLT; ability = ABILITY_PROTOSYNTHESIS; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_RAGING_BOLT; ability = ABILITY_PROTOSYNTHESIS; item = ITEM_BOOSTER_ENERGY; } + + PARAMETRIZE { species = SPECIES_IRON_MOTH; ability = ABILITY_QUARK_DRIVE; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_IRON_MOTH; ability = ABILITY_QUARK_DRIVE; item = ITEM_BOOSTER_ENERGY; } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_ROUND) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(species) { Attack(100); Defense(100); Speed(100); SpAttack(110); SpDefense(100); Ability(ability); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(100); }; + } WHEN { + TURN { MOVE(player, MOVE_ROUND); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.3), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Booster Energy increases special defense by 30% if it is the highest stat (Multi)", s16 damage) +{ + u32 species; + enum Ability ability; + u32 item; + + PARAMETRIZE { species = SPECIES_RAGING_BOLT; ability = ABILITY_PROTOSYNTHESIS; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_RAGING_BOLT; ability = ABILITY_PROTOSYNTHESIS; item = ITEM_BOOSTER_ENERGY; } + + PARAMETRIZE { species = SPECIES_IRON_MOTH; ability = ABILITY_QUARK_DRIVE; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_IRON_MOTH; ability = ABILITY_QUARK_DRIVE; item = ITEM_BOOSTER_ENERGY; } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_ROUND) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(species) { Attack(100); Defense(100); Speed(100); SpAttack(100); SpDefense(110); Ability(ability); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(100); }; + } WHEN { + TURN { MOVE(opponent, MOVE_ROUND); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.77), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Booster Energy can't be flung if a Paradox species is involved (Multi)") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_IRON_MOTH].isParadox == TRUE); + PLAYER(SPECIES_IRON_MOTH); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_BOOSTER_ENERGY); } + } WHEN { + TURN { MOVE(opponent, MOVE_FLING); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, opponent); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Booster Energy can't be tricked if a Paradox species is involved (Multi)") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_IRON_MOTH].isParadox == TRUE); + PLAYER(SPECIES_IRON_MOTH) { Items(ITEM_NONE, ITEM_BERRY_JUICE); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_BOOSTER_ENERGY); } + } WHEN { + TURN { MOVE(opponent, MOVE_TRICK); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponent); + MESSAGE("But it failed!"); + } +} + +DOUBLE_BATTLE_TEST("Booster Energy triggers correctly for all battlers if multiple fainted the previous turn (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_CATERPIE) { HP(1); } + PLAYER(SPECIES_GOUGING_FIRE) { Items(ITEM_PECHA_BERRY, ITEM_BOOSTER_ENERGY); } + PLAYER(SPECIES_IRON_MOTH) { Items(ITEM_PECHA_BERRY, ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_CATERPIE) { HP(1); } + OPPONENT(SPECIES_CATERPIE) { HP(1); } + OPPONENT(SPECIES_FLUTTER_MANE) { Items(ITEM_PECHA_BERRY, ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_CATERPIE); + } WHEN { + TURN { MOVE(playerLeft, MOVE_EXPLOSION); + SEND_OUT(opponentRight, 3); + SEND_OUT(opponentLeft, 2); + SEND_OUT(playerRight, 3); + SEND_OUT(playerLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, playerLeft); + ABILITY_POPUP(playerLeft, ABILITY_PROTOSYNTHESIS); + ABILITY_POPUP(playerRight, ABILITY_QUARK_DRIVE); + ABILITY_POPUP(opponentLeft, ABILITY_PROTOSYNTHESIS); + } +} + +DOUBLE_BATTLE_TEST("Booster Energy activates on any terrain (Multi)") +{ + GIVEN { + PLAYER(SPECIES_IRON_MOTH) { Speed(110); Ability(ABILITY_QUARK_DRIVE); Items(ITEM_PECHA_BERRY, ITEM_BOOSTER_ENERGY); } + PLAYER(SPECIES_WOBBUFFET) { Speed(80); } + OPPONENT(SPECIES_TAPU_BULU) { Speed(100); Ability(ABILITY_GRASSY_SURGE); } + OPPONENT(SPECIES_TAPU_KOKO) { Speed(10); Ability(ABILITY_ELECTRIC_SURGE); }; + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_GRASSY_SURGE); + ABILITY_POPUP(playerLeft, ABILITY_QUARK_DRIVE); + ABILITY_POPUP(opponentRight, ABILITY_ELECTRIC_SURGE); + } +} + +DOUBLE_BATTLE_TEST("Booster Energy activates on air locked sun (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_RAGING_BOLT) { Ability(ABILITY_PROTOSYNTHESIS); Items(ITEM_PECHA_BERRY, ITEM_BOOSTER_ENERGY); } + OPPONENT(SPECIES_PSYDUCK) { Ability(ABILITY_CLOUD_NINE); } + OPPONENT(SPECIES_TORKOAL) { Ability(ABILITY_DROUGHT); }; + } WHEN { + TURN { SWITCH(playerLeft, 2); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_CLOUD_NINE); + ABILITY_POPUP(opponentRight, ABILITY_DROUGHT); + ABILITY_POPUP(playerLeft, ABILITY_PROTOSYNTHESIS); + } +} + +DOUBLE_BATTLE_TEST("Booster Energy will not activate on terrain if user has Protosynthesis instead of Quark Drive (Multi)") +{ + GIVEN { + PLAYER(SPECIES_RAGING_BOLT) { Speed(110); Ability(ABILITY_PROTOSYNTHESIS); Items(ITEM_PECHA_BERRY, ITEM_BOOSTER_ENERGY); } + PLAYER(SPECIES_WOBBUFFET) { Speed(80); } + OPPONENT(SPECIES_TAPU_BULU) { Speed(100); Ability(ABILITY_GRASSY_SURGE); } + OPPONENT(SPECIES_TAPU_KOKO) { Speed(10); Ability(ABILITY_ELECTRIC_SURGE); }; + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_GRASSY_SURGE); + NOT ABILITY_POPUP(playerLeft, ABILITY_PROTOSYNTHESIS); + ABILITY_POPUP(opponentRight, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(playerLeft, ABILITY_PROTOSYNTHESIS); // Activation after all terrains + } +} +#endif diff --git a/test/battle/hold_effect/clear_amulet.c b/test/battle/hold_effect/clear_amulet.c index f073fe3a000c..b816e2ec4c94 100644 --- a/test/battle/hold_effect/clear_amulet.c +++ b/test/battle/hold_effect/clear_amulet.c @@ -119,3 +119,119 @@ SINGLE_BATTLE_TEST("Clear Amulet protects from Protect's secondary effects") } } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Clear Amulet prevents Intimidate (Multi)") +{ + s16 turnOneHit; + s16 turnTwoHit; + + GIVEN { + PLAYER(SPECIES_EKANS) { Ability(ABILITY_SHED_SKIN); }; + PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); }; + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_CLEAR_AMULET); }; + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + HP_BAR(player, captureDamage: &turnOneHit); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("The effects of the Clear Amulet held by the opposing Wobbuffet prevents its stats from being lowered!"); + HP_BAR(player, captureDamage: &turnTwoHit); + } THEN { + EXPECT_EQ(turnOneHit, turnTwoHit); + } +} + +SINGLE_BATTLE_TEST("Clear Amulet prevents stat reducing effects (Multi)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_GROWL; } + PARAMETRIZE { move = MOVE_LEER; } + PARAMETRIZE { move = MOVE_CONFIDE; } + PARAMETRIZE { move = MOVE_FAKE_TEARS; } + PARAMETRIZE { move = MOVE_SCARY_FACE; } + PARAMETRIZE { move = MOVE_SWEET_SCENT; } + PARAMETRIZE { move = MOVE_SAND_ATTACK; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_GROWL) == EFFECT_ATTACK_DOWN); + ASSUME(GetMoveEffect(MOVE_LEER) == EFFECT_DEFENSE_DOWN); + ASSUME(GetMoveEffect(MOVE_CONFIDE) == EFFECT_SPECIAL_ATTACK_DOWN); + ASSUME(GetMoveEffect(MOVE_FAKE_TEARS) == EFFECT_SPECIAL_DEFENSE_DOWN_2); + ASSUME(GetMoveEffect(MOVE_SCARY_FACE) == EFFECT_SPEED_DOWN_2); + ASSUME(GetMoveEffect(MOVE_SWEET_SCENT) == (B_UPDATED_MOVE_DATA >= GEN_6 ? EFFECT_EVASION_DOWN_2 : EFFECT_EVASION_DOWN)); + ASSUME(GetMoveEffect(MOVE_SAND_ATTACK) == EFFECT_ACCURACY_DOWN); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_CLEAR_AMULET); }; + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The effects of the Clear Amulet held by the opposing Wobbuffet prevents its stats from being lowered!"); + } +} + +SINGLE_BATTLE_TEST("Clear Amulet prevents secondary effects that reduce stats (Multi)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_AURORA_BEAM; } + PARAMETRIZE { move = MOVE_ROCK_SMASH; } + PARAMETRIZE { move = MOVE_SNARL; } + PARAMETRIZE { move = MOVE_PSYCHIC; } + PARAMETRIZE { move = MOVE_BUBBLE_BEAM; } + PARAMETRIZE { move = MOVE_MUD_SLAP; } + + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_AURORA_BEAM, MOVE_EFFECT_ATK_MINUS_1) == TRUE); + ASSUME(MoveHasAdditionalEffect(MOVE_ROCK_SMASH, MOVE_EFFECT_DEF_MINUS_1) == TRUE); + ASSUME(MoveHasAdditionalEffect(MOVE_BUBBLE_BEAM, MOVE_EFFECT_SPD_MINUS_1) == TRUE); + ASSUME(MoveHasAdditionalEffect(MOVE_SNARL, MOVE_EFFECT_SP_ATK_MINUS_1) == TRUE); + ASSUME(MoveHasAdditionalEffect(MOVE_PSYCHIC, MOVE_EFFECT_SP_DEF_MINUS_1) == TRUE); + ASSUME(MoveHasAdditionalEffect(MOVE_MUD_SLAP, MOVE_EFFECT_ACC_MINUS_1) == TRUE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_CLEAR_AMULET); }; + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The effects of the Clear Amulet held by the opposing Wobbuffet prevents its stats from being lowered!"); + } + } +} + +SINGLE_BATTLE_TEST("Clear Amulet protects from Protect's secondary effects (Multi)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SPIKY_SHIELD; } + PARAMETRIZE { move = MOVE_BANEFUL_BUNKER; } + PARAMETRIZE { move = MOVE_BURNING_BULWARK; } + PARAMETRIZE { move = MOVE_KINGS_SHIELD; } + PARAMETRIZE { move = MOVE_SILK_TRAP; } + PARAMETRIZE { move = MOVE_OBSTRUCT; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_CLEAR_AMULET); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + if (move == MOVE_KINGS_SHIELD) { + MESSAGE("Wobbuffet's Attack fell!"); + } else if (move == MOVE_SILK_TRAP) { + MESSAGE("Wobbuffet's Speed fell!"); + } else if (move == MOVE_OBSTRUCT) { + MESSAGE("Wobbuffet's Defense harshly fell!"); + } + } + } +} +#endif diff --git a/test/battle/hold_effect/covert_cloak.c b/test/battle/hold_effect/covert_cloak.c index 64f75703ccd1..88782d54d464 100644 --- a/test/battle/hold_effect/covert_cloak.c +++ b/test/battle/hold_effect/covert_cloak.c @@ -202,3 +202,216 @@ SINGLE_BATTLE_TEST("Covert Cloak does not prevent ability stat changes") MESSAGE("Wobbuffet's Speed fell!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Covert Cloak does not prevent ability stat changes (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_COVERT_CLOAK); } + OPPONENT(SPECIES_ELDEGOSS) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_COTTON_DOWN); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet's Speed fell!"); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Covert Cloak blocks secondary effects (Multi)") +{ + u16 move; + PARAMETRIZE { move = MOVE_NUZZLE; } + PARAMETRIZE { move = MOVE_INFERNO; } + PARAMETRIZE { move = MOVE_MORTAL_SPIN; } + PARAMETRIZE { move = MOVE_FAKE_OUT; } + PARAMETRIZE { move = MOVE_ROCK_TOMB; } + PARAMETRIZE { move = MOVE_SPIRIT_SHACKLE; } + PARAMETRIZE { move = MOVE_PSYCHIC_NOISE; } + + GIVEN { + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_NUZZLE, MOVE_EFFECT_PARALYSIS, 100) == TRUE); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_INFERNO, MOVE_EFFECT_BURN, 100) == TRUE); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_MORTAL_SPIN, MOVE_EFFECT_POISON, 100) == TRUE); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_FAKE_OUT, MOVE_EFFECT_FLINCH, 100) == TRUE); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_ROCK_TOMB, MOVE_EFFECT_SPD_MINUS_1, 100) == TRUE); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_SPIRIT_SHACKLE, MOVE_EFFECT_PREVENT_ESCAPE, 100) == TRUE); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_PSYCHIC_NOISE, MOVE_EFFECT_PSYCHIC_NOISE, 100) == TRUE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_COVERT_CLOAK); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + NONE_OF { + MESSAGE("The opposing Wobbuffet is paralyzed, so it may be unable to move!"); + MESSAGE("The opposing Wobbuffet was burned!"); + MESSAGE("The opposing Wobbuffet was poisoned!"); + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wobbuffet was prevented from healing!"); + } + } THEN { // Can't find good way to test trapping + EXPECT(!opponent->volatiles.escapePrevention); + } +} + +SINGLE_BATTLE_TEST("Covert Cloak does not block primary effects (Multi)") +{ + u16 move; + PARAMETRIZE { move = MOVE_INFESTATION; } + PARAMETRIZE { move = MOVE_THOUSAND_ARROWS; } + PARAMETRIZE { move = MOVE_JAW_LOCK; } + PARAMETRIZE { move = MOVE_PAY_DAY; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_THOUSAND_ARROWS) == EFFECT_SMACK_DOWN); + ASSUME(GetMoveEffect(MOVE_SMACK_DOWN) == EFFECT_SMACK_DOWN); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_INFESTATION, MOVE_EFFECT_WRAP, 0) == TRUE); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_JAW_LOCK, MOVE_EFFECT_TRAP_BOTH, 0) == TRUE); + ASSUME(MoveHasAdditionalEffectWithChance(MOVE_PAY_DAY, MOVE_EFFECT_PAYDAY, 0) == TRUE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SKARMORY) { Items(ITEM_PECHA_BERRY, ITEM_COVERT_CLOAK); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + switch (move) { + case MOVE_INFESTATION: + MESSAGE("The opposing Skarmory has been afflicted with an infestation by Wobbuffet!"); + break; + case MOVE_THOUSAND_ARROWS: + MESSAGE("The opposing Skarmory fell straight down!"); + break; + case MOVE_JAW_LOCK: + MESSAGE("Neither Pokémon can run away!"); + break; + case MOVE_PAY_DAY: + MESSAGE("Coins were scattered everywhere!"); + break; + } + } THEN { // Can't find good way to test trapping + if (move == MOVE_JAW_LOCK) { + EXPECT(opponent->volatiles.escapePrevention); + EXPECT(player->volatiles.escapePrevention); + } + } +} + +SINGLE_BATTLE_TEST("Covert Cloak does not block self-targeting effects, primary or secondary (Multi)") +{ + u16 move; + PARAMETRIZE { move = MOVE_POWER_UP_PUNCH; } + PARAMETRIZE { move = MOVE_FLAME_CHARGE; } + PARAMETRIZE { move = MOVE_LEAF_STORM; } + PARAMETRIZE { move = MOVE_METEOR_ASSAULT; } + + GIVEN { + ASSUME(MoveHasAdditionalEffectSelf(MOVE_FLAME_CHARGE, MOVE_EFFECT_SPD_PLUS_1) == TRUE); + ASSUME(MoveHasAdditionalEffectSelf(MOVE_POWER_UP_PUNCH, MOVE_EFFECT_ATK_PLUS_1) == TRUE); + ASSUME(MoveHasAdditionalEffectSelf(MOVE_LEAF_STORM, MOVE_EFFECT_SP_ATK_MINUS_2) == TRUE); + ASSUME(MoveHasAdditionalEffectSelf(MOVE_METEOR_ASSAULT, MOVE_EFFECT_RECHARGE) == TRUE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_COVERT_CLOAK); } + } WHEN { + TURN { MOVE(player, move); } + if (move == MOVE_METEOR_ASSAULT) { + TURN { SKIP_TURN(player); } + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + switch (move) { + case MOVE_POWER_UP_PUNCH: + case MOVE_FLAME_CHARGE: + case MOVE_LEAF_STORM: + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + break; + case MOVE_METEOR_ASSAULT: // second turn + MESSAGE("Wobbuffet must recharge!"); + break; + } + } +} + +DOUBLE_BATTLE_TEST("Covert Cloak does or does not block Sparkling Aria depending on number of targets hit (Multi)") +{ + u32 moveToUse; + PARAMETRIZE { moveToUse = MOVE_FINAL_GAMBIT; } + PARAMETRIZE { moveToUse = MOVE_SCRATCH; } + GIVEN { + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_COVERT_CLOAK); Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, moveToUse, target: opponentRight); MOVE(playerLeft, MOVE_SPARKLING_ARIA); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPARKLING_ARIA, playerLeft); + if (moveToUse == MOVE_SCRATCH) { + MESSAGE("The opposing Wobbuffet's burn was cured!"); + STATUS_ICON(opponentLeft, none: TRUE); + } else { + NONE_OF { + MESSAGE("The opposing Wobbuffet's burn was cured!"); + STATUS_ICON(opponentLeft, none: TRUE); + } + } + } +} + +DOUBLE_BATTLE_TEST("Covert Cloak does block Sparkling Aria when only one mon is hit (Multi)") +{ + u32 move; + PARAMETRIZE { move = MOVE_PROTECT; } + PARAMETRIZE { move = MOVE_FLY; } + + GIVEN { + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_COVERT_CLOAK); Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentRight, move, target: playerLeft); + MOVE(playerRight, move, target: opponentRight); + MOVE(playerLeft, MOVE_SPARKLING_ARIA); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPARKLING_ARIA, playerLeft); + NONE_OF { + MESSAGE("The opposing Wobbuffet's burn was cured!"); + STATUS_ICON(opponentLeft, none: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Covert Cloak blocks Sparkling Aria in singles (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_COVERT_CLOAK); Status1(STATUS1_BURN); } + } WHEN { + TURN { MOVE(player, MOVE_SPARKLING_ARIA); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPARKLING_ARIA, player); + NONE_OF { + MESSAGE("The opposing Wobbuffet's burn was cured!"); + STATUS_ICON(opponent, none: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Covert Cloak does not prevent ability stat changes (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_COVERT_CLOAK); } + OPPONENT(SPECIES_ELDEGOSS) { Ability(ABILITY_COTTON_DOWN); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet's Speed fell!"); + } +} +#endif diff --git a/test/battle/hold_effect/critical_up.c b/test/battle/hold_effect/critical_up.c index 163d8f1c271b..c1f060bdb647 100644 --- a/test/battle/hold_effect/critical_up.c +++ b/test/battle/hold_effect/critical_up.c @@ -71,3 +71,69 @@ SINGLE_BATTLE_TEST("Lansat Berry raises the holder's critical-hit-ratio by 2 sta MESSAGE("A critical hit!"); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Lansat Berry raises the holder's critical-hit-ratio by two stages when HP drops to 1/4 or below (Multi)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_DRAGON_RAGE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { MaxHP(160); HP(80); Items(ITEM_PECHA_BERRY, ITEM_LANSAT_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + if (move == MOVE_SCRATCH) { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet used the Lansat Berry to get pumped!"); + } + } else { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet used the Lansat Berry to get pumped!"); + } + } +} + +SINGLE_BATTLE_TEST("Lansat Berry raises the holder's critical-hit-ratio by two stages when HP drops to 1/2 or below (Multi)") +{ + GIVEN { + PLAYER(SPECIES_BELLSPROUT) { MaxHP(80); HP(80); Ability(ABILITY_GLUTTONY); Items(ITEM_PECHA_BERRY, ITEM_LANSAT_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Bellsprout used the Lansat Berry to get pumped!"); + } +} + +SINGLE_BATTLE_TEST("Lansat Berry raises the holder's critical-hit-ratio by 2 stages (Multi)") +{ + u32 genConfig = 0, chance; + for (u32 j = GEN_1; j <= GEN_5; j++) + PARAMETRIZE { genConfig = j; chance = 4; } // 25% + for (u32 j = GEN_6; j <= GEN_9; j++) + PARAMETRIZE { genConfig = j; chance = 2; } // 50% + PASSES_RANDOMLY(1, chance, RNG_CRITICAL_HIT); + GIVEN { + WITH_CONFIG(CONFIG_CRIT_CHANCE, genConfig); + ASSUME(GetMoveCriticalHitStage(MOVE_SCRATCH) == 0); + PLAYER(SPECIES_WOBBUFFET) { MaxHP(160); HP(80); Items(ITEM_PECHA_BERRY, ITEM_LANSAT_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet used the Lansat Berry to get pumped!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("A critical hit!"); + } +} +#endif diff --git a/test/battle/hold_effect/cure_status.c b/test/battle/hold_effect/cure_status.c index bffe04137256..1c8d556596a7 100644 --- a/test/battle/hold_effect/cure_status.c +++ b/test/battle/hold_effect/cure_status.c @@ -356,3 +356,319 @@ SINGLE_BATTLE_TEST("Lum Berry properly cures a battler affected by a non-volatil EXPECT(player->volatiles.confusionTurns == 0); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Pecha and Lum Berries cure poison (Multi)") +{ + u16 item; + + PARAMETRIZE { item = ITEM_PECHA_BERRY; } + PARAMETRIZE { item = ITEM_LUM_BERRY; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_PECHA_BERRY].holdEffect == HOLD_EFFECT_CURE_PSN); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, item); } + } WHEN { + TURN { MOVE(player, MOVE_POISON_POWDER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_POWDER, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, poison: TRUE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + STATUS_ICON(opponent, poison: FALSE); + } +} + +SINGLE_BATTLE_TEST("Pecha and Lum Berries cure bad poison (Multi)") +{ + u16 item; + + PARAMETRIZE { item = ITEM_PECHA_BERRY; } + PARAMETRIZE { item = ITEM_LUM_BERRY; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_PECHA_BERRY].holdEffect == HOLD_EFFECT_CURE_PSN); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, item); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, badPoison: TRUE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + STATUS_ICON(opponent, badPoison: FALSE); + } +} + +SINGLE_BATTLE_TEST("Rawst and Lum Berries cure burn (Multi)") +{ + u16 item; + + PARAMETRIZE { item = ITEM_RAWST_BERRY; } + PARAMETRIZE { item = ITEM_LUM_BERRY; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_RAWST_BERRY].holdEffect == HOLD_EFFECT_CURE_BRN); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, item); } + } WHEN { + TURN { MOVE(player, MOVE_WILL_O_WISP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WILL_O_WISP, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, opponent); + STATUS_ICON(opponent, burn: TRUE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + STATUS_ICON(opponent, burn: FALSE); + } +} + +SINGLE_BATTLE_TEST("Aspear and Lum Berries cure freeze or frostbite (Multi)") +{ + u16 item; + + PARAMETRIZE { item = ITEM_ASPEAR_BERRY; } + PARAMETRIZE { item = ITEM_LUM_BERRY; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_ASPEAR_BERRY].holdEffect == HOLD_EFFECT_CURE_FRZ); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, item); } + } WHEN { + TURN { MOVE(player, MOVE_ICE_PUNCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ICE_PUNCH, player); + ANIMATION(ANIM_TYPE_STATUS, (B_USE_FROSTBITE ? B_ANIM_STATUS_FRB : B_ANIM_STATUS_FRZ), opponent); + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + FREEZE_OR_FROSTBURN_STATUS(opponent, FALSE); + } +} + +SINGLE_BATTLE_TEST("Chesto and Lum Berries cure sleep (Multi)") +{ + u16 item; + + PARAMETRIZE { item = ITEM_CHESTO_BERRY; } + PARAMETRIZE { item = ITEM_LUM_BERRY; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_CHESTO_BERRY].holdEffect == HOLD_EFFECT_CURE_SLP); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, item); } + } WHEN { + TURN { MOVE(player, MOVE_HYPNOSIS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPNOSIS, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + STATUS_ICON(opponent, sleep: TRUE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + STATUS_ICON(opponent, sleep: FALSE); + } +} + +TO_DO_BATTLE_TEST("Chesto and Lum Berries don't trigger if the holder has Comatose (Multi)") + +SINGLE_BATTLE_TEST("Cheri and Lum Berries cure paralysis (Multi)") +{ + u16 item; + + PARAMETRIZE { item = ITEM_CHERI_BERRY; } + PARAMETRIZE { item = ITEM_LUM_BERRY; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_CHERI_BERRY].holdEffect == HOLD_EFFECT_CURE_PAR); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, item); } + } WHEN { + TURN { MOVE(player, MOVE_THUNDER_WAVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_WAVE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, opponent); + STATUS_ICON(opponent, paralysis: TRUE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + STATUS_ICON(opponent, paralysis: FALSE); + } +} + +SINGLE_BATTLE_TEST("Perism and Lum Berries cure confusion (Multi)") +{ + u16 item; + + PARAMETRIZE { item = ITEM_PERSIM_BERRY; } + PARAMETRIZE { item = ITEM_LUM_BERRY; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_PERSIM_BERRY].holdEffect == HOLD_EFFECT_CURE_CONFUSION); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, item); } + } WHEN { + TURN { MOVE(player, MOVE_CONFUSE_RAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} + +SINGLE_BATTLE_TEST("Berry hold effect cures status if a Pokémon enters a battle (Multi)") +{ + u16 status; + u16 item; + + PARAMETRIZE { status = STATUS1_BURN; item = ITEM_RAWST_BERRY; } + PARAMETRIZE { status = STATUS1_FREEZE; item = ITEM_ASPEAR_BERRY; } + PARAMETRIZE { status = STATUS1_PARALYSIS; item = ITEM_CHERI_BERRY; } + PARAMETRIZE { status = STATUS1_POISON; item = ITEM_PECHA_BERRY; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; item = ITEM_PECHA_BERRY; } + PARAMETRIZE { status = STATUS1_SLEEP; item = ITEM_CHESTO_BERRY; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_RAWST_BERRY].holdEffect == HOLD_EFFECT_CURE_BRN); + ASSUME(gItemsInfo[ITEM_ASPEAR_BERRY].holdEffect == HOLD_EFFECT_CURE_FRZ); + ASSUME(gItemsInfo[ITEM_CHERI_BERRY].holdEffect == HOLD_EFFECT_CURE_PAR); + ASSUME(gItemsInfo[ITEM_PECHA_BERRY].holdEffect == HOLD_EFFECT_CURE_PSN); + ASSUME(gItemsInfo[ITEM_CHESTO_BERRY].holdEffect == HOLD_EFFECT_CURE_SLP); + PLAYER(SPECIES_WOBBUFFET) { Status1(status); Items(ITEM_ORAN_BERRY, ITEM_LUM_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Status1(status); Items(ITEM_ORAN_BERRY, item); } + } WHEN { + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} + +SINGLE_BATTLE_TEST("Opponent Pokemon can be further poisoned with Toxic spikes after a status healing hold effect was previously used (Multi)") +{ + u16 item; + + PARAMETRIZE { item = ITEM_PECHA_BERRY; } + PARAMETRIZE { item = ITEM_LUM_BERRY; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_PECHA_BERRY].holdEffect == HOLD_EFFECT_CURE_PSN); + ASSUME(gItemsInfo[ITEM_LUM_BERRY].holdEffect == HOLD_EFFECT_CURE_STATUS); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { Items(ITEM_ORAN_BERRY, item); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC_SPIKES); } + TURN { SWITCH(opponent, 1); } + TURN { SWITCH(opponent, 0); } + } SCENE { + MESSAGE("Wobbuffet used Toxic Spikes!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, player); + MESSAGE("Poison spikes were scattered on the ground all around the opposing team!"); + // 1st switch-in + MESSAGE("2 sent out Wynaut!"); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, poison: TRUE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + if (item == ITEM_PECHA_BERRY) { + MESSAGE("The opposing Wynaut's Pecha Berry cured its poison!"); + } else { + MESSAGE("The opposing Wynaut's Lum Berry cured its poison problem!"); + } + STATUS_ICON(opponent, poison: FALSE); + // 2nd switch-in + MESSAGE("2 sent out Wobbuffet!"); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, poison: TRUE); + } +} + +// Basically same as above, but with the sides reversed. +SINGLE_BATTLE_TEST("Player Pokemon can be further poisoned with Toxic spikes after a status healing hold effect was previously used (Multi)") +{ + u16 item; + + PARAMETRIZE { item = ITEM_PECHA_BERRY; } + PARAMETRIZE { item = ITEM_LUM_BERRY; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_PECHA_BERRY].holdEffect == HOLD_EFFECT_CURE_PSN); + ASSUME(gItemsInfo[ITEM_LUM_BERRY].holdEffect == HOLD_EFFECT_CURE_STATUS); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET) {Items(ITEM_ORAN_BERRY, item); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TOXIC_SPIKES); } + TURN { SWITCH(player, 1); } + TURN { SWITCH(player, 2); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Toxic Spikes!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, opponent); + MESSAGE("Poison spikes were scattered on the ground all around your team!"); + // 1st switch-in + SEND_IN_MESSAGE("Wobbuffet"); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + STATUS_ICON(player, poison: TRUE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + if (item == ITEM_PECHA_BERRY) { + MESSAGE("Wobbuffet's Pecha Berry cured its poison!"); + } else { + MESSAGE("Wobbuffet's Lum Berry cured its poison problem!"); + } + STATUS_ICON(player, poison: FALSE); + // 2nd switch-in + SEND_IN_MESSAGE("Wynaut"); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + STATUS_ICON(player, poison: TRUE); + } +} + +DOUBLE_BATTLE_TEST("Lum Berry correctly cures all battlers if multiple fainted the previous turn (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_CATERPIE) { HP(1); } + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, ITEM_LUM_BERRY); Status1(STATUS1_BURN); } + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, ITEM_LUM_BERRY); Status1(STATUS1_POISON); } + OPPONENT(SPECIES_CATERPIE) { HP(1); } + OPPONENT(SPECIES_CATERPIE) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, ITEM_LUM_BERRY); Status1(STATUS1_PARALYSIS); } + OPPONENT(SPECIES_CATERPIE); + } WHEN { + TURN { MOVE(playerLeft, MOVE_EXPLOSION); + SEND_OUT(opponentRight, 3); + SEND_OUT(opponentLeft, 2); + SEND_OUT(playerRight, 3); + SEND_OUT(playerLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, playerLeft); + } THEN { + EXPECT_EQ(playerLeft->status1, STATUS1_NONE); + EXPECT_EQ(playerRight->status1, STATUS1_NONE); + EXPECT_EQ(opponentLeft->status1, STATUS1_NONE); + } +} + +SINGLE_BATTLE_TEST("Lum Berry properly cures a battler affected by a non-volatiles status and confusion (Multi)") +{ + u32 status; + PARAMETRIZE { status = STATUS1_BURN;} + PARAMETRIZE { status = STATUS1_FREEZE;} + PARAMETRIZE { status = STATUS1_PARALYSIS;} + PARAMETRIZE { status = STATUS1_POISON;} + PARAMETRIZE { status = STATUS1_TOXIC_POISON;} + PARAMETRIZE { status = STATUS1_SLEEP;} + + GIVEN { + ASSUME(GetMoveEffect(MOVE_CONFUSE_RAY) == EFFECT_CONFUSE); + PLAYER(SPECIES_WOBBUFFET) { Status1(status); Speed(1);}; + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, ITEM_LUM_BERRY); Speed(2);}; + } WHEN { + TURN { MOVE(opponent, MOVE_CONFUSE_RAY); MOVE(player, MOVE_CELEBRATE, WITH_RNG(RNG_FROZEN, 0));} + TURN { MOVE(opponent, MOVE_SWITCHEROO);} + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet's Lum Berry normalized its status!"); + } THEN { + EXPECT_EQ(player->status1, STATUS1_NONE); + EXPECT(player->volatiles.confusionTurns == 0); + } +} +#endif diff --git a/test/battle/hold_effect/custap_berry.c b/test/battle/hold_effect/custap_berry.c index 4774b9243938..446733cd08d8 100644 --- a/test/battle/hold_effect/custap_berry.c +++ b/test/battle/hold_effect/custap_berry.c @@ -49,3 +49,66 @@ SINGLE_BATTLE_TEST("Custap Berry activates even if the opposing mon switches out MESSAGE("Regirock can act faster than normal, thanks to its Custap Berry!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Custap Berry allows the holder to move first in its priority bracket when HP is below 1/2. If the holder has Gluttony (Traits)") +{ + GIVEN { + PLAYER(SPECIES_BELLSPROUT) { Speed(1); MaxHP(160); HP(80); Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_GLUTTONY); Item(ITEM_CUSTAP_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(2); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Bellsprout can act faster than normal, thanks to its Custap Berry!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Custap Berry allows the holder to move first in its priority bracket when HP is below 1/4 (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(1); MaxHP(160); HP(40); Items(ITEM_PECHA_BERRY, ITEM_CUSTAP_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(2); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet can act faster than normal, thanks to its Custap Berry!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +SINGLE_BATTLE_TEST("Custap Berry allows the holder to move first in its priority bracket when HP is below 1/2. If the holder has Gluttony (Multi)") +{ + GIVEN { + PLAYER(SPECIES_BELLSPROUT) { Speed(1); MaxHP(160); HP(80); Ability(ABILITY_GLUTTONY); Items(ITEM_PECHA_BERRY, ITEM_CUSTAP_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(2); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Bellsprout can act faster than normal, thanks to its Custap Berry!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +SINGLE_BATTLE_TEST("Custap Berry activates even if the opposing mon switches out (Multi)") +{ + GIVEN { + PLAYER(SPECIES_REGIROCK) { HP(1); Items(ITEM_PECHA_BERRY, ITEM_CUSTAP_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Regirock can act faster than normal, thanks to its Custap Berry!"); + } +} +#endif diff --git a/test/battle/hold_effect/defense_up.c b/test/battle/hold_effect/defense_up.c index a29f75b1e53e..c5a505cab776 100644 --- a/test/battle/hold_effect/defense_up.c +++ b/test/battle/hold_effect/defense_up.c @@ -68,3 +68,100 @@ SINGLE_BATTLE_TEST("Ganlon Berry raises Defense by one stage when HP drops to 1/ EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 2); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Ganlon Berry raises Defense by one stage when HP drops to 1/2 or below if holder has Gluttony (Traits)") +{ + GIVEN { + PLAYER(SPECIES_BELLSPROUT) { MaxHP(80); HP(80); Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_GLUTTONY); Item(ITEM_GANLON_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Ganlon Berry, the Defense of Bellsprout rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Ganlon Berry raises Defense by one stage when HP drops to 1/4 or below if holder has Ripen (Traits)") +{ + GIVEN { + PLAYER(SPECIES_APPLIN) { MaxHP(160); HP(80); Ability(ABILITY_BULLETPROOF); Innates(ABILITY_RIPEN); Item(ITEM_GANLON_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Ganlon Berry, the Defense of Applin sharply rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 2); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Ganlon Berry raises the holder's Defense by one stage when HP drops to 1/4 or below (Multi)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_DRAGON_RAGE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { MaxHP(160); HP(80); Items(ITEM_PECHA_BERRY, ITEM_GANLON_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + if (move == MOVE_SCRATCH) { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Ganlon Berry, the Defense of Wobbuffet rose!"); + } + } else { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Ganlon Berry, the Defense of Wobbuffet rose!"); + } + } THEN { + if (move == MOVE_DRAGON_RAGE) + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Ganlon Berry raises Defense by one stage when HP drops to 1/2 or below if holder has Gluttony (Multi)") +{ + GIVEN { + PLAYER(SPECIES_BELLSPROUT) { MaxHP(80); HP(80); Ability(ABILITY_GLUTTONY); Items(ITEM_PECHA_BERRY, ITEM_GANLON_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Ganlon Berry, the Defense of Bellsprout rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Ganlon Berry raises Defense by one stage when HP drops to 1/4 or below if holder has Ripen (Multi)") +{ + GIVEN { + PLAYER(SPECIES_APPLIN) { MaxHP(160); HP(80); Ability(ABILITY_RIPEN); Items(ITEM_PECHA_BERRY, ITEM_GANLON_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Ganlon Berry, the Defense of Applin sharply rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 2); + } +} +#endif diff --git a/test/battle/hold_effect/destiny_knot.c b/test/battle/hold_effect/destiny_knot.c index 566ded5735c0..a8bddb1b6c13 100644 --- a/test/battle/hold_effect/destiny_knot.c +++ b/test/battle/hold_effect/destiny_knot.c @@ -78,3 +78,126 @@ SINGLE_BATTLE_TEST("Destiny Knot procs but fails if the target is already infatu MESSAGE("But it failed!"); } } + +SINGLE_BATTLE_TEST("Destiny Knot infatuates back when holder is attacking") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Gender(MON_MALE); Item(ITEM_DESTINY_KNOT);} + OPPONENT(SPECIES_CLEFAIRY) { Gender(MON_FEMALE); Ability(ABILITY_FRIEND_GUARD); Innates(ABILITY_CUTE_CHARM);} + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("The opposing Clefairy fell in love because of the Destiny Knot!"); + } THEN { + EXPECT(opponent->volatiles.infatuation); + } +} + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Destiny Knot procs but fails if the target is oblivious (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Gender(MON_MALE); Ability(ABILITY_SHADOW_TAG); Innates(ABILITY_OBLIVIOUS); } + OPPONENT(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); Item(ITEM_DESTINY_KNOT); } + } WHEN { + TURN { MOVE(player, MOVE_ATTRACT); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ABILITY_POPUP(player, ABILITY_OBLIVIOUS); + } THEN { + EXPECT(!player->volatiles.infatuation); + } +} + + +SINGLE_BATTLE_TEST("Destiny Knot procs but fails if the target is already infatuated (cute charm) (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CLEFAIRY) { Gender(MON_MALE); Ability(ABILITY_FRIEND_GUARD); Innates(ABILITY_CUTE_CHARM);} + OPPONENT(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); Item(ITEM_DESTINY_KNOT); } + } WHEN { + TURN { MOVE(opponent, MOVE_ATTRACT);} + TURN { MOVE(opponent, MOVE_TACKLE);} + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("But it failed!"); + } +} + +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Destiny Knot infatuates back when holder is targeted (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Gender(MON_MALE); } + OPPONENT(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); Items(ITEM_PECHA_BERRY, ITEM_DESTINY_KNOT); } + } WHEN { + TURN { MOVE(player, MOVE_ATTRACT); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("Wobbuffet fell in love because of the Destiny Knot!"); + } THEN { + EXPECT(player->volatiles.infatuation); + } +} + +SINGLE_BATTLE_TEST("Destiny Knot infatuates back when holder is attacking (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Gender(MON_MALE); Items(ITEM_PECHA_BERRY, ITEM_DESTINY_KNOT);} + OPPONENT(SPECIES_CLEFAIRY) { Gender(MON_FEMALE); Ability(ABILITY_CUTE_CHARM);} + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("The opposing Clefairy fell in love because of the Destiny Knot!"); + } THEN { + EXPECT(opponent->volatiles.infatuation); + } +} + + +SINGLE_BATTLE_TEST("Destiny Knot procs but fails if the target is already infatuated (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Gender(MON_MALE); } + OPPONENT(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); Items(ITEM_PECHA_BERRY, ITEM_DESTINY_KNOT); } + } WHEN { + TURN { MOVE(opponent, MOVE_ATTRACT); MOVE(player, MOVE_ATTRACT, WITH_RNG(RNG_INFATUATION, FALSE)); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Destiny Knot procs but fails if the target is oblivious (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Gender(MON_MALE); Ability(ABILITY_OBLIVIOUS); } + OPPONENT(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); Items(ITEM_PECHA_BERRY, ITEM_DESTINY_KNOT); } + } WHEN { + TURN { MOVE(player, MOVE_ATTRACT); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ABILITY_POPUP(player, ABILITY_OBLIVIOUS); + } THEN { + EXPECT(!player->volatiles.infatuation); + } +} + +SINGLE_BATTLE_TEST("Destiny Knot procs but fails if the target is already infatuated (cute charm) (Multi)") +{ + GIVEN { + PLAYER(SPECIES_CLEFAIRY) { Gender(MON_MALE); Ability(ABILITY_CUTE_CHARM);} + OPPONENT(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); Items(ITEM_PECHA_BERRY, ITEM_DESTINY_KNOT); } + } WHEN { + TURN { MOVE(opponent, MOVE_ATTRACT);} + TURN { MOVE(opponent, MOVE_TACKLE);} + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("But it failed!"); + } +} +#endif diff --git a/test/battle/hold_effect/eject_button.c b/test/battle/hold_effect/eject_button.c index 681858605b00..d6fa233b4f05 100644 --- a/test/battle/hold_effect/eject_button.c +++ b/test/battle/hold_effect/eject_button.c @@ -250,3 +250,372 @@ SINGLE_BATTLE_TEST("Eject Button activates after Wandring Spirit") ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Eject Button is not activated by a Sheer Force boosted move (Traits)") +{ + GIVEN { + PLAYER(SPECIES_NIDOKING) { Ability(ABILITY_POISON_POINT); Innates(ABILITY_SHEER_FORCE); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_FLAMETHROWER); + MOVE(opponent, MOVE_SCRATCH); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLAMETHROWER, player); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Wobbuffet is switched out with the Eject Button!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + } +} + +SINGLE_BATTLE_TEST("Eject Button is not blocked by trapping abilities or moves (Traits)") +{ + GIVEN { + PLAYER(SPECIES_DUGTRIO) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_ARENA_TRAP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_SCRATCH); + SEND_OUT(opponent, 1); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Wobbuffet is switched out with the Eject Button!"); + MESSAGE("2 sent out Wobbuffet!"); + } +} + +SINGLE_BATTLE_TEST("Eject Button is not triggered after given to player by Picketpocket (Traits)") +{ + GIVEN { + PLAYER(SPECIES_REGIELEKI) { Item(ITEM_EJECT_BUTTON); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_INNER_FOCUS); Innates(ABILITY_PICKPOCKET); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_SCRATCH); + MOVE(opponent, MOVE_SCRATCH); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Regieleki's Eject Button!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + } +} + +SINGLE_BATTLE_TEST("Eject Button is activated before Emergency Exit (Traits)") +{ + GIVEN { + PLAYER(SPECIES_LATIAS); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); Item(ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_THUNDERBOLT); + SEND_OUT(opponent, 1); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Golisopod is switched out with the Eject Button!"); + } +} + +DOUBLE_BATTLE_TEST("Eject Button activation will not trigger an attack from the incoming mon (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TATSUGIRI) { Speed(10); Ability(ABILITY_COMMANDER); } + PLAYER(SPECIES_WOBBUFFET) { Speed(100); Item(ITEM_EJECT_BUTTON); } + PLAYER(SPECIES_DONDOZO) { Speed(20); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); Item(ITEM_EJECT_PACK); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } + OPPONENT(SPECIES_WYNAUT) { Speed(1); } + } WHEN { + TURN { MOVE(opponentRight, MOVE_MAKE_IT_RAIN); SEND_OUT(playerRight, 2); SEND_OUT(opponentRight, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MAKE_IT_RAIN, opponentRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); + MESSAGE("Wobbuffet is switched out with the Eject Button!"); + ABILITY_POPUP(playerLeft, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + MESSAGE("Wobbuffet is switched out with the Eject Pack!"); + } + } +} + +SINGLE_BATTLE_TEST("Eject Button activates after Wandring Spirit (Traits)") +{ + GIVEN { + PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_YAMASK_GALAR) { Item(ITEM_EJECT_BUTTON); Ability(ABILITY_WANDERING_SPIRIT); } + } WHEN { + TURN { + SWITCH(opponent, 1); + MOVE(player, MOVE_DRAGON_CLAW); + SEND_OUT(opponent, 0); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_CLAW, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Eject Button is not triggered when there is nothing to switch in (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_WOBBUFFET) { HP(0); } + } WHEN { + TURN { + MOVE(player, MOVE_QUICK_ATTACK); + MOVE(opponent, MOVE_SCRATCH); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Wobbuffet is switched out with the Eject Button!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + } +} + +SINGLE_BATTLE_TEST("Eject Button is not activated by a Sheer Force boosted move (Multi)") +{ + GIVEN { + PLAYER(SPECIES_NIDOKING) { Ability(ABILITY_SHEER_FORCE); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_FLAMETHROWER); + MOVE(opponent, MOVE_SCRATCH); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLAMETHROWER, player); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Wobbuffet is switched out with the Eject Button!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + } +} + +SINGLE_BATTLE_TEST("Eject Button will not activate under Substitute (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_RAICHU) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(opponent, MOVE_SUBSTITUTE); + MOVE(player, MOVE_SCRATCH); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, opponent); + MESSAGE("The opposing Raichu put in a substitute!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("The substitute took damage for the opposing Raichu!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Raichu is switched out with the Eject Button!"); + } + } +} + +SINGLE_BATTLE_TEST("Eject Button is not blocked by trapping abilities or moves (Multi)") +{ + GIVEN { + PLAYER(SPECIES_DUGTRIO) { Ability(ABILITY_ARENA_TRAP); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_SCRATCH); + SEND_OUT(opponent, 1); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Wobbuffet is switched out with the Eject Button!"); + MESSAGE("2 sent out Wobbuffet!"); + } +} + +SINGLE_BATTLE_TEST("Eject Button is not triggered after the mon loses Eject Button (Multi)") +{ + GIVEN { + PLAYER(SPECIES_RAICHU); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_KNOCK_OFF); + MOVE(opponent, MOVE_SCRATCH); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Wobbuffet is switched out with the Eject Button!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + } +} + +SINGLE_BATTLE_TEST("Eject Button is not triggered after given to player by Picketpocket (Multi)") +{ + GIVEN { + PLAYER(SPECIES_REGIELEKI) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SNEASEL) { Ability(ABILITY_PICKPOCKET); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_SCRATCH); + MOVE(opponent, MOVE_SCRATCH); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ABILITY_POPUP(opponent, ABILITY_PICKPOCKET); + MESSAGE("The opposing Sneasel stole Regieleki's Eject Button!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + } +} + +SINGLE_BATTLE_TEST("Eject Button has no chance to activate after Dragon Tail (Multi)") +{ + GIVEN { + PLAYER(SPECIES_KOMMO_O); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_CHANSEY); + } WHEN { + TURN { + MOVE(player, MOVE_DRAGON_TAIL); + MOVE(opponent, MOVE_SCRATCH); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + MESSAGE("The opposing Chansey was dragged out!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Chansey is switched out with the Eject Button!"); + } + } +} + +SINGLE_BATTLE_TEST("Eject Button prevents Volt Switch / U-Turn from activating (Multi)") +{ + GIVEN { + PLAYER(SPECIES_MANECTRIC); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { + MOVE(player, MOVE_VOLT_SWITCH); + SEND_OUT(opponent, 1); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_VOLT_SWITCH, player); + MESSAGE("The opposing Wobbuffet is switched out with the Eject Button!"); + } +} + +SINGLE_BATTLE_TEST("Eject Button is activated before Emergency Exit (Multi)") +{ + GIVEN { + PLAYER(SPECIES_LATIAS); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_THUNDERBOLT); + SEND_OUT(opponent, 1); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Golisopod is switched out with the Eject Button!"); + } +} + +SINGLE_BATTLE_TEST("Eject Button is not triggered after High Jump Kick crash damage (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_PROTECT); + MOVE(opponent, MOVE_HIGH_JUMP_KICK); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, player); + MESSAGE("The opposing Wobbuffet kept going and crashed!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Wobbuffet is switched out with the Eject Button!"); + } + } +} + +DOUBLE_BATTLE_TEST("Eject Button activation will not trigger an attack from the incoming mon (Multi)") +{ + GIVEN { + PLAYER(SPECIES_TATSUGIRI) { Speed(10); Ability(ABILITY_COMMANDER); } + PLAYER(SPECIES_WOBBUFFET) { Speed(100); Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + PLAYER(SPECIES_DONDOZO) { Speed(20); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } + OPPONENT(SPECIES_WYNAUT) { Speed(1); } + } WHEN { + TURN { MOVE(opponentRight, MOVE_MAKE_IT_RAIN); SEND_OUT(playerRight, 2); SEND_OUT(opponentRight, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MAKE_IT_RAIN, opponentRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); + MESSAGE("Wobbuffet is switched out with the Eject Button!"); + ABILITY_POPUP(playerLeft, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + MESSAGE("Wobbuffet is switched out with the Eject Pack!"); + } + } +} + +SINGLE_BATTLE_TEST("Eject Button activates after Wandring Spirit (Multi)") +{ + GIVEN { + PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_YAMASK_GALAR) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); Ability(ABILITY_WANDERING_SPIRIT); } + } WHEN { + TURN { + SWITCH(opponent, 1); + MOVE(player, MOVE_DRAGON_CLAW); + SEND_OUT(opponent, 0); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_CLAW, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} +#endif diff --git a/test/battle/hold_effect/eject_pack.c b/test/battle/hold_effect/eject_pack.c index 7e854055dde4..74103c8d1701 100644 --- a/test/battle/hold_effect/eject_pack.c +++ b/test/battle/hold_effect/eject_pack.c @@ -361,3 +361,579 @@ DOUBLE_BATTLE_TEST("Eject Pack will trigger on the fastest mon at the end of the NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Eject Pack does not activate if there are no Pokémon left to battle (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_WOBBUFFET) { HP(0); } + OPPONENT(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + } WHEN { + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet is switched out with the Eject Pack!"); + } + } +} + +SINGLE_BATTLE_TEST("Eject Pack will miss timing to switch out user if Emergency Exit was activated on target (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(133); }; + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_OVERHEAT); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_OVERHEAT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet is switched out with the Eject Pack!"); + } + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } THEN { + EXPECT(player->species == SPECIES_WOBBUFFET); + EXPECT(opponent->species == SPECIES_WYNAUT); + } +} + +SINGLE_BATTLE_TEST("Eject Pack activates once intimidate mon switches in (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + } WHEN { + TURN { SWITCH(opponent, 1); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet is switched out with the Eject Pack!"); + } +} + +DOUBLE_BATTLE_TEST("Eject Pack will not trigger if the conditions are not met (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); }; + PLAYER(SPECIES_RALTS) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_CLEAR_BODY); Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); } + } WHEN { + TURN { SWITCH(opponentLeft, 2); SEND_OUT(playerLeft, 2); } + } SCENE { + + } +} + +DOUBLE_BATTLE_TEST("Eject Pack: Only the fastest Eject Pack will activate after an ability stat drop (Traits)") +{ + u32 speed; + u32 species, ability; + + PARAMETRIZE { species = SPECIES_EKANS; ability = ABILITY_INTIMIDATE; speed = 1; } + PARAMETRIZE { species = SPECIES_EKANS; ability = ABILITY_INTIMIDATE; speed = 11; } + + PARAMETRIZE { species = SPECIES_DIPPLIN; ability = ABILITY_SUPERSWEET_SYRUP; speed = 1; } + PARAMETRIZE { species = SPECIES_DIPPLIN; ability = ABILITY_SUPERSWEET_SYRUP; 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) { Speed(6); Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { + SWITCH(opponentLeft, 2); + if (speed == 11) + SEND_OUT(playerRight, 2); + else + SEND_OUT(playerLeft, 2); + } + } SCENE { + ABILITY_POPUP(opponentLeft, ability); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + if (speed == 11) { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft); + 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 intimidate (switch in after fainting) (Traits)") +{ + 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) { HP(1); Speed(4); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_EKANS) { Speed(6); Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + } WHEN { + TURN { + MOVE(playerLeft, MOVE_POUND, target: opponentLeft); + SEND_OUT(opponentLeft, 2); + if (speed == 11) + SEND_OUT(playerRight, 2); + else + SEND_OUT(playerLeft, 2); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, playerLeft); + 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 intimidate (switch in after 2 mons fainted) (Traits)") +{ + 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(1); } + OPPONENT(SPECIES_WYNAUT) { HP(1); Speed(4); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_WYNAUT) { Speed(4); } + OPPONENT(SPECIES_EKANS) { Speed(6); Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + } WHEN { + TURN { + MOVE(playerLeft, MOVE_HYPER_VOICE); + SEND_OUT(opponentLeft, 3); + SEND_OUT(opponentRight, 2); + if (speed == 11) + SEND_OUT(playerRight, 2); + else + SEND_OUT(playerLeft, 2); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerLeft); + 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); + } + } +} + +SINGLE_BATTLE_TEST("Eject Pack does not activate if mon is switched in due to Eject Button (Traits)") +{ + GIVEN { + PLAYER(SPECIES_DUGTRIO) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_ARENA_TRAP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_PACK); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_BULLDOZE); + SEND_OUT(opponent, 1); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLDOZE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Wobbuffet is switched out with the Eject Button!"); + MESSAGE("2 sent out Wobbuffet!"); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Eject Pack does not cause the new Pokémon to lose HP due to it's held Life Orb (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LIFE_ORB].holdEffect == HOLD_EFFECT_LIFE_ORB); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT) { Items(ITEM_PECHA_BERRY, ITEM_LIFE_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_OVERHEAT); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_OVERHEAT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet is switched out with the Eject Pack!"); + SEND_IN_MESSAGE("Wynaut"); + NOT HP_BAR(player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +SINGLE_BATTLE_TEST("Eject Pack does not activate if there are no Pokémon left to battle (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + PLAYER(SPECIES_WOBBUFFET) { HP(0); } + OPPONENT(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); } + } WHEN { + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet is switched out with the Eject Pack!"); + } + } +} + +SINGLE_BATTLE_TEST("Eject Pack is triggered by self-inflicting stat decreases (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LIFE_ORB].holdEffect == HOLD_EFFECT_LIFE_ORB); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_OVERHEAT); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_OVERHEAT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet is switched out with the Eject Pack!"); + SEND_IN_MESSAGE("Wynaut"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } +} + +SINGLE_BATTLE_TEST("Eject Pack will miss timing to switch out user if Emergency Exit was activated on target (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(133); }; + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_OVERHEAT); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_OVERHEAT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet is switched out with the Eject Pack!"); + } + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } THEN { + EXPECT(player->species == SPECIES_WOBBUFFET); + EXPECT(opponent->species == SPECIES_WYNAUT); + } +} + +SINGLE_BATTLE_TEST("Eject Pack activates once intimidate mon switches in (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); } + } WHEN { + TURN { SWITCH(opponent, 1); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet is switched out with the Eject Pack!"); + } +} + +SINGLE_BATTLE_TEST("Eject Pack will not activate if Parting Shot user can switch out (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_PARTING_SHOT); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet is switched out with the Eject Pack!"); + } + } +} + +DOUBLE_BATTLE_TEST("Eject Pack will not trigger if the conditions are not met (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + PLAYER(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); }; + PLAYER(SPECIES_RALTS) { Ability(ABILITY_TRACE); Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); } + } WHEN { + TURN { SWITCH(opponentLeft, 2); SEND_OUT(playerLeft, 2); } + } SCENE { + + } +} + +SINGLE_BATTLE_TEST("Eject Pack will miss timing to switch out user if Eject Button was activated on target (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(10); Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT) { Speed(10); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_WYNAUT) { Speed(10); } + } WHEN { + TURN { MOVE(player, MOVE_OVERHEAT); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_OVERHEAT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet is switched out with the Eject Pack!"); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT(player->species == SPECIES_WOBBUFFET); + EXPECT(opponent->species == SPECIES_WYNAUT); + } +} + +DOUBLE_BATTLE_TEST("Eject Pack: Only the fastest Eject Pack will activate after an ability stat drop (Multi)") +{ + u32 speed; + u32 species, ability; + + PARAMETRIZE { species = SPECIES_EKANS; ability = ABILITY_INTIMIDATE; speed = 1; } + PARAMETRIZE { species = SPECIES_EKANS; ability = ABILITY_INTIMIDATE; speed = 11; } + + PARAMETRIZE { species = SPECIES_DIPPLIN; ability = ABILITY_SUPERSWEET_SYRUP; speed = 1; } + PARAMETRIZE { species = SPECIES_DIPPLIN; ability = ABILITY_SUPERSWEET_SYRUP; speed = 11; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(10); Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT) { Speed(speed); Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + PLAYER(SPECIES_WOBBUFFET) { Speed(3); } + OPPONENT(SPECIES_WYNAUT) { Speed(4); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(species) { Speed(6); Ability(ability); } + } WHEN { + TURN { + SWITCH(opponentLeft, 2); + if (speed == 11) + SEND_OUT(playerRight, 2); + else + SEND_OUT(playerLeft, 2); + } + } SCENE { + ABILITY_POPUP(opponentLeft, ability); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + if (speed == 11) { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft); + 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 (Multi)") +{ + u32 speed; + + PARAMETRIZE { speed = 1; } + PARAMETRIZE { speed = 11; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(10); Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT) { Speed(speed); Items(ITEM_PECHA_BERRY, 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); + } + } +} + +DOUBLE_BATTLE_TEST("Eject Pack: Only the fastest Eject Pack will activate after intimidate (switch in after fainting) (Multi)") +{ + u32 speed; + + PARAMETRIZE { speed = 1; } + PARAMETRIZE { speed = 11; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(10); Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT) { Speed(speed); Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + PLAYER(SPECIES_WOBBUFFET) { Speed(3); } + OPPONENT(SPECIES_WYNAUT) { HP(1); Speed(4); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_EKANS) { Speed(6); Ability(ABILITY_INTIMIDATE); } + } WHEN { + TURN { + MOVE(playerLeft, MOVE_POUND, target: opponentLeft); + SEND_OUT(opponentLeft, 2); + if (speed == 11) + SEND_OUT(playerRight, 2); + else + SEND_OUT(playerLeft, 2); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, playerLeft); + 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 intimidate (switch in after 2 mons fainted) (Multi)") +{ + u32 speed; + + PARAMETRIZE { speed = 1; } + PARAMETRIZE { speed = 11; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(10); Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT) { Speed(speed); Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + PLAYER(SPECIES_WOBBUFFET) { Speed(1); } + OPPONENT(SPECIES_WYNAUT) { HP(1); Speed(4); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_WYNAUT) { Speed(4); } + OPPONENT(SPECIES_EKANS) { Speed(6); Ability(ABILITY_INTIMIDATE); } + } WHEN { + TURN { + MOVE(playerLeft, MOVE_HYPER_VOICE); + SEND_OUT(opponentLeft, 3); + SEND_OUT(opponentRight, 2); + if (speed == 11) + SEND_OUT(playerRight, 2); + else + SEND_OUT(playerLeft, 2); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerLeft); + 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); + } + } +} + +SINGLE_BATTLE_TEST("Eject Pack does not activate if mon is switched in due to Eject Button (Multi)") +{ + GIVEN { + PLAYER(SPECIES_DUGTRIO) { Ability(ABILITY_ARENA_TRAP); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_BULLDOZE); + SEND_OUT(opponent, 1); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLDOZE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Wobbuffet is switched out with the Eject Button!"); + MESSAGE("2 sent out Wobbuffet!"); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} + +DOUBLE_BATTLE_TEST("Eject Pack will trigger on the fastest mon at the end of the turn (Multi)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_SYRUP_BOMB, MOVE_EFFECT_SYRUP_BOMB) == TRUE); + PLAYER(SPECIES_WOBBUFFET) { Speed(1); Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT) { Speed(10); Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + PLAYER(SPECIES_WOBBUFFET) { Speed(2); } + OPPONENT(SPECIES_WYNAUT) { Speed(4); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(3); } + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_SYRUP_BOMB, target: playerLeft); + MOVE(opponentRight, MOVE_SYRUP_BOMB, target: playerRight); + SEND_OUT(playerRight, 2); + } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SYRUP_BOMB_SPEED_DROP, playerRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SYRUP_BOMB_SPEED_DROP, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + } +} +#endif diff --git a/test/battle/hold_effect/enigma_berry.c b/test/battle/hold_effect/enigma_berry.c index d1f897277c7a..95b8afac1310 100644 --- a/test/battle/hold_effect/enigma_berry.c +++ b/test/battle/hold_effect/enigma_berry.c @@ -74,3 +74,74 @@ DOUBLE_BATTLE_TEST("Enigma Berry doesn't trigger if partner was hit") EXPECT(opponentRight->item == ITEM_ENIGMA_BERRY); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Enigma Berry recovers 25% of HP if hit by super effective move (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT) { MaxHP(100); HP(2); Items(ITEM_PECHA_BERRY, ITEM_ENIGMA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ENDURE); MOVE(opponent, MOVE_BITE); } + } SCENE { + s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ENDURE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wynaut restored its health using its Enigma Berry!"); + HP_BAR(player, damage: -maxHP / 4); + } +} + +SINGLE_BATTLE_TEST("Enigma Berry does nothing if not hit by super effective move (Multi)") +{ + GIVEN { + PLAYER(SPECIES_MIGHTYENA) { MaxHP(100); HP(2); Items(ITEM_PECHA_BERRY, ITEM_ENIGMA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ENDURE); MOVE(opponent, MOVE_BITE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ENDURE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Mightyena restored its health using its Enigma Berry!"); + } + } +} + +SINGLE_BATTLE_TEST("Enigma Berry does nothing if Heal Block applies (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT) { MaxHP(100); HP(2); Items(ITEM_PECHA_BERRY, ITEM_ENIGMA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_HEAL_BLOCK); } + TURN { MOVE(player, MOVE_ENDURE); MOVE(opponent, MOVE_BITE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEAL_BLOCK, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ENDURE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wynaut restored its health using its Enigma Berry!"); + } + } +} + +DOUBLE_BATTLE_TEST("Enigma Berry doesn't trigger if partner was hit (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { Items(ITEM_PECHA_BERRY, ITEM_ENIGMA_BERRY); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + } THEN { + EXPECT(opponentRight->item == ITEM_ENIGMA_BERRY); + } +} +#endif diff --git a/test/battle/hold_effect/flinch.c b/test/battle/hold_effect/flinch.c index 9d396391fcc2..5d4c29781a29 100644 --- a/test/battle/hold_effect/flinch.c +++ b/test/battle/hold_effect/flinch.c @@ -34,3 +34,34 @@ SINGLE_BATTLE_TEST("Kings Rock does not increase flinch chance of a move that ha MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Kings Rock holder will flinch the target 10% of the time (Multi)") +{ + PASSES_RANDOMLY(10, 100, RNG_HOLD_EFFECT_FLINCH); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_KINGS_ROCK); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + } +} + +SINGLE_BATTLE_TEST("Kings Rock does not increase flinch chance of a move that has the flinch effect (Multi)") +{ + PASSES_RANDOMLY(30, 100, RNG_SECONDARY_EFFECT); + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_HEADBUTT, MOVE_EFFECT_FLINCH)); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_KINGS_ROCK); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HEADBUTT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEADBUTT, player); + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + } +} +#endif diff --git a/test/battle/hold_effect/gems.c b/test/battle/hold_effect/gems.c index 8455fe09da40..d0115961f51a 100644 --- a/test/battle/hold_effect/gems.c +++ b/test/battle/hold_effect/gems.c @@ -117,3 +117,135 @@ SINGLE_BATTLE_TEST("Gem is consumed if the move type is changed") ANIMATION(ANIM_TYPE_MOVE, MOVE_FEINT_ATTACK, player); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Gem is consumed if the move type is changed (Traits)") +{ + GIVEN { + PLAYER(SPECIES_DELCATTY) { Ability(ABILITY_CUTE_CHARM); Innates(ABILITY_NORMALIZE); Item(ITEM_NORMAL_GEM); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_FEINT_ATTACK); + } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("The Normal Gem strengthened Delcatty's power!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FEINT_ATTACK, player); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Gem is consumed when it corresponds to the type of a move (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMAL_GEM); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_EMBER); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("The Fire Gem strengthened Wobbuffet's power!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("The Normal Gem strengthened Wobbuffet's power!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + } +} + +SINGLE_BATTLE_TEST("Gem is not consumed when using Struggle (Multi)", s16 damage) +{ + u32 item = 0; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_NORMAL_GEM; } + + GIVEN { + if (item != ITEM_NONE) { + ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_GEMS); + ASSUME(GetItemSecondaryId(item) == GetMoveType(MOVE_STRUGGLE)); + } + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STRUGGLE); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("The Normal Gem strengthened Wobbuffet's power!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Gem boost is only applied once (Multi)") +{ + s16 boostedHit; + s16 normalHit; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMAL_GEM); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("The Normal Gem strengthened Wobbuffet's power!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &boostedHit); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &normalHit); + } THEN { + if (I_GEM_BOOST_POWER >= GEN_6) + EXPECT_MUL_EQ(normalHit, Q_4_12(1.3), boostedHit); + else + EXPECT_MUL_EQ(normalHit, Q_4_12(1.5), boostedHit); + } +} + +SINGLE_BATTLE_TEST("Gem modifier is used for all hits of Multi Hit Moves (Multi)") +{ + s16 firstHit; + s16 secondHit; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMAL_GEM); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_DOUBLE_HIT); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_HIT, player); + HP_BAR(opponent, captureDamage: &firstHit); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_HIT, player); + HP_BAR(opponent, captureDamage: &secondHit); + } THEN { + EXPECT_EQ(firstHit, secondHit); + } +} + +SINGLE_BATTLE_TEST("Gem is consumed if the move type is changed (Multi)") +{ + GIVEN { + PLAYER(SPECIES_DELCATTY) { Ability(ABILITY_NORMALIZE); Items(ITEM_PECHA_BERRY, ITEM_NORMAL_GEM); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_FEINT_ATTACK); + } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("The Normal Gem strengthened Delcatty's power!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FEINT_ATTACK, player); + } +} +#endif diff --git a/test/battle/hold_effect/iron_ball.c b/test/battle/hold_effect/iron_ball.c index 48c87dbccb72..698c9327eccf 100644 --- a/test/battle/hold_effect/iron_ball.c +++ b/test/battle/hold_effect/iron_ball.c @@ -27,3 +27,28 @@ SINGLE_BATTLE_TEST("Ground-type moves do neutral damage to non-grounded Flying t } } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Ground-type moves do neutral damage to non-grounded Flying types holding Iron Ball regardless of other typings (Gen5+) (Multi)") +{ + u32 config; + PARAMETRIZE { config = GEN_4; } + PARAMETRIZE { config = GEN_5; } + GIVEN { + WITH_CONFIG(CONFIG_IRON_BALL, config); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SKARMORY) { Items(ITEM_PECHA_BERRY, ITEM_IRON_BALL); }; + } WHEN { + TURN { MOVE(player, MOVE_EARTHQUAKE); }; + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, player); + if (config >= GEN_5) { + NONE_OF { + MESSAGE("It's super effective!"); + } + } else { + MESSAGE("It's super effective!"); + } + } +} +#endif diff --git a/test/battle/hold_effect/jaboca_berry.c b/test/battle/hold_effect/jaboca_berry.c index a23cd90bb941..6b6dc23d8c6f 100644 --- a/test/battle/hold_effect/jaboca_berry.c +++ b/test/battle/hold_effect/jaboca_berry.c @@ -72,3 +72,71 @@ SINGLE_BATTLE_TEST("Jaboca Berry is triggered even if berry user dies") MESSAGE("Wobbuffet was hurt by the opposing Wobbuffet's Jaboca Berry!"); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Jaboca Berry causes the attacker to lose 1/8 of its max HP if a physical move was used (Multi)") +{ + s16 damage; + u16 move; + + PARAMETRIZE { move = MOVE_SWIFT; } + PARAMETRIZE { move = MOVE_SCRATCH; } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_SWIFT) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_JABOCA_BERRY); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + if (move == MOVE_SCRATCH) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + HP_BAR(player, captureDamage: &damage); + MESSAGE("Wobbuffet was hurt by the opposing Wobbuffet's Jaboca Berry!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("Wobbuffet was hurt by the opposing Wobbuffet's Jaboca Berry!"); + } + } + } THEN { + if (move == MOVE_SCRATCH) + EXPECT_EQ(player->maxHP / 8, damage); + } +} + +SINGLE_BATTLE_TEST("Jaboca Berry triggers before Bug Bite can steal it (Multi)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_BUG_BITE) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_JABOCA_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_BUG_BITE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BUG_BITE, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + HP_BAR(player); + MESSAGE("Wynaut was hurt by the opposing Wobbuffet's Jaboca Berry!"); + NOT MESSAGE("Wynaut stole and ate the opposing Wobbuffet's Jaboca Berry!"); + } +} + +SINGLE_BATTLE_TEST("Jaboca Berry is triggered even if berry user dies (Multi)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_SWIFT) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Items(ITEM_PECHA_BERRY, ITEM_JABOCA_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the opposing Wobbuffet's Jaboca Berry!"); + } +} +#endif diff --git a/test/battle/hold_effect/kee_berry.c b/test/battle/hold_effect/kee_berry.c index d37a132be494..30e64a6e00fe 100644 --- a/test/battle/hold_effect/kee_berry.c +++ b/test/battle/hold_effect/kee_berry.c @@ -104,4 +104,140 @@ SINGLE_BATTLE_TEST("Kee Berry doesn't trigger if the move was boosted by Sheer F } THEN { EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); } -} \ No newline at end of file +} + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Kee Berry raises the holder's Defense by two stages with Ripen when hit by a physical move (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_APPLIN) { Item(ITEM_KEE_BERRY); Ability(ABILITY_CUTE_CHARM); Innates(ABILITY_RIPEN); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("Using Kee Berry, the Defense of the opposing Applin sharply rose!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Kee Berry doesn't trigger if the move was boosted by Sheer Force (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_KEE_BERRY); } + OPPONENT(SPECIES_NIDOKING) { Ability(ABILITY_RIVALRY); Innates(ABILITY_SHEER_FORCE); } + } WHEN { + TURN { MOVE(opponent, MOVE_EMBER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); + HP_BAR(player); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Kee Berry raises the holder's Defense by one stage when hit by a physical move (Multi)") +{ + u16 move; + + PARAMETRIZE { move = MOVE_SWIFT; } + PARAMETRIZE { move = MOVE_SCRATCH; } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_SWIFT) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_KEE_BERRY); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + if (move == MOVE_SCRATCH) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("Using Kee Berry, the Defense of the opposing Wobbuffet rose!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("Using Kee Berry, the Defense of the opposing Wobbuffet rose!"); + } + } + } THEN { + if (move == MOVE_SCRATCH) + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Kee Berry raises the holder's Defense by two stages with Ripen when hit by a physical move (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_APPLIN) { Items(ITEM_PECHA_BERRY, ITEM_KEE_BERRY); Ability(ABILITY_RIPEN); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("Using Kee Berry, the Defense of the opposing Applin sharply rose!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Kee Berry doesn't trigger if the item hold user used a physical move (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_KEE_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Kee Berry, the Defense of Wobbuffet rose!"); + } + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Kee Berry doesn't trigger if partner was hit (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { Items(ITEM_PECHA_BERRY, ITEM_KEE_BERRY); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + } THEN { + EXPECT(opponentRight->item == ITEM_KEE_BERRY); + } +} + +SINGLE_BATTLE_TEST("Kee Berry doesn't trigger if the move was boosted by Sheer Force (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_KEE_BERRY); } + OPPONENT(SPECIES_NIDOKING) { Ability(ABILITY_SHEER_FORCE); } + } WHEN { + TURN { MOVE(opponent, MOVE_EMBER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); + HP_BAR(player); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} +#endif diff --git a/test/battle/hold_effect/lagging_tail.c b/test/battle/hold_effect/lagging_tail.c index 13312034e1ae..eb681fb54204 100644 --- a/test/battle/hold_effect/lagging_tail.c +++ b/test/battle/hold_effect/lagging_tail.c @@ -29,3 +29,29 @@ DOUBLE_BATTLE_TEST("Lagging Tail priority bracket will not change if the item is ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft); // Now last because of Tricking Lagging Tail onto itself } } + +#if MAX_MON_ITEMS > 1 +DOUBLE_BATTLE_TEST("Lagging Tail priority bracket will not change if the item is removed is changed mid-turn (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(100); Items(ITEM_PECHA_BERRY, ITEM_LAGGING_TAIL); } + PLAYER(SPECIES_WYNAUT) { Speed(10); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(30); Items(ITEM_PECHA_BERRY, ITEM_BERRY_JUICE); } + OPPONENT(SPECIES_WYNAUT) { Speed(20); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_TRICK, target: playerLeft); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft); + + // Turn 2 + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft); // Now last because of Tricking Lagging Tail onto itself + } +} +#endif diff --git a/test/battle/hold_effect/leek.c b/test/battle/hold_effect/leek.c index 025686485c91..afaf24bbdd82 100644 --- a/test/battle/hold_effect/leek.c +++ b/test/battle/hold_effect/leek.c @@ -34,3 +34,39 @@ SINGLE_BATTLE_TEST("Leek increases critical hit ratio by 2 stages for the Farfet MESSAGE("A critical hit!"); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Leek increases critical hit ratio by 2 stages for the Farfetch'd Family (Multi)") +{ + u32 species, genConfig, passes, trials; + + PARAMETRIZE { genConfig = GEN_1; passes = 15; trials = 16; species = SPECIES_FARFETCHD; } // ~93.8% with Farfetch'd's base speed + PARAMETRIZE { genConfig = GEN_1; passes = 27; trials = 32; species = SPECIES_FARFETCHD_GALAR; } // ~84.4% with Galarian Farfetch'd's base speed + PARAMETRIZE { genConfig = GEN_1; passes = 1; trials = 1; species = SPECIES_SIRFETCHD; } // 100% with Sirfetch'd's base speed + for (u32 j = GEN_2; j <= GEN_5; j++) { + PARAMETRIZE { genConfig = j; passes = 1; trials = 4; species = SPECIES_FARFETCHD; } // 25% + PARAMETRIZE { genConfig = j; passes = 1; trials = 4; species = SPECIES_FARFETCHD_GALAR; } // 25% + PARAMETRIZE { genConfig = j; passes = 1; trials = 4; species = SPECIES_SIRFETCHD; } // 25% + } + for (u32 j = GEN_6; j <= GEN_9; j++) { + PARAMETRIZE { genConfig = j; passes = 1; trials = 2; species = SPECIES_FARFETCHD; } // 50% + PARAMETRIZE { genConfig = j; passes = 1; trials = 2; species = SPECIES_FARFETCHD_GALAR; } // 50% + PARAMETRIZE { genConfig = j; passes = 1; trials = 2; species = SPECIES_SIRFETCHD; } // 50% + } + PASSES_RANDOMLY(passes, trials, RNG_CRITICAL_HIT); + GIVEN { + WITH_CONFIG(CONFIG_CRIT_CHANCE, genConfig); + ASSUME(GetSpeciesBaseSpeed(SPECIES_FARFETCHD) == 60); + ASSUME(GetSpeciesBaseSpeed(SPECIES_FARFETCHD_GALAR) == 55); + ASSUME(GetSpeciesBaseSpeed(SPECIES_SIRFETCHD) == 65); + ASSUME(gItemsInfo[ITEM_LEEK].holdEffect == HOLD_EFFECT_LEEK); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Items(ITEM_PECHA_BERRY, ITEM_LEEK); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + MESSAGE("A critical hit!"); + } +} +#endif diff --git a/test/battle/hold_effect/leftovers.c b/test/battle/hold_effect/leftovers.c index 4ba6d7b97b71..b27d9063d4a1 100644 --- a/test/battle/hold_effect/leftovers.c +++ b/test/battle/hold_effect/leftovers.c @@ -52,3 +52,52 @@ SINGLE_BATTLE_TEST("Leftovers does nothing if Heal Block applies") } } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Leftovers recovers 1/16th HP at end of turn (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { MaxHP(100); HP(1); Items(ITEM_PECHA_BERRY, ITEM_LEFTOVERS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN {} + } SCENE { + s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet restored a little HP using its Leftovers!"); + HP_BAR(player, damage: -maxHP / 16); + } +} + +SINGLE_BATTLE_TEST("Leftovers does nothing if max HP (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LEFTOVERS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN {} + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet restored a little HP using its Leftovers!"); + HP_BAR(player); + } + } +} + +SINGLE_BATTLE_TEST("Leftovers does nothing if Heal Block applies (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { MaxHP(100); HP(1); Items(ITEM_PECHA_BERRY, ITEM_LEFTOVERS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_HEAL_BLOCK); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet restored a little HP using its Leftovers!"); + HP_BAR(player); + } + } +} +#endif diff --git a/test/battle/hold_effect/life_orb.c b/test/battle/hold_effect/life_orb.c index 39891ca53bde..3e5a4f585a50 100644 --- a/test/battle/hold_effect/life_orb.c +++ b/test/battle/hold_effect/life_orb.c @@ -157,3 +157,176 @@ SINGLE_BATTLE_TEST("Life Orb does not activate on a charge turn") HP_BAR(player); // Lief Orb } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Life Orb does not activate if move was absorbed by target (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LIFE_ORB); } + OPPONENT(SPECIES_RAICHU) { Ability(ABILITY_STATIC); Innates(ABILITY_LIGHTNING_ROD); } + } WHEN { + TURN { MOVE(player, MOVE_SHOCK_WAVE); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHOCK_WAVE, player); + HP_BAR(opponent); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the Life Orb!"); + } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Life Orb activates when users attack is succesful (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LIFE_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_POUND); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, player); + HP_BAR(opponent); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the Life Orb!"); + } +} + +SINGLE_BATTLE_TEST("Life Orb activates if it hits a Substitute (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LIFE_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the Life Orb!"); + } +} + +SINGLE_BATTLE_TEST("Life Orb does not activate if using status move on a Substitute (Multi)") +{ + GIVEN { + ASSUME(MoveIgnoresSubstitute(MOVE_GROWL)); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LIFE_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_GROWL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, player); + NONE_OF { + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the Life Orb!"); + } + } +} + +SINGLE_BATTLE_TEST("Life Orb does not activate if using a status move (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LIFE_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_GROWL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, player); + NONE_OF { + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the Life Orb!"); + } + } +} + +SINGLE_BATTLE_TEST("Life Orb doesn't cause any HP loss if user is unable to attack (Multi)") +{ + PASSES_RANDOMLY(25, 100, RNG_PARALYSIS); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LIFE_ORB); Status1(STATUS1_PARALYSIS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_POUND); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + HP_BAR(opponent); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the Life Orb!"); + } + } +} + +SINGLE_BATTLE_TEST("Life Orb does not activate if on a confusion hit (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LIFE_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_CONFUSE_RAY); MOVE(player, MOVE_POUND, WITH_RNG(RNG_CONFUSION, TRUE)); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, opponent); + HP_BAR(player); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, player); + HP_BAR(opponent); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the Life Orb!"); + } + } +} + +SINGLE_BATTLE_TEST("Life Orb does not activate if move was absorbed by target (Multi)") +{ + GIVEN { + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, GEN_5); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LIFE_ORB); } + OPPONENT(SPECIES_RAICHU) { Ability(ABILITY_LIGHTNING_ROD); } + } WHEN { + TURN { MOVE(player, MOVE_SHOCK_WAVE); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHOCK_WAVE, player); + HP_BAR(opponent); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the Life Orb!"); + } + } +} + +SINGLE_BATTLE_TEST("Life Orb activates if move connected but no damage was dealt (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LIFE_ORB); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_FALSE_SWIPE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FALSE_SWIPE, player); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the Life Orb!"); + } +} + +SINGLE_BATTLE_TEST("Life Orb does not activate on a charge turn (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LIFE_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FLY); } + TURN { SKIP_TURN(player); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLY, player); + NONE_OF { + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the Life Orb!"); + } + HP_BAR(opponent); + HP_BAR(player); // Lief Orb + } +} +#endif diff --git a/test/battle/hold_effect/light_ball.c b/test/battle/hold_effect/light_ball.c index c35dd75343df..2d9d62ac949b 100644 --- a/test/battle/hold_effect/light_ball.c +++ b/test/battle/hold_effect/light_ball.c @@ -97,3 +97,77 @@ SINGLE_BATTLE_TEST("Light Ball doubles Pikachu's Attack (Gen4+)", s16 damage) } } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Light Ball doubles Pikachu's Special Attack (Multi)", s16 damage) +{ + u32 species = 0, item = 0; + + for (u32 j = 0; j < ARRAY_COUNT(speciesToCheck); j++) { + PARAMETRIZE { item = ITEM_NONE; species = speciesToCheck[j]; } + PARAMETRIZE { item = ITEM_LIGHT_BALL; species = speciesToCheck[j]; } + } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_THUNDERSHOCK) == DAMAGE_CATEGORY_SPECIAL); + if (species == SPECIES_PIKACHU_GMAX) { + PLAYER(SPECIES_PIKACHU) { Items(ITEM_PECHA_BERRY, item); GigantamaxFactor(TRUE); } + } else { + PLAYER(species) { Items(ITEM_PECHA_BERRY, item); } + } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (species == SPECIES_PIKACHU_GMAX) { + TURN { MOVE(player, MOVE_THUNDERSHOCK, gimmick: GIMMICK_DYNAMAX); } + } else { + TURN { MOVE(player, MOVE_THUNDERSHOCK); } + } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (i == 1) { // First check to avoid boosting other species + EXPECT_EQ(results[i - 1].damage, results[i].damage); + } else if (i % 2 == 1) { // Every 2nd test afterwards + EXPECT_MUL_EQ(results[i - 1].damage, Q_4_12(2.0), results[i].damage); + } + } +} + +SINGLE_BATTLE_TEST("Light Ball doubles Pikachu's Attack (Gen4+) (Multi)", s16 damage) +{ + u32 species = 0, item = 0, config = 0; + + for (u32 j = 0; j < ARRAY_COUNT(speciesToCheck); j++) { + PARAMETRIZE { item = ITEM_NONE; config = GEN_3; species = speciesToCheck[j]; } + PARAMETRIZE { item = ITEM_LIGHT_BALL; config = GEN_3; species = speciesToCheck[j]; } + PARAMETRIZE { item = ITEM_LIGHT_BALL; config = GEN_4; species = speciesToCheck[j]; } + } + + GIVEN { + WITH_CONFIG(CONFIG_LIGHT_BALL_ATTACK_BOOST, config); + ASSUME(GetMoveCategory(MOVE_SPARK) == DAMAGE_CATEGORY_PHYSICAL); + if (species == SPECIES_PIKACHU_GMAX) { + PLAYER(SPECIES_PIKACHU) { Items(ITEM_PECHA_BERRY, item); GigantamaxFactor(TRUE); } + } else { + PLAYER(species) { Items(ITEM_PECHA_BERRY, item); } + } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (species == SPECIES_PIKACHU_GMAX) { + TURN { MOVE(player, MOVE_SPARK, gimmick: GIMMICK_DYNAMAX); } + } else { + TURN { MOVE(player, MOVE_SPARK); } + } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (i == 2) { // First check to avoid boosting other species + EXPECT_EQ(results[i - 2].damage, results[i].damage); // No item vs Light Ball + EXPECT_EQ(results[i - 1].damage, results[i].damage); // Gen 3 vs Gen 4 + } else if (i % 3 == 2) { // Every 3rd test afterwards + EXPECT_MUL_EQ(results[i - 2].damage, Q_4_12(2.0), results[i].damage); // No item vs Light Ball + EXPECT_MUL_EQ(results[i - 1].damage, Q_4_12(2.0), results[i].damage); // Gen 3 vs Gen 4 + } + } +} +#endif diff --git a/test/battle/hold_effect/lucky_punch.c b/test/battle/hold_effect/lucky_punch.c index bbc379d9772e..c9ae8cc11d73 100644 --- a/test/battle/hold_effect/lucky_punch.c +++ b/test/battle/hold_effect/lucky_punch.c @@ -23,3 +23,28 @@ SINGLE_BATTLE_TEST("Lucky Punch increases critical hit ratio by 2 stages for Cha MESSAGE("A critical hit!"); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Lucky Punch increases critical hit ratio by 2 stages for Chansey (Multi)") +{ + u32 genConfig, passes, trials; + PARAMETRIZE { genConfig = GEN_1; passes = 25; trials = 32; } // ~78.1% with Chansey's base speed + for (u32 j = GEN_2; j <= GEN_5; j++) + PARAMETRIZE { genConfig = j; passes = 1; trials = 4; } // 25% + for (u32 j = GEN_6; j <= GEN_9; j++) + PARAMETRIZE { genConfig = j; passes = 1; trials = 2; } // 50% + PASSES_RANDOMLY(passes, trials, RNG_CRITICAL_HIT); + GIVEN { + WITH_CONFIG(CONFIG_CRIT_CHANCE, genConfig); + ASSUME(gItemsInfo[ITEM_LUCKY_PUNCH].holdEffect == HOLD_EFFECT_LUCKY_PUNCH); + ASSUME(GetSpeciesBaseSpeed(SPECIES_CHANSEY) == 50); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CHANSEY) { Items(ITEM_PECHA_BERRY, ITEM_LUCKY_PUNCH); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + MESSAGE("A critical hit!"); + } +} +#endif diff --git a/test/battle/hold_effect/maranga_berry.c b/test/battle/hold_effect/maranga_berry.c index 785e040ca4c0..d71ca55b778a 100644 --- a/test/battle/hold_effect/maranga_berry.c +++ b/test/battle/hold_effect/maranga_berry.c @@ -105,3 +105,141 @@ SINGLE_BATTLE_TEST("Maranga Berry doesn't trigger if the move was boosted by She EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Maranga Berry raises the holder's Sp. Def by two stages with Ripen when hit by a special move (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_SWIFT) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_APPLIN) { Item(ITEM_MARANGA_BERRY); Ability(ABILITY_BULLETPROOF); Innates(ABILITY_RIPEN); } + } WHEN { + TURN { MOVE(player, MOVE_SWIFT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWIFT, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("Using Maranga Berry, the Sp. Def of the opposing Applin sharply rose!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Maranga Berry doesn't trigger if the move was boosted by Sheer Force (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MARANGA_BERRY); } + OPPONENT(SPECIES_NIDOKING) { Ability(ABILITY_POISON_POINT); Innates(ABILITY_SHEER_FORCE); } + } WHEN { + TURN { MOVE(opponent, MOVE_FIRE_PUNCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PUNCH, opponent); + HP_BAR(player); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Maranga Berry raises the holder's Sp. Def by one stage when hit by a special move (Multi)") +{ + u16 move = MOVE_NONE; + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_SWIFT; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMoveCategory(MOVE_SWIFT) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_MARANGA_BERRY); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + if (move == MOVE_SWIFT) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("Using Maranga Berry, the Sp. Def of the opposing Wobbuffet rose!"); + } + else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("Using Maranga Berry, the Sp. Def of the opposing Wobbuffet rose!"); + } + } + } THEN { + if (move == MOVE_SWIFT) + EXPECT_EQ(opponent->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Maranga Berry raises the holder's Sp. Def by two stages with Ripen when hit by a special move (Multi)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_SWIFT) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_APPLIN) { Items(ITEM_PECHA_BERRY, ITEM_MARANGA_BERRY); Ability(ABILITY_RIPEN); } + } WHEN { + TURN { MOVE(player, MOVE_SWIFT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWIFT, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("Using Maranga Berry, the Sp. Def of the opposing Applin sharply rose!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Maranga Berry doesn't trigger if the item hold user used a special move (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_MARANGA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SWIFT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWIFT, player); + HP_BAR(opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Maranga Berry, the Sp. Def of Applin sharply rose!"); + } + } THEN { + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Maranga Berry doesn't trigger if partner was hit (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { Items(ITEM_PECHA_BERRY, ITEM_MARANGA_BERRY); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + } THEN { + EXPECT(opponentRight->item == ITEM_MARANGA_BERRY); + } +} + +SINGLE_BATTLE_TEST("Maranga Berry doesn't trigger if the move was boosted by Sheer Force (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_MARANGA_BERRY); } + OPPONENT(SPECIES_NIDOKING) { Ability(ABILITY_SHEER_FORCE); } + } WHEN { + TURN { MOVE(opponent, MOVE_FIRE_PUNCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PUNCH, opponent); + HP_BAR(player); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} +#endif diff --git a/test/battle/hold_effect/metronome.c b/test/battle/hold_effect/metronome.c index 8e9e07d9ff12..a93e120169f8 100644 --- a/test/battle/hold_effect/metronome.c +++ b/test/battle/hold_effect/metronome.c @@ -152,3 +152,140 @@ SINGLE_BATTLE_TEST("Metronome Item doesn't increase damage per hit of multi-hit EXPECT_EQ(damage[0], damage[1]); // Do not get the bonus while still inside the first turn } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Metronome Item gradually boosts power of consecutively used moves by 20%, up to 100% (Multi)") +{ + s16 damage[METRONOME_TURNS]; + u32 j; + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_METRONOME); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + for (j = 0; j < METRONOME_TURNS; ++j) { + TURN { MOVE(player, MOVE_SCRATCH); } + } + } SCENE { + for (j = 0; j < METRONOME_TURNS; ++j) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &damage[j]); + } + } THEN { + for (j = 0; j < METRONOME_TURNS; ++j) { + EXPECT_MUL_EQ(damage[0], MetronomeMultipliers[j], damage[j]); + } + } +} + +SINGLE_BATTLE_TEST("Metronome Item's boost is reset if the attacker uses a different move (Multi)") +{ + s16 damage[2]; + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_METRONOME); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_QUICK_ATTACK); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &damage[0]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} + +SINGLE_BATTLE_TEST("Metronome Item's boost is reset if the move fails (Multi)") +{ + s16 damage[2]; + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_METRONOME); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(opponent, MOVE_PROTECT); MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &damage[0]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} + +SINGLE_BATTLE_TEST("Metronome Item counts called moves instead of the calling move (Multi)") +{ + s16 damage[2]; + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_METRONOME); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_METRONOME, WITH_RNG(RNG_METRONOME, MOVE_SCRATCH)); } + TURN { MOVE(player, MOVE_METRONOME, WITH_RNG(RNG_METRONOME, MOVE_SCRATCH)); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &damage[0]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[0], UQ_4_12(1.2), damage[1]); + } +} + +SINGLE_BATTLE_TEST("Metronome Item counts charging turn of moves for its attacking turn (Multi)", s16 damage) +{ + u32 item; + + PARAMETRIZE {item = ITEM_NONE; } + PARAMETRIZE {item = ITEM_METRONOME; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_SOLAR_BEAM) == EFFECT_SOLAR_BEAM); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SOLAR_BEAM); } + TURN { SKIP_TURN(player); } + } SCENE { + MESSAGE("Wobbuffet used Solar Beam!"); + MESSAGE("Wobbuffet absorbed light!"); + MESSAGE("The opposing Wobbuffet used Celebrate!"); + MESSAGE("Congratulations, 1!"); + MESSAGE("Wobbuffet used Solar Beam!"); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(1.2), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Metronome Item doesn't increase damage per hit of multi-hit moves (Multi)") +{ + s16 damage[3]; + GIVEN { + ASSUME(GetMoveEffect(MOVE_FURY_ATTACK) == EFFECT_MULTI_HIT); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_METRONOME); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FURY_ATTACK); } + TURN { MOVE(player, MOVE_FURY_ATTACK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_ATTACK, player); + HP_BAR(opponent, captureDamage: &damage[0]); + HP_BAR(opponent, captureDamage: &damage[1]); + MESSAGE("The Pokémon was hit 5 time(s)!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_ATTACK, player); + HP_BAR(opponent, captureDamage: &damage[2]); + } THEN { + EXPECT_MUL_EQ(damage[0], UQ_4_12(1.2), damage[2]); // Got bonus once for the second turn + EXPECT_EQ(damage[0], damage[1]); // Do not get the bonus while still inside the first turn + } +} +#endif diff --git a/test/battle/hold_effect/micle_berry.c b/test/battle/hold_effect/micle_berry.c index 24d3ef1cd61f..6ba874617fa9 100644 --- a/test/battle/hold_effect/micle_berry.c +++ b/test/battle/hold_effect/micle_berry.c @@ -97,3 +97,111 @@ SINGLE_BATTLE_TEST("Micle Berry increases the accuracy of the next used move the ANIMATION(ANIM_TYPE_MOVE, MOVE_ROCK_SLIDE, player); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Micle Berry raises the holder's accuracy by 1.2 when HP drops to 1/2 or below (Traits)") +{ + GIVEN { + PLAYER(SPECIES_BELLSPROUT) { MaxHP(80); HP(80); Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_GLUTTONY); Item(ITEM_MICLE_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Bellsprout boosted the accuracy of its next move using Micle Berry!"); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Micle Berry raises the holder's accuracy by 1.2 when HP drops to 1/4 or below (Multi)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_DRAGON_RAGE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { MaxHP(160); HP(80); Items(ITEM_PECHA_BERRY, ITEM_MICLE_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + if (move == MOVE_SCRATCH) { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet boosted the accuracy of its next move using Micle Berry!"); + } + } else { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet boosted the accuracy of its next move using Micle Berry!"); + } + } +} + +SINGLE_BATTLE_TEST("Micle Berry raises the holder's accuracy by 1.2 when HP drops to 1/2 or below (Multi)") +{ + GIVEN { + PLAYER(SPECIES_BELLSPROUT) { MaxHP(80); HP(80); Ability(ABILITY_GLUTTONY); Items(ITEM_PECHA_BERRY, ITEM_MICLE_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Bellsprout boosted the accuracy of its next move using Micle Berry!"); + } +} + +SINGLE_BATTLE_TEST("Micle Berry raises the holder's accuracy by 1.2") +{ + PASSES_RANDOMLY(24, 25, RNG_ACCURACY); + GIVEN { + ASSUME(GetMoveAccuracy(MOVE_SUBMISSION) == 80); + PLAYER(SPECIES_WOBBUFFET) { MaxHP(160); HP(80); Items(ITEM_PECHA_BERRY, ITEM_MICLE_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); MOVE(player, MOVE_SUBMISSION); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet boosted the accuracy of its next move using Micle Berry!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBMISSION, player); + } +} + +SINGLE_BATTLE_TEST("Micle Berry increases the accuracy of the next used move across turns (Multi)") +{ + GIVEN { + ASSUME(GetMoveAccuracy(MOVE_ROCK_SLIDE) == 90); + PASSES_RANDOMLY(100, 100, RNG_ACCURACY); + PLAYER(SPECIES_WOBBUFFET) { MaxHP(100); HP(26); Items(ITEM_PECHA_BERRY, ITEM_MICLE_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_ROCK_SLIDE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROCK_SLIDE, player); + } +} + +SINGLE_BATTLE_TEST("Micle Berry increases the accuracy of the next used move the same turn the berry was triggered (Multi)") +{ + GIVEN { + ASSUME(GetMoveAccuracy(MOVE_ROCK_SLIDE) == 90); + PASSES_RANDOMLY(100, 100, RNG_ACCURACY); + PLAYER(SPECIES_WOBBUFFET) { MaxHP(100); HP(26); Items(ITEM_PECHA_BERRY, ITEM_MICLE_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_ROCK_SLIDE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROCK_SLIDE, player); + } +} +#endif diff --git a/test/battle/hold_effect/mirror_herb.c b/test/battle/hold_effect/mirror_herb.c index def6cdd4291d..d7776a9a06ba 100644 --- a/test/battle/hold_effect/mirror_herb.c +++ b/test/battle/hold_effect/mirror_herb.c @@ -88,3 +88,130 @@ SINGLE_BATTLE_TEST("Mirror Herb copies the boost gained by an ability") EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Mirror Herb does not trigger for Ally's Soul Heart's stat raise (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MIRROR_HERB); } + PLAYER(SPECIES_WYNAUT) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_SOUL_HEART); } // Raises Sp. Atk after fainting am on + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, MOVE_SCRATCH, target:opponentLeft); } + } SCENE { + MESSAGE("Wynaut used Scratch!"); + MESSAGE("The opposing Wobbuffet fainted!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet used its Mirror Herb to mirror its opponent's stat changes!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + } + } + THEN { + EXPECT_EQ(playerRight->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(playerLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Mirror Herb copies the boost gained by an ability (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ZACIAN) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_INTREPID_SWORD); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_MIRROR_HERB); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_INTREPID_SWORD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Mirror Herb copies all of foe's positive stat changes in a turn (Multi)", s16 damage) +{ + u32 item; + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_MIRROR_HERB; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); Items(ITEM_PECHA_BERRY, item); } + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_DANCE); } + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + if (item == ITEM_NONE) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + } + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + EXPECT_EQ(player->statStages[STAT_ATK], opponent->statStages[STAT_ATK]); + EXPECT_EQ(player->statStages[STAT_SPEED], opponent->statStages[STAT_SPEED]); + } +} + +SINGLE_BATTLE_TEST("Mirror Herb copies all of Stuff Cheeks' stat boosts (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LIECHI_BERRY].holdEffect == HOLD_EFFECT_ATTACK_UP); + PLAYER(SPECIES_SKWOVET) { Items(ITEM_PECHA_BERRY, ITEM_LIECHI_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_MIRROR_HERB); } + } WHEN { + TURN { MOVE(player, MOVE_STUFF_CHEEKS); } + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], opponent->statStages[STAT_ATK]); + EXPECT_EQ(player->statStages[STAT_DEF], opponent->statStages[STAT_DEF]); + } +} + +DOUBLE_BATTLE_TEST("Mirror Herb does not trigger for Ally's Soul Heart's stat raise (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_MIRROR_HERB); } + PLAYER(SPECIES_WYNAUT) { Ability(ABILITY_SOUL_HEART); } // Raises Sp. Atk after fainting am on + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, MOVE_SCRATCH, target:opponentLeft); } + } SCENE { + MESSAGE("Wynaut used Scratch!"); + MESSAGE("The opposing Wobbuffet fainted!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet used its Mirror Herb to mirror its opponent's stat changes!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + } + } + THEN { + EXPECT_EQ(playerRight->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(playerLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Mirror Herb copies the boost gained by an ability (Multi)") +{ + GIVEN { + PLAYER(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_MIRROR_HERB); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_INTREPID_SWORD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} +#endif diff --git a/test/battle/hold_effect/ogerpon_mask.c b/test/battle/hold_effect/ogerpon_mask.c index 7dcce5690b35..65d61b0ff4a6 100644 --- a/test/battle/hold_effect/ogerpon_mask.c +++ b/test/battle/hold_effect/ogerpon_mask.c @@ -34,3 +34,29 @@ SINGLE_BATTLE_TEST("Ogerpon Masks increase the base power of moves by 20%", s16 EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.2), results[3].damage); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Ogerpon Masks increase the base power of moves by 20% (Multi)", s16 damage) +{ + u32 species; + u32 item; + PARAMETRIZE { species = SPECIES_OGERPON_TEAL; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_OGERPON_WELLSPRING; item = ITEM_CORNERSTONE_MASK; } + PARAMETRIZE { species = SPECIES_OGERPON_HEARTHFLAME; item = ITEM_WELLSPRING_MASK; } + PARAMETRIZE { species = SPECIES_OGERPON_CORNERSTONE; item = ITEM_HEARTHFLAME_MASK; } + + GIVEN { + ASSUME(GetMovePower(MOVE_SCRATCH) > 0); + PLAYER(species) { Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.2), results[1].damage); + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.2), results[2].damage); + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.2), results[3].damage); + } +} +#endif diff --git a/test/battle/hold_effect/protective_pads.c b/test/battle/hold_effect/protective_pads.c index 6f8d068d06be..501f1852db3f 100644 --- a/test/battle/hold_effect/protective_pads.c +++ b/test/battle/hold_effect/protective_pads.c @@ -110,3 +110,109 @@ SINGLE_BATTLE_TEST("Protective Pads protects from Protect's secondary effects") } } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Protective Pads protected moves still make direct contact (Multi)", s16 damage) +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_KLUTZ; } + PARAMETRIZE { ability = ABILITY_FLUFFY; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_PROTECTIVE_PADS); } + OPPONENT(SPECIES_STUFFUL) { Ability(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet used Scratch!"); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(0.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Protective Pads doesn't reduce tough claws damage (Multi)", s16 damage) +{ + u32 item; + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_PROTECTIVE_PADS; } + GIVEN { + PLAYER(SPECIES_BINACLE) { Ability(ABILITY_TOUGH_CLAWS); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Binacle used Scratch!"); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Protective Pads doesn't invalid unseen fist (Multi)") +{ + GIVEN { + PLAYER(SPECIES_URSHIFU_RAPID_STRIKE) { Ability(ABILITY_UNSEEN_FIST); Items(ITEM_PECHA_BERRY, ITEM_PROTECTIVE_PADS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_PROTECT); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Protective Pads protects from Rocly Helmet Damage (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_PROTECTIVE_PADS); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_ROCKY_HELMET); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + NONE_OF { + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the opposing Wobbuffet's Rocky Helmet!"); + } + } +} + +SINGLE_BATTLE_TEST("Protective Pads protects from Protect's secondary effects (Multi)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SPIKY_SHIELD; } + PARAMETRIZE { move = MOVE_BANEFUL_BUNKER; } + PARAMETRIZE { move = MOVE_BURNING_BULWARK; } + PARAMETRIZE { move = MOVE_KINGS_SHIELD; } + PARAMETRIZE { move = MOVE_SILK_TRAP; } + PARAMETRIZE { move = MOVE_OBSTRUCT; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_PROTECTIVE_PADS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + if (move == MOVE_SPIKY_SHIELD) { + HP_BAR(player); + } else if (move == MOVE_BANEFUL_BUNKER) { + STATUS_ICON(player, STATUS1_POISON); + } else if (move == MOVE_BURNING_BULWARK) { + STATUS_ICON(player, STATUS1_BURN); + } else if (move == MOVE_KINGS_SHIELD) { + MESSAGE("Wobbuffet's Attack fell!"); + } else if (move == MOVE_SILK_TRAP) { + MESSAGE("Wobbuffet's Speed fell!"); + } else if (move == MOVE_OBSTRUCT) { + MESSAGE("Wobbuffet's Defense harshly fell!"); + } + } + } +} +#endif diff --git a/test/battle/hold_effect/quick_claw.c b/test/battle/hold_effect/quick_claw.c index dfbdb245b9f5..bbaf10b3ad3a 100644 --- a/test/battle/hold_effect/quick_claw.c +++ b/test/battle/hold_effect/quick_claw.c @@ -20,3 +20,19 @@ SINGLE_BATTLE_TEST("Quick Claw activates 20% of the time") MESSAGE("The opposing Wobbuffet used Celebrate!"); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Quick Claw activates 20% of the time (Multi)") +{ + PASSES_RANDOMLY(2, 10, RNG_QUICK_CLAW); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(1); Items(ITEM_PECHA_BERRY, ITEM_QUICK_CLAW); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(100); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet used Scratch!"); + MESSAGE("The opposing Wobbuffet used Celebrate!"); + } +} +#endif diff --git a/test/battle/hold_effect/random_stat_up.c b/test/battle/hold_effect/random_stat_up.c index 53a11c6f9f31..a6ff7f0aed5b 100644 --- a/test/battle/hold_effect/random_stat_up.c +++ b/test/battle/hold_effect/random_stat_up.c @@ -104,3 +104,161 @@ SINGLE_BATTLE_TEST("Starf Berry randomly raises the holder's Attack, Defense, Sp EXPECT_EQ(boostedStats, 1); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Starf Berry randomly raises the holder's Attack, Defense, Sp. Atk, Sp. Def, or Speed by two stages when the holder's HP drop to 1/2 or below if it has Gluttony (Traits)") +{ + GIVEN { + PLAYER(SPECIES_MUNCHLAX) { Item(ITEM_STARF_BERRY); HP(201); MaxHP(400); Ability(ABILITY_PICKUP); Innates(ABILITY_GLUTTONY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + int boostedStats = 0; + EXPECT_EQ(player->item, ITEM_NONE); + EXPECT_LE(player->hp * 2, player->maxHP); + for (int stat = STAT_ATK; stat < NUM_STATS; stat++) + { + if (player->statStages[stat] == DEFAULT_STAT_STAGE + 2) + boostedStats++; + else + EXPECT_EQ(player->statStages[stat], DEFAULT_STAT_STAGE); + } + EXPECT_EQ(boostedStats, 1); + } +} + +SINGLE_BATTLE_TEST("Starf Berry randomly raises the holder's Attack, Defense, Sp. Atk, Sp. Def, or Speed by four stages when the holder's HP drop to 1/4 or below if it has Ripen (Traits)") +{ + GIVEN { + PLAYER(SPECIES_FLAPPLE) { Item(ITEM_STARF_BERRY); HP(101); MaxHP(400); Ability(ABILITY_BULLETPROOF); Innates(ABILITY_RIPEN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_RIPEN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + int boostedStats = 0; + EXPECT_EQ(player->item, ITEM_NONE); + EXPECT_LE(player->hp * 4, player->maxHP); + for (int stat = STAT_ATK; stat < NUM_STATS; stat++) + { + if (player->statStages[stat] == DEFAULT_STAT_STAGE + 4) + boostedStats++; + else + EXPECT_EQ(player->statStages[stat], DEFAULT_STAT_STAGE); + } + EXPECT_EQ(boostedStats, 1); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Starf Berry randomly raises the holder's Attack, Defense, Sp. Atk, Sp. Def, or Speed by two stages (Multi)") +{ + PASSES_RANDOMLY(1, 5, RNG_RANDOM_STAT_UP); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); HP(100); MaxHP(400); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Starf Berry, the Attack of Wobbuffet sharply rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Starf Berry randomly raises the holder's Attack, Defense, Sp. Atk, Sp. Def, or Speed by two stages when the holder's HP drop to 1/4 or below (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); HP(101); MaxHP(400); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + int boostedStats = 0; + EXPECT_EQ(player->item, ITEM_NONE); + EXPECT_LE(player->hp * 4, player->maxHP); + for (int stat = STAT_ATK; stat < NUM_STATS; stat++) + { + if (player->statStages[stat] == DEFAULT_STAT_STAGE + 2) + boostedStats++; + else + EXPECT_EQ(player->statStages[stat], DEFAULT_STAT_STAGE); + } + EXPECT_EQ(boostedStats, 1); + } +} + +SINGLE_BATTLE_TEST("Starf Berry randomly raises the holder's Attack, Defense, Sp. Atk, Sp. Def, or Speed by two stages when the holder's HP drop to 1/2 or below if it has Gluttony (Multi)") +{ + GIVEN { + PLAYER(SPECIES_MUNCHLAX) { Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); HP(201); MaxHP(400); Ability(ABILITY_GLUTTONY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + int boostedStats = 0; + EXPECT_EQ(player->item, ITEM_NONE); + EXPECT_LE(player->hp * 2, player->maxHP); + for (int stat = STAT_ATK; stat < NUM_STATS; stat++) + { + if (player->statStages[stat] == DEFAULT_STAT_STAGE + 2) + boostedStats++; + else + EXPECT_EQ(player->statStages[stat], DEFAULT_STAT_STAGE); + } + EXPECT_EQ(boostedStats, 1); + } +} + +SINGLE_BATTLE_TEST("Starf Berry randomly raises the holder's Attack, Defense, Sp. Atk, Sp. Def, or Speed by four stages when the holder's HP drop to 1/4 or below if it has Ripen (Multi)") +{ + GIVEN { + PLAYER(SPECIES_FLAPPLE) { Items(ITEM_PECHA_BERRY, ITEM_STARF_BERRY); HP(101); MaxHP(400); Ability(ABILITY_RIPEN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_RIPEN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + int boostedStats = 0; + EXPECT_EQ(player->item, ITEM_NONE); + EXPECT_LE(player->hp * 4, player->maxHP); + for (int stat = STAT_ATK; stat < NUM_STATS; stat++) + { + if (player->statStages[stat] == DEFAULT_STAT_STAGE + 4) + boostedStats++; + else + EXPECT_EQ(player->statStages[stat], DEFAULT_STAT_STAGE); + } + EXPECT_EQ(boostedStats, 1); + } +} +#endif diff --git a/test/battle/hold_effect/red_card.c b/test/battle/hold_effect/red_card.c index e020b43f591a..45d10cbe5022 100644 --- a/test/battle/hold_effect/red_card.c +++ b/test/battle/hold_effect/red_card.c @@ -539,3 +539,669 @@ SINGLE_BATTLE_TEST("Red Card activates before Eject Pack") } } +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Red Card does not activate if stolen by Magician (Traits)") +{ + u32 item; + bool32 activate; + PARAMETRIZE { item = ITEM_NONE; activate = FALSE; } + PARAMETRIZE { item = ITEM_POTION; activate = TRUE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_FENNEKIN) { Ability(ABILITY_BLAZE); Innates(ABILITY_MAGICIAN); Item(item); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + if (activate) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Fennekin!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Fennekin!"); + } + } + } THEN { + EXPECT(player->item == ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Red Card activates but fails if the attacker has Suction Cups (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_OCTILLERY) { Ability(ABILITY_SNIPER); Innates(ABILITY_SUCTION_CUPS); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_UNOWN); + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); + MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against the opposing Octillery!"); + MESSAGE("The opposing Octillery anchors itself with Suction Cups!"); + NOT MESSAGE("The opposing Unown was dragged out!"); + + // Red Card already consumed so cannot activate. + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentRight); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wynaut!"); + } + } +} + +DOUBLE_BATTLE_TEST("Red Card activates but fails if the attacker has Guard Dog (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_OKIDOGI) { Ability(ABILITY_TOXIC_CHAIN); Innates(ABILITY_GUARD_DOG); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_UNOWN); + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); + MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against the opposing Okidogi!"); + NOT MESSAGE("The opposing Unown was dragged out!"); + + // Red Card already consumed so cannot activate. + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentRight); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wynaut!"); + } + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if attacker's Sheer Force applied (Traits)") +{ + u32 move; + bool32 activate; + PARAMETRIZE { move = MOVE_SCRATCH; activate = TRUE; } + PARAMETRIZE { move = MOVE_STOMP; activate = FALSE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_TAUROS) { Ability(ABILITY_ANGER_POINT); Innates(ABILITY_SHEER_FORCE); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + if (activate) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Tauros!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Tauros!"); + } + } + } +} + +SINGLE_BATTLE_TEST("Red Card prevents Emergency Exit activation when triggered (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_GOLISOPOD) { Item(ITEM_RED_CARD); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUPER_FANG); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Red Card switches the attacker with a random non-fainted replacement (Multi)") +{ + PASSES_RANDOMLY(1, 2, RNG_FORCE_RANDOM_SWITCH); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_CHARMANDER); + OPPONENT(SPECIES_SQUIRTLE) { HP(0); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wobbuffet!"); + MESSAGE("The opposing Bulbasaur was dragged out!"); + } THEN { + EXPECT(player->item == ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Red Card switches the target with a random non-battler, non-fainted replacement (Multi)") +{ + PASSES_RANDOMLY(1, 2, RNG_FORCE_RANDOM_SWITCH); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_CHARMANDER); + OPPONENT(SPECIES_SQUIRTLE) { HP(0); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wobbuffet!"); + MESSAGE("The opposing Bulbasaur was dragged out!"); + } THEN { + EXPECT(playerLeft->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if holder faints (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wobbuffet!"); + } + } THEN { + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if target is behind a Substitute (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wobbuffet!"); + } + } THEN { + EXPECT(player->item == ITEM_RED_CARD); // Not activated, so still has the item. + } +} + +SINGLE_BATTLE_TEST("Red Card activates after the last hit of a multi-hit move (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_DOUBLE_KICK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_KICK, opponent); + HP_BAR(player); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wobbuffet!"); + } THEN { + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if no replacements (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wobbuffet!"); + } + } THEN { + EXPECT(player->item == ITEM_RED_CARD); // Not activated, so still has the item. + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if replacements fainted (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { HP(0); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wobbuffet!"); + } + } THEN { + EXPECT(player->item == ITEM_RED_CARD); // Not activated, so still has the item. + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if knocked off (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_KNOCK_OFF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wobbuffet!"); + } + } THEN { + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if stolen by a move (Multi)") +{ + u32 item; + bool32 activate; + PARAMETRIZE { item = ITEM_NONE; activate = FALSE; } + PARAMETRIZE { item = ITEM_POTION; activate = TRUE; } + ASSUME(GetMoveEffect(MOVE_THIEF) == EFFECT_STEAL_ITEM); + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_THIEF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THIEF, opponent); + if (activate) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wobbuffet!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wobbuffet!"); + } + } + } THEN { + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if stolen by Magician (Multi)") +{ + u32 item; + bool32 activate; + PARAMETRIZE { item = ITEM_NONE; activate = FALSE; } + PARAMETRIZE { item = ITEM_POTION; activate = TRUE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + OPPONENT(SPECIES_FENNEKIN) { Ability(ABILITY_MAGICIAN); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + if (activate) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Fennekin!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Fennekin!"); + } + } + } THEN { + EXPECT(player->item == ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Red Card activates for only the fastest target (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(3); Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + PLAYER(SPECIES_WYNAUT) { Speed(2); Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_WYNAUT) { Speed(4); } + OPPONENT(SPECIES_UNOWN) { Speed(1); } + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_ROCK_SLIDE); + MOVE(opponentRight, MOVE_SCRATCH, target: playerRight); + } + } SCENE { + // Fastest target's Red Card activates. + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROCK_SLIDE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wobbuffet!"); + MESSAGE("The opposing Unown was dragged out!"); + + // Slower target's Red Card still able to activate on other battler. + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); + MESSAGE("Wynaut held up its Red Card against the opposing Wynaut!"); + MESSAGE("The opposing Wobbuffet was dragged out!"); + } THEN { + EXPECT(playerLeft->item == ITEM_NONE); + EXPECT(playerRight->item == ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Red Card activates but fails if the attacker is rooted (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_UNOWN); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_INGRAIN); } + TURN { + MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); + MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wobbuffet!"); + MESSAGE("The opposing Wobbuffet anchored itself with its roots!"); + NOT MESSAGE("The opposing Unown was dragged out!"); + + // Red Card already consumed so cannot activate. + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentRight); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wynaut!"); + } + } +} + +DOUBLE_BATTLE_TEST("Red Card activates but fails if the attacker has Suction Cups (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_OCTILLERY) { Ability(ABILITY_SUCTION_CUPS); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_UNOWN); + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); + MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against the opposing Octillery!"); + MESSAGE("The opposing Octillery anchors itself with Suction Cups!"); + NOT MESSAGE("The opposing Unown was dragged out!"); + + // Red Card already consumed so cannot activate. + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentRight); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wynaut!"); + } + } +} + +DOUBLE_BATTLE_TEST("Red Card activates but fails if the attacker has Guard Dog (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_OKIDOGI) { Ability(ABILITY_GUARD_DOG); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_UNOWN); + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); + MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against the opposing Okidogi!"); + NOT MESSAGE("The opposing Unown was dragged out!"); + + // Red Card already consumed so cannot activate. + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentRight); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wynaut!"); + } + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if switched by Dragon Tail (Multi)") +{ + bool32 hasWynaut, activate; + PARAMETRIZE { hasWynaut = TRUE; activate = FALSE; } + PARAMETRIZE { hasWynaut = FALSE; activate = TRUE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + if (hasWynaut) PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, opponent); + if (activate) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wobbuffet!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wobbuffet!"); + } + } + } +} + +SINGLE_BATTLE_TEST("Red Card activates and overrides U-turn (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_U_TURN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wobbuffet!"); + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if attacker's Sheer Force applied (Multi)") +{ + u32 move; + bool32 activate; + PARAMETRIZE { move = MOVE_SCRATCH; activate = TRUE; } + PARAMETRIZE { move = MOVE_STOMP; activate = FALSE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + OPPONENT(SPECIES_TAUROS) { Ability(ABILITY_SHEER_FORCE); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + if (activate) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Tauros!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Tauros!"); + } + } + } +} + +SINGLE_BATTLE_TEST("Red Card is consumed after dragged out replacement has its Speed lowered by Sticky Web (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_STICKY_WEB) == EFFECT_STICKY_WEB); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + } WHEN { + TURN { MOVE(opponent, MOVE_STICKY_WEB); } + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + // 1st turn Sticky Web + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponent); + // 2nd turn Red Card activation + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Wobbuffet held up its Red Card against Wobbuffet!"); + MESSAGE("Wynaut was dragged out!"); + MESSAGE("Wynaut was caught in a sticky web!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + // 3rd turn, Red Card was consumed, it can't trigger again + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Wobbuffet held up its Red Card against Wynaut!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } + } THEN { + EXPECT(opponent->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Red Card does not cause the dragged out mon to lose hp due to it's held Life Orb (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { Items(ITEM_PECHA_BERRY, ITEM_LIFE_ORB); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Wobbuffet held up its Red Card against Wobbuffet!"); + MESSAGE("Wynaut was dragged out!"); + NOT HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Red Card does not activate if holder is switched in mid-turn (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_ENDURE); MOVE(opponent, MOVE_SCRATCH); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ENDURE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet is switched out with the Eject Button!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wobbuffet!"); + } + } +} + +SINGLE_BATTLE_TEST("Red Card prevents Emergency Exit activation when triggered (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_GOLISOPOD) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUPER_FANG); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + +SINGLE_BATTLE_TEST("Red Card activates and is consumed but fails if the attacker is Dynamaxed (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + } WHEN { + TURN { + MOVE(opponent, MOVE_SCRATCH); + MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_DYNAMAX); + } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Wobbuffet held up its Red Card against Wobbuffet!"); + NOT MESSAGE("Wobbuffet is switched out with the Eject Button!"); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Red Card activates before Eject Pack (Multi)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffectSelf(MOVE_OVERHEAT, MOVE_EFFECT_SP_ATK_MINUS_2) == TRUE); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_OVERHEAT); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_OVERHEAT, player); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet is switched out with the Eject Button!"); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Wobbuffet held up its Red Card against Wobbuffet!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + } +} +#endif diff --git a/test/battle/hold_effect/resist_berry.c b/test/battle/hold_effect/resist_berry.c index b13ca356657d..0188f171415a 100644 --- a/test/battle/hold_effect/resist_berry.c +++ b/test/battle/hold_effect/resist_berry.c @@ -143,3 +143,126 @@ SINGLE_BATTLE_TEST("Weakness berries do not activate if Disguise blocks the dama ANIMATION(ANIM_TYPE_MOVE, MOVE_METAL_CLAW, player); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Weakness berries decrease the base power of moves by half (Multi)", s16 damage) +{ + u32 move = 0, item = 0, defender = 0; + enum Type type = TYPE_NONE; + + for (u32 j = 0; j < ARRAY_COUNT(sMoveItemTable); j++) + { + PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; defender = sMoveItemTable[j][3]; item = ITEM_NONE; } + PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; defender = sMoveItemTable[j][3]; item = sMoveItemTable[j][2]; } + } + + GIVEN { + ASSUME(GetMovePower(move) > 0); + ASSUME(GetMoveType(move) == type); + ASSUME(GetSpeciesType(defender, 0) == GetSpeciesType(defender, 1)); + if (type != TYPE_NORMAL) { + ASSUME(gTypeEffectivenessTable[type][GetSpeciesType(defender, 0)] > UQ_4_12(1.0)); + } + if (item != ITEM_NONE) { + ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_RESIST_BERRY); + ASSUME(GetItemHoldEffectParam(item) == type); + } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(defender) { Items(ITEM_PECHA_BERRY, item); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + if (1 == i % 2) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + for (u32 j = 0; j < ARRAY_COUNT(sMoveItemTable); j++) { + EXPECT_MUL_EQ(results[j*2].damage, Q_4_12(0.5), results[(j*2)+1].damage); + } + } +} + +SINGLE_BATTLE_TEST("Weakness berries do not activate unless a move is super effective (Multi)", s16 damage) +{ + u32 move = 0, item = 0, defender = 0; + enum Type type = TYPE_NONE; + + for (u32 j = 0; j < ARRAY_COUNT(sMoveItemTable); j++) + { + if (TYPE_NORMAL == type) + { + // ITEM_CHILAN_BERRY activates without a weakness + } + else if (TYPE_FAIRY == type) + { + PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; item = sMoveItemTable[j][2]; defender = SPECIES_WOBBUFFET; } + } + else + { + PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; item = sMoveItemTable[j][2]; defender = SPECIES_SABLEYE; } + } + } + + GIVEN { + ASSUME(GetMovePower(move) > 0); + ASSUME(uq4_12_multiply(gTypeEffectivenessTable[type][GetSpeciesType(defender, 0)], + gTypeEffectivenessTable[type][GetSpeciesType(defender, 1)]) <= UQ_4_12(1.0)); + ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_RESIST_BERRY); + ASSUME(GetItemHoldEffectParam(item) == type); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(defender) { Items(ITEM_PECHA_BERRY, item); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} + +SINGLE_BATTLE_TEST("Weakness berries do not decrease the power of Struggle (Multi)", s16 damage) +{ + u32 item = 0; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_CHILAN_BERRY; } + + GIVEN { + if (item != ITEM_NONE) { + ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_RESIST_BERRY); + ASSUME(GetItemHoldEffectParam(item) == TYPE_NORMAL); + } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, item); } + } WHEN { + TURN { MOVE(player, MOVE_STRUGGLE); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The Chilan Berry weakened the damage to the opposing Wobbuffet!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Weakness berries do not activate if Disguise blocks the damage (Multi)") +{ + GIVEN { + ASSUME(GetItemHoldEffect(ITEM_BABIRI_BERRY) == HOLD_EFFECT_RESIST_BERRY); + ASSUME(GetItemHoldEffectParam(ITEM_BABIRI_BERRY) == TYPE_STEEL); + ASSUME(GetMoveType(MOVE_METAL_CLAW) == TYPE_STEEL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MIMIKYU) { Items(ITEM_PECHA_BERRY, ITEM_BABIRI_BERRY); Ability(ABILITY_DISGUISE); } + } WHEN { + TURN { MOVE(player, MOVE_METAL_CLAW); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The Babiri Berry weakened the damage to the opposing Mimikyu!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_METAL_CLAW, player); + } +} +#endif diff --git a/test/battle/hold_effect/restore_hp.c b/test/battle/hold_effect/restore_hp.c index 2441e924495f..c5e2523ad97b 100644 --- a/test/battle/hold_effect/restore_hp.c +++ b/test/battle/hold_effect/restore_hp.c @@ -82,3 +82,87 @@ SINGLE_BATTLE_TEST("Sitrus Berry restores HP immediately after Leech Seed damage HP_BAR(player); } } + +#if MAX_MON_ITEMS > 1 +DOUBLE_BATTLE_TEST("Restore HP Item effects do not miss timing (Multi)") +{ + u16 item; + + PARAMETRIZE { item = ITEM_BERRY_JUICE; } + PARAMETRIZE { item = ITEM_ORAN_BERRY; } + PARAMETRIZE { item = ITEM_SITRUS_BERRY; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffect == HOLD_EFFECT_RESTORE_HP); + ASSUME(gItemsInfo[ITEM_BERRY_JUICE].holdEffect == HOLD_EFFECT_RESTORE_HP); + ASSUME(gItemsInfo[ITEM_SITRUS_BERRY].holdEffect == HOLD_EFFECT_RESTORE_PCT_HP); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { MaxHP(100); HP(51); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_FIRE_PLEDGE, target: opponentRight); MOVE(playerRight, MOVE_GRASS_PLEDGE, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, playerRight); + MESSAGE("A sea of fire enveloped the opposing team!"); + MESSAGE("The opposing Wynaut was hurt by the sea of fire!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + MESSAGE("The opposing Wobbuffet was hurt by the sea of fire!"); + } +} + +DOUBLE_BATTLE_TEST("Restore HP Item effects do not miss timing after a recoil move (Multi)") +{ + u16 item; + + PARAMETRIZE { item = ITEM_BERRY_JUICE; } + PARAMETRIZE { item = ITEM_ORAN_BERRY; } + PARAMETRIZE { item = ITEM_SITRUS_BERRY; } + + GIVEN { + ASSUME(GetMoveRecoil(MOVE_TAKE_DOWN) == 25); + ASSUME(gItemsInfo[ITEM_ORAN_BERRY].holdEffect == HOLD_EFFECT_RESTORE_HP); + ASSUME(gItemsInfo[ITEM_BERRY_JUICE].holdEffect == HOLD_EFFECT_RESTORE_HP); + ASSUME(gItemsInfo[ITEM_SITRUS_BERRY].holdEffect == HOLD_EFFECT_RESTORE_PCT_HP); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { MaxHP(100); HP(51); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_TAKE_DOWN, target: playerLeft); + MOVE(opponentRight, MOVE_CELEBRATE); + MOVE(playerLeft, MOVE_CELEBRATE); + MOVE(playerRight, MOVE_CELEBRATE); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAKE_DOWN, opponentLeft); + HP_BAR(playerLeft); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + } +} + +SINGLE_BATTLE_TEST("Sitrus Berry restores HP immediately after Leech Seed damage (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_LEECH_SEED) == EFFECT_LEECH_SEED); + ASSUME(gItemsInfo[ITEM_SITRUS_BERRY].holdEffect == HOLD_EFFECT_RESTORE_PCT_HP); + PLAYER(SPECIES_WOBBUFFET) { MaxHP(80); HP(41); Items(ITEM_NUGGET, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_LEECH_SEED); } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LEECH_SEED, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_LEECH_SEED_DRAIN, player); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player); + } +} +#endif diff --git a/test/battle/hold_effect/restore_pp.c b/test/battle/hold_effect/restore_pp.c index c3810652a46d..daf00fc3799d 100644 --- a/test/battle/hold_effect/restore_pp.c +++ b/test/battle/hold_effect/restore_pp.c @@ -21,3 +21,21 @@ SINGLE_BATTLE_TEST("Restore PP berry activates immediately on switch in") EXPECT(player->item == ITEM_NONE); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Restore PP berry activates immediately on switch in (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LEPPA_BERRY); MovesWithPP({MOVE_SCRATCH, 0}, {MOVE_CELEBRATE, 20}); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_POUND); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } THEN { + EXPECT(player->item == ITEM_NONE); + } +} +#endif diff --git a/test/battle/hold_effect/rocky_helmet.c b/test/battle/hold_effect/rocky_helmet.c index f49d26c1ebe1..29a5383ecc37 100644 --- a/test/battle/hold_effect/rocky_helmet.c +++ b/test/battle/hold_effect/rocky_helmet.c @@ -23,3 +23,21 @@ SINGLE_BATTLE_TEST("Rocky Helmet damages attacker even if damage is blocked by D } TO_DO_BATTLE_TEST("TODO: Write Rocky Helmet (Hold Effect) test titles") + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Rocky Helmet damages attacker even if damage is blocked by Disguise (Multi)") +{ + GIVEN { + PLAYER(SPECIES_MIMIKYU) { Items(ITEM_PECHA_BERRY, ITEM_ROCKY_HELMET); Ability(ABILITY_DISGUISE); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SHADOW_SNEAK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHADOW_SNEAK, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(opponent); + } +} + +TO_DO_BATTLE_TEST("TODO: Write Rocky Helmet (Hold Effect) test titles (Multi)") +#endif diff --git a/test/battle/hold_effect/room_service.c b/test/battle/hold_effect/room_service.c index e775ca496cf5..ab3095ee5087 100644 --- a/test/battle/hold_effect/room_service.c +++ b/test/battle/hold_effect/room_service.c @@ -31,3 +31,59 @@ SINGLE_BATTLE_TEST("Room Serive decreases the holder's seep by one stage") EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Room Serive decreases the holder's seep by one stage (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_U_TURN) == EFFECT_HIT_ESCAPE); + ASSUME(GetMoveEffect(MOVE_TRICK_ROOM) == EFFECT_TRICK_ROOM); + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); Item(ITEM_ROOM_SERVICE); } + OPPONENT(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_TRICK_ROOM); } + TURN { MOVE(player, MOVE_EXPLOSION); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK_ROOM, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, player); + HP_BAR(opponent); + MESSAGE("2 sent out Wynaut!"); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Room Serive decreases the holder's seep by one stage (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_U_TURN) == EFFECT_HIT_ESCAPE); + ASSUME(GetMoveEffect(MOVE_TRICK_ROOM) == EFFECT_TRICK_ROOM); + ASSUME(GetMoveEffect(MOVE_EXPLOSION) == EFFECT_EXPLOSION); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); Items(ITEM_PECHA_BERRY, ITEM_ROOM_SERVICE); } + OPPONENT(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_TRICK_ROOM); } + TURN { MOVE(player, MOVE_EXPLOSION); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK_ROOM, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, player); + HP_BAR(opponent); + MESSAGE("2 sent out Wynaut!"); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + } +} +#endif diff --git a/test/battle/hold_effect/rowap_berry.c b/test/battle/hold_effect/rowap_berry.c index 93ac49f6089e..936060de351f 100644 --- a/test/battle/hold_effect/rowap_berry.c +++ b/test/battle/hold_effect/rowap_berry.c @@ -72,3 +72,72 @@ SINGLE_BATTLE_TEST("Rowap Berry is triggered even if berry user dies") MESSAGE("Wobbuffet was hurt by the opposing Wobbuffet's Rowap Berry!"); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Rowap Berry causes the attacker to lose 1/8 of its max HP if a special move was used (Multi)") +{ + s16 damage; + u16 move; + + PARAMETRIZE { move = MOVE_SWIFT; } + PARAMETRIZE { move = MOVE_SCRATCH; } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_SWIFT) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_ROWAP_BERRY); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + if (move == MOVE_SWIFT) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + HP_BAR(player, captureDamage: &damage); + MESSAGE("Wobbuffet was hurt by the opposing Wobbuffet's Rowap Berry!"); + } else { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("Wobbuffet was hurt by the opposing Wobbuffet's Rowap Berry!"); + } + } + } THEN { + if (move == MOVE_SWIFT) + EXPECT_EQ(player->maxHP / 8, damage); + } +} + +SINGLE_BATTLE_TEST("Rowap Berry is not triggered by a physical move (Multi)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_ROWAP_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("Wobbuffet was hurt by the opposing Wobbuffet's Rowap Berry!"); + } + } +} + +SINGLE_BATTLE_TEST("Rowap Berry is triggered even if berry user dies (Multi)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_TACKLE) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Items(ITEM_PECHA_BERRY, ITEM_ROWAP_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SWIFT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWIFT, player); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the opposing Wobbuffet's Rowap Berry!"); + } +} +#endif diff --git a/test/battle/hold_effect/safety_goggles.c b/test/battle/hold_effect/safety_goggles.c index a5cfca07690a..7189c1f00c16 100644 --- a/test/battle/hold_effect/safety_goggles.c +++ b/test/battle/hold_effect/safety_goggles.c @@ -75,3 +75,109 @@ SINGLE_BATTLE_TEST("Safety Goggles blocks Effect Spore's effect") } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Safety Goggles blocks Effect Spore's effect (Traits)") +{ + KNOWN_FAILING; + PASSES_RANDOMLY(100, 100, RNG_EFFECT_SPORE); + GIVEN { + WITH_CONFIG(CONFIG_POWDER_GRASS, GEN_5); // Setting it to Gen 6 causes it to pass + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_SAFETY_GOGGLES); } + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_TECHNICIAN); Innates(ABILITY_EFFECT_SPORE); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + MESSAGE("Wobbuffet was poisoned by the opposing Breloom's Effect Spore!"); + STATUS_ICON(player, poison: TRUE); + + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, player); + MESSAGE("The opposing Breloom's Effect Spore paralyzed Wobbuffet, so it may be unable to move!"); + STATUS_ICON(player, paralysis: TRUE); + + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, player); + MESSAGE("The opposing Breloom's Effect Spore made Wobbuffet sleep!"); + STATUS_ICON(player, sleep: TRUE); + } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Safety Goggles block powder and spore moves (Multi)") +{ + GIVEN { + ASSUME(IsPowderMove(MOVE_STUN_SPORE)); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_ABRA) { Items(ITEM_PECHA_BERRY, ITEM_SAFETY_GOGGLES); } + } WHEN { + TURN { MOVE(player, MOVE_STUN_SPORE); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, player); + MESSAGE("The opposing Abra is not affected thanks to its Safety Goggles!"); + } +} + +SINGLE_BATTLE_TEST("Safety Goggles blocks damage from Hail (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_SAFETY_GOGGLES); }; + } WHEN { + TURN { MOVE(player, MOVE_HAIL); } + } SCENE { + NOT MESSAGE("The opposing Wobbuffet is buffeted by the hail!"); + } +} + +SINGLE_BATTLE_TEST("Safety Goggles blocks damage from Sandstorm (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_SAFETY_GOGGLES); }; + } WHEN { + TURN { MOVE(player, MOVE_SANDSTORM); } + } SCENE { + NOT MESSAGE("The opposing Wobbuffet is buffeted by the sandstorm!"); + } +} + +SINGLE_BATTLE_TEST("Safety Goggles blocks Effect Spore's effect (Multi)") +{ + KNOWN_FAILING; + PASSES_RANDOMLY(100, 100, RNG_EFFECT_SPORE); + GIVEN { + WITH_CONFIG(CONFIG_POWDER_GRASS, GEN_5); // Setting it to Gen 6 causes it to pass + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_SAFETY_GOGGLES); } + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_EFFECT_SPORE); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + MESSAGE("Wobbuffet was poisoned by the opposing Breloom's Effect Spore!"); + STATUS_ICON(player, poison: TRUE); + + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, player); + MESSAGE("The opposing Breloom's Effect Spore paralyzed Wobbuffet, so it may be unable to move!"); + STATUS_ICON(player, paralysis: TRUE); + + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, player); + MESSAGE("The opposing Breloom's Effect Spore made Wobbuffet sleep!"); + STATUS_ICON(player, sleep: TRUE); + } + } +} +#endif diff --git a/test/battle/hold_effect/scope_lens.c b/test/battle/hold_effect/scope_lens.c index f74a06a1c58e..5eae90b8db86 100644 --- a/test/battle/hold_effect/scope_lens.c +++ b/test/battle/hold_effect/scope_lens.c @@ -21,3 +21,26 @@ SINGLE_BATTLE_TEST("Scope Lens increases the critical hit ratio by 1 stage") MESSAGE("A critical hit!"); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Scope Lens increases the critical hit ratio by 1 stage (Multi)") +{ + u32 genConfig = 0, passes, trials; + PARAMETRIZE { genConfig = GEN_1; passes = 1; trials = 4; } // 25% with Wobbuffet's base speed + for (u32 j = GEN_2; j <= GEN_9; j++) + PARAMETRIZE { genConfig = j; passes = 1; trials = 8; } // 12.5% + PASSES_RANDOMLY(passes, trials, RNG_CRITICAL_HIT); + GIVEN { + WITH_CONFIG(CONFIG_CRIT_CHANCE, genConfig); + ASSUME(gItemsInfo[ITEM_SCOPE_LENS].holdEffect == HOLD_EFFECT_SCOPE_LENS); + ASSUME(GetSpeciesBaseSpeed(SPECIES_WOBBUFFET) == 33); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_SCOPE_LENS); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("A critical hit!"); + } +} +#endif diff --git a/test/battle/hold_effect/shed_shell.c b/test/battle/hold_effect/shed_shell.c index f8f6277ff23a..c7d0679c4ded 100644 --- a/test/battle/hold_effect/shed_shell.c +++ b/test/battle/hold_effect/shed_shell.c @@ -65,3 +65,95 @@ SINGLE_BATTLE_TEST("Shed Shell does not allow Teleport when trapped") MESSAGE("But it failed!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Shed Shell allows switching out even when trapped by Shadow Tag (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_SHED_SHELL); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Innates(ABILITY_SHADOW_TAG); } + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Wynaut"); + } +} + +SINGLE_BATTLE_TEST("Shed Shell allows switching out even when trapped by Arena Trap (Traits)") +{ + GIVEN { + PLAYER(SPECIES_DIGLETT) { Item(ITEM_SHED_SHELL); } // Grounded + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_DIGLETT) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_ARENA_TRAP); } + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + SWITCH_OUT_MESSAGE("Diglett"); + SEND_IN_MESSAGE("Wynaut"); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Shed Shell allows switching out even when trapped by Mean Look (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_SHED_SHELL); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_GASTLY); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_MEAN_LOOK); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MEAN_LOOK, opponent); + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Wynaut"); + } +} + +SINGLE_BATTLE_TEST("Shed Shell allows switching out even when trapped by Shadow Tag (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_SHED_SHELL); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); } + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Wynaut"); + } +} + +SINGLE_BATTLE_TEST("Shed Shell allows switching out even when trapped by Arena Trap (Multi)") +{ + GIVEN { + PLAYER(SPECIES_DIGLETT) { Items(ITEM_PECHA_BERRY, ITEM_SHED_SHELL); } // Grounded + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_DIGLETT) { Ability(ABILITY_ARENA_TRAP); } + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + SWITCH_OUT_MESSAGE("Diglett"); + SEND_IN_MESSAGE("Wynaut"); + } +} + +SINGLE_BATTLE_TEST("Shed Shell does not allow Teleport when trapped (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TELEPORT) == EFFECT_TELEPORT); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_SHED_SHELL); Moves(MOVE_TELEPORT, MOVE_SPLASH, MOVE_CELEBRATE); } + OPPONENT(SPECIES_GASTLY); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_MEAN_LOOK); } + TURN { MOVE(player, MOVE_TELEPORT); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MEAN_LOOK, opponent); + MESSAGE("Wobbuffet used Teleport!"); + MESSAGE("But it failed!"); + } +} +#endif diff --git a/test/battle/hold_effect/shell_bell.c b/test/battle/hold_effect/shell_bell.c index 7473f6db59cc..ac8776d8363d 100644 --- a/test/battle/hold_effect/shell_bell.c +++ b/test/battle/hold_effect/shell_bell.c @@ -291,3 +291,312 @@ SINGLE_BATTLE_TEST("Shell Bell recovers only 1 damage if the move only did 1 dam TO_DO_BATTLE_TEST("If a Pokémon steals a Shell Bell with Thief or Covet, it will recover HP for the use of that move that stole the Shell Bell") TO_DO_BATTLE_TEST("If a Pokémon steals a Shell Bell with Magician, it will recover HP for the use of that move that stole the Shell Bell") + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Shell Bell activates after Rough Skin (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_SHELL_BELL); } + OPPONENT(SPECIES_GIBLE) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_ROUGH_SKIN); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player); + } +} + +TO_DO_BATTLE_TEST("If a Pokémon steals a Shell Bell with Thief or Covet, it will recover HP for the use of that move that stole the Shell Bell (Traits)") +TO_DO_BATTLE_TEST("If a Pokémon steals a Shell Bell with Magician, it will recover HP for the use of that move that stole the Shell Bell (Traits)") +#endif + +#if MAX_MON_ITEMS > 1 +#define HITS 5 +SINGLE_BATTLE_TEST("Shell Bell recovers 1/8 of HP from after the last hit from all hits of a multi hit move (Multi)") +{ + s16 multiHitDamage[HITS]; + s16 totalDamage = 0; + s16 shellBellRecovery = 0; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Items(ITEM_PECHA_BERRY, ITEM_SHELL_BELL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BULLET_SEED); } + } SCENE { + for (u32 i = 0; i < HITS; i++) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, player); + HP_BAR(opponent, captureDamage: &multiHitDamage[i]); + totalDamage += multiHitDamage[i]; + } + HP_BAR(player, captureDamage: &shellBellRecovery); + } THEN { + EXPECT_EQ(totalDamage / 8, -1 * shellBellRecovery); + } +} +#undef HITS + +SINGLE_BATTLE_TEST("Shell Bell recovers no HP if the move did no damage (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Items(ITEM_PECHA_BERRY, ITEM_SHELL_BELL); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); }; + } WHEN { + TURN { MOVE(player, MOVE_FALSE_SWIPE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FALSE_SWIPE, player); + HP_BAR(opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player); + } + } +} + +SINGLE_BATTLE_TEST("Shell Bell activates if it hits a Substitute (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Items(ITEM_PECHA_BERRY, ITEM_SHELL_BELL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + // HP_BAR(opponent); // When you hit a sub the hp bar check doesn't work. Not sure if this is a bug + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Shell Bell activates after Absorb (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Items(ITEM_PECHA_BERRY, ITEM_SHELL_BELL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ABSORB); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ABSORB, player); + HP_BAR(opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Shell Bell activates after Rough Skin (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_SHELL_BELL); } + OPPONENT(SPECIES_GIBLE) { Ability(ABILITY_ROUGH_SKIN); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Shell Bell does not activate on Future Sight if the original user is on the field (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Items(ITEM_PECHA_BERRY, ITEM_SHELL_BELL); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_FUTURE_SIGHT); } + TURN {} + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player); + MESSAGE("The opposing Wynaut took the Future Sight attack!"); + HP_BAR(opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player); + } + } +} + +SINGLE_BATTLE_TEST("Shell Bell restores 1/8 HP of damage dealt (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Level(16); Items(ITEM_PECHA_BERRY, ITEM_SHELL_BELL); HP(10); } + OPPONENT(SPECIES_WOBBUFFET) { Level(16); }; + } WHEN { + TURN { MOVE(player, MOVE_SEISMIC_TOSS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SEISMIC_TOSS, player); + HP_BAR(opponent); + HP_BAR(player, damage: -2); + } +} + +SINGLE_BATTLE_TEST("Shell Bell doesn't restore HP for damage dealt by a foreseen move (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_FUTURE_SIGHT) == EFFECT_FUTURE_SIGHT); + PLAYER(SPECIES_WOBBUFFET) { Level(16); Items(ITEM_PECHA_BERRY, ITEM_SHELL_BELL); HP(10); } + OPPONENT(SPECIES_WOBBUFFET) { Level(16); }; + } WHEN { + TURN { MOVE(player, MOVE_FUTURE_SIGHT); } + TURN { } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player); + MESSAGE("The opposing Wobbuffet took the Future Sight attack!"); + HP_BAR(opponent); + NONE_OF { + HP_BAR(player); + } + } +} + +SINGLE_BATTLE_TEST("Shell Bell does not activate on Future Sight if the original user is not on the field (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET) { HP(1); Items(ITEM_PECHA_BERRY, ITEM_SHELL_BELL); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_FUTURE_SIGHT); } + TURN { SWITCH(player, 1); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player); + MESSAGE("The opposing Wynaut took the Future Sight attack!"); + HP_BAR(opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player); + } + } +} + + +SINGLE_BATTLE_TEST("Shell Bell does not activate on Future Sight if the original user is on the field (Multi)") +{ + s16 damage = 0; + s16 healed = 0; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Items(ITEM_PECHA_BERRY, ITEM_SHELL_BELL); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_FUTURE_SIGHT); } + TURN {} + TURN {} + TURN { MOVE(player, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player); + MESSAGE("The opposing Wynaut took the Future Sight attack!"); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, player); + HP_BAR(opponent, captureDamage: &damage); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player, captureDamage: &healed); + } THEN { + EXPECT_MUL_EQ(damage, Q_4_12(-0.25), healed); + } +} + +DOUBLE_BATTLE_TEST("Shell Bell heals accumulated damage for spread moves (Multi)") +{ + s16 opponentLeftDamage; + s16 opponentRightDamage; + s16 playerRightDamage; + s16 shellBellHeal; + + const u16 maxHp = 200; + const u16 initHp = 1; + GIVEN { + ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(SPECIES_ARIADOS) { MaxHP(maxHp); HP(initHp); Items(ITEM_PECHA_BERRY, ITEM_SHELL_BELL); } + PLAYER(SPECIES_WOBBUFFET) {} + OPPONENT(SPECIES_GYARADOS) {} + OPPONENT(SPECIES_CHANSEY) {} + } WHEN { + TURN { + MOVE(playerLeft, MOVE_DISCHARGE); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DISCHARGE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &opponentLeftDamage); + HP_BAR(playerRight, captureDamage: &playerRightDamage); + HP_BAR(opponentRight, captureDamage: &opponentRightDamage); + + HP_BAR(playerLeft, captureDamage: &shellBellHeal); + } THEN { + const s16 totalDamage = opponentLeftDamage + + playerRightDamage + opponentRightDamage; + EXPECT_EQ(shellBellHeal, -totalDamage / 8); + EXPECT_EQ(playerLeft->hp, initHp + (totalDamage / 8)); + } +} + +SINGLE_BATTLE_TEST("Shell Bell restores 1/8 HP at move end, one strike (Multi)") +{ + const u16 maxHp = 200; + u16 hp, opponentHp; + u16 hpGainFromDamage, hpGainActual; + + PARAMETRIZE { hp = maxHp; opponentHp = maxHp; } + PARAMETRIZE { hp = maxHp - 1; opponentHp = maxHp; } + PARAMETRIZE { hp = maxHp / 2; opponentHp = maxHp; } + PARAMETRIZE { hp = maxHp; opponentHp = 24; } // dragon rage only does 24 dmg, only heal 3 HP instead of 5 + PARAMETRIZE { hp = maxHp - 1; opponentHp = 24; } // dragon rage only does 24 dmg, only heal 3 HP instead of 5 + PARAMETRIZE { hp = maxHp / 2; opponentHp = 24; } // dragon rage only does 24 dmg, only heal 3 HP instead of 5 + PARAMETRIZE { hp = maxHp; opponentHp = 1; } + PARAMETRIZE { hp = maxHp - 1; opponentHp = 1; } + PARAMETRIZE { hp = maxHp / 2; opponentHp = 1; } + + hpGainFromDamage = max(1, min(40, opponentHp) / 8); + hpGainActual = min(maxHp - hp, hpGainFromDamage); + + GIVEN { + ASSUME(GetMoveEffect(MOVE_DRAGON_RAGE) == EFFECT_FIXED_HP_DAMAGE); + ASSUME(GetMoveFixedHPDamage(MOVE_DRAGON_RAGE) == 40); + PLAYER(SPECIES_WOBBUFFET) { MaxHP(maxHp); HP(hp); Items(ITEM_PECHA_BERRY, ITEM_SHELL_BELL); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(maxHp); HP(opponentHp); } + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, player); + HP_BAR(opponent); + if (hp < maxHp) { + HP_BAR(player, damage: -hpGainActual); + } else { + NOT HP_BAR(player); + } + } THEN { + EXPECT_EQ(player->hp, hp + hpGainActual); + } +} + +SINGLE_BATTLE_TEST("Shell Bell recovers only 1 damage if the move only did 1 damage (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Items(ITEM_PECHA_BERRY, ITEM_SHELL_BELL); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); }; + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player); + + } THEN { + EXPECT_EQ(player->hp, 2); + } +} + +TO_DO_BATTLE_TEST("If a Pokémon steals a Shell Bell with Thief or Covet, it will recover HP for the use of that move that stole the Shell Bell (Multi)") +TO_DO_BATTLE_TEST("If a Pokémon steals a Shell Bell with Magician, it will recover HP for the use of that move that stole the Shell Bell (Multi)") +#endif diff --git a/test/battle/hold_effect/sp_attack_up.c b/test/battle/hold_effect/sp_attack_up.c index f4256a533581..52f9a253d45b 100644 --- a/test/battle/hold_effect/sp_attack_up.c +++ b/test/battle/hold_effect/sp_attack_up.c @@ -68,3 +68,100 @@ SINGLE_BATTLE_TEST("Petaya Berry raises Sp. Atk by one stage when HP drops to 1/ EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 2); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Petaya Berry raises Sp. Atk by one stage when HP drops to 1/2 or below if holder has Gluttony (Traits)") +{ + GIVEN { + PLAYER(SPECIES_BELLSPROUT) { MaxHP(80); HP(80); Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_GLUTTONY); Item(ITEM_PETAYA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Petaya Berry, the Sp. Atk of Bellsprout rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Petaya Berry raises Sp. Atk by one stage when HP drops to 1/4 or below if holder has Ripen (Traits)") +{ + GIVEN { + PLAYER(SPECIES_APPLIN) { MaxHP(160); HP(80); Ability(ABILITY_BULLETPROOF); Innates(ABILITY_RIPEN); Item(ITEM_PETAYA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Petaya Berry, the Sp. Atk of Applin sharply rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 2); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Petaya Berry raises the holder's Sp. Atk by one stage when HP drops to 1/4 or below (Multi)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_DRAGON_RAGE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { MaxHP(160); HP(80); Items(ITEM_PECHA_BERRY, ITEM_PETAYA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + if (move == MOVE_SCRATCH) { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Petaya Berry, the Sp. Atk of Wobbuffet rose!"); + } + } else { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Petaya Berry, the Sp. Atk of Wobbuffet rose!"); + } + } THEN { + if (move == MOVE_DRAGON_RAGE) + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Petaya Berry raises Sp. Atk by one stage when HP drops to 1/2 or below if holder has Gluttony (Multi)") +{ + GIVEN { + PLAYER(SPECIES_BELLSPROUT) { MaxHP(80); HP(80); Ability(ABILITY_GLUTTONY); Items(ITEM_PECHA_BERRY, ITEM_PETAYA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Petaya Berry, the Sp. Atk of Bellsprout rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Petaya Berry raises Sp. Atk by one stage when HP drops to 1/4 or below if holder has Ripen (Multi)") +{ + GIVEN { + PLAYER(SPECIES_APPLIN) { MaxHP(160); HP(80); Ability(ABILITY_RIPEN); Items(ITEM_PECHA_BERRY, ITEM_PETAYA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Petaya Berry, the Sp. Atk of Applin sharply rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 2); + } +} +#endif diff --git a/test/battle/hold_effect/sp_defense_up.c b/test/battle/hold_effect/sp_defense_up.c index e7d8f9a170c8..176c8ce651e6 100644 --- a/test/battle/hold_effect/sp_defense_up.c +++ b/test/battle/hold_effect/sp_defense_up.c @@ -68,3 +68,100 @@ SINGLE_BATTLE_TEST("Apicot Berry raises Sp. Def by one stage when HP drops to 1/ EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 2); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Apicot Berry raises Sp. Def by one stage when HP drops to 1/2 or below if holder has Gluttony (Traits)") +{ + GIVEN { + PLAYER(SPECIES_BELLSPROUT) { MaxHP(80); HP(80); Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_GLUTTONY); Item(ITEM_APICOT_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Apicot Berry, the Sp. Def of Bellsprout rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Apicot Berry raises Sp. Def by one stage when HP drops to 1/4 or below if holder has Ripen (Traits)") +{ + GIVEN { + PLAYER(SPECIES_APPLIN) { MaxHP(160); HP(80); Ability(ABILITY_BULLETPROOF); Innates(ABILITY_RIPEN); Item(ITEM_APICOT_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Apicot Berry, the Sp. Def of Applin sharply rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 2); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Apicot Berry raises the holder's Sp. Def by one stage when HP drops to 1/4 or below (Multi)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_DRAGON_RAGE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { MaxHP(160); HP(80); Items(ITEM_PECHA_BERRY, ITEM_APICOT_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + if (move == MOVE_SCRATCH) { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Apicot Berry, the Sp. Def of Wobbuffet rose!"); + } + } else { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Apicot Berry, the Sp. Def of Wobbuffet rose!"); + } + } THEN { + if (move == MOVE_DRAGON_RAGE) + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Apicot Berry raises Sp. Def by one stage when HP drops to 1/2 or below if holder has Gluttony (Multi)") +{ + GIVEN { + PLAYER(SPECIES_BELLSPROUT) { MaxHP(80); HP(80); Ability(ABILITY_GLUTTONY); Items(ITEM_PECHA_BERRY, ITEM_APICOT_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Apicot Berry, the Sp. Def of Bellsprout rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Apicot Berry raises Sp. Def by one stage when HP drops to 1/4 or below if holder has Ripen (Multi)") +{ + GIVEN { + PLAYER(SPECIES_APPLIN) { MaxHP(160); HP(80); Ability(ABILITY_RIPEN); Items(ITEM_PECHA_BERRY, ITEM_APICOT_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Apicot Berry, the Sp. Def of Applin sharply rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 2); + } +} +#endif diff --git a/test/battle/hold_effect/speed_up.c b/test/battle/hold_effect/speed_up.c index b1725597a9b6..eaa67b87900f 100644 --- a/test/battle/hold_effect/speed_up.c +++ b/test/battle/hold_effect/speed_up.c @@ -87,3 +87,119 @@ DOUBLE_BATTLE_TEST("Salac Berry does not miss timing miss timing") MESSAGE("The opposing Wobbuffet was hurt by the sea of fire!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Salac Berry raises Speed by one stage when HP drops to 1/2 or below if holder has Gluttony (Traits)") +{ + GIVEN { + PLAYER(SPECIES_BELLSPROUT) { MaxHP(80); HP(80); Ability(ABILITY_CHLOROPHYLL); Innates(ABILITY_GLUTTONY); Item(ITEM_SALAC_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Salac Berry, the Speed of Bellsprout rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Salac Berry raises Speed by one stage when HP drops to 1/4 or below if holder has Ripen (Traits)") +{ + GIVEN { + PLAYER(SPECIES_APPLIN) { MaxHP(160); HP(80); Ability(ABILITY_BULLETPROOF); Innates(ABILITY_RIPEN); Item(ITEM_SALAC_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Salac Berry, the Speed of Applin sharply rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 2); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Salac Berry raises the holder's Speed by one stage when HP drops to 1/4 or below (Multi)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_DRAGON_RAGE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { MaxHP(160); HP(80); Items(ITEM_PECHA_BERRY, ITEM_SALAC_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + if (move == MOVE_SCRATCH) { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Salac Berry, the Speed of Wobbuffet rose!"); + } + } else { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Salac Berry, the Speed of Wobbuffet rose!"); + } + } THEN { + if (move == MOVE_DRAGON_RAGE) + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Salac Berry raises Speed by one stage when HP drops to 1/2 or below if holder has Gluttony (Multi)") +{ + GIVEN { + PLAYER(SPECIES_BELLSPROUT) { MaxHP(80); HP(80); Ability(ABILITY_GLUTTONY); Items(ITEM_PECHA_BERRY, ITEM_SALAC_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Salac Berry, the Speed of Bellsprout rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Salac Berry raises Speed by one stage when HP drops to 1/4 or below if holder has Ripen (Multi)") +{ + GIVEN { + PLAYER(SPECIES_APPLIN) { MaxHP(160); HP(80); Ability(ABILITY_RIPEN); Items(ITEM_PECHA_BERRY, ITEM_SALAC_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Using Salac Berry, the Speed of Applin sharply rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 2); + } +} + +DOUBLE_BATTLE_TEST("Salac Berry does not miss timing miss timing (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { MaxHP(100); HP(26); Items(ITEM_PECHA_BERRY, ITEM_SALAC_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_FIRE_PLEDGE, target: opponentRight); MOVE(playerRight, MOVE_GRASS_PLEDGE, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, playerRight); + MESSAGE("A sea of fire enveloped the opposing team!"); + MESSAGE("The opposing Wynaut was hurt by the sea of fire!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + MESSAGE("Using Salac Berry, the Speed of the opposing Wynaut rose!"); + MESSAGE("The opposing Wobbuffet was hurt by the sea of fire!"); + } +} +#endif diff --git a/test/battle/hold_effect/sticky_barb.c b/test/battle/hold_effect/sticky_barb.c index b13f2a598789..501445eef9ad 100644 --- a/test/battle/hold_effect/sticky_barb.c +++ b/test/battle/hold_effect/sticky_barb.c @@ -49,3 +49,49 @@ SINGLE_BATTLE_TEST("Sticky Barb gets transferred if its holder is hit by a conta } } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Sticky Barb hurts its holder at the end of the turn (Multi)") +{ + s16 damage; + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_STICKY_BARB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { } + } SCENE { + HP_BAR(player, captureDamage: &damage); + } THEN { + EXPECT_EQ(damage, player->maxHP / 8); + } +} + +SINGLE_BATTLE_TEST("Sticky Barb gets transferred if its holder is hit by a contact move (Multi)") +{ + u32 move; + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_GROWL; } + PARAMETRIZE { move = MOVE_HYPER_VOICE; } + GIVEN { + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + ASSUME(!MoveMakesContact(MOVE_GROWL)); + ASSUME(!MoveMakesContact(MOVE_HYPER_VOICE)); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_STICKY_BARB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + if (MoveMakesContact(move)) + { + MESSAGE("The Sticky Barb attached itself to the opposing Wobbuffet!"); + MESSAGE("The opposing Wobbuffet was hurt by the Sticky Barb!"); + } + else + { + NOT MESSAGE("The Sticky Barb attached itself to the opposing Wobbuffet!"); + MESSAGE("Wobbuffet was hurt by the Sticky Barb!"); + } + } +} +#endif diff --git a/test/battle/hold_effect/terrain_seed.c b/test/battle/hold_effect/terrain_seed.c index 1ff1c252bfc0..19703cfb1418 100644 --- a/test/battle/hold_effect/terrain_seed.c +++ b/test/battle/hold_effect/terrain_seed.c @@ -200,3 +200,381 @@ SINGLE_BATTLE_TEST("Electric Seed doesn't activate on existing Electric Terrain ABILITY_POPUP(player, ABILITY_GRASSY_SURGE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Electric Seed raises the holder's Defense on Electric Terrain (Traits)") +{ + enum Ability ability; + u32 item; + PARAMETRIZE { ability = ABILITY_TELEPATHY; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_TELEPATHY; item = ITEM_ELECTRIC_SEED; } + PARAMETRIZE { ability = ABILITY_ELECTRIC_SURGE; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_ELECTRIC_SURGE; item = ITEM_ELECTRIC_SEED; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_ELECTRIC_SEED); } + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_ELECTRIC_SEED); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_LIGHT_METAL); Innates(ability); Item(item); } + } WHEN { + if (ability == ABILITY_TELEPATHY) + TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Electric Seed, the Defense of Wobbuffet rose!"); + if (item == ITEM_ELECTRIC_SEED) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Using Electric Seed, the Defense of the opposing Tapu Koko rose!"); + } + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Wobbuffet"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Electric Seed, the Defense of Wobbuffet rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Grassy Seed raises the holder's Defense on Grassy Terrain (Traits)") +{ + enum Ability ability; + u32 item; + PARAMETRIZE { ability = ABILITY_TELEPATHY; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_TELEPATHY; item = ITEM_GRASSY_SEED; } + PARAMETRIZE { ability = ABILITY_GRASSY_SURGE; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_GRASSY_SURGE; item = ITEM_GRASSY_SEED; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_GRASSY_SEED); } + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_GRASSY_SEED); } + OPPONENT(SPECIES_TAPU_BULU) { Ability(ABILITY_LIGHT_METAL); Innates(ability); Item(item); } + } WHEN { + if (ability == ABILITY_TELEPATHY) + TURN { MOVE(player, MOVE_GRASSY_TERRAIN); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Grassy Seed, the Defense of Wobbuffet rose!"); + if (item == ITEM_GRASSY_SEED) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Using Grassy Seed, the Defense of the opposing Tapu Bulu rose!"); + } + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Wobbuffet"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Grassy Seed, the Defense of Wobbuffet rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Misty Seed raises the holder's Sp. Defense on Misty Terrain (Traits)") +{ + enum Ability ability; + u32 item; + PARAMETRIZE { ability = ABILITY_TELEPATHY; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_TELEPATHY; item = ITEM_MISTY_SEED; } + PARAMETRIZE { ability = ABILITY_MISTY_SURGE; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_MISTY_SURGE; item = ITEM_MISTY_SEED; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MISTY_SEED); } + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_MISTY_SEED); } + OPPONENT(SPECIES_TAPU_FINI) { Ability(ABILITY_LIGHT_METAL); Innates(ability); Item(item); } + } WHEN { + if (ability == ABILITY_TELEPATHY) + TURN { MOVE(player, MOVE_MISTY_TERRAIN); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Misty Seed, the Sp. Def of Wobbuffet rose!"); + if (item == ITEM_MISTY_SEED) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Using Misty Seed, the Sp. Def of the opposing Tapu Fini rose!"); + } + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Wobbuffet"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Misty Seed, the Sp. Def of Wobbuffet rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Psychic Seed raises the holder's Sp. Defense on Psychic Terrain (Traits)") +{ + enum Ability ability; + u32 item; + PARAMETRIZE { ability = ABILITY_TELEPATHY; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_TELEPATHY; item = ITEM_PSYCHIC_SEED; } + PARAMETRIZE { ability = ABILITY_PSYCHIC_SURGE; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_PSYCHIC_SURGE; item = ITEM_PSYCHIC_SEED; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_PSYCHIC_SEED); } + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_PSYCHIC_SEED); } + OPPONENT(SPECIES_TAPU_LELE) { Ability(ABILITY_LIGHT_METAL); Innates(ability); Item(item); } + } WHEN { + if (ability == ABILITY_TELEPATHY) + TURN { MOVE(player, MOVE_PSYCHIC_TERRAIN); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Psychic Seed, the Sp. Def of Wobbuffet rose!"); + if (item == ITEM_PSYCHIC_SEED) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Using Psychic Seed, the Sp. Def of the opposing Tapu Lele rose!"); + } + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Wobbuffet"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Psychic Seed, the Sp. Def of Wobbuffet rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Seeds get consumed in Terrain even if holder is not affected by Terrain (Traits)") +{ + u32 species, item; + enum Ability ability; + PARAMETRIZE { species = SPECIES_TAPU_KOKO; ability = ABILITY_ELECTRIC_SURGE; item = ITEM_ELECTRIC_SEED; } + PARAMETRIZE { species = SPECIES_TAPU_BULU; ability = ABILITY_GRASSY_SURGE; item = ITEM_GRASSY_SEED; } + PARAMETRIZE { species = SPECIES_TAPU_FINI; ability = ABILITY_MISTY_SURGE; item = ITEM_MISTY_SEED; } + PARAMETRIZE { species = SPECIES_TAPU_LELE; ability = ABILITY_PSYCHIC_SURGE; item = ITEM_PSYCHIC_SEED; } + GIVEN { + ASSUME(GetSpeciesType(SPECIES_PIDGEY, 0) == TYPE_FLYING || GetSpeciesType(SPECIES_PIDGEY, 1) == TYPE_FLYING); + PLAYER(SPECIES_PIDGEY) { Item(item); } + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ability); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Electric Seed is consumed on Electric Terrain before other abilities change the terrain (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TAPU_BULU) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GRASSY_SURGE); Item(ITEM_ELECTRIC_SEED); Speed(5); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ELECTRIC_SURGE); Item(ITEM_ELECTRIC_SEED); Speed(10); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Using Electric Seed, the Defense of the opposing Tapu Koko rose!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Electric Seed, the Defense of Tapu Bulu rose!"); + ABILITY_POPUP(player, ABILITY_GRASSY_SURGE); + } +} + +SINGLE_BATTLE_TEST("Electric Seed doesn't activate on existing Electric Terrain before user's ability changes the terrain (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_TAPU_BULU) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GRASSY_SURGE); Item(ITEM_ELECTRIC_SEED); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ELECTRIC_SURGE); } + } WHEN { + TURN { SWITCH(player, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Tapu Bulu"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Electric Seed, the Defense of Tapu Bulu rose!"); + } + ABILITY_POPUP(player, ABILITY_GRASSY_SURGE); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Electric Seed raises the holder's Defense on Electric Terrain (Multi)") +{ + enum Ability ability; + u32 item; + PARAMETRIZE { ability = ABILITY_TELEPATHY; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_TELEPATHY; item = ITEM_ELECTRIC_SEED; } + PARAMETRIZE { ability = ABILITY_ELECTRIC_SURGE; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_ELECTRIC_SURGE; item = ITEM_ELECTRIC_SEED; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_ELECTRIC_SEED); } + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_ELECTRIC_SEED); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ability); Items(ITEM_PECHA_BERRY, item); } + } WHEN { + if (ability == ABILITY_TELEPATHY) + TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Electric Seed, the Defense of Wobbuffet rose!"); + if (item == ITEM_ELECTRIC_SEED) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Using Electric Seed, the Defense of the opposing Tapu Koko rose!"); + } + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Wobbuffet"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Electric Seed, the Defense of Wobbuffet rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Grassy Seed raises the holder's Defense on Grassy Terrain (Multi)") +{ + enum Ability ability; + u32 item; + PARAMETRIZE { ability = ABILITY_TELEPATHY; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_TELEPATHY; item = ITEM_GRASSY_SEED; } + PARAMETRIZE { ability = ABILITY_GRASSY_SURGE; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_GRASSY_SURGE; item = ITEM_GRASSY_SEED; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_GRASSY_SEED); } + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_GRASSY_SEED); } + OPPONENT(SPECIES_TAPU_BULU) { Ability(ability); Items(ITEM_PECHA_BERRY, item); } + } WHEN { + if (ability == ABILITY_TELEPATHY) + TURN { MOVE(player, MOVE_GRASSY_TERRAIN); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Grassy Seed, the Defense of Wobbuffet rose!"); + if (item == ITEM_GRASSY_SEED) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Using Grassy Seed, the Defense of the opposing Tapu Bulu rose!"); + } + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Wobbuffet"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Grassy Seed, the Defense of Wobbuffet rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Misty Seed raises the holder's Sp. Defense on Misty Terrain (Multi)") +{ + enum Ability ability; + u32 item; + PARAMETRIZE { ability = ABILITY_TELEPATHY; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_TELEPATHY; item = ITEM_MISTY_SEED; } + PARAMETRIZE { ability = ABILITY_MISTY_SURGE; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_MISTY_SURGE; item = ITEM_MISTY_SEED; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_MISTY_SEED); } + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_MISTY_SEED); } + OPPONENT(SPECIES_TAPU_FINI) { Ability(ability); Items(ITEM_PECHA_BERRY, item); } + } WHEN { + if (ability == ABILITY_TELEPATHY) + TURN { MOVE(player, MOVE_MISTY_TERRAIN); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Misty Seed, the Sp. Def of Wobbuffet rose!"); + if (item == ITEM_MISTY_SEED) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Using Misty Seed, the Sp. Def of the opposing Tapu Fini rose!"); + } + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Wobbuffet"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Misty Seed, the Sp. Def of Wobbuffet rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Psychic Seed raises the holder's Sp. Defense on Psychic Terrain (Multi)") +{ + enum Ability ability; + u32 item; + PARAMETRIZE { ability = ABILITY_TELEPATHY; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_TELEPATHY; item = ITEM_PSYCHIC_SEED; } + PARAMETRIZE { ability = ABILITY_PSYCHIC_SURGE; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_PSYCHIC_SURGE; item = ITEM_PSYCHIC_SEED; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_PSYCHIC_SEED); } + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_PSYCHIC_SEED); } + OPPONENT(SPECIES_TAPU_LELE) { Ability(ability); Items(ITEM_PECHA_BERRY, item); } + } WHEN { + if (ability == ABILITY_TELEPATHY) + TURN { MOVE(player, MOVE_PSYCHIC_TERRAIN); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Psychic Seed, the Sp. Def of Wobbuffet rose!"); + if (item == ITEM_PSYCHIC_SEED) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Using Psychic Seed, the Sp. Def of the opposing Tapu Lele rose!"); + } + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Wobbuffet"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Psychic Seed, the Sp. Def of Wobbuffet rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Seeds get consumed in Terrain even if holder is not affected by Terrain (Multi)") +{ + u32 species, item; + enum Ability ability; + PARAMETRIZE { species = SPECIES_TAPU_KOKO; ability = ABILITY_ELECTRIC_SURGE; item = ITEM_ELECTRIC_SEED; } + PARAMETRIZE { species = SPECIES_TAPU_BULU; ability = ABILITY_GRASSY_SURGE; item = ITEM_GRASSY_SEED; } + PARAMETRIZE { species = SPECIES_TAPU_FINI; ability = ABILITY_MISTY_SURGE; item = ITEM_MISTY_SEED; } + PARAMETRIZE { species = SPECIES_TAPU_LELE; ability = ABILITY_PSYCHIC_SURGE; item = ITEM_PSYCHIC_SEED; } + GIVEN { + ASSUME(GetSpeciesType(SPECIES_PIDGEY, 0) == TYPE_FLYING || GetSpeciesType(SPECIES_PIDGEY, 1) == TYPE_FLYING); + PLAYER(SPECIES_PIDGEY) { Items(ITEM_PECHA_BERRY, item); } + OPPONENT(species) { Ability(ability); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ability); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Electric Seed is consumed on Electric Terrain before other abilities change the terrain (Multi)") +{ + GIVEN { + PLAYER(SPECIES_TAPU_BULU) { Ability(ABILITY_GRASSY_SURGE); Items(ITEM_PECHA_BERRY, ITEM_ELECTRIC_SEED); Speed(5); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); Items(ITEM_PECHA_BERRY, ITEM_ELECTRIC_SEED); Speed(10); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Using Electric Seed, the Defense of the opposing Tapu Koko rose!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Electric Seed, the Defense of Tapu Bulu rose!"); + ABILITY_POPUP(player, ABILITY_GRASSY_SURGE); + } +} + +SINGLE_BATTLE_TEST("Electric Seed doesn't activate on existing Electric Terrain before user's ability changes the terrain (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_TAPU_BULU) { Ability(ABILITY_GRASSY_SURGE); Items(ITEM_PECHA_BERRY, ITEM_ELECTRIC_SEED); } + OPPONENT(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); } + } WHEN { + TURN { SWITCH(player, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + SWITCH_OUT_MESSAGE("Wobbuffet"); + SEND_IN_MESSAGE("Tapu Bulu"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Electric Seed, the Defense of Tapu Bulu rose!"); + } + ABILITY_POPUP(player, ABILITY_GRASSY_SURGE); + } +} +#endif diff --git a/test/battle/hold_effect/throat_spray.c b/test/battle/hold_effect/throat_spray.c index a8e6cc5c1000..9bc2d7d94779 100644 --- a/test/battle/hold_effect/throat_spray.c +++ b/test/battle/hold_effect/throat_spray.c @@ -117,3 +117,131 @@ SINGLE_BATTLE_TEST("Throat Spray is not blocked by Sheer Force") ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Throat Spray is not blocked by Sheer Force (Traits)") +{ + GIVEN { + PLAYER(SPECIES_NIDOKING) { Ability(ABILITY_RIVALRY); Innates(ABILITY_SHEER_FORCE); Item(ITEM_THROAT_SPRAY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BUG_BUZZ); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BUG_BUZZ, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +DOUBLE_BATTLE_TEST("Throat Spray activates after both hits of a spread move (Multi)") +{ + s16 firstHit, secondHit; + + GIVEN { + ASSUME(GetMoveTarget(MOVE_HYPER_VOICE) == MOVE_TARGET_BOTH); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_THROAT_SPRAY); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_HYPER_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &firstHit); + HP_BAR(opponentRight, captureDamage: &secondHit); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + } THEN { + EXPECT_EQ(firstHit, secondHit); + } +} + +SINGLE_BATTLE_TEST("Throat Spray increases Sp. Atk by one stage (Multi)") +{ + s16 normalHit; + s16 boostedHit; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_THROAT_SPRAY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HYPER_VOICE); } + TURN { MOVE(player, MOVE_HYPER_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, player); + HP_BAR(opponent, captureDamage: &normalHit); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, player); + HP_BAR(opponent, captureDamage: &boostedHit); + } THEN { + EXPECT_MUL_EQ(normalHit, Q_4_12(1.5), boostedHit); + } +} + +SINGLE_BATTLE_TEST("Throat Spray activates when a sound move is used (Multi)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SWIFT; } + PARAMETRIZE { move = MOVE_HYPER_VOICE; } + + GIVEN { + ASSUME(IsSoundMove(MOVE_SWIFT) != IsSoundMove(MOVE_HYPER_VOICE)); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_THROAT_SPRAY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + if (move == MOVE_HYPER_VOICE) + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + else + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } +} + +SINGLE_BATTLE_TEST("Throat Spray does not activate if move fails (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_THROAT_SPRAY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_PROTECT); MOVE(player, MOVE_HYPER_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } + } +} + +SINGLE_BATTLE_TEST("Throat Spray does not activate if user flinches (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_THROAT_SPRAY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_FAKE_OUT); MOVE(player, MOVE_HYPER_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FAKE_OUT, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } + } +} + +SINGLE_BATTLE_TEST("Throat Spray is not blocked by Sheer Force (Multi)") +{ + GIVEN { + PLAYER(SPECIES_NIDOKING) { Ability(ABILITY_SHEER_FORCE); Items(ITEM_PECHA_BERRY, ITEM_THROAT_SPRAY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BUG_BUZZ); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BUG_BUZZ, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } +} +#endif diff --git a/test/battle/hold_effect/type_power.c b/test/battle/hold_effect/type_power.c index c03d2487d573..834ed2d65257 100644 --- a/test/battle/hold_effect/type_power.c +++ b/test/battle/hold_effect/type_power.c @@ -77,3 +77,60 @@ SINGLE_BATTLE_TEST("Type-enhancing items do not increase the power of Struggle", EXPECT_EQ(results[0].damage, results[1].damage); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Type-enhancing items increase the base power of moves by 20% (Multi)", s16 damage) +{ + u32 move = 0, item = 0, type = 0; + + for (u32 j = 0; j < ARRAY_COUNT(sMoveItemTable); j++) { + PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; item = ITEM_NONE; } + PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; item = sMoveItemTable[j][2]; } + } + + GIVEN { + ASSUME(GetMovePower(move) > 0); + if (item != ITEM_NONE) { + ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_TYPE_POWER); + ASSUME(GetItemSecondaryId(item) == type); + } + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + for (u32 j = 0; j < ARRAY_COUNT(sMoveItemTable); j++) { + if (I_TYPE_BOOST_POWER >= GEN_4) + EXPECT_MUL_EQ(results[j*2].damage, Q_4_12(1.2), results[(j*2)+1].damage); + else + EXPECT_MUL_EQ(results[j*2].damage, Q_4_12(1.1), results[(j*2)+1].damage); + } + } +} + +SINGLE_BATTLE_TEST("Type-enhancing items do not increase the power of Struggle (Multi)", s16 damage) +{ + u32 item = 0; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_SILK_SCARF; } + + GIVEN { + if (item != ITEM_NONE) { + ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_TYPE_POWER); + ASSUME(GetItemSecondaryId(item) == GetMoveType(MOVE_STRUGGLE)); + } + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STRUGGLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} +#endif diff --git a/test/battle/hold_effect/utility_umbrella.c b/test/battle/hold_effect/utility_umbrella.c index 83bb0539f4c0..d956a1028123 100644 --- a/test/battle/hold_effect/utility_umbrella.c +++ b/test/battle/hold_effect/utility_umbrella.c @@ -54,3 +54,49 @@ SINGLE_BATTLE_TEST("Utility Umbrella blocks Rain damage modifiers", s16 damage) } // Moves and abilities affected by Utility Umbrella have their tests in the respective files + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Utility Umbrella blocks Sun damage modifiers (Multi)", s16 damage) +{ + u16 setupMove, attackingMove, heldItem; + PARAMETRIZE { setupMove = MOVE_SUNNY_DAY; attackingMove = MOVE_EMBER; heldItem = ITEM_UTILITY_UMBRELLA; } + PARAMETRIZE { setupMove = MOVE_SUNNY_DAY; attackingMove = MOVE_EMBER; heldItem = ITEM_NONE; } + PARAMETRIZE { setupMove = MOVE_SUNNY_DAY; attackingMove = MOVE_WATER_GUN; heldItem = ITEM_UTILITY_UMBRELLA; } + PARAMETRIZE { setupMove = MOVE_SUNNY_DAY; attackingMove = MOVE_WATER_GUN; heldItem = ITEM_NONE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, heldItem); }; + } WHEN { + TURN { MOVE(opponent, setupMove); } + TURN { MOVE(player, attackingMove); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, attackingMove, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + EXPECT_MUL_EQ(results[2].damage, Q_4_12(0.5), results[3].damage); + } +} + +SINGLE_BATTLE_TEST("Utility Umbrella blocks Rain damage modifiers (Multi)", s16 damage) +{ + u16 setupMove, attackingMove, heldItem; + PARAMETRIZE { setupMove = MOVE_RAIN_DANCE; attackingMove = MOVE_EMBER; heldItem = ITEM_UTILITY_UMBRELLA; } + PARAMETRIZE { setupMove = MOVE_RAIN_DANCE; attackingMove = MOVE_EMBER; heldItem = ITEM_NONE; } + PARAMETRIZE { setupMove = MOVE_RAIN_DANCE; attackingMove = MOVE_WATER_GUN; heldItem = ITEM_UTILITY_UMBRELLA; } + PARAMETRIZE { setupMove = MOVE_RAIN_DANCE; attackingMove = MOVE_WATER_GUN; heldItem = ITEM_NONE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, heldItem); }; + } WHEN { + TURN { MOVE(opponent, setupMove); } + TURN { MOVE(player, attackingMove); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, attackingMove, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.5), results[1].damage); + EXPECT_MUL_EQ(results[2].damage, Q_4_12(1.5), results[3].damage); + } +} +#endif diff --git a/test/battle/hold_effect/weakness_policy.c b/test/battle/hold_effect/weakness_policy.c index d080e9ad650a..129ef13e8c5f 100644 --- a/test/battle/hold_effect/weakness_policy.c +++ b/test/battle/hold_effect/weakness_policy.c @@ -29,3 +29,27 @@ SINGLE_BATTLE_TEST("Weakness Policy does not activate if Disguise blocks the dam } TO_DO_BATTLE_TEST("TODO: Write Weakness Policy (Hold Effect) test titles") + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Weakness Policy does not activate if Disguise blocks the damage (Multi)") +{ + u32 species; + + PARAMETRIZE { species = SPECIES_MIMIKYU_BUSTED; } + PARAMETRIZE { species = SPECIES_MIMIKYU_DISGUISED; } + + GIVEN { + ASSUME(GetMoveType(MOVE_METAL_CLAW) == TYPE_STEEL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Items(ITEM_PECHA_BERRY, ITEM_WEAKNESS_POLICY); Ability(ABILITY_DISGUISE); } + } WHEN { + TURN { MOVE(player, MOVE_METAL_CLAW); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_METAL_CLAW, player); + if (species == SPECIES_MIMIKYU_BUSTED) + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + else + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} +#endif diff --git a/test/battle/hold_effect/white_herb.c b/test/battle/hold_effect/white_herb.c index fe4a34ad9777..9928dc0e2f4e 100644 --- a/test/battle/hold_effect/white_herb.c +++ b/test/battle/hold_effect/white_herb.c @@ -234,3 +234,429 @@ DOUBLE_BATTLE_TEST("White Herb is correctly displayed") EXPECT(playerLeft->statStages[STAT_DEF] = DEFAULT_STAT_STAGE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("White Herb restores stats after Attack was lowered by Intimidate in singles (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_WHITE_HERB); } + OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + } WHEN { + TURN { ; } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet returned its stats to normal using its White Herb!"); + } THEN { + EXPECT(player->item == ITEM_NONE); + EXPECT(player->statStages[STAT_DEF] = DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("White Herb restores stats after Attack was lowered by Intimidate in doubles (Traits)") +{ + GIVEN { + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_WHITE_HERB); } + OPPONENT(SPECIES_WYNAUT) { Item(ITEM_WHITE_HERB); } + PLAYER(SPECIES_ARBOK) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + PLAYER(SPECIES_WOBBUFFET); + } WHEN { + TURN { ; } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + MESSAGE("The opposing Wobbuffet returned its stats to normal using its White Herb!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + MESSAGE("The opposing Wynaut returned its stats to normal using its White Herb!"); + } THEN { + EXPECT(opponentLeft->item == ITEM_NONE); + EXPECT(opponentLeft->statStages[STAT_DEF] = DEFAULT_STAT_STAGE); + EXPECT(opponentRight->item == ITEM_NONE); + EXPECT(opponentRight->statStages[STAT_DEF] = DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("White Herb restores stats after Attack was lowered by Intimidate while switching in (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_WHITE_HERB); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + } WHEN { + TURN { SWITCH(opponent, 1); MOVE(player, MOVE_CLOSE_COMBAT); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet returned its stats to normal using its White Herb!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CLOSE_COMBAT, player); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet returned its stats to normal using its White Herb!"); + } + } THEN { + EXPECT(player->item == ITEM_NONE); + EXPECT(player->statStages[STAT_DEF] = DEFAULT_STAT_STAGE - 1); + EXPECT(player->statStages[STAT_SPDEF] = DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("White Herb restores stats after all hits of a multi hit move happened (Traits)") +{ + u16 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_SLIGGOO_HISUI; ability = ABILITY_GOOEY; } + PARAMETRIZE { species = SPECIES_DUGTRIO_ALOLA; ability = ABILITY_TANGLING_HAIR; } + + GIVEN { + ASSUME(GetMoveStrikeCount(MOVE_DUAL_WINGBEAT) == 2); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_WHITE_HERB); } + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_DUAL_WINGBEAT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DUAL_WINGBEAT, player); + ABILITY_POPUP(opponent, ability); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Wobbuffet's Speed fell!"); + ABILITY_POPUP(opponent, ability); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Wobbuffet's Speed fell!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet returned its stats to normal using its White Herb!"); + } THEN { + EXPECT(player->item == ITEM_NONE); + EXPECT(player->statStages[STAT_SPEED] = DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("White Herb wont have time to activate if it is knocked off or stolen by Thief (Traits)") +{ + u16 move; + + PARAMETRIZE { move = MOVE_THIEF; } + PARAMETRIZE { move = MOVE_KNOCK_OFF; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_KNOCK_OFF) == EFFECT_KNOCK_OFF); + ASSUME(GetMoveEffect(MOVE_THIEF) == EFFECT_STEAL_ITEM); + PLAYER(SPECIES_SLUGMA) { Ability(ABILITY_FLAME_BODY); Innates(ABILITY_WEAK_ARMOR); Item(ITEM_WHITE_HERB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ABILITY_POPUP(player, ABILITY_WEAK_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Slugma's Weak Armor lowered its Defense!"); + MESSAGE("Slugma's Weak Armor sharply raised its Speed!"); + if (move == MOVE_KNOCK_OFF) { + MESSAGE("The opposing Wobbuffet knocked off Slugma's White Herb!"); + } else if (move == MOVE_THIEF) { + MESSAGE("The opposing Wobbuffet stole Slugma's White Herb!"); + } + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Slugma returned its stats to normal using its White Herb!"); + } + } THEN { + EXPECT(player->statStages[STAT_DEF] = DEFAULT_STAT_STAGE - 1); + EXPECT(player->statStages[STAT_SPEED] = DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("White Herb wont have time to activate if Magician steals it (Traits)") +{ + GIVEN { + PLAYER(SPECIES_SLUGMA) { Ability(ABILITY_FLAME_BODY); Innates(ABILITY_WEAK_ARMOR); Item(ITEM_WHITE_HERB); } + OPPONENT(SPECIES_FENNEKIN) { Ability(ABILITY_BLAZE); Innates(ABILITY_MAGICIAN); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(player, ABILITY_WEAK_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Slugma's Weak Armor lowered its Defense!"); + MESSAGE("Slugma's Weak Armor sharply raised its Speed!"); + ABILITY_POPUP(opponent, ABILITY_MAGICIAN); + MESSAGE("The opposing Fennekin stole Slugma's White Herb!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Slugma returned its stats to normal using its White Herb!"); + } + } THEN { + EXPECT(player->statStages[STAT_DEF] = DEFAULT_STAT_STAGE - 1); + EXPECT(player->statStages[STAT_SPEED] = DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("White Herb has correct interactions with Intimidate triggered Defiant and Competitive (Traits)") +{ + u16 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_IGGLYBUFF; ability = ABILITY_COMPETITIVE; } + PARAMETRIZE { species = SPECIES_MANKEY; ability = ABILITY_DEFIANT; } + + GIVEN { + PLAYER(species) { Ability(ability); Item(ITEM_WHITE_HERB); } + OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + } WHEN { + TURN { ; } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + ABILITY_POPUP(player, ability); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + // Defiant activates first, so White Herb doesn't have a chance to trigger. + if (ability == ABILITY_COMPETITIVE) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Igglybuff returned its stats to normal using its White Herb!"); + } + } THEN { + if (ability == ABILITY_COMPETITIVE) { + EXPECT(player->item == ITEM_NONE); + EXPECT(player->statStages[STAT_ATK] = DEFAULT_STAT_STAGE); + EXPECT(player->statStages[STAT_SPATK] = DEFAULT_STAT_STAGE + 2); + } else { + EXPECT(player->statStages[STAT_ATK] = DEFAULT_STAT_STAGE + 1); + } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("White Herb restores stats when they're lowered (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_LEER) == EFFECT_DEFENSE_DOWN); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_WHITE_HERB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_LEER); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet returned its stats to normal using its White Herb!"); + } THEN { + EXPECT(player->item == ITEM_NONE); + EXPECT(player->statStages[STAT_DEF] = DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("White Herb restores stats after Attack was lowered by Intimidate in singles (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_WHITE_HERB); } + OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_INTIMIDATE); } + } WHEN { + TURN { ; } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet returned its stats to normal using its White Herb!"); + } THEN { + EXPECT(player->item == ITEM_NONE); + EXPECT(player->statStages[STAT_DEF] = DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("White Herb restores stats after Attack was lowered by Intimidate in doubles (Multi)") +{ + GIVEN { + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_WHITE_HERB); } + OPPONENT(SPECIES_WYNAUT) { Items(ITEM_PECHA_BERRY, ITEM_WHITE_HERB); } + PLAYER(SPECIES_ARBOK) { Ability(ABILITY_INTIMIDATE); } + PLAYER(SPECIES_WOBBUFFET); + } WHEN { + TURN { ; } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + MESSAGE("The opposing Wobbuffet returned its stats to normal using its White Herb!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + MESSAGE("The opposing Wynaut returned its stats to normal using its White Herb!"); + } THEN { + EXPECT(opponentLeft->item == ITEM_NONE); + EXPECT(opponentLeft->statStages[STAT_DEF] = DEFAULT_STAT_STAGE); + EXPECT(opponentRight->item == ITEM_NONE); + EXPECT(opponentRight->statStages[STAT_DEF] = DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("White Herb restores stats after Attack was lowered by Intimidate while switching in (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_WHITE_HERB); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_INTIMIDATE); } + } WHEN { + TURN { SWITCH(opponent, 1); MOVE(player, MOVE_CLOSE_COMBAT); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet returned its stats to normal using its White Herb!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CLOSE_COMBAT, player); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet returned its stats to normal using its White Herb!"); + } + } THEN { + EXPECT(player->item == ITEM_NONE); + EXPECT(player->statStages[STAT_DEF] = DEFAULT_STAT_STAGE - 1); + EXPECT(player->statStages[STAT_SPDEF] = DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("White Herb restores stats after all hits of a multi hit move happened (Multi)") +{ + u16 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_SLIGGOO_HISUI; ability = ABILITY_GOOEY; } + PARAMETRIZE { species = SPECIES_DUGTRIO_ALOLA; ability = ABILITY_TANGLING_HAIR; } + + GIVEN { + ASSUME(GetMoveStrikeCount(MOVE_DUAL_WINGBEAT) == 2); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_WHITE_HERB); } + OPPONENT(species) { Ability(ability); } + } WHEN { + TURN { MOVE(player, MOVE_DUAL_WINGBEAT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DUAL_WINGBEAT, player); + ABILITY_POPUP(opponent, ability); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Wobbuffet's Speed fell!"); + ABILITY_POPUP(opponent, ability); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Wobbuffet's Speed fell!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet returned its stats to normal using its White Herb!"); + } THEN { + EXPECT(player->item == ITEM_NONE); + EXPECT(player->statStages[STAT_SPEED] = DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("White Herb wont have time to activate if it is knocked off or stolen by Thief (Multi)") +{ + u16 move; + + PARAMETRIZE { move = MOVE_THIEF; } + PARAMETRIZE { move = MOVE_KNOCK_OFF; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_KNOCK_OFF) == EFFECT_KNOCK_OFF); + ASSUME(GetMoveEffect(MOVE_THIEF) == EFFECT_STEAL_ITEM); + PLAYER(SPECIES_SLUGMA) { Ability(ABILITY_WEAK_ARMOR); Items(ITEM_PECHA_BERRY, ITEM_WHITE_HERB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ABILITY_POPUP(player, ABILITY_WEAK_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Slugma's Weak Armor lowered its Defense!"); + MESSAGE("Slugma's Weak Armor sharply raised its Speed!"); + if (move == MOVE_KNOCK_OFF) { + MESSAGE("The opposing Wobbuffet knocked off Slugma's White Herb!"); + } else if (move == MOVE_THIEF) { + MESSAGE("The opposing Wobbuffet stole Slugma's White Herb!"); + } + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Slugma returned its stats to normal using its White Herb!"); + } + } THEN { + EXPECT(player->statStages[STAT_DEF] = DEFAULT_STAT_STAGE - 1); + EXPECT(player->statStages[STAT_SPEED] = DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("White Herb wont have time to activate if Magician steals it (Multi)") +{ + GIVEN { + PLAYER(SPECIES_SLUGMA) { Ability(ABILITY_WEAK_ARMOR); Items(ITEM_PECHA_BERRY, ITEM_WHITE_HERB); } + OPPONENT(SPECIES_FENNEKIN) { Ability(ABILITY_MAGICIAN); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(player, ABILITY_WEAK_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Slugma's Weak Armor lowered its Defense!"); + MESSAGE("Slugma's Weak Armor sharply raised its Speed!"); + ABILITY_POPUP(opponent, ABILITY_MAGICIAN); + MESSAGE("The opposing Fennekin stole Slugma's White Herb!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Slugma returned its stats to normal using its White Herb!"); + } + } THEN { + EXPECT(player->statStages[STAT_DEF] = DEFAULT_STAT_STAGE - 1); + EXPECT(player->statStages[STAT_SPEED] = DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("White Herb has correct interactions with Intimidate triggered Defiant and Competitive (Multi)") +{ + u16 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_IGGLYBUFF; ability = ABILITY_COMPETITIVE; } + PARAMETRIZE { species = SPECIES_MANKEY; ability = ABILITY_DEFIANT; } + + GIVEN { + PLAYER(species) { Ability(ability); Items(ITEM_PECHA_BERRY, ITEM_WHITE_HERB); } + OPPONENT(SPECIES_ARBOK) { Ability(ABILITY_INTIMIDATE); } + } WHEN { + TURN { ; } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + ABILITY_POPUP(player, ability); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + // Defiant activates first, so White Herb doesn't have a chance to trigger. + if (ability == ABILITY_COMPETITIVE) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Igglybuff returned its stats to normal using its White Herb!"); + } + } THEN { + if (ability == ABILITY_COMPETITIVE) { + EXPECT(player->item == ITEM_NONE); + EXPECT(player->statStages[STAT_ATK] = DEFAULT_STAT_STAGE); + EXPECT(player->statStages[STAT_SPATK] = DEFAULT_STAT_STAGE + 2); + } else { + EXPECT(player->statStages[STAT_ATK] = DEFAULT_STAT_STAGE + 1); + } + } +} + +DOUBLE_BATTLE_TEST("White Herb is correctly displayed (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { Items(ITEM_PECHA_BERRY, ITEM_WHITE_HERB); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerRight, MOVE_SUPERPOWER, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); + MESSAGE("Wynaut returned its stats to normal using its White Herb!"); + } THEN { + EXPECT(playerLeft->item == ITEM_NONE); + EXPECT(playerLeft->statStages[STAT_DEF] = DEFAULT_STAT_STAGE); + } +} +#endif diff --git a/test/battle/item_effect/escape.c b/test/battle/item_effect/escape.c index dd27c425c244..785ea0ba9bba 100644 --- a/test/battle/item_effect/escape.c +++ b/test/battle/item_effect/escape.c @@ -48,3 +48,17 @@ WILD_BATTLE_TEST("Poke Toy lets the player escape from a wild battle even if an MESSAGE("{PLAY_SE SE_FLEE}You got away safely!\p"); } } + +#if MAX_MON_TRAITS > 1 +WILD_BATTLE_TEST("Poke Toy lets the player escape from a wild battle even if an ability forbid them to (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_DIGLETT) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_ARENA_TRAP); } + } WHEN { + TURN { USE_ITEM(player, ITEM_POKE_TOY); } + } SCENE { + MESSAGE("{PLAY_SE SE_FLEE}You got away safely!\p"); + } +} +#endif diff --git a/test/battle/item_effect/poke_flute.c b/test/battle/item_effect/poke_flute.c index c9aebed5d389..488c1d8e5894 100644 --- a/test/battle/item_effect/poke_flute.c +++ b/test/battle/item_effect/poke_flute.c @@ -41,3 +41,25 @@ DOUBLE_BATTLE_TEST("Poke Flute does not heal battlers with Soundproof from being EXPECT_NE(opponentRight->status1, STATUS1_NONE); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Poke Flute does not heal battlers with Soundproof from being asleep (Traits)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_POKE_FLUTE].battleUsage == EFFECT_ITEM_USE_POKE_FLUTE); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); } + PLAYER(SPECIES_EXPLOUD) { Ability(ABILITY_SCRAPPY); Innates(ABILITY_SOUNDPROOF); Status1(STATUS1_SLEEP); } + OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); } + OPPONENT(SPECIES_EXPLOUD) { Ability(ABILITY_SCRAPPY); Innates(ABILITY_SOUNDPROOF); Status1(STATUS1_SLEEP); } + } WHEN { + TURN { USE_ITEM(playerLeft, ITEM_POKE_FLUTE, partyIndex: 0); } + } SCENE { + MESSAGE("The Pokémon hearing the flute awoke!"); + } THEN { + EXPECT_EQ(playerLeft->status1, STATUS1_NONE); + EXPECT_NE(playerRight->status1, STATUS1_NONE); + EXPECT_EQ(opponentLeft->status1, STATUS1_NONE); + EXPECT_NE(opponentRight->status1, STATUS1_NONE); + } +} +#endif diff --git a/test/battle/item_effect/revive.c b/test/battle/item_effect/revive.c index f12c208fb0e5..33f0ef3f868f 100644 --- a/test/battle/item_effect/revive.c +++ b/test/battle/item_effect/revive.c @@ -224,3 +224,80 @@ DOUBLE_BATTLE_TEST("Revive force revived pokemon to replace absent battler immed } TO_DO_BATTLE_TEST("Revive won't restore a battler's HP if it hasn't fainted") + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Revive can trigger switch-in abilities (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ARBOK) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); } + TURN { USE_ITEM(playerRight, ITEM_REVIVE, partyIndex: 0); SKIP_TURN(playerLeft); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } THEN { + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 2); + EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 2); + } +} + +DOUBLE_BATTLE_TEST("Revive does reset abilities (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ARBOK) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentRight, MOVE_WORRY_SEED, target: playerLeft); MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); } + TURN { USE_ITEM(playerRight, ITEM_REVIVE, partyIndex: 0); SKIP_TURN(playerLeft); MOVE(opponentRight, MOVE_SPORE, target: playerLeft);} + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } THEN { + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 2); + EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 2); + } +} + +DOUBLE_BATTLE_TEST("Revive force revived pokemon to replace absent battler immediately (Traits)", s16 damage) +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_INTIMIDATE; } + PARAMETRIZE { ability = ABILITY_SHED_SKIN; } + + GIVEN { + PLAYER(SPECIES_WYNAUT) { HP(1); } + PLAYER(SPECIES_WOBBUFFET) { }; + PLAYER(SPECIES_ARBOK) { Ability(ABILITY_UNNERVE); Innates(ability); HP(0) ;} ; + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft);} + TURN { USE_ITEM(playerRight, ITEM_REVIVE, partyIndex: 2); SKIP_TURN(playerLeft); MOVE(opponentRight, MOVE_SCRATCH, target: playerRight); } + } SCENE { + if (ability == ABILITY_INTIMIDATE) + { + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentRight); + HP_BAR(playerRight, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} +#endif diff --git a/test/battle/move_animations/all_anims.c b/test/battle/move_animations/all_anims.c index d77cb5ec4c4a..74eaf423520c 100644 --- a/test/battle/move_animations/all_anims.c +++ b/test/battle/move_animations/all_anims.c @@ -2227,4 +2227,1538 @@ DOUBLE_BATTLE_TEST("Tera Blast doesn't leak when used - Doubles (opponentRight t } } +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Move Animations don't leak when used - Singles (player to opponent) (Multi)") +{ + u32 j = ANIM_TEST_START_MOVE, move = 0, species = 0; + u32 k = 0, variation = 0, variationsNumber; + u32 friendship = 0, tempFriendship; + u32 tempMove, tempSpecies; + FORCE_MOVE_ANIM(TRUE); + for (; j <= ANIM_TEST_END_MOVE; j++) { + variationsNumber = GetVariationsNumber(j, FALSE); + for (k = 0; k < variationsNumber; k++) { + ParametrizeMovesAndSpecies(j, &tempMove, &tempSpecies, k); + tempFriendship = ParametrizeFriendship(j, k); + PARAMETRIZE { move = tempMove; species = tempSpecies; variation = k; friendship = tempFriendship;} + } + } + GIVEN { + PLAYER(species) { + Level(GetParametrizedLevel(move, variation)); + HP(GetParametrizedHP(move, variation)); MaxHP(9999); Items(ITEM_NONE, GetParametrizedItem(move, variation)); + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + if (friendship) Friendship(friendship); + if (GetParametrizedShinyness(move, variation)) Shiny(TRUE); + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); MaxHP(9999); Moves(MOVE_POUND); + HP(GetMoveEffect(move) == EFFECT_REVIVAL_BLESSING ? 0 : 9998); + } + OPPONENT(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + OPPONENT(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); } + } WHEN { + WhenSingles(move, player, opponent, variation); + } SCENE { + SceneSingles(move, player); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +SINGLE_BATTLE_TEST("Move Animations don't leak when used - Singles (opponent to player) (Multi)") +{ + u32 j = ANIM_TEST_START_MOVE, move = 0, species = 0; + u32 k = 0, variation = 0, variationsNumber; + u32 friendship = 0, tempFriendship; + u32 tempMove, tempSpecies; + FORCE_MOVE_ANIM(TRUE); + for (; j <= ANIM_TEST_END_MOVE; j++) { + variationsNumber = GetVariationsNumber(j, FALSE); + for (k = 0; k < variationsNumber; k++) { + ParametrizeMovesAndSpecies(j, &tempMove, &tempSpecies, k); + tempFriendship = ParametrizeFriendship(j, k); + PARAMETRIZE { move = tempMove; species = tempSpecies; variation = k; friendship = tempFriendship;} + } + } + GIVEN { + OPPONENT(species) { + Level(GetParametrizedLevel(move, variation)); + HP(GetParametrizedHP(move, variation)); MaxHP(9999); Items(ITEM_NONE, GetParametrizedItem(move, variation)); + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + if (friendship) Friendship(friendship); + if (GetParametrizedShinyness(move, variation)) Shiny(TRUE); + } + OPPONENT(SPECIES_WOBBUFFET) { + Gender(MON_MALE); MaxHP(9999); Moves(MOVE_POUND); + HP(GetMoveEffect(move) == EFFECT_REVIVAL_BLESSING ? 0 : 9998); + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + PLAYER(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); } + } WHEN { + WhenSingles(move, opponent, player, variation); + } SCENE { + SceneSingles(move, opponent); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (playerLeft to opponentLeft) (Multi)") +{ + u32 j = ANIM_TEST_START_MOVE, move = 0, species = 0; + u32 k = 0, variation = 0, variationsNumber; + u32 friendship = 0, tempFriendship; + u32 tempMove, tempSpecies; + FORCE_MOVE_ANIM(TRUE); + struct BattlePokemon *attacker = playerLeft; + struct BattlePokemon *target = opponentLeft; + struct BattlePokemon *ignore1 = playerRight; + struct BattlePokemon *ignore2 = opponentRight; + for (; j <= ANIM_TEST_END_MOVE; j++) { + variationsNumber = GetVariationsNumber(j, TRUE); + for (k = 0; k < variationsNumber; k++) { + ParametrizeMovesAndSpecies(j, &tempMove, &tempSpecies, k); + tempFriendship = ParametrizeFriendship(j, k); + PARAMETRIZE { move = tempMove; species = tempSpecies; variation = k; friendship = tempFriendship;} + } + } + GIVEN { + PLAYER(species) { + Level(GetParametrizedLevel(move, variation)); + HP(GetParametrizedHP(move, variation)); MaxHP(9999); Items(ITEM_NONE, GetParametrizedItem(move, variation)); + if (attacker == playerLeft) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + if (friendship) Friendship(friendship); + if (GetParametrizedShinyness(move, variation)) Shiny(TRUE); + } + } + PLAYER(species) { + Level(GetParametrizedLevel(move, variation)); + HP(GetParametrizedHP(move, variation)); MaxHP(9999); Items(ITEM_NONE, GetParametrizedItem(move, variation)); + if (attacker == playerRight) + { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + if (friendship) Friendship(friendship); + if (GetParametrizedShinyness(move, variation)) Shiny(TRUE); + } + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); MaxHP(9999); Moves(MOVE_POUND, MOVE_CELEBRATE); + HP(GetMoveEffect(move) == EFFECT_REVIVAL_BLESSING ? 0 : 9998); + } + OPPONENT(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + OPPONENT(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + OPPONENT(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); } + } WHEN { + DoublesWhen(move, attacker, target, ignore1, ignore2, variation); + } SCENE { + DoublesScene(move, attacker); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (opponentLeft to playerLeft) (Multi)") +{ + u32 j = ANIM_TEST_START_MOVE, move = 0, species = 0; + u32 k = 0, variation = 0, variationsNumber; + u32 friendship = 0, tempFriendship; + u32 tempMove, tempSpecies; + FORCE_MOVE_ANIM(TRUE); + struct BattlePokemon *attacker = opponentLeft; + struct BattlePokemon *target = playerLeft; + struct BattlePokemon *ignore1 = opponentRight; + struct BattlePokemon *ignore2 = playerRight; + for (; j <= ANIM_TEST_END_MOVE; j++) { + variationsNumber = GetVariationsNumber(j, TRUE); + for (k = 0; k < variationsNumber; k++) { + ParametrizeMovesAndSpecies(j, &tempMove, &tempSpecies, k); + tempFriendship = ParametrizeFriendship(j, k); + PARAMETRIZE { move = tempMove; species = tempSpecies; variation = k; friendship = tempFriendship;} + } + } + GIVEN { + OPPONENT(species) { + Level(GetParametrizedLevel(move, variation)); + HP(GetParametrizedHP(move, variation)); MaxHP(9999); Items(ITEM_NONE, GetParametrizedItem(move, variation)); + if (attacker == opponentLeft) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + if (friendship) Friendship(friendship); + if (GetParametrizedShinyness(move, variation)) Shiny(TRUE); + } + } + OPPONENT(species) { + Level(GetParametrizedLevel(move, variation)); + HP(GetParametrizedHP(move, variation)); MaxHP(9999); Items(ITEM_NONE, GetParametrizedItem(move, variation)); + if (attacker == opponentRight) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + if (friendship) Friendship(friendship); + if (GetParametrizedShinyness(move, variation)) Shiny(TRUE); + } + } + OPPONENT(SPECIES_WOBBUFFET) { + Gender(MON_MALE); MaxHP(9999); Moves(MOVE_POUND, MOVE_CELEBRATE); + HP(GetMoveEffect(move) == EFFECT_REVIVAL_BLESSING ? 0 : 9998); + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + PLAYER(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); } + } WHEN { + DoublesWhen(move, attacker, target, ignore1, ignore2, variation); + } SCENE { + DoublesScene(move, attacker); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (playerLeft to opponentRight) (Multi)") +{ + u32 j = ANIM_TEST_START_MOVE, move = 0, species = 0; + u32 k = 0, variation = 0, variationsNumber; + u32 friendship = 0, tempFriendship; + u32 tempMove, tempSpecies; + FORCE_MOVE_ANIM(TRUE); + struct BattlePokemon *attacker = playerLeft; + struct BattlePokemon *target = opponentRight; + struct BattlePokemon *ignore1 = playerRight; + struct BattlePokemon *ignore2 = opponentLeft; + for (; j <= ANIM_TEST_END_MOVE; j++) { + variationsNumber = GetVariationsNumber(j, TRUE); + for (k = 0; k < variationsNumber; k++) { + ParametrizeMovesAndSpecies(j, &tempMove, &tempSpecies, k); + tempFriendship = ParametrizeFriendship(j, k); + PARAMETRIZE { move = tempMove; species = tempSpecies; variation = k; friendship = tempFriendship;} + } + } + GIVEN { + PLAYER(species) { + Level(GetParametrizedLevel(move, variation)); + HP(GetParametrizedHP(move, variation)); MaxHP(9999); Items(ITEM_NONE, GetParametrizedItem(move, variation)); + if (attacker == playerLeft) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + if (friendship) Friendship(friendship); + if (GetParametrizedShinyness(move, variation)) Shiny(TRUE); + } + } + PLAYER(species) { + Level(GetParametrizedLevel(move, variation)); + HP(GetParametrizedHP(move, variation)); MaxHP(9999); Items(ITEM_NONE, GetParametrizedItem(move, variation)); + if (attacker == playerRight) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + if (friendship) Friendship(friendship); + if (GetParametrizedShinyness(move, variation)) Shiny(TRUE); + } + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); MaxHP(9999); Moves(MOVE_POUND, MOVE_CELEBRATE); + HP(GetMoveEffect(move) == EFFECT_REVIVAL_BLESSING ? 0 : 9998); + } + OPPONENT(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + OPPONENT(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + OPPONENT(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); } + } WHEN { + DoublesWhen(move, attacker, target, ignore1, ignore2, variation); + } SCENE { + DoublesScene(move, attacker); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (opponentRight to playerLeft) (Multi)") +{ + u32 j = ANIM_TEST_START_MOVE, move = 0, species = 0; + u32 k = 0, variation = 0, variationsNumber; + u32 friendship = 0, tempFriendship; + u32 tempMove, tempSpecies; + FORCE_MOVE_ANIM(TRUE); + struct BattlePokemon *attacker = opponentRight; + struct BattlePokemon *target = playerLeft; + struct BattlePokemon *ignore1 = opponentLeft; + struct BattlePokemon *ignore2 = playerRight; + for (; j <= ANIM_TEST_END_MOVE; j++) { + variationsNumber = GetVariationsNumber(j, TRUE); + for (k = 0; k < variationsNumber; k++) { + ParametrizeMovesAndSpecies(j, &tempMove, &tempSpecies, k); + tempFriendship = ParametrizeFriendship(j, k); + PARAMETRIZE { move = tempMove; species = tempSpecies; variation = k; friendship = tempFriendship;} + } + } + GIVEN { + OPPONENT(species) { + Level(GetParametrizedLevel(move, variation)); + HP(GetParametrizedHP(move, variation)); MaxHP(9999); Items(ITEM_NONE, GetParametrizedItem(move, variation)); + if (attacker == opponentLeft) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + if (friendship) Friendship(friendship); + if (GetParametrizedShinyness(move, variation)) Shiny(TRUE); + } + } + OPPONENT(species) { + Level(GetParametrizedLevel(move, variation)); + HP(GetParametrizedHP(move, variation)); MaxHP(9999); Items(ITEM_NONE, GetParametrizedItem(move, variation)); + if (attacker == opponentRight) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + if (friendship) Friendship(friendship); + if (GetParametrizedShinyness(move, variation)) Shiny(TRUE); + } + } + OPPONENT(SPECIES_WOBBUFFET) { + Gender(MON_MALE); MaxHP(9999); Moves(MOVE_POUND, MOVE_CELEBRATE); + HP(GetMoveEffect(move) == EFFECT_REVIVAL_BLESSING ? 0 : 9998); + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + PLAYER(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); } + } WHEN { + DoublesWhen(move, attacker, target, ignore1, ignore2, variation); + } SCENE { + DoublesScene(move, attacker); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (playerRight to opponentLeft) (Multi)") +{ + u32 j = ANIM_TEST_START_MOVE, move = 0, species = 0; + u32 k = 0, variation = 0, variationsNumber; + u32 friendship = 0, tempFriendship; + u32 tempMove, tempSpecies; + FORCE_MOVE_ANIM(TRUE); + struct BattlePokemon *attacker = playerRight; + struct BattlePokemon *target = opponentLeft; + struct BattlePokemon *ignore1 = playerLeft; + struct BattlePokemon *ignore2 = opponentRight; + for (; j <= ANIM_TEST_END_MOVE; j++) { + variationsNumber = GetVariationsNumber(j, TRUE); + for (k = 0; k < variationsNumber; k++) { + ParametrizeMovesAndSpecies(j, &tempMove, &tempSpecies, k); + tempFriendship = ParametrizeFriendship(j, k); + PARAMETRIZE { move = tempMove; species = tempSpecies; variation = k; friendship = tempFriendship;} + } + } + GIVEN { + PLAYER(species) { + Level(GetParametrizedLevel(move, variation)); + HP(GetParametrizedHP(move, variation)); MaxHP(9999); Items(ITEM_NONE, GetParametrizedItem(move, variation)); + if (attacker == playerLeft) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + if (friendship) Friendship(friendship); + if (GetParametrizedShinyness(move, variation)) Shiny(TRUE); + } + } + PLAYER(species) { + Level(GetParametrizedLevel(move, variation)); + HP(GetParametrizedHP(move, variation)); MaxHP(9999); Items(ITEM_NONE, GetParametrizedItem(move, variation)); + if (attacker == playerRight) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + if (friendship) Friendship(friendship); + if (GetParametrizedShinyness(move, variation)) Shiny(TRUE); + } + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); MaxHP(9999); Moves(MOVE_POUND, MOVE_CELEBRATE); + HP(GetMoveEffect(move) == EFFECT_REVIVAL_BLESSING ? 0 : 9998); + } + OPPONENT(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + OPPONENT(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + OPPONENT(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); } + } WHEN { + DoublesWhen(move, attacker, target, ignore1, ignore2, variation); + } SCENE { + DoublesScene(move, attacker); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (opponentLeft to playerRight) (Multi)") +{ + u32 j = ANIM_TEST_START_MOVE, move = 0, species = 0; + u32 k = 0, variation = 0, variationsNumber; + u32 friendship = 0, tempFriendship; + u32 tempMove, tempSpecies; + FORCE_MOVE_ANIM(TRUE); + struct BattlePokemon *attacker = opponentLeft; + struct BattlePokemon *target = playerRight; + struct BattlePokemon *ignore1 = playerLeft; + struct BattlePokemon *ignore2 = opponentRight; + for (; j <= ANIM_TEST_END_MOVE; j++) { + variationsNumber = GetVariationsNumber(j, TRUE); + for (k = 0; k < variationsNumber; k++) { + ParametrizeMovesAndSpecies(j, &tempMove, &tempSpecies, k); + tempFriendship = ParametrizeFriendship(j, k); + PARAMETRIZE { move = tempMove; species = tempSpecies; variation = k; friendship = tempFriendship;} + } + } + GIVEN { + OPPONENT(species) { + Level(GetParametrizedLevel(move, variation)); + HP(GetParametrizedHP(move, variation)); MaxHP(9999); Items(ITEM_NONE, GetParametrizedItem(move, variation)); + if (attacker == opponentLeft) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + if (friendship) Friendship(friendship); + if (GetParametrizedShinyness(move, variation)) Shiny(TRUE); + } + } + OPPONENT(species) { + Level(GetParametrizedLevel(move, variation)); + HP(GetParametrizedHP(move, variation)); MaxHP(9999); Items(ITEM_NONE, GetParametrizedItem(move, variation)); + if (attacker == opponentRight) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + if (friendship) Friendship(friendship); + if (GetParametrizedShinyness(move, variation)) Shiny(TRUE); + } + } + OPPONENT(SPECIES_WOBBUFFET) { + Gender(MON_MALE); MaxHP(9999); Moves(MOVE_POUND, MOVE_CELEBRATE); + HP(GetMoveEffect(move) == EFFECT_REVIVAL_BLESSING ? 0 : 9998); + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + PLAYER(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); } + } WHEN { + DoublesWhen(move, attacker, target, ignore1, ignore2, variation); + } SCENE { + DoublesScene(move, attacker); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (playerRight to opponentRight) (Multi)") +{ + u32 j = ANIM_TEST_START_MOVE, move = 0, species = 0; + u32 k = 0, variation = 0, variationsNumber; + u32 friendship = 0, tempFriendship; + u32 tempMove, tempSpecies; + FORCE_MOVE_ANIM(TRUE); + struct BattlePokemon *attacker = playerRight; + struct BattlePokemon *target = opponentRight; + struct BattlePokemon *ignore1 = playerLeft; + struct BattlePokemon *ignore2 = opponentLeft; + for (; j <= ANIM_TEST_END_MOVE; j++) { + variationsNumber = GetVariationsNumber(j, TRUE); + for (k = 0; k < variationsNumber; k++) { + ParametrizeMovesAndSpecies(j, &tempMove, &tempSpecies, k); + tempFriendship = ParametrizeFriendship(j, k); + PARAMETRIZE { move = tempMove; species = tempSpecies; variation = k; friendship = tempFriendship;} + } + } + GIVEN { + PLAYER(species) { + Level(GetParametrizedLevel(move, variation)); + HP(GetParametrizedHP(move, variation)); MaxHP(9999); Items(ITEM_NONE, GetParametrizedItem(move, variation)); + if (attacker == playerLeft) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + if (friendship) Friendship(friendship); + if (GetParametrizedShinyness(move, variation)) Shiny(TRUE); + } + } + PLAYER(species) { + Level(GetParametrizedLevel(move, variation)); + HP(GetParametrizedHP(move, variation)); MaxHP(9999); Items(ITEM_NONE, GetParametrizedItem(move, variation)); + if (attacker == playerRight) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + if (friendship) Friendship(friendship); + if (GetParametrizedShinyness(move, variation)) Shiny(TRUE); + } + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); MaxHP(9999); Moves(MOVE_POUND, MOVE_CELEBRATE); + HP(GetMoveEffect(move) == EFFECT_REVIVAL_BLESSING ? 0 : 9998); + } + OPPONENT(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + OPPONENT(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + OPPONENT(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); } + } WHEN { + DoublesWhen(move, attacker, target, ignore1, ignore2, variation); + } SCENE { + DoublesScene(move, attacker); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (opponentRight to playerRight) (Multi)") +{ + u32 j = ANIM_TEST_START_MOVE, move = 0, species = 0; + u32 k = 0, variation = 0, variationsNumber; + u32 friendship = 0, tempFriendship; + u32 tempMove, tempSpecies; + FORCE_MOVE_ANIM(TRUE); + struct BattlePokemon *attacker = opponentRight; + struct BattlePokemon *target = playerRight; + struct BattlePokemon *ignore1 = playerLeft; + struct BattlePokemon *ignore2 = opponentLeft; + for (; j <= ANIM_TEST_END_MOVE; j++) { + variationsNumber = GetVariationsNumber(j, TRUE); + for (k = 0; k < variationsNumber; k++) { + ParametrizeMovesAndSpecies(j, &tempMove, &tempSpecies, k); + tempFriendship = ParametrizeFriendship(j, k); + PARAMETRIZE { move = tempMove; species = tempSpecies; variation = k; friendship = tempFriendship;} + } + } + GIVEN { + OPPONENT(species) { + Level(GetParametrizedLevel(move, variation)); + HP(GetParametrizedHP(move, variation)); MaxHP(9999); Items(ITEM_NONE, GetParametrizedItem(move, variation)); + if (attacker == opponentLeft) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + if (friendship) Friendship(friendship); + if (GetParametrizedShinyness(move, variation)) Shiny(TRUE); + } + } + OPPONENT(species) { + Level(GetParametrizedLevel(move, variation)); + HP(GetParametrizedHP(move, variation)); MaxHP(9999); Items(ITEM_NONE, GetParametrizedItem(move, variation)); + if (attacker == opponentRight) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + if (friendship) Friendship(friendship); + if (GetParametrizedShinyness(move, variation)) Shiny(TRUE); + } + } + OPPONENT(SPECIES_WOBBUFFET) { + Gender(MON_MALE); MaxHP(9999); Moves(MOVE_POUND, MOVE_CELEBRATE); + HP(GetMoveEffect(move) == EFFECT_REVIVAL_BLESSING ? 0 : 9998); + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + PLAYER(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); } + } WHEN { + DoublesWhen(move, attacker, target, ignore1, ignore2, variation); + } SCENE { + DoublesScene(move, attacker); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +/* +DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (playerLeft to playerRight) (Multi)") +{ + u32 j = ANIM_TEST_START_MOVE, move = 0, species = 0; + u32 tempMove, tempSpecies; + FORCE_MOVE_ANIM(TRUE); + struct BattlePokemon *attacker = playerLeft; + struct BattlePokemon *target = playerRight; + struct BattlePokemon *ignore1 = opponentRight; + struct BattlePokemon *ignore2 = opponentLeft; + for (; j <= ANIM_TEST_END_MOVE; j++) { + ParametrizeMovesAndSpecies(j, &tempMove, &tempSpecies, 0); + PARAMETRIZE { move = tempMove; species = tempSpecies; } + } + GIVEN { + OPPONENT(species) { + HP(9997); MaxHP(9999); Items(ITEM_NONE, ITEM_ORAN_BERRY); + if (attacker == opponentLeft) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + } + } + OPPONENT(species) { + HP(9997); MaxHP(9999); Items(ITEM_NONE, ITEM_ORAN_BERRY); + if (attacker == opponentRight) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + } + } + OPPONENT(SPECIES_WOBBUFFET) { + Gender(MON_MALE); MaxHP(9999); Moves(MOVE_POUND, MOVE_CELEBRATE); + HP(GetMoveEffect(move) == EFFECT_REVIVAL_BLESSING ? 0 : 9998); + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + PLAYER(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); } + } WHEN { + DoublesWhen(move, attacker, target, ignore1, ignore2); + } SCENE { + SameSideTargeting(move, attacker); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + } +} + +DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (playerRight to playerLeft) (Multi)") +{ + u32 j = ANIM_TEST_START_MOVE, move = 0, species = 0; + u32 tempMove, tempSpecies; + FORCE_MOVE_ANIM(TRUE); + struct BattlePokemon *attacker = playerRight; + struct BattlePokemon *target = playerLeft; + struct BattlePokemon *ignore1 = opponentRight; + struct BattlePokemon *ignore2 = opponentLeft; + for (; j <= ANIM_TEST_END_MOVE; j++) { + ParametrizeMovesAndSpecies(j, &tempMove, &tempSpecies, 0); + PARAMETRIZE { move = tempMove; species = tempSpecies; } + } + GIVEN { + OPPONENT(species) { + HP(9997); MaxHP(9999); Items(ITEM_NONE, ITEM_ORAN_BERRY); + if (attacker == opponentLeft) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + } + } + OPPONENT(species) { + HP(9997); MaxHP(9999); Items(ITEM_NONE, ITEM_ORAN_BERRY); + if (attacker == opponentRight) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + } + } + OPPONENT(SPECIES_WOBBUFFET) { + Gender(MON_MALE); MaxHP(9999); Moves(MOVE_POUND, MOVE_CELEBRATE); + HP(GetMoveEffect(move) == EFFECT_REVIVAL_BLESSING ? 0 : 9998); + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + PLAYER(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); } + } WHEN { + DoublesWhen(move, attacker, target, ignore1, ignore2); + } SCENE { + SameSideTargeting(move, attacker); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + } +} + +DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (opponentleft to opponentRight) (Multi)") +{ + u32 j = ANIM_TEST_START_MOVE, move = 0, species = 0; + u32 tempMove, tempSpecies; + FORCE_MOVE_ANIM(TRUE); + struct BattlePokemon *attacker = opponentLeft; + struct BattlePokemon *target = opponentRight; + struct BattlePokemon *ignore1 = playerLeft; + struct BattlePokemon *ignore2 = playerRight; + for (; j <= ANIM_TEST_END_MOVE; j++) { + ParametrizeMovesAndSpecies(j, &tempMove, &tempSpecies, 0); + PARAMETRIZE { move = tempMove; species = tempSpecies; } + } + GIVEN { + OPPONENT(species) { + HP(9997); MaxHP(9999); Items(ITEM_NONE, ITEM_ORAN_BERRY); + if (attacker == opponentLeft) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + } + } + OPPONENT(species) { + HP(9997); MaxHP(9999); Items(ITEM_NONE, ITEM_ORAN_BERRY); + if (attacker == opponentRight) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + } + } + OPPONENT(SPECIES_WOBBUFFET) { + Gender(MON_MALE); MaxHP(9999); Moves(MOVE_POUND, MOVE_CELEBRATE); + HP(GetMoveEffect(move) == EFFECT_REVIVAL_BLESSING ? 0 : 9998); + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + PLAYER(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); } + } WHEN { + DoublesWhen(move, attacker, target, ignore1, ignore2); + } SCENE { + SameSideTargeting(move, attacker); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + } +} + +DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (opponentRight to opponentLeft)") +{ + u32 j = ANIM_TEST_START_MOVE, move = 0, species = 0; + u32 tempMove, tempSpecies; + FORCE_MOVE_ANIM(TRUE); + struct BattlePokemon *attacker = opponentRight; + struct BattlePokemon *target = opponentLeft; + struct BattlePokemon *ignore1 = playerLeft; + struct BattlePokemon *ignore2 = playerRight; + for (; j <= ANIM_TEST_END_MOVE; j++) { + ParametrizeMovesAndSpecies(j, &tempMove, &tempSpecies, 0); + PARAMETRIZE { move = tempMove; species = tempSpecies; } + } + GIVEN { + OPPONENT(species) { + HP(9997); MaxHP(9999); Items(ITEM_NONE, ITEM_ORAN_BERRY); + if (attacker == opponentLeft) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + } + } + OPPONENT(species) { + HP(9997); MaxHP(9999); Items(ITEM_NONE, ITEM_ORAN_BERRY); + if (attacker == opponentRight) { + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + } + } + OPPONENT(SPECIES_WOBBUFFET) { + Gender(MON_MALE); MaxHP(9999); Moves(MOVE_POUND, MOVE_CELEBRATE); + HP(GetMoveEffect(move) == EFFECT_REVIVAL_BLESSING ? 0 : 9998); + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) { + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + } + PLAYER(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); } + } WHEN { + DoublesWhen(move, attacker, target, ignore1, ignore2); + } SCENE { + SameSideTargeting(move, attacker); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + } +} +*/ + +SINGLE_BATTLE_TEST("Move Animations occur before their stat change animations - Singles (player to opponent) (Multi)") +{ + u32 j = ANIM_TEST_START_MOVE, move = 0, species = 0; + u32 k = 0, variation = 0, variationsNumber; + u32 friendship = 0, tempFriendship; + u32 tempMove, tempSpecies; + FORCE_MOVE_ANIM(TRUE); + for (; j <= ANIM_TEST_END_MOVE; j++) { + variationsNumber = GetVariationsNumber(j, FALSE); + for (k = 0; k < variationsNumber; k++) { + ParametrizeMovesAndSpecies(j, &tempMove, &tempSpecies, k); + tempFriendship = ParametrizeFriendship(j, k); + PARAMETRIZE { move = tempMove; species = tempSpecies; variation = k; friendship = tempFriendship;} + } + } + GIVEN { + PLAYER(species) { + Level(GetParametrizedLevel(move, variation)); + HP(GetParametrizedHP(move, variation)); MaxHP(9999); Items(ITEM_NONE, GetParametrizedItem(move, variation)); + if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE); + if (GetMoveEffect(move) == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND); + if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS); + if (friendship) Friendship(friendship); + if (GetParametrizedShinyness(move, variation)) Shiny(TRUE); + } + PLAYER(SPECIES_WOBBUFFET) { + Gender(MON_MALE); MaxHP(9999); Moves(MOVE_POUND); + HP(GetMoveEffect(move) == EFFECT_REVIVAL_BLESSING ? 0 : 9998); + } + OPPONENT(SPECIES_WOBBUFFET) { + Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY); + if (GetMoveEffect(move) != EFFECT_BESTOW) + Items(ITEM_NONE, ITEM_ORAN_BERRY); + } + OPPONENT(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); } + } WHEN { + WhenSingles(move, player, opponent, variation); + } SCENE { + if (!(GetMoveEffect(move) == EFFECT_RECYCLE + || GetMoveEffect(move) == EFFECT_BELCH + || GetMoveEffect(move) == EFFECT_SPIT_UP + || GetMoveEffect(move) == EFFECT_SWALLOW + || GetMoveEffect(move) == EFFECT_TOPSY_TURVY)) // require a move that boosts stats before using this move + { + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } + } + SceneSingles(move, player); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +SINGLE_BATTLE_TEST("Z-Moves don't leak when used - Singles (player to opponent) (Multi)") +{ + FORCE_MOVE_ANIM(TRUE); + u32 species, move, item, zmove; + Z_MOVE_PARAMETERS; + GIVEN { + PLAYER(species) { Items(ITEM_NONE, item); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + } WHEN { + if (species == SPECIES_NECROZMA_DAWN_WINGS) + { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } + TURN { MOVE(player, move, gimmick: GIMMICK_Z_MOVE); } + } + else + { + TURN { MOVE(player, move, gimmick: GIMMICK_Z_MOVE); } + } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, zmove, player); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +SINGLE_BATTLE_TEST("Z-Moves don't leak when used - Singles (opponent to player) (Multi)") +{ + FORCE_MOVE_ANIM(TRUE); + u32 species, move, item, zmove; + Z_MOVE_PARAMETERS; + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + OPPONENT(species) { Items(ITEM_NONE, item); } + } WHEN { + if (species == SPECIES_NECROZMA_DAWN_WINGS) + { + TURN { MOVE(opponent, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } + TURN { MOVE(opponent, move, gimmick: GIMMICK_Z_MOVE); } + } + else + { + TURN { MOVE(opponent, move, gimmick: GIMMICK_Z_MOVE); } + } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, zmove, opponent); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Z-Moves don't leak when used - Doubles (playerLeft to opponentLeft) (Multi)") +{ + FORCE_MOVE_ANIM(TRUE); + u32 species, move, item, zmove; + Z_MOVE_PARAMETERS; + GIVEN { + PLAYER(species) { Items(ITEM_NONE, item); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + OPPONENT(SPECIES_WYNAUT) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + } WHEN { + if (species == SPECIES_NECROZMA_DAWN_WINGS) + { + TURN { MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } + TURN { MOVE(playerLeft, move, gimmick: GIMMICK_Z_MOVE, target: opponentLeft); } + } + else + { + TURN { MOVE(playerLeft, move, gimmick: GIMMICK_Z_MOVE, target: opponentLeft); } + } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, zmove, playerLeft); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Z-Moves don't leak when used - Doubles (playerLeft to opponentRight) (Multi)") +{ + FORCE_MOVE_ANIM(TRUE); + u32 species, move, item, zmove; + Z_MOVE_PARAMETERS; + GIVEN { + PLAYER(species) { Items(ITEM_NONE, item); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + OPPONENT(SPECIES_WYNAUT) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + } WHEN { + if (species == SPECIES_NECROZMA_DAWN_WINGS) + { + TURN { MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } + TURN { MOVE(playerLeft, move, gimmick: GIMMICK_Z_MOVE, target: opponentRight); } + } + else + { + TURN { MOVE(playerLeft, move, gimmick: GIMMICK_Z_MOVE, target: opponentRight); } + } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, zmove, playerLeft); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Z-Moves don't leak when used - Doubles (playerRight to opponentLeft) (Multi)") +{ + FORCE_MOVE_ANIM(TRUE); + u32 species, move, item, zmove; + Z_MOVE_PARAMETERS; + GIVEN { + PLAYER(SPECIES_WYNAUT); + PLAYER(species) { Items(ITEM_NONE, item); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + OPPONENT(SPECIES_WYNAUT) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + } WHEN { + if (species == SPECIES_NECROZMA_DAWN_WINGS) + { + TURN { MOVE(playerRight, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } + TURN { MOVE(playerRight, move, gimmick: GIMMICK_Z_MOVE, target: opponentLeft); } + } + else + { + TURN { MOVE(playerRight, move, gimmick: GIMMICK_Z_MOVE, target: opponentLeft); } + } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, zmove, playerRight); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Z-Moves don't leak when used - Doubles (playerRight to opponentRight) (Multi)") +{ + FORCE_MOVE_ANIM(TRUE); + u32 species, move, item, zmove; + Z_MOVE_PARAMETERS; + GIVEN { + PLAYER(SPECIES_WYNAUT); + PLAYER(species) { Items(ITEM_NONE, item); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + OPPONENT(SPECIES_WYNAUT) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + } WHEN { + if (species == SPECIES_NECROZMA_DAWN_WINGS) + { + TURN { MOVE(playerRight, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } + TURN { MOVE(playerRight, move, gimmick: GIMMICK_Z_MOVE, target: opponentRight); } + } + else + { + TURN { MOVE(playerRight, move, gimmick: GIMMICK_Z_MOVE, target: opponentRight); } + } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, zmove, playerRight); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Z-Moves don't leak when used - Doubles (opponentLeft to playerLeft) (Multi)") +{ + FORCE_MOVE_ANIM(TRUE); + u32 species, move, item, zmove; + Z_MOVE_PARAMETERS; + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + PLAYER(SPECIES_WYNAUT) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + OPPONENT(species) { Items(ITEM_NONE, item); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + if (species == SPECIES_NECROZMA_DAWN_WINGS) + { + TURN { MOVE(opponentLeft, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } + TURN { MOVE(opponentLeft, move, gimmick: GIMMICK_Z_MOVE, target: playerLeft); } + } + else + { + TURN { MOVE(opponentLeft, move, gimmick: GIMMICK_Z_MOVE, target: playerLeft); } + } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, zmove, opponentLeft); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Z-Moves don't leak when used - Doubles (opponentLeft to playerRight) (Multi)") +{ + FORCE_MOVE_ANIM(TRUE); + u32 species, move, item, zmove; + Z_MOVE_PARAMETERS; + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + PLAYER(SPECIES_WYNAUT) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + OPPONENT(species) { Items(ITEM_NONE, item); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + if (species == SPECIES_NECROZMA_DAWN_WINGS) + { + TURN { MOVE(opponentLeft, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } + TURN { MOVE(opponentLeft, move, gimmick: GIMMICK_Z_MOVE, target: playerRight); } + } + else + { + TURN { MOVE(opponentLeft, move, gimmick: GIMMICK_Z_MOVE, target: playerRight); } + } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, zmove, opponentLeft); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Z-Moves don't leak when used - Doubles (opponentRight to playerLeft) (Multi)") +{ + FORCE_MOVE_ANIM(TRUE); + u32 species, move, item, zmove; + Z_MOVE_PARAMETERS; + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + PLAYER(SPECIES_WYNAUT) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(species) { Items(ITEM_NONE, item); } + } WHEN { + if (species == SPECIES_NECROZMA_DAWN_WINGS) + { + TURN { MOVE(opponentRight, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } + TURN { MOVE(opponentRight, move, gimmick: GIMMICK_Z_MOVE, target: playerLeft); } + } + else + { + TURN { MOVE(opponentRight, move, gimmick: GIMMICK_Z_MOVE, target: playerLeft); } + } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, zmove, opponentRight); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Z-Moves don't leak when used - Doubles (opponentRight to playerRight) (Multi)") +{ + FORCE_MOVE_ANIM(TRUE); + u32 species, move, item, zmove; + Z_MOVE_PARAMETERS; + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + PLAYER(SPECIES_WYNAUT) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(species) { Items(ITEM_NONE, item); } + } WHEN { + if (species == SPECIES_NECROZMA_DAWN_WINGS) + { + TURN { MOVE(opponentRight, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } + TURN { MOVE(opponentRight, move, gimmick: GIMMICK_Z_MOVE, target: playerRight); } + } + else + { + TURN { MOVE(opponentRight, move, gimmick: GIMMICK_Z_MOVE, target: playerRight); } + } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, zmove, opponentRight); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +// Max Moves + +SINGLE_BATTLE_TEST("Tera Blast doesn't leak when used - Singles (player to opponent) (Multi)") +{ + FORCE_MOVE_ANIM(TRUE); + u32 species, move, type; + TERA_BLAST_PARAMETERS; + GIVEN { + PLAYER(species) { TeraType(type); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + } WHEN { + TURN { MOVE(player, move, gimmick: GIMMICK_TERA); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_CHARGE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, move, player); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +SINGLE_BATTLE_TEST("Tera Blast doesn't leak when used - Singles (opponent to player) (Multi)") +{ + FORCE_MOVE_ANIM(TRUE); + u32 species, move, type; + TERA_BLAST_PARAMETERS; + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + OPPONENT(species) { TeraType(type); } + } WHEN { + TURN { MOVE(opponent, move, gimmick: GIMMICK_TERA); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_CHARGE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_ACTIVATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Tera Blast doesn't leak when used - Doubles (playerLeft to opponentLeft) (Multi)") +{ + FORCE_MOVE_ANIM(TRUE); + u32 species, move, type; + TERA_BLAST_PARAMETERS; + GIVEN { + PLAYER(species) { TeraType(type); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + OPPONENT(SPECIES_WYNAUT) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + } WHEN { + TURN { MOVE(playerLeft, move, gimmick: GIMMICK_TERA, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_CHARGE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_ACTIVATE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, move, playerLeft); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Tera Blast doesn't leak when used - Doubles (playerLeft to opponentRight) (Multi)") +{ + FORCE_MOVE_ANIM(TRUE); + u32 species, move, type; + TERA_BLAST_PARAMETERS; + GIVEN { + PLAYER(species) { TeraType(type); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + OPPONENT(SPECIES_WYNAUT) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + } WHEN { + TURN { MOVE(playerLeft, move, gimmick: GIMMICK_TERA, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_CHARGE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_ACTIVATE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, move, playerLeft); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Tera Blast doesn't leak when used - Doubles (playerRight to opponentLeft) (Multi)") +{ + FORCE_MOVE_ANIM(TRUE); + u32 species, move, type; + TERA_BLAST_PARAMETERS; + GIVEN { + PLAYER(SPECIES_WYNAUT); + PLAYER(species) { TeraType(type); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + OPPONENT(SPECIES_WYNAUT) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + } WHEN { + TURN { MOVE(playerRight, move, gimmick: GIMMICK_TERA, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_CHARGE, playerRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_ACTIVATE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, move, playerRight); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Tera Blast doesn't leak when used - Doubles (playerRight to opponentRight) (Multi)") +{ + FORCE_MOVE_ANIM(TRUE); + u32 species, move, type; + TERA_BLAST_PARAMETERS; + GIVEN { + PLAYER(SPECIES_WYNAUT); + PLAYER(species) { TeraType(type); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + OPPONENT(SPECIES_WYNAUT) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + } WHEN { + TURN { MOVE(playerRight, move, gimmick: GIMMICK_TERA, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_CHARGE, playerRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_ACTIVATE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, move, playerRight); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Tera Blast doesn't leak when used - Doubles (opponentLeft to playerLeft) (Multi)") +{ + FORCE_MOVE_ANIM(TRUE); + u32 species, move, type; + TERA_BLAST_PARAMETERS; + GIVEN { + PLAYER(species) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + PLAYER(SPECIES_WYNAUT) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + OPPONENT(SPECIES_WOBBUFFET) { TeraType(type); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, move, gimmick: GIMMICK_TERA, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_CHARGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_ACTIVATE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, move, opponentLeft); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Tera Blast doesn't leak when used - Doubles (opponentLeft to playerRight) (Multi)") +{ + FORCE_MOVE_ANIM(TRUE); + u32 species, move, type; + TERA_BLAST_PARAMETERS; + GIVEN { + PLAYER(species) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + PLAYER(SPECIES_WYNAUT) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + OPPONENT(SPECIES_WOBBUFFET) { TeraType(type); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, move, gimmick: GIMMICK_TERA, target: playerRight); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_CHARGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_ACTIVATE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, move, opponentLeft); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Tera Blast doesn't leak when used - Doubles (opponentRight to playerLeft) (Multi)") +{ + FORCE_MOVE_ANIM(TRUE); + u32 species, move, type; + TERA_BLAST_PARAMETERS; + GIVEN { + PLAYER(species) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + PLAYER(SPECIES_WYNAUT) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { TeraType(type); } + } WHEN { + TURN { MOVE(opponentRight, move, gimmick: GIMMICK_TERA, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_CHARGE, opponentRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_ACTIVATE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, move, opponentRight); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +DOUBLE_BATTLE_TEST("Tera Blast doesn't leak when used - Doubles (opponentRight to playerRight) (Multi)") +{ + FORCE_MOVE_ANIM(TRUE); + u32 species, move, type; + TERA_BLAST_PARAMETERS; + GIVEN { + PLAYER(species) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + PLAYER(SPECIES_WYNAUT) { Items(ITEM_NONE, ITEM_FOCUS_SASH); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { TeraType(type); } + } WHEN { + TURN { MOVE(opponentRight, move, gimmick: GIMMICK_TERA, target: playerRight); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_CHARGE, opponentRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_TERA_ACTIVATE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, move, opponentRight); + } THEN { + FORCE_MOVE_ANIM(FALSE); + if (gLoadFail || gSpriteAllocs != 0) + DebugPrintf("Move failed: %S (%u)", GetMoveName(move), move); + EXPECT_EQ(gLoadFail, FALSE); + EXPECT_EQ(gSpriteAllocs, 0); + } +} + +#endif #endif diff --git a/test/battle/move_effect/ally_switch.c b/test/battle/move_effect/ally_switch.c index af4e1810fcde..346292e4cb16 100644 --- a/test/battle/move_effect/ally_switch.c +++ b/test/battle/move_effect/ally_switch.c @@ -408,3 +408,131 @@ DOUBLE_BATTLE_TEST("Ally Switch updates attract battler") // Triple Battles required to test //TO_DO_BATTLE_TEST("Ally Switch fails if the user is in the middle of the field in a Triple Battle"); + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Ally Switch does not redirect moves done by Pokémon with Stalwart and Propeller Tail (Traits)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_STALWART; } + PARAMETRIZE { ability = ABILITY_PROPELLER_TAIL; } + PARAMETRIZE { ability = ABILITY_TELEPATHY; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); // Wobb is playerLeft, but it'll be Wynaut after Ally Switch + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_KADABRA) { Ability(ABILITY_INNER_FOCUS); Innates(ability); } + OPPONENT(SPECIES_ABRA); + } WHEN { + TURN { MOVE(playerLeft, MOVE_ALLY_SWITCH); MOVE(opponentLeft, MOVE_SCRATCH, target:playerRight); } // Kadabra targets playerRight Wynaut. + } SCENE { + MESSAGE("Wobbuffet used Ally Switch!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ALLY_SWITCH, playerLeft); + MESSAGE("Wobbuffet and Wynaut switched places!"); + + MESSAGE("The opposing Kadabra used Scratch!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); + HP_BAR((ability == ABILITY_STALWART || ability == ABILITY_PROPELLER_TAIL) ? playerLeft : playerRight); + } +} + +DOUBLE_BATTLE_TEST("Ally switch swaps sky drop targets if being used by partner (Traits)") +{ + u8 visibility; + GIVEN { + ASSUME(GetMoveEffect(MOVE_SKY_DROP) == EFFECT_SKY_DROP); + PLAYER(SPECIES_FEAROW) { Speed(100); } + PLAYER(SPECIES_XATU) { Speed(150); } + OPPONENT(SPECIES_ARON) { Speed(25); Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_STURDY); } + OPPONENT(SPECIES_WYNAUT) { Speed(30); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SKY_DROP, target: opponentLeft); } + TURN { MOVE(playerRight, MOVE_ALLY_SWITCH); SKIP_TURN(playerLeft); MOVE(opponentRight, MOVE_MUD_SPORT); MOVE(opponentLeft, MOVE_IRON_DEFENSE); } + } SCENE { + MESSAGE("Fearow used Sky Drop!"); + MESSAGE("Fearow took the opposing Aron into the sky!"); + // turn 2 + MESSAGE("Xatu used Ally Switch!"); + MESSAGE("Xatu and Fearow switched places!"); + MESSAGE("Fearow used Sky Drop!"); + HP_BAR(opponentLeft); + MESSAGE("The opposing Wynaut used Mud Sport!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MUD_SPORT, opponentRight); + MESSAGE("The opposing Aron used Iron Defense!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_IRON_DEFENSE, opponentLeft); + } THEN { + // all battlers should be visible + visibility = gBattleSpritesDataPtr->battlerData[0].invisible; + EXPECT_EQ(visibility, 0); + visibility = gBattleSpritesDataPtr->battlerData[1].invisible; + EXPECT_EQ(visibility, 0); + visibility = gBattleSpritesDataPtr->battlerData[2].invisible; + EXPECT_EQ(visibility, 0); + visibility = gBattleSpritesDataPtr->battlerData[3].invisible; + EXPECT_EQ(visibility, 0); + } +} + +DOUBLE_BATTLE_TEST("Ally switch swaps opposing sky drop targets if partner is being held in the air (Traits)") +{ + u8 visibility; + GIVEN { + ASSUME(GetMoveEffect(MOVE_SKY_DROP) == EFFECT_SKY_DROP); + PLAYER(SPECIES_ARON) { Speed(25); Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_STURDY); } + PLAYER(SPECIES_WYNAUT) { Speed(30); } + OPPONENT(SPECIES_FEAROW) { Speed(100); } + OPPONENT(SPECIES_XATU) { Speed(150); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SKY_DROP, target: playerLeft); } + TURN { MOVE(opponentRight, MOVE_ALLY_SWITCH); SKIP_TURN(opponentLeft); MOVE(playerRight, MOVE_MUD_SPORT); MOVE(playerLeft, MOVE_IRON_DEFENSE); } + } SCENE { + MESSAGE("The opposing Fearow used Sky Drop!"); + MESSAGE("The opposing Fearow took Aron into the sky!"); + // turn 2 + MESSAGE("The opposing Xatu used Ally Switch!"); + MESSAGE("The opposing Xatu and the opposing Fearow switched places!"); + MESSAGE("The opposing Fearow used Sky Drop!"); + HP_BAR(playerLeft); + MESSAGE("Wynaut used Mud Sport!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MUD_SPORT, playerRight); + MESSAGE("Aron used Iron Defense!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_IRON_DEFENSE, playerLeft); + } THEN { + // all battlers should be visible + visibility = gBattleSpritesDataPtr->battlerData[0].invisible; + EXPECT_EQ(visibility, 0); + visibility = gBattleSpritesDataPtr->battlerData[1].invisible; + EXPECT_EQ(visibility, 0); + visibility = gBattleSpritesDataPtr->battlerData[2].invisible; + EXPECT_EQ(visibility, 0); + visibility = gBattleSpritesDataPtr->battlerData[3].invisible; + EXPECT_EQ(visibility, 0); + } +} + +DOUBLE_BATTLE_TEST("Ally Switch updates attract battler (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(100); Gender(MON_MALE); } + PLAYER(SPECIES_SOLOSIS) { Speed(50); } + OPPONENT(SPECIES_CLEFAIRY) { Speed(20); Gender(MON_FEMALE); Ability(ABILITY_FRIEND_GUARD); Innates(ABILITY_CUTE_CHARM); } + OPPONENT(SPECIES_RALTS) { Speed(30); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft); } + TURN { MOVE(opponentRight, MOVE_ALLY_SWITCH); } + TURN { ; } + } SCENE { + // turn 1 + MESSAGE("Wobbuffet used Tackle!"); + HP_BAR(opponentLeft); + ABILITY_POPUP(opponentLeft, ABILITY_CUTE_CHARM); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_INFATUATION, playerLeft); + MESSAGE("The opposing Clefairy's Cute Charm infatuated Wobbuffet!"); + // turn 2 + MESSAGE("The opposing Ralts used Ally Switch!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ALLY_SWITCH, opponentRight); + MESSAGE("The opposing Ralts and the opposing Clefairy switched places!"); + // turn 3 + MESSAGE("Wobbuffet is in love with the opposing Clefairy!"); // tracks attract battler + } +} +#endif diff --git a/test/battle/move_effect/assist.c b/test/battle/move_effect/assist.c index 3a380ef125a1..281e67aca768 100644 --- a/test/battle/move_effect/assist.c +++ b/test/battle/move_effect/assist.c @@ -57,3 +57,29 @@ SINGLE_BATTLE_TEST("Assisted move triggers correct weakness berry") ANIMATION(ANIM_TYPE_MOVE, MOVE_SURF, player); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Assisted move triggers correct weakness berry (Multi)") +{ + u16 item; + PARAMETRIZE { item = ITEM_CHILAN_BERRY; } + PARAMETRIZE { item = ITEM_PASSHO_BERRY; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_ASSIST, MOVE_NONE, MOVE_NONE, MOVE_NONE); } + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_SURF, MOVE_NONE, MOVE_NONE, MOVE_NONE); } + OPPONENT(SPECIES_ARON) { Items(ITEM_GREAT_BALL, item); } + } WHEN { + TURN { MOVE(player, MOVE_ASSIST); } + } SCENE { + MESSAGE("Wobbuffet used Assist!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ASSIST, player); + MESSAGE("Wobbuffet used Surf!"); + if (item == ITEM_PASSHO_BERRY) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } else { + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURF, player); + } +} +#endif diff --git a/test/battle/move_effect/assurance.c b/test/battle/move_effect/assurance.c index 488740ec8f12..8ed745622084 100644 --- a/test/battle/move_effect/assurance.c +++ b/test/battle/move_effect/assurance.c @@ -60,3 +60,27 @@ TO_DO_BATTLE_TEST("Assurance doubles in power if the target has been damaged in TO_DO_BATTLE_TEST("Assurance doubles in power if the target has been damaged in the same turn - Confusion"); TO_DO_BATTLE_TEST("Assurance doubles in power if the target has been damaged in the same turn - Rocky Helmet"); + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Assurance doubles in power if the target has been damaged in the same turn - Life Orb (Multi)") +{ + s16 hits[2]; + + GIVEN { + ASSUME(gItemsInfo[ITEM_LIFE_ORB].holdEffect == HOLD_EFFECT_LIFE_ORB); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_LIFE_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_ASSURANCE); } + TURN { MOVE(player, MOVE_POUND); MOVE(opponent, MOVE_ASSURANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ASSURANCE, opponent); + HP_BAR(player, captureDamage: &hits[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, player); + MESSAGE("Wobbuffet was hurt by the Life Orb!"); + HP_BAR(player, captureDamage: &hits[1]); + } THEN { + EXPECT_MUL_EQ(hits[0], Q_4_12(2.0), hits[1]); + } +} +#endif diff --git a/test/battle/move_effect/attack_up_user_ally.c b/test/battle/move_effect/attack_up_user_ally.c index 767e9fdef7f9..1d5cf88fb6f5 100644 --- a/test/battle/move_effect/attack_up_user_ally.c +++ b/test/battle/move_effect/attack_up_user_ally.c @@ -95,3 +95,39 @@ DOUBLE_BATTLE_TEST("Howl does not work on partner if it has Soundproof") EXPECT_EQ(damage[0], damage[1]); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Howl does not work on partner if it has Soundproof (Traits)") +{ + s16 damage[2]; + + GIVEN { + ASSUME(GetMoveEffect(MOVE_HOWL) == EFFECT_ATTACK_UP_USER_ALLY); + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_WOBBUFFET) { Speed(15); } + PLAYER(SPECIES_VOLTORB) { Speed(10); Ability(ABILITY_STATIC); Innates(ABILITY_SOUNDPROOF); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_WYNAUT) { Speed(1); } + } WHEN { + TURN { MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); } + TURN { MOVE(playerLeft, MOVE_HOWL); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerRight); + HP_BAR(opponentLeft, captureDamage: &damage[0]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_HOWL, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Wobbuffet's Attack rose!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Wynaut's Attack rose!"); + } + ABILITY_POPUP(playerRight, ABILITY_SOUNDPROOF); + MESSAGE("Voltorb's Soundproof blocks Howl!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerRight); + HP_BAR(opponentLeft, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} +#endif diff --git a/test/battle/move_effect/aura_wheel.c b/test/battle/move_effect/aura_wheel.c index 55af998c63b4..13d2963b86b1 100644 --- a/test/battle/move_effect/aura_wheel.c +++ b/test/battle/move_effect/aura_wheel.c @@ -87,3 +87,62 @@ SINGLE_BATTLE_TEST("Aura Wheel can be turned into a Normal-type move after Morpe } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Aura Wheel changes type depending on Morpeko's form (Traits)") +{ + GIVEN { + PLAYER(SPECIES_MORPEKO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_HUNGER_SWITCH); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_AURA_WHEEL); } + TURN { MOVE(player, MOVE_AURA_WHEEL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AURA_WHEEL, player); + HP_BAR(opponent); + NOT MESSAGE("It's super effective!"); + // Turn 2 (Hangry) + ANIMATION(ANIM_TYPE_MOVE, MOVE_AURA_WHEEL, player); + HP_BAR(opponent); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Aura Wheel can be used by Pokémon transformed into Morpeko (Traits)") +{ + GIVEN { + PLAYER(SPECIES_MORPEKO) { Moves(MOVE_AURA_WHEEL, MOVE_CELEBRATE); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_HUNGER_SWITCH); } + OPPONENT(SPECIES_DITTO) { Moves(MOVE_AURA_WHEEL, MOVE_CELEBRATE); Ability(ABILITY_LIMBER); Innates(ABILITY_IMPOSTER); } + } WHEN { + TURN { MOVE(opponent, MOVE_AURA_WHEEL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AURA_WHEEL, opponent); + } +} + +SINGLE_BATTLE_TEST("Aura Wheel can be turned into a Normal-type move after Morpeko gains Normalize (Traits)") +{ + bool32 hangryMode; + PARAMETRIZE { hangryMode = FALSE; } + PARAMETRIZE { hangryMode = TRUE; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_ENTRAINMENT) == EFFECT_ENTRAINMENT); + ASSUME(gSpeciesInfo[SPECIES_DUSKULL].types[0] == TYPE_GHOST || gSpeciesInfo[SPECIES_DUSKULL].types[1] == TYPE_GHOST); + PLAYER(SPECIES_MORPEKO) { Ability(ABILITY_HUNGER_SWITCH); Innates(ABILITY_NORMALIZE); } + OPPONENT(SPECIES_DELCATTY) { Ability(ABILITY_CUTE_CHARM); } + OPPONENT(SPECIES_DUSKULL); + } WHEN { + if (hangryMode) + TURN { } + TURN { MOVE(opponent, MOVE_ENTRAINMENT); } + TURN { MOVE(player, MOVE_AURA_WHEEL); SWITCH(opponent, 1); } + } SCENE { + if (hangryMode) + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AURA_WHEEL, player); + HP_BAR(opponent); + } + } +} +#endif diff --git a/test/battle/move_effect/aurora_veil.c b/test/battle/move_effect/aurora_veil.c index 1b480ad1ed63..0fcc5a2ca147 100644 --- a/test/battle/move_effect/aurora_veil.c +++ b/test/battle/move_effect/aurora_veil.c @@ -60,3 +60,35 @@ TO_DO_BATTLE_TEST("Aurora Veil's damage reduction is ignored by Critical Hits") TO_DO_BATTLE_TEST("Aurora Veil's damage reduction doesn't stack with Reflect or Light Screen") TO_DO_BATTLE_TEST("Aurora Veil doesn't reduce confusion damage") TO_DO_BATTLE_TEST("Aurora Veil doesn't reduce damage done by moves that do direct damage") // Bide, Counter, Endeavor, Final Gambit, Metal Burst, Mirror Coat, Psywave, Seismic Toss, Sonic Boom, Super Fang + + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Aurora Veil will prevent Protean activation if it fails due to no Snow/Hail (Traits)") +{ + GIVEN { + PLAYER(SPECIES_KECLEON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTEAN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_AURORA_VEIL); } + } SCENE { + MESSAGE("But it failed!"); + NOT ABILITY_POPUP(player, ABILITY_PROTEAN); + } +} + +SINGLE_BATTLE_TEST("Aurora Veil wont prevent Protean activation when it fails due to being set up already (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_KECLEON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTEAN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SNOWSCAPE); MOVE(player, MOVE_AURORA_VEIL); } + TURN { SWITCH(player, 1); } + TURN { MOVE(player, MOVE_AURORA_VEIL); } + } SCENE { + ABILITY_POPUP(player, ABILITY_PROTEAN); + MESSAGE("But it failed!"); + } +} +#endif diff --git a/test/battle/move_effect/beat_up.c b/test/battle/move_effect/beat_up.c index 719772c38cc4..52cdad965509 100644 --- a/test/battle/move_effect/beat_up.c +++ b/test/battle/move_effect/beat_up.c @@ -393,3 +393,126 @@ SINGLE_BATTLE_TEST("Beat Up ignores Choice Band", s16 damage) EXPECT_EQ(results[i].damage, results[0].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Beat Up doesn't consider Comatose as a status (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_KOMALA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMATOSE); } + PLAYER(SPECIES_WYNAUT) { HP(0); } + PLAYER(SPECIES_WYNAUT) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_WYNAUT) { Status1(STATUS1_SLEEP); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + MESSAGE("The Pokémon was hit 2 time(s)!"); + } +} + +SINGLE_BATTLE_TEST("Beat Up's damage considers Huge Power and Choice Band (Gen5+) (Traits)", s16 damage) +{ + u16 ability; + u16 item; + + PARAMETRIZE { ability = ABILITY_THICK_FAT; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_HUGE_POWER; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_THICK_FAT; item = ITEM_CHOICE_BAND; } + + GIVEN { + WITH_CONFIG(CONFIG_BEAT_UP, GEN_5); + PLAYER(SPECIES_AZUMARILL) { Ability(ABILITY_SAP_SIPPER); Innates(ability); Item(item); Moves(MOVE_BEAT_UP); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (i == 1) + EXPECT_GT(results[i].damage, results[0].damage); + if (i == 2) + EXPECT_GT(results[i].damage, results[0].damage); + } +} + +SINGLE_BATTLE_TEST("Beat Up ignores Huge Power (Traits)", s16 damage) +{ + u16 ability; + + PARAMETRIZE { ability = ABILITY_THICK_FAT; } + PARAMETRIZE { ability = ABILITY_HUGE_POWER; } + + GIVEN { + WITH_CONFIG(CONFIG_BEAT_UP, GEN_3); + PLAYER(SPECIES_AZUMARILL) { Ability(ABILITY_SAP_SIPPER); Innates(ability); Moves(MOVE_BEAT_UP); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (ability == ABILITY_HUGE_POWER) + EXPECT_EQ(results[i].damage, results[0].damage); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Beat Up's damage considers Huge Power and Choice Band (Gen5+) (Multi)", s16 damage) +{ + u16 ability; + u16 item; + + PARAMETRIZE { ability = ABILITY_THICK_FAT; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_HUGE_POWER; item = ITEM_NONE; } + PARAMETRIZE { ability = ABILITY_THICK_FAT; item = ITEM_CHOICE_BAND; } + + GIVEN { + WITH_CONFIG(CONFIG_BEAT_UP, GEN_5); + PLAYER(SPECIES_AZUMARILL) { Ability(ability); Items(ITEM_GREAT_BALL, item); Moves(MOVE_BEAT_UP); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (i == 1) + EXPECT_GT(results[i].damage, results[0].damage); + if (i == 2) + EXPECT_GT(results[i].damage, results[0].damage); + } +} + +SINGLE_BATTLE_TEST("Beat Up ignores Choice Band (Multi)", s16 damage) +{ + u16 item; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_CHOICE_BAND; } + + GIVEN { + WITH_CONFIG(CONFIG_BEAT_UP, GEN_3); + PLAYER(SPECIES_URSARING) { Items(ITEM_GREAT_BALL, item); Moves(MOVE_BEAT_UP); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BEAT_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BEAT_UP, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (item == ITEM_CHOICE_BAND) + EXPECT_EQ(results[i].damage, results[0].damage); + } +} +#endif diff --git a/test/battle/move_effect/belch.c b/test/battle/move_effect/belch.c index 01bb244002aa..e312961ae110 100644 --- a/test/battle/move_effect/belch.c +++ b/test/battle/move_effect/belch.c @@ -114,3 +114,109 @@ SINGLE_BATTLE_TEST("Belch can still be used after restoring the consumed berry") ANIMATION(ANIM_TYPE_MOVE, MOVE_BELCH, player); } } + +#if MAX_MON_ITEMS > 1 +AI_SINGLE_BATTLE_TEST("AI: Belch has nonzero score after eating a berry (Multi)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_BAYLEEF) { Level(18); Moves(MOVE_MUD_SHOT, MOVE_SCRATCH); } + OPPONENT(SPECIES_PIKACHU) { Level(15); Items(ITEM_GREAT_BALL, ITEM_SHUCA_BERRY); Moves(MOVE_BELCH, MOVE_SCRATCH); } + } WHEN { + TURN { MOVE(player, MOVE_MUD_SHOT); EXPECT_MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_BELCH);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BELCH, opponent); + } +} + +SINGLE_BATTLE_TEST("Belch cannot be used if the user has not eaten a berry (Multi)") +{ + u16 item = 0; + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_ORAN_BERRY; } + GIVEN { + PLAYER(SPECIES_SKWOVET) { Items(ITEM_GREAT_BALL, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (item == ITEM_NONE) + TURN { MOVE(player, MOVE_BELCH, allowed: FALSE); MOVE(player, MOVE_CELEBRATE); } + else { + TURN { MOVE(player, MOVE_STUFF_CHEEKS); } + TURN { MOVE(player, MOVE_BELCH); } + TURN { MOVE(player, MOVE_BELCH); } + } + } SCENE { + if (item == ITEM_NONE) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } + else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STUFF_CHEEKS, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BELCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BELCH, player); + } + } +} + +SINGLE_BATTLE_TEST("Belch can still be used after switching out (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_STUFF_CHEEKS) == EFFECT_STUFF_CHEEKS); + PLAYER(SPECIES_GREEDENT) { Items(ITEM_GREAT_BALL, ITEM_ORAN_BERRY); } + PLAYER(SPECIES_SKWOVET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STUFF_CHEEKS); } + TURN { SWITCH(player, 1); } + TURN { SWITCH(player, 0); } + TURN { MOVE(player, MOVE_BELCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STUFF_CHEEKS, player); + SWITCH_OUT_MESSAGE("Greedent"); + SWITCH_OUT_MESSAGE("Skwovet"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BELCH, player); + } +} + +SINGLE_BATTLE_TEST("Belch can still be used after fainting (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_STUFF_CHEEKS) == EFFECT_STUFF_CHEEKS); + ASSUME(GetMoveEffect(MOVE_FISSURE) == EFFECT_OHKO); + ASSUME(GetMoveEffect(MOVE_REVIVAL_BLESSING) == EFFECT_REVIVAL_BLESSING); + PLAYER(SPECIES_GREEDENT) { Items(ITEM_GREAT_BALL, ITEM_ORAN_BERRY); } + PLAYER(SPECIES_SKWOVET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STUFF_CHEEKS); MOVE(opponent, MOVE_FISSURE); SEND_OUT(player, 1); } + TURN { MOVE(player, MOVE_REVIVAL_BLESSING, partyIndex: 0); } + TURN { SWITCH(player, 0); } + TURN { MOVE(player, MOVE_BELCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STUFF_CHEEKS, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FISSURE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_REVIVAL_BLESSING, player); + SWITCH_OUT_MESSAGE("Skwovet"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BELCH, player); + } +} + +SINGLE_BATTLE_TEST("Belch can still be used after restoring the consumed berry (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_STUFF_CHEEKS) == EFFECT_STUFF_CHEEKS); + ASSUME(GetMoveEffect(MOVE_RECYCLE) == EFFECT_RECYCLE); + PLAYER(SPECIES_GREEDENT) { Items(ITEM_GREAT_BALL, ITEM_ORAN_BERRY); } + PLAYER(SPECIES_SKWOVET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STUFF_CHEEKS); } + TURN { MOVE(player, MOVE_RECYCLE); } + TURN { MOVE(player, MOVE_BELCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STUFF_CHEEKS, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RECYCLE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BELCH, player); + } +} +#endif diff --git a/test/battle/move_effect/belly_drum.c b/test/battle/move_effect/belly_drum.c index 8b2cdf1d1803..bea54e956ee1 100644 --- a/test/battle/move_effect/belly_drum.c +++ b/test/battle/move_effect/belly_drum.c @@ -216,3 +216,106 @@ SINGLE_BATTLE_TEST("Belly Drum deducts HP if the user has Contrary and is at -6" MESSAGE("Serperior cut its own HP and maximized its Attack!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Belly Drum minimizes the user's Attack stat with Contrary (Traits)", s16 damage) +{ + bool32 raiseAttack; + PARAMETRIZE { raiseAttack = FALSE; } + PARAMETRIZE { raiseAttack = TRUE; } + GIVEN { + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_CONTRARY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (raiseAttack) TURN { MOVE(player, MOVE_BELLY_DRUM); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + if (raiseAttack) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BELLY_DRUM, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Wobbuffet cut its own HP and maximized its Attack!"); // Message unaffected by Contrary + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[1].damage, Q_4_12(4), results[0].damage); + } +} + +SINGLE_BATTLE_TEST("Belly Drum fails if the user's Attack is already at +6, even with Contrary (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_CHARM) == EFFECT_ATTACK_DOWN_2); + PLAYER(SPECIES_SERPERIOR) { Ability(ABILITY_OVERGROW); Innates(ABILITY_CONTRARY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_CHARM); } + TURN { MOVE(opponent, MOVE_CHARM); } + TURN { MOVE(opponent, MOVE_CHARM); } + TURN { MOVE(player, MOVE_BELLY_DRUM); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Serperior's Attack sharply rose!"); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Serperior's Attack sharply rose!"); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Serperior's Attack sharply rose!"); + + MESSAGE("But it failed!"); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BELLY_DRUM, player); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } + } +} + +SINGLE_BATTLE_TEST("Belly Drum deducts HP if the user has Contrary and is at -6 (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SWORDS_DANCE) == EFFECT_ATTACK_UP_2); + PLAYER(SPECIES_SERPERIOR) { Ability(ABILITY_OVERGROW); Innates(ABILITY_CONTRARY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SWORDS_DANCE); } + TURN { MOVE(player, MOVE_SWORDS_DANCE); } + TURN { MOVE(player, MOVE_SWORDS_DANCE); } + TURN { MOVE(player, MOVE_BELLY_DRUM); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Serperior's Attack harshly fell!"); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Serperior's Attack harshly fell!"); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Serperior's Attack harshly fell!"); + + NOT MESSAGE("But it failed!"); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_BELLY_DRUM, player); + s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); + HP_BAR(player, hp: maxHP / 2); + MESSAGE("Serperior cut its own HP and maximized its Attack!"); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Belly Drum's HP cost doesn't trigger effects that trigger on damage taken (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_AIR_BALLOON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BELLY_DRUM); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BELLY_DRUM, player); + MESSAGE("Wobbuffet cut its own HP and maximized its Attack!"); + NOT MESSAGE("Wobbuffet's Air Balloon popped!"); + } +} +#endif diff --git a/test/battle/move_effect/bestow.c b/test/battle/move_effect/bestow.c index 7017ede805a5..1077b59ae648 100644 --- a/test/battle/move_effect/bestow.c +++ b/test/battle/move_effect/bestow.c @@ -145,3 +145,135 @@ SINGLE_BATTLE_TEST("Bestow fails if the user's held item changes its form") EXPECT(opponent->item == ITEM_NONE); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Bestow transfers its held item to the target (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, 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 target already has a held item (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, 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 (Multi)") +{ + GIVEN { + ASSUME(ItemIsMail(ITEM_ORANGE_MAIL)); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, 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 (Multi)") +{ + GIVEN { + PLAYER(SPECIES_BLAZIKEN) { Items(ITEM_NONE, 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 (Multi)") +{ + GIVEN { + ASSUME(GetItemHoldEffect(ITEM_FIGHTINIUM_Z) == HOLD_EFFECT_Z_CRYSTAL); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, 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 doesn't fail if the user has Sticky Hold (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_STICKY_HOLD); Items(ITEM_NONE, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BESTOW); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BESTOW, player); + } THEN { + EXPECT(player->item == ITEM_NONE); + EXPECT(opponent->item == ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Bestow fails if the target is behind a Substitute (Gen 6+) (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_SITRUS_BERRY); Speed(50); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(100); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_BESTOW); } + } SCENE { + if (B_UPDATED_MOVE_FLAGS >= GEN_6) { + NOT MESSAGE("But it failed!"); + } else { + MESSAGE("But it failed!"); + } + } THEN { + if (B_UPDATED_MOVE_FLAGS >= GEN_6) { + EXPECT(player->item == ITEM_NONE); + EXPECT(opponent->item == ITEM_SITRUS_BERRY); + } else { + 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 (Multi)") +{ + GIVEN { + PLAYER(SPECIES_GIRATINA_ORIGIN) { Items(ITEM_NONE, ITEM_GRISEOUS_CORE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BESTOW); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(player->item == ITEM_GRISEOUS_CORE); + EXPECT(opponent->item == ITEM_NONE); + } +} +#endif diff --git a/test/battle/move_effect/brick_break.c b/test/battle/move_effect/brick_break.c index 05e22f42d965..b4a2861973b0 100644 --- a/test/battle/move_effect/brick_break.c +++ b/test/battle/move_effect/brick_break.c @@ -157,3 +157,34 @@ DOUBLE_BATTLE_TEST("Brick Break and Psychic Fangs can remove Light Screen, Refle HP_BAR(playerLeft); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Brick Break and Psychic Fangs don't remove Light Screen, Reflect and Aurora Veil if it misses (Multi)") +{ + u32 move; + u32 breakingMove; + + 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) { Items(ITEM_PECHA_BERRY, ITEM_BRIGHT_POWDER); } + } WHEN { + TURN { MOVE(player, MOVE_SNOWSCAPE); MOVE(opponent, move); } + 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, breakingMove, player); + MESSAGE("The wall shattered!"); + HP_BAR(opponent); + } + } +} +#endif diff --git a/test/battle/move_effect/ceaseless_edge.c b/test/battle/move_effect/ceaseless_edge.c index 288045945fbc..f6e827ec05b5 100644 --- a/test/battle/move_effect/ceaseless_edge.c +++ b/test/battle/move_effect/ceaseless_edge.c @@ -94,3 +94,21 @@ SINGLE_BATTLE_TEST("Ceaseless Edge does not set up hazards if target was not hit } } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Ceaseless Edge fails to set up hazards if user faints (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_ROCKY_HELMET); } + } WHEN { + TURN { MOVE(player, MOVE_CEASELESS_EDGE); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CEASELESS_EDGE, player); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the opposing Wobbuffet's Rocky Helmet!"); + NOT MESSAGE("Spikes were scattered on the ground all around the opposing team!"); + } +} +#endif diff --git a/test/battle/move_effect/change_type_on_item.c b/test/battle/move_effect/change_type_on_item.c index f5c8f92376c1..42392ddc002a 100644 --- a/test/battle/move_effect/change_type_on_item.c +++ b/test/battle/move_effect/change_type_on_item.c @@ -31,3 +31,27 @@ SINGLE_BATTLE_TEST("Techno Blast changes type depending on the drive the user ho TO_DO_BATTLE_TEST("Judgement changes type depending on the plate the user holds"); TO_DO_BATTLE_TEST("Multi Attack changes type depending on the memory the user holds"); + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Techno Blast changes type depending on the drive the user holds (Multi)") +{ + u16 species; + u16 item; + + PARAMETRIZE { species = SPECIES_CHARIZARD; item = ITEM_DOUSE_DRIVE; } + PARAMETRIZE { species = SPECIES_BLASTOISE; item = ITEM_SHOCK_DRIVE; } + PARAMETRIZE { species = SPECIES_VENUSAUR; item = ITEM_BURN_DRIVE; } + PARAMETRIZE { species = SPECIES_DRATINI; item = ITEM_CHILL_DRIVE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, item); } + OPPONENT(species); + } WHEN { + TURN { MOVE(player, MOVE_TECHNO_BLAST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TECHNO_BLAST, player); + HP_BAR(opponent); + MESSAGE("It's super effective!"); + } +} +#endif diff --git a/test/battle/move_effect/charge.c b/test/battle/move_effect/charge.c index 610230de11f4..d18f6ce41a34 100644 --- a/test/battle/move_effect/charge.c +++ b/test/battle/move_effect/charge.c @@ -154,3 +154,61 @@ SINGLE_BATTLE_TEST("Charge will expire if user flinches while using an electric EXPECT_EQ(damage[0], damage[1]); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Charge's effect does not stack with Electromorphosis or Wind Power (Traits)") +{ + u32 species; + enum Ability ability; + s16 damage[2]; + + PARAMETRIZE { species = SPECIES_WATTREL; ability = ABILITY_WIND_POWER; } + PARAMETRIZE { species = SPECIES_TADBULB; ability = ABILITY_ELECTROMORPHOSIS; } + + GIVEN { + ASSUME(IsWindMove(MOVE_AIR_CUTTER)); + PLAYER(species) { Ability(ABILITY_OWN_TEMPO); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_THUNDERBOLT); } + TURN { MOVE(player, MOVE_CHARGE); MOVE(opponent, MOVE_AIR_CUTTER); } + TURN { MOVE(player, MOVE_THUNDERBOLT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARGE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_AIR_CUTTER, opponent); + ABILITY_POPUP(player, ability); + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[0], Q_4_12(2.0), damage[1]); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Charge will expire if user flinches while using an electric move (Multi)") +{ + s16 damage[2]; + GIVEN { + ASSUME(GetMoveAdditionalEffectById(MOVE_IRON_HEAD, 0)->moveEffect == MOVE_EFFECT_FLINCH); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LUM_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_THUNDERBOLT); } + TURN { MOVE(player, MOVE_CHARGE); } + TURN { MOVE(opponent, MOVE_IRON_HEAD); MOVE(player, MOVE_THUNDERBOLT); } + TURN { MOVE(opponent, MOVE_IRON_HEAD); MOVE(player, MOVE_THUNDERBOLT); } + TURN { MOVE(player, MOVE_THUNDERBOLT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CHARGE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} +#endif diff --git a/test/battle/move_effect/chilly_reception.c b/test/battle/move_effect/chilly_reception.c index 0361f9db4f99..318c34e22177 100644 --- a/test/battle/move_effect/chilly_reception.c +++ b/test/battle/move_effect/chilly_reception.c @@ -106,3 +106,23 @@ SINGLE_BATTLE_TEST("Chilly Reception changes the weather, even if the user canno } TO_DO_BATTLE_TEST("Chilly Reception doesn't announce its move if it's called by a different move"); + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Chilly Reception switches the user out, even if the weather does not change (Multi)") +{ + GIVEN { + PLAYER(SPECIES_SLOWKING_GALAR); + PLAYER(SPECIES_SLOWPOKE_GALAR); + OPPONENT(SPECIES_KYOGRE) { Items(ITEM_PECHA_BERRY, ITEM_BLUE_ORB); } + } WHEN { + TURN { MOVE(player, MOVE_CHILLY_RECEPTION); SEND_OUT(player, 1); } + } SCENE { + MESSAGE("Slowking is preparing to tell a chillingly bad joke!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CHILLY_RECEPTION, player); + MESSAGE("There is no relief from this heavy rain!"); + MESSAGE("Slowking went back to 1!"); + SEND_IN_MESSAGE("Slowpoke"); + MESSAGE("Rain continues to fall."); + } +} +#endif diff --git a/test/battle/move_effect/chloroblast.c b/test/battle/move_effect/chloroblast.c index d60449324f69..77f960dff14f 100644 --- a/test/battle/move_effect/chloroblast.c +++ b/test/battle/move_effect/chloroblast.c @@ -156,3 +156,50 @@ SINGLE_BATTLE_TEST("Chloroblast is not affected by Reckless", s16 damage) EXPECT_EQ(results[0].damage, results[1].damage); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Chloroblast hp loss is prevented by Magic Guard (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CLEFAIRY) { Ability(ABILITY_FRIEND_GUARD); Innates(ABILITY_MAGIC_GUARD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CHLOROBLAST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CHLOROBLAST, player); + HP_BAR(opponent); + NOT HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Chloroblast does not cause recoil damage if the user has Rock Head (Traits)") +{ + GIVEN { + PLAYER(SPECIES_AERODACTYL) { Ability(ABILITY_PRESSURE); Innates(ABILITY_ROCK_HEAD); } + OPPONENT(SPECIES_WOBBUFFET) { HP(400); MaxHP(400); } + } WHEN { + TURN { MOVE(player, MOVE_CHLOROBLAST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CHLOROBLAST, player); + HP_BAR(opponent); + NOT HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Chloroblast does not cause the user to lose HP even if it is absorbed by Sap Sipper (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_CHLOROBLAST) == TYPE_GRASS); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOGOAT) { Ability(ABILITY_GRASS_PELT); Innates(ABILITY_SAP_SIPPER); } + } WHEN { + TURN { MOVE(player, MOVE_CHLOROBLAST); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_SAP_SIPPER); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CHLOROBLAST, player); + HP_BAR(player); + } + } +} +#endif diff --git a/test/battle/move_effect/confuse.c b/test/battle/move_effect/confuse.c index 425adfc889ba..5bcbb64137a7 100644 --- a/test/battle/move_effect/confuse.c +++ b/test/battle/move_effect/confuse.c @@ -56,3 +56,22 @@ DOUBLE_BATTLE_TEST("Teeter Dance can confuse foes and allies") MESSAGE("The opposing Wynaut became confused!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Teeter Dance confusion is blocked by Own Tempo (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SLOWPOKE) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_OWN_TEMPO); } + } WHEN { + TURN { MOVE(player, MOVE_TEETER_DANCE); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_OWN_TEMPO); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEETER_DANCE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, opponent); + MESSAGE("The opposing Wobbuffet became confused!"); + } + } +} +#endif diff --git a/test/battle/move_effect/conversion_2.c b/test/battle/move_effect/conversion_2.c index 169243019b5f..3c779108319a 100644 --- a/test/battle/move_effect/conversion_2.c +++ b/test/battle/move_effect/conversion_2.c @@ -242,3 +242,35 @@ SINGLE_BATTLE_TEST("Conversion 2 fails if last hit by a Stellar-type move (Gen 1 MESSAGE("But it failed!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Conversion 2's type change considers move types changed by Normalize and Electrify (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Innates(ABILITY_NORMALIZE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ELECTRIFY); MOVE(opponent, MOVE_POUND); } + TURN { MOVE(player, MOVE_CONVERSION_2); } + TURN { MOVE(player, MOVE_WATER_GUN); MOVE(opponent, MOVE_CONVERSION_2); } + } SCENE { + // turn 1 + MESSAGE("Wobbuffet used Electrify!"); + MESSAGE("The opposing Wobbuffet used Pound!"); + // turn 2 + ONE_OF { + MESSAGE("Wobbuffet transformed into the Ground type!"); + MESSAGE("Wobbuffet transformed into the Dragon type!"); + MESSAGE("Wobbuffet transformed into the Grass type!"); + MESSAGE("Wobbuffet transformed into the Electric type!"); + } + // turn 3 + MESSAGE("Wobbuffet used Water Gun!"); + ONE_OF { + MESSAGE("The opposing Wobbuffet transformed into the Steel type!"); + MESSAGE("The opposing Wobbuffet transformed into the Rock type!"); + MESSAGE("The opposing Wobbuffet transformed into the Ghost type!"); + } + } +} +#endif diff --git a/test/battle/move_effect/corrosive_gas.c b/test/battle/move_effect/corrosive_gas.c index 2d3678eacad6..09c044360c92 100644 --- a/test/battle/move_effect/corrosive_gas.c +++ b/test/battle/move_effect/corrosive_gas.c @@ -120,3 +120,137 @@ DOUBLE_BATTLE_TEST("Corrosive Gas destroys foes and ally's items if they have on TO_DO_BATTLE_TEST("Corrosive Gas doesn't destroy the item of a Pokemon behind a Substitute"); TO_DO_BATTLE_TEST("Corrosive Gas doesn't destroy items if they change the Pokémon's form"); // Giratina, Genesect, Silvally, Zacian, Zamazenta. Bulbapedia hasn't confirmed Arceus or Ogerpon, but it's a safe assumption that they will also fail. + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Corrosive Gas doesn't destroy the item of a Pokemon with the Sticky Hold ability (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MUK) {Item(ITEM_POISON_BARB); Ability(ABILITY_LIQUID_OOZE); Innates(ABILITY_STICKY_HOLD); } + } WHEN { + TURN { MOVE(player, MOVE_CORROSIVE_GAS); } + } SCENE { + MESSAGE("Wobbuffet used Corrosive Gas!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CORROSIVE_GAS, player); + NOT MESSAGE("Wobbuffet corroded the opposing Wobbuffet's Potion!"); + ABILITY_POPUP(opponent, ABILITY_STICKY_HOLD); + MESSAGE("The opposing Muk's Sticky Hold made Corrosive Gas ineffective!"); + } THEN { + EXPECT_EQ(opponent->item, ITEM_POISON_BARB); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Corrosive Gas destroys the target's item or fails if the target has no item (Multi)") +{ + u16 item; + + PARAMETRIZE {item = ITEM_NONE; } + PARAMETRIZE {item = ITEM_POTION; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) {Items(ITEM_NONE, item); } + } WHEN { + TURN { MOVE(player, MOVE_CORROSIVE_GAS); } + } SCENE { + MESSAGE("Wobbuffet used Corrosive Gas!"); + if (item == ITEM_POTION) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CORROSIVE_GAS, player); + MESSAGE("Wobbuffet corroded the opposing Wobbuffet's Potion!"); + } + else { + MESSAGE("It won't have any effect on the opposing Wobbuffet!"); + } + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Corrosive Gas doesn't destroy the item of a Pokemon with the Sticky Hold ability (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MUK) {Items(ITEM_NONE, ITEM_POISON_BARB); Ability(ABILITY_STICKY_HOLD); } + } WHEN { + TURN { MOVE(player, MOVE_CORROSIVE_GAS); } + } SCENE { + MESSAGE("Wobbuffet used Corrosive Gas!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CORROSIVE_GAS, player); + NOT MESSAGE("Wobbuffet corroded the opposing Wobbuffet's Potion!"); + ABILITY_POPUP(opponent, ABILITY_STICKY_HOLD); + MESSAGE("The opposing Muk's Sticky Hold made Corrosive Gas ineffective!"); + } THEN { + EXPECT_EQ(opponent->item, ITEM_POISON_BARB); + } +} + +SINGLE_BATTLE_TEST("Items lost to Corrosive Gas cannot be restored by Recycle (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_RECYCLE) == EFFECT_RECYCLE); + PLAYER(SPECIES_WOBBUFFET) {Speed(15); } + OPPONENT(SPECIES_WOBBUFFET) {Items(ITEM_NONE, ITEM_ORAN_BERRY); Speed(10); } + } WHEN { + TURN { MOVE(player, MOVE_CORROSIVE_GAS); MOVE(opponent, MOVE_RECYCLE); } + } SCENE { + MESSAGE("Wobbuffet used Corrosive Gas!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CORROSIVE_GAS, player); + MESSAGE("Wobbuffet corroded the opposing Wobbuffet's Oran Berry!"); + MESSAGE("The opposing Wobbuffet used Recycle!"); + MESSAGE("But it failed!"); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Corrosive Gas destroys foes and ally's items if they have one (Multi)") +{ + // Check it affects all targets in all possible configurations. + u32 j, k, l; + u16 itemOpponentLeft, itemOpponentRight, itemPlayerLeft; + + for (j = 0; j < 2; j++) { + for (k = 0; k < 2; k++) { + for (l = 0; l < 2; l++) { + PARAMETRIZE {itemOpponentLeft = (j & 1) ? ITEM_ORAN_BERRY : ITEM_NONE; + itemOpponentRight = (k & 1) ? ITEM_CHESTO_BERRY : ITEM_NONE; + itemPlayerLeft = (l & 1) ? ITEM_CHERI_BERRY : ITEM_NONE; } + } + } + } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) {Items(ITEM_NONE, itemPlayerLeft);} + PLAYER(SPECIES_WYNAUT) {Items(ITEM_NONE, ITEM_SITRUS_BERRY);} + OPPONENT(SPECIES_ABRA) {Items(ITEM_NONE, itemOpponentLeft);} + OPPONENT(SPECIES_KADABRA) {Items(ITEM_NONE, itemOpponentRight);} + } WHEN { + TURN { MOVE(playerRight, MOVE_CORROSIVE_GAS); } + } SCENE { + MESSAGE("Wynaut used Corrosive Gas!"); + if (itemPlayerLeft == ITEM_CHERI_BERRY) { + MESSAGE("Wynaut corroded Wobbuffet's Cheri Berry!"); + } else { + MESSAGE("It won't have any effect on Wobbuffet!"); + } + if (itemOpponentLeft == ITEM_ORAN_BERRY) { + MESSAGE("Wynaut corroded the opposing Abra's Oran Berry!"); + } else { + MESSAGE("It won't have any effect on the opposing Abra!"); + } + if (itemOpponentRight == ITEM_CHESTO_BERRY) { + MESSAGE("Wynaut corroded the opposing Kadabra's Chesto Berry!"); + } else { + MESSAGE("It won't have any effect on the opposing Kadabra!"); + } + + } THEN { + EXPECT_EQ(playerRight->item, ITEM_SITRUS_BERRY); // Attacker doesn't lose its item. + EXPECT_EQ(playerLeft->item, ITEM_NONE); + EXPECT_EQ(opponentLeft->item, ITEM_NONE); + EXPECT_EQ(opponentRight->item, ITEM_NONE); + } +} +#endif diff --git a/test/battle/move_effect/curse.c b/test/battle/move_effect/curse.c index 355972e74e86..7efd4f43454e 100644 --- a/test/battle/move_effect/curse.c +++ b/test/battle/move_effect/curse.c @@ -74,3 +74,24 @@ SINGLE_BATTLE_TEST("Curse applies to the opponent if user is afflicted by Trick- } TO_DO_BATTLE_TEST("Baton Pass passes Cursed status"); + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Curse applies to the user if used with Protean/Libero (Traits)") +{ + u32 ability, species; + PARAMETRIZE { ability = ABILITY_PROTEAN; species = SPECIES_KECLEON; } + PARAMETRIZE { ability = ABILITY_LIBERO; species = SPECIES_RABOOT; } + GIVEN { + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CURSE, target: player); } + } SCENE { + s32 playerMaxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); + ABILITY_POPUP(player, ability); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CURSE, player); + HP_BAR(player, damage: playerMaxHP / 2); + HP_BAR(player, damage: playerMaxHP / 4); + } +} +#endif diff --git a/test/battle/move_effect/dragon_darts.c b/test/battle/move_effect/dragon_darts.c index 73df37e654c7..40d05a12a85a 100644 --- a/test/battle/move_effect/dragon_darts.c +++ b/test/battle/move_effect/dragon_darts.c @@ -308,3 +308,100 @@ DOUBLE_BATTLE_TEST("Dragon Darts fails to strike the second target if first targ } } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Dragon Darts strikes an opponent twice if electrified and the other one has Volt Absorb (Traits)") +{ + struct BattlePokemon *chosenTarget = NULL; + struct BattlePokemon *finalTarget = NULL; + enum Ability abilityLeft, abilityRight; + PARAMETRIZE { chosenTarget = opponentLeft; finalTarget = opponentLeft; abilityLeft = ABILITY_WATER_ABSORB; abilityRight = ABILITY_VOLT_ABSORB; } + PARAMETRIZE { chosenTarget = opponentRight; finalTarget = opponentLeft; abilityLeft = ABILITY_WATER_ABSORB; abilityRight = ABILITY_VOLT_ABSORB; } + PARAMETRIZE { chosenTarget = opponentLeft; finalTarget = opponentRight; abilityLeft = ABILITY_VOLT_ABSORB; abilityRight = ABILITY_WATER_ABSORB; } + PARAMETRIZE { chosenTarget = opponentRight; finalTarget = opponentRight; abilityLeft = ABILITY_VOLT_ABSORB; abilityRight = ABILITY_WATER_ABSORB; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_ELECTRIFY) == EFFECT_ELECTRIFY); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_LANTURN) { Ability(ABILITY_ILLUMINATE); Innates(abilityLeft); }; + OPPONENT(SPECIES_LANTURN) { Ability(ABILITY_ILLUMINATE); Innates(abilityRight); }; + } WHEN { + TURN { MOVE(opponentRight, MOVE_ELECTRIFY, target: playerLeft); MOVE(playerLeft, MOVE_DRAGON_DARTS, target: chosenTarget); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(finalTarget); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(finalTarget); + MESSAGE("The Pokémon was hit 2 time(s)!"); + } +} + +DOUBLE_BATTLE_TEST("Dragon Darts strikes an opponent twice if electrified and the other one has Motor Drive (Traits)") +{ + struct BattlePokemon *chosenTarget = NULL; + struct BattlePokemon *finalTarget = NULL; + enum Ability abilityLeft, abilityRight; + PARAMETRIZE { chosenTarget = opponentLeft; finalTarget = opponentLeft; abilityLeft = ABILITY_VITAL_SPIRIT; abilityRight = ABILITY_MOTOR_DRIVE; } + PARAMETRIZE { chosenTarget = opponentRight; finalTarget = opponentLeft; abilityLeft = ABILITY_VITAL_SPIRIT; abilityRight = ABILITY_MOTOR_DRIVE; } + PARAMETRIZE { chosenTarget = opponentLeft; finalTarget = opponentRight; abilityLeft = ABILITY_MOTOR_DRIVE; abilityRight = ABILITY_VITAL_SPIRIT; } + PARAMETRIZE { chosenTarget = opponentRight; finalTarget = opponentRight; abilityLeft = ABILITY_MOTOR_DRIVE; abilityRight = ABILITY_VITAL_SPIRIT; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_ELECTRIFY) == EFFECT_ELECTRIFY); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ELECTIVIRE) { Ability(ABILITY_ILLUMINATE); Innates(abilityLeft); }; + OPPONENT(SPECIES_ELECTIVIRE) { Ability(ABILITY_ILLUMINATE); Innates(abilityRight); }; + } WHEN { + TURN { MOVE(opponentRight, MOVE_ELECTRIFY, target: playerLeft); MOVE(playerLeft, MOVE_DRAGON_DARTS, target: chosenTarget); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(finalTarget); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(finalTarget); + MESSAGE("The Pokémon was hit 2 time(s)!"); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +DOUBLE_BATTLE_TEST("Dragon Darts strikes left ally twice if one strike misses (Multi)") +{ + struct BattlePokemon *chosenTarget = NULL; + struct BattlePokemon *finalTarget = NULL; + u32 itemLeft, itemRight; + PARAMETRIZE { chosenTarget = opponentLeft; finalTarget = opponentRight; itemLeft = ITEM_BRIGHT_POWDER; itemRight = ITEM_NONE; } + PARAMETRIZE { chosenTarget = opponentRight; finalTarget = opponentLeft; itemLeft = ITEM_NONE; itemRight = ITEM_BRIGHT_POWDER; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, itemLeft); }; + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, itemRight); }; + } WHEN { + TURN { MOVE(playerLeft, MOVE_DRAGON_DARTS, target: chosenTarget, hit: FALSE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(finalTarget); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(finalTarget); + MESSAGE("The Pokémon was hit 2 time(s)!"); + } +} + +DOUBLE_BATTLE_TEST("Dragon Darts strikes right ally twice if one strike misses (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, 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("The Pokémon was hit 2 time(s)!"); + } +} +#endif diff --git a/test/battle/move_effect/dream_eater.c b/test/battle/move_effect/dream_eater.c index d65064c92369..60f8400c7585 100644 --- a/test/battle/move_effect/dream_eater.c +++ b/test/battle/move_effect/dream_eater.c @@ -115,3 +115,23 @@ SINGLE_BATTLE_TEST("Dream Eater works if the target is behind a Substitute (Gen } } #endif + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Dream Eater works on targets with Comatose (Traits)") +{ + s16 damage; + s16 healed; + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_KOMALA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMATOSE); } + } WHEN { + TURN { MOVE(player, MOVE_DREAM_EATER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DREAM_EATER, player); + HP_BAR(opponent, captureDamage: &damage); + HP_BAR(player, captureDamage: &healed); + } THEN { + EXPECT_MUL_EQ(damage, Q_4_12(-1.0/2.0), healed); + } +} +#endif diff --git a/test/battle/move_effect/electric_terrain.c b/test/battle/move_effect/electric_terrain.c index bf6d2536e61b..1269ba9e7775 100644 --- a/test/battle/move_effect/electric_terrain.c +++ b/test/battle/move_effect/electric_terrain.c @@ -70,3 +70,23 @@ SINGLE_BATTLE_TEST("Electric Terrain lasts for 5 turns") MESSAGE("The electricity disappeared from the battlefield."); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Electric Terrain protects grounded battlers from falling asleep (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CLAYDOL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_LEVITATE); } + } WHEN { + TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); MOVE(opponent, MOVE_SPORE); } + TURN { MOVE(player, MOVE_SPORE); } + } SCENE { + MESSAGE("Wobbuffet used Electric Terrain!"); + MESSAGE("The opposing Claydol used Spore!"); + MESSAGE("Wobbuffet surrounds itself with electrified terrain!"); + MESSAGE("Wobbuffet used Spore!"); + MESSAGE("The opposing Claydol fell asleep!"); + STATUS_ICON(opponent, sleep: TRUE); + } +} +#endif diff --git a/test/battle/move_effect/embargo.c b/test/battle/move_effect/embargo.c index ef577fef61bc..6953846ccb43 100644 --- a/test/battle/move_effect/embargo.c +++ b/test/battle/move_effect/embargo.c @@ -391,3 +391,391 @@ SINGLE_BATTLE_TEST("Embargo doesn't prevent Primal Reversion") TO_DO_BATTLE_TEST("Embargo doesn't prevent the usage of Z-Moves") TO_DO_BATTLE_TEST("Embargo doesn't block held item effects that affect prize money") + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Embargo blocks the effect of an affected Pokémon's held item (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_FOCUS_SASH].holdEffect == HOLD_EFFECT_FOCUS_SASH); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_FOCUS_SASH); }; + } WHEN { + TURN { MOVE(player, MOVE_EMBARGO); } + TURN { MOVE(player, MOVE_FISSURE); } + } SCENE { + // Turn 1 + MESSAGE("Wobbuffet used Embargo!"); + MESSAGE("The opposing Wobbuffet can't use items anymore!"); + // Turn 2 + MESSAGE("Wobbuffet used Fissure!"); + HP_BAR(opponent, hp: 0); + } +} + +SINGLE_BATTLE_TEST("Embargo blocks an affected Pokémon's trainer from using items (Multi)") +{ + // As of writing, the battle tests system doesn't perform all the operations involved + // in the action of an NPC using an item in battle. + KNOWN_FAILING; + GIVEN { + ASSUME(gItemsInfo[ITEM_POTION].battleUsage == EFFECT_ITEM_RESTORE_HP); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_EMBARGO); } + TURN { USE_ITEM(opponent, ITEM_POTION, partyIndex: 0); } + } SCENE { + MESSAGE("Wobbuffet used Embargo!"); + MESSAGE("The opposing Wobbuffet can't use items anymore!"); + } THEN { + EXPECT_EQ(opponent->hp, 1); + } +} + +WILD_BATTLE_TEST("Embargo doesn't block held item effects that affect experience gain (Multi)", s32 exp) +{ + u32 item; + + PARAMETRIZE { item = ITEM_LUCKY_EGG; } + PARAMETRIZE { item = ITEM_NONE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Level(20); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_CATERPIE) { Level(10); HP(1); } + ASSUME(gItemsInfo[ITEM_LUCKY_EGG].holdEffect == HOLD_EFFECT_LUCKY_EGG); + } WHEN { + TURN { MOVE(opponent, MOVE_EMBARGO); MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("The wild Caterpie used Embargo!"); + MESSAGE("Wobbuffet can't use items anymore!"); + MESSAGE("Wobbuffet used Scratch!"); + MESSAGE("The wild Caterpie fainted!"); + EXPERIENCE_BAR(player, captureGainedExp: &results[i].exp); + } FINALLY { + EXPECT_MUL_EQ(results[1].exp, Q_4_12(1.5), results[0].exp); + } +} + +WILD_BATTLE_TEST("Embargo doesn't block held item effects that affect effort values (Multi)") +{ + u32 finalHPEVAmount; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_POWER_WEIGHT); } + OPPONENT(SPECIES_CATERPIE) { HP(1); } + ASSUME(gItemsInfo[ITEM_POWER_WEIGHT].holdEffect == HOLD_EFFECT_POWER_ITEM); + ASSUME(gItemsInfo[ITEM_POWER_WEIGHT].holdEffectParam != 0); + ASSUME(gItemsInfo[ITEM_POWER_WEIGHT].secondaryId == STAT_HP); + ASSUME(gSpeciesInfo[SPECIES_CATERPIE].evYield_HP == 1); + } WHEN { + TURN { MOVE(opponent, MOVE_EMBARGO); MOVE(player, MOVE_SCRATCH); } + } SCENE { + // Turn 1 + MESSAGE("The wild Caterpie used Embargo!"); + MESSAGE("Wobbuffet can't use items anymore!"); + // Turn 2 + MESSAGE("Wobbuffet used Scratch!"); + MESSAGE("The wild Caterpie fainted!"); + } THEN { + finalHPEVAmount = (GetMonData(&PLAYER_PARTY[0], MON_DATA_HP_EV) + gItemsInfo[ITEM_POWER_WEIGHT].holdEffectParam + gSpeciesInfo[SPECIES_CATERPIE].evYield_HP); + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_HP_EV), finalHPEVAmount); + } +} + +SINGLE_BATTLE_TEST("Embargo negates a held item's Speed reduction (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(19); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_MACHO_BRACE); Speed(20); } + ASSUME(gItemsInfo[ITEM_MACHO_BRACE].holdEffect == HOLD_EFFECT_MACHO_BRACE); + } WHEN { + TURN { MOVE(player, MOVE_EMBARGO); } + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + // Turn 1 + MESSAGE("Wobbuffet used Embargo!"); + MESSAGE("The opposing Wobbuffet can't use items anymore!"); + // Turn 2 + MESSAGE("The opposing Wobbuffet used Scratch!"); + MESSAGE("Wobbuffet used Scratch!"); + } +} + +// This is a useful test, but under the current circumstances, we can't actually test this without modifying +// X_ITEM_FRIENDSHIP_INCREASE. Since HOLD_EFFECT_FRIENDSHIP_UP applies a 1.5x modifier, and the stock +// Friendship increase is 1, the held item effect actually does not affect the Friendship gained. +// +// WILD_BATTLE_TEST("Embargo doesn't block held item effects that affect friendship (Multi)") +// { +// u32 initialFriendship; +// u32 finalFriendship; + +// KNOWN_FAILING; // Pokémon are currently not obtaining Friendship for using items in battle. +// GIVEN { +// ASSUME(gItemsInfo[ITEM_X_ACCURACY].battleUsage == EFFECT_ITEM_INCREASE_STAT); +// PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_SOOTHE_BELL); }; +// OPPONENT(SPECIES_WOBBUFFET); +// } WHEN { +// TURN { USE_ITEM(player, ITEM_X_ACCURACY); } +// TURN { MOVE(player, MOVE_SING); } +// } SCENE { +// MESSAGE("Wobbuffet used Sing!"); +// MESSAGE("Wild Wobbuffet fell asleep!"); +// } THEN { +// initialFriendship = GetMonData(&PLAYER_PARTY[0], MON_DATA_FRIENDSHIP); +// finalFriendship = GetMonData(&gPlayerParty[0], MON_DATA_FRIENDSHIP); +// EXPECT_EQ(finalFriendship, initialFriendship + 2); +// } +// } + +SINGLE_BATTLE_TEST("Embargo doesn't block a held item's form-changing effect, but it does block its other effects (Multi)", s16 damage) +{ + u32 heldItem; + + PARAMETRIZE { heldItem = ITEM_NONE; } + PARAMETRIZE { heldItem = ITEM_MEADOW_PLATE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ARCEUS_GRASS) { Items(ITEM_PECHA_BERRY, heldItem); }; + ASSUME(gItemsInfo[ITEM_MEADOW_PLATE].holdEffect == HOLD_EFFECT_PLATE); + ASSUME(gItemsInfo[ITEM_MEADOW_PLATE].holdEffectParam == 20); + ASSUME(gItemsInfo[ITEM_MEADOW_PLATE].secondaryId == TYPE_GRASS); + } WHEN { + TURN { MOVE(player, MOVE_EMBARGO); MOVE(opponent, MOVE_RAZOR_LEAF); } + } SCENE { + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.0), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Embargo makes Fling and Natural Gift fail (Multi)") +{ + u32 heldItem, moveId; + + PARAMETRIZE { heldItem = ITEM_LIGHT_BALL; moveId = MOVE_FLING; } + PARAMETRIZE { heldItem = ITEM_CHERI_BERRY; moveId = MOVE_NATURAL_GIFT; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, heldItem); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_EMBARGO); } + TURN { MOVE(player, moveId); } + } SCENE { + // Turn 1 + MESSAGE("The opposing Wobbuffet used Embargo!"); + MESSAGE("Wobbuffet can't use items anymore!"); + // Turn 2 + if (moveId == MOVE_FLING) + MESSAGE("Wobbuffet used Fling!"); + else + MESSAGE("Wobbuffet used Natural Gift!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Embargo doesn't stop an item flung at an affected target from activating (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LIGHT_BALL); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_EMBARGO); } + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + // Turn 1 + MESSAGE("Wobbuffet used Embargo!"); + MESSAGE("The opposing Wobbuffet can't use items anymore!"); + // Turn 2 + MESSAGE("Wobbuffet used Fling!"); + MESSAGE("Wobbuffet flung its Light Ball!"); + HP_BAR(opponent); + MESSAGE("The opposing Wobbuffet is paralyzed, so it may be unable to move!"); + } +} + +SINGLE_BATTLE_TEST("Baton Pass passes Embargo's effect (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { Items(ITEM_PECHA_BERRY, ITEM_LIGHT_BALL); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_EMBARGO); } + TURN { MOVE(player, MOVE_BATON_PASS); SEND_OUT(player, 1); } + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + // Turn 1 + MESSAGE("The opposing Wobbuffet used Embargo!"); + MESSAGE("Wobbuffet can't use items anymore!"); + // Turn 2 + MESSAGE("Wobbuffet used Baton Pass!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BATON_PASS, player); + SEND_IN_MESSAGE("Wynaut"); + // Turn 3 + MESSAGE("Wynaut used Fling!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Embargo doesn't block the effects of berries obtained through Bug Bite or Pluck (Multi)") +{ + u32 hp = 10; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); }; + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_ORAN_BERRY); }; + } WHEN { + TURN { MOVE(opponent, MOVE_EMBARGO); } + TURN { MOVE(player, MOVE_PLUCK); } + } SCENE { + // Turn 1 + MESSAGE("The opposing Wobbuffet used Embargo!"); + MESSAGE("Wobbuffet can't use items anymore!"); + // Turn 2 + MESSAGE("Wobbuffet used Pluck!"); + HP_BAR(opponent); + MESSAGE("Wobbuffet stole and ate its target's Oran Berry!"); + HP_BAR(player, damage: -hp); + } +} + +SINGLE_BATTLE_TEST("Embargo disables the effect of the Plate items on the move Judgment (Multi)", s16 damage) +{ + u32 heldItem; + + PARAMETRIZE { heldItem = ITEM_NONE; } + PARAMETRIZE { heldItem = ITEM_PIXIE_PLATE; } + GIVEN { + PLAYER(SPECIES_ARCEUS) { Items(ITEM_PECHA_BERRY, heldItem); }; + OPPONENT(SPECIES_DRAGONITE); + } WHEN { + TURN { MOVE(opponent, MOVE_EMBARGO); MOVE(player, MOVE_JUDGMENT); } + } SCENE { + MESSAGE("The opposing Dragonite used Embargo!"); + MESSAGE("Arceus can't use items anymore!"); + MESSAGE("Arceus used Judgment!"); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.0), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Embargo disables the effect of the Drive items on the move Techno Blast (Multi)", s16 damage) +{ + u32 heldItem; + + PARAMETRIZE { heldItem = ITEM_NONE; } + PARAMETRIZE { heldItem = ITEM_SHOCK_DRIVE; } + GIVEN { + PLAYER(SPECIES_GENESECT) { Items(ITEM_PECHA_BERRY, heldItem); }; + OPPONENT(SPECIES_GYARADOS); + } WHEN { + TURN { MOVE(opponent, MOVE_EMBARGO); MOVE(player, MOVE_TECHNO_BLAST); } + } SCENE { + MESSAGE("The opposing Gyarados used Embargo!"); + MESSAGE("Genesect can't use items anymore!"); + MESSAGE("Genesect used Techno Blast!"); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.0), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Embargo disables the effect of the Memory items on the move Multi Attack (Multi)", s16 damage) +{ + u32 heldItem; + + PARAMETRIZE { heldItem = ITEM_NONE; } + PARAMETRIZE { heldItem = ITEM_FIRE_MEMORY; } + GIVEN { + PLAYER(SPECIES_SILVALLY) { Items(ITEM_PECHA_BERRY, heldItem); }; + OPPONENT(SPECIES_VENUSAUR); + } WHEN { + TURN { MOVE(opponent, MOVE_EMBARGO); MOVE(player, MOVE_MULTI_ATTACK); } + } SCENE { + MESSAGE("The opposing Venusaur used Embargo!"); + MESSAGE("Silvally can't use items anymore!"); + MESSAGE("Silvally used Multi-Attack!"); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.0), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Embargo can be reflected by Magic Coat (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LIGHT_BALL); }; + } WHEN { + TURN { MOVE(player, MOVE_MAGIC_COAT); MOVE(opponent, MOVE_EMBARGO); } + TURN { MOVE(opponent, MOVE_FLING); } + } SCENE { + // Turn 1 + MESSAGE("Wobbuffet used Magic Coat!"); + MESSAGE("Wobbuffet shrouded itself with Magic Coat!"); + MESSAGE("The opposing Wobbuffet used Embargo!"); + MESSAGE("Wobbuffet bounced the Embargo back!"); + MESSAGE("The opposing Wobbuffet can't use items anymore!"); + // Turn 2 + MESSAGE("The opposing Wobbuffet used Fling!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Embargo doesn't prevent Mega Evolution (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CHARIZARD) { Items(ITEM_PECHA_BERRY, ITEM_CHARIZARDITE_Y); }; + } WHEN { + TURN { MOVE(player, MOVE_EMBARGO); } + TURN { MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); } + TURN { MOVE(opponent, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + } SCENE { + // Turn 1 + MESSAGE("Wobbuffet used Embargo!"); + MESSAGE("The opposing Wobbuffet can't use items anymore!"); + // Turn 2 + MESSAGE("The opposing Wobbuffet used Baton Pass!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BATON_PASS, opponent); + MESSAGE("2 sent out Charizard!"); + // Turn 3 + MESSAGE("The opposing Charizard's Charizardite Y is reacting to 2's Mega Ring!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponent); + MESSAGE("The opposing Charizard has Mega Evolved into Mega Charizard!"); + } +} + +SINGLE_BATTLE_TEST("Embargo doesn't prevent Primal Reversion (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); }; + } WHEN { + TURN { MOVE(player, MOVE_EMBARGO); } + TURN { MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); } + TURN { MOVE(opponent, MOVE_FLING); } + } SCENE { + // Turn 1 + MESSAGE("Wobbuffet used Embargo!"); + MESSAGE("The opposing Wobbuffet can't use items anymore!"); + // Turn 2 + MESSAGE("The opposing Wobbuffet used Baton Pass!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BATON_PASS, opponent); + MESSAGE("2 sent out Groudon!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, opponent); + MESSAGE("The opposing Groudon's Primal Reversion! It reverted to its primal state!"); + ABILITY_POPUP(opponent); + // Turn 3 + MESSAGE("The opposing Groudon used Fling!"); + MESSAGE("But it failed!"); + } +} + +TO_DO_BATTLE_TEST("Embargo doesn't prevent the usage of Z-Moves (Multi)") +TO_DO_BATTLE_TEST("Embargo doesn't block held item effects that affect prize money (Multi)") +#endif diff --git a/test/battle/move_effect/endure.c b/test/battle/move_effect/endure.c index 2d66bc41ef3b..235f9c7e69ab 100644 --- a/test/battle/move_effect/endure.c +++ b/test/battle/move_effect/endure.c @@ -81,3 +81,32 @@ TO_DO_BATTLE_TEST("Endure doesn't trigger effects that require damage to be done TO_DO_BATTLE_TEST("Endure triggers effects that require damage to be done to the Pokémon (Gen 5+)"); // Eg. Rough Skin TO_DO_BATTLE_TEST("Endure doesn't protect against Future Sight (Gen 2-4)"); TO_DO_BATTLE_TEST("Endure protects against Future Sight (Gen 5+)"); + +#if MAX_MON_ITEMS > 1 +DOUBLE_BATTLE_TEST("Endure is not transferred to a mon that is switched in due to Eject Button (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { HP(1); Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_SQUIRTLE) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(opponentRight, MOVE_ENDURE); + MOVE(playerLeft, MOVE_POUND, target: opponentRight); + SEND_OUT(opponentRight, 2); + MOVE(playerRight, MOVE_POUND, target: opponentRight); + SEND_OUT(opponentRight, 3); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ENDURE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, playerLeft); + MESSAGE("The opposing Wynaut endured the hit!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, playerRight); + NOT MESSAGE("The opposing Squirtle endured the hit!"); + } +} +#endif diff --git a/test/battle/move_effect/entrainment.c b/test/battle/move_effect/entrainment.c index ead4ff3b60a4..b6f914760cd7 100644 --- a/test/battle/move_effect/entrainment.c +++ b/test/battle/move_effect/entrainment.c @@ -74,3 +74,20 @@ SINGLE_BATTLE_TEST("Entrainment causes primal weather to revert") } TO_DO_BATTLE_TEST("Entrainment fails on Dynamaxed Pokémon"); + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Entrainment causes primal weather to revert (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); } + OPPONENT(SPECIES_GROUDON) { Items(ITEM_PECHA_BERRY, ITEM_RED_ORB); } + } WHEN { + TURN { MOVE(player, MOVE_ENTRAINMENT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ENTRAINMENT, player); + MESSAGE("The extremely harsh sunlight faded!"); + } THEN { + EXPECT(opponent->ability == ABILITY_TELEPATHY); + } +} +#endif diff --git a/test/battle/move_effect/explosion.c b/test/battle/move_effect/explosion.c index d54648e13af8..819066693a35 100644 --- a/test/battle/move_effect/explosion.c +++ b/test/battle/move_effect/explosion.c @@ -145,3 +145,45 @@ DOUBLE_BATTLE_TEST("Explosion boosted by Galvanize is correctly blocked by Volt MESSAGE("Geodude fainted!"); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Explosion boosted by Galvanize is correctly blocked by Volt Absorb (Traits)") +{ + GIVEN { + PLAYER(SPECIES_GEODUDE_ALOLA) { Ability(ABILITY_MAGNET_PULL); Innates(ABILITY_GALVANIZE); } + PLAYER(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_LANTURN) { Ability(ABILITY_ILLUMINATE); Innates(ABILITY_VOLT_ABSORB); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_EXPLOSION); } + } SCENE { + MESSAGE("Geodude used Explosion!"); + HP_BAR(playerLeft, hp: 0); + ABILITY_POPUP(opponentLeft, ABILITY_VOLT_ABSORB); + NOT HP_BAR(opponentLeft, hp: 0); + HP_BAR(playerRight, hp: 0); + HP_BAR(opponentRight, hp: 0); + MESSAGE("Wynaut fainted!"); + MESSAGE("The opposing Wobbuffet fainted!"); + MESSAGE("Geodude fainted!"); + } +} + +SINGLE_BATTLE_TEST("Explosion is blocked by Ability Damp (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_SWIFT_SWIM); Innates(ABILITY_DAMP); } + } WHEN { + TURN { MOVE(player, MOVE_EXPLOSION); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, player); + HP_BAR(player, hp: 0); + } + ABILITY_POPUP(opponent, ABILITY_DAMP); + MESSAGE("The opposing Golduck's Damp prevents Wobbuffet from using Explosion!"); + } +} + +#endif diff --git a/test/battle/move_effect/fail_if_not_arg_type.c b/test/battle/move_effect/fail_if_not_arg_type.c index 9714c15752b1..77b24323e4a1 100644 --- a/test/battle/move_effect/fail_if_not_arg_type.c +++ b/test/battle/move_effect/fail_if_not_arg_type.c @@ -133,3 +133,24 @@ SINGLE_BATTLE_TEST("Double Shock user loses its Electric-type if enemy faints") MESSAGE("Pikachu used up all its electricity!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Burn Up fails if the user has Protean/Libero and is not a Fire-type (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_PROTEAN_LIBERO, GEN_6); + PLAYER(SPECIES_REGIROCK); + OPPONENT(SPECIES_KECLEON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTEAN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_BURN_UP); } + } SCENE { + MESSAGE("The opposing Kecleon used Burn Up!"); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_PROTEAN); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BURN_UP, player); + } + MESSAGE("But it failed!"); + } +} +#endif diff --git a/test/battle/move_effect/fillet_away.c b/test/battle/move_effect/fillet_away.c index 0cd53e6848a6..05bcf2275d1c 100644 --- a/test/battle/move_effect/fillet_away.c +++ b/test/battle/move_effect/fillet_away.c @@ -74,3 +74,21 @@ SINGLE_BATTLE_TEST("Fillet Away's HP cost doesn't trigger effects that trigger o } TO_DO_BATTLE_TEST("Fillet Away fails if the user's Attack, Sp. Atk and Speed are all maxed out") + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Fillet Away's HP cost doesn't trigger effects that trigger on damage taken (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_AIR_BALLOON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FILLET_AWAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FILLET_AWAY, player); + MESSAGE("Wobbuffet's Attack sharply rose!"); + MESSAGE("Wobbuffet's Sp. Atk sharply rose!"); + MESSAGE("Wobbuffet's Speed sharply rose!"); + NOT MESSAGE("Wobbuffet's Air Balloon popped!"); + } +} +#endif diff --git a/test/battle/move_effect/fling.c b/test/battle/move_effect/fling.c index 440d43ccb607..453778f9c3e2 100644 --- a/test/battle/move_effect/fling.c +++ b/test/battle/move_effect/fling.c @@ -534,3 +534,672 @@ SINGLE_BATTLE_TEST("Fling deals damage based on a TM's move power if reusable or EXPECT_EQ(damage[0], damage[1]); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Fling fails for Pokémon with Klutz ability (Traits)") +{ + enum Ability ability; + + PARAMETRIZE {ability = ABILITY_KLUTZ; } + PARAMETRIZE {ability = ABILITY_RUN_AWAY; } + + GIVEN { + ASSUME(B_KLUTZ_FLING_INTERACTION >= GEN_5); + PLAYER(SPECIES_BUNEARY) { Item(ITEM_RAZOR_CLAW); Ability(ABILITY_RUN_AWAY); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + MESSAGE("Buneary used Fling!"); + if (ability != ABILITY_KLUTZ) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + } else { + MESSAGE("But it failed!"); + } + } +} + +SINGLE_BATTLE_TEST("Fling - Item does not get blocked by Unnerve if it isn't a berry (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TAUNT) == EFFECT_TAUNT); + PLAYER(SPECIES_CALYREX) { Item(ITEM_MENTAL_HERB); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_UNNERVE); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_ORAN_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TAUNT); MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_FLING); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAUNT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + MESSAGE("The opposing Wobbuffet's Taunt wore off!"); + } +} + +SINGLE_BATTLE_TEST("Fling's secondary effects are blocked by Shield Dust (Traits)") +{ + u16 item; + + PARAMETRIZE {item = ITEM_FLAME_ORB; } + PARAMETRIZE {item = ITEM_LIGHT_BALL; } + PARAMETRIZE {item = ITEM_POISON_BARB; } + PARAMETRIZE {item = ITEM_TOXIC_ORB; } + PARAMETRIZE {item = ITEM_RAZOR_FANG; } + PARAMETRIZE {item = ITEM_KINGS_ROCK; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(item); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Innates(ABILITY_SHIELD_DUST); } + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + MESSAGE("Wobbuffet used Fling!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + switch (item) + { + case ITEM_FLAME_ORB: + { + NONE_OF { + MESSAGE("The opposing Wobbuffet was burned!"); + STATUS_ICON(opponent, STATUS1_BURN); + } + MESSAGE("The Flame Orb was used up…"); + } + break; + case ITEM_LIGHT_BALL: + { + NONE_OF { + MESSAGE("The opposing Wobbuffet is paralyzed, so it may be unable to move!"); + STATUS_ICON(opponent, STATUS1_PARALYSIS); + } + MESSAGE("The Light Ball was used up…"); + } + break; + case ITEM_POISON_BARB: + { + NONE_OF { + MESSAGE("The opposing Wobbuffet was poisoned!"); + STATUS_ICON(opponent, STATUS1_POISON); + } + MESSAGE("The Poison Barb was used up…"); + } + break; + case ITEM_TOXIC_ORB: + { + NONE_OF { + MESSAGE("The opposing Wobbuffet was badly poisoned!"); + STATUS_ICON(opponent, STATUS1_TOXIC_POISON); + } + MESSAGE("The Toxic Orb was used up…"); + } + break; + case ITEM_RAZOR_FANG: + case ITEM_KINGS_ROCK: + { + NONE_OF { + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + } + switch (item) + { + case ITEM_RAZOR_FANG: + MESSAGE("The Razor Fang was used up…"); + break; + case ITEM_KINGS_ROCK: + MESSAGE("The King's Rock was used up…"); + break; + } + } + break; + } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Fling fails if Pokémon holds no item (Multi)") +{ + u16 item; + + PARAMETRIZE {item = ITEM_NONE; } + PARAMETRIZE {item = ITEM_RAZOR_CLAW; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FLING);} + } SCENE { + MESSAGE("Wobbuffet used Fling!"); + if (item != ITEM_NONE) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + } else { + MESSAGE("But it failed!"); + } + } +} + +SINGLE_BATTLE_TEST("Fling fails if Pokémon is under the effects of Embargo or Magic Room (Multi)") +{ + u16 move; + + PARAMETRIZE {move = MOVE_CELEBRATE; } + PARAMETRIZE {move = MOVE_EMBARGO; } + PARAMETRIZE {move = MOVE_MAGIC_ROOM; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_EMBARGO) == EFFECT_EMBARGO); + ASSUME(GetMoveEffect(MOVE_MAGIC_ROOM) == EFFECT_MAGIC_ROOM); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_RAZOR_CLAW); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + MESSAGE("Wobbuffet used Fling!"); + if (move == MOVE_CELEBRATE) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + } else { + MESSAGE("But it failed!"); + } + } +} + +SINGLE_BATTLE_TEST("Fling fails for Pokémon with Klutz ability (Gen5+) (Multi)") +{ + enum Ability ability; + u32 config; + + PARAMETRIZE { ability = ABILITY_RUN_AWAY; config = GEN_4; } + PARAMETRIZE { ability = ABILITY_KLUTZ; config = GEN_4; } + PARAMETRIZE { ability = ABILITY_KLUTZ; config = GEN_5; } + + GIVEN { + WITH_CONFIG(CONFIG_KLUTZ_FLING_INTERACTION, config); + PLAYER(SPECIES_BUNEARY) { Items(ITEM_NONE, ITEM_RAZOR_CLAW); Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + MESSAGE("Buneary used Fling!"); + if (ability != ABILITY_KLUTZ || config == GEN_4) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + } else { + MESSAGE("But it failed!"); + } + } +} + +SINGLE_BATTLE_TEST("Fling fails if the item changes the Pokémon's form (Multi)") +{ + GIVEN { + PLAYER(SPECIES_GIRATINA_ORIGIN) { Items(ITEM_NONE, ITEM_GRISEOUS_CORE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(player->item == ITEM_GRISEOUS_CORE); + } +} + +SINGLE_BATTLE_TEST("Fling works if the item changes a Pokémon's form but not the one holding it (Multi)") +{ + GIVEN { + PLAYER(SPECIES_VENUSAUR) { Items(ITEM_NONE, ITEM_BLASTOISINITE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + NOT MESSAGE("But it failed!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + } THEN { + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Fling's thrown item can be regained with Recycle (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_RECYCLE) == EFFECT_RECYCLE); + PLAYER(SPECIES_WOBBUFFET) {Items(ITEM_NONE, ITEM_RAZOR_CLAW); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FLING);} + TURN { MOVE(player, MOVE_RECYCLE);} + TURN { MOVE(player, MOVE_FLING);} + } SCENE { + MESSAGE("Wobbuffet used Fling!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + MESSAGE("Wobbuffet used Recycle!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RECYCLE, player); + MESSAGE("Wobbuffet found one Razor Claw!"); + MESSAGE("Wobbuffet used Fling!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Fling - Item is lost even when there is no target (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SELF_DESTRUCT) == EFFECT_EXPLOSION); + PLAYER(SPECIES_WOBBUFFET) {Items(ITEM_NONE, ITEM_RAZOR_CLAW); Speed(2); } + OPPONENT(SPECIES_WOBBUFFET) {Speed(5); } + OPPONENT(SPECIES_WOBBUFFET) {Speed(5); } + } WHEN { + TURN { MOVE(opponent, MOVE_SELF_DESTRUCT); MOVE(player, MOVE_FLING); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Self-Destruct!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SELF_DESTRUCT, opponent); + HP_BAR(player); + MESSAGE("The opposing Wobbuffet fainted!"); + MESSAGE("Wobbuffet used Fling!"); + MESSAGE("But it failed!"); + + MESSAGE("Wobbuffet used Fling!"); + MESSAGE("But it failed!"); + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Fling - Item is lost when target protects itself (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_PROTECT) == EFFECT_PROTECT); + PLAYER(SPECIES_WOBBUFFET) {Items(ITEM_NONE, ITEM_RAZOR_CLAW); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_PROTECT); MOVE(player, MOVE_FLING);} + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Protect!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponent); + MESSAGE("Wobbuffet used Fling!"); + MESSAGE("The opposing Wobbuffet protected itself!"); + + MESSAGE("Wobbuffet used Fling!"); + MESSAGE("But it failed!"); + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Fling - Item does not get blocked by Unnerve if it isn't a berry (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TAUNT) == EFFECT_TAUNT); + PLAYER(SPECIES_CALYREX) { Items(ITEM_NONE, ITEM_MENTAL_HERB); Ability(ABILITY_UNNERVE); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_ORAN_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TAUNT); MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_FLING); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAUNT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + MESSAGE("The opposing Wobbuffet's Taunt wore off!"); + } +} + +SINGLE_BATTLE_TEST("Fling doesn't consume the item if Pokémon is asleep/frozen/paralyzed (Multi)") +{ + u32 status; + u16 item; + + PARAMETRIZE {status = STATUS1_SLEEP_TURN(2); item = ITEM_RAZOR_CLAW; } + PARAMETRIZE {status = STATUS1_PARALYSIS; item = ITEM_RAZOR_CLAW; } + PARAMETRIZE {status = STATUS1_FREEZE; item = ITEM_RAZOR_CLAW; } + PARAMETRIZE {status = STATUS1_SLEEP_TURN(2); item = ITEM_NONE; } + PARAMETRIZE {status = STATUS1_PARALYSIS; item = ITEM_NONE; } + PARAMETRIZE {status = STATUS1_FREEZE; item = ITEM_NONE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) {Items(ITEM_NONE, item); Status1(status); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (status == STATUS1_FREEZE) { + TURN { MOVE(player, MOVE_FLING, WITH_RNG(RNG_FROZEN, FALSE)); } + TURN { MOVE(player, MOVE_FLING, WITH_RNG(RNG_FROZEN, TRUE)); } + } else if (status == STATUS1_PARALYSIS) { + TURN { MOVE(player, MOVE_FLING, WITH_RNG(RNG_PARALYSIS, FALSE)); } + TURN { MOVE(player, MOVE_FLING, WITH_RNG(RNG_PARALYSIS, TRUE)); } + } else { + TURN { MOVE(player, MOVE_FLING); } + TURN { MOVE(player, MOVE_FLING); } + } + } SCENE { + if (status == STATUS1_FREEZE) { + MESSAGE("Wobbuffet is frozen solid!"); + MESSAGE("Wobbuffet thawed out!"); + } + else if (status == STATUS1_PARALYSIS) { + MESSAGE("Wobbuffet couldn't move because it's paralyzed!"); + } + else { + MESSAGE("Wobbuffet is fast asleep."); + MESSAGE("Wobbuffet woke up!"); + } + MESSAGE("Wobbuffet used Fling!"); + if (item != ITEM_NONE) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + } else { + MESSAGE("But it failed!"); + } + + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Fling applies special effects when throwing specific Items (Multi)") +{ + u16 item; + + PARAMETRIZE {item = ITEM_FLAME_ORB; } + PARAMETRIZE {item = ITEM_LIGHT_BALL; } + PARAMETRIZE {item = ITEM_POISON_BARB; } + PARAMETRIZE {item = ITEM_TOXIC_ORB; } + PARAMETRIZE {item = ITEM_RAZOR_FANG; } + PARAMETRIZE {item = ITEM_KINGS_ROCK; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + MESSAGE("Wobbuffet used Fling!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + switch (item) + { + case ITEM_FLAME_ORB: + { + MESSAGE("The opposing Wobbuffet was burned!"); + STATUS_ICON(opponent, STATUS1_BURN); + } + break; + case ITEM_LIGHT_BALL: + { + MESSAGE("The opposing Wobbuffet is paralyzed, so it may be unable to move!"); + STATUS_ICON(opponent, STATUS1_PARALYSIS); + } + break; + case ITEM_POISON_BARB: + { + MESSAGE("The opposing Wobbuffet was poisoned!"); + STATUS_ICON(opponent, STATUS1_POISON); + } + break; + case ITEM_TOXIC_ORB: + { + MESSAGE("The opposing Wobbuffet was badly poisoned!"); + STATUS_ICON(opponent, STATUS1_TOXIC_POISON); + } + break; + case ITEM_RAZOR_FANG: + case ITEM_KINGS_ROCK: + { + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + } + break; + } + } +} + +SINGLE_BATTLE_TEST("Fling's secondary effects are blocked by Shield Dust (Multi)") +{ + u16 item; + + PARAMETRIZE {item = ITEM_FLAME_ORB; } + PARAMETRIZE {item = ITEM_LIGHT_BALL; } + PARAMETRIZE {item = ITEM_POISON_BARB; } + PARAMETRIZE {item = ITEM_TOXIC_ORB; } + PARAMETRIZE {item = ITEM_RAZOR_FANG; } + PARAMETRIZE {item = ITEM_KINGS_ROCK; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, item); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_SHIELD_DUST); } + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + MESSAGE("Wobbuffet used Fling!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + switch (item) + { + case ITEM_FLAME_ORB: + { + NONE_OF { + MESSAGE("The opposing Wobbuffet was burned!"); + STATUS_ICON(opponent, STATUS1_BURN); + } + MESSAGE("The Flame Orb was used up…"); + } + break; + case ITEM_LIGHT_BALL: + { + NONE_OF { + MESSAGE("The opposing Wobbuffet is paralyzed, so it may be unable to move!"); + STATUS_ICON(opponent, STATUS1_PARALYSIS); + } + MESSAGE("The Light Ball was used up…"); + } + break; + case ITEM_POISON_BARB: + { + NONE_OF { + MESSAGE("The opposing Wobbuffet was poisoned!"); + STATUS_ICON(opponent, STATUS1_POISON); + } + MESSAGE("The Poison Barb was used up…"); + } + break; + case ITEM_TOXIC_ORB: + { + NONE_OF { + MESSAGE("The opposing Wobbuffet was badly poisoned!"); + STATUS_ICON(opponent, STATUS1_TOXIC_POISON); + } + MESSAGE("The Toxic Orb was used up…"); + } + break; + case ITEM_RAZOR_FANG: + case ITEM_KINGS_ROCK: + { + NONE_OF { + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + } + switch (item) + { + case ITEM_RAZOR_FANG: + MESSAGE("The Razor Fang was used up…"); + break; + case ITEM_KINGS_ROCK: + MESSAGE("The King's Rock was used up…"); + break; + } + } + break; + } + } +} + +SINGLE_BATTLE_TEST("Fling - thrown berry's effect activates for the target even if the trigger conditions are not met (Multi)") +{ + u16 item, effect; + u8 statId = 0; + u32 status1 = STATUS1_NONE; + + PARAMETRIZE { item = ITEM_ORAN_BERRY; effect = HOLD_EFFECT_RESTORE_HP; } + PARAMETRIZE { item = ITEM_SITRUS_BERRY; effect = HOLD_EFFECT_RESTORE_HP; } + PARAMETRIZE { item = ITEM_ENIGMA_BERRY; effect = HOLD_EFFECT_ENIGMA_BERRY; } + PARAMETRIZE { item = ITEM_LEPPA_BERRY; effect = HOLD_EFFECT_RESTORE_PP; } + PARAMETRIZE { item = ITEM_CHESTO_BERRY; effect = HOLD_EFFECT_CURE_SLP; status1 = STATUS1_SLEEP; } + PARAMETRIZE { item = ITEM_CHERI_BERRY; effect = HOLD_EFFECT_CURE_PAR; status1 = STATUS1_PARALYSIS; } + PARAMETRIZE { item = ITEM_PECHA_BERRY; effect = HOLD_EFFECT_CURE_PSN; status1 = STATUS1_POISON; } + PARAMETRIZE { item = ITEM_PECHA_BERRY; effect = HOLD_EFFECT_CURE_PSN; status1 = STATUS1_TOXIC_POISON; } + PARAMETRIZE { item = ITEM_RAWST_BERRY; effect = HOLD_EFFECT_CURE_BRN; status1 = STATUS1_BURN; } + PARAMETRIZE { item = ITEM_ASPEAR_BERRY; effect = HOLD_EFFECT_CURE_FRZ; status1 = STATUS1_FREEZE; } + PARAMETRIZE { item = ITEM_ASPEAR_BERRY; effect = HOLD_EFFECT_CURE_FRZ; status1 = STATUS1_FROSTBITE; } + PARAMETRIZE { item = ITEM_APICOT_BERRY; effect = HOLD_EFFECT_SP_DEFENSE_UP; statId = STAT_SPDEF; } + PARAMETRIZE { item = ITEM_MARANGA_BERRY; effect = HOLD_EFFECT_MARANGA_BERRY; statId = STAT_SPDEF; } + PARAMETRIZE { item = ITEM_GANLON_BERRY; effect = HOLD_EFFECT_DEFENSE_UP; statId = STAT_DEF; } + PARAMETRIZE { item = ITEM_KEE_BERRY; effect = HOLD_EFFECT_KEE_BERRY; statId = STAT_DEF; } + PARAMETRIZE { item = ITEM_LIECHI_BERRY; effect = HOLD_EFFECT_ATTACK_UP; statId = STAT_ATK; } + PARAMETRIZE { item = ITEM_PETAYA_BERRY; effect = HOLD_EFFECT_SP_ATTACK_UP; statId = STAT_SPATK; } + PARAMETRIZE { item = ITEM_SALAC_BERRY; effect = HOLD_EFFECT_SPEED_UP; statId = STAT_SPEED; } + + GIVEN { + ASSUME(GetMoveCategory(MOVE_FLING) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, item); Attack(1); } + OPPONENT(SPECIES_WOBBUFFET) { Status1(status1); HP(399); MaxHP(400); MovesWithPP({MOVE_CELEBRATE, 35}); } + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + MESSAGE("Wobbuffet used Fling!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + if (effect == HOLD_EFFECT_RESTORE_HP) { + if (item == ITEM_ORAN_BERRY) { + MESSAGE("The opposing Wobbuffet restored its health using its Oran Berry!"); + } else if (item == ITEM_SITRUS_BERRY) { + MESSAGE("The opposing Wobbuffet restored its health using its Sitrus Berry!"); + } else { + MESSAGE("Wobbuffet restored its health using its Enigma Berry!"); + } + HP_BAR(opponent); + } + else if (effect == HOLD_EFFECT_RESTORE_PP) { + MESSAGE("The opposing Wobbuffet restored PP to its move Celebrate using its Leppa Berry!"); + } + else if (status1 != STATUS1_NONE) { + if (status1 == STATUS1_BURN) { + MESSAGE("The opposing Wobbuffet's Rawst Berry cured its burn!"); + } else if (status1 == STATUS1_SLEEP) { + MESSAGE("The opposing Wobbuffet's Chesto Berry woke it up!"); + } else if (status1 == STATUS1_FREEZE) { + MESSAGE("The opposing Wobbuffet's Aspear Berry defrosted it!"); + } else if (status1 == STATUS1_FROSTBITE) { + MESSAGE("The opposing Wobbuffet's Aspear Berry cured its frostbite!"); + } else if (status1 == STATUS1_PARALYSIS) { + MESSAGE("The opposing Wobbuffet's Cheri Berry cured its paralysis!"); + } else if (status1 == STATUS1_TOXIC_POISON || status1 == STATUS1_POISON) { + MESSAGE("The opposing Wobbuffet's Pecha Berry cured its poison!"); + } + NOT STATUS_ICON(opponent, status1); + } + else if (statId != 0) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + if (statId == STAT_ATK) { + MESSAGE("Using Liechi Berry, the Attack of the opposing Wobbuffet rose!"); + } else if (statId == STAT_DEF) { + if (item == ITEM_GANLON_BERRY) { + MESSAGE("Using Ganlon Berry, the Defense of the opposing Wobbuffet rose!"); + } else { + MESSAGE("Using Kee Berry, the Defense of the opposing Wobbuffet rose!"); + } + } else if (statId == STAT_SPDEF) { + if (item == ITEM_APICOT_BERRY) { + MESSAGE("Using Apicot Berry, the Sp. Def of the opposing Wobbuffet rose!"); + } else { + MESSAGE("Using Maranga Berry, the Sp. Def of the opposing Wobbuffet rose!"); + } + } else if (statId == STAT_SPEED) { + MESSAGE("Using Salac Berry, the Speed of the opposing Wobbuffet rose!"); + } else if (statId == STAT_SPATK) { + MESSAGE("Using Petaya Berry, the Sp. Atk of the opposing Wobbuffet rose!"); + } + } + } THEN { + if (effect == HOLD_EFFECT_RESTORE_HP) { + EXPECT_EQ(opponent->hp, opponent->maxHP); + } else if (effect == HOLD_EFFECT_RESTORE_PP) { + EXPECT_EQ(opponent->pp[0], 39); // Not 40, because Celebrate was used. + } else if (status1 != STATUS1_NONE) { + EXPECT_EQ(opponent->status1, STATUS1_NONE); + } + else if (statId != 0) { + EXPECT_EQ(opponent->statStages[statId], DEFAULT_STAT_STAGE + 1); + } + } +} + +SINGLE_BATTLE_TEST("Fling deals damage based on items fling power (Multi)") +{ + s16 damage[2]; + + GIVEN { + ASSUME(GetMovePower(MOVE_CRUNCH) == 80); + ASSUME(gItemsInfo[ITEM_VENUSAURITE].flingPower == 80); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_VENUSAURITE); } + OPPONENT(SPECIES_REGIROCK); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + TURN { MOVE(player, MOVE_CRUNCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CRUNCH, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} + +SINGLE_BATTLE_TEST("Fling deals damage based on a TM's move power (Multi)") +{ + s16 damage[2]; + + GIVEN { + ASSUME(GetMovePower(MOVE_EARTHQUAKE) == GetMovePower(MOVE_EGG_BOMB)); + ASSUME(!IsSpeciesOfType(SPECIES_WOBBUFFET, TYPE_DARK)); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_TM_EARTHQUAKE); } + OPPONENT(SPECIES_HIPPOWDON); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + TURN { MOVE(player, MOVE_EGG_BOMB); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EGG_BOMB, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} + +SINGLE_BATTLE_TEST("Fling deals damage based on a TM's move power (Multi)") +{ + s16 damage[2]; + + GIVEN { + ASSUME(GetMovePower(MOVE_EARTHQUAKE) == GetMovePower(MOVE_EGG_BOMB)); + ASSUME(!IsSpeciesOfType(SPECIES_WOBBUFFET, TYPE_DARK)); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_TM_EARTHQUAKE); } + OPPONENT(SPECIES_HIPPOWDON); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + TURN { MOVE(player, MOVE_EGG_BOMB); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EGG_BOMB, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} +#endif diff --git a/test/battle/move_effect/fury_cutter.c b/test/battle/move_effect/fury_cutter.c index 74362c631692..5bf4d254d710 100644 --- a/test/battle/move_effect/fury_cutter.c +++ b/test/battle/move_effect/fury_cutter.c @@ -64,3 +64,29 @@ SINGLE_BATTLE_TEST("Fury Cutter counter is the same for both hits of Parental Bo EXPECT_NE(damage[0], damage[2]); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Fury Cutter counter is the same for both hits of Parental Bond (Traits)") +{ + s16 damage[4]; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Innates(ABILITY_PARENTAL_BOND); } + OPPONENT(SPECIES_REGIROCK); + } WHEN { + TURN { MOVE(player, MOVE_FURY_CUTTER); } + TURN { MOVE(player, MOVE_FURY_CUTTER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_CUTTER, player); + HP_BAR(opponent, captureDamage: &damage[0]); + HP_BAR(opponent, captureDamage: &damage[1]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_CUTTER, player); + HP_BAR(opponent, captureDamage: &damage[2]); + HP_BAR(opponent, captureDamage: &damage[3]); + } THEN { + EXPECT_MUL_EQ(damage[0], B_PARENTAL_BOND_DMG >= GEN_7 ? UQ_4_12(0.25) : UQ_4_12(0.5), damage[1]); + EXPECT_MUL_EQ(damage[2], B_PARENTAL_BOND_DMG >= GEN_7 ? UQ_4_12(0.25) : UQ_4_12(0.5), damage[3]); + EXPECT_NE(damage[0], damage[2]); + } +} +#endif diff --git a/test/battle/move_effect/future_sight.c b/test/battle/move_effect/future_sight.c index 01a5bed349ff..3b60e72356c4 100644 --- a/test/battle/move_effect/future_sight.c +++ b/test/battle/move_effect/future_sight.c @@ -187,3 +187,81 @@ SINGLE_BATTLE_TEST("Future Sight breaks Focus Sash and doesn't make the holder e MESSAGE("The opposing Pidgey fainted!"); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Future Sight uses Sp. Atk stat of the original user without modifiers (Multi)") +{ + u32 item; + s16 seedFlareDmg; + s16 futureSightDmg; + + PARAMETRIZE { item = ITEM_TWISTED_SPOON; } + PARAMETRIZE { item = ITEM_PSYCHIC_GEM; } + + GIVEN { + PLAYER(SPECIES_PIKACHU) { Items(ITEM_PECHA_BERRY, item); } + PLAYER(SPECIES_RAICHU) { Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_REGICE); + } WHEN { + TURN { MOVE(player, FUTURE_SIGHT_EQUIVALENT, WITH_RNG(RNG_SECONDARY_EFFECT, FALSE)); } + TURN { MOVE(player, MOVE_FUTURE_SIGHT); } + TURN { SWITCH(player, 1); } + TURN { } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, FUTURE_SIGHT_EQUIVALENT, player); + HP_BAR(opponent, captureDamage: &seedFlareDmg); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player); + MESSAGE("The opposing Regice took the Future Sight attack!"); + HP_BAR(opponent, captureDamage: &futureSightDmg); + } THEN { + EXPECT_EQ(seedFlareDmg, futureSightDmg); + } +} + +SINGLE_BATTLE_TEST("Future Sight is not boosted by Life Orb is original user if not on the field (Multi)") +{ + s16 seedFlareDmg; + s16 futureSightDmg; + + GIVEN { + PLAYER(SPECIES_PIKACHU); + PLAYER(SPECIES_RAICHU) { Items(ITEM_PECHA_BERRY, ITEM_LIFE_ORB); } + OPPONENT(SPECIES_REGICE); + } WHEN { + TURN { MOVE(player, FUTURE_SIGHT_EQUIVALENT, WITH_RNG(RNG_SECONDARY_EFFECT, FALSE)); } + TURN { MOVE(player, MOVE_FUTURE_SIGHT); } + TURN { SWITCH(player, 1); } + TURN { } + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, FUTURE_SIGHT_EQUIVALENT, player); + HP_BAR(opponent, captureDamage: &seedFlareDmg); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player); + MESSAGE("The opposing Regice took the Future Sight attack!"); + HP_BAR(opponent, captureDamage: &futureSightDmg); + NOT HP_BAR(player); + } THEN { + EXPECT_EQ(seedFlareDmg, futureSightDmg); + } +} + +SINGLE_BATTLE_TEST("Future Sight breaks Focus Sash and doesn't make the holder endure another move (Multi)") +{ + GIVEN { + ASSUME(GetMovePower(MOVE_PSYCHIC) > 0); + ASSUME(gItemsInfo[ITEM_FOCUS_SASH].holdEffect == HOLD_EFFECT_FOCUS_SASH); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY) { Level(1); Items(ITEM_PECHA_BERRY, ITEM_FOCUS_SASH); } + } WHEN { + TURN { MOVE(player, MOVE_FUTURE_SIGHT); } + TURN { } + TURN { } + TURN { MOVE(player, MOVE_PSYCHIC); } + } SCENE { + MESSAGE("The opposing Pidgey hung on using its Focus Sash!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCHIC, player); + MESSAGE("The opposing Pidgey fainted!"); + } +} +#endif diff --git a/test/battle/move_effect/gear_up.c b/test/battle/move_effect/gear_up.c index 9cac73d69477..0d8baf7291df 100644 --- a/test/battle/move_effect/gear_up.c +++ b/test/battle/move_effect/gear_up.c @@ -45,3 +45,18 @@ DOUBLE_BATTLE_TEST("Gear Up raises Attack and Sp. Attack of all Plus/Minus allie EXPECT_EQ(opponentRight->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); } } + +#if MAX_MON_TRAITS > 1 +AI_DOUBLE_BATTLE_TEST("AI uses Gear Up (Traits)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, MOVE_CELEBRATE); } + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, MOVE_CELEBRATE); } + OPPONENT(SPECIES_KLINKLANG) { Ability(ABILITY_CLEAR_BODY); Innates(ABILITY_PLUS); Moves(MOVE_GEAR_UP, MOVE_WATER_GUN, MOVE_POUND); } + OPPONENT(SPECIES_KLINKLANG) { Ability(ABILITY_CLEAR_BODY); Innates(ABILITY_PLUS); Moves(MOVE_GEAR_UP, MOVE_WATER_GUN, MOVE_POUND); } + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, MOVE_GEAR_UP); } + } +} +#endif diff --git a/test/battle/move_effect/glaive_rush.c b/test/battle/move_effect/glaive_rush.c index 31e923443b38..3436dd3d8ed8 100644 --- a/test/battle/move_effect/glaive_rush.c +++ b/test/battle/move_effect/glaive_rush.c @@ -141,3 +141,29 @@ SINGLE_BATTLE_TEST("Glaive Rush status last until the the user's next turn") EXPECT_EQ(normalDmgFirstHit, normalDmgSecondHit); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("If Glaive Rush isn't successful moves targeted at the user don't deal double damage (Multi)", s16 damage) +{ + bool32 missesGlaiveRush; + + PARAMETRIZE { missesGlaiveRush = FALSE; } + PARAMETRIZE { missesGlaiveRush = TRUE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_BRIGHT_POWDER); } + } WHEN { + TURN { MOVE(player, MOVE_GLAIVE_RUSH, hit: missesGlaiveRush); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + if (!missesGlaiveRush) + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_GLAIVE_RUSH, player); + else + ANIMATION(ANIM_TYPE_MOVE, MOVE_GLAIVE_RUSH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[1].damage); + } +} +#endif diff --git a/test/battle/move_effect/gravity.c b/test/battle/move_effect/gravity.c index 7175f085f745..9fedf05c27ab 100644 --- a/test/battle/move_effect/gravity.c +++ b/test/battle/move_effect/gravity.c @@ -81,3 +81,37 @@ AI_DOUBLE_BATTLE_TEST("AI uses Gravity") TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_GRAVITY); } } } + +#if MAX_MON_ITEMS > 1 +AI_DOUBLE_BATTLE_TEST("AI uses Gravity (Multi)") +{ + u32 move, friendItem, foeItem; + u64 aiFlags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT; + + PARAMETRIZE { move = MOVE_THUNDER; friendItem = ITEM_NONE; foeItem = ITEM_NONE; } + PARAMETRIZE { move = MOVE_HEADBUTT; friendItem = ITEM_AIR_BALLOON; foeItem = ITEM_NONE; } + PARAMETRIZE { move = MOVE_HEADBUTT; friendItem = ITEM_AIR_BALLOON; foeItem = ITEM_AIR_BALLOON; } + PARAMETRIZE { move = MOVE_HEADBUTT; friendItem = ITEM_NONE; foeItem = ITEM_AIR_BALLOON; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + move = MOVE_THUNDER; friendItem = ITEM_NONE; foeItem = ITEM_NONE; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + move = MOVE_HEADBUTT; friendItem = ITEM_AIR_BALLOON; foeItem = ITEM_NONE; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + move = MOVE_HEADBUTT; friendItem = ITEM_AIR_BALLOON; foeItem = ITEM_AIR_BALLOON; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + move = MOVE_HEADBUTT; friendItem = ITEM_NONE; foeItem = ITEM_AIR_BALLOON; } + + GIVEN { + AI_FLAGS(aiFlags); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, foeItem); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_GRAVITY, MOVE_HEADBUTT, MOVE_TAUNT); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(move, MOVE_EARTH_POWER); Items(ITEM_PECHA_BERRY, friendItem); } + } WHEN { + if (move == MOVE_THUNDER || (foeItem == ITEM_AIR_BALLOON && friendItem != ITEM_AIR_BALLOON)) + TURN { EXPECT_MOVE(opponentLeft, MOVE_GRAVITY); } + else + TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_GRAVITY); } + } +} +#endif diff --git a/test/battle/move_effect/grudge.c b/test/battle/move_effect/grudge.c index 8da8b33fb89a..0f39b71b4ee9 100644 --- a/test/battle/move_effect/grudge.c +++ b/test/battle/move_effect/grudge.c @@ -305,3 +305,84 @@ SINGLE_BATTLE_TEST("Grudge's effect doesn't trigger on indirect damage - Future } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Grudge does not deplete PP of a Z-Move (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); Moves(MOVE_CELEBRATE, MOVE_SCRATCH, MOVE_POUND, MOVE_SURF); }; + } WHEN { + TURN { + MOVE(player, MOVE_GRUDGE); + MOVE(opponent, MOVE_SCRATCH, gimmick: GIMMICK_Z_MOVE); + SEND_OUT(player, 1); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRUDGE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, opponent); + MESSAGE("Wobbuffet fainted!"); + } THEN { + EXPECT_GT(opponent->pp[0], 0); + EXPECT_GT(opponent->pp[1], 0); + EXPECT_GT(opponent->pp[2], 0); + EXPECT_GT(opponent->pp[3], 0); + } +} + +SINGLE_BATTLE_TEST("Grudge depletes all PP from a Max Move's base move (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_SCRATCH, MOVE_POUND, MOVE_SURF); Items(ITEM_PECHA_BERRY, ITEM_LAGGING_TAIL); } + } WHEN { + TURN { MOVE(player, MOVE_GRUDGE); MOVE(opponent, MOVE_SCRATCH, gimmick: GIMMICK_DYNAMAX); SEND_OUT(player, 1); } + } SCENE { + MESSAGE("Wobbuffet used Grudge!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRUDGE, player); + MESSAGE("The opposing Wobbuffet used Max Strike!"); + MESSAGE("Wobbuffet fainted!"); + MESSAGE("The opposing Wobbuffet's Scratch lost all its PP due to the grudge!"); + } THEN { + EXPECT_GT(opponent->pp[0], 0); + EXPECT_EQ(opponent->pp[1], 0); + EXPECT_GT(opponent->pp[2], 0); + EXPECT_GT(opponent->pp[3], 0); + } +} + +SINGLE_BATTLE_TEST("Grudge's effect disappears if the user takes a new turn - Flinching (Multi)") +{ + PASSES_RANDOMLY(10, 100, RNG_HOLD_EFFECT_FLINCH); + GIVEN { + ASSUME(GetMoveEffect(MOVE_FALSE_SWIPE) == EFFECT_FALSE_SWIPE); + PLAYER (SPECIES_WOBBUFFET) { HP(1); } + PLAYER (SPECIES_WOBBUFFET); + OPPONENT (SPECIES_WOBBUFFET); + OPPONENT (SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_SCRATCH, MOVE_FALSE_SWIPE, MOVE_SURF); Items(ITEM_PECHA_BERRY, ITEM_KINGS_ROCK); } + } + WHEN { + TURN { SWITCH(opponent, 1); MOVE(player, MOVE_GRUDGE); } + TURN { MOVE(opponent, MOVE_FALSE_SWIPE); MOVE(player, MOVE_CELEBRATE); } + TURN { MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_CELEBRATE); SEND_OUT(player, 1); } + } + SCENE { + SEND_IN_MESSAGE("Wobbuffet"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRUDGE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FALSE_SWIPE, opponent); + MESSAGE("Wobbuffet flinched and couldn't move!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + MESSAGE("Wobbuffet fainted!"); + NOT MESSAGE("The opposing Wobbuffet's Scratch lost all its PP due to the grudge!"); + } + THEN { + EXPECT_GT(opponent->pp[0], 0); + EXPECT_GT(opponent->pp[1], 0); + EXPECT_GT(opponent->pp[2], 0); + EXPECT_GT(opponent->pp[3], 0); + } +} +#endif diff --git a/test/battle/move_effect/heal_bell.c b/test/battle/move_effect/heal_bell.c index 66a754df5c7e..5a9e45521f2a 100644 --- a/test/battle/move_effect/heal_bell.c +++ b/test/battle/move_effect/heal_bell.c @@ -216,3 +216,138 @@ SINGLE_BATTLE_TEST("Aromatherapy cures inactive Soundproof Pokemon regardless of NOT MESSAGE("Exploud was hurt by its poisoning!"); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Heal Bell does not cure Soundproof partners (Gen 4, Gen 6+) (Traits)") +{ + enum Ability ability; + u32 config; + + PARAMETRIZE { ability = ABILITY_SCRAPPY; config = GEN_4; } + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; config = GEN_4; } + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; config = GEN_5; } + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; config = GEN_6; } + + GIVEN { + ASSUME(IsSoundMove(MOVE_HEAL_BELL)); + WITH_CONFIG(CONFIG_HEAL_BELL_SOUNDPROOF, config); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_EXPLOUD) { Ability(ABILITY_SCRAPPY); Innates(ability); Status1(STATUS1_POISON); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_HEAL_BELL, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEAL_BELL, playerLeft); + if (ability == ABILITY_SOUNDPROOF && config != GEN_5) { + MESSAGE("Exploud was hurt by its poisoning!"); + } else { + NOT MESSAGE("Exploud was hurt by its poisoning!"); + } + } +} + +SINGLE_BATTLE_TEST("Heal Bell cures inactive Soundproof Pokemon (Gen5+) (Traits)") +{ + u32 config; + enum Ability ability; + + PARAMETRIZE { config = GEN_4, ability = ABILITY_SCRAPPY; } + PARAMETRIZE { config = GEN_4, ability = ABILITY_SOUNDPROOF; } + PARAMETRIZE { config = GEN_5, ability = ABILITY_SOUNDPROOF; } + + GIVEN { + ASSUME(IsSoundMove(MOVE_HEAL_BELL)); + WITH_CONFIG(CONFIG_HEAL_BELL_SOUNDPROOF, config); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_EXPLOUD) { Ability(ABILITY_SCRAPPY); Innates(ability); Status1(STATUS1_POISON); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_HEAL_BELL, target: player); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEAL_BELL, player); + SEND_IN_MESSAGE("Exploud"); + if (ability == ABILITY_SCRAPPY || config >= GEN_5) { + NOT MESSAGE("Exploud was hurt by its poisoning!"); + } else { + MESSAGE("Exploud was hurt by its poisoning!"); + } + } +} + + +SINGLE_BATTLE_TEST("Heal Bell cures a Soundproof user (Gen5, Gen8+) (Traits)") +{ + u32 config; + PARAMETRIZE { config = GEN_4; } + PARAMETRIZE { config = GEN_5; } + PARAMETRIZE { config = GEN_6; } + PARAMETRIZE { config = GEN_8; } + GIVEN { + ASSUME(IsSoundMove(MOVE_HEAL_BELL)); + WITH_CONFIG(CONFIG_HEAL_BELL_SOUNDPROOF, config); + PLAYER(SPECIES_EXPLOUD) { Ability(ABILITY_SCRAPPY); Innates(ABILITY_SOUNDPROOF); Status1(STATUS1_POISON); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_HEAL_BELL, target: player); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEAL_BELL, player); + if (config == GEN_5 || config >= GEN_8) { + NOT MESSAGE("Exploud was hurt by its poisoning!"); + } else { + MESSAGE("Exploud was hurt by its poisoning!"); + } + } +} + +DOUBLE_BATTLE_TEST("Aromatherapy cure Soundproof battlers regardless of config (Traits)") +{ + u32 ability, config; + + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; config = GEN_4; } + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; config = GEN_5; } + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; config = GEN_6; } + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; config = GEN_8; } + + GIVEN { + ASSUME(!IsSoundMove(MOVE_AROMATHERAPY)); + WITH_CONFIG(CONFIG_HEAL_BELL_SOUNDPROOF, config); + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Innates(ability); Status1(STATUS1_POISON); }; + PLAYER(SPECIES_EXPLOUD) { Ability(ABILITY_SCRAPPY); Innates(ability); Status1(STATUS1_POISON); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_AROMATHERAPY, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AROMATHERAPY, playerLeft); + NONE_OF { + MESSAGE("Exploud was hurt by its poisoning!"); + MESSAGE("Wobbuffet was hurt by its poisoning!"); + } + } +} + +SINGLE_BATTLE_TEST("Aromatherapy cures inactive Soundproof Pokemon regardless of config (Traits)") +{ + u32 config, ability; + + PARAMETRIZE { config = GEN_4, ability = ABILITY_SOUNDPROOF; } + PARAMETRIZE { config = GEN_5, ability = ABILITY_SOUNDPROOF; } + + GIVEN { + ASSUME(!IsSoundMove(MOVE_AROMATHERAPY)); + WITH_CONFIG(CONFIG_HEAL_BELL_SOUNDPROOF, config); + PLAYER(SPECIES_WOBBUFFET) { } + PLAYER(SPECIES_EXPLOUD) { Ability(ABILITY_SCRAPPY); Innates(ability); Status1(STATUS1_POISON); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_AROMATHERAPY, target: player); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AROMATHERAPY, player); + SEND_IN_MESSAGE("Exploud"); + NOT MESSAGE("Exploud was hurt by its poisoning!"); + } +} +#endif diff --git a/test/battle/move_effect/heal_pulse.c b/test/battle/move_effect/heal_pulse.c index c383b7268225..638cb507d405 100644 --- a/test/battle/move_effect/heal_pulse.c +++ b/test/battle/move_effect/heal_pulse.c @@ -100,3 +100,20 @@ SINGLE_BATTLE_TEST("Floral Healing heals the target by 2/3rd of it's maxHP if Gr HP_BAR(player, damage: -maxHP * 2 / 3); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Heal Pulse ignores accurace checks (Multi)") +{ + GIVEN { + PASSES_RANDOMLY(100, 100, RNG_ACCURACY); + PLAYER(SPECIES_WOBBUFFET) { MaxHP(100); HP(1); Items(ITEM_PECHA_BERRY, ITEM_BRIGHT_POWDER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_HEAL_PULSE); } + } SCENE { + s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEAL_PULSE, opponent); + HP_BAR(player, damage: -maxHP / 2); + } +} +#endif diff --git a/test/battle/move_effect/hidden_power.c b/test/battle/move_effect/hidden_power.c index 9ac88e687f59..71985fbc0a36 100644 --- a/test/battle/move_effect/hidden_power.c +++ b/test/battle/move_effect/hidden_power.c @@ -135,3 +135,111 @@ SINGLE_BATTLE_TEST("Hidden Power's type is determined by IVs") } TO_DO_BATTLE_TEST("Hidden Power's power is determined by IVs before Gen6"); + +#if MAX_MON_ITEMS > 1 +// IV combinations sourced from https://www.smogon.com/forums/threads/hidden-power-iv-combinations.78083/ +SINGLE_BATTLE_TEST("Hidden Power's type is determined by IVs (Multi)") +{ + enum Type type, foeType, j; + u32 foeSpecies, foeItem; + u32 hp, atk, def, spAtk, spDef, speed; + bool32 hidden; + + PARAMETRIZE { type = TYPE_NONE; hidden = FALSE; } + PARAMETRIZE { type = TYPE_NORMAL; hidden = FALSE; } + PARAMETRIZE { type = TYPE_FIGHTING; hidden = TRUE; foeType = TYPE_DARK; foeSpecies = SPECIES_UMBREON; foeItem = ITEM_CHOPLE_BERRY; hp = 30; atk = 2; def = 31; spAtk = 30; spDef = 30; speed = 30; } + PARAMETRIZE { type = TYPE_FIGHTING; hidden = TRUE; foeType = TYPE_DARK; foeSpecies = SPECIES_UMBREON; foeItem = ITEM_CHOPLE_BERRY; hp = 31; atk = 15; def = 30; spAtk = 30; spDef = 30; speed = 30; } + PARAMETRIZE { type = TYPE_FIGHTING; hidden = TRUE; foeType = TYPE_DARK; foeSpecies = SPECIES_UMBREON; foeItem = ITEM_CHOPLE_BERRY; hp = 30; atk = 22; def = 31; spAtk = 30; spDef = 30; speed = 30; } + PARAMETRIZE { type = TYPE_FIGHTING; hidden = TRUE; foeType = TYPE_DARK; foeSpecies = SPECIES_UMBREON; foeItem = ITEM_CHOPLE_BERRY; hp = 31; atk = 31; def = 30; spAtk = 30; spDef = 30; speed = 30; } + PARAMETRIZE { type = TYPE_FLYING; hidden = TRUE; foeType = TYPE_GRASS; foeSpecies = SPECIES_TANGELA; foeItem = ITEM_COBA_BERRY; hp = 31; atk = 2; def = 31; spAtk = 30; spDef = 30; speed = 30; } + PARAMETRIZE { type = TYPE_FLYING; hidden = TRUE; foeType = TYPE_GRASS; foeSpecies = SPECIES_TANGELA; foeItem = ITEM_COBA_BERRY; hp = 31; atk = 15; def = 31; spAtk = 30; spDef = 30; speed = 30; } + PARAMETRIZE { type = TYPE_FLYING; hidden = TRUE; foeType = TYPE_GRASS; foeSpecies = SPECIES_TANGELA; foeItem = ITEM_COBA_BERRY; hp = 31; atk = 22; def = 31; spAtk = 30; spDef = 30; speed = 30; } + PARAMETRIZE { type = TYPE_FLYING; hidden = TRUE; foeType = TYPE_GRASS; foeSpecies = SPECIES_TANGELA; foeItem = ITEM_COBA_BERRY; hp = 31; atk = 31; def = 31; spAtk = 30; spDef = 30; speed = 30; } + PARAMETRIZE { type = TYPE_POISON; hidden = TRUE; foeType = TYPE_GRASS; foeSpecies = SPECIES_TANGELA; foeItem = ITEM_KEBIA_BERRY; hp = 30; atk = 2; def = 31; spAtk = 30; spDef = 30; speed = 31; } + PARAMETRIZE { type = TYPE_POISON; hidden = TRUE; foeType = TYPE_GRASS; foeSpecies = SPECIES_TANGELA; foeItem = ITEM_KEBIA_BERRY; hp = 31; atk = 15; def = 30; spAtk = 30; spDef = 30; speed = 31; } + PARAMETRIZE { type = TYPE_POISON; hidden = TRUE; foeType = TYPE_GRASS; foeSpecies = SPECIES_TANGELA; foeItem = ITEM_KEBIA_BERRY; hp = 30; atk = 22; def = 31; spAtk = 30; spDef = 30; speed = 31; } + PARAMETRIZE { type = TYPE_POISON; hidden = TRUE; foeType = TYPE_GRASS; foeSpecies = SPECIES_TANGELA; foeItem = ITEM_KEBIA_BERRY; hp = 31; atk = 31; def = 30; spAtk = 30; spDef = 30; speed = 31; } + PARAMETRIZE { type = TYPE_GROUND; hidden = TRUE; foeType = TYPE_STEEL; foeSpecies = SPECIES_KLINK; foeItem = ITEM_SHUCA_BERRY; hp = 31; atk = 2; def = 31; spAtk = 30; spDef = 30; speed = 31; } + PARAMETRIZE { type = TYPE_GROUND; hidden = TRUE; foeType = TYPE_STEEL; foeSpecies = SPECIES_KLINK; foeItem = ITEM_SHUCA_BERRY; hp = 31; atk = 15; def = 31; spAtk = 30; spDef = 30; speed = 31; } + PARAMETRIZE { type = TYPE_GROUND; hidden = TRUE; foeType = TYPE_STEEL; foeSpecies = SPECIES_KLINK; foeItem = ITEM_SHUCA_BERRY; hp = 31; atk = 22; def = 31; spAtk = 30; spDef = 30; speed = 31; } + PARAMETRIZE { type = TYPE_GROUND; hidden = TRUE; foeType = TYPE_STEEL; foeSpecies = SPECIES_KLINK; foeItem = ITEM_SHUCA_BERRY; hp = 31; atk = 31; def = 31; spAtk = 30; spDef = 30; speed = 31; } + PARAMETRIZE { type = TYPE_ROCK; hidden = TRUE; foeType = TYPE_FIRE; foeSpecies = SPECIES_VULPIX; foeItem = ITEM_CHARTI_BERRY; hp = 31; atk = 2; def = 30; spAtk = 31; spDef = 30; speed = 30; } + PARAMETRIZE { type = TYPE_ROCK; hidden = TRUE; foeType = TYPE_FIRE; foeSpecies = SPECIES_VULPIX; foeItem = ITEM_CHARTI_BERRY; hp = 31; atk = 15; def = 30; spAtk = 31; spDef = 30; speed = 30; } + PARAMETRIZE { type = TYPE_ROCK; hidden = TRUE; foeType = TYPE_FIRE; foeSpecies = SPECIES_VULPIX; foeItem = ITEM_CHARTI_BERRY; hp = 31; atk = 22; def = 30; spAtk = 31; spDef = 30; speed = 30; } + PARAMETRIZE { type = TYPE_ROCK; hidden = TRUE; foeType = TYPE_FIRE; foeSpecies = SPECIES_VULPIX; foeItem = ITEM_CHARTI_BERRY; hp = 31; atk = 31; def = 30; spAtk = 31; spDef = 30; speed = 30; } + PARAMETRIZE { type = TYPE_BUG; hidden = TRUE; foeType = TYPE_DARK; foeSpecies = SPECIES_UMBREON; foeItem = ITEM_TANGA_BERRY; hp = 31; atk = 2; def = 31; spAtk = 31; spDef = 30; speed = 30; } + PARAMETRIZE { type = TYPE_BUG; hidden = TRUE; foeType = TYPE_DARK; foeSpecies = SPECIES_UMBREON; foeItem = ITEM_TANGA_BERRY; hp = 31; atk = 15; def = 31; spAtk = 31; spDef = 30; speed = 30; } + PARAMETRIZE { type = TYPE_BUG; hidden = TRUE; foeType = TYPE_DARK; foeSpecies = SPECIES_UMBREON; foeItem = ITEM_TANGA_BERRY; hp = 31; atk = 22; def = 31; spAtk = 31; spDef = 30; speed = 30; } + PARAMETRIZE { type = TYPE_BUG; hidden = TRUE; foeType = TYPE_DARK; foeSpecies = SPECIES_UMBREON; foeItem = ITEM_TANGA_BERRY; hp = 31; atk = 31; def = 31; spAtk = 31; spDef = 30; speed = 30; } + PARAMETRIZE { type = TYPE_GHOST; hidden = TRUE; foeType = TYPE_PSYCHIC; foeSpecies = SPECIES_WOBBUFFET; foeItem = ITEM_KASIB_BERRY; hp = 31; atk = 2; def = 31; spAtk = 31; spDef = 30; speed = 31; } + PARAMETRIZE { type = TYPE_GHOST; hidden = TRUE; foeType = TYPE_PSYCHIC; foeSpecies = SPECIES_WOBBUFFET; foeItem = ITEM_KASIB_BERRY; hp = 31; atk = 15; def = 30; spAtk = 31; spDef = 30; speed = 31; } + PARAMETRIZE { type = TYPE_GHOST; hidden = TRUE; foeType = TYPE_PSYCHIC; foeSpecies = SPECIES_WOBBUFFET; foeItem = ITEM_KASIB_BERRY; hp = 31; atk = 22; def = 31; spAtk = 31; spDef = 30; speed = 31; } + PARAMETRIZE { type = TYPE_GHOST; hidden = TRUE; foeType = TYPE_PSYCHIC; foeSpecies = SPECIES_WOBBUFFET; foeItem = ITEM_KASIB_BERRY; hp = 31; atk = 31; def = 30; spAtk = 31; spDef = 30; speed = 31; } + PARAMETRIZE { type = TYPE_STEEL; hidden = TRUE; foeType = TYPE_ROCK; foeSpecies = SPECIES_NOSEPASS; foeItem = ITEM_BABIRI_BERRY; hp = 31; atk = 2; def = 30; spAtk = 30; spDef = 31; speed = 30; } + PARAMETRIZE { type = TYPE_STEEL; hidden = TRUE; foeType = TYPE_ROCK; foeSpecies = SPECIES_NOSEPASS; foeItem = ITEM_BABIRI_BERRY; hp = 31; atk = 15; def = 31; spAtk = 31; spDef = 30; speed = 31; } + PARAMETRIZE { type = TYPE_STEEL; hidden = TRUE; foeType = TYPE_ROCK; foeSpecies = SPECIES_NOSEPASS; foeItem = ITEM_BABIRI_BERRY; hp = 31; atk = 22; def = 30; spAtk = 30; spDef = 31; speed = 30; } + PARAMETRIZE { type = TYPE_STEEL; hidden = TRUE; foeType = TYPE_ROCK; foeSpecies = SPECIES_NOSEPASS; foeItem = ITEM_BABIRI_BERRY; hp = 31; atk = 31; def = 31; spAtk = 31; spDef = 30; speed = 31; } + PARAMETRIZE { type = TYPE_FIRE; hidden = TRUE; foeType = TYPE_GRASS; foeSpecies = SPECIES_TANGELA; foeItem = ITEM_OCCA_BERRY; hp = 31; atk = 2; def = 31; spAtk = 30; spDef = 31; speed = 30; } + PARAMETRIZE { type = TYPE_FIRE; hidden = TRUE; foeType = TYPE_GRASS; foeSpecies = SPECIES_TANGELA; foeItem = ITEM_OCCA_BERRY; hp = 31; atk = 15; def = 30; spAtk = 30; spDef = 31; speed = 30; } + PARAMETRIZE { type = TYPE_FIRE; hidden = TRUE; foeType = TYPE_GRASS; foeSpecies = SPECIES_TANGELA; foeItem = ITEM_OCCA_BERRY; hp = 31; atk = 22; def = 31; spAtk = 30; spDef = 31; speed = 30; } + PARAMETRIZE { type = TYPE_FIRE; hidden = TRUE; foeType = TYPE_GRASS; foeSpecies = SPECIES_TANGELA; foeItem = ITEM_OCCA_BERRY; hp = 31; atk = 31; def = 30; spAtk = 30; spDef = 31; speed = 30; } + PARAMETRIZE { type = TYPE_WATER; hidden = TRUE; foeType = TYPE_ROCK; foeSpecies = SPECIES_NOSEPASS; foeItem = ITEM_PASSHO_BERRY; hp = 31; atk = 2; def = 30; spAtk = 30; spDef = 31; speed = 31; } + PARAMETRIZE { type = TYPE_WATER; hidden = TRUE; foeType = TYPE_ROCK; foeSpecies = SPECIES_NOSEPASS; foeItem = ITEM_PASSHO_BERRY; hp = 31; atk = 15; def = 31; spAtk = 30; spDef = 31; speed = 30; } + PARAMETRIZE { type = TYPE_WATER; hidden = TRUE; foeType = TYPE_ROCK; foeSpecies = SPECIES_NOSEPASS; foeItem = ITEM_PASSHO_BERRY; hp = 31; atk = 22; def = 30; spAtk = 30; spDef = 31; speed = 31; } + PARAMETRIZE { type = TYPE_WATER; hidden = TRUE; foeType = TYPE_ROCK; foeSpecies = SPECIES_NOSEPASS; foeItem = ITEM_PASSHO_BERRY; hp = 31; atk = 31; def = 31; spAtk = 30; spDef = 31; speed = 30; } + PARAMETRIZE { type = TYPE_GRASS; hidden = TRUE; foeType = TYPE_ROCK; foeSpecies = SPECIES_NOSEPASS; foeItem = ITEM_RINDO_BERRY; hp = 30; atk = 2; def = 31; spAtk = 30; spDef = 31; speed = 31; } + PARAMETRIZE { type = TYPE_GRASS; hidden = TRUE; foeType = TYPE_ROCK; foeSpecies = SPECIES_NOSEPASS; foeItem = ITEM_RINDO_BERRY; hp = 30; atk = 15; def = 31; spAtk = 30; spDef = 31; speed = 31; } + PARAMETRIZE { type = TYPE_GRASS; hidden = TRUE; foeType = TYPE_ROCK; foeSpecies = SPECIES_NOSEPASS; foeItem = ITEM_RINDO_BERRY; hp = 30; atk = 22; def = 31; spAtk = 30; spDef = 31; speed = 31; } + PARAMETRIZE { type = TYPE_GRASS; hidden = TRUE; foeType = TYPE_ROCK; foeSpecies = SPECIES_NOSEPASS; foeItem = ITEM_RINDO_BERRY; hp = 30; atk = 31; def = 31; spAtk = 30; spDef = 31; speed = 31; } + PARAMETRIZE { type = TYPE_ELECTRIC; hidden = TRUE; foeType = TYPE_WATER; foeSpecies = SPECIES_SQUIRTLE; foeItem = ITEM_WACAN_BERRY; hp = 31; atk = 2; def = 30; spAtk = 31; spDef = 31; speed = 30; } + PARAMETRIZE { type = TYPE_ELECTRIC; hidden = TRUE; foeType = TYPE_WATER; foeSpecies = SPECIES_SQUIRTLE; foeItem = ITEM_WACAN_BERRY; hp = 30; atk = 15; def = 30; spAtk = 31; spDef = 31; speed = 30; } + PARAMETRIZE { type = TYPE_ELECTRIC; hidden = TRUE; foeType = TYPE_WATER; foeSpecies = SPECIES_SQUIRTLE; foeItem = ITEM_WACAN_BERRY; hp = 31; atk = 22; def = 30; spAtk = 31; spDef = 31; speed = 30; } + PARAMETRIZE { type = TYPE_ELECTRIC; hidden = TRUE; foeType = TYPE_WATER; foeSpecies = SPECIES_SQUIRTLE; foeItem = ITEM_WACAN_BERRY; hp = 30; atk = 31; def = 30; spAtk = 31; spDef = 31; speed = 30; } + PARAMETRIZE { type = TYPE_PSYCHIC; hidden = TRUE; foeType = TYPE_POISON; foeSpecies = SPECIES_KOFFING; foeItem = ITEM_PAYAPA_BERRY; hp = 31; atk = 2; def = 31; spAtk = 31; spDef = 31; speed = 30; } + PARAMETRIZE { type = TYPE_PSYCHIC; hidden = TRUE; foeType = TYPE_POISON; foeSpecies = SPECIES_KOFFING; foeItem = ITEM_PAYAPA_BERRY; hp = 30; atk = 15; def = 31; spAtk = 31; spDef = 31; speed = 30; } + PARAMETRIZE { type = TYPE_PSYCHIC; hidden = TRUE; foeType = TYPE_POISON; foeSpecies = SPECIES_KOFFING; foeItem = ITEM_PAYAPA_BERRY; hp = 31; atk = 22; def = 31; spAtk = 31; spDef = 31; speed = 30; } + PARAMETRIZE { type = TYPE_PSYCHIC; hidden = TRUE; foeType = TYPE_POISON; foeSpecies = SPECIES_KOFFING; foeItem = ITEM_PAYAPA_BERRY; hp = 30; atk = 31; def = 31; spAtk = 31; spDef = 31; speed = 30; } + PARAMETRIZE { type = TYPE_ICE; hidden = TRUE; foeType = TYPE_GRASS; foeSpecies = SPECIES_TANGELA; foeItem = ITEM_YACHE_BERRY; hp = 30; atk = 2; def = 30; spAtk = 31; spDef = 31; speed = 31; } + PARAMETRIZE { type = TYPE_ICE; hidden = TRUE; foeType = TYPE_GRASS; foeSpecies = SPECIES_TANGELA; foeItem = ITEM_YACHE_BERRY; hp = 30; atk = 15; def = 30; spAtk = 31; spDef = 31; speed = 31; } + PARAMETRIZE { type = TYPE_ICE; hidden = TRUE; foeType = TYPE_GRASS; foeSpecies = SPECIES_TANGELA; foeItem = ITEM_YACHE_BERRY; hp = 30; atk = 22; def = 30; spAtk = 31; spDef = 31; speed = 31; } + PARAMETRIZE { type = TYPE_ICE; hidden = TRUE; foeType = TYPE_GRASS; foeSpecies = SPECIES_TANGELA; foeItem = ITEM_YACHE_BERRY; hp = 30; atk = 31; def = 30; spAtk = 31; spDef = 31; speed = 31; } + PARAMETRIZE { type = TYPE_MYSTERY; hidden = FALSE; } + PARAMETRIZE { type = TYPE_DRAGON; hidden = TRUE; foeType = TYPE_DRAGON; foeSpecies = SPECIES_DRATINI; foeItem = ITEM_HABAN_BERRY; hp = 30; atk = 2; def = 31; spAtk = 31; spDef = 31; speed = 31; } + PARAMETRIZE { type = TYPE_DRAGON; hidden = TRUE; foeType = TYPE_DRAGON; foeSpecies = SPECIES_DRATINI; foeItem = ITEM_HABAN_BERRY; hp = 30; atk = 15; def = 31; spAtk = 31; spDef = 31; speed = 31; } + PARAMETRIZE { type = TYPE_DRAGON; hidden = TRUE; foeType = TYPE_DRAGON; foeSpecies = SPECIES_DRATINI; foeItem = ITEM_HABAN_BERRY; hp = 30; atk = 22; def = 31; spAtk = 31; spDef = 31; speed = 31; } + PARAMETRIZE { type = TYPE_DRAGON; hidden = TRUE; foeType = TYPE_DRAGON; foeSpecies = SPECIES_DRATINI; foeItem = ITEM_HABAN_BERRY; hp = 30; atk = 31; def = 31; spAtk = 31; spDef = 31; speed = 31; } + PARAMETRIZE { type = TYPE_DARK; hidden = TRUE; foeType = TYPE_PSYCHIC; foeSpecies = SPECIES_WOBBUFFET; foeItem = ITEM_COLBUR_BERRY; hp = 31; atk = 3; def = 31; spAtk = 31; spDef = 31; speed = 31; } + PARAMETRIZE { type = TYPE_DARK; hidden = TRUE; foeType = TYPE_PSYCHIC; foeSpecies = SPECIES_WOBBUFFET; foeItem = ITEM_COLBUR_BERRY; hp = 31; atk = 15; def = 31; spAtk = 31; spDef = 31; speed = 31; } + PARAMETRIZE { type = TYPE_DARK; hidden = TRUE; foeType = TYPE_PSYCHIC; foeSpecies = SPECIES_WOBBUFFET; foeItem = ITEM_COLBUR_BERRY; hp = 31; atk = 23; def = 31; spAtk = 31; spDef = 31; speed = 31; } + PARAMETRIZE { type = TYPE_DARK; hidden = TRUE; foeType = TYPE_PSYCHIC; foeSpecies = SPECIES_WOBBUFFET; foeItem = ITEM_COLBUR_BERRY; hp = 31; atk = 31; def = 31; spAtk = 31; spDef = 31; speed = 31; } + + // Any type after Dark shouldn't be part of Hidden Power officially. + for (j = TYPE_DARK + 1; j < NUMBER_OF_MON_TYPES; j++) { + PARAMETRIZE { type = j; hidden = FALSE; } + } + + GIVEN { + if (hidden) { + ASSUME(gTypeEffectivenessTable[type][foeType] == UQ_4_12(2.0)); // Foe's Type resists + ASSUME(GetSpeciesType(foeSpecies, 0) == GetSpeciesType(foeSpecies, 1)); // Foe's pure type + ASSUME(GetSpeciesType(foeSpecies, 0) == foeType); // Foe is the super-effective type + ASSUME(GetItemHoldEffect(foeItem) == HOLD_EFFECT_RESIST_BERRY); // Item is resist berry + ASSUME(GetItemHoldEffectParam(foeItem) == type); // Resist berry of type + PLAYER(SPECIES_DUNSPARCE) { HPIV(hp); AttackIV(atk); DefenseIV(def); SpAttackIV(spAtk); SpDefenseIV(spDef); SpeedIV(speed); } + } else { + PLAYER(SPECIES_DUNSPARCE); + } + OPPONENT(foeSpecies) { Items(ITEM_PECHA_BERRY, foeItem); } + } WHEN { + TURN { MOVE(player, MOVE_HIDDEN_POWER); } + } SCENE { + // Only test valid Hidden Power types + if (hidden) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); // Check that the item is triggered + ANIMATION(ANIM_TYPE_MOVE, MOVE_HIDDEN_POWER, player); + HP_BAR(opponent); + MESSAGE("It's super effective!"); + } + } +} +#endif diff --git a/test/battle/move_effect/hit_escape.c b/test/battle/move_effect/hit_escape.c index b4e3e2f4e5c1..548b932870d9 100644 --- a/test/battle/move_effect/hit_escape.c +++ b/test/battle/move_effect/hit_escape.c @@ -207,3 +207,234 @@ SINGLE_BATTLE_TEST("Hit Escape: U-turn will fail to switch if the user faints") HP_BAR(opponent); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Hit Escape: U-turn does not switch the user out if Wimp Out activates (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WIMPOD) { MaxHP(200); HP(101); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_WIMP_OUT); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_WIMP_OUT); + MESSAGE("2 sent out Wobbuffet!"); + } +} + +SINGLE_BATTLE_TEST("Hit Escape: U-turn switches the user out if Wimp Out fails to activate (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WIMPOD) { MaxHP(200); HP(101); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_WIMP_OUT); } + } WHEN { + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + HP_BAR(opponent); + NOT ABILITY_POPUP(opponent); + SEND_IN_MESSAGE("Wynaut"); + } +} + +SINGLE_BATTLE_TEST("Hit Escape: U-turn switches the user out after Ice Face activates (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_U_TURN) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_BEEDRILL); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_EISCUE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ICE_FACE); } + } WHEN { + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_ICE_FACE); + MESSAGE("The opposing Eiscue transformed!"); + SEND_IN_MESSAGE("Wynaut"); + } +} + +SINGLE_BATTLE_TEST("Hit Escape: Held items are consumed immediately after a mon switched in by U-turn: player side (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); }; + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); Item(ITEM_ELECTRIC_SEED); } + OPPONENT(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ELECTRIC_SURGE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("2 sent out Wynaut!"); + NOT ABILITY_POPUP(player, ABILITY_INTIMIDATE); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Hit Escape: Held items are consumed immediately after a mon switched in by U-turn: opposing side (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); }; + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WYNAUT) { Item(ITEM_ELECTRIC_SEED); } + } WHEN { + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ELECTRIC_SURGE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + HP_BAR(opponent); + MESSAGE("2 sent out Wynaut!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + NOT ABILITY_POPUP(player, ABILITY_INTIMIDATE); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Hit Escape: Electric Seed boost is received by the right Pokémon after U-turn and Intimidate (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TAPU_KOKO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ELECTRIC_SURGE); }; + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_INTIMIDATE); Item(ITEM_ELECTRIC_SEED); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ELECTRIC_SURGE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + HP_BAR(opponent); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Hit Escape: U-turn triggers before Eject Pack (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_PACK); }; + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_GOODRA_HISUI) { Ability(ABILITY_SAP_SIPPER); Innates(ABILITY_GOOEY); }; + } WHEN { + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + HP_BAR(opponent); + SEND_IN_MESSAGE("Wynaut"); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Hit Escape: Held items are consumed immediately after a mon switched in by U-turn: player side (Multi)") +{ + GIVEN { + PLAYER(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); }; + PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); Items(ITEM_PECHA_BERRY, ITEM_ELECTRIC_SEED); } + OPPONENT(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ELECTRIC_SURGE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("2 sent out Wynaut!"); + NOT ABILITY_POPUP(player, ABILITY_INTIMIDATE); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Hit Escape: Held items are consumed immediately after a mon switched in by U-turn: opposing side (Multi)") +{ + GIVEN { + PLAYER(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); }; + PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WYNAUT) { Items(ITEM_PECHA_BERRY, ITEM_ELECTRIC_SEED); } + } WHEN { + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ELECTRIC_SURGE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + HP_BAR(opponent); + MESSAGE("2 sent out Wynaut!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + NOT ABILITY_POPUP(player, ABILITY_INTIMIDATE); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Hit Escape: Electric Seed boost is received by the right Pokémon after U-turn and Intimidate (Multi)") +{ + GIVEN { + PLAYER(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); }; + PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); Items(ITEM_PECHA_BERRY, ITEM_ELECTRIC_SEED); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ELECTRIC_SURGE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + HP_BAR(opponent); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Hit Escape: U-turn triggers before Eject Pack (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_PACK); }; + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_GOODRA_HISUI) { Ability(ABILITY_GOOEY); }; + } WHEN { + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + HP_BAR(opponent); + SEND_IN_MESSAGE("Wynaut"); + } +} + +SINGLE_BATTLE_TEST("Hit Escape: U-turn will fail to switch if the user faints (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_ROCKY_HELMET); } + } WHEN { + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + HP_BAR(opponent); + } +} +#endif diff --git a/test/battle/move_effect/hit_switch_target.c b/test/battle/move_effect/hit_switch_target.c index 7287e8010a5e..8b0e422be6d6 100644 --- a/test/battle/move_effect/hit_switch_target.c +++ b/test/battle/move_effect/hit_switch_target.c @@ -164,3 +164,122 @@ SINGLE_BATTLE_TEST("Dragon Tail switches target out and incoming mon has Levitat HP_BAR(opponent); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Dragon Tail switches the target after Rocky Helmet and Iron Barbs (Traits)") +{ + PASSES_RANDOMLY(1, 2, RNG_FORCE_RANDOM_SWITCH); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TOGEDEMARU) { Ability(ABILITY_STURDY); Innates(ABILITY_IRON_BARBS); Item(ITEM_ROCKY_HELMET); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CHARMANDER); + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the opposing Togedemaru's Iron Barbs!"); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the opposing Togedemaru's Rocky Helmet!"); + MESSAGE("The opposing Charmander was dragged out!"); + } +} + +SINGLE_BATTLE_TEST("Dragon Tail effect fails against target with Guard Dog (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_OKIDOGI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GUARD_DOG); } + OPPONENT(SPECIES_CHARMANDER); + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + NOT MESSAGE("The opposing Charmander was dragged out!"); + } +} + +SINGLE_BATTLE_TEST("Dragon Tail effect fails against target with Suction Cups (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_OCTILLERY) { Ability(ABILITY_SNIPER); Innates(ABILITY_SUCTION_CUPS); } + OPPONENT(SPECIES_CHARMANDER); + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + ABILITY_POPUP(opponent, ABILITY_SUCTION_CUPS); + MESSAGE("The opposing Octillery anchors itself with Suction Cups!"); + NOT MESSAGE("The opposing Charmander was dragged out!"); + } +} + +SINGLE_BATTLE_TEST("Dragon Tail switches target out and incoming mon has Immunity negated by Mold Breaker (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOXIC_SPIKES) == EFFECT_TOXIC_SPIKES); + PLAYER(SPECIES_PANCHAM) { Ability(ABILITY_IRON_FIST); Innates(ABILITY_MOLD_BREAKER); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SNORLAX) { Ability(ABILITY_IRON_FIST); Innates(ABILITY_IMMUNITY); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC_SPIKES); } + TURN { MOVE(player, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + HP_BAR(opponent); + MESSAGE("The opposing Snorlax was dragged out!"); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Dragon Tail switches target out and incoming mon has Levitate negated by Mold Breaker (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOXIC_SPIKES) == EFFECT_TOXIC_SPIKES); + ASSUME(GetMoveEffect(MOVE_SPIKES) == EFFECT_SPIKES); + ASSUME(GetSpeciesType(SPECIES_WEEZING, 0) == TYPE_POISON || GetSpeciesType(SPECIES_WEEZING, 1) == TYPE_POISON); + PLAYER(SPECIES_PANCHAM) { Ability(ABILITY_IRON_FIST); Innates(ABILITY_MOLD_BREAKER); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WEEZING) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_LEVITATE); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC_SPIKES); } + TURN { MOVE(player, MOVE_SPIKES); } + TURN { MOVE(player, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKES, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + HP_BAR(opponent); + MESSAGE("The opposing Weezing was dragged out!"); + MESSAGE("The poison spikes disappeared from the ground around the opposing team!"); + NOT STATUS_ICON(opponent, poison: TRUE); + HP_BAR(opponent); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Dragon Tail switches the target after Rocky Helmet and Iron Barbs (Multi)") +{ + PASSES_RANDOMLY(1, 2, RNG_FORCE_RANDOM_SWITCH); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TOGEDEMARU) { Ability(ABILITY_IRON_BARBS); Items(ITEM_PECHA_BERRY, ITEM_ROCKY_HELMET); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CHARMANDER); + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_TAIL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the opposing Togedemaru's Iron Barbs!"); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the opposing Togedemaru's Rocky Helmet!"); + MESSAGE("The opposing Charmander was dragged out!"); + } +} +#endif diff --git a/test/battle/move_effect/hydro_steam.c b/test/battle/move_effect/hydro_steam.c index ff1407a0e72d..6ed9794f95bc 100644 --- a/test/battle/move_effect/hydro_steam.c +++ b/test/battle/move_effect/hydro_steam.c @@ -49,3 +49,27 @@ SINGLE_BATTLE_TEST("Hydro Steam is affected by Utility Umbrella", s16 damage) EXPECT_MUL_EQ(results[2].damage, Q_4_12(0.5), results[0].damage); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Hydro Steam is affected by Utility Umbrella (Multi)", s16 damage) +{ + u32 itemPlayer; + u32 itemOpponent; + PARAMETRIZE { itemPlayer = ITEM_UTILITY_UMBRELLA; itemOpponent = ITEM_NONE; } + PARAMETRIZE { itemPlayer = ITEM_NONE; itemOpponent = ITEM_UTILITY_UMBRELLA; } + PARAMETRIZE { itemPlayer = ITEM_UTILITY_UMBRELLA; itemOpponent = ITEM_UTILITY_UMBRELLA; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, itemPlayer); }; + OPPONENT(SPECIES_WOBBUFFET) {Items(ITEM_PECHA_BERRY, itemOpponent); }; + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); } + TURN { MOVE(player, MOVE_HYDRO_STEAM); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYDRO_STEAM, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[2].damage, Q_4_12(1.5), results[1].damage); + EXPECT_MUL_EQ(results[2].damage, Q_4_12(0.5), results[0].damage); + } +} +#endif diff --git a/test/battle/move_effect/ice_spinner.c b/test/battle/move_effect/ice_spinner.c index c04f16b880c0..8650643af999 100644 --- a/test/battle/move_effect/ice_spinner.c +++ b/test/battle/move_effect/ice_spinner.c @@ -123,3 +123,35 @@ AI_SINGLE_BATTLE_TEST("Ice Spinner can be chosen by AI regardless if there is a } } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Ice Spinner fails to remove terrain if user faints during attack execution (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LIFE_ORB); HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); MOVE(opponent, MOVE_ICE_SPINNER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ICE_SPINNER, opponent); + NOT MESSAGE("The electricity disappeared from the battlefield."); + } +} + +SINGLE_BATTLE_TEST("Ice Spinner will not be remove Terrain if user is switched out due to Red Card (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); MOVE(opponent, MOVE_ICE_SPINNER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ICE_SPINNER, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + NOT MESSAGE("The electricity disappeared from the battlefield."); + } +} +#endif diff --git a/test/battle/move_effect/instruct.c b/test/battle/move_effect/instruct.c index 04f555815581..3c0a5190bb1a 100644 --- a/test/battle/move_effect/instruct.c +++ b/test/battle/move_effect/instruct.c @@ -347,3 +347,72 @@ DOUBLE_BATTLE_TEST("Instruct message references the correct battlers") ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerRight); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Instruct fails if target doesn't know the last move it used (Traits)") +{ + GIVEN { + ASSUME(IsDanceMove(MOVE_DRAGON_DANCE)); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ORICORIO) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); Moves(MOVE_SCRATCH, MOVE_POUND, MOVE_SCRATCH, MOVE_CELEBRATE); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_DRAGON_DANCE); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, opponentLeft); + ABILITY_POPUP(playerRight, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, playerRight); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, playerRight); + } + } +} + +DOUBLE_BATTLE_TEST("Instructed move will be redirected and absorbed by Lightning Rod if it turns into an Electric Type move (Traits)") +{ + struct BattlePokemon *moveTarget = NULL; + PARAMETRIZE { moveTarget = opponentLeft; } + PARAMETRIZE { moveTarget = opponentRight; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_PIKACHU) { Ability(ABILITY_STATIC); Innates(ABILITY_LIGHTNING_ROD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(playerLeft, MOVE_SCRATCH, target: moveTarget); + MOVE(opponentLeft, MOVE_PLASMA_FISTS, target: playerLeft); + MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft); + MOVE(opponentRight, MOVE_CELEBRATE); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLASMA_FISTS, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerRight); + ABILITY_POPUP(opponentLeft, ABILITY_LIGHTNING_ROD); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +DOUBLE_BATTLE_TEST("Instruct-called status moves don't fail if holding Assault Vest (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_ASSAULT_VEST].holdEffect == HOLD_EFFECT_ASSAULT_VEST); + ASSUME(GetMoveEffect(MOVE_TRICK) == EFFECT_TRICK); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_SCRATCH, MOVE_POUND, MOVE_SCRATCH, MOVE_TRICK); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_ASSAULT_VEST); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, MOVE_TRICK, target: opponentLeft); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, playerRight); + } +} +#endif diff --git a/test/battle/move_effect/ion_deluge.c b/test/battle/move_effect/ion_deluge.c index 4f74c6db72b6..c3e04557660c 100644 --- a/test/battle/move_effect/ion_deluge.c +++ b/test/battle/move_effect/ion_deluge.c @@ -63,3 +63,47 @@ SINGLE_BATTLE_TEST("Ion Deluge makes Normal type moves Electric type") MESSAGE("It's super effective!"); // Because Scratch is now electric type. } } + +// For some reason SINGLE_BATTLE_TEST didn't catch these two issues. +#if MAX_MON_TRAITS > 1 +WILD_BATTLE_TEST("Ion Deluge works the same way as always when used by a mon with Volt Absorb (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_LANTURN) { Ability(ABILITY_ILLUMINATE); Innates(ABILITY_VOLT_ABSORB); HP(1); } + } WHEN { + TURN { MOVE(opponent, MOVE_ION_DELUGE); } + } SCENE { + MESSAGE("The wild Lanturn used Ion Deluge!"); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_VOLT_ABSORB); + HP_BAR(opponent); + MESSAGE("Wild Lanturn restored HP using its Volt Absorb!"); + } + MESSAGE("A deluge of ions showers the battlefield!"); + } +} + +WILD_BATTLE_TEST("Ion Deluge works the same way as always when used by a mon with Lightning Rod / Motor Drive (Traits)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_LIGHTNING_ROD; } + PARAMETRIZE { ability = ABILITY_MOTOR_DRIVE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZEBSTRIKA) { Ability(ABILITY_SAP_SIPPER); Innates(ability); } + } WHEN { + TURN { MOVE(opponent, MOVE_ION_DELUGE); } + } SCENE { + MESSAGE("The wild Zebstrika used Ion Deluge!"); + NONE_OF { + ABILITY_POPUP(opponent, ability); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Wild Zebstrika's Sp. Atk rose!"); + MESSAGE("Wild Zebstrika's Speed rose!"); + } + MESSAGE("A deluge of ions showers the battlefield!"); + } +} +#endif diff --git a/test/battle/move_effect/ivy_cudgel.c b/test/battle/move_effect/ivy_cudgel.c index fff580a1f8ba..0a74a7132c12 100644 --- a/test/battle/move_effect/ivy_cudgel.c +++ b/test/battle/move_effect/ivy_cudgel.c @@ -47,3 +47,49 @@ SINGLE_BATTLE_TEST("Ivy Cudgel does not change the move type if used by Pokémon MESSAGE("It's super effective!"); // Should be super effective everytime if type isnt being changed } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Ivy Cudgel changes the move type depending on the form of Ogerpon (Multi)") +{ + u16 species; + u16 ogerpon; + u16 item; + + PARAMETRIZE { species = SPECIES_BLASTOISE; ogerpon = SPECIES_OGERPON_TEAL; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_CHARIZARD; ogerpon = SPECIES_OGERPON_CORNERSTONE; item = ITEM_CORNERSTONE_MASK; } + PARAMETRIZE { species = SPECIES_CHARIZARD; ogerpon = SPECIES_OGERPON_WELLSPRING; item = ITEM_WELLSPRING_MASK; } + PARAMETRIZE { species = SPECIES_VENUSAUR; ogerpon = SPECIES_OGERPON_HEARTHFLAME; item = ITEM_HEARTHFLAME_MASK; } + + GIVEN { + PLAYER(ogerpon) { Items(ITEM_PECHA_BERRY, item); } + OPPONENT(species); + } WHEN { + TURN { MOVE(player, MOVE_IVY_CUDGEL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_IVY_CUDGEL, player); + HP_BAR(opponent); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Ivy Cudgel does not change the move type if used by Pokémon other than Ogerpon (Multi)") +{ + u16 item; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_CORNERSTONE_MASK; } + PARAMETRIZE { item = ITEM_WELLSPRING_MASK; } + PARAMETRIZE { item = ITEM_HEARTHFLAME_MASK; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_BLASTOISE); + } WHEN { + TURN { MOVE(player, MOVE_IVY_CUDGEL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_IVY_CUDGEL, player); + HP_BAR(opponent); + MESSAGE("It's super effective!"); // Should be super effective everytime if type isnt being changed + } +} +#endif diff --git a/test/battle/move_effect/knock_off.c b/test/battle/move_effect/knock_off.c index fd7a9a88245d..45450b990d68 100644 --- a/test/battle/move_effect/knock_off.c +++ b/test/battle/move_effect/knock_off.c @@ -437,3 +437,474 @@ SINGLE_BATTLE_TEST("Knock Off does not activate if the item was previously consu EXPECT(opponent->item == ITEM_NONE); } } + +// Knock Off triggers Unburden regardless of whether the item is fully removed (Gen 5+) or not. + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Knock Off triggers Unburden (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(60); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Innates(ABILITY_UNBURDEN); Item(ITEM_LEFTOVERS); Speed(50); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); } + TURN { MOVE(player, MOVE_CELEBRATE); } + } SCENE { + // turn 1 + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); + MESSAGE("Wobbuffet knocked off the opposing Wobbuffet's Leftovers!"); + // turn 2 + MESSAGE("The opposing Wobbuffet used Celebrate!"); + MESSAGE("Wobbuffet used Celebrate!"); + } THEN { + EXPECT(opponent->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Knock Off doesn't remove item if it's prevented by Sticky Hold (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MUK) { MaxHP(100); HP(51); Item(ITEM_ORAN_BERRY); Ability(ABILITY_POISON_TOUCH); Innates(ABILITY_STICKY_HOLD); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_STICKY_HOLD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +WILD_BATTLE_TEST("Knock Off does not remove item when used by Wild Pokemon (Gen 5+) (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LEFTOVERS); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_EVIOLITE); } + } WHEN { + TURN { MOVE(opponent, MOVE_KNOCK_OFF); } + TURN { MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + // Turn 1 + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, opponent); + if (B_KNOCK_OFF_REMOVAL >= GEN_5) + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF, player); + else + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF, player); + // Turn 2 + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF, opponent); + } THEN { + EXPECT(player->item == ITEM_LEFTOVERS); + if (B_KNOCK_OFF_REMOVAL >= GEN_5) + EXPECT(opponent->item == ITEM_NONE); + else + EXPECT(opponent->item == ITEM_EVIOLITE); + } +} + +SINGLE_BATTLE_TEST("Knock Off knocks a healing berry before it has the chance to activate (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_SITRUS_BERRY); MaxHP(500); HP(255); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Wobbuffet restored its health using its Sitrus Berry!"); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); + MESSAGE("Wobbuffet knocked off the opposing Wobbuffet's Sitrus Berry!"); + } THEN { + EXPECT(opponent->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Knock Off activates after Rocky Helmet and Weakness Policy (Multi)") +{ + u16 item = 0; + + PARAMETRIZE { item = ITEM_WEAKNESS_POLICY; } + PARAMETRIZE { item = ITEM_ROCKY_HELMET; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, item); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + if (item == ITEM_WEAKNESS_POLICY) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE); + MESSAGE("Using Weakness Policy, the Attack of the opposing Wobbuffet sharply rose!"); + MESSAGE("Using Weakness Policy, the Sp. Atk of the opposing Wobbuffet sharply rose!"); + } else if (item == ITEM_ROCKY_HELMET) { + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the opposing Wobbuffet's Rocky Helmet!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); + MESSAGE("Wobbuffet knocked off the opposing Wobbuffet's Rocky Helmet!"); + } + } THEN { + EXPECT(opponent->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Knock Off deals additional damage to opponents holding an item in Gen 6+ (Multi)", s16 damage) +{ + u16 item = 0; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_LEFTOVERS; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, item); }; + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + if (item != ITEM_NONE) + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); + } FINALLY { + if (B_KNOCK_OFF_DMG >= GEN_6) + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(1.5), results[1].damage); + else + EXPECT_EQ(results[0].damage, results[1].damage); + } THEN { + EXPECT(opponent->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Knock Off does not remove items through Substitute (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LEFTOVERS); }; + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + NOT { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); } + } THEN { + EXPECT(opponent->item == ITEM_LEFTOVERS); + } +} + +SINGLE_BATTLE_TEST("Knock Off does not remove items through Substitute even if it breaks it (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(4); HP(4); Items(ITEM_NONE, ITEM_LEFTOVERS); }; + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + MESSAGE("The opposing Wobbuffet's substitute faded!"); + NOT { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); } + } THEN { + EXPECT(opponent->item == ITEM_LEFTOVERS); + } +} + +SINGLE_BATTLE_TEST("Knock Off does not remove items through Protect (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LEFTOVERS); }; + } WHEN { + TURN { MOVE(opponent, MOVE_PROTECT); MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); + } + } THEN { + EXPECT(opponent->item == ITEM_LEFTOVERS); + } +} + +SINGLE_BATTLE_TEST("Knock Off does not remove items if target is immune (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ELECTRIFY) == EFFECT_ELECTRIFY); + ASSUME(GetSpeciesType(SPECIES_DONPHAN, 0) == TYPE_GROUND || GetSpeciesType(SPECIES_DONPHAN, 1) == TYPE_GROUND); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_DONPHAN) { Items(ITEM_NONE, ITEM_LEFTOVERS); }; + } WHEN { + TURN { MOVE(opponent, MOVE_ELECTRIFY); MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); + } + } THEN { + EXPECT(opponent->item == ITEM_LEFTOVERS); + } +} + +SINGLE_BATTLE_TEST("Recycle cannot recover an item removed by Knock Off (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LEFTOVERS); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); + MOVE(opponent, MOVE_RECYCLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); + MESSAGE("Wobbuffet knocked off the opposing Wobbuffet's Leftovers!"); + + MESSAGE("The opposing Wobbuffet used Recycle!"); + MESSAGE("But it failed!"); + } THEN { + EXPECT(opponent->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Knock Off does not prevent targets from receiving another item in Gen 5+ (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LEFTOVERS); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LEFTOVERS); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); } + TURN { MOVE(player, MOVE_BESTOW); } + } SCENE { + // turn 1 + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); + MESSAGE("Wobbuffet knocked off the opposing Wobbuffet's Leftovers!"); + // turn 2 + if (B_KNOCK_OFF_REMOVAL >= GEN_5) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BESTOW, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT); + MESSAGE("The opposing Wobbuffet restored a little HP using its Leftovers!"); + } else { + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_BESTOW, player); } + MESSAGE("But it failed!"); + } + } THEN { + if (B_KNOCK_OFF_REMOVAL >= GEN_5) + EXPECT(opponent->item == ITEM_LEFTOVERS); + else + EXPECT(opponent->item == ITEM_NONE); + } +} + +// Knock Off triggers Unburden regardless of whether the item is fully removed (Gen 5+) or not. +SINGLE_BATTLE_TEST("Knock Off triggers Unburden (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(60); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_UNBURDEN); Items(ITEM_NONE, ITEM_LEFTOVERS); Speed(50); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); } + TURN { MOVE(player, MOVE_CELEBRATE); } + } SCENE { + // turn 1 + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); + MESSAGE("Wobbuffet knocked off the opposing Wobbuffet's Leftovers!"); + // turn 2 + MESSAGE("The opposing Wobbuffet used Celebrate!"); + MESSAGE("Wobbuffet used Celebrate!"); + } THEN { + EXPECT(opponent->item == ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Knock Off does not trigger the opposing ally's Symbiosis (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LEFTOVERS); } + PLAYER(SPECIES_FLORGES) { Items(ITEM_NONE, ITEM_LEFTOVERS); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_KNOCK_OFF, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); + MESSAGE("The opposing Wobbuffet knocked off Wobbuffet's Leftovers!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT); + MESSAGE("Wobbuffet restored its health using its Leftovers!"); + } + } THEN { + EXPECT(playerLeft->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Knock Off does knock off Mega Stones from Pokemon that don't actually use them (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_ABSOLITE); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + MESSAGE("Wobbuffet knocked off the opposing Wobbuffet's Absolite!"); + } +} + +SINGLE_BATTLE_TEST("Knock Off doesn't knock off Mega Stones from Pokemon that actually use them (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ABSOL) { Items(ITEM_NONE, ITEM_ABSOLITE); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + NOT MESSAGE("Wobbuffet knocked off the opposing Absol's Absolite!"); + } +} + +SINGLE_BATTLE_TEST("Knock Off does knock off Orbs for Primal Reversion from Pokemon that don't actually use them (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_RED_ORB); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + MESSAGE("Wobbuffet knocked off the opposing Wobbuffet's Red Orb!"); + } +} + +SINGLE_BATTLE_TEST("Knock Off doesn't knock off Orbs for Primal Reversion from Pokemon that actually use them (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GROUDON) { Items(ITEM_NONE, ITEM_RED_ORB); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + NOT MESSAGE("Wobbuffet knocked off the opposing Groudon's Red Orb!"); + } +} + +SINGLE_BATTLE_TEST("Knock Off doesn't knock off Z-Crystals (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_ELECTRIUM_Z); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + NOT MESSAGE("Wobbuffet knocked off the opposing Wobbuffet's Electrium Z!"); + } +} + +SINGLE_BATTLE_TEST("Knock Off doesn't knock off Ultranecrozium Z from Pokemon that actually use it (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_NECROZMA_DUSK_MANE) { Items(ITEM_NONE, ITEM_ULTRANECROZIUM_Z); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + NOT MESSAGE("Wobbuffet knocked off the opposing Necrozma's Ultranecrozium Z!"); + } +} + +SINGLE_BATTLE_TEST("Knock Off does knock off other form-change hold items from Pokemon that don't actually use them (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_SKY_PLATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + MESSAGE("Wobbuffet knocked off the opposing Wobbuffet's Sky Plate!"); + } +} + +SINGLE_BATTLE_TEST("Knock Off doesn't knock off other form-change hold items from Pokemon that actually use them (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ARCEUS) { Items(ITEM_NONE, ITEM_SKY_PLATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + NOT MESSAGE("Wobbuffet knocked off the opposing Arceus's Sky Plate!"); + } +} + +SINGLE_BATTLE_TEST("Knock Off does knock off begin-battle form-change hold items from Pokemon that don't actually use them (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_RUSTED_SHIELD); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + MESSAGE("Wobbuffet knocked off the opposing Wobbuffet's Rusted Shield!"); + } +} + +SINGLE_BATTLE_TEST("Knock Off doesn't knock off begin-battle form-change hold items from Pokemon that actually use them (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZAMAZENTA_HERO) { Items(ITEM_NONE, ITEM_RUSTED_SHIELD); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + NOT MESSAGE("Wobbuffet knocked off the opposing Zamazenta's Rusted Shield!"); + } +} + +SINGLE_BATTLE_TEST("Knock Off does not activate if user faints (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_ROCKY_HELMET); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("Wobbuffet was hurt by the opposing Wobbuffet's Rocky Helmet!"); + MESSAGE("Wobbuffet fainted!"); + } THEN { + EXPECT(opponent->item == ITEM_ROCKY_HELMET); + } +} + +SINGLE_BATTLE_TEST("Knock Off doesn't remove item if it's prevented by Sticky Hold (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MUK) { MaxHP(100); HP(51); Items(ITEM_NONE, ITEM_ORAN_BERRY); Ability(ABILITY_STICKY_HOLD); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_STICKY_HOLD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} + +SINGLE_BATTLE_TEST("Knock Off does not activate if the item was previously consumed (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_AIR_BALLOON); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + MESSAGE("The opposing Wobbuffet's Air Balloon popped!"); + NOT MESSAGE("Wobbuffet knocked off the opposing Wobbuffet's Air Balloon!"); + } THEN { + EXPECT(opponent->item == ITEM_NONE); + } +} +#endif diff --git a/test/battle/move_effect/lash_out.c b/test/battle/move_effect/lash_out.c index 52fca8575866..0d0509207e7d 100644 --- a/test/battle/move_effect/lash_out.c +++ b/test/battle/move_effect/lash_out.c @@ -186,3 +186,142 @@ DOUBLE_BATTLE_TEST("Lash Out damage is not boosted by Treasure of Ruin ability a EXPECT_EQ(damage[0], damage[1]); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Lash Out damage is only boosted on the turn that Intimidate switches in (Traits)") +{ + s16 damage[3] = {0}; + u32 move = MOVE_NONE; + + PARAMETRIZE { move = MOVE_LASH_OUT; } + PARAMETRIZE { move = MOVE_SPLASH; } + + GIVEN { + PLAYER(SPECIES_GRIMMSNARL) { Moves(move, MOVE_CELEBRATE, MOVE_LASH_OUT); } + OPPONENT(SPECIES_INCINEROAR) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_BLAZE); Moves(MOVE_CELEBRATE); } + OPPONENT(SPECIES_INCINEROAR) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_INTIMIDATE); Moves(MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_LASH_OUT); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, move); SWITCH(opponent, 1); } + TURN { MOVE(player, MOVE_LASH_OUT); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LASH_OUT, player); + HP_BAR(opponent, captureDamage: &damage[0]); + + ANIMATION(ANIM_TYPE_MOVE, move, player); + if (move == MOVE_LASH_OUT) + HP_BAR(opponent, captureDamage: &damage[1]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_LASH_OUT, player); + HP_BAR(opponent, captureDamage: &damage[2]); + } THEN { + EXPECT_EQ(damage[0], damage[2]); + EXPECT_MUL_EQ(damage[0], (move == MOVE_LASH_OUT ? UQ_4_12(1.33) : UQ_4_12(0.00)), damage[1]); + } +} + +SINGLE_BATTLE_TEST("Lash Out damage is boosted on turn 1 by switch in abilities (Traits)") +{ + s16 damage[2] = {0}; + u32 species = SPECIES_NONE, ability = ABILITY_NONE; + + PARAMETRIZE { species = SPECIES_INCINEROAR, ability = ABILITY_BLAZE; } + PARAMETRIZE { species = SPECIES_INCINEROAR, ability = ABILITY_INTIMIDATE; } + PARAMETRIZE { species = SPECIES_HYDRAPPLE, ability = ABILITY_REGENERATOR; } + PARAMETRIZE { species = SPECIES_HYDRAPPLE, ability = ABILITY_SUPERSWEET_SYRUP; } + + GIVEN { + PLAYER(SPECIES_GRIMMSNARL) { Moves(MOVE_LASH_OUT); } + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_LASH_OUT); } + TURN { MOVE(player, MOVE_LASH_OUT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LASH_OUT, player); + HP_BAR(opponent, captureDamage: &damage[0]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_LASH_OUT, player); + HP_BAR(opponent, captureDamage: &damage[1]); + + } THEN { + if (ability == ABILITY_INTIMIDATE) + EXPECT_MUL_EQ(damage[0], UQ_4_12(1.33) , damage[1]); + else if (ability == ABILITY_SUPERSWEET_SYRUP) + EXPECT_MUL_EQ(damage[0], UQ_4_12(2.00) , damage[1]); + else + EXPECT_EQ(damage[0], damage[1]); + } +} + +DOUBLE_BATTLE_TEST("Lash Out damage is boosted by Cotton Down activation in doubles (Traits)") +{ + s16 damage[2] = {0}; + enum Ability ability = ABILITY_NONE; + + PARAMETRIZE { ability = ABILITY_REGENERATOR; } + PARAMETRIZE { ability = ABILITY_COTTON_DOWN; } + + GIVEN { + PLAYER(SPECIES_GRIMMSNARL) { Speed(1); Moves(MOVE_LASH_OUT); } + PLAYER(SPECIES_RATTATA) { Speed(2); Moves(MOVE_TACKLE, MOVE_CELEBRATE); } + OPPONENT(SPECIES_ELDEGOSS) { Speed(3); Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_GOLEM) { Speed(4); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_LASH_OUT, target:opponentRight); MOVE(playerRight, MOVE_TACKLE, target:opponentLeft); } + TURN { MOVE(playerLeft, MOVE_LASH_OUT, target:opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_LASH_OUT, playerLeft); + HP_BAR(opponentRight, captureDamage: &damage[0]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_LASH_OUT, playerLeft); + HP_BAR(opponentRight, captureDamage: &damage[1]); + + } THEN { + if (ability == ABILITY_COTTON_DOWN) + EXPECT_MUL_EQ(damage[0], UQ_4_12(2.00) , damage[1]); + else + EXPECT_EQ(damage[0], damage[1]); + } +} + +DOUBLE_BATTLE_TEST("Lash Out damage is not boosted by Treasure of Ruin ability activation in doubles (Traits)") +{ + s16 damage[2] = {0}; + u32 species = SPECIES_NONE, ability = ABILITY_NONE; + + PARAMETRIZE { species = SPECIES_KANGASKHAN, ability = ABILITY_INNER_FOCUS; } + PARAMETRIZE { species = SPECIES_HYDRAPPLE, ability = ABILITY_SUPERSWEET_SYRUP; } + PARAMETRIZE { species = SPECIES_WO_CHIEN, ability = ABILITY_TABLETS_OF_RUIN; } + PARAMETRIZE { species = SPECIES_CHIEN_PAO, ability = ABILITY_SWORD_OF_RUIN; } + PARAMETRIZE { species = SPECIES_TING_LU, ability = ABILITY_VESSEL_OF_RUIN; } + PARAMETRIZE { species = SPECIES_CHI_YU, ability = ABILITY_BEADS_OF_RUIN; } + + GIVEN { + PLAYER(SPECIES_GRIMMSNARL) { Moves(MOVE_LASH_OUT); } + PLAYER(SPECIES_RATTATA); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOLEM); + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_LASH_OUT, target:opponentRight); } + TURN { MOVE(playerLeft, MOVE_LASH_OUT, target:opponentRight); SWITCH(opponentLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LASH_OUT, playerLeft); + HP_BAR(opponentRight, captureDamage: &damage[0]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_LASH_OUT, playerLeft); + HP_BAR(opponentRight, captureDamage: &damage[1]); + + } THEN { + if (ability == ABILITY_SUPERSWEET_SYRUP) + EXPECT_MUL_EQ(damage[0], UQ_4_12(2.00) , damage[1]); + else if (ability == ABILITY_TABLETS_OF_RUIN) + EXPECT_MUL_EQ(damage[0], UQ_4_12(0.75) , damage[1]); + else if (ability == ABILITY_SWORD_OF_RUIN) + EXPECT_MUL_EQ(damage[0], UQ_4_12(1.33) , damage[1]); + else + EXPECT_EQ(damage[0], damage[1]); + } +} +#endif diff --git a/test/battle/move_effect/last_respects.c b/test/battle/move_effect/last_respects.c index 1e0ddaed28b1..2be6b5eaa8cb 100644 --- a/test/battle/move_effect/last_respects.c +++ b/test/battle/move_effect/last_respects.c @@ -65,3 +65,65 @@ SINGLE_BATTLE_TEST("Last Respects power is multiplied by the amount of fainted m EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.0 + faintCount), results[faintCount].damage); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Last Respects power is multiplied by the amount of fainted mon in the user's side - Player (Multi)", s16 damage) +{ + u32 j = 0, faintCount = 0; + PARAMETRIZE { faintCount = 0; } + PARAMETRIZE { faintCount = 1; } + PARAMETRIZE { faintCount = 2; } + GIVEN { + PLAYER(SPECIES_HITMONLEE); // Not Wobbuffet to omit type effectiveness + PLAYER(SPECIES_GEODUDE); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LEPPA_BERRY); Moves(MOVE_RECYCLE, MOVE_NONE, MOVE_NONE, MOVE_NONE); } + } WHEN { + for (j = 0; j < faintCount; j++) + { + TURN { MOVE(opponent, MOVE_RECYCLE); SWITCH(player, 1); } + TURN { MOVE(opponent, MOVE_RECYCLE); MOVE(player, MOVE_MEMENTO); SEND_OUT(player, 0); } + TURN { MOVE(opponent, MOVE_RECYCLE); USE_ITEM(player, ITEM_REVIVE, partyIndex: 1); } + } + TURN { + MOVE(opponent, MOVE_RECYCLE); + MOVE(player, MOVE_LAST_RESPECTS); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LAST_RESPECTS, player); + HP_BAR(opponent, captureDamage: &results[j].damage); + } THEN { + if (faintCount > 0) + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.0 + faintCount), results[faintCount].damage); + } +} + +SINGLE_BATTLE_TEST("Last Respects power is multiplied by the amount of fainted mon in the user's side - Opponent (Multi)", s16 damage) +{ + u32 j = 0, faintCount = 0; + PARAMETRIZE { faintCount = 0; } + PARAMETRIZE { faintCount = 1; } + PARAMETRIZE { faintCount = 2; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LEPPA_BERRY); Moves(MOVE_RECYCLE, MOVE_NONE, MOVE_NONE, MOVE_NONE); } + OPPONENT(SPECIES_HITMONLEE); // Not Wobbuffet to omit type effectiveness + OPPONENT(SPECIES_GEODUDE); + } WHEN { + for (j = 0; j < faintCount; j++) + { + TURN { MOVE(player, MOVE_RECYCLE); SWITCH(opponent, 1); } + TURN { MOVE(player, MOVE_RECYCLE); MOVE(opponent, MOVE_MEMENTO); SEND_OUT(opponent, 0); } + TURN { MOVE(player, MOVE_RECYCLE); USE_ITEM(opponent, ITEM_REVIVE, partyIndex: 1); } + } + TURN { + MOVE(player, MOVE_RECYCLE); + MOVE(opponent, MOVE_LAST_RESPECTS); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LAST_RESPECTS, opponent); + HP_BAR(player, captureDamage: &results[j].damage); + } THEN { + if (faintCount > 0) + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.0 + faintCount), results[faintCount].damage); + } +} +#endif diff --git a/test/battle/move_effect/magic_room.c b/test/battle/move_effect/magic_room.c index 02953f533d3a..2681c9a9fd17 100644 --- a/test/battle/move_effect/magic_room.c +++ b/test/battle/move_effect/magic_room.c @@ -76,3 +76,74 @@ SINGLE_BATTLE_TEST("Magic Room: An item that can activate will activate once Mag } TO_DO_BATTLE_TEST("TODO: Write Magic Room (Move Effect) test titles") + +#if MAX_MON_ITEMS > 1 +DOUBLE_BATTLE_TEST("Magic Room prevents item hold effects (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_BERRY_JUICE].holdEffect == HOLD_EFFECT_RESTORE_HP); + ASSUME(GetMoveEffect(MOVE_DRAGON_RAGE) == EFFECT_FIXED_HP_DAMAGE); + ASSUME(GetMoveFixedHPDamage(MOVE_DRAGON_RAGE) == 40); + + PLAYER(SPECIES_WOBBUFFET) { MaxHP(100); HP(60); Items(ITEM_PECHA_BERRY, ITEM_BERRY_JUICE); } + PLAYER(SPECIES_WOBBUFFET) { MaxHP(100); HP(60); Items(ITEM_PECHA_BERRY, ITEM_BERRY_JUICE); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(60); Items(ITEM_PECHA_BERRY, ITEM_BERRY_JUICE); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(60); Items(ITEM_PECHA_BERRY, ITEM_BERRY_JUICE); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_MAGIC_ROOM); } + TURN { + MOVE(playerLeft, MOVE_DRAGON_RAGE, target: opponentLeft); + MOVE(opponentLeft, MOVE_DRAGON_RAGE, target: playerLeft); + MOVE(playerRight, MOVE_DRAGON_RAGE, target: opponentRight); + MOVE(opponentRight, MOVE_DRAGON_RAGE, target: playerRight); + } + TURN { MOVE(playerLeft, MOVE_MAGIC_ROOM); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MAGIC_ROOM, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MAGIC_ROOM, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + } +} + +SINGLE_BATTLE_TEST("Magic Room: An item that can activate will activate once Magic Room is over (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_WHITE_HERB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_MAGIC_ROOM); MOVE(opponent, MOVE_GROWL); } + TURN {} + TURN {} + TURN {} + TURN {} + TURN {} + } SCENE { + // Turn 1 + ANIMATION(ANIM_TYPE_MOVE, MOVE_MAGIC_ROOM, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, opponent); + // Turn 2 + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + // Turn 3 + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + // Turn 4 + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + // Turn 5 + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } + + MESSAGE("Magic Room wore off, and held items' effects returned to normal!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } +} +#endif diff --git a/test/battle/move_effect/magnetic_flux.c b/test/battle/move_effect/magnetic_flux.c index ac969a9dd27f..d7078e55c9d2 100644 --- a/test/battle/move_effect/magnetic_flux.c +++ b/test/battle/move_effect/magnetic_flux.c @@ -45,3 +45,18 @@ DOUBLE_BATTLE_TEST("Magnetic Flux raises Defense and Sp. Defense of all Plus/Min EXPECT_EQ(opponentRight->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE); } } + +#if MAX_MON_TRAITS > 1 +AI_DOUBLE_BATTLE_TEST("AI uses Magnetic Flux (Traits)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, MOVE_CELEBRATE); } + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, MOVE_CELEBRATE); } + OPPONENT(SPECIES_KLINK) { Ability(ABILITY_CLEAR_BODY); Innates(ABILITY_PLUS); Moves(MOVE_MAGNETIC_FLUX, MOVE_POUND); } + OPPONENT(SPECIES_KLINK) { Ability(ABILITY_CLEAR_BODY); Innates(ABILITY_PLUS); Moves(MOVE_MAGNETIC_FLUX, MOVE_POUND); } + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, MOVE_MAGNETIC_FLUX); } + } +} +#endif diff --git a/test/battle/move_effect/max_hp_50_recoil.c b/test/battle/move_effect/max_hp_50_recoil.c index 3e54e05532cc..fa0c7224ccd4 100644 --- a/test/battle/move_effect/max_hp_50_recoil.c +++ b/test/battle/move_effect/max_hp_50_recoil.c @@ -147,3 +147,36 @@ SINGLE_BATTLE_TEST("Steel Beam is not blocked by Damp") } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Steel Beam hp loss is prevented by Magic Guard (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CLEFAIRY) { Ability(ABILITY_CUTE_CHARM); Innates(ABILITY_MAGIC_GUARD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STEEL_BEAM); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEEL_BEAM, player); + HP_BAR(opponent); + NOT HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Steel Beam is not blocked by Damp (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(400); MaxHP(400); } + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_SWIFT_SWIM); Innates(ABILITY_DAMP); } + } WHEN { + TURN { MOVE(player, MOVE_STEEL_BEAM); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEEL_BEAM, player); + HP_BAR(player, damage: 200); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_DAMP); + MESSAGE("The opposing Golduck's Damp prevents Wobbuffet from using Steel Beam!"); + } + } +} +#endif diff --git a/test/battle/move_effect/me_first.c b/test/battle/move_effect/me_first.c index 8ccaa324efa4..47e8287a0b4d 100644 --- a/test/battle/move_effect/me_first.c +++ b/test/battle/move_effect/me_first.c @@ -100,3 +100,17 @@ SINGLE_BATTLE_TEST("Me First deducts power points from itself, not the copied mo } // TO_DO_BATTLE_TEST: Not everything has been tested + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Me First can be selected if users holds Assault Vest (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(100); Items(ITEM_PECHA_BERRY, ITEM_ASSAULT_VEST); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); } + } WHEN { + TURN { MOVE(player, MOVE_ME_FIRST); MOVE(opponent, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ME_FIRST, player); + } +} +#endif diff --git a/test/battle/move_effect/misty_terrain.c b/test/battle/move_effect/misty_terrain.c index cd9a92eb3767..0ae5955d077f 100644 --- a/test/battle/move_effect/misty_terrain.c +++ b/test/battle/move_effect/misty_terrain.c @@ -100,3 +100,23 @@ SINGLE_BATTLE_TEST("Misty Terrain will fail if there is one already on the field NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_MISTY_TERRAIN, opponent); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Misty Terrain protects grounded battlers from non-volatile status conditions (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CLAYDOL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_LEVITATE); } + } WHEN { + TURN { MOVE(player, MOVE_MISTY_TERRAIN); MOVE(opponent, MOVE_TOXIC); } + TURN { MOVE(player, MOVE_TOXIC); } + } SCENE { + MESSAGE("Wobbuffet used Misty Terrain!"); + MESSAGE("The opposing Claydol used Toxic!"); + MESSAGE("Wobbuffet surrounds itself with a protective mist!"); + NOT { STATUS_ICON(opponent, badPoison: TRUE); } + MESSAGE("Wobbuffet used Toxic!"); + STATUS_ICON(opponent, badPoison: TRUE); + } +} +#endif diff --git a/test/battle/move_effect/multi_hit.c b/test/battle/move_effect/multi_hit.c index 62f955687974..1a2ca5d66490 100644 --- a/test/battle/move_effect/multi_hit.c +++ b/test/battle/move_effect/multi_hit.c @@ -284,3 +284,139 @@ SINGLE_BATTLE_TEST("Multi Hit moves will not disrupt Destiny Bond flag") MESSAGE("The opposing Wobbuffet fainted!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Multi hit Moves hit the maximum amount with Skill Link (Traits)") +{ + PASSES_RANDOMLY(100, 100, RNG_HITS); + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_SKILL_LINK); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BULLET_SEED); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, player); + MESSAGE("The Pokémon was hit 5 time(s)!"); + } +} + +SINGLE_BATTLE_TEST("Scale Shot decreases defense and increases speed after killing opposing with less then 4 hits (Traits)") +{ + u32 item; + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_LOADED_DICE; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SCALE_SHOT) == EFFECT_MULTI_HIT); + PLAYER(SPECIES_BAGON) { Item(item); } + OPPONENT(SPECIES_SLUGMA) { Ability(ABILITY_FLAME_BODY); Innates(ABILITY_WEAK_ARMOR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCALE_SHOT); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCALE_SHOT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCALE_SHOT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCALE_SHOT, player); + MESSAGE("The opposing Slugma fainted!"); + MESSAGE("The Pokémon was hit 3 time(s)!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Bagon's Defense fell!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Bagon's Speed rose!"); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Multi hit Moves hit at least four times with Loaded Dice (Multi)") +{ + PASSES_RANDOMLY(50, 100, RNG_LOADED_DICE); + + GIVEN { + ASSUME(gItemsInfo[ITEM_LOADED_DICE].holdEffect == HOLD_EFFECT_LOADED_DICE); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LOADED_DICE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BULLET_SEED); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, player); + MESSAGE("The Pokémon was hit 4 time(s)!"); + } +} + +SINGLE_BATTLE_TEST("Multi hit Moves hit five times 50 Percent of the time with Loaded Dice (Multi)") +{ + PASSES_RANDOMLY(50, 100, RNG_LOADED_DICE); + + GIVEN { + ASSUME(gItemsInfo[ITEM_LOADED_DICE].holdEffect == HOLD_EFFECT_LOADED_DICE); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LOADED_DICE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BULLET_SEED); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, player); + MESSAGE("The Pokémon was hit 5 time(s)!"); + } +} + +SINGLE_BATTLE_TEST("Scale Shot decreases defense and increases speed after the 4th hit of Loaded Dice (Multi)") +{ + PASSES_RANDOMLY(50, 100, RNG_LOADED_DICE); + GIVEN { + ASSUME(GetMoveEffect(MOVE_SCALE_SHOT) == EFFECT_MULTI_HIT); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LOADED_DICE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCALE_SHOT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCALE_SHOT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCALE_SHOT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCALE_SHOT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCALE_SHOT, player); + MESSAGE("The Pokémon was hit 4 time(s)!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Wobbuffet's Defense fell!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Wobbuffet's Speed rose!"); + } +} + +SINGLE_BATTLE_TEST("Scale Shot decreases defense and increases speed after killing opposing with less then 4 hits (Multi)") +{ + u32 item; + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_LOADED_DICE; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SCALE_SHOT) == EFFECT_MULTI_HIT); + PLAYER(SPECIES_BAGON) { Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_SLUGMA) { Ability(ABILITY_WEAK_ARMOR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCALE_SHOT); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCALE_SHOT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCALE_SHOT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCALE_SHOT, player); + MESSAGE("The opposing Slugma fainted!"); + MESSAGE("The Pokémon was hit 3 time(s)!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Bagon's Defense fell!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Bagon's Speed rose!"); + } +} +#endif diff --git a/test/battle/move_effect/natural_gift.c b/test/battle/move_effect/natural_gift.c index ab483db1da4e..6ddb8b750760 100644 --- a/test/battle/move_effect/natural_gift.c +++ b/test/battle/move_effect/natural_gift.c @@ -63,3 +63,68 @@ SINGLE_BATTLE_TEST("Natural Gift removes the berry if user missed") } TO_DO_BATTLE_TEST("TODO: Write Natural Gift (Move Effect) test titles") + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Natural Gift removes berry if move fails due to an immunity (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_PECHA_BERRY); } + OPPONENT(SPECIES_PHANPY); + } WHEN { + TURN { MOVE(player, MOVE_NATURAL_GIFT); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player); + } THEN { + EXPECT(player->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Natural Gift does not remove berry if user is ejected out (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_PECHA_BERRY); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_RED_CARD); } + } WHEN { + TURN { MOVE(player, MOVE_NATURAL_GIFT); } + TURN { SWITCH(player, 0); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT(player->item == ITEM_PECHA_BERRY); + } +} + +SINGLE_BATTLE_TEST("Natural Gift does not remove berry if user is unable to use a move (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_PECHA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_THUNDER_WAVE); MOVE(player, MOVE_NATURAL_GIFT, WITH_RNG(RNG_PARALYSIS, FALSE)); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_WAVE, opponent); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player); + } THEN { + EXPECT(player->item == ITEM_PECHA_BERRY); + } +} + +SINGLE_BATTLE_TEST("Natural Gift removes the berry if user missed (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_PECHA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SAND_ATTACK); MOVE(player, MOVE_NATURAL_GIFT, hit: FALSE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SAND_ATTACK, opponent); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player); + } THEN { + EXPECT(player->item == ITEM_NONE); + } +} + +TO_DO_BATTLE_TEST("TODO: Write Natural Gift (Move Effect) test titles (Multi)") +#endif diff --git a/test/battle/move_effect/no_retreat.c b/test/battle/move_effect/no_retreat.c index fa944ab77618..fa3ffe83aad6 100644 --- a/test/battle/move_effect/no_retreat.c +++ b/test/battle/move_effect/no_retreat.c @@ -59,3 +59,24 @@ SINGLE_BATTLE_TEST("No Retreat won't activate Protean if it fails due to already } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("No Retreat won't activate Protean if it fails due to already being used by the user (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KECLEON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTEAN); } + } WHEN { + TURN { MOVE(player, MOVE_NO_RETREAT); MOVE(opponent, MOVE_SKILL_SWAP); } + TURN { MOVE(player, MOVE_NO_RETREAT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_NO_RETREAT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_NO_RETREAT, player); + ABILITY_POPUP(player, ABILITY_PROTEAN); + } + } +} +#endif diff --git a/test/battle/move_effect/octolock.c b/test/battle/move_effect/octolock.c index b98c17908d5e..450eaf739d79 100644 --- a/test/battle/move_effect/octolock.c +++ b/test/battle/move_effect/octolock.c @@ -179,3 +179,119 @@ SINGLE_BATTLE_TEST("Octolock ends after user that set the lock switches out") } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Octolock reduction is prevented by Clear Body, White Smoke and Full Metal Body (Traits)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_BELDUM; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE { species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } + PARAMETRIZE { species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_OCTOLOCK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_OCTOLOCK, player); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + if (species == SPECIES_BELDUM) + { + MESSAGE("The opposing Beldum can no longer escape because of Octolock!"); + ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + MESSAGE("The opposing Beldum's Clear Body prevents stat loss!"); + NONE_OF { + MESSAGE("The opposing Beldum's Defense fell!"); + MESSAGE("The opposing Beldum's Sp. Def fell!"); + } + } + else if (species == SPECIES_TORKOAL) + { + MESSAGE("The opposing Torkoal can no longer escape because of Octolock!"); + ABILITY_POPUP(opponent, ABILITY_WHITE_SMOKE); + MESSAGE("The opposing Torkoal's White Smoke prevents stat loss!"); + NONE_OF { + MESSAGE("The opposing Torkoal's Defense fell!"); + MESSAGE("The opposing Torkoal's Sp. Def fell!"); + } + } + else if (species == SPECIES_SOLGALEO) + { + MESSAGE("The opposing Solgaleo can no longer escape because of Octolock!"); + ABILITY_POPUP(opponent, ABILITY_FULL_METAL_BODY); + MESSAGE("The opposing Solgaleo's Full Metal Body prevents stat loss!"); + NONE_OF { + MESSAGE("The opposing Solgaleo's Defense fell!"); + MESSAGE("The opposing Solgaleo's Sp. Def fell!"); + } + } + } +} + +SINGLE_BATTLE_TEST("Octolock Defense reduction is prevented by Big Pecks (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_BIG_PECKS); } + } WHEN { + TURN { MOVE(player, MOVE_OCTOLOCK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_OCTOLOCK, player); + MESSAGE("The opposing Pidgey can no longer escape because of Octolock!"); + NOT MESSAGE("The opposing Pidgey's Defense fell!"); + ABILITY_POPUP(opponent, ABILITY_BIG_PECKS); + MESSAGE("The opposing Pidgey's Big Pecks prevents Defense loss!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Pidgey's Sp. Def fell!"); + } +} + +SINGLE_BATTLE_TEST("Octolock triggers Defiant for both stat reductions (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BISHARP) { Ability(ABILITY_PRESSURE); Innates(ABILITY_DEFIANT); } + } WHEN { + TURN { MOVE(player, MOVE_OCTOLOCK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_OCTOLOCK, player); + MESSAGE("The opposing Bisharp can no longer escape because of Octolock!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Bisharp's Defense fell!"); + ABILITY_POPUP(opponent, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Bisharp's Attack sharply rose!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Bisharp's Sp. Def fell!"); + ABILITY_POPUP(opponent, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Bisharp's Attack sharply rose!"); + } +} + +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Octolock reduction is prevented by Clear Amulet (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_CLEAR_AMULET); } + } WHEN { + TURN { MOVE(player, MOVE_OCTOLOCK); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_OCTOLOCK, player); + MESSAGE("The opposing Wobbuffet can no longer escape because of Octolock!"); + MESSAGE("The effects of the Clear Amulet held by the opposing Wobbuffet prevents its stats from being lowered!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wobbuffet's Defense fell!"); + MESSAGE("The opposing Wobbuffet's Sp. Def fell!"); + } + } +} +#endif diff --git a/test/battle/move_effect/ohko.c b/test/battle/move_effect/ohko.c index 81847a1ba570..6e5710d1afb8 100644 --- a/test/battle/move_effect/ohko.c +++ b/test/battle/move_effect/ohko.c @@ -86,3 +86,49 @@ SINGLE_BATTLE_TEST("OHKO moves fail if target protects") TO_DO_BATTLE_TEST("OHKO moves faints the target, skipping regular damage calculations") TO_DO_BATTLE_TEST("OHKO moves's accuracy increases by 1% for every level the user has over the target") TO_DO_BATTLE_TEST("OHKO moves's ignores non-stage accuracy modifiers") // Gravity, Wide Lens, Compound Eyes + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("OHKO moves can hit semi-invulnerable mons when the user has No-Guard (Traits)") +{ + GIVEN { + ASSUME(GetItemHoldEffect(ITEM_FOCUS_SASH) == HOLD_EFFECT_FOCUS_SASH); + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_NO_GUARD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_FLY); } + TURN { MOVE(player, MOVE_FISSURE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FISSURE, player); + HP_BAR(opponent, hp: 0); + } +} + +SINGLE_BATTLE_TEST("OHKO moves can can be endured by Sturdy (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GEODUDE) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_STURDY); } + } WHEN { + TURN { MOVE(player, MOVE_FISSURE); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_FISSURE, player); + ABILITY_POPUP(opponent, ABILITY_STURDY); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("OHKO moves can can be endured by Focus Sash (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_FOCUS_SASH); } + } WHEN { + TURN { MOVE(player, MOVE_FISSURE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FISSURE, player); + HP_BAR(opponent, hp: 1); + MESSAGE("The opposing Wobbuffet hung on using its Focus Sash!"); + } +} +#endif diff --git a/test/battle/move_effect/photon_geyser.c b/test/battle/move_effect/photon_geyser.c index 3f4bb10146ee..c713aff7bd7a 100644 --- a/test/battle/move_effect/photon_geyser.c +++ b/test/battle/move_effect/photon_geyser.c @@ -51,3 +51,19 @@ SINGLE_BATTLE_TEST("Photon Geyser ignores ignorable Abilities like Battle Armor" MESSAGE("A critical hit!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Photon Geyser ignores ignorable Abilities like Battle Armor (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_LAPRAS) { Ability(ABILITY_WATER_ABSORB); Innates(ABILITY_SHELL_ARMOR); } + } WHEN { + TURN { MOVE(player, MOVE_PHOTON_GEYSER, criticalHit: TRUE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PHOTON_GEYSER, player, ); + HP_BAR(opponent); + MESSAGE("A critical hit!"); + } +} +#endif diff --git a/test/battle/move_effect/pledge.c b/test/battle/move_effect/pledge.c index 3c6b04b17a0b..8d6eee2db9fd 100644 --- a/test/battle/move_effect/pledge.c +++ b/test/battle/move_effect/pledge.c @@ -1095,3 +1095,254 @@ DOUBLE_BATTLE_TEST("Pledge move combo doesn't trigger on opponent's Pledge move HP_BAR(opponentRight); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Rainbow flinch chance does not stack with Serene Grace (Traits)") +{ + PASSES_RANDOMLY(60, 100, RNG_SECONDARY_EFFECT); + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_BITE, MOVE_EFFECT_FLINCH) == TRUE); + PLAYER(SPECIES_TOGEPI) { Speed(8); Ability(ABILITY_SUPER_LUCK); Innates(ABILITY_SERENE_GRACE); } + PLAYER(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(4); } + OPPONENT(SPECIES_WYNAUT) { Speed(3); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_WATER_PLEDGE, target: opponentLeft); + MOVE(playerRight, MOVE_FIRE_PLEDGE, target: opponentRight); + } + TURN { MOVE(playerLeft, MOVE_BITE, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, playerLeft); + MESSAGE("The opposing Wynaut flinched and couldn't move!"); + } +} + +DOUBLE_BATTLE_TEST("Pledge moves can not be redirected by absorbing abilities (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_LILEEP) { Ability(ABILITY_SUCTION_CUPS); Innates(ABILITY_STORM_DRAIN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_WATER_PLEDGE, target: opponentRight);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, playerLeft); + HP_BAR(opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Pledge move combo doesn't trigger on opponent's Pledge move - Electrify (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ELECTRIFY) == EFFECT_ELECTRIFY); + PLAYER(SPECIES_ELECTIVIRE) { Ability(ABILITY_VITAL_SPIRIT); Innates(ABILITY_MOTOR_DRIVE); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerRight, MOVE_ELECTRIFY, target: opponentRight); + MOVE(opponentLeft, MOVE_GRASS_PLEDGE, target: playerLeft); + MOVE(opponentRight, MOVE_FIRE_PLEDGE, target: playerLeft); + MOVE(playerLeft, MOVE_WATER_PLEDGE, target: opponentRight); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASS_PLEDGE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASS_PLEDGE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, playerLeft); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, playerLeft); + HP_BAR(opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Pledge move combo doesn't trigger on opponent's Pledge move - Storm Drain (Traits)") +{ + GIVEN { + PLAYER(SPECIES_GASTRODON) { Ability(ABILITY_SAND_FORCE); Innates(ABILITY_STORM_DRAIN); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_FIRE_PLEDGE, target: playerLeft); + MOVE(opponentRight, MOVE_WATER_PLEDGE, target: playerLeft); + MOVE(playerLeft, MOVE_GRASS_PLEDGE, target: opponentRight); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, playerLeft); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASS_PLEDGE, playerLeft); + HP_BAR(opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Pledge move combo doesn't trigger on opponent's Pledge move - Sap Sipper (Traits)") +{ + GIVEN { + PLAYER(SPECIES_GOODRA) { Ability(ABILITY_GOOEY); Innates(ABILITY_SAP_SIPPER); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_WATER_PLEDGE, target: playerLeft); + MOVE(opponentRight, MOVE_GRASS_PLEDGE, target: playerLeft); + MOVE(playerLeft, MOVE_FIRE_PLEDGE, target: opponentRight); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASS_PLEDGE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASS_PLEDGE, playerLeft); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, playerLeft); + HP_BAR(opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Pledge move combo doesn't trigger on opponent's Pledge move - Dry Skin (Traits)") +{ + GIVEN { + PLAYER(SPECIES_PARASECT) { Ability(ABILITY_DAMP); Innates(ABILITY_DRY_SKIN); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_FIRE_PLEDGE, target: playerLeft); + MOVE(opponentRight, MOVE_WATER_PLEDGE, target: playerLeft); + MOVE(playerLeft, MOVE_GRASS_PLEDGE, target: opponentRight); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, playerLeft); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASS_PLEDGE, playerLeft); + HP_BAR(opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Pledge move combo doesn't trigger on opponent's Pledge move - Flash Fire (Traits)") +{ + GIVEN { + PLAYER(SPECIES_HEATRAN) { Ability(ABILITY_FLAME_BODY); Innates(ABILITY_FLASH_FIRE); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_GRASS_PLEDGE, target: playerLeft); + MOVE(opponentRight, MOVE_FIRE_PLEDGE, target: playerLeft); + MOVE(playerLeft, MOVE_WATER_PLEDGE, target: opponentRight); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASS_PLEDGE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASS_PLEDGE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, playerLeft); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, playerLeft); + HP_BAR(opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Pledge move combo doesn't trigger on opponent's Pledge move - Motor Drive (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ELECTRIFY) == EFFECT_ELECTRIFY); + PLAYER(SPECIES_ELECTIVIRE) { Ability(ABILITY_VITAL_SPIRIT); Innates(ABILITY_MOTOR_DRIVE); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerRight, MOVE_ELECTRIFY, target: opponentRight); + MOVE(opponentLeft, MOVE_WATER_PLEDGE, target: playerLeft); + MOVE(opponentRight, MOVE_GRASS_PLEDGE, target: playerLeft); + MOVE(playerLeft, MOVE_FIRE_PLEDGE, target: opponentRight); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASS_PLEDGE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASS_PLEDGE, playerLeft); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, playerLeft); + HP_BAR(opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Pledge move combo doesn't trigger on opponent's Pledge move - Volt Absorb (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ELECTRIFY) == EFFECT_ELECTRIFY); + PLAYER(SPECIES_JOLTEON) { Ability(ABILITY_QUICK_FEET); Innates(ABILITY_VOLT_ABSORB); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerRight, MOVE_ELECTRIFY, target: opponentRight); + MOVE(opponentLeft, MOVE_WATER_PLEDGE, target: playerLeft); + MOVE(opponentRight, MOVE_GRASS_PLEDGE, target: playerLeft); + MOVE(playerLeft, MOVE_GRASS_PLEDGE, target: opponentRight); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASS_PLEDGE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, playerLeft); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASS_PLEDGE, playerLeft); + HP_BAR(opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Pledge move combo doesn't trigger on opponent's Pledge move - Water Absorb (Traits)") +{ + GIVEN { + PLAYER(SPECIES_VAPOREON) { Ability(ABILITY_HYDRATION); Innates(ABILITY_WATER_ABSORB); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_FIRE_PLEDGE, target: playerLeft); + MOVE(opponentRight, MOVE_WATER_PLEDGE, target: playerLeft); + MOVE(playerLeft, MOVE_GRASS_PLEDGE, target: opponentRight); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, playerLeft); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASS_PLEDGE, playerLeft); + HP_BAR(opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Pledge move combo doesn't trigger on opponent's Pledge move - Well Baked Body (Traits)") +{ + GIVEN { + PLAYER(SPECIES_DACHSBUN) { Ability(ABILITY_AROMA_VEIL); Innates(ABILITY_WELL_BAKED_BODY); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_GRASS_PLEDGE, target: playerLeft); + MOVE(opponentRight, MOVE_FIRE_PLEDGE, target: playerLeft); + MOVE(playerLeft, MOVE_WATER_PLEDGE, target: opponentRight); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASS_PLEDGE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASS_PLEDGE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, playerLeft); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, playerLeft); + HP_BAR(opponentRight); + } +} +#endif diff --git a/test/battle/move_effect/powder.c b/test/battle/move_effect/powder.c index 3f0a3135d975..c317adaaed1f 100644 --- a/test/battle/move_effect/powder.c +++ b/test/battle/move_effect/powder.c @@ -315,3 +315,115 @@ DOUBLE_BATTLE_TEST("Powder damages a target using Shell Trap even if it wasn't h HP_BAR(playerLeft); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Powder doesn't damage target if it has Magic Guard (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ALAKAZAM) { Ability(ABILITY_INNER_FOCUS); Innates(ABILITY_MAGIC_GUARD); } + OPPONENT(SPECIES_VIVILLON); + } WHEN { + TURN { MOVE(opponent, MOVE_POWDER); MOVE(player, MOVE_EMBER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POWDER, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, player); + HP_BAR(opponent); + } + } THEN { + EXPECT_EQ(player->maxHP, player->hp); + } +} + +SINGLE_BATTLE_TEST("Powder damages the target under heavy rain (Gen 6) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_POWDER_RAIN, GEN_6); + PLAYER(SPECIES_KYOGRE_PRIMAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PRIMORDIAL_SEA); } + OPPONENT(SPECIES_VIVILLON); + } WHEN { + TURN { MOVE(opponent, MOVE_POWDER); MOVE(player, MOVE_EMBER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POWDER, opponent); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, player); + HP_BAR(player); + } THEN { + EXPECT_LT(player->hp, player->maxHP); + } +} + +SINGLE_BATTLE_TEST("Powder doesn't damage target under heavy rain (Gen 7+) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_POWDER_RAIN, GEN_7); + PLAYER(SPECIES_KYOGRE_PRIMAL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PRIMORDIAL_SEA); } + OPPONENT(SPECIES_VIVILLON); + } WHEN { + TURN { MOVE(opponent, MOVE_POWDER); MOVE(player, MOVE_EMBER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POWDER, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, player); + HP_BAR(player); + } + } THEN { + EXPECT_EQ(player->maxHP, player->hp); + } +} + +SINGLE_BATTLE_TEST("Powder fails if the target has Overcoat (Gen6+) (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_POWDER_OVERCOAT, GEN_6); + PLAYER(SPECIES_FORRETRESS) { Ability(ABILITY_STURDY); Innates(ABILITY_OVERCOAT); } + OPPONENT(SPECIES_VIVILLON); + } WHEN { + TURN { MOVE(opponent, MOVE_POWDER); MOVE(player, MOVE_EMBER); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_POWDER, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, player); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Powder prevents Protean/Libero from changing its user to Fire type (Traits)") +{ + u32 ability, species; + PARAMETRIZE { ability = ABILITY_PROTEAN; species = SPECIES_GRENINJA; } + PARAMETRIZE { ability = ABILITY_LIBERO; species = SPECIES_RABOOT; } + GIVEN { + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_VIVILLON); + } WHEN { + TURN { MOVE(opponent, MOVE_POWDER); MOVE(player, MOVE_EMBER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POWDER, opponent); + NONE_OF { + ABILITY_POPUP(player, ability); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, player); + HP_BAR(opponent); + } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Powder doesn't consume Berry from Fire type Natural Gift but prevents using the move (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_NATURAL_GIFT) == EFFECT_NATURAL_GIFT); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, ITEM_CHERI_BERRY); } + OPPONENT(SPECIES_VIVILLON); + } WHEN { + TURN { MOVE(opponent, MOVE_POWDER); MOVE(player, MOVE_NATURAL_GIFT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POWDER, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, player); + HP_BAR(opponent); + } + } THEN { + EXPECT_EQ(player->item, ITEM_CHERI_BERRY); + } +} +#endif diff --git a/test/battle/move_effect/protect.c b/test/battle/move_effect/protect.c index 930f6d829a5d..7720e39e9698 100644 --- a/test/battle/move_effect/protect.c +++ b/test/battle/move_effect/protect.c @@ -959,3 +959,137 @@ DOUBLE_BATTLE_TEST("Wide Guard is still activate even if user is switched out du } } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Protect is not transferred to a mon that is switched in due to Eject Button (Traits)") +{ + GIVEN { + PLAYER(SPECIES_URSHIFU) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_UNSEEN_FIST); }; + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { Item(ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_SQUIRTLE); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(opponentRight, MOVE_PROTECT); + MOVE(playerLeft, MOVE_POUND, target: opponentRight); + SEND_OUT(opponentRight, 2); + MOVE(playerRight, MOVE_POUND, target: opponentRight); + SEND_OUT(opponentRight, 3); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, playerRight); + HP_BAR(opponentRight); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Protect: Quick Guard, Wide Guard and Crafty Shield don't reduce Z-Move demage (Multi)", s16 damage) +{ + bool32 protected; + u32 move; + + PARAMETRIZE { protected = TRUE; move = MOVE_WIDE_GUARD; } + PARAMETRIZE { protected = FALSE; move = MOVE_WIDE_GUARD; } + + PARAMETRIZE { protected = TRUE; move = MOVE_QUICK_GUARD; } + PARAMETRIZE { protected = FALSE; move = MOVE_QUICK_GUARD; } + + PARAMETRIZE { protected = TRUE; move = MOVE_CRAFTY_SHIELD; } + PARAMETRIZE { protected = FALSE; move = MOVE_CRAFTY_SHIELD; } + + GIVEN { + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (protected) + TURN { MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_Z_MOVE); MOVE(opponent, move); } + else + TURN { MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_EQ(results[2].damage, results[3].damage); + EXPECT_EQ(results[4].damage, results[5].damage); + } +} + +SINGLE_BATTLE_TEST("Protect: Protective Pads protects from secondary effects (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_PROTECTIVE_PADS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_BURNING_BULWARK); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BURNING_BULWARK, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + STATUS_ICON(player, STATUS1_BURN); + } + } +} + +DOUBLE_BATTLE_TEST("Protect is not transferred to a mon that is switched in due to Eject Button (Multi)") +{ + GIVEN { + PLAYER(SPECIES_URSHIFU) { Ability(ABILITY_UNSEEN_FIST); }; + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_SQUIRTLE); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(opponentRight, MOVE_PROTECT); + MOVE(playerLeft, MOVE_POUND, target: opponentRight); + SEND_OUT(opponentRight, 2); + MOVE(playerRight, MOVE_POUND, target: opponentRight); + SEND_OUT(opponentRight, 3); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, playerRight); + HP_BAR(opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Wide Guard is still activate even if user is switched out due to Eject Button (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_SQUIRTLE); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(opponentRight, MOVE_WIDE_GUARD); + MOVE(playerLeft, MOVE_POUND, target: opponentRight); + SEND_OUT(opponentRight, 2); + MOVE(playerRight, MOVE_HYPER_VOICE, target: opponentRight); + SEND_OUT(opponentRight, 3); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WIDE_GUARD, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerRight); + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + } + } +} +#endif diff --git a/test/battle/move_effect/psychic_terrain.c b/test/battle/move_effect/psychic_terrain.c index c8fb061d9c69..32b9a922b9c2 100644 --- a/test/battle/move_effect/psychic_terrain.c +++ b/test/battle/move_effect/psychic_terrain.c @@ -210,3 +210,163 @@ DOUBLE_BATTLE_TEST("Psychic Terrain protects grounded battlers from priority mov ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Psychic Terrain protects grounded battlers from priority moves (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CLAYDOL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_LEVITATE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PSYCHIC_TERRAIN); } + TURN { MOVE(player, MOVE_QUICK_ATTACK); MOVE(opponent, MOVE_QUICK_ATTACK); } + } SCENE { + MESSAGE("Claydol used Psychic Terrain!"); + MESSAGE("The opposing Wobbuffet is protected by the Psychic Terrain!"); + NOT { HP_BAR(opponent); } + MESSAGE("The opposing Wobbuffet used Quick Attack!"); + HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Psychic Terrain doesn't blocks priority moves that target the user (Traits)") +{ + GIVEN { + PLAYER(SPECIES_SABLEYE) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PRANKSTER); HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PSYCHIC_TERRAIN); } + TURN { MOVE(player, MOVE_RECOVER); } + } SCENE { + MESSAGE("Sableye used Psychic Terrain!"); + MESSAGE("Sableye used Recover!"); + HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Psychic Terrain doesn't block priority moves that target all battlers (Traits)") +{ + GIVEN { + PLAYER(SPECIES_SABLEYE) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PRANKSTER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PSYCHIC_TERRAIN); } + TURN { MOVE(player, MOVE_HAZE); } + } SCENE { + MESSAGE("Sableye used Psychic Terrain!"); + MESSAGE("Sableye used Haze!"); + } +} + +SINGLE_BATTLE_TEST("Psychic Terrain doesn't block priority moves that target all opponents (Traits)") +{ + GIVEN { + PLAYER(SPECIES_SABLEYE) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PRANKSTER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PSYCHIC_TERRAIN); } + TURN { MOVE(player, MOVE_SPIKES); } + } SCENE { + MESSAGE("Sableye used Psychic Terrain!"); + MESSAGE("Sableye used Spikes!"); + } +} + +DOUBLE_BATTLE_TEST("Psychic Terrain doesn't block priority moves that target allies (Traits)") +{ + GIVEN { + PLAYER(SPECIES_SABLEYE) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PRANKSTER); } + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_PSYCHIC_TERRAIN); } + TURN { MOVE(playerLeft, MOVE_HEAL_PULSE, target: playerRight); } + } SCENE { + MESSAGE("Sableye used Psychic Terrain!"); + MESSAGE("Sableye used Heal Pulse!"); + } +} + +SINGLE_BATTLE_TEST("Psychic Terrain doesn't block priority field moves (Traits)") +{ + GIVEN { + PLAYER(SPECIES_SABLEYE) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_PRANKSTER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PSYCHIC_TERRAIN); } + TURN { MOVE(player, MOVE_SUNNY_DAY); } + } SCENE { + MESSAGE("Sableye used Psychic Terrain!"); + MESSAGE("Sableye used Sunny Day!"); + } +} + +SINGLE_BATTLE_TEST("Psychic Terrain doesn't block priority moves against semi-invulnerable targets (Traits)") +{ + u32 move = 0, shouldWork = 0; + PARAMETRIZE { move = MOVE_SOLAR_BEAM; shouldWork = FALSE;} + PARAMETRIZE { move = MOVE_FLY; shouldWork = TRUE;} + GIVEN { + WITH_CONFIG(CONFIG_TOXIC_NEVER_MISS, GEN_6); + ASSUME(IsSpeciesOfType(SPECIES_SHROODLE, TYPE_POISON)); + PLAYER(SPECIES_SHROODLE) { Ability(ABILITY_UNBURDEN); Innates(ABILITY_PRANKSTER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PSYCHIC_TERRAIN); MOVE(opponent,move);} + TURN { MOVE(player, MOVE_TOXIC); SKIP_TURN(opponent);} + } SCENE { + if (shouldWork) + { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + } + else + { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + } + } + } THEN { + if (shouldWork) + EXPECT(opponent->status1 & STATUS1_TOXIC_POISON); + else + EXPECT(!(opponent->status1 & STATUS1_TOXIC_POISON)); + } +} + +DOUBLE_BATTLE_TEST("Psychic Terrain protects grounded battlers from priority moves in doubles - Left (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CLAYDOL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_LEVITATE); } + PLAYER(SPECIES_TAPU_LELE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PSYCHIC_SURGE); } + OPPONENT(SPECIES_VOLBEAT) { Ability(ABILITY_ILLUMINATE); Innates(ABILITY_PRANKSTER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_COTTON_SPORE); } + } SCENE { + ABILITY_POPUP(playerRight, ABILITY_PSYCHIC_SURGE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COTTON_SPORE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + } +} + +DOUBLE_BATTLE_TEST("Psychic Terrain protects grounded battlers from priority moves in doubles - Right (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TAPU_LELE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PSYCHIC_SURGE); } + PLAYER(SPECIES_CLAYDOL) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_LEVITATE); } + OPPONENT(SPECIES_VOLBEAT) { Ability(ABILITY_ILLUMINATE); Innates(ABILITY_PRANKSTER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_COTTON_SPORE); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_PSYCHIC_SURGE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COTTON_SPORE, opponentLeft); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + } +} +#endif diff --git a/test/battle/move_effect/purify.c b/test/battle/move_effect/purify.c index 13d1a6e74628..fcb3e4545fdc 100644 --- a/test/battle/move_effect/purify.c +++ b/test/battle/move_effect/purify.c @@ -96,3 +96,63 @@ SINGLE_BATTLE_TEST("Purify doesn't heal HP if the target has Comatose") EXPECT_EQ(player->hp, 50); } } + +#if MAX_MON_TRAITS > 1 +AI_SINGLE_BATTLE_TEST("AI uses Purify to heal an enemy with Guts (Traits)") +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_GUTS; } + PARAMETRIZE { ability = ABILITY_BULLETPROOF; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_URSALUNA) { Ability(ABILITY_PICKUP); Innates(ability); Moves(MOVE_HEADLONG_RUSH, MOVE_CELEBRATE); Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_HEADBUTT, MOVE_PURIFY); } + } WHEN { + if (ability == ABILITY_GUTS) + TURN { EXPECT_MOVE(opponent, MOVE_PURIFY); } + else + TURN { NOT_EXPECT_MOVE(opponent, MOVE_PURIFY); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI does not use Purify to heal an ally with Guts (Traits)") +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_GUTS; } + PARAMETRIZE { ability = ABILITY_BULLETPROOF; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_HEADBUTT, MOVE_PURIFY); } + OPPONENT(SPECIES_URSALUNA) { Ability(ABILITY_PICKUP); Innates(ability); Moves(MOVE_HEADLONG_RUSH); Status1(STATUS1_BURN); } + } WHEN { + if (ability == ABILITY_GUTS) + TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_PURIFY); } + else + TURN { EXPECT_MOVE(opponentLeft, MOVE_PURIFY, target: opponentRight); } + } +} + +SINGLE_BATTLE_TEST("Purify doesn't heal HP if the target has Comatose (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_PURIFY) == EFFECT_PURIFY); + PLAYER(SPECIES_WOBBUFFET) { HP(50); MaxHP(100); } + OPPONENT(SPECIES_KOMALA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMATOSE); } + } WHEN { + TURN { MOVE(player, MOVE_PURIFY); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_PURIFY, player); + MESSAGE("But it failed!"); + NOT HP_BAR(player); + } THEN { + EXPECT_EQ(player->hp, 50); + } +} + +#endif diff --git a/test/battle/move_effect/pursuit.c b/test/battle/move_effect/pursuit.c index cc8c0a72b848..73254f527afe 100644 --- a/test/battle/move_effect/pursuit.c +++ b/test/battle/move_effect/pursuit.c @@ -695,3 +695,302 @@ DOUBLE_BATTLE_TEST("Pursuit user switches out due to Red Card and partner's swit } TO_DO_BATTLE_TEST("Baton Pass doesn't cause Pursuit to increase its power or priority"); + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Pursuit ignores accuracy checks when attacking a switching target (Traits)") +{ + PASSES_RANDOMLY(100, 100, RNG_ACCURACY); + GIVEN { + ASSUME(GetMoveEffect(MOVE_SAND_ATTACK) == EFFECT_ACCURACY_DOWN); + ASSUME(GetMoveEffect(MOVE_HAIL) == EFFECT_HAIL); + PLAYER(SPECIES_GLACEON) { Ability(ABILITY_ICE_BODY); Innates(ABILITY_SNOW_CLOAK); } + PLAYER(SPECIES_ZIGZAGOON); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SAND_ATTACK); MOVE(opponent, MOVE_HAIL); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_PURSUIT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SAND_ATTACK, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HAIL, opponent); + SWITCH_OUT_MESSAGE("Glaceon"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PURSUIT, opponent); + SEND_IN_MESSAGE("Zigzagoon"); + } +} + +DOUBLE_BATTLE_TEST("Pursuit affected by Electrify fails against target with Volt Absorb (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ELECTRIFY) == EFFECT_ELECTRIFY); + PLAYER(SPECIES_LANTURN) { Ability(ABILITY_ILLUMINATE); Innates(ABILITY_VOLT_ABSORB); } + PLAYER(SPECIES_HELIOLISK); + PLAYER(SPECIES_ZIGZAGOON); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_LINOONE); + } WHEN { + TURN { MOVE(playerRight, MOVE_ELECTRIFY, target: opponentLeft); MOVE(playerLeft, MOVE_VOLT_SWITCH, target: opponentLeft); MOVE(opponentLeft, MOVE_PURSUIT, target: playerLeft); SEND_OUT(playerLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIFY, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_VOLT_SWITCH, playerLeft); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_PURSUIT, opponentLeft); + ABILITY_POPUP(playerLeft, ABILITY_VOLT_ABSORB); + SEND_IN_MESSAGE("Zigzagoon"); + } +} + +SINGLE_BATTLE_TEST("Pursuited mon correctly switches out after it got hit and activated ability Tangling Hair (Traits)") +{ + GIVEN { + PLAYER(SPECIES_DUGTRIO_ALOLA) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_TANGLING_HAIR); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_PURSUIT); } + } SCENE { + SWITCH_OUT_MESSAGE("Dugtrio"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PURSUIT, opponent); + ABILITY_POPUP(player, ABILITY_TANGLING_HAIR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wynaut's Speed fell!"); + SEND_IN_MESSAGE("Wobbuffet"); + } +} + +DOUBLE_BATTLE_TEST("Pursuited mon correctly switches out after it got hit and activated ability Tangling Hair - Doubles (Traits)") +{ + GIVEN { + PLAYER(SPECIES_DUGTRIO_ALOLA) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_TANGLING_HAIR); } + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(playerLeft, 2); MOVE(opponentLeft, MOVE_PURSUIT, target: playerLeft); MOVE(opponentRight, MOVE_PURSUIT, target: playerLeft); } + } SCENE { + SWITCH_OUT_MESSAGE("Dugtrio"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PURSUIT, opponentLeft); + ABILITY_POPUP(playerLeft, ABILITY_TANGLING_HAIR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("The opposing Wynaut's Speed fell!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PURSUIT, opponentRight); + ABILITY_POPUP(playerLeft, ABILITY_TANGLING_HAIR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("The opposing Wobbuffet's Speed fell!"); + SEND_IN_MESSAGE("Wobbuffet"); + } +} + +SINGLE_BATTLE_TEST("Pursuited mon correctly switches out after it got hit and activated ability Tangling Hair - Mirror Armor (Traits)") +{ + GIVEN { + PLAYER(SPECIES_DUGTRIO_ALOLA) { Ability(ABILITY_SAND_VEIL); Innates(ABILITY_TANGLING_HAIR); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CORVIKNIGHT) { Ability(ABILITY_PRESSURE); Innates(ABILITY_MIRROR_ARMOR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_PURSUIT); } + } SCENE { + SWITCH_OUT_MESSAGE("Dugtrio"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PURSUIT, opponent); + ABILITY_POPUP(player, ABILITY_TANGLING_HAIR); + ABILITY_POPUP(opponent, ABILITY_MIRROR_ARMOR); + SEND_IN_MESSAGE("Wobbuffet"); + } +} + +DOUBLE_BATTLE_TEST("Pursuited mon correctly switches out after it got hit and activated ability Cotton Down (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ELDEGOSS) { Ability(ABILITY_REGENERATOR); Innates(ABILITY_COTTON_DOWN); } + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(playerLeft, 2); MOVE(opponentLeft, MOVE_PURSUIT, target: playerLeft); MOVE(opponentRight, MOVE_PURSUIT, target: playerLeft); } + } SCENE { + SWITCH_OUT_MESSAGE("Eldegoss"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PURSUIT, opponentLeft); + ABILITY_POPUP(playerLeft, ABILITY_COTTON_DOWN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("The opposing Wynaut's Speed fell!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Wobbuffet's Speed fell!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("The opposing Wobbuffet's Speed fell!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PURSUIT, opponentRight); + ABILITY_POPUP(playerLeft, ABILITY_COTTON_DOWN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("The opposing Wynaut's Speed fell!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Wobbuffet's Speed fell!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("The opposing Wobbuffet's Speed fell!"); + SEND_IN_MESSAGE("Wobbuffet"); + } +} + +SINGLE_BATTLE_TEST("Pursuit doesn't cause mon with Emergency Exit to switch twice (Traits)") +{ + GIVEN { + PLAYER(SPECIES_GOLISOPOD) { HP(101); MaxHP(200); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); } + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_VOLTORB); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_PURSUIT); SEND_OUT(player, 2); } + } SCENE { + SWITCH_OUT_MESSAGE("Golisopod"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PURSUIT, opponent); + ABILITY_POPUP(player, ABILITY_EMERGENCY_EXIT); + SEND_IN_MESSAGE("Voltorb"); + } THEN { + EXPECT_EQ(player->species, SPECIES_VOLTORB); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Pursuit attacks a switching foe and takes Life Orb damage (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LIFE_ORB].holdEffect == HOLD_EFFECT_LIFE_ORB); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ZIGZAGOON); + OPPONENT(SPECIES_WYNAUT) { Items(ITEM_PECHA_BERRY, ITEM_LIFE_ORB); } + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_PURSUIT); } + } SCENE { + SWITCH_OUT_MESSAGE("Wobbuffet"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PURSUIT, opponent); + HP_BAR(opponent); + SEND_IN_MESSAGE("Zigzagoon"); + } +} + +SINGLE_BATTLE_TEST("Pursuit user mega evolves before attacking a switching foe and hits twice if user has Parental Bond (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ZIGZAGOON); + OPPONENT(SPECIES_KANGASKHAN) { Items(ITEM_PECHA_BERRY, ITEM_KANGASKHANITE); } + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_PURSUIT, gimmick: GIMMICK_MEGA); } + } SCENE { + SWITCH_OUT_MESSAGE("Wobbuffet"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PURSUIT, opponent); + HP_BAR(player); + HP_BAR(player); + SEND_IN_MESSAGE("Zigzagoon"); + } +} + +DOUBLE_BATTLE_TEST("Pursuit user mega evolves before attacking a switching foe and others mega evolve after switch (Multi)") +{ + GIVEN { + PLAYER(SPECIES_CHARIZARD) { Items(ITEM_PECHA_BERRY, ITEM_CHARIZARDITE_X); } + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ZIGZAGOON); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KANGASKHAN) { Items(ITEM_PECHA_BERRY, ITEM_KANGASKHANITE); } + } WHEN { + TURN { SWITCH(playerRight, 2); MOVE(opponentRight, MOVE_PURSUIT, gimmick: GIMMICK_MEGA, target: playerRight); MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + } SCENE { + SWITCH_OUT_MESSAGE("Wobbuffet"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PURSUIT, opponentRight); + HP_BAR(playerRight); + HP_BAR(playerRight); + SEND_IN_MESSAGE("Zigzagoon"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft); + } +} + +// Checked so that Pursuit has only 1 PP and it forces the player to use Struggle. +SINGLE_BATTLE_TEST("Pursuit becomes a locked move after being used on switch-out while holding a Choice Item (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_CHOICE_BAND].holdEffect == HOLD_EFFECT_CHOICE_BAND); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_CHOICE_BAND); MovesWithPP({MOVE_PURSUIT, 1}, {MOVE_CELEBRATE, 10}, {MOVE_WATER_GUN, 10}, {MOVE_SCRATCH, 10}); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(opponent, 1); MOVE(player, MOVE_PURSUIT); } + TURN { FORCED_MOVE(player); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PURSUIT, player); + HP_BAR(opponent); + MESSAGE("2 sent out Wobbuffet!"); + + MESSAGE("Wobbuffet used Struggle!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + } +} + +SINGLE_BATTLE_TEST("Pursuit user gets forced out by Red Card and target still switches out (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_RED_CARD].holdEffect == HOLD_EFFECT_RED_CARD); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + PLAYER(SPECIES_VOLTORB); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_VOLTORB); + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_PURSUIT); } + } SCENE { + SWITCH_OUT_MESSAGE("Wobbuffet"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PURSUIT, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("The opposing Voltorb was dragged out!"); + SEND_IN_MESSAGE("Voltorb"); + } THEN { + EXPECT_EQ(player->species, SPECIES_VOLTORB); + EXPECT_EQ(opponent->species, SPECIES_VOLTORB); + } +} + +SINGLE_BATTLE_TEST("Pursuit user faints to Life Orb and target still switches out (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LIFE_ORB].holdEffect == HOLD_EFFECT_LIFE_ORB); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_VOLTORB); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LIFE_ORB); HP(1); } + OPPONENT(SPECIES_VOLTORB); + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_PURSUIT); SEND_OUT(opponent, 1); } + } SCENE { + SWITCH_OUT_MESSAGE("Wobbuffet"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PURSUIT, opponent); + HP_BAR(opponent); + MESSAGE("The opposing Wobbuffet fainted!"); + SEND_IN_MESSAGE("Voltorb"); + } THEN { + EXPECT_EQ(player->species, SPECIES_VOLTORB); + EXPECT_EQ(opponent->species, SPECIES_VOLTORB); + } +} + +DOUBLE_BATTLE_TEST("Pursuit user switches out due to Red Card and partner's switch is cancelled if switching to same Pokémon (Multi)") +{ + GIVEN { + ASSUME(GetItemHoldEffect(ITEM_RED_CARD) == HOLD_EFFECT_RED_CARD); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_ARCEUS); + OPPONENT(SPECIES_WYNAUT) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ARCEUS); + } WHEN { + TURN { SWITCH(opponentLeft, 2); SWITCH(playerRight, 2); MOVE(playerLeft, MOVE_PURSUIT, target: opponentLeft); } + } THEN { + // playerLeft switches to Arceus + EXPECT_EQ(playerLeft->species, SPECIES_ARCEUS); + // playerRight has their switch cancelled + EXPECT_EQ(playerRight->species, SPECIES_WYNAUT); + } +} +#endif diff --git a/test/battle/move_effect/quash.c b/test/battle/move_effect/quash.c index 0fc2fbd25d36..bfb81a07b239 100644 --- a/test/battle/move_effect/quash.c +++ b/test/battle/move_effect/quash.c @@ -131,3 +131,99 @@ DOUBLE_BATTLE_TEST("Quash-affected mon that acted early via After You is not aff ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); // this is the relevant part, testing if quash affected battler becomes last to move causing playerRight to not move } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Quash-affected target will move last in the priority bracket (Traits)") +{ + GIVEN { + PLAYER(SPECIES_VOLBEAT) { Speed(10); Ability(ABILITY_ILLUMINATE); Innates(ABILITY_PRANKSTER); } + PLAYER(SPECIES_WOBBUFFET) { Speed(30); } + OPPONENT(SPECIES_TORCHIC) { Speed(20); } + OPPONENT(SPECIES_TREECKO) { Speed(40); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_QUASH, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUASH, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Quash is not affected by dynamic speed (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_RECALC_TURN_AFTER_ACTIONS, GEN_8); + ASSUME(GetMoveEffect(MOVE_TAILWIND) == EFFECT_TAILWIND); + PLAYER(SPECIES_VOLBEAT) { Speed(10); Ability(ABILITY_ILLUMINATE); Innates(ABILITY_PRANKSTER); } + PLAYER(SPECIES_WOBBUFFET) { Speed(30); } + OPPONENT(SPECIES_TORCHIC) { Speed(50); } + OPPONENT(SPECIES_TREECKO) { Speed(40); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_QUASH, target: opponentLeft); + MOVE(opponentRight, MOVE_TAILWIND); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUASH, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAILWIND, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft); + } +} + +DOUBLE_BATTLE_TEST("Quash-affected targets move from fastest to slowest (Gen 8+) or from first affected battler to last (Gen 7-) (Traits)") +{ + u32 speedLeft, speedRight; + + PARAMETRIZE { speedLeft = 60; speedRight = 50; } + PARAMETRIZE { speedLeft = 50; speedRight = 60; } + GIVEN { + PLAYER(SPECIES_VOLBEAT) { Speed(10); Ability(ABILITY_ILLUMINATE); Innates(ABILITY_PRANKSTER); } + PLAYER(SPECIES_WOBBUFFET) { Speed(70); } + OPPONENT(SPECIES_TORCHIC) { Speed(speedLeft); } + OPPONENT(SPECIES_TREECKO) { Speed(speedRight); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_QUASH, target: opponentRight); + MOVE(playerRight, MOVE_QUASH, target: opponentLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUASH, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUASH, playerRight); + if (B_QUASH_TURN_ORDER < GEN_8) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft); + } + else if (speedLeft > speedRight) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight); + } + else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft); + } + } +} + +DOUBLE_BATTLE_TEST("Quash-affected mon that acted early via After You is not affected by dynamic speed (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_RECALC_TURN_AFTER_ACTIONS, GEN_8); + ASSUME(GetMoveEffect(MOVE_TAILWIND) == EFFECT_TAILWIND); + ASSUME(GetMoveEffect(MOVE_AFTER_YOU) == EFFECT_AFTER_YOU); + PLAYER(SPECIES_VOLBEAT) { Speed(20); Ability(ABILITY_ILLUMINATE); Innates(ABILITY_PRANKSTER); } + PLAYER(SPECIES_WOBBUFFET) { Speed(30); } + OPPONENT(SPECIES_TORCHIC) { Speed(10); } + OPPONENT(SPECIES_TREECKO) { Speed(40); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_QUASH, target: opponentLeft); + MOVE(opponentRight, MOVE_AFTER_YOU, target: opponentLeft); + MOVE(opponentLeft, MOVE_TAILWIND); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUASH, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_AFTER_YOU, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAILWIND, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); // this is the relevant part, testing if quash affected battler becomes last to move causing playerRight to not move + } +} +#endif diff --git a/test/battle/move_effect/rage_fist.c b/test/battle/move_effect/rage_fist.c index 7a0b6bc3220f..733c76f01926 100644 --- a/test/battle/move_effect/rage_fist.c +++ b/test/battle/move_effect/rage_fist.c @@ -395,3 +395,59 @@ SINGLE_BATTLE_TEST("Rage Fist counter will be updated correctly after absorb mov EXPECT_MUL_EQ(timesGotHit[0], Q_4_12(2.0), timesGotHit[1]); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Rage Fist base power is increased if Disguise breaks (Gen7) (Traits)") +{ + s16 timesGotHit[2]; + u16 species = SPECIES_NONE; + + PARAMETRIZE { species = SPECIES_MIMIKYU_DISGUISED; } + PARAMETRIZE { species = SPECIES_MIMIKYU_TOTEM_DISGUISED; } + + GIVEN { + WITH_CONFIG(CONFIG_DISGUISE_HP_LOSS, GEN_7); + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DISGUISE); } + OPPONENT(SPECIES_REGIROCK); + } WHEN { + TURN { MOVE(player, MOVE_RAGE_FIST); MOVE(opponent, MOVE_ROCK_THROW); } + TURN { MOVE(player, MOVE_RAGE_FIST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAGE_FIST, player); + HP_BAR(opponent, captureDamage: ×GotHit[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROCK_THROW, opponent); + ABILITY_POPUP(player, ABILITY_DISGUISE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAGE_FIST, player); + HP_BAR(opponent, captureDamage: ×GotHit[1]); + } THEN { + EXPECT_MUL_EQ(timesGotHit[0], Q_4_12(2.0), timesGotHit[1]); + } +} + +SINGLE_BATTLE_TEST("Rage Fist base power is increased if Disguise breaks (Gen8+) (Traits)") +{ + s16 timesGotHit[2]; + u16 species = SPECIES_NONE; + + PARAMETRIZE { species = SPECIES_MIMIKYU_DISGUISED; } + PARAMETRIZE { species = SPECIES_MIMIKYU_TOTEM_DISGUISED; } + + GIVEN { + WITH_CONFIG(CONFIG_DISGUISE_HP_LOSS, GEN_8); + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DISGUISE); } + OPPONENT(SPECIES_REGIROCK); + } WHEN { + TURN { MOVE(player, MOVE_RAGE_FIST); MOVE(opponent, MOVE_ROCK_THROW); } + TURN { MOVE(player, MOVE_RAGE_FIST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAGE_FIST, player); + HP_BAR(opponent, captureDamage: ×GotHit[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROCK_THROW, opponent); + ABILITY_POPUP(player, ABILITY_DISGUISE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAGE_FIST, player); + HP_BAR(opponent, captureDamage: ×GotHit[1]); + } THEN { + EXPECT_MUL_EQ(timesGotHit[0], Q_4_12(2.0), timesGotHit[1]); + } +} +#endif diff --git a/test/battle/move_effect/raging_bull.c b/test/battle/move_effect/raging_bull.c index 056a5093a8d7..10142decb8e6 100644 --- a/test/battle/move_effect/raging_bull.c +++ b/test/battle/move_effect/raging_bull.c @@ -157,3 +157,30 @@ SINGLE_BATTLE_TEST("Move Raging Bull changes it's type depending on the Tauros F MESSAGE("It's not very effective…"); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Raging Bull doesn't remove Light Screen, Reflect and Aurora Veil if it misses (Multi)") +{ + u16 move; + + PARAMETRIZE { move = MOVE_LIGHT_SCREEN; } + PARAMETRIZE { move = MOVE_REFLECT; } + PARAMETRIZE { move = MOVE_AURORA_VEIL; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_BRIGHT_POWDER); } + } WHEN { + TURN { MOVE(player, MOVE_SNOWSCAPE); MOVE(opponent, move); } + TURN { MOVE(player, MOVE_RAGING_BULL, hit: FALSE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SNOWSCAPE, player); + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAGING_BULL, player); + MESSAGE("The wall shattered!"); + HP_BAR(opponent); + } + } +} +#endif diff --git a/test/battle/move_effect/rapid_spin.c b/test/battle/move_effect/rapid_spin.c index 852b4d69a784..60bf32d54d5c 100644 --- a/test/battle/move_effect/rapid_spin.c +++ b/test/battle/move_effect/rapid_spin.c @@ -92,3 +92,22 @@ SINGLE_BATTLE_TEST("Rapid Spin blows away all hazards") EXPECT_EQ(gBattleStruct->hazardsQueue[0][5], HAZARDS_NONE); } } + +SINGLE_BATTLE_TEST("Rapid Spin activates after Toxic Debris") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_RAPID_SPIN) == EFFECT_RAPID_SPIN); + #if B_SPEED_BUFFING_RAPID_SPIN >= GEN_8 + ASSUME(MoveHasAdditionalEffectSelf(MOVE_RAPID_SPIN, MOVE_EFFECT_SPD_PLUS_1) == TRUE); + #endif + PLAYER(SPECIES_GLIMMORA) { Ability(ABILITY_CORROSION); Innates(ABILITY_TOXIC_DEBRIS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_RAPID_SPIN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAPID_SPIN, opponent); + ABILITY_POPUP(player, ABILITY_TOXIC_DEBRIS); + MESSAGE("Poison spikes were scattered on the ground all around the opposing team!"); + MESSAGE("The opposing Wobbuffet blew away Toxic Spikes!"); + } +} diff --git a/test/battle/move_effect/recoil_if_miss.c b/test/battle/move_effect/recoil_if_miss.c index 335120a5e5c4..793b48b49c0a 100644 --- a/test/battle/move_effect/recoil_if_miss.c +++ b/test/battle/move_effect/recoil_if_miss.c @@ -154,3 +154,43 @@ SINGLE_BATTLE_TEST("Recoil if miss: Disguise doesn't prevent crash damage from J } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Recoil if miss: Supercell Slam causes recoil if it is absorbed (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIKACHU) { Ability(ABILITY_STATIC); Innates(ABILITY_LIGHTNING_ROD); } + } WHEN { + TURN { MOVE(player, MOVE_SUPERCELL_SLAM); } + } SCENE { + s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); + ABILITY_POPUP(opponent, ABILITY_LIGHTNING_ROD); + MESSAGE("Wobbuffet kept going and crashed!"); + HP_BAR(player, damage: maxHP / 2); + } +} + +SINGLE_BATTLE_TEST("Recoil if miss: Disguise doesn't prevent crash damage from Jump Kick into ghost types (Traits)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_EARLY_BIRD; } + PARAMETRIZE { ability = ABILITY_SCRAPPY; } + + GIVEN { + PLAYER(SPECIES_KANGASKHAN) { Ability(ABILITY_INNER_FOCUS); Innates(ability); }; + OPPONENT(SPECIES_MIMIKYU_DISGUISED) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DISGUISE); } + } WHEN { + TURN { MOVE(player, MOVE_JUMP_KICK); } + } SCENE { + s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP); + MESSAGE("Kangaskhan used Jump Kick!"); + if (ability == ABILITY_SCRAPPY) { + NONE_OF { + MESSAGE("Kangaskhan kept going and crashed!"); + HP_BAR(player, damage: maxHP / 2); + } + } + } +} +#endif diff --git a/test/battle/move_effect/rest.c b/test/battle/move_effect/rest.c index af1fb19c8c13..705f9c057bbc 100644 --- a/test/battle/move_effect/rest.c +++ b/test/battle/move_effect/rest.c @@ -103,3 +103,36 @@ DOUBLE_BATTLE_TEST("Rest doesn't fail if the user is protected by Flower Veil") } TO_DO_BATTLE_TEST("TODO: Write Rest (Move Effect) test titles") + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Rest fails if the user is protected by Shields Down (Traits)") +{ + GIVEN { + PLAYER(SPECIES_MINIOR_METEOR) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_SHIELDS_DOWN); HP(299); MaxHP(300); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_REST); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, player); + } THEN { + EXPECT(!(player->status1 & STATUS1_SLEEP)); + } +} + +DOUBLE_BATTLE_TEST("Rest doesn't fail if the user is protected by Flower Veil (Traits)") +{ + GIVEN { + ASSUME(GetSpeciesType(SPECIES_CHIKORITA, 0) == TYPE_GRASS || GetSpeciesType(SPECIES_CHIKORITA, 1) == TYPE_GRASS); + PLAYER(SPECIES_CHIKORITA) { HP(1); } + PLAYER(SPECIES_FLORGES) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_FLOWER_VEIL); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_REST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, playerLeft); + } THEN { + EXPECT(playerLeft->status1 & STATUS1_SLEEP); + } +} +#endif diff --git a/test/battle/move_effect/retaliate.c b/test/battle/move_effect/retaliate.c index 850340858c4b..0a7c14fdf60c 100644 --- a/test/battle/move_effect/retaliate.c +++ b/test/battle/move_effect/retaliate.c @@ -138,3 +138,80 @@ SINGLE_BATTLE_TEST("Retaliate works with self-inflicted fainting") EXPECT_MUL_EQ(damage[1], Q_4_12(2.0), damage[0]); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Retaliate works with passive damage (Traits)") +{ + s16 damage[2]; + u32 move; + u32 move2 = MOVE_CELEBRATE; + struct BattlePokemon *moveTarget = playerLeft; + PARAMETRIZE { move = MOVE_TOXIC; moveTarget = playerLeft; } + PARAMETRIZE { move = MOVE_POISON_POWDER; moveTarget = playerLeft; } + PARAMETRIZE { move = MOVE_WILL_O_WISP; moveTarget = playerLeft; } + #if B_USE_FROSTBITE == TRUE + PARAMETRIZE { move = MOVE_ICE_BEAM; moveTarget = playerLeft; } + #endif + PARAMETRIZE { move = MOVE_SANDSTORM; moveTarget = playerLeft; } + PARAMETRIZE { move = MOVE_HAIL; moveTarget = playerLeft; } + PARAMETRIZE { move = MOVE_LEECH_SEED; moveTarget = playerLeft; } + PARAMETRIZE { move = MOVE_MAGMA_STORM; moveTarget = playerLeft; } + PARAMETRIZE { move = MOVE_FLAME_BURST; moveTarget = playerRight; } + PARAMETRIZE { move = MOVE_FIRE_PLEDGE; moveTarget = playerRight; move2 = MOVE_GRASS_PLEDGE; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_TOXIC) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_TOXIC) == MOVE_EFFECT_TOXIC); + ASSUME(GetMoveEffect(MOVE_POISON_POWDER) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_POISON_POWDER) == MOVE_EFFECT_POISON); + ASSUME(GetMoveEffect(MOVE_WILL_O_WISP) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_WILL_O_WISP) == MOVE_EFFECT_BURN); + #if B_USE_FROSTBITE == TRUE + ASSUME(GetMoveAdditionalEffectById(MOVE_ICE_BEAM, 0)->moveEffect == MOVE_EFFECT_FREEZE_OR_FROSTBITE); + #endif + ASSUME(GetMoveEffect(MOVE_SANDSTORM) == EFFECT_SANDSTORM); + ASSUME(GetMoveEffect(MOVE_HAIL) == EFFECT_HAIL); + ASSUME(GetMoveEffect(MOVE_LEECH_SEED) == EFFECT_LEECH_SEED); + ASSUME(GetMoveAdditionalEffectById(MOVE_MAGMA_STORM, 0)->moveEffect == MOVE_EFFECT_WRAP); + ASSUME(GetMoveAdditionalEffectById(MOVE_FLAME_BURST, 0)->moveEffect == MOVE_EFFECT_FLAME_BURST); + PLAYER(SPECIES_WYNAUT) { Ability(ABILITY_TELEPATHY); Innates(ABILITY_SHADOW_TAG); HP(18); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Innates(ABILITY_SHADOW_TAG); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CLEFABLE) { Ability(ABILITY_FRIEND_GUARD); Innates(ABILITY_MAGIC_GUARD); Level(1); } + OPPONENT(SPECIES_CLEFABLE) { Ability(ABILITY_FRIEND_GUARD); Innates(ABILITY_MAGIC_GUARD); } + } WHEN { + TURN { MOVE(opponentRight, move2, target: moveTarget); MOVE(opponentLeft, move, target: moveTarget); MOVE(playerLeft, MOVE_CELEBRATE); SEND_OUT(playerLeft, 2); } + TURN { MOVE(opponentRight, MOVE_CELEBRATE, target: moveTarget); MOVE(playerLeft, MOVE_RETALIATE, target: opponentRight); } + TURN { MOVE(opponentRight, MOVE_CELEBRATE, target: moveTarget); MOVE(playerLeft, MOVE_RETALIATE, target: opponentRight); } + } SCENE { + if (move != MOVE_FLAME_BURST) + MESSAGE("Wynaut used Celebrate!"); + HP_BAR(opponentRight, captureDamage: &damage[0]); + HP_BAR(opponentRight, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[1], Q_4_12(2), damage[0]); + } +} + +SINGLE_BATTLE_TEST("Retaliate works with Perish Song (Traits)") +{ + s16 damage[2]; + GIVEN { + ASSUME(GetMoveEffect(MOVE_PERISH_SONG) == EFFECT_PERISH_SONG); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KOMMO_O) { Ability(ABILITY_BULLETPROOF); Innates(ABILITY_SOUNDPROOF); } + } WHEN { + TURN { MOVE(opponent, MOVE_PERISH_SONG); } + TURN { MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(opponent, MOVE_CELEBRATE); SEND_OUT(player, 1); } + TURN { MOVE(player, MOVE_RETALIATE); } + TURN { MOVE(player, MOVE_RETALIATE); } + } SCENE { + HP_BAR(opponent, captureDamage: &damage[0]); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[1], Q_4_12(2), damage[0]); + } +} +#endif diff --git a/test/battle/move_effect/revelation_dance.c b/test/battle/move_effect/revelation_dance.c index 96106e192341..80f7e1c2c515 100644 --- a/test/battle/move_effect/revelation_dance.c +++ b/test/battle/move_effect/revelation_dance.c @@ -147,3 +147,42 @@ SINGLE_BATTLE_TEST("Revelation Dance becomes Normal type if used by a Typeless P } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Revelation Dance becomes Normal type if used by a Typeless Pokemon due to Roost (Traits)") +{ + u16 speciesOpponent; + + PARAMETRIZE { speciesOpponent = SPECIES_SABLEYE; } + PARAMETRIZE { speciesOpponent = SPECIES_AGGRON; } + + ASSUME(B_ROOST_PURE_FLYING >= GEN_5); + + GIVEN { + PLAYER(SPECIES_ORICORIO_BAILE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DANCER); } + OPPONENT(speciesOpponent); + } WHEN { + TURN { MOVE(player, MOVE_BURN_UP); MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_ROOST); MOVE(opponent, MOVE_REVELATION_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BURN_UP, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_REVELATION_DANCE, opponent); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_DANCER); + if (speciesOpponent == SPECIES_AGGRON) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_REVELATION_DANCE, player); + HP_BAR(opponent); + MESSAGE("It's not very effective…"); + } + else { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_REVELATION_DANCE, player); + HP_BAR(opponent); + MESSAGE("It's not very effective…"); + } + } + } +} +#endif diff --git a/test/battle/move_effect/revival_blessing.c b/test/battle/move_effect/revival_blessing.c index add15a7c3d5e..db6761d9ca16 100644 --- a/test/battle/move_effect/revival_blessing.c +++ b/test/battle/move_effect/revival_blessing.c @@ -133,3 +133,34 @@ DOUBLE_BATTLE_TEST("Revival Blessing correctly updates battler absent flags") MESSAGE("It doesn't affect the opposing Starly…"); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Revival Blessing correctly updates battler absent flags (Traits)") +{ + GIVEN { + PLAYER(SPECIES_SALAMENCE) { Level(40); } + PLAYER(SPECIES_PIDGEOT) { Level(40); } + OPPONENT(SPECIES_GEODUDE) { Level(5); Ability(ABILITY_SAND_VEIL); Innates(ABILITY_ROCK_HEAD); } + OPPONENT(SPECIES_STARLY) { Level(5); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_EARTHQUAKE); + MOVE(opponentRight, MOVE_REVIVAL_BLESSING, partyIndex: 0); } + TURN { MOVE(playerLeft, MOVE_EARTHQUAKE); } + } SCENE { + // Turn 1 + MESSAGE("Salamence used Earthquake!"); + HP_BAR(opponentLeft); + MESSAGE("The opposing Geodude fainted!"); + MESSAGE("It doesn't affect Pidgeot…"); + MESSAGE("It doesn't affect the opposing Starly…"); + MESSAGE("The opposing Starly used Revival Blessing!"); + MESSAGE("Geodude was revived and is ready to fight again!"); // Should have prefix but it doesn't currently. + // Turn 2 + MESSAGE("Salamence used Earthquake!"); + HP_BAR(opponentLeft); + MESSAGE("The opposing Geodude fainted!"); + MESSAGE("It doesn't affect Pidgeot…"); + MESSAGE("It doesn't affect the opposing Starly…"); + } +} +#endif diff --git a/test/battle/move_effect/roar.c b/test/battle/move_effect/roar.c index 5c46e0cdf4c9..caf657ab9e7c 100644 --- a/test/battle/move_effect/roar.c +++ b/test/battle/move_effect/roar.c @@ -105,3 +105,40 @@ SINGLE_BATTLE_TEST("Roar fails to switch out target with Suction Cups") NOT MESSAGE("The opposing Charmander was dragged out!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Roar fails against target with Guard Dog (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_OKIDOGI) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_GUARD_DOG); } + OPPONENT(SPECIES_CHARMANDER); + } WHEN { + TURN { MOVE(player, MOVE_ROAR); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROAR, player); + MESSAGE("The opposing Charmander was dragged out!"); + } + MESSAGE("Wobbuffet used Roar!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Roar fails to switch out target with Suction Cups (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_OCTILLERY) { Ability(ABILITY_SNIPER); Innates(ABILITY_SUCTION_CUPS); } + OPPONENT(SPECIES_CHARMANDER); + } WHEN { + TURN { MOVE(player, MOVE_ROAR); } + } SCENE { + MESSAGE("Wobbuffet used Roar!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_ROAR, player); + ABILITY_POPUP(opponent, ABILITY_SUCTION_CUPS); + MESSAGE("The opposing Octillery anchors itself with Suction Cups!"); + NOT MESSAGE("The opposing Charmander was dragged out!"); + } +} +#endif diff --git a/test/battle/move_effect/roost.c b/test/battle/move_effect/roost.c index 8748508c9b2e..c0975991c838 100644 --- a/test/battle/move_effect/roost.c +++ b/test/battle/move_effect/roost.c @@ -442,3 +442,135 @@ SINGLE_BATTLE_TEST("Roost does not suppress the ungrounded effect of Telekinesis // Transform does not copy the Roost "status" either. // Probably better as a Transform test. TO_DO_BATTLE_TEST("Roost's suppression does not prevent others who are Transforming into the user from copying its Flying-type"); + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Roost suppresses the user's Flying-typing this turn, then restores it at the end of the turn (Traits)") +{ + GIVEN { + ASSUME(GetSpeciesType(SPECIES_SKARMORY, 0) == TYPE_STEEL); + ASSUME(GetSpeciesType(SPECIES_SKARMORY, 1) == TYPE_FLYING); + PLAYER(SPECIES_SKARMORY) { HP(50); MaxHP(100); Ability(ABILITY_STURDY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROOST); MOVE(opponent, MOVE_EARTHQUAKE); } + TURN { MOVE(opponent, MOVE_EARTHQUAKE); } + } SCENE { + // Turn 1: EQ hits when Roosted + MESSAGE("Skarmory used Roost!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + MESSAGE("Skarmory's HP was restored."); + MESSAGE("The opposing Wobbuffet used Earthquake!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, opponent); + MESSAGE("It's super effective!"); + // Turn 2: EQ has no effect because Roost expired + MESSAGE("The opposing Wobbuffet used Earthquake!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, opponent); + MESSAGE("It doesn't affect Skarmory…"); + NOT HP_BAR(player); + } +} + +// Tested in ORAS +DOUBLE_BATTLE_TEST("Roost suppresses the user's not-yet-aquired Flying-type this turn (Traits)") +{ + GIVEN { + ASSUME(GetSpeciesType(SPECIES_KECLEON, 0) != TYPE_FLYING); + ASSUME(GetSpeciesType(SPECIES_KECLEON, 1) != TYPE_FLYING); + PLAYER(SPECIES_KECLEON) { Speed(40); HP(150); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COLOR_CHANGE); } + PLAYER(SPECIES_WOBBUFFET) { Speed(10); } + OPPONENT(SPECIES_PIDGEY) { Speed(30); } + OPPONENT(SPECIES_SANDSHREW) { Speed(20); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_ROOST); + MOVE(opponentLeft, MOVE_GUST, target: playerLeft); + MOVE(opponentRight, MOVE_EARTHQUAKE, target: playerLeft); } + } SCENE { + MESSAGE("Kecleon used Roost!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, playerLeft); + MESSAGE("Kecleon's HP was restored."); + MESSAGE("The opposing Pidgey used Gust!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GUST, opponentLeft); + MESSAGE("Kecleon's Color Change made it the Flying type!"); + MESSAGE("The opposing Sandshrew used Earthquake!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, opponentRight); + MESSAGE("Kecleon's Color Change made it the Ground type!"); + } +} + +// Tested in ORAS +SINGLE_BATTLE_TEST("Roost prevents a Flying-type user from being protected by Delta Stream (Traits)") +{ + GIVEN { + ASSUME(GetSpeciesType(SPECIES_RAYQUAZA, 1) == TYPE_FLYING); + PLAYER(SPECIES_RAYQUAZA) { HP(1); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DELTA_STREAM); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROOST); MOVE(opponent, MOVE_ICE_BEAM); } + } SCENE { + MESSAGE("Rayquaza used Roost!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + MESSAGE("Rayquaza's HP was restored."); + MESSAGE("The opposing Wobbuffet used Ice Beam!"); + NOT MESSAGE("The mysterious strong winds weakened the attack!"); + } +} + +// https://www.smogon.com/forums/threads/sword-shield-battle-mechanics-research.3655528/page-64#post-9244179 +SINGLE_BATTLE_TEST("Roost's effect is lifted after Grassy Terrain's healing (Traits)") +{ + GIVEN { + ASSUME(GetSpeciesType(SPECIES_SWELLOW, 0) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_SWELLOW, 1) == TYPE_FLYING); + PLAYER(SPECIES_SWELLOW) { HP(1); Ability(ABILITY_GUTS); Innates(ABILITY_GRASSY_SURGE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROOST); } + } SCENE { + MESSAGE("Swellow used Roost!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + MESSAGE("Swellow's HP was restored."); + MESSAGE("Swellow is healed by the grassy terrain!"); + HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Roost does not suppress the ungrounded effect of Levitate (Traits)") +{ + GIVEN { + PLAYER(SPECIES_FLYGON) { HP(1); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_LEVITATE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROOST); MOVE(opponent, MOVE_EARTHQUAKE); } + } SCENE { + MESSAGE("Flygon used Roost!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + MESSAGE("Flygon's HP was restored."); + MESSAGE("The opposing Wobbuffet used Earthquake!"); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, opponent); + HP_BAR(player); + } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Roost does not suppress the ungrounded effect of Air Balloon (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Items(ITEM_PECHA_BERRY, ITEM_AIR_BALLOON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROOST); MOVE(opponent, MOVE_EARTHQUAKE); } + } SCENE { + MESSAGE("Wobbuffet used Roost!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + MESSAGE("Wobbuffet's HP was restored."); + MESSAGE("The opposing Wobbuffet used Earthquake!"); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, opponent); + HP_BAR(player); + } + } +} +#endif diff --git a/test/battle/move_effect/round.c b/test/battle/move_effect/round.c index 985822d46d84..0187888f3264 100644 --- a/test/battle/move_effect/round.c +++ b/test/battle/move_effect/round.c @@ -112,3 +112,49 @@ DOUBLE_BATTLE_TEST("Round causes opposing Pokémon to use Round immediately") ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft); } } + +#if MAX_MON_ITEMS > 1 +DOUBLE_BATTLE_TEST("Round allows other battlers which also selected the moves to immediately use the move, ignoring turn order (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LAGGING_TAIL].holdEffect == HOLD_EFFECT_LAGGING_TAIL); + ASSUME(GetMoveAdditionalEffectById(MOVE_IRON_HEAD, 0)->moveEffect == MOVE_EFFECT_FLINCH); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LAGGING_TAIL); } + } WHEN { + TURN { + MOVE(playerRight, MOVE_CELEBRATE); + MOVE(opponentLeft, MOVE_ROUND, target: playerLeft); + MOVE(playerLeft, MOVE_IRON_HEAD, target: opponentRight); + MOVE(opponentRight, MOVE_ROUND, target: playerLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponentLeft); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_IRON_HEAD, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_IRON_HEAD, playerLeft); + } +} + +DOUBLE_BATTLE_TEST("Round causes opposing Pokémon to use Round immediately (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LAGGING_TAIL].holdEffect == HOLD_EFFECT_LAGGING_TAIL); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LAGGING_TAIL); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_CELEBRATE); MOVE(playerRight, MOVE_ROUND, target: opponentLeft); MOVE(playerLeft, MOVE_CELEBRATE, target: opponentRight); MOVE(opponentRight, MOVE_ROUND, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, playerRight); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft); + } +} +#endif diff --git a/test/battle/move_effect/semi_invulnerable.c b/test/battle/move_effect/semi_invulnerable.c index 66086ca458cc..a49ec301c472 100644 --- a/test/battle/move_effect/semi_invulnerable.c +++ b/test/battle/move_effect/semi_invulnerable.c @@ -248,3 +248,109 @@ SINGLE_BATTLE_TEST("Semi-invulnerable moves apply a status that won't block cert HP_BAR(player); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Semi-invulnerable moves don't need to charge with Power Herb (Multi)") +{ + u16 move; + + PARAMETRIZE { move = MOVE_FLY; } + PARAMETRIZE { move = MOVE_DIG; } + PARAMETRIZE { move = MOVE_BOUNCE; } + PARAMETRIZE { move = MOVE_DIVE; } + PARAMETRIZE { move = MOVE_PHANTOM_FORCE; } + PARAMETRIZE { move = MOVE_SHADOW_FORCE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_POWER_HERB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + // Charging turn + if (B_UPDATED_MOVE_DATA >= GEN_5) + { + switch (move) + { + case MOVE_FLY: + NOT MESSAGE("Wobbuffet flew up high!"); + MESSAGE("Wobbuffet used Fly!"); + break; + case MOVE_DIG: + NOT MESSAGE("Wobbuffet dug a hole!"); + MESSAGE("Wobbuffet used Dig!"); + break; + case MOVE_BOUNCE: + NOT MESSAGE("Wobbuffet sprang up!"); + MESSAGE("Wobbuffet used Bounce!"); + break; + case MOVE_DIVE: + NOT MESSAGE("Wobbuffet hid underwater!"); + MESSAGE("Wobbuffet used Dive!"); + break; + case MOVE_PHANTOM_FORCE: + NOT MESSAGE("Wobbuffet vanished instantly!"); + MESSAGE("Wobbuffet used Phantom Force!"); + break; + case MOVE_SHADOW_FORCE: + NOT MESSAGE("Wobbuffet vanished instantly!"); + MESSAGE("Wobbuffet used Shadow Force!"); + break; + } + } else { + ANIMATION(ANIM_TYPE_MOVE, move, player); + } + if (B_UPDATED_MOVE_DATA < GEN_5) + { + switch (move) + { + case MOVE_FLY: + MESSAGE("Wobbuffet flew up high!"); + break; + case MOVE_DIG: + MESSAGE("Wobbuffet dug a hole!"); + break; + case MOVE_BOUNCE: + MESSAGE("Wobbuffet sprang up!"); + break; + case MOVE_DIVE: + MESSAGE("Wobbuffet hid underwater!"); + break; + case MOVE_PHANTOM_FORCE: + case MOVE_SHADOW_FORCE: + MESSAGE("Wobbuffet vanished instantly!"); + break; + } + } + else + ANIMATION(ANIM_TYPE_MOVE, move, player); + MESSAGE("Wobbuffet became fully charged due to its Power Herb!"); + if (B_UPDATED_MOVE_DATA < GEN_5) + { + switch (move) + { + case MOVE_FLY: + MESSAGE("Wobbuffet used Fly!"); + break; + case MOVE_DIG: + MESSAGE("Wobbuffet used Dig!"); + break; + case MOVE_BOUNCE: + MESSAGE("Wobbuffet used Bounce!"); + break; + case MOVE_DIVE: + MESSAGE("Wobbuffet used Dive!"); + break; + case MOVE_PHANTOM_FORCE: + MESSAGE("Wobbuffet used Phantom Force!"); + break; + case MOVE_SHADOW_FORCE: + MESSAGE("Wobbuffet used Shadow Force!"); + break; + } + } + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + } +} +#endif diff --git a/test/battle/move_effect/shed_tail.c b/test/battle/move_effect/shed_tail.c index ba284f0c0dd3..f2d75a878485 100644 --- a/test/battle/move_effect/shed_tail.c +++ b/test/battle/move_effect/shed_tail.c @@ -125,3 +125,55 @@ SINGLE_BATTLE_TEST("Shed Tail creates a Substitute with 1/4 of user maximum heal NOT MESSAGE("Bulbasaur's substitute faded!"); } } + +#if MAX_MON_TRAITS > 1 +AI_SINGLE_BATTLE_TEST("AI will use Shed Tail to pivot to another mon while in damage stalemate with player rather than hard switching (Traits)") +{ + u32 aiFlags; + PARAMETRIZE { aiFlags = 0; } + PARAMETRIZE { aiFlags = AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_MON_CHOICES; } + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiFlags); + PLAYER(SPECIES_WOBBUFFET) { Speed(100); Ability(ABILITY_TELEPATHY); Innates(ABILITY_RUN_AWAY); Moves(MOVE_SCRATCH, MOVE_CELEBRATE); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); Ability(ABILITY_TELEPATHY); Innates(ABILITY_RUN_AWAY); Moves(MOVE_CONFUSION, MOVE_SHED_TAIL); } + OPPONENT(SPECIES_SCIZOR) { Speed(101); Moves(MOVE_CELEBRATE, MOVE_X_SCISSOR); } + } WHEN { + if (aiFlags == 0) + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_CONFUSION); } + TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_SHED_TAIL); } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Shed Tail's HP cost can trigger a berry before the user switches out (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_SITRUS_BERRY].battleUsage == EFFECT_ITEM_RESTORE_HP); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_SITRUS_BERRY); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SHED_TAIL); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHED_TAIL, player); + MESSAGE("Wobbuffet restored its health using its Sitrus Berry!"); + SEND_IN_MESSAGE("Wynaut"); + } +} + +SINGLE_BATTLE_TEST("Shed Tail's HP cost doesn't trigger effects that trigger on damage taken (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_AIR_BALLOON); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SHED_TAIL); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHED_TAIL, player); + MESSAGE("Wobbuffet shed its tail to create a decoy!"); + NOT MESSAGE("Wobbuffet's Air Balloon popped!"); + } +} +#endif diff --git a/test/battle/move_effect/sheer_cold.c b/test/battle/move_effect/sheer_cold.c index c0b076f448aa..3654f48aae4a 100644 --- a/test/battle/move_effect/sheer_cold.c +++ b/test/battle/move_effect/sheer_cold.c @@ -84,3 +84,49 @@ TO_DO_BATTLE_TEST("Sheer Cold always fails if the target has a higher level than TO_DO_BATTLE_TEST("Sheer Cold's accuracy increases by 1% for every level the user has over the target") TO_DO_BATTLE_TEST("Sheer Cold's accuracy decreasaes by 10% if the user is not Ice type") TO_DO_BATTLE_TEST("Sheer Cold's ignores non-stage accuracy modifiers") // Gravity, Wide Lens, Compound Eyes + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Sheer Cold can hit semi-invulnerable mons when the user has No-Guard (Traits)") +{ + GIVEN { + ASSUME(GetItemHoldEffect(ITEM_FOCUS_SASH) == HOLD_EFFECT_FOCUS_SASH); + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_TELEPATHY); Innates(ABILITY_NO_GUARD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_FLY); } + TURN { MOVE(player, MOVE_SHEER_COLD); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player); + HP_BAR(opponent, hp: 0); + } +} + +SINGLE_BATTLE_TEST("Sheer Cold can be endured by Sturdy (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GEODUDE) { Ability(ABILITY_ROCK_HEAD); Innates(ABILITY_STURDY); } + } WHEN { + TURN { MOVE(player, MOVE_SHEER_COLD); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player); + ABILITY_POPUP(opponent, ABILITY_STURDY); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Sheer Cold can be endured by Focus Sash (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_FOCUS_SASH); } + } WHEN { + TURN { MOVE(player, MOVE_SHEER_COLD); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player); + HP_BAR(opponent, hp: 1); + MESSAGE("The opposing Wobbuffet hung on using its Focus Sash!"); + } +} +#endif diff --git a/test/battle/move_effect/shell_side_arm.c b/test/battle/move_effect/shell_side_arm.c index 4a6be08f180e..089b319d6cff 100644 --- a/test/battle/move_effect/shell_side_arm.c +++ b/test/battle/move_effect/shell_side_arm.c @@ -85,3 +85,32 @@ DOUBLE_BATTLE_TEST("Shell Side Arm chooses its category for each battler on the HP_BAR(playerLeft); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Shell Side Arm does not change category mid-turn (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SCREECH) == EFFECT_DEFENSE_DOWN_2); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_SHUCKLE) { Ability(ABILITY_STURDY); Innates(ABILITY_CONTRARY); Defense(100); SpDefense(120); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SHELL_SIDE_ARM, target: opponentLeft); MOVE(opponentLeft, MOVE_MIRROR_COAT, target: opponentLeft); } + TURN { MOVE(playerRight, MOVE_SCREECH, target: opponentLeft); MOVE(playerLeft, MOVE_SHELL_SIDE_ARM, target: opponentLeft); MOVE(opponentLeft, MOVE_MIRROR_COAT, target: opponentLeft); } + TURN { MOVE(playerLeft, MOVE_SHELL_SIDE_ARM, target: opponentLeft); MOVE(opponentLeft, MOVE_MIRROR_COAT, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, playerLeft); + HP_BAR(opponentLeft); + NOT HP_BAR(playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCREECH, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, playerLeft); + HP_BAR(opponentLeft); + NOT HP_BAR(playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_SIDE_ARM, playerLeft); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRROR_COAT, opponentLeft); + HP_BAR(playerLeft); + } +} +#endif diff --git a/test/battle/move_effect/shell_trap.c b/test/battle/move_effect/shell_trap.c index f3f0068d6f6d..aa05b2e06c67 100644 --- a/test/battle/move_effect/shell_trap.c +++ b/test/battle/move_effect/shell_trap.c @@ -279,3 +279,36 @@ DOUBLE_BATTLE_TEST("Shell Trap does not trigger when hit into Substitute") ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_TRAP, playerLeft); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Shell Trap does not activate if attacker's Sheer Force applied (Traits)") +{ + u32 move; + bool32 activate; + PARAMETRIZE { move = MOVE_SCRATCH; activate = TRUE; } + PARAMETRIZE { move = MOVE_STOMP; activate = FALSE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TAUROS) { Ability(ABILITY_ANGER_POINT); Innates(ABILITY_SHEER_FORCE); } + } WHEN { + TURN { MOVE(player, MOVE_SHELL_TRAP); MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SHELL_TRAP_SETUP, player); + MESSAGE("Wobbuffet set a shell trap!"); + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + if (activate) { + MESSAGE("Wobbuffet used Shell Trap!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_TRAP, player); + HP_BAR(opponent); + } else { + MESSAGE("Wobbuffet's shell trap didn't work!"); + NONE_OF { + MESSAGE("Wobbuffet used Shell Trap!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SHELL_TRAP, player); + HP_BAR(opponent); + } + } + } +} +#endif diff --git a/test/battle/move_effect/sleep_talk.c b/test/battle/move_effect/sleep_talk.c index 6980954714bd..3ca717148ff6 100644 --- a/test/battle/move_effect/sleep_talk.c +++ b/test/battle/move_effect/sleep_talk.c @@ -187,3 +187,63 @@ SINGLE_BATTLE_TEST("Sleep Talk deducts power points from itself, not the called EXPECT_EQ(player->pp[1], 35); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Sleep Talk calls move and that move may be redirected by Lightning Rod (Traits)") +{ + PASSES_RANDOMLY(1, 2, RNG_RANDOM_TARGET); + GIVEN { + ASSUME(GetMoveType(MOVE_SPARK) == 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_STATIC); Innates(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 (Traits)") +{ + PASSES_RANDOMLY(1, 2, RNG_RANDOM_TARGET); + GIVEN { + ASSUME(GetMoveType(MOVE_WATER_GUN) == 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_STICKY_HOLD); Innates(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); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Sleep Talk can use moves while choiced into Sleep Talk (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_CHOICE_BAND); Status1(STATUS1_SLEEP); Moves(MOVE_SLEEP_TALK, MOVE_SCRATCH, MOVE_FLY, MOVE_DIG); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SLEEP_TALK); } + TURN { MOVE(player, MOVE_SLEEP_TALK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SLEEP_TALK, player); + NOT MESSAGE("But it failed!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SLEEP_TALK, player); + NOT MESSAGE("But it failed!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH); + } +} +#endif diff --git a/test/battle/move_effect/soak.c b/test/battle/move_effect/soak.c index 307b4802db34..d9c93554547b 100644 --- a/test/battle/move_effect/soak.c +++ b/test/battle/move_effect/soak.c @@ -43,3 +43,34 @@ TO_DO_BATTLE_TEST("(TERA) Soak/Magic Powder's type change overritten if the targ TO_DO_BATTLE_TEST("Soak/Magic Powder fails if the target is behind a Substitute"); TO_DO_BATTLE_TEST("Soak/Magic Powder fails if the target is already Water/Psychic"); TO_DO_BATTLE_TEST("Soak/Magic Powder fails if the target has Multitype or RKS System"); + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Soak/Magic Powder's type change is overwitten if the target changes form (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_SOAK; } + PARAMETRIZE { move = MOVE_MAGIC_POWDER; } + GIVEN { + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + PLAYER(SPECIES_MIMIKYU_DISGUISED) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DISGUISE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + // Turn 1 + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + switch (move) { + case MOVE_SOAK: MESSAGE("Mimikyu transformed into the Water type!"); break; + case MOVE_MAGIC_POWDER: MESSAGE("Mimikyu transformed into the Psychic type!"); break; + } + // Turn 2 + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + NOT MESSAGE("It doesn't affect Mimikyu…"); + ABILITY_POPUP(player, ABILITY_DISGUISE); + // Turn 3 + MESSAGE("It doesn't affect Mimikyu…"); + } +} +#endif diff --git a/test/battle/move_effect/solar_beam.c b/test/battle/move_effect/solar_beam.c index 6113b5c4acd3..fc16bd714fcf 100644 --- a/test/battle/move_effect/solar_beam.c +++ b/test/battle/move_effect/solar_beam.c @@ -32,3 +32,31 @@ SINGLE_BATTLE_TEST("Solar Beam does not need a charging turn if Sun is up") ANIMATION(ANIM_TYPE_MOVE, MOVE_SOLAR_BEAM, player); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Solar Beam does not need a charging turn if Sun is up (Traits)") +{ + enum Ability ability; + + PARAMETRIZE { ability = ABILITY_DROUGHT; } + PARAMETRIZE { ability = ABILITY_WHITE_SMOKE; } + + GIVEN { + PLAYER(SPECIES_TORKOAL) { Ability(ABILITY_WHITE_SMOKE); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SOLAR_BEAM); } + if (ability == ABILITY_WHITE_SMOKE) { + TURN { SKIP_TURN(player); } + } + } SCENE { + if (ability == ABILITY_WHITE_SMOKE) { + MESSAGE("Torkoal used Solar Beam!"); + MESSAGE("Torkoal absorbed light!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + } + MESSAGE("Torkoal used Solar Beam!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SOLAR_BEAM, player); + } +} +#endif diff --git a/test/battle/move_effect/speed_down.c b/test/battle/move_effect/speed_down.c index 88d62f2f387a..822f69773137 100644 --- a/test/battle/move_effect/speed_down.c +++ b/test/battle/move_effect/speed_down.c @@ -30,3 +30,35 @@ DOUBLE_BATTLE_TEST("Speed Down: Cotton Spore does not fail if it is blocked by o } } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Speed Down: Cotton Spore does not fail if it is blocked by one target (Traits)") +{ + enum Ability abilityOne, abilityTwo; + + PARAMETRIZE { abilityOne = ABILITY_OVERCOAT; abilityTwo = ABILITY_SKILL_LINK; } + PARAMETRIZE { abilityOne = ABILITY_SKILL_LINK; abilityTwo = ABILITY_OVERCOAT; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_COTTON_SPORE) == EFFECT_SPEED_DOWN_2); + ASSUME(GetMoveTarget(MOVE_COTTON_SPORE) == MOVE_TARGET_BOTH); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SHELLDER) { Ability(ABILITY_SHELL_ARMOR); Innates(abilityOne); } + OPPONENT(SPECIES_SHELLDER) { Ability(ABILITY_SHELL_ARMOR); Innates(abilityTwo); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_COTTON_SPORE); } + } SCENE { + if (abilityOne == ABILITY_OVERCOAT) { + ABILITY_POPUP(opponentLeft, ABILITY_OVERCOAT); + ANIMATION(ANIM_TYPE_MOVE, MOVE_COTTON_SPORE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } + else if (abilityTwo == ABILITY_OVERCOAT) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_COTTON_SPORE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ABILITY_POPUP(opponentRight, ABILITY_OVERCOAT); + } + } +} +#endif diff --git a/test/battle/move_effect/speed_swap.c b/test/battle/move_effect/speed_swap.c index 3405d510ca81..895926138558 100644 --- a/test/battle/move_effect/speed_swap.c +++ b/test/battle/move_effect/speed_swap.c @@ -57,3 +57,36 @@ SINGLE_BATTLE_TEST("Speed Swap doesn't swap user and target's speed modifiers") } } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Speed Swap doesn't swap user and target's speed modifiers (Traits)") +{ + u32 species, move; + enum Ability ability; + PARAMETRIZE { species = SPECIES_WOBBUFFET; ability = ABILITY_TELEPATHY; move = MOVE_ROCK_POLISH; } // x2.0 + PARAMETRIZE { species = SPECIES_PSYDUCK; ability = ABILITY_SWIFT_SWIM; move = MOVE_RAIN_DANCE; } // x2.0 + GIVEN { + ASSUME(GetMoveEffect(MOVE_ROCK_POLISH) == EFFECT_SPEED_UP_2); + ASSUME(GetMoveEffect(MOVE_RAIN_DANCE) == EFFECT_RAIN_DANCE); + PLAYER(SPECIES_WOBBUFFET) { Speed(8); } + OPPONENT(species) { Speed(10); Ability(ABILITY_TELEPATHY); Innates(ability); } + }WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_SPEED_SWAP); } + TURN { MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_SCRATCH); } + } SCENE { + // Turn 1 + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPEED_SWAP, player); + // Turn 2 + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); // Opponent is still first + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + } THEN { + EXPECT_EQ(player->speed, 10); + EXPECT_EQ(opponent->speed, 8); + if (move == MOVE_ROCK_POLISH) { + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 2); + } + } +} +#endif diff --git a/test/battle/move_effect/spicy_extract.c b/test/battle/move_effect/spicy_extract.c index ea78e08eeb65..d55af6e89e0c 100644 --- a/test/battle/move_effect/spicy_extract.c +++ b/test/battle/move_effect/spicy_extract.c @@ -206,3 +206,212 @@ AI_DOUBLE_BATTLE_TEST("Spicy Extract user will not choose the move if it does no } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Spicy Extract is prevented by target's ability if it's Attack stat is maxed out (Traits)") +{ + enum Ability ability; + + PARAMETRIZE { ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE { ability = ABILITY_LIGHT_METAL; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SWORDS_DANCE) == EFFECT_ATTACK_UP_2); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BELDUM) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(opponent, MOVE_SWORDS_DANCE); } + TURN { MOVE(opponent, MOVE_SWORDS_DANCE); } + TURN { MOVE(opponent, MOVE_SWORDS_DANCE); MOVE(player, MOVE_SPICY_EXTRACT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Wobbuffet used Spicy Extract!"); + if (ability == ABILITY_CLEAR_BODY) { + ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + MESSAGE("The opposing Beldum's Clear Body prevents stat loss!"); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPICY_EXTRACT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPICY_EXTRACT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } + } +} + +SINGLE_BATTLE_TEST("Spicy Extract Defense loss is prevented by Big Pecks (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_BIG_PECKS); } + } WHEN { + TURN { MOVE(player, MOVE_SPICY_EXTRACT); } + } SCENE { + MESSAGE("Wobbuffet used Spicy Extract!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPICY_EXTRACT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Pidgey's Attack sharply rose!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wobbuffet's Defense harshly fell!"); + } + ABILITY_POPUP(opponent, ABILITY_BIG_PECKS); + MESSAGE("The opposing Pidgey's Big Pecks prevents Defense loss!"); + } +} + +SINGLE_BATTLE_TEST("Spicy Extract stat changes will be inverted by Contrary (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SNIVY) { Ability(ABILITY_OVERGROW); Innates(ABILITY_CONTRARY); } + } WHEN { + TURN { MOVE(player, MOVE_SPICY_EXTRACT); } + } SCENE { + MESSAGE("Wobbuffet used Spicy Extract!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPICY_EXTRACT, player); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Snivy's Attack harshly fell!"); + + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Snivy's Defense sharply rose!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 2); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("Spicy Extract against Clear Amulet and Contrary raises Defense only (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SNIVY) { Ability(ABILITY_OVERGROW); Innates(ABILITY_CONTRARY); Item(ITEM_CLEAR_AMULET); } + } WHEN { + TURN { MOVE(player, MOVE_SPICY_EXTRACT); } + } SCENE { + MESSAGE("Wobbuffet used Spicy Extract!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPICY_EXTRACT, player); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Snivy's Attack harshly fell!"); + } + MESSAGE("The effects of the Clear Amulet held by the opposing Snivy prevents its stats from being lowered!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Snivy's Defense sharply rose!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 2); + } +} + +AI_DOUBLE_BATTLE_TEST("Spicy Extract user will not choose the move if it does not benefit partner (Traits)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_GHOLDENGO; ability = ABILITY_GOOD_AS_GOLD; } + PARAMETRIZE { species = SPECIES_SNIVY; ability = ABILITY_CONTRARY; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { Speed(10); } + PLAYER(SPECIES_WOBBUFFET) { Speed(10); } + OPPONENT(species) { Speed(20); Ability(ABILITY_LIGHT_METAL); Innates(ability); Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(40); Moves(MOVE_SCRATCH, MOVE_SPICY_EXTRACT); } + } WHEN { + TURN { + EXPECT_MOVE(opponentRight, MOVE_SCRATCH); + } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Spicy Extract bypasses accuracy checks (Multi)") +{ + PASSES_RANDOMLY(100, 100, RNG_ACCURACY); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_BRIGHTPOWDER); } + } WHEN { + TURN { MOVE(player, MOVE_SPICY_EXTRACT); } + } SCENE { + MESSAGE("Wobbuffet used Spicy Extract!"); + NOT MESSAGE("Wobbuffet's attack missed!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPICY_EXTRACT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wobbuffet's Attack sharply rose!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wobbuffet's Defense harshly fell!"); + } +} + +SINGLE_BATTLE_TEST("Spicy Extract will fail if target is in a semi-invulnerability state (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_BRIGHTPOWDER); } + } WHEN { + TURN { MOVE(opponent, MOVE_DIVE); MOVE(player, MOVE_SPICY_EXTRACT); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Dive!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DIVE, opponent); + MESSAGE("Wobbuffet used Spicy Extract!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SPICY_EXTRACT, player); + MESSAGE("Wobbuffet's attack missed!"); + } +} + +SINGLE_BATTLE_TEST("Spicy Extract against Clear Amulet and Contrary raises Defense only (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SNIVY) { Ability(ABILITY_CONTRARY); Items(ITEM_PECHA_BERRY, ITEM_CLEAR_AMULET); } + } WHEN { + TURN { MOVE(player, MOVE_SPICY_EXTRACT); } + } SCENE { + MESSAGE("Wobbuffet used Spicy Extract!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPICY_EXTRACT, player); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Snivy's Attack harshly fell!"); + } + MESSAGE("The effects of the Clear Amulet held by the opposing Snivy prevents its stats from being lowered!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Snivy's Defense sharply rose!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 2); + } +} + +AI_DOUBLE_BATTLE_TEST("Spicy Extract user will use it if partner holds Clear Amulet and a physical move (Multi)") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SCRATCH; } + PARAMETRIZE { move = MOVE_SWIFT;} + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { Speed(10); } + PLAYER(SPECIES_WOBBUFFET) { Speed(10); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(20); Items(ITEM_PECHA_BERRY, ITEM_CLEAR_AMULET); Moves(move); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(40); Moves(MOVE_SCRATCH, MOVE_SPICY_EXTRACT); } + } WHEN { + TURN { + if (move == MOVE_SCRATCH) + EXPECT_MOVE(opponentRight, MOVE_SPICY_EXTRACT); + else + EXPECT_MOVE(opponentRight, MOVE_SCRATCH); + } + } +} +#endif diff --git a/test/battle/move_effect/spikes.c b/test/battle/move_effect/spikes.c index 4b458f5c68e6..bad0f44ce802 100644 --- a/test/battle/move_effect/spikes.c +++ b/test/battle/move_effect/spikes.c @@ -160,3 +160,103 @@ SINGLE_BATTLE_TEST("Toxic Spikes: Only three layers can be set up") EXPECT_EQ(spikesAmount, 3); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Spikes do not damage airborne Pokemon (Traits)") +{ + u32 species = SPECIES_WOBBUFFET; + u32 item = ITEM_NONE; + u32 move1 = MOVE_CELEBRATE; + u32 move2 = MOVE_CELEBRATE; + bool32 airborne; + + ASSUME(GetSpeciesType(SPECIES_PIDGEY, 1) == TYPE_FLYING); + PARAMETRIZE { species = SPECIES_PIDGEY; airborne = TRUE; } + PARAMETRIZE { species = SPECIES_PIDGEY; item = ITEM_IRON_BALL; airborne = FALSE; } + PARAMETRIZE { species = SPECIES_PIDGEY; move1 = MOVE_GRAVITY; airborne = FALSE; } + PARAMETRIZE { species = SPECIES_PIDGEY; move1 = MOVE_INGRAIN; airborne = FALSE; } + + ASSUME(GetSpeciesAbility(SPECIES_UNOWN, 0) == ABILITY_LEVITATE); + PARAMETRIZE { species = SPECIES_UNOWN; airborne = TRUE; } + PARAMETRIZE { species = SPECIES_UNOWN; item = ITEM_IRON_BALL; airborne = FALSE; } + PARAMETRIZE { species = SPECIES_UNOWN; move1 = MOVE_GRAVITY; airborne = FALSE; } + PARAMETRIZE { species = SPECIES_UNOWN; move1 = MOVE_INGRAIN; airborne = FALSE; } + + PARAMETRIZE { move1 = MOVE_MAGNET_RISE; airborne = TRUE; } + PARAMETRIZE { move1 = MOVE_MAGNET_RISE; item = ITEM_IRON_BALL; airborne = FALSE; } + PARAMETRIZE { move1 = MOVE_MAGNET_RISE; move2 = MOVE_GRAVITY; airborne = FALSE; } + // Magnet Rise fails under Gravity. + // Magnet Rise fails under Ingrain and vice-versa. + + PARAMETRIZE { item = ITEM_AIR_BALLOON; airborne = TRUE; } + PARAMETRIZE { item = ITEM_AIR_BALLOON; move1 = MOVE_GRAVITY; airborne = FALSE; } + PARAMETRIZE { item = ITEM_AIR_BALLOON; move1 = MOVE_INGRAIN; airborne = FALSE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_LEVITATE); Item(item); } + } WHEN { + TURN { MOVE(player, MOVE_SPIKES); MOVE(opponent, move1); } + TURN { MOVE(opponent, move2); } + TURN { MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); } + } SCENE { + s32 maxHP = GetMonData(&OPPONENT_PARTY[1], MON_DATA_MAX_HP); + if (airborne) { + NOT HP_BAR(opponent, damage: maxHP / 8); + } else { + HP_BAR(opponent, damage: maxHP / 8); + } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Spikes do not damage airborne Pokemon (Multi)") +{ + u32 species = SPECIES_WOBBUFFET; + u32 item = ITEM_NONE; + u32 move1 = MOVE_CELEBRATE; + u32 move2 = MOVE_CELEBRATE; + bool32 airborne; + + ASSUME(GetSpeciesType(SPECIES_PIDGEY, 1) == TYPE_FLYING); + PARAMETRIZE { species = SPECIES_PIDGEY; airborne = TRUE; } + PARAMETRIZE { species = SPECIES_PIDGEY; item = ITEM_IRON_BALL; airborne = FALSE; } + PARAMETRIZE { species = SPECIES_PIDGEY; move1 = MOVE_GRAVITY; airborne = FALSE; } + PARAMETRIZE { species = SPECIES_PIDGEY; move1 = MOVE_INGRAIN; airborne = FALSE; } + + ASSUME(GetSpeciesAbility(SPECIES_UNOWN, 0) == ABILITY_LEVITATE); + PARAMETRIZE { species = SPECIES_UNOWN; airborne = TRUE; } + PARAMETRIZE { species = SPECIES_UNOWN; item = ITEM_IRON_BALL; airborne = FALSE; } + PARAMETRIZE { species = SPECIES_UNOWN; move1 = MOVE_GRAVITY; airborne = FALSE; } + PARAMETRIZE { species = SPECIES_UNOWN; move1 = MOVE_INGRAIN; airborne = FALSE; } + + PARAMETRIZE { move1 = MOVE_MAGNET_RISE; airborne = TRUE; } + PARAMETRIZE { move1 = MOVE_MAGNET_RISE; item = ITEM_IRON_BALL; airborne = FALSE; } + PARAMETRIZE { move1 = MOVE_MAGNET_RISE; move2 = MOVE_GRAVITY; airborne = FALSE; } + // Magnet Rise fails under Gravity. + // Magnet Rise fails under Ingrain and vice-versa. + + PARAMETRIZE { item = ITEM_AIR_BALLOON; airborne = TRUE; } + PARAMETRIZE { item = ITEM_AIR_BALLOON; move1 = MOVE_GRAVITY; airborne = FALSE; } + PARAMETRIZE { item = ITEM_AIR_BALLOON; move1 = MOVE_INGRAIN; airborne = FALSE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(species) { Items(ITEM_PECHA_BERRY, item); } + } WHEN { + TURN { MOVE(player, MOVE_SPIKES); MOVE(opponent, move1); } + TURN { MOVE(opponent, move2); } + TURN { MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); } + } SCENE { + s32 maxHP = GetMonData(&OPPONENT_PARTY[1], MON_DATA_MAX_HP); + if (airborne) { + NOT HP_BAR(opponent, damage: maxHP / 8); + } else { + HP_BAR(opponent, damage: maxHP / 8); + } + } +} +#endif diff --git a/test/battle/move_effect/steal_item.c b/test/battle/move_effect/steal_item.c index dffc1ecd6a33..e82ae3b49b3f 100644 --- a/test/battle/move_effect/steal_item.c +++ b/test/battle/move_effect/steal_item.c @@ -185,3 +185,188 @@ SINGLE_BATTLE_TEST("Thief and Covet: Berries that activate on a Status activate NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_STEAL, opponent); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Thief and Covet: Berries that activate on a Status activate before the item can be stolen (Traits)") +{ + u32 move; + PARAMETRIZE { move = MOVE_THIEF; } + PARAMETRIZE { move = MOVE_COVET; } + + GIVEN { + PLAYER(SPECIES_TOXICROAK) { Ability(ABILITY_ANTICIPATION); Innates(ABILITY_POISON_TOUCH); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LUM_BERRY); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_STEAL, opponent); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Thief and Covet steal target's held item (Multi)") +{ + u32 move; + PARAMETRIZE { move = MOVE_THIEF; } + PARAMETRIZE { move = MOVE_COVET; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_HYPER_POTION); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_STEAL, opponent); + } THEN { + EXPECT_EQ(player->item, ITEM_HYPER_POTION); + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Thief and Covet steal player's held item if opponent is a trainer (Multi)") +{ + u32 move; + PARAMETRIZE { move = MOVE_THIEF; } + PARAMETRIZE { move = MOVE_COVET; } + GIVEN { + ASSUME(B_TRAINERS_KNOCK_OFF_ITEMS == TRUE); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_HYPER_POTION); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_STEAL, player); + } THEN { + EXPECT_EQ(opponent->item, ITEM_HYPER_POTION); + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +WILD_BATTLE_TEST("Thief and Covet don't steal player's held item if opponent is a wild mon (Multi)") +{ + u32 move; + PARAMETRIZE { move = MOVE_THIEF; } + PARAMETRIZE { move = MOVE_COVET; } + GIVEN { + ASSUME(B_TRAINERS_KNOCK_OFF_ITEMS == TRUE); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_HYPER_POTION); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + HP_BAR(player); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_STEAL, player); + } THEN { + EXPECT_EQ(player->item, ITEM_HYPER_POTION); + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Thief and Covet don't steal target's held item if user is holding an item (Multi)") +{ + u32 move; + PARAMETRIZE { move = MOVE_THIEF; } + PARAMETRIZE { move = MOVE_COVET; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_POTION); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_HYPER_POTION); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_STEAL, opponent); + } THEN { + EXPECT_EQ(player->item, ITEM_POTION); + EXPECT_EQ(opponent->item, ITEM_HYPER_POTION); + } +} + +// Test can't currently verify if the item is sent to Bag +WILD_BATTLE_TEST("Thief and Covet steal target's held item and it's added to Bag in wild battles (Gen 9+) (Multi)") +{ + u32 move; + PARAMETRIZE { move = MOVE_THIEF; } + PARAMETRIZE { move = MOVE_COVET; } + GIVEN { + WITH_CONFIG(CONFIG_STEAL_WILD_ITEMS, GEN_9); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_HYPER_POTION); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_STEAL, opponent); + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Thief and Covet can't steal target's held item if user faints before (Multi)") +{ + u32 move; + PARAMETRIZE { move = MOVE_THIEF; } + PARAMETRIZE { move = MOVE_COVET; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); }; + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_ROCKY_HELMET); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_STEAL, opponent); + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + EXPECT_EQ(opponent->item, ITEM_ROCKY_HELMET); + } +} + +SINGLE_BATTLE_TEST("Thief and Covet: Berries that activate on HP thresholds are stolen before they can activate (Multi)") +{ + u32 move; + PARAMETRIZE { move = MOVE_THIEF; } + PARAMETRIZE { move = MOVE_COVET; } + + GIVEN { + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(200); HP(101); Items(ITEM_NONE, ITEM_ORAN_BERRY); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_STEAL, opponent); + } +} + +SINGLE_BATTLE_TEST("Thief and Covet: Berries that activate on a Status activate before the item can be stolen (Multi)") +{ + u32 move; + PARAMETRIZE { move = MOVE_THIEF; } + PARAMETRIZE { move = MOVE_COVET; } + + GIVEN { + PLAYER(SPECIES_TOXICROAK) { Ability(ABILITY_POISON_TOUCH); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LUM_BERRY); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_STEAL, opponent); + } +} +#endif diff --git a/test/battle/move_effect/stealth_rock.c b/test/battle/move_effect/stealth_rock.c index 1ea6bea23b73..5cf79a1140d4 100644 --- a/test/battle/move_effect/stealth_rock.c +++ b/test/battle/move_effect/stealth_rock.c @@ -100,3 +100,48 @@ SINGLE_BATTLE_TEST("Stealth Rock damage terastalized mons with the correct amoun EXPECT_GT(results[0].damage, results[1].damage); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Stealth Rock damages the correct Pokémon when Eject Button is triggered (Multi)") +{ + GIVEN { + PLAYER(SPECIES_METAPOD) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + PLAYER(SPECIES_METAPOD); + OPPONENT(SPECIES_JOLTEON); + } WHEN { + TURN { MOVE(opponent, MOVE_STEALTH_ROCK); MOVE(player, MOVE_HARDEN); } + TURN { MOVE(opponent, MOVE_QUICK_ATTACK); MOVE(player, MOVE_HARDEN); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEALTH_ROCK, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HARDEN, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, opponent); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_HARDEN, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + SEND_IN_MESSAGE("Metapod"); + HP_BAR(player); + } THEN { + EXPECT_EQ(opponent->hp, opponent->maxHP); + } +} + +DOUBLE_BATTLE_TEST("Stealth Rock damages the correct Pokémon when Eject Button is triggered in double battle (Multi)") +{ + GIVEN { + PLAYER(SPECIES_METAPOD) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + PLAYER(SPECIES_METAPOD) { Items(ITEM_PECHA_BERRY, ITEM_EJECT_BUTTON); } + PLAYER(SPECIES_METAPOD); + OPPONENT(SPECIES_JOLTEON); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_STEALTH_ROCK); MOVE(opponentRight, MOVE_SCRATCH, target: playerLeft); SEND_OUT(playerLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEALTH_ROCK, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + SEND_IN_MESSAGE("Metapod"); + HP_BAR(playerLeft); + } THEN { + EXPECT_EQ(opponentLeft->hp, opponentLeft->maxHP); + } +} +#endif diff --git a/test/battle/move_effect/steel_roller.c b/test/battle/move_effect/steel_roller.c index 4b658a612445..99b696eb4530 100644 --- a/test/battle/move_effect/steel_roller.c +++ b/test/battle/move_effect/steel_roller.c @@ -73,3 +73,35 @@ AI_SINGLE_BATTLE_TEST("Steel Roller wont be chosen by AI if there is no terrain } } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Steel Roller removes Terrain even if user faints during attack execution (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_LIFE_ORB); HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); MOVE(opponent, MOVE_STEEL_ROLLER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEEL_ROLLER, opponent); + MESSAGE("The electricity disappeared from the battlefield."); + } +} + +SINGLE_BATTLE_TEST("Steel Roller removes Terrain if user is switched out due to Red Card (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); MOVE(opponent, MOVE_STEEL_ROLLER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEEL_ROLLER, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("The electricity disappeared from the battlefield."); + } +} +#endif diff --git a/test/battle/move_effect/sticky_web.c b/test/battle/move_effect/sticky_web.c index cbcf39ad7b25..de9cad9e9e4e 100644 --- a/test/battle/move_effect/sticky_web.c +++ b/test/battle/move_effect/sticky_web.c @@ -299,3 +299,371 @@ DOUBLE_BATTLE_TEST("Sticky Web setter has their speed lowered with Mirror Armor MESSAGE("The opposing Caterpie's Speed fell!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Sticky Web raises Speed by 1 for a Pokemon with Contrary (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SHUCKLE) { Ability(ABILITY_STURDY); Innates(ABILITY_CONTRARY); } + } WHEN { + TURN { MOVE(player, MOVE_STICKY_WEB); } + TURN { SWITCH(opponent, 1); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, player); + MESSAGE("A sticky web has been laid out on the ground around the opposing team!"); + MESSAGE("2 sent out Shuckle!"); + MESSAGE("The opposing Shuckle was caught in a sticky web!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Shuckle's Speed rose!"); + } +} + +#define BATTLER_OPPONENT (opponentSetUpper == 0 ? opponentLeft : opponentRight) +#define BATTLER_PLAYER (playerSetUpper == 0 ? playerLeft : playerRight) + +DOUBLE_BATTLE_TEST("Sticky Web has correct interactions with Mirror Armor - the battler which set up Sticky Web has its Speed lowered instead (Traits)") +{ + u8 playerSetUpper, opponentSetUpper; // 0 left, 1 right + + PARAMETRIZE {playerSetUpper = 0; opponentSetUpper = 0; } + PARAMETRIZE {playerSetUpper = 0; opponentSetUpper = 1; } + PARAMETRIZE {playerSetUpper = 1; opponentSetUpper = 0; } + PARAMETRIZE {playerSetUpper = 1; opponentSetUpper = 1; } + + GIVEN { + PLAYER(SPECIES_SQUIRTLE); + PLAYER(SPECIES_CHARMANDER); + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_PRESSURE); Innates(ABILITY_MIRROR_ARMOR); Item(ITEM_IRON_BALL); } // Iron Ball, so that flying type Corviknight is affected by Sticky Web. + OPPONENT(SPECIES_CATERPIE); + OPPONENT(SPECIES_WEEDLE); + } WHEN { + TURN { MOVE(BATTLER_OPPONENT, MOVE_STICKY_WEB); } + TURN { MOVE(BATTLER_PLAYER, MOVE_STICKY_WEB); } + TURN { SWITCH(playerRight, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, BATTLER_OPPONENT); + MESSAGE("A sticky web has been laid out on the ground around your team!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, BATTLER_PLAYER); + MESSAGE("A sticky web has been laid out on the ground around the opposing team!"); + + SEND_IN_MESSAGE("Corviknight"); + MESSAGE("Corviknight was caught in a sticky web!"); + ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, BATTLER_OPPONENT); + if (opponentSetUpper == 0) { + MESSAGE("The opposing Caterpie's Speed fell!"); + NONE_OF { + MESSAGE("The opposing Caterpie was caught in a sticky web!"); + } + } else { + MESSAGE("The opposing Weedle's Speed fell!"); + NONE_OF { + MESSAGE("The opposing Weedle was caught in a sticky web!"); + } + } + } +} + +#undef BATTLER_OPPONENT +#undef BATTLER_PLAYER + +DOUBLE_BATTLE_TEST("Sticky Web has correct interactions with Mirror Armor - no one has their Speed lowered if the set upper switched (Traits)") +{ + u16 speedPlayer, speedOpponent; + + // We need to make sure Sticky Web user saves for both sides, so it doesn't matter who sets it first. + PARAMETRIZE { speedPlayer = 5; speedOpponent = 10; } + PARAMETRIZE { speedPlayer = 10; speedOpponent = 5; } + + GIVEN { + PLAYER(SPECIES_SQUIRTLE) { Speed(speedPlayer); } + PLAYER(SPECIES_CHARMANDER) { Speed(speedPlayer); } + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_PRESSURE); Innates(ABILITY_MIRROR_ARMOR); Item(ITEM_IRON_BALL); Speed(speedOpponent); } // Iron Ball, so that flying type Corviknight is affected by Sticky Web. + OPPONENT(SPECIES_CATERPIE) { Speed(speedOpponent); } + OPPONENT(SPECIES_WEEDLE) { Speed(speedOpponent); } + OPPONENT(SPECIES_PIDGEY) { Speed(speedOpponent); } // Flying type,so not affected by Sticky Web. + } WHEN { + TURN { MOVE(opponentLeft, MOVE_STICKY_WEB); MOVE(playerRight, MOVE_STICKY_WEB); } + TURN { SWITCH(opponentLeft, 2); } + TURN { SWITCH(playerRight, 2); } + } SCENE { + if (speedPlayer > speedOpponent) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, playerRight); + MESSAGE("A sticky web has been laid out on the ground around the opposing team!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponentLeft); + MESSAGE("A sticky web has been laid out on the ground around your team!"); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponentLeft); + MESSAGE("A sticky web has been laid out on the ground around your team!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, playerRight); + MESSAGE("A sticky web has been laid out on the ground around the opposing team!"); + } + + SEND_IN_MESSAGE("Corviknight"); + MESSAGE("Corviknight was caught in a sticky web!"); + ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(playerRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Sticky Web has correct interactions with Mirror Armor - no one has their Speed lowered if the set upper fainted (Traits)") +{ + bool8 hasReplacement; + + // We need to make sure Sticky Web user saves for both sides, so it doesn't matter who sets it first. + PARAMETRIZE {hasReplacement = TRUE;} + PARAMETRIZE {hasReplacement = FALSE;} + + GIVEN { + ASSUME(GetMoveEffect(MOVE_MEMENTO) == EFFECT_MEMENTO); + PLAYER(SPECIES_SQUIRTLE) {Speed(5); } + PLAYER(SPECIES_CHARMANDER) {Speed(5); } + PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_PRESSURE); Innates(ABILITY_MIRROR_ARMOR); Item(ITEM_IRON_BALL); Speed(5); } // Iron Ball, so that flying type Corviknight is affected by Sticky Web. + OPPONENT(SPECIES_CATERPIE) {Speed(7); } + OPPONENT(SPECIES_WEEDLE) {Speed(7); } + if (hasReplacement) { + OPPONENT(SPECIES_PIDGEY) {Speed(7); } + } + + } WHEN { + TURN { MOVE(opponentLeft, MOVE_STICKY_WEB); } + if (hasReplacement) { + TURN { MOVE(opponentLeft, MOVE_MEMENTO, target:playerLeft); SEND_OUT(opponentLeft, 2); } + } else { + TURN { MOVE(opponentLeft, MOVE_MEMENTO, target:playerLeft);} + } + TURN { SWITCH(playerRight, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponentLeft); + MESSAGE("A sticky web has been laid out on the ground around your team!"); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_MEMENTO, opponentLeft); + MESSAGE("The opposing Caterpie fainted!"); + if (hasReplacement) { + MESSAGE("2 sent out Pidgey!"); + } + + SEND_IN_MESSAGE("Corviknight"); + MESSAGE("Corviknight was caught in a sticky web!"); + ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + } THEN { + if (hasReplacement) { + EXPECT_EQ(opponentLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } + EXPECT_EQ(playerLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(playerRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Sticky Web setter has their speed lowered with Mirror Armor even after Ally Switch (Traits)") +{ + GIVEN { + PLAYER(SPECIES_SQUIRTLE); + PLAYER(SPECIES_CHARMANDER); + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_PRESSURE); Innates(ABILITY_MIRROR_ARMOR); Item(ITEM_IRON_BALL); } // Iron Ball, so that flying type Corviknight is affected by Sticky Web. + OPPONENT(SPECIES_CATERPIE); + OPPONENT(SPECIES_NATU); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_STICKY_WEB); } + TURN { MOVE(opponentRight, MOVE_ALLY_SWITCH); } + TURN { SWITCH(playerRight, 2); } + } SCENE { + // Turn 1 - set up sticky web + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponentLeft); + MESSAGE("A sticky web has been laid out on the ground around your team!"); + // Turn 2 - ally switch + MESSAGE("The opposing Natu used Ally Switch!"); + // turn 3 - send our corviknight + SEND_IN_MESSAGE("Corviknight"); + MESSAGE("Corviknight was caught in a sticky web!"); + ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR); + // sticky web setter - caterpie (now opponentRight) gets speed lowered + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("The opposing Caterpie's Speed fell!"); + } +} +#endif + +#if MAX_MON_ITEMS > 1 + +#define BATTLER_OPPONENT (opponentSetUpper == 0 ? opponentLeft : opponentRight) +#define BATTLER_PLAYER (playerSetUpper == 0 ? playerLeft : playerRight) +DOUBLE_BATTLE_TEST("Sticky Web has correct interactions with Mirror Armor - the battler which set up Sticky Web has its Speed lowered instead (Multi)") +{ + u8 playerSetUpper, opponentSetUpper; // 0 left, 1 right + + PARAMETRIZE {playerSetUpper = 0; opponentSetUpper = 0; } + PARAMETRIZE {playerSetUpper = 0; opponentSetUpper = 1; } + PARAMETRIZE {playerSetUpper = 1; opponentSetUpper = 0; } + PARAMETRIZE {playerSetUpper = 1; opponentSetUpper = 1; } + + GIVEN { + PLAYER(SPECIES_SQUIRTLE); + PLAYER(SPECIES_CHARMANDER); + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); Items(ITEM_PECHA_BERRY, ITEM_IRON_BALL); } // Iron Ball, so that flying type Corviknight is affected by Sticky Web. + OPPONENT(SPECIES_CATERPIE); + OPPONENT(SPECIES_WEEDLE); + } WHEN { + TURN { MOVE(BATTLER_OPPONENT, MOVE_STICKY_WEB); } + TURN { MOVE(BATTLER_PLAYER, MOVE_STICKY_WEB); } + TURN { SWITCH(playerRight, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, BATTLER_OPPONENT); + MESSAGE("A sticky web has been laid out on the ground around your team!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, BATTLER_PLAYER); + MESSAGE("A sticky web has been laid out on the ground around the opposing team!"); + + SEND_IN_MESSAGE("Corviknight"); + MESSAGE("Corviknight was caught in a sticky web!"); + ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, BATTLER_OPPONENT); + if (opponentSetUpper == 0) { + MESSAGE("The opposing Caterpie's Speed fell!"); + NONE_OF { + MESSAGE("The opposing Caterpie was caught in a sticky web!"); + } + } else { + MESSAGE("The opposing Weedle's Speed fell!"); + NONE_OF { + MESSAGE("The opposing Weedle was caught in a sticky web!"); + } + } + } +} + +#undef BATTLER_OPPONENT +#undef BATTLER_PLAYER + +DOUBLE_BATTLE_TEST("Sticky Web has correct interactions with Mirror Armor - no one has their Speed lowered if the set upper switched (Multi)") +{ + u16 speedPlayer, speedOpponent; + + // We need to make sure Sticky Web user saves for both sides, so it doesn't matter who sets it first. + PARAMETRIZE { speedPlayer = 5; speedOpponent = 10; } + PARAMETRIZE { speedPlayer = 10; speedOpponent = 5; } + + GIVEN { + PLAYER(SPECIES_SQUIRTLE) { Speed(speedPlayer); } + PLAYER(SPECIES_CHARMANDER) { Speed(speedPlayer); } + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); Items(ITEM_PECHA_BERRY, ITEM_IRON_BALL); Speed(speedOpponent); } // Iron Ball, so that flying type Corviknight is affected by Sticky Web. + OPPONENT(SPECIES_CATERPIE) { Speed(speedOpponent); } + OPPONENT(SPECIES_WEEDLE) { Speed(speedOpponent); } + OPPONENT(SPECIES_PIDGEY) { Speed(speedOpponent); } // Flying type,so not affected by Sticky Web. + } WHEN { + TURN { MOVE(opponentLeft, MOVE_STICKY_WEB); MOVE(playerRight, MOVE_STICKY_WEB); } + TURN { SWITCH(opponentLeft, 2); } + TURN { SWITCH(playerRight, 2); } + } SCENE { + if (speedPlayer > speedOpponent) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, playerRight); + MESSAGE("A sticky web has been laid out on the ground around the opposing team!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponentLeft); + MESSAGE("A sticky web has been laid out on the ground around your team!"); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponentLeft); + MESSAGE("A sticky web has been laid out on the ground around your team!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, playerRight); + MESSAGE("A sticky web has been laid out on the ground around the opposing team!"); + } + + SEND_IN_MESSAGE("Corviknight"); + MESSAGE("Corviknight was caught in a sticky web!"); + ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(playerRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Sticky Web has correct interactions with Mirror Armor - no one has their Speed lowered if the set upper fainted (Multi)") +{ + bool8 hasReplacement; + + // We need to make sure Sticky Web user saves for both sides, so it doesn't matter who sets it first. + PARAMETRIZE {hasReplacement = TRUE;} + PARAMETRIZE {hasReplacement = FALSE;} + + GIVEN { + ASSUME(GetMoveEffect(MOVE_MEMENTO) == EFFECT_MEMENTO); + PLAYER(SPECIES_SQUIRTLE) {Speed(5); } + PLAYER(SPECIES_CHARMANDER) {Speed(5); } + PLAYER(SPECIES_CORVIKNIGHT) {Ability(ABILITY_MIRROR_ARMOR); Items(ITEM_PECHA_BERRY, ITEM_IRON_BALL); Speed(5); } // Iron Ball, so that flying type Corviknight is affected by Sticky Web. + OPPONENT(SPECIES_CATERPIE) {Speed(7); } + OPPONENT(SPECIES_WEEDLE) {Speed(7); } + if (hasReplacement) { + OPPONENT(SPECIES_PIDGEY) {Speed(7); } + } + + } WHEN { + TURN { MOVE(opponentLeft, MOVE_STICKY_WEB); } + if (hasReplacement) { + TURN { MOVE(opponentLeft, MOVE_MEMENTO, target:playerLeft); SEND_OUT(opponentLeft, 2); } + } else { + TURN { MOVE(opponentLeft, MOVE_MEMENTO, target:playerLeft);} + } + TURN { SWITCH(playerRight, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponentLeft); + MESSAGE("A sticky web has been laid out on the ground around your team!"); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_MEMENTO, opponentLeft); + MESSAGE("The opposing Caterpie fainted!"); + if (hasReplacement) { + MESSAGE("2 sent out Pidgey!"); + } + + SEND_IN_MESSAGE("Corviknight"); + MESSAGE("Corviknight was caught in a sticky web!"); + ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + } THEN { + if (hasReplacement) { + EXPECT_EQ(opponentLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } + EXPECT_EQ(playerLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(playerRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Sticky Web setter has their speed lowered with Mirror Armor even after Ally Switch (Multi)") +{ + GIVEN { + PLAYER(SPECIES_SQUIRTLE); + PLAYER(SPECIES_CHARMANDER); + PLAYER(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); Items(ITEM_PECHA_BERRY, ITEM_IRON_BALL); } // Iron Ball, so that flying type Corviknight is affected by Sticky Web. + OPPONENT(SPECIES_CATERPIE); + OPPONENT(SPECIES_NATU); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_STICKY_WEB); } + TURN { MOVE(opponentRight, MOVE_ALLY_SWITCH); } + TURN { SWITCH(playerRight, 2); } + } SCENE { + // Turn 1 - set up sticky web + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponentLeft); + MESSAGE("A sticky web has been laid out on the ground around your team!"); + // Turn 2 - ally switch + MESSAGE("The opposing Natu used Ally Switch!"); + // turn 3 - send our corviknight + SEND_IN_MESSAGE("Corviknight"); + MESSAGE("Corviknight was caught in a sticky web!"); + ABILITY_POPUP(playerRight, ABILITY_MIRROR_ARMOR); + // sticky web setter - caterpie (now opponentRight) gets speed lowered + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("The opposing Caterpie's Speed fell!"); + } +} +#endif diff --git a/test/battle/move_effect/stomping_tantrum.c b/test/battle/move_effect/stomping_tantrum.c index 43580f685147..eadc5a852f87 100644 --- a/test/battle/move_effect/stomping_tantrum.c +++ b/test/battle/move_effect/stomping_tantrum.c @@ -157,3 +157,56 @@ SINGLE_BATTLE_TEST("Stomping Tantrum will deal double damage if user was immune EXPECT_MUL_EQ(damage[0], Q_4_12(2.0), damage[1]); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Stomping Tantrum will deal double damage if user failed to attack due to paralysis (Multi)") +{ + s16 damage[3]; + PASSES_RANDOMLY(25, 100, RNG_PARALYSIS); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(100); Items(ITEM_PECHA_BERRY, ITEM_POTION); }; + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); Items(ITEM_PECHA_BERRY, ITEM_LUM_BERRY); }; + } WHEN { + TURN { MOVE(player, MOVE_STOMPING_TANTRUM); MOVE(opponent, MOVE_THUNDER_WAVE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_TRICK); } + TURN { MOVE(player, MOVE_STOMPING_TANTRUM); } + TURN { MOVE(player, MOVE_STOMPING_TANTRUM); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STOMPING_TANTRUM, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_WAVE, opponent); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponent); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_STOMPING_TANTRUM, player); + HP_BAR(opponent, captureDamage: &damage[1]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_STOMPING_TANTRUM, player); + HP_BAR(opponent, captureDamage: &damage[2]); + } THEN { + EXPECT_MUL_EQ(damage[0], Q_4_12(2.0), damage[1]); + EXPECT_EQ(damage[0], damage[2]); + } +} + +SINGLE_BATTLE_TEST("Stomping Tantrum will not deal double if it missed (Multi)") +{ + s16 damage[2]; + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_BRIGHTPOWDER); }; + } WHEN { + TURN { MOVE(player, MOVE_STOMPING_TANTRUM); } + TURN { MOVE(player, MOVE_STOMPING_TANTRUM, hit: FALSE); } + TURN { MOVE(player, MOVE_STOMPING_TANTRUM); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STOMPING_TANTRUM, player); + HP_BAR(opponent, captureDamage: &damage[0]); + MESSAGE("Wobbuffet's attack missed!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STOMPING_TANTRUM, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[0], Q_4_12(2.0), damage[1]); + } +} +#endif diff --git a/test/battle/move_effect/stone_axe.c b/test/battle/move_effect/stone_axe.c index d65798d18b27..cf4cc34d0719 100644 --- a/test/battle/move_effect/stone_axe.c +++ b/test/battle/move_effect/stone_axe.c @@ -92,3 +92,37 @@ SINGLE_BATTLE_TEST("Stone Axe fails to set up hazards if user faints") NOT MESSAGE("Pointed stones float in the air around the opposing team!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Stone Axe sets up hazards after any ability activation (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SKARMORY) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_WEAK_ARMOR); } + } WHEN { + TURN { MOVE(player, MOVE_STONE_AXE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STONE_AXE, player); + ABILITY_POPUP(opponent, ABILITY_WEAK_ARMOR); + MESSAGE("Pointed stones float in the air around the opposing team!"); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Stone Axe fails to set up hazards if user faints (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_ROCKY_HELMET); } + } WHEN { + TURN { MOVE(player, MOVE_STONE_AXE); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STONE_AXE, player); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the opposing Wobbuffet's Rocky Helmet!"); + NOT MESSAGE("Pointed stones float in the air around the opposing team!"); + } +} +#endif diff --git a/test/battle/move_effect/strength_sap.c b/test/battle/move_effect/strength_sap.c index 675d5090eeea..74592e364228 100644 --- a/test/battle/move_effect/strength_sap.c +++ b/test/battle/move_effect/strength_sap.c @@ -167,3 +167,30 @@ SINGLE_BATTLE_TEST("Strength Sap restores more HP if Big Root is held", s16 hp) EXPECT_GT(abs(results[1].hp), abs(results[0].hp)); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Strength Sap restores more HP if Big Root is held (Multi)", s16 hp) +{ + u32 item; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_BIG_ROOT; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_BIG_ROOT].holdEffect == HOLD_EFFECT_BIG_ROOT); + PLAYER(SPECIES_WOBBUFFET) { HP(200); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET) { Attack(100); } + } WHEN { + TURN { MOVE(player, MOVE_STRENGTH_SAP); } + } SCENE { + MESSAGE("Wobbuffet used Strength Sap!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRENGTH_SAP, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wobbuffet's Attack fell!"); + HP_BAR(player, captureDamage: &results[i].hp); + MESSAGE("The opposing Wobbuffet had its energy drained!"); + } FINALLY { + EXPECT_GT(abs(results[1].hp), abs(results[0].hp)); + } +} +#endif diff --git a/test/battle/move_effect/stuff_cheeks.c b/test/battle/move_effect/stuff_cheeks.c index b8dda1d8b6b5..d104cd8d8b49 100644 --- a/test/battle/move_effect/stuff_cheeks.c +++ b/test/battle/move_effect/stuff_cheeks.c @@ -116,3 +116,118 @@ AI_SINGLE_BATTLE_TEST("AI uses Stuff Cheeks") TURN { EXPECT_MOVE(opponent, MOVE_STUFF_CHEEKS); } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Stuff Cheeks can be used even if Unnerve is present (Traits)") +{ + GIVEN { + PLAYER(SPECIES_SKWOVET) { Item(ITEM_LIECHI_BERRY); } + OPPONENT(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); Innates(ABILITY_UNNERVE); } + } WHEN { + TURN { MOVE(player, MOVE_STUFF_CHEEKS); } + } SCENE { + MESSAGE("Skwovet used Stuff Cheeks!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STUFF_CHEEKS, player); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Stuff Cheeks cannot be used if the user doesn't hold a berry (Multi)") +{ + u16 item = 0; + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_LIECHI_BERRY; } + GIVEN { + PLAYER(SPECIES_SKWOVET) { Items(item, ITEM_PECHA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (item == ITEM_NONE) + TURN { MOVE(player, MOVE_STUFF_CHEEKS, allowed: FALSE); MOVE(player, MOVE_CELEBRATE); } + else + TURN { MOVE(player, MOVE_STUFF_CHEEKS); } + } SCENE { + if (item == ITEM_NONE) + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + else + ANIMATION(ANIM_TYPE_MOVE, MOVE_STUFF_CHEEKS, player); + } +} + +SINGLE_BATTLE_TEST("Stuff Cheeks raises Defense by 2 stages after consuming the berry and gaining its effect (Multi)") +{ + GIVEN { + PLAYER(SPECIES_SKWOVET) { Items(ITEM_GREAT_BALL, ITEM_LIECHI_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STUFF_CHEEKS); } + } SCENE { + MESSAGE("Skwovet used Stuff Cheeks!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STUFF_CHEEKS, player); + MESSAGE("Using Liechi Berry, the Attack of Skwovet rose!"); + MESSAGE("Skwovet's Defense sharply rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 2); + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Stuff Cheeks can be used even if Unnerve is present (Multi)") +{ + GIVEN { + PLAYER(SPECIES_SKWOVET) { Items(ITEM_LIECHI_BERRY, ITEM_PECHA_BERRY); } + OPPONENT(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); } + } WHEN { + TURN { MOVE(player, MOVE_STUFF_CHEEKS); } + } SCENE { + MESSAGE("Skwovet used Stuff Cheeks!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STUFF_CHEEKS, player); + } +} + +SINGLE_BATTLE_TEST("Stuff Cheeks can be used even if Magic Room is active (Multi)") +{ + GIVEN { + PLAYER(SPECIES_SKWOVET) { Items(ITEM_LIECHI_BERRY, ITEM_GREAT_BALL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(opponent, MOVE_MAGIC_ROOM); + MOVE(player, MOVE_STUFF_CHEEKS); + } + } SCENE { + MESSAGE("Skwovet used Stuff Cheeks!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STUFF_CHEEKS, player); + MESSAGE("Using Liechi Berry, the Attack of Skwovet rose!"); + } +} + +SINGLE_BATTLE_TEST("Stuff Cheeks fails if the user's berry is removed before they use the move (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_KNOCK_OFF) == EFFECT_KNOCK_OFF); + PLAYER(SPECIES_SKWOVET) { Items(ITEM_LIECHI_BERRY, ITEM_PECHA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_KNOCK_OFF); } + TURN { MOVE(opponent, MOVE_KNOCK_OFF); MOVE(player, MOVE_STUFF_CHEEKS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, opponent); + MESSAGE("Skwovet used Stuff Cheeks!"); + MESSAGE("But it failed!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI uses Stuff Cheeks (Multi)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_STUFF_CHEEKS) == EFFECT_STUFF_CHEEKS); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_HEADBUTT); } + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_HEADBUTT); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_LIECHI_BERRY, ITEM_PECHA_BERRY); Moves(MOVE_HEADBUTT, MOVE_STUFF_CHEEKS); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_STUFF_CHEEKS); } + } +} +#endif diff --git a/test/battle/move_effect/substitute.c b/test/battle/move_effect/substitute.c index ce174ec02596..7d897f641735 100644 --- a/test/battle/move_effect/substitute.c +++ b/test/battle/move_effect/substitute.c @@ -177,3 +177,34 @@ SINGLE_BATTLE_TEST("Substitute hits are detected by SUB_HIT, break FALSE, failin } TO_DO_BATTLE_TEST("Baton Pass passes Substitutes"); + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Substitute's HP cost can trigger a berry (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_SITRUS_BERRY].battleUsage == EFFECT_ITEM_RESTORE_HP); + PLAYER(SPECIES_WOBBUFFET) { HP(300); Items(ITEM_PECHA_BERRY, ITEM_SITRUS_BERRY); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, player); + MESSAGE("Wobbuffet restored its health using its Sitrus Berry!"); + } +} + +SINGLE_BATTLE_TEST("Substitute's HP cost doesn't trigger effects that trigger on damage taken (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_AIR_BALLOON); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, player); + MESSAGE("Wobbuffet put in a substitute!"); + NOT MESSAGE("Wobbuffet's Air Balloon popped!"); + } +} +#endif diff --git a/test/battle/move_effect/synthesis.c b/test/battle/move_effect/synthesis.c index 5245ab9b389d..e12d438a073c 100644 --- a/test/battle/move_effect/synthesis.c +++ b/test/battle/move_effect/synthesis.c @@ -75,3 +75,23 @@ TO_DO_BATTLE_TEST("TODO: Synthesis recovers 1/8 of the user's max HP in Rain, Sa TO_DO_BATTLE_TEST("TODO: Synthesis recovers 2/4 of the user's max HP while it is day (Gen2)") TO_DO_BATTLE_TEST("TODO: Synthesis recovers 2/2 of the user's max HP in Sunlight while it is day (Gen2)") TO_DO_BATTLE_TEST("TODO: Synthesis recovers 2/8 of the user's max HP in Rain, Sandstorm, Hail, and Snow while it is day (Gen2)") + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Synthesis recovers regular amount in sandstorm if holding utility umbrella (Multi)") +{ + u32 item; + PARAMETRIZE { item = ITEM_LIFE_ORB; } + PARAMETRIZE { item = ITEM_UTILITY_UMBRELLA; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); MaxHP(400); Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SANDSTORM); MOVE(player, MOVE_SYNTHESIS); } + } SCENE { + if (item != ITEM_UTILITY_UMBRELLA) + HP_BAR(player, damage: -(400 / 4)); + else + HP_BAR(player, damage: -(400 / 2)); + } +} +#endif diff --git a/test/battle/move_effect/teatime.c b/test/battle/move_effect/teatime.c index b5935d0cde1b..3891b5e1d895 100644 --- a/test/battle/move_effect/teatime.c +++ b/test/battle/move_effect/teatime.c @@ -270,3 +270,399 @@ SINGLE_BATTLE_TEST("Teatime triggers Motor Drive if it has been affected by Elec MESSAGE("Using Liechi Berry, the Attack of the opposing Wobbuffet rose!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Teatime causes the user to consume its Berry, even in the pressence of Unnerve (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LIECHI_BERRY); } + OPPONENT(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); Innates(ABILITY_UNNERVE); } + } WHEN { + TURN { MOVE(player, MOVE_TEATIME); } + } SCENE { + MESSAGE("Wobbuffet used Teatime!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEATIME, player); + MESSAGE("Using Liechi Berry, the Attack of Wobbuffet rose!"); + } +} + +SINGLE_BATTLE_TEST("Teatime triggers Volt Absorb if it has been affected by Electrify or Plasma Fists, even when not holding a Berry (Traits)") +{ + u32 move; + u32 item = ITEM_LIECHI_BERRY; + bool8 shouldTriggerAbility = TRUE; + + PARAMETRIZE { move = MOVE_CELEBRATE; shouldTriggerAbility = FALSE; } + PARAMETRIZE { move = MOVE_ELECTRIFY; } + PARAMETRIZE { move = MOVE_PLASMA_FISTS; } + PARAMETRIZE { move = MOVE_ELECTRIFY; item = ITEM_NONE; } + PARAMETRIZE { move = MOVE_PLASMA_FISTS; item = ITEM_NONE; } + + GIVEN { + PLAYER(SPECIES_JOLTEON) { Ability(ABILITY_STATIC); Innates(ABILITY_VOLT_ABSORB); Item(item); HP(55); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LIECHI_BERRY); } + } WHEN { + TURN { + MOVE(player, move); + MOVE(opponent, MOVE_TEATIME); + } + } SCENE { + MESSAGE("The opposing Wobbuffet used Teatime!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEATIME, opponent); + if (shouldTriggerAbility) + { + ABILITY_POPUP(player, ABILITY_VOLT_ABSORB); + HP_BAR(player, damage: -25); + NOT MESSAGE("Using Liechi Berry, the Attack of Jolteon rose!"); + } else { + NOT ABILITY_POPUP(player, ABILITY_VOLT_ABSORB); + MESSAGE("Using Liechi Berry, the Attack of Jolteon rose!"); + } + MESSAGE("Using Liechi Berry, the Attack of the opposing Wobbuffet rose!"); + } +} + +SINGLE_BATTLE_TEST("Teatime triggers Lightning Rod if it has been affected by Electrify or Plasma Fists, even when not holding a Berry (Traits)") +{ + u32 move; + u32 item = ITEM_LIECHI_BERRY; + bool8 shouldTriggerAbility = TRUE; + + PARAMETRIZE { move = MOVE_CELEBRATE; shouldTriggerAbility = FALSE; } + PARAMETRIZE { move = MOVE_ELECTRIFY; } + PARAMETRIZE { move = MOVE_PLASMA_FISTS; } + PARAMETRIZE { move = MOVE_ELECTRIFY; item = ITEM_NONE; } + PARAMETRIZE { move = MOVE_PLASMA_FISTS; item = ITEM_NONE; } + + GIVEN { + PLAYER(SPECIES_PIKACHU) { Ability(ABILITY_STATIC); Innates(ABILITY_LIGHTNING_ROD); Item(item); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LIECHI_BERRY); } + } WHEN { + TURN { + MOVE(player, move); + MOVE(opponent, MOVE_TEATIME); + } + } SCENE { + MESSAGE("The opposing Wobbuffet used Teatime!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEATIME, opponent); + if (shouldTriggerAbility) + { + ABILITY_POPUP(player, ABILITY_LIGHTNING_ROD); + MESSAGE("Pikachu's Sp. Atk rose!"); + NOT MESSAGE("Using Liechi Berry, the Attack of Pikachu rose!"); + } else { + NONE_OF { + ABILITY_POPUP(player, ABILITY_LIGHTNING_ROD); + MESSAGE("Pikachu's Sp. Atk rose!"); + } + MESSAGE("Using Liechi Berry, the Attack of Pikachu rose!"); + } + MESSAGE("Using Liechi Berry, the Attack of the opposing Wobbuffet rose!"); + } +} + +SINGLE_BATTLE_TEST("Teatime triggers Motor Drive if it has been affected by Electrify or Plasma Fists, even when not holding a Berry (Traits)") +{ + u32 move; + u32 item= ITEM_LIECHI_BERRY; + bool8 shouldTriggerAbility = TRUE; + + PARAMETRIZE { move = MOVE_CELEBRATE; shouldTriggerAbility = FALSE; } + PARAMETRIZE { move = MOVE_ELECTRIFY; } + PARAMETRIZE { move = MOVE_PLASMA_FISTS; } + PARAMETRIZE { move = MOVE_ELECTRIFY; item = ITEM_NONE; } + PARAMETRIZE { move = MOVE_PLASMA_FISTS; item = ITEM_NONE; } + + GIVEN { + PLAYER(SPECIES_ELECTIVIRE) { Ability(ABILITY_VITAL_SPIRIT); Innates(ABILITY_MOTOR_DRIVE); Item(item); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LIECHI_BERRY); } + } WHEN { + TURN { + MOVE(player, move); + MOVE(opponent, MOVE_TEATIME); + } + } SCENE { + MESSAGE("The opposing Wobbuffet used Teatime!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEATIME, opponent); + if (shouldTriggerAbility) + { + ABILITY_POPUP(player, ABILITY_MOTOR_DRIVE); + MESSAGE("Electivire's Speed rose!"); + NOT MESSAGE("Using Liechi Berry, the Attack of Electivire rose!"); + } else { + NONE_OF { + ABILITY_POPUP(player, ABILITY_MOTOR_DRIVE); + MESSAGE("Electivire's Speed rose!"); + } + MESSAGE("Using Liechi Berry, the Attack of Electivire rose!"); + } + MESSAGE("Using Liechi Berry, the Attack of the opposing Wobbuffet rose!"); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Teatime causes the user to consume its Berry, ignoring HP requirements (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LIECHI_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_NONE); } + } WHEN { + TURN { MOVE(player, MOVE_TEATIME); } + } SCENE { + MESSAGE("Wobbuffet used Teatime!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEATIME, player); + MESSAGE("Using Liechi Berry, the Attack of Wobbuffet rose!"); + } +} + +SINGLE_BATTLE_TEST("Teatime causes the user to consume its Berry, even in the pressence of Unnerve (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LIECHI_BERRY); } + OPPONENT(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); } + } WHEN { + TURN { MOVE(player, MOVE_TEATIME); } + } SCENE { + MESSAGE("Wobbuffet used Teatime!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEATIME, player); + MESSAGE("Using Liechi Berry, the Attack of Wobbuffet rose!"); + } +} + +SINGLE_BATTLE_TEST("Teatime causes the user to consume its Berry, even under the effects of Magic Room (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LIECHI_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(opponent, MOVE_MAGIC_ROOM); + MOVE(player, MOVE_TEATIME); + } + } SCENE { + MESSAGE("Wobbuffet used Teatime!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEATIME, player); + MESSAGE("Using Liechi Berry, the Attack of Wobbuffet rose!"); + } +} + +SINGLE_BATTLE_TEST("Teatime causes the user to consume its Berry, ignoring HP requirements, when not used by the Player (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_NONE); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LIECHI_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_TEATIME); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Teatime!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEATIME, opponent); + MESSAGE("Using Liechi Berry, the Attack of the opposing Wobbuffet rose!"); + } +} + +SINGLE_BATTLE_TEST("Teatime causes other Pokemon to consume their Berry even if the user doesn't have a Berry as its held item (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_NONE); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LIECHI_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TEATIME); } + } SCENE { + MESSAGE("Wobbuffet used Teatime!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEATIME, player); + MESSAGE("Using Liechi Berry, the Attack of the opposing Wobbuffet rose!"); + } +} + +SINGLE_BATTLE_TEST("Teatime causes other Pokemon to consume their Berry even if the user doesn't have a Berry as its held item, when not used by the Player (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LIECHI_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_NONE); } + } WHEN { + TURN { MOVE(opponent, MOVE_TEATIME); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Teatime!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEATIME, opponent); + MESSAGE("Using Liechi Berry, the Attack of Wobbuffet rose!"); + } +} + +DOUBLE_BATTLE_TEST("Teatime causes all Pokémon to consume their berry (Multi)") +{ + struct BattlePokemon *user = NULL; + PARAMETRIZE { user = playerLeft; } + PARAMETRIZE { user = playerRight; } + PARAMETRIZE { user = opponentLeft; } + PARAMETRIZE { user = opponentRight; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LIECHI_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LIECHI_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LIECHI_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LIECHI_BERRY); } + } WHEN { + TURN { MOVE(user, MOVE_TEATIME); } + } SCENE { + if (user == playerLeft || user == playerRight) + { + MESSAGE("Wobbuffet used Teatime!"); + } else { + MESSAGE("The opposing Wobbuffet used Teatime!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEATIME, user); + MESSAGE("Using Liechi Berry, the Attack of Wobbuffet rose!"); + MESSAGE("Using Liechi Berry, the Attack of the opposing Wobbuffet rose!"); + MESSAGE("Using Liechi Berry, the Attack of Wobbuffet rose!"); + MESSAGE("Using Liechi Berry, the Attack of the opposing Wobbuffet rose!"); + } +} + +SINGLE_BATTLE_TEST("Teatime fails if no Pokémon is holding a Berry (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_NONE); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_NONE); } + } WHEN { + TURN { MOVE(player, MOVE_TEATIME); } + } SCENE { + MESSAGE("Wobbuffet used Teatime!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TEATIME, player); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Teatime does not affect Pokémon in the semi-invulnerable turn of a move (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_NONE); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LIECHI_BERRY); } + } WHEN { + TURN { + MOVE(opponent, MOVE_FLY); + MOVE(player, MOVE_TEATIME); + } + } SCENE { + MESSAGE("Wobbuffet used Teatime!"); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEATIME, player); + MESSAGE("Using Liechi Berry, the Attack of the opposing Wobbuffet rose!"); + } + } +} + +SINGLE_BATTLE_TEST("Teatime triggers Volt Absorb if it has been affected by Electrify or Plasma Fists, even when not holding a Berry (Multi)") +{ + u32 move; + u32 item = ITEM_LIECHI_BERRY; + bool8 shouldTriggerAbility = TRUE; + + PARAMETRIZE { move = MOVE_CELEBRATE; shouldTriggerAbility = FALSE; } + PARAMETRIZE { move = MOVE_ELECTRIFY; } + PARAMETRIZE { move = MOVE_PLASMA_FISTS; } + PARAMETRIZE { move = MOVE_ELECTRIFY; item = ITEM_NONE; } + PARAMETRIZE { move = MOVE_PLASMA_FISTS; item = ITEM_NONE; } + + GIVEN { + PLAYER(SPECIES_JOLTEON) { Ability(ABILITY_VOLT_ABSORB); Items(ITEM_NONE, item); HP(55); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LIECHI_BERRY); } + } WHEN { + TURN { + MOVE(player, move); + MOVE(opponent, MOVE_TEATIME); + } + } SCENE { + MESSAGE("The opposing Wobbuffet used Teatime!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEATIME, opponent); + if (shouldTriggerAbility) + { + ABILITY_POPUP(player, ABILITY_VOLT_ABSORB); + HP_BAR(player, damage: -25); + NOT MESSAGE("Using Liechi Berry, the Attack of Jolteon rose!"); + } else { + NOT ABILITY_POPUP(player, ABILITY_VOLT_ABSORB); + MESSAGE("Using Liechi Berry, the Attack of Jolteon rose!"); + } + MESSAGE("Using Liechi Berry, the Attack of the opposing Wobbuffet rose!"); + } +} + +SINGLE_BATTLE_TEST("Teatime triggers Lightning Rod if it has been affected by Electrify or Plasma Fists, even when not holding a Berry (Multi)") +{ + u32 move; + u32 item = ITEM_LIECHI_BERRY; + bool8 shouldTriggerAbility = TRUE; + + PARAMETRIZE { move = MOVE_CELEBRATE; shouldTriggerAbility = FALSE; } + PARAMETRIZE { move = MOVE_ELECTRIFY; } + PARAMETRIZE { move = MOVE_PLASMA_FISTS; } + PARAMETRIZE { move = MOVE_ELECTRIFY; item = ITEM_NONE; } + PARAMETRIZE { move = MOVE_PLASMA_FISTS; item = ITEM_NONE; } + + GIVEN { + WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, GEN_5); + PLAYER(SPECIES_PIKACHU) { Ability(ABILITY_LIGHTNING_ROD); Items(ITEM_NONE, item); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LIECHI_BERRY); } + } WHEN { + TURN { + MOVE(player, move); + MOVE(opponent, MOVE_TEATIME); + } + } SCENE { + MESSAGE("The opposing Wobbuffet used Teatime!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEATIME, opponent); + if (shouldTriggerAbility) + { + ABILITY_POPUP(player, ABILITY_LIGHTNING_ROD); + MESSAGE("Pikachu's Sp. Atk rose!"); + NOT MESSAGE("Using Liechi Berry, the Attack of Pikachu rose!"); + } else { + NONE_OF { + ABILITY_POPUP(player, ABILITY_LIGHTNING_ROD); + MESSAGE("Pikachu's Sp. Atk rose!"); + } + MESSAGE("Using Liechi Berry, the Attack of Pikachu rose!"); + } + MESSAGE("Using Liechi Berry, the Attack of the opposing Wobbuffet rose!"); + } +} + +SINGLE_BATTLE_TEST("Teatime triggers Motor Drive if it has been affected by Electrify or Plasma Fists, even when not holding a Berry (Multi)") +{ + u32 move; + u32 item= ITEM_LIECHI_BERRY; + bool8 shouldTriggerAbility = TRUE; + + PARAMETRIZE { move = MOVE_CELEBRATE; shouldTriggerAbility = FALSE; } + PARAMETRIZE { move = MOVE_ELECTRIFY; } + PARAMETRIZE { move = MOVE_PLASMA_FISTS; } + PARAMETRIZE { move = MOVE_ELECTRIFY; item = ITEM_NONE; } + PARAMETRIZE { move = MOVE_PLASMA_FISTS; item = ITEM_NONE; } + + GIVEN { + PLAYER(SPECIES_ELECTIVIRE) { Ability(ABILITY_MOTOR_DRIVE); Items(ITEM_NONE, item); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LIECHI_BERRY); } + } WHEN { + TURN { + MOVE(player, move); + MOVE(opponent, MOVE_TEATIME); + } + } SCENE { + MESSAGE("The opposing Wobbuffet used Teatime!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TEATIME, opponent); + if (shouldTriggerAbility) + { + ABILITY_POPUP(player, ABILITY_MOTOR_DRIVE); + MESSAGE("Electivire's Speed rose!"); + NOT MESSAGE("Using Liechi Berry, the Attack of Electivire rose!"); + } else { + NONE_OF { + ABILITY_POPUP(player, ABILITY_MOTOR_DRIVE); + MESSAGE("Electivire's Speed rose!"); + } + MESSAGE("Using Liechi Berry, the Attack of Electivire rose!"); + } + MESSAGE("Using Liechi Berry, the Attack of the opposing Wobbuffet rose!"); + } +} +#endif diff --git a/test/battle/move_effect/tera_blast.c b/test/battle/move_effect/tera_blast.c index 1e041dda97fb..a29e296162fd 100644 --- a/test/battle/move_effect/tera_blast.c +++ b/test/battle/move_effect/tera_blast.c @@ -186,3 +186,37 @@ SINGLE_BATTLE_TEST("Flying-type Tera Blast does not have its priority boosted by ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Flying-type Tera Blast does not have its priority boosted by Gale Wings (Traits)") +{ + GIVEN { + PLAYER(SPECIES_TALONFLAME) { Ability(ABILITY_FLAME_BODY); Innates(ABILITY_GALE_WINGS); TeraType(TYPE_FLYING); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_QUICK_ATTACK); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Quick Attack!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, opponent); + MESSAGE("Talonflame used Tera Blast!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Stellar-type Tera Blast activates a Stellar-type Pokemon's Weakness Policy (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_WEAKNESS_POLICY); TeraType(TYPE_NORMAL); } + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } + } SCENE { + MESSAGE("Wobbuffet used Tera Blast!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + MESSAGE("It's super effective!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} +#endif diff --git a/test/battle/move_effect/toxic.c b/test/battle/move_effect/toxic.c index 55e09b24eb65..1afca1e02214 100644 --- a/test/battle/move_effect/toxic.c +++ b/test/battle/move_effect/toxic.c @@ -91,3 +91,24 @@ AI_SINGLE_BATTLE_TEST("AI avoids toxic when it can not poison target") TURN { SCORE_EQ(opponent, MOVE_CELEBRATE, MOVE_TOXIC); } // Both get -10 } } + +#if MAX_MON_TRAITS > 1 +AI_SINGLE_BATTLE_TEST("AI avoids toxic when it can not poison target (Traits)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_SNORLAX; ability = ABILITY_IMMUNITY; } + PARAMETRIZE { species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } + PARAMETRIZE { species = SPECIES_NACLI; ability = ABILITY_PURIFYING_SALT; } + PARAMETRIZE { species = SPECIES_BULBASAUR; ability = ABILITY_OVERGROW; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_TOXIC); } + } WHEN { + TURN { SCORE_EQ(opponent, MOVE_CELEBRATE, MOVE_TOXIC); } // Both get -10 + } +} +#endif diff --git a/test/battle/move_effect/toxic_spikes.c b/test/battle/move_effect/toxic_spikes.c index f74e1bddb495..96354c3422b7 100644 --- a/test/battle/move_effect/toxic_spikes.c +++ b/test/battle/move_effect/toxic_spikes.c @@ -291,3 +291,186 @@ SINGLE_BATTLE_TEST("Toxic Spikes: Only two layers can be set up") EXPECT_EQ(toxicSpikesAmount, 2); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Toxic Spikes do not poison airborne Pokemon (Traits)") +{ + u32 species = SPECIES_WOBBUFFET; + u32 item = ITEM_NONE; + u32 move1 = MOVE_CELEBRATE; + u32 move2 = MOVE_CELEBRATE; + bool32 airborne; + + ASSUME(GetSpeciesType(SPECIES_PIDGEY, 1) == TYPE_FLYING); + PARAMETRIZE { species = SPECIES_PIDGEY; airborne = TRUE; } + PARAMETRIZE { species = SPECIES_PIDGEY; item = ITEM_IRON_BALL; airborne = FALSE; } + PARAMETRIZE { species = SPECIES_PIDGEY; move1 = MOVE_GRAVITY; airborne = FALSE; } + PARAMETRIZE { species = SPECIES_PIDGEY; move1 = MOVE_INGRAIN; airborne = FALSE; } + + ASSUME(GetSpeciesAbility(SPECIES_UNOWN, 0) == ABILITY_LEVITATE); + PARAMETRIZE { species = SPECIES_UNOWN; airborne = TRUE; } + PARAMETRIZE { species = SPECIES_UNOWN; item = ITEM_IRON_BALL; airborne = FALSE; } + PARAMETRIZE { species = SPECIES_UNOWN; move1 = MOVE_GRAVITY; airborne = FALSE; } + PARAMETRIZE { species = SPECIES_UNOWN; move1 = MOVE_INGRAIN; airborne = FALSE; } + + PARAMETRIZE { move1 = MOVE_MAGNET_RISE; airborne = TRUE; } + PARAMETRIZE { move1 = MOVE_MAGNET_RISE; item = ITEM_IRON_BALL; airborne = FALSE; } + PARAMETRIZE { move1 = MOVE_MAGNET_RISE; move2 = MOVE_GRAVITY; airborne = FALSE; } + // Magnet Rise fails under Gravity. + // Magnet Rise fails under Ingrain and vice-versa. + + PARAMETRIZE { item = ITEM_AIR_BALLOON; airborne = TRUE; } + PARAMETRIZE { item = ITEM_AIR_BALLOON; move1 = MOVE_GRAVITY; airborne = FALSE; } + PARAMETRIZE { item = ITEM_AIR_BALLOON; move1 = MOVE_INGRAIN; airborne = FALSE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_LEVITATE); Item(item); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC_SPIKES); MOVE(opponent, move1); } + TURN { MOVE(opponent, move2); } + TURN { MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); } + } SCENE { + if (airborne) { + NOT STATUS_ICON(opponent, poison: TRUE); + } else { + STATUS_ICON(opponent, poison: TRUE); + } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Toxic Spikes do not poison airborne Pokemon (Multi)") +{ + u32 species = SPECIES_WOBBUFFET; + u32 item = ITEM_NONE; + u32 move1 = MOVE_CELEBRATE; + u32 move2 = MOVE_CELEBRATE; + bool32 airborne; + + ASSUME(GetSpeciesType(SPECIES_PIDGEY, 1) == TYPE_FLYING); + PARAMETRIZE { species = SPECIES_PIDGEY; airborne = TRUE; } + PARAMETRIZE { species = SPECIES_PIDGEY; item = ITEM_IRON_BALL; airborne = FALSE; } + PARAMETRIZE { species = SPECIES_PIDGEY; move1 = MOVE_GRAVITY; airborne = FALSE; } + PARAMETRIZE { species = SPECIES_PIDGEY; move1 = MOVE_INGRAIN; airborne = FALSE; } + + ASSUME(GetSpeciesAbility(SPECIES_UNOWN, 0) == ABILITY_LEVITATE); + PARAMETRIZE { species = SPECIES_UNOWN; airborne = TRUE; } + PARAMETRIZE { species = SPECIES_UNOWN; item = ITEM_IRON_BALL; airborne = FALSE; } + PARAMETRIZE { species = SPECIES_UNOWN; move1 = MOVE_GRAVITY; airborne = FALSE; } + PARAMETRIZE { species = SPECIES_UNOWN; move1 = MOVE_INGRAIN; airborne = FALSE; } + + PARAMETRIZE { move1 = MOVE_MAGNET_RISE; airborne = TRUE; } + PARAMETRIZE { move1 = MOVE_MAGNET_RISE; item = ITEM_IRON_BALL; airborne = FALSE; } + PARAMETRIZE { move1 = MOVE_MAGNET_RISE; move2 = MOVE_GRAVITY; airborne = FALSE; } + // Magnet Rise fails under Gravity. + // Magnet Rise fails under Ingrain and vice-versa. + + PARAMETRIZE { item = ITEM_AIR_BALLOON; airborne = TRUE; } + PARAMETRIZE { item = ITEM_AIR_BALLOON; move1 = MOVE_GRAVITY; airborne = FALSE; } + PARAMETRIZE { item = ITEM_AIR_BALLOON; move1 = MOVE_INGRAIN; airborne = FALSE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(species) { Items(ITEM_PECHA_BERRY, item); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC_SPIKES); MOVE(opponent, move1); } + TURN { MOVE(opponent, move2); } + TURN { MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); } + } SCENE { + if (airborne) { + NOT STATUS_ICON(opponent, poison: TRUE); + } else { + STATUS_ICON(opponent, poison: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Toxic Spikes are removed by grounded Poison-type Pokémon on switch in (Multi)") +{ + u32 species; + u32 item = ITEM_NONE; + u32 move = MOVE_CELEBRATE; + bool32 grounded; + PARAMETRIZE { species = SPECIES_EKANS; grounded = TRUE; } + PARAMETRIZE { species = SPECIES_ZUBAT; grounded = FALSE; } + PARAMETRIZE { species = SPECIES_ZUBAT; item = ITEM_IRON_BALL; grounded = TRUE; } + PARAMETRIZE { species = SPECIES_ZUBAT; move = MOVE_GRAVITY; grounded = TRUE; } + PARAMETRIZE { species = SPECIES_ZUBAT; move = MOVE_INGRAIN; grounded = TRUE; } + GIVEN { + ASSUME(GetSpeciesType(SPECIES_EKANS, 0) == TYPE_POISON); + ASSUME(GetSpeciesType(SPECIES_ZUBAT, 0) == TYPE_POISON); + ASSUME(GetSpeciesType(SPECIES_ZUBAT, 1) == TYPE_FLYING); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(species) { Items(ITEM_PECHA_BERRY, item); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC_SPIKES); MOVE(opponent, move); } + TURN { MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); } + TURN { SWITCH(opponent, 0); } + } SCENE { + if (grounded) { + NOT STATUS_ICON(opponent, poison: TRUE); + MESSAGE("The poison spikes disappeared from the ground around the opposing team!"); + NOT STATUS_ICON(opponent, poison: TRUE); + } else { + NOT STATUS_ICON(opponent, poison: TRUE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BATON_PASS, opponent); + STATUS_ICON(opponent, poison: TRUE); + } + } +} + +// Tested in Gen 7 on cartridge +SINGLE_BATTLE_TEST("Toxic Spikes are not removed by Poison-type Pokémon affected by Magnet Rise on switch in (Multi)") +{ + GIVEN { + ASSUME(GetSpeciesType(SPECIES_EKANS, 0) == TYPE_POISON); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EKANS); + } WHEN { + TURN { MOVE(opponent, MOVE_MAGNET_RISE); } + TURN { MOVE(player, MOVE_TOXIC_SPIKES); MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); } + TURN { SWITCH(opponent, 0); } + } SCENE { + NOT MESSAGE("The poison spikes disappeared from the ground around the opposing team!"); + STATUS_ICON(opponent, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Toxic Spikes inflicts poison on switch in after Primal Reversed mon fainted (Multi)") // Oddly specific, but encountered during testing +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_MEMENTO) == EFFECT_MEMENTO); // Faints the user. + PLAYER(SPECIES_WOBBUFFET) {Speed(5); } + PLAYER(SPECIES_GROUDON) { Items(ITEM_RED_ORB); Speed(1); } + PLAYER(SPECIES_WYNAUT) {Speed(5); } + OPPONENT(SPECIES_WOBBUFFET) {Speed(15); } + } WHEN { + TURN { MOVE(opponent, MOVE_TOXIC_SPIKES); } + TURN { SWITCH(player, 1); } + TURN { MOVE(player, MOVE_MEMENTO); SEND_OUT(player, 2); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Toxic Spikes!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, opponent); + MESSAGE("Poison spikes were scattered on the ground all around your team!"); + // Switch in + SEND_IN_MESSAGE("Groudon"); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + STATUS_ICON(player, poison: TRUE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, player); + MESSAGE("Groudon's Primal Reversion! It reverted to its primal state!"); + // Memento + MESSAGE("Groudon used Memento!"); + MESSAGE("Groudon fainted!"); + // 2nd switch-in + SEND_IN_MESSAGE("Wynaut"); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + STATUS_ICON(player, poison: TRUE); + } +} +#endif diff --git a/test/battle/move_effect/trick.c b/test/battle/move_effect/trick.c index ffd9975bdd41..7c2426a75726 100644 --- a/test/battle/move_effect/trick.c +++ b/test/battle/move_effect/trick.c @@ -195,3 +195,184 @@ SINGLE_BATTLE_TEST("Trick can be used against targets with an active form change ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponent); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Trick swaps held items (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LUM_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TRICK); } + } THEN { + EXPECT(player->item == ITEM_LUM_BERRY); + EXPECT(opponent->item == ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Trick succeeds if only the user has an item (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TRICK); } + } THEN { + EXPECT(player->item == ITEM_NONE); + EXPECT(opponent->item == ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Trick succeeds if only the target has an item (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LUM_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TRICK); } + } THEN { + EXPECT(player->item == ITEM_LUM_BERRY); + EXPECT(opponent->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Trick fails if either item is Mail (Multi)") +{ + u16 atkItem = ITEM_NONE, defItem = ITEM_NONE; + + ASSUME(ItemIsMail(ITEM_ORANGE_MAIL)); + PARAMETRIZE { atkItem = ITEM_ORANGE_MAIL; defItem = ITEM_NONE; } + PARAMETRIZE { atkItem = ITEM_ORAN_BERRY; defItem = ITEM_ORANGE_MAIL; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, atkItem); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, defItem); } + } WHEN { + TURN { MOVE(player, MOVE_TRICK); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(player->item == atkItem); + EXPECT(opponent->item == defItem); + } +} + +SINGLE_BATTLE_TEST("Trick fails if either item is a Z-Crystal (Multi)") +{ + u16 atkItem = ITEM_NONE, defItem = ITEM_NONE; + + ASSUME(GetItemHoldEffect(ITEM_FIGHTINIUM_Z) == HOLD_EFFECT_Z_CRYSTAL); + PARAMETRIZE { atkItem = ITEM_FIGHTINIUM_Z; defItem = ITEM_NONE; } + PARAMETRIZE { atkItem = ITEM_SITRUS_BERRY; defItem = ITEM_FIGHTINIUM_Z; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, atkItem); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, defItem); } + } WHEN { + TURN { MOVE(player, MOVE_TRICK); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(player->item == atkItem); + EXPECT(opponent->item == defItem); + } +} + +SINGLE_BATTLE_TEST("Trick fails if either battler holds a Mega Stone (Multi)") +{ + u16 atkItem = ITEM_NONE, defItem = ITEM_NONE; + u16 atkSpecies = SPECIES_WOBBUFFET, defSpecies = SPECIES_WOBBUFFET; + + PARAMETRIZE { atkSpecies = SPECIES_BLAZIKEN; atkItem = ITEM_BLAZIKENITE; defSpecies = SPECIES_WOBBUFFET; defItem = ITEM_SITRUS_BERRY; } + PARAMETRIZE { atkSpecies = SPECIES_WOBBUFFET; atkItem = ITEM_SITRUS_BERRY; defSpecies = SPECIES_BLAZIKEN; defItem = ITEM_BLAZIKENITE; } + + GIVEN { + PLAYER(atkSpecies) { Items(ITEM_NONE, atkItem); } + OPPONENT(defSpecies) { Items(ITEM_NONE, defItem); } + } WHEN { + TURN { MOVE(player, MOVE_TRICK); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(player->item == atkItem); + EXPECT(opponent->item == defItem); + } +} + +SINGLE_BATTLE_TEST("Trick fails if an item changes the holder's form (Multi)") +{ + u16 atkItem = ITEM_NONE, defItem = ITEM_NONE; + + PARAMETRIZE { atkItem = ITEM_GRISEOUS_CORE; defItem = ITEM_SITRUS_BERRY; } + PARAMETRIZE { atkItem = ITEM_SITRUS_BERRY; defItem = ITEM_GRISEOUS_CORE; } + + GIVEN { + PLAYER(SPECIES_GIRATINA_ORIGIN) { Items(ITEM_NONE, atkItem); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, defItem); } + } WHEN { + TURN { MOVE(player, MOVE_TRICK); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(player->item == atkItem); + EXPECT(opponent->item == defItem); + } +} + +SINGLE_BATTLE_TEST("Trick doesn't fail if the user has Sticky Hold (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_STICKY_HOLD); Items(ITEM_NONE, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LUM_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TRICK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, player); + } THEN { + EXPECT(player->item == ITEM_LUM_BERRY); + EXPECT(opponent->item == ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Trick fails against Sticky Hold (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_STICKY_HOLD); Items(ITEM_NONE, ITEM_LUM_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TRICK); } + } SCENE { + MESSAGE("The opposing Wobbuffet's Sticky Hold made Trick ineffective!"); + } THEN { + EXPECT(player->item == ITEM_SITRUS_BERRY); + EXPECT(opponent->item == ITEM_LUM_BERRY); + } +} + +SINGLE_BATTLE_TEST("Trick fails if the target is behind a Substitute (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_SITRUS_BERRY); Speed(50); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NONE, ITEM_LUM_BERRY); Speed(100); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_TRICK); } + } SCENE { + MESSAGE("But it failed!"); + } THEN { + EXPECT(player->item == ITEM_SITRUS_BERRY); + EXPECT(opponent->item == ITEM_LUM_BERRY); + } +} + +SINGLE_BATTLE_TEST("Trick can be used against targets with an active form change that doesn't require items (Multi)") +{ + GIVEN { + PLAYER(SPECIES_XERNEAS); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_NUGGET, ITEM_ORAN_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_TRICK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponent); + } +} +#endif diff --git a/test/battle/move_effect/two_turns_attack.c b/test/battle/move_effect/two_turns_attack.c index 94540f566e7f..2b53dbb4f20f 100644 --- a/test/battle/move_effect/two_turns_attack.c +++ b/test/battle/move_effect/two_turns_attack.c @@ -469,3 +469,214 @@ SINGLE_BATTLE_TEST("Electro Shot doesn't need to charge with Power Herb") HP_BAR(opponent); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Razor Wind doesn't need to charge with Power Herb (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_POWER_HERB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_RAZOR_WIND); } + } SCENE { + if (B_UPDATED_MOVE_DATA >= GEN_5) { + NOT MESSAGE("Wobbuffet whipped up a whirlwind!"); + MESSAGE("Wobbuffet used Razor Wind!"); + } else + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAZOR_WIND, player); + if (B_UPDATED_MOVE_DATA < GEN_5) + MESSAGE("Wobbuffet whipped up a whirlwind!"); + else + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAZOR_WIND, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet became fully charged due to its Power Herb!"); + if (B_UPDATED_MOVE_DATA < GEN_5) + MESSAGE("Wobbuffet used Razor Wind!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAZOR_WIND, player); + HP_BAR(opponent); + } +} + +DOUBLE_BATTLE_TEST("Razor Wind successfully KOs both opponents (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_POWER_HERB); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WYNAUT) { HP(1); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_RAZOR_WIND); } + } SCENE { + if (B_UPDATED_MOVE_DATA >= GEN_5) { + NOT MESSAGE("Wobbuffet whipped up a whirlwind!"); + MESSAGE("Wobbuffet used Razor Wind!"); + } else + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAZOR_WIND, playerLeft); + if (B_UPDATED_MOVE_DATA < GEN_5) + MESSAGE("Wobbuffet whipped up a whirlwind!"); + else + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAZOR_WIND, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet became fully charged due to its Power Herb!"); + if (B_UPDATED_MOVE_DATA < GEN_5) + MESSAGE("Wobbuffet used Razor Wind!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAZOR_WIND, playerLeft); + HP_BAR(opponentLeft); + MESSAGE("The opposing Wobbuffet fainted!"); + MESSAGE("The opposing Wynaut fainted!"); + } +} + +SINGLE_BATTLE_TEST("Skull Bash doesn't need to charge with Power Herb (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_POWER_HERB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SKULL_BASH); } + } SCENE { + if (B_UPDATED_MOVE_DATA >= GEN_5) { + NOT MESSAGE("Wobbuffet lowered its head!"); + MESSAGE("Wobbuffet used Skull Bash!"); + } else + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKULL_BASH, player); + if (B_UPDATED_MOVE_DATA < GEN_5) + MESSAGE("Wobbuffet lowered its head!"); + else + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKULL_BASH, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Wobbuffet's Defense rose!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet became fully charged due to its Power Herb!"); + if (B_UPDATED_MOVE_DATA < GEN_5) + MESSAGE("Wobbuffet used Skull Bash!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKULL_BASH, player); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Sky Attack doesn't need to charge with Power Herb (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_POWER_HERB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SKY_ATTACK); } + } SCENE { + if (B_UPDATED_MOVE_DATA >= GEN_5) { + NONE_OF { + MESSAGE("Wobbuffet became cloaked in a harsh light!"); + MESSAGE("Wobbuffet is glowing!"); + } + MESSAGE("Wobbuffet used Sky Attack!"); + } else + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_ATTACK, player); + if (B_UPDATED_MOVE_DATA < GEN_4) + MESSAGE("Wobbuffet is glowing!"); + else if (B_UPDATED_MOVE_DATA < GEN_5) + MESSAGE("Wobbuffet became cloaked in a harsh light!"); + else + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_ATTACK, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet became fully charged due to its Power Herb!"); + if (B_UPDATED_MOVE_DATA < GEN_5) + MESSAGE("Wobbuffet used Sky Attack!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_ATTACK, player); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Solar Beam's power is halved in a Sandstorm (Multi)", s16 damage) +{ + u16 move; + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SANDSTORM; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_SAFETY_GOGGLES); }; + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_SOLAR_BEAM); } + TURN { SKIP_TURN(player); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Solar Blade's power is halved in a Sandstorm (Multi)", s16 damage) +{ + u16 move; + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SANDSTORM; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_SAFETY_GOGGLES); }; + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_SOLAR_BLADE); } + TURN { SKIP_TURN(player); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Solar Beam's power is halved in Hail (Multi)", s16 damage) +{ + u16 move; + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_HAIL; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_SAFETY_GOGGLES); }; + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_SOLAR_BEAM); } + TURN { SKIP_TURN(player); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Solar Blade's power is halved in Hail (Multi)", s16 damage) +{ + u16 move; + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_HAIL; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_SAFETY_GOGGLES); }; + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_SOLAR_BLADE); } + TURN { SKIP_TURN(player); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Electro Shot doesn't need to charge with Power Herb (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_POWER_HERB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ELECTRO_SHOT); } + } SCENE { + MESSAGE("Wobbuffet used Electro Shot!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRO_SHOT, player); + MESSAGE("Wobbuffet absorbed electricity!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Wobbuffet's Sp. Atk rose!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet became fully charged due to its Power Herb!"); + NONE_OF { + MESSAGE("Wobbuffet used Electro Shot!"); + } + HP_BAR(opponent); + } +} +#endif diff --git a/test/battle/move_effect/upper_hand.c b/test/battle/move_effect/upper_hand.c index 49dc21bf49df..bbb088e40f97 100644 --- a/test/battle/move_effect/upper_hand.c +++ b/test/battle/move_effect/upper_hand.c @@ -173,3 +173,97 @@ SINGLE_BATTLE_TEST("Upper Hand failing will prevent Protean activation") } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Upper Hand fails if the target is not using a priority move (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_DRAINING_KISS) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMovePriority(MOVE_DRAINING_KISS) == 0); + PLAYER(SPECIES_MIENSHAO); + OPPONENT(SPECIES_COMFEY) { Ability(ABILITY_NATURAL_CURE); Innates(ABILITY_FLOWER_VEIL); } + } WHEN { + TURN { MOVE(opponent, MOVE_DRAINING_KISS); MOVE(player, MOVE_UPPER_HAND); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_UPPER_HAND, player); + MESSAGE("Mienshao used Upper Hand!"); + MESSAGE("But it failed!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAINING_KISS, opponent); + HP_BAR(player); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Upper Hand succeeds if the target's move is boosted in priority by an Ability (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_DRAINING_KISS) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMovePriority(MOVE_DRAINING_KISS) == 0); + ASSUME(IsHealingMove(MOVE_DRAINING_KISS)); // Doesn't have the Healing Move flag in Gen 5 + PLAYER(SPECIES_MIENSHAO) { Speed(10); } + OPPONENT(SPECIES_COMFEY) { Speed(5); Ability(ABILITY_NATURAL_CURE); Innates(ABILITY_TRIAGE); } + } WHEN { + TURN { MOVE(opponent, MOVE_DRAINING_KISS); MOVE(player, MOVE_UPPER_HAND); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_UPPER_HAND, player); + HP_BAR(opponent); + MESSAGE("The opposing Comfey flinched and couldn't move!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAINING_KISS, opponent); + } +} + +SINGLE_BATTLE_TEST("Upper Hand fails if the target moves first (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_DRAINING_KISS) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMovePriority(MOVE_DRAINING_KISS) == 0); + ASSUME(IsHealingMove(MOVE_DRAINING_KISS)); // Doesn't have the Healing Move flag in Gen 5 + PLAYER(SPECIES_MIENSHAO) { Speed(5); } + OPPONENT(SPECIES_COMFEY) { Speed(10); Ability(ABILITY_NATURAL_CURE); Innates(ABILITY_TRIAGE); } + } WHEN { + TURN { MOVE(opponent, MOVE_DRAINING_KISS); MOVE(player, MOVE_UPPER_HAND); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAINING_KISS, opponent); + HP_BAR(player); + HP_BAR(opponent); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_UPPER_HAND, player); + MESSAGE("Mienshao used Upper Hand!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Upper Hand is boosted by Sheer Force (Traits)") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_EXTREME_SPEED) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMovePriority(MOVE_EXTREME_SPEED) == 2); + ASSUME(MoveIsAffectedBySheerForce(MOVE_UPPER_HAND) == TRUE); + PLAYER(SPECIES_HARIYAMA) { Ability(ABILITY_VITAL_SPIRIT); Innates(ABILITY_SHEER_FORCE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_EXTREME_SPEED); MOVE(player, MOVE_UPPER_HAND); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_UPPER_HAND, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXTREME_SPEED, opponent); + HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Upper Hand failing will prevent Protean activation (Traits)") +{ + GIVEN { + WITH_CONFIG(CONFIG_PROTEAN_LIBERO, GEN_6); + PLAYER(SPECIES_REGIROCK); + OPPONENT(SPECIES_KECLEON) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_PROTEAN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_UPPER_HAND); } + } SCENE { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_PROTEAN); + ANIMATION(ANIM_TYPE_MOVE, MOVE_UPPER_HAND, player); + } + } +} +#endif diff --git a/test/battle/move_effect/uproar.c b/test/battle/move_effect/uproar.c index 93f8be9c5bb6..0a668bb09b9d 100644 --- a/test/battle/move_effect/uproar.c +++ b/test/battle/move_effect/uproar.c @@ -41,3 +41,27 @@ SINGLE_BATTLE_TEST("Uproar wakes up other pokemon on field") MESSAGE("The uproar woke Wobbuffet!"); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Uproar status causes sleeping Pokémon to wake up during an attack (Traits)") +{ + PASSES_RANDOMLY(1, 2, RNG_RANDOM_TARGET); // test fails if we target soundproof mon + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); } + OPPONENT(SPECIES_VOLTORB) { Ability(ABILITY_STATIC); Innates(ABILITY_SOUNDPROOF); Status1(STATUS1_SLEEP); } + OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_UPROAR); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_UPROAR, playerLeft); + HP_BAR(opponentRight); + MESSAGE("The uproar woke Wobbuffet!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + MESSAGE("The uproar woke the opposing Voltorb!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft); + MESSAGE("The uproar woke the opposing Wobbuffet!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight); + } +} +#endif diff --git a/test/battle/move_effect/weather_ball.c b/test/battle/move_effect/weather_ball.c index 3586b71a3231..639aab48e67d 100644 --- a/test/battle/move_effect/weather_ball.c +++ b/test/battle/move_effect/weather_ball.c @@ -80,3 +80,42 @@ SINGLE_BATTLE_TEST("Weather Ball doubles its power and turns to an Ice-type move } TO_DO_BATTLE_TEST("Weather Ball doesn't double its power or change type if Cloud Nine/Air Lock is on the field"); + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Weather Ball doubles its power and turns to a Rock-type move in a Sandstorm (Multi)", s16 damage) +{ + u16 move; + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SANDSTORM; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MAGMAR) { Items(ITEM_PECHA_BERRY, ITEM_SAFETY_GOGGLES); }; + } WHEN { + TURN { MOVE(player, move); } + TURN { MOVE(player, MOVE_WEATHER_BALL); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(4.0), results[1].damage); // double base power + type effectiveness. + } +} + +SINGLE_BATTLE_TEST("Weather Ball doubles its power and turns to an Ice-type move in Hail and Snow (Multi)", s16 damage) +{ + u16 move; + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_DRAGONAIR) { Items(ITEM_PECHA_BERRY, ITEM_SAFETY_GOGGLES); }; + } WHEN { + TURN { MOVE(player, move); } + TURN { MOVE(player, MOVE_WEATHER_BALL); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(4.0), results[1].damage); // double base power + type effectiveness. + } +} +#endif diff --git a/test/battle/move_effect_secondary/bug_bite.c b/test/battle/move_effect_secondary/bug_bite.c index c0b2bc297909..01bb82ec0e46 100644 --- a/test/battle/move_effect_secondary/bug_bite.c +++ b/test/battle/move_effect_secondary/bug_bite.c @@ -148,3 +148,164 @@ SINGLE_BATTLE_TEST("Bug Bite ignores Unnerve") EXPECT_EQ(opponent->item, ITEM_NONE); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Bug Bite ignores Unnerve (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_TYRANITAR) { Ability(ABILITY_SAND_STREAM); Innates(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); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +// Pretty much copy/paste of the Berry Fling Test. +SINGLE_BATTLE_TEST("Bug Bite eats the target's berry and immediately gains its effect (Multi)") +{ + u16 item = ITEM_NONE; + u32 status1 = STATUS1_NONE, effect = HOLD_EFFECT_NONE, statId = 0; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_ORAN_BERRY; effect = HOLD_EFFECT_RESTORE_HP; } + PARAMETRIZE { item = ITEM_SITRUS_BERRY; effect = HOLD_EFFECT_RESTORE_HP; } + PARAMETRIZE { item = ITEM_ENIGMA_BERRY; effect = HOLD_EFFECT_ENIGMA_BERRY; } + PARAMETRIZE { item = ITEM_LEPPA_BERRY; effect = HOLD_EFFECT_RESTORE_PP; } + PARAMETRIZE { item = ITEM_CHESTO_BERRY; effect = HOLD_EFFECT_CURE_SLP; status1 = STATUS1_SLEEP; } + PARAMETRIZE { item = ITEM_CHERI_BERRY; effect = HOLD_EFFECT_CURE_PAR; status1 = STATUS1_PARALYSIS; } + PARAMETRIZE { item = ITEM_PECHA_BERRY; effect = HOLD_EFFECT_CURE_PSN; status1 = STATUS1_POISON; } + PARAMETRIZE { item = ITEM_PECHA_BERRY; effect = HOLD_EFFECT_CURE_PSN; status1 = STATUS1_TOXIC_POISON; } + PARAMETRIZE { item = ITEM_RAWST_BERRY; effect = HOLD_EFFECT_CURE_BRN; status1 = STATUS1_BURN; } + PARAMETRIZE { item = ITEM_ASPEAR_BERRY; effect = HOLD_EFFECT_CURE_FRZ; status1 = STATUS1_FROSTBITE; } + PARAMETRIZE { item = ITEM_APICOT_BERRY; effect = HOLD_EFFECT_SP_DEFENSE_UP; statId = STAT_SPDEF; } + PARAMETRIZE { item = ITEM_MARANGA_BERRY; effect = HOLD_EFFECT_MARANGA_BERRY; statId = STAT_SPDEF; } + PARAMETRIZE { item = ITEM_GANLON_BERRY; effect = HOLD_EFFECT_DEFENSE_UP; statId = STAT_DEF; } + PARAMETRIZE { item = ITEM_KEE_BERRY; effect = HOLD_EFFECT_KEE_BERRY; statId = STAT_DEF; } + PARAMETRIZE { item = ITEM_LIECHI_BERRY; effect = HOLD_EFFECT_ATTACK_UP; statId = STAT_ATK; } + PARAMETRIZE { item = ITEM_PETAYA_BERRY; effect = HOLD_EFFECT_SP_ATTACK_UP; statId = STAT_SPATK; } + PARAMETRIZE { item = ITEM_SALAC_BERRY; effect = HOLD_EFFECT_SPEED_UP; statId = STAT_SPEED; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(399); MaxHP(400); Status1(status1); Moves(MOVE_SLEEP_TALK, MOVE_BUG_BITE); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_GREAT_BALL, item); } + } WHEN { + // Chesto Berry can only be applied if the Pokémon is asleep and uses Sleep Talk. + if (item == ITEM_CHESTO_BERRY) { + TURN { MOVE(player, MOVE_SLEEP_TALK); } + } else { + TURN { MOVE(player, MOVE_BUG_BITE); } + } + + } SCENE { + if (item == ITEM_CHESTO_BERRY) { + MESSAGE("Wobbuffet used Sleep Talk!"); + } + MESSAGE("Wobbuffet used Bug Bite!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BUG_BITE, player); + HP_BAR(opponent); + if (effect == HOLD_EFFECT_RESTORE_HP || effect == HOLD_EFFECT_ENIGMA_BERRY) { + if (item == ITEM_ORAN_BERRY) { + MESSAGE("Wobbuffet restored its health using its Oran Berry!"); + } else if (item == ITEM_SITRUS_BERRY) { + MESSAGE("Wobbuffet restored its health using its Sitrus Berry!"); + } else { + MESSAGE("Wobbuffet restored its health using its Enigma Berry!"); + } + HP_BAR(player); + } + else if (effect == HOLD_EFFECT_RESTORE_PP) { + MESSAGE("Wobbuffet restored PP to its move Bug Bite using its Leppa Berry!"); + } + else if (status1 != STATUS1_NONE) { + if (status1 == STATUS1_BURN) { + MESSAGE("Wobbuffet's Rawst Berry cured its burn!"); + } else if (status1 == STATUS1_SLEEP) { + MESSAGE("Wobbuffet's Chesto Berry woke it up!"); + } else if (status1 == STATUS1_PARALYSIS) { + MESSAGE("Wobbuffet's Cheri Berry cured its paralysis!"); + } else if (status1 == STATUS1_TOXIC_POISON || status1 == STATUS1_POISON) { + MESSAGE("Wobbuffet's Pecha Berry cured its poison!"); + } else if (status1 == STATUS1_FROSTBITE) { + MESSAGE("Wobbuffet's Aspear Berry cured its frostbite!"); + } + NOT STATUS_ICON(player, status1); + } + else if (statId != 0) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + if (statId == STAT_ATK) { + MESSAGE("Using Liechi Berry, the Attack of Wobbuffet rose!"); + } else if (statId == STAT_DEF) { + if (item == ITEM_GANLON_BERRY) { + MESSAGE("Using Ganlon Berry, the Defense of Wobbuffet rose!"); + } else { + MESSAGE("Using Kee Berry, the Defense of Wobbuffet rose!"); + } + } else if (statId == STAT_SPDEF) { + if (item == ITEM_APICOT_BERRY) { + MESSAGE("Using Apicot Berry, the Sp. Def of Wobbuffet rose!"); + } else { + MESSAGE("Using Maranga Berry, the Sp. Def of Wobbuffet rose!"); + } + } else if (statId == STAT_SPEED) { + MESSAGE("Using Salac Berry, the Speed of Wobbuffet rose!"); + } else if (statId == STAT_SPATK) { + MESSAGE("Using Petaya Berry, the Sp. Atk of Wobbuffet rose!"); + } + } + } THEN { + if (effect == HOLD_EFFECT_RESTORE_HP) { + EXPECT_EQ(player->hp, player->maxHP); + } else if (effect == HOLD_EFFECT_RESTORE_PP) { + EXPECT_EQ(player->pp[1], 20); + } else if (status1 != STATUS1_NONE) { + EXPECT_EQ(player->status1, STATUS1_NONE); + } + else if (statId != 0) { + EXPECT_EQ(player->statStages[statId], DEFAULT_STAT_STAGE + 1); + } + EXPECT_EQ(opponent->item, ITEM_NONE); // Opponent's Berry was eaten. + } +} + +SINGLE_BATTLE_TEST("Tanga Berry activates before Bug Bite (Multi)") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_TANGA_BERRY].holdEffect == HOLD_EFFECT_RESIST_BERRY); + ASSUME(gItemsInfo[ITEM_TANGA_BERRY].holdEffectParam == TYPE_BUG); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) {Items(ITEM_GREAT_BALL, ITEM_TANGA_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_BUG_BITE); } + } SCENE { + MESSAGE("Wobbuffet used Bug Bite!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The Tanga Berry weakened the damage to the opposing Wobbuffet!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BUG_BITE, player); + HP_BAR(opponent); + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Bug Bite ignores Unnerve (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_TYRANITAR) { Ability(ABILITY_UNNERVE); Items(ITEM_GREAT_BALL, 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); + } +} +#endif diff --git a/test/battle/move_effect_secondary/confusion.c b/test/battle/move_effect_secondary/confusion.c index 72a96393506a..45f9f17a82a1 100644 --- a/test/battle/move_effect_secondary/confusion.c +++ b/test/battle/move_effect_secondary/confusion.c @@ -48,3 +48,24 @@ SINGLE_BATTLE_TEST("Alluring Voice confuse effect is removed if it is Sheer Forc } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Alluring Voice confuse effect is removed if it is Sheer Force boosted (Traits)") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_ALLURING_VOICE, MOVE_EFFECT_CONFUSION)); + PLAYER(SPECIES_NIDOKING) { Ability(ABILITY_RIVALRY); Innates(ABILITY_SHEER_FORCE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SWORDS_DANCE); MOVE(player, MOVE_ALLURING_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ALLURING_VOICE, player); + HP_BAR(opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, opponent); + MESSAGE("The opposing Wobbuffet became confused!"); + } + } +} +#endif diff --git a/test/battle/move_effect_secondary/dire_claw.c b/test/battle/move_effect_secondary/dire_claw.c index e45b8e394744..5f95e5cf9c1e 100644 --- a/test/battle/move_effect_secondary/dire_claw.c +++ b/test/battle/move_effect_secondary/dire_claw.c @@ -122,3 +122,43 @@ SINGLE_BATTLE_TEST("Dire Claw cannot poison/paralyze/cause to fall asleep a mon } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Dire Claw cannot poison/paralyze/cause to fall asleep Pokémon with abilities preventing respective statuses (Traits)") +{ + KNOWN_FAILING; + u8 statusAnim; + u16 species; + enum Ability ability; + u32 rng; + if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5) + PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_RAICHU; ability = ABILITY_LIGHTNING_ROD; } + PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_JOLTEON; ability = ABILITY_VOLT_ABSORB; } + PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_ELECTIVIRE; ability = ABILITY_MOTOR_DRIVE; } + PARAMETRIZE { statusAnim = B_ANIM_STATUS_PSN; rng = MOVE_EFFECT_POISON; species = SPECIES_ZANGOOSE; ability = ABILITY_IMMUNITY; } + PARAMETRIZE { statusAnim = B_ANIM_STATUS_SLP; rng = MOVE_EFFECT_SLEEP; species = SPECIES_VIGOROTH; ability = ABILITY_VITAL_SPIRIT; } + PARAMETRIZE { statusAnim = B_ANIM_STATUS_SLP; rng = MOVE_EFFECT_SLEEP; species = SPECIES_HYPNO; ability = ABILITY_INSOMNIA; } + + GIVEN { + WITH_CONFIG(CONFIG_PARALYZE_ELECTRIC, GEN_5); // To prevent Electric paralysis immunity from affecting the test + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_DIRE_CLAW, WITH_RNG(RNG_DIRE_CLAW, rng)); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DIRE_CLAW, player); + HP_BAR(opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_STATUS, statusAnim, opponent); + if (statusAnim == B_ANIM_STATUS_PRZ) { + STATUS_ICON(opponent, paralysis: TRUE); + } else if (statusAnim == B_ANIM_STATUS_SLP) { + STATUS_ICON(opponent, sleep: TRUE); + } else if (statusAnim == B_ANIM_STATUS_PSN) { + STATUS_ICON(opponent, poison: TRUE); + } + } + } +} +#endif diff --git a/test/battle/move_effect_secondary/ion_deluge.c b/test/battle/move_effect_secondary/ion_deluge.c index 003bfc825ff2..cda96b8176a6 100644 --- a/test/battle/move_effect_secondary/ion_deluge.c +++ b/test/battle/move_effect_secondary/ion_deluge.c @@ -116,3 +116,39 @@ SINGLE_BATTLE_TEST("Plasma Fists turns normal type dynamax-moves into electric t MESSAGE("It's super effective!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Plasma Fists type-changing effect does not override Pixilate (Traits)") +{ + GIVEN { + PLAYER(SPECIES_KRABBY) { Speed(300); }; + OPPONENT(SPECIES_SYLVEON) { Speed(1); Ability(ABILITY_CUTE_CHARM); Innates(ABILITY_PIXILATE); } + } WHEN { + TURN { MOVE(player, MOVE_PLASMA_FISTS); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Krabby used Plasma Fists!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLASMA_FISTS, player); + MESSAGE("A deluge of ions showers the battlefield!"); + MESSAGE("The opposing Sylveon used Scratch!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + NOT MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Plasma Fists type-changing effect is applied after Normalize (Traits)") +{ + GIVEN { + PLAYER(SPECIES_KRABBY); + OPPONENT(SPECIES_SKITTY) { Ability(ABILITY_CUTE_CHARM); Innates(ABILITY_NORMALIZE); } + } WHEN { + TURN { MOVE(player, MOVE_PLASMA_FISTS); MOVE(opponent, MOVE_EMBER); } + } SCENE { + MESSAGE("Krabby used Plasma Fists!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLASMA_FISTS, player); + MESSAGE("A deluge of ions showers the battlefield!"); + MESSAGE("The opposing Skitty used Ember!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); + MESSAGE("It's super effective!"); + } +} +#endif diff --git a/test/battle/move_effect_secondary/order_up.c b/test/battle/move_effect_secondary/order_up.c index b24b366c5636..d71ded52935f 100644 --- a/test/battle/move_effect_secondary/order_up.c +++ b/test/battle/move_effect_secondary/order_up.c @@ -170,3 +170,156 @@ DOUBLE_BATTLE_TEST("Order Up is always boosted by Sheer Force", s16 damage) EXPECT_MUL_EQ(results[0].damage, UQ_4_12(1.3), results[2].damage); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Order Up increases a stat based on Tatsugiri's form (Traits)") +{ + u32 species = 0; + PARAMETRIZE { species = SPECIES_TATSUGIRI_CURLY; } + PARAMETRIZE { species = SPECIES_TATSUGIRI_DROOPY; } + PARAMETRIZE { species = SPECIES_TATSUGIRI_STRETCHY; } + + GIVEN { + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + PLAYER(SPECIES_DONDOZO); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_VOLBEAT) { Ability(ABILITY_ILLUMINATE); Innates(ABILITY_PRANKSTER); }; + } WHEN { + TURN { MOVE(opponentRight, MOVE_HAZE); MOVE(playerRight, MOVE_ORDER_UP, target: opponentLeft); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HAZE, opponentRight); // Remove previous stat boosts + ANIMATION(ANIM_TYPE_MOVE, MOVE_ORDER_UP, playerRight); + switch (species) + { + case SPECIES_TATSUGIRI_CURLY: + MESSAGE("Dondozo's Attack rose!"); + break; + case SPECIES_TATSUGIRI_DROOPY: + MESSAGE("Dondozo's Defense rose!"); + break; + case SPECIES_TATSUGIRI_STRETCHY: + MESSAGE("Dondozo's Speed rose!"); + break; + } + } THEN { + switch (species) + { + case SPECIES_TATSUGIRI_CURLY: + EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + break; + case SPECIES_TATSUGIRI_DROOPY: + EXPECT_EQ(playerRight->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + break; + case SPECIES_TATSUGIRI_STRETCHY: + EXPECT_EQ(playerRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 1); + break; + } + } +} + +DOUBLE_BATTLE_TEST("Order Up increases a stat based on Tatsugiri's form even if Tatsugiri fainted inside Dondozo (Traits)") +{ + u32 species = 0; + PARAMETRIZE { species = SPECIES_TATSUGIRI_CURLY; } + PARAMETRIZE { species = SPECIES_TATSUGIRI_DROOPY; } + PARAMETRIZE { species = SPECIES_TATSUGIRI_STRETCHY; } + + GIVEN { + PLAYER(species) { HP(1); Status1(STATUS1_POISON); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); } + PLAYER(SPECIES_DONDOZO); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_VOLBEAT) { Ability(ABILITY_ILLUMINATE); Innates(ABILITY_PRANKSTER); }; + } WHEN { + TURN { } + TURN { MOVE(opponentRight, MOVE_HAZE); MOVE(playerRight, MOVE_ORDER_UP, target: opponentLeft); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_COMMANDER); + MESSAGE("Tatsugiri was swallowed by Dondozo and became Dondozo's commander!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Tatsugiri was hurt by its poisoning!"); + MESSAGE("Tatsugiri fainted!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HAZE, opponentRight); // Remove previous stat boosts + ANIMATION(ANIM_TYPE_MOVE, MOVE_ORDER_UP, playerRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + switch (species) + { + case SPECIES_TATSUGIRI_CURLY: + MESSAGE("Dondozo's Attack rose!"); + break; + case SPECIES_TATSUGIRI_DROOPY: + MESSAGE("Dondozo's Defense rose!"); + break; + case SPECIES_TATSUGIRI_STRETCHY: + MESSAGE("Dondozo's Speed rose!"); + break; + } + } THEN { + switch (species) + { + case SPECIES_TATSUGIRI_CURLY: + EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + break; + case SPECIES_TATSUGIRI_DROOPY: + EXPECT_EQ(playerRight->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + break; + case SPECIES_TATSUGIRI_STRETCHY: + EXPECT_EQ(playerRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 1); + break; + } + } +} + +DOUBLE_BATTLE_TEST("Order Up is boosted by Sheer Force without removing the stat boosting effect (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ENTRAINMENT) == EFFECT_ENTRAINMENT); + PLAYER(SPECIES_DONDOZO) { Speed(10); } + PLAYER(SPECIES_TATSUGIRI_CURLY) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_COMMANDER); Speed(9); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(8); } + OPPONENT(SPECIES_TAUROS) { Speed(21); Ability(ABILITY_SHEER_FORCE); } + } WHEN { + TURN { MOVE(opponentRight, MOVE_ENTRAINMENT, target: playerLeft); MOVE(playerLeft, MOVE_ORDER_UP, target: opponentLeft); } + } SCENE { + MESSAGE("The opposing Tauros used Entrainment!"); + MESSAGE("Dondozo acquired Sheer Force!"); + MESSAGE("Dondozo used Order Up!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + } +} +DOUBLE_BATTLE_TEST("Order Up is always boosted by Sheer Force (Traits)", s16 damage) +{ + u32 move; + enum Ability ability; + PARAMETRIZE(move = MOVE_CELEBRATE, ability = ABILITY_STORM_DRAIN); + PARAMETRIZE(move = MOVE_ENTRAINMENT, ability = ABILITY_STORM_DRAIN); + PARAMETRIZE(move = MOVE_ENTRAINMENT, ability = ABILITY_COMMANDER); + + GIVEN { + ASSUME(GetMoveEffect(MOVE_HAZE) == EFFECT_HAZE); + ASSUME(GetMoveEffect(MOVE_ENTRAINMENT) == EFFECT_ENTRAINMENT); + PLAYER(SPECIES_DONDOZO) { Speed(10); } + PLAYER(SPECIES_TATSUGIRI_CURLY) { Speed(9); Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_TAUROS) { Speed(21); Ability(ABILITY_SHEER_FORCE); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(22); } + } WHEN { + TURN { MOVE(opponentRight, MOVE_HAZE); + MOVE(opponentLeft, move, target: playerLeft); + MOVE(playerLeft, MOVE_ORDER_UP, target: opponentRight); } + } SCENE { + MESSAGE("The opposing Wobbuffet used Haze!"); + if (move == MOVE_ENTRAINMENT) + { + MESSAGE("The opposing Tauros used Entrainment!"); + MESSAGE("Dondozo acquired Sheer Force!"); + } + MESSAGE("Dondozo used Order Up!"); + HP_BAR(opponentRight, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(1.3), results[1].damage); + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(1.3), results[2].damage); + } +} +#endif diff --git a/test/battle/move_effect_secondary/psychic_noise.c b/test/battle/move_effect_secondary/psychic_noise.c index 8e5eae4efb4a..c793187982c2 100644 --- a/test/battle/move_effect_secondary/psychic_noise.c +++ b/test/battle/move_effect_secondary/psychic_noise.c @@ -71,3 +71,51 @@ DOUBLE_BATTLE_TEST("Psychic Noise heal block effect is blocked by partners Aroma ANIMATION(ANIM_TYPE_MOVE, MOVE_RECOVER, opponentLeft); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Psychic Noise is blocked by Soundproof (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_VOLTORB) { HP(1); Ability(ABILITY_AFTERMATH); Innates(ABILITY_SOUNDPROOF); } + } WHEN { + TURN { MOVE(player, MOVE_PSYCHIC_NOISE); MOVE(opponent, MOVE_RECOVER); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_SOUNDPROOF); + MESSAGE("The opposing Voltorb's Soundproof blocks Psychic Noise!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RECOVER, opponent); + } +} + +SINGLE_BATTLE_TEST("Psychic Noise heal block effect is blocked by Aroma Veil (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MILCERY) { Ability(ABILITY_SWEET_VEIL); Innates(ABILITY_AROMA_VEIL); } + } WHEN { + TURN { MOVE(player, MOVE_PSYCHIC_NOISE); MOVE(opponent, MOVE_RECOVER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCHIC_NOISE, player); + ABILITY_POPUP(opponent, ABILITY_AROMA_VEIL); + MESSAGE("The opposing Milcery is protected by an aromatic veil!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RECOVER, opponent); + } +} + +DOUBLE_BATTLE_TEST("Psychic Noise heal block effect is blocked by partners Aroma Veil in doubles (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MILCERY) { Ability(ABILITY_SWEET_VEIL); Innates(ABILITY_AROMA_VEIL); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_PSYCHIC_NOISE, target: opponentLeft); MOVE(opponentLeft, MOVE_RECOVER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCHIC_NOISE, playerLeft); + ABILITY_POPUP(opponentRight, ABILITY_AROMA_VEIL); + MESSAGE("The opposing Wobbuffet is protected by an aromatic veil!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RECOVER, opponentLeft); + } +} +#endif diff --git a/test/battle/move_effect_secondary/remove_status.c b/test/battle/move_effect_secondary/remove_status.c index 02389a50ed20..628139895484 100644 --- a/test/battle/move_effect_secondary/remove_status.c +++ b/test/battle/move_effect_secondary/remove_status.c @@ -137,3 +137,68 @@ DOUBLE_BATTLE_TEST("Sparkling Aria cures burns from all Pokemon on the field and MESSAGE("The opposing Wynaut's burn was cured!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Smelling Salts does not cure paralyzed pokemons behind substitutes or get increased power (Traits)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_INNER_FOCUS; } + PARAMETRIZE { ability = ABILITY_INFILTRATOR; } + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_SMELLING_SALTS, MOVE_EFFECT_REMOVE_STATUS) == TRUE); + ASSUME(GetMoveEffectArg_Status(MOVE_SMELLING_SALTS) == STATUS1_PARALYSIS); + PLAYER(SPECIES_CROBAT) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_SEISMITOAD) { Status1(STATUS1_PARALYSIS); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_CELEBRATE); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_SMELLING_SALTS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SMELLING_SALTS, player); + if (ability == ABILITY_INNER_FOCUS) + { + MESSAGE("The substitute took damage for the opposing Seismitoad!"); + NONE_OF + { + MESSAGE("The opposing Seismitoad's substitute faded!"); // Smelling Salts does 86 damage, the sub has 122 HP, if hitting a sub it shouldn't get boosted damage. + MESSAGE("The opposing Seismitoad was cured of paralysis!"); + STATUS_ICON(opponent, none: TRUE); + } + } + else + { + MESSAGE("The opposing Seismitoad was cured of paralysis!"); + STATUS_ICON(opponent, none: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Wake-Up Slap does not cure paralyzed pokemons behind substitutes or get increased power (Traits)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_INNER_FOCUS; } + PARAMETRIZE { ability = ABILITY_INFILTRATOR; } + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_WAKE_UP_SLAP, MOVE_EFFECT_REMOVE_STATUS) == TRUE); + ASSUME(GetMoveEffectArg_Status(MOVE_WAKE_UP_SLAP) == STATUS1_SLEEP); + PLAYER(SPECIES_CROBAT) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_SEISMITOAD); + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_SING); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_WAKE_UP_SLAP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WAKE_UP_SLAP, player); + if (ability == ABILITY_INNER_FOCUS) { + MESSAGE("The substitute took damage for the opposing Seismitoad!"); + NONE_OF + { + MESSAGE("The opposing Seismitoad's substitute faded!"); // Smelling Salts does 86 damage, the sub has 122 HP, if hitting a sub it shouldn't get boosted damage. + MESSAGE("The opposing Seismitoad woke up!"); + STATUS_ICON(opponent, none: TRUE); + } + } else { + MESSAGE("The opposing Seismitoad woke up!"); + STATUS_ICON(opponent, none: TRUE); + } + } +} +#endif diff --git a/test/battle/move_effect_secondary/salt_cure.c b/test/battle/move_effect_secondary/salt_cure.c index 70ba855b9583..98716e203e32 100644 --- a/test/battle/move_effect_secondary/salt_cure.c +++ b/test/battle/move_effect_secondary/salt_cure.c @@ -148,3 +148,23 @@ DOUBLE_BATTLE_TEST("Salt Cure works in double battles") HP_BAR(opponentLeft); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Salt Cure residual damage does not inflict any damage against Magic Guard (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CLEFABLE) { Ability(ABILITY_FRIEND_GUARD); Innates(ABILITY_MAGIC_GUARD); }; + } WHEN { + TURN { MOVE(player, MOVE_SALT_CURE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SALT_CURE, player); + HP_BAR(opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SALT_CURE_DAMAGE, opponent); + HP_BAR(opponent); + MESSAGE("The opposing Clefable is hurt by Salt Cure!"); + } + } +} +#endif diff --git a/test/battle/move_effect_secondary/stealth_rock.c b/test/battle/move_effect_secondary/stealth_rock.c index 026db13e7acd..53ce32780f74 100644 --- a/test/battle/move_effect_secondary/stealth_rock.c +++ b/test/battle/move_effect_secondary/stealth_rock.c @@ -19,3 +19,19 @@ SINGLE_BATTLE_TEST("Steath Rock: Rock from G-Max Stonesurge are set up before an ABILITY_POPUP(opponent, ABILITY_WEAK_ARMOR); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Steath Rock: Rock from G-Max Stonesurge are set up before any ability activation (Traits)") +{ + GIVEN { + PLAYER(SPECIES_DREDNAW) { GigantamaxFactor(TRUE); } + OPPONENT(SPECIES_SKARMORY) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_WEAK_ARMOR); } + } WHEN { + TURN { MOVE(player, MOVE_WATERFALL, gimmick: GIMMICK_DYNAMAX); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_G_MAX_STONESURGE, player); + MESSAGE("Pointed stones float in the air around the opposing team!"); + ABILITY_POPUP(opponent, ABILITY_WEAK_ARMOR); + } +} +#endif diff --git a/test/battle/move_effect_secondary/syrup_bomb.c b/test/battle/move_effect_secondary/syrup_bomb.c index 4cf217206963..ecd8ab6ed4b7 100644 --- a/test/battle/move_effect_secondary/syrup_bomb.c +++ b/test/battle/move_effect_secondary/syrup_bomb.c @@ -217,3 +217,95 @@ SINGLE_BATTLE_TEST("Sticky Syrup is removed when the user faints") } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Syrup Bomb is prevented by Bulletproof (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CHESPIN) { Ability(ABILITY_OVERGROW); Innates(ABILITY_BULLETPROOF); } + } WHEN { + TURN { MOVE(player, MOVE_SYRUP_BOMB); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_BULLETPROOF); + MESSAGE("The opposing Chespin's Bulletproof blocks Syrup Bomb!"); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SYRUP_BOMB, player); + HP_BAR(opponent); + } + } +} + +SINGLE_BATTLE_TEST("Sticky Syrup speed reduction is prevented by Clear Body, White Smoke or Full Metal Body (Traits)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_BELDUM; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE { species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } + PARAMETRIZE { species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SYRUP_BOMB); } + TURN {} + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SYRUP_BOMB, player); + HP_BAR(opponent); + if (species == SPECIES_BELDUM) + { + MESSAGE("The opposing Beldum got covered in sticky candy syrup!"); + ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SYRUP_BOMB_SPEED_DROP, opponent); + MESSAGE("The opposing Beldum's Clear Body prevents stat loss!"); + NONE_OF { + MESSAGE("The opposing Beldum's Speed fell!"); + } + } + else if (species == SPECIES_TORKOAL) + { + MESSAGE("The opposing Torkoal got covered in sticky candy syrup!"); + ABILITY_POPUP(opponent, ABILITY_WHITE_SMOKE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SYRUP_BOMB_SPEED_DROP, opponent); + MESSAGE("The opposing Torkoal's White Smoke prevents stat loss!"); + NONE_OF { + MESSAGE("The opposing Torkoal's Speed fell!"); + } + } + else if (species == SPECIES_SOLGALEO) + { + MESSAGE("The opposing Solgaleo got covered in sticky candy syrup!"); + ABILITY_POPUP(opponent, ABILITY_FULL_METAL_BODY); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SYRUP_BOMB_SPEED_DROP, opponent); + MESSAGE("The opposing Solgaleo's Full Metal Body prevents stat loss!"); + NONE_OF { + MESSAGE("The opposing Solgaleo's Speed fell!"); + } + } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Sticky Syrup speed reduction is prevented by Clear Amulet (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_CLEAR_AMULET); } + } WHEN { + TURN { MOVE(player, MOVE_SYRUP_BOMB); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SYRUP_BOMB, player); + HP_BAR(opponent); + MESSAGE("The opposing Wobbuffet got covered in sticky candy syrup!"); + MESSAGE("The effects of the Clear Amulet held by the opposing Wobbuffet prevents its stats from being lowered!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SYRUP_BOMB_SPEED_DROP, opponent); + MESSAGE("The opposing Wobbuffet's Speed fell!"); + } + } +} +#endif diff --git a/test/battle/move_effect_secondary/tri_attack.c b/test/battle/move_effect_secondary/tri_attack.c index 308e3914fde9..f60690d20544 100644 --- a/test/battle/move_effect_secondary/tri_attack.c +++ b/test/battle/move_effect_secondary/tri_attack.c @@ -144,3 +144,45 @@ SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze a mon which is alread } } } + +#if B_USE_FROSTBITE == TRUE +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/frostbite Pokémon with abilities preventing respective statuses (Traits)") +#else +SINGLE_BATTLE_TEST("Tri Attack cannot paralyze/burn/freeze Pokémon with abilities preventing respective statuses (Traits)") +#endif +{ + u8 statusAnim; + u16 species; + enum Ability ability; + u32 rng; + PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_PERSIAN; ability = ABILITY_LIMBER; } + PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = MOVE_EFFECT_PARALYSIS; species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } + PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = MOVE_EFFECT_BURN; species = SPECIES_DEWPIDER; ability = ABILITY_WATER_BUBBLE; } + PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = MOVE_EFFECT_BURN; species = SPECIES_SEAKING; ability = ABILITY_WATER_VEIL; } + PARAMETRIZE { statusAnim = B_ANIM_STATUS_BRN; rng = MOVE_EFFECT_BURN; species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } + PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE_OR_FROSTBITE; species = SPECIES_CAMERUPT; ability = ABILITY_MAGMA_ARMOR; } + PARAMETRIZE { statusAnim = B_ANIM_STATUS_FRZ; rng = MOVE_EFFECT_FREEZE_OR_FROSTBITE; species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_TRI_ATTACK, WITH_RNG(RNG_TRI_ATTACK, rng)); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRI_ATTACK, player); + HP_BAR(opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_STATUS, statusAnim, opponent); + if (statusAnim == B_ANIM_STATUS_BRN) { + STATUS_ICON(opponent, burn: TRUE); + } else if (statusAnim == B_ANIM_STATUS_FRZ) { + FREEZE_OR_FROSTBURN_STATUS(opponent, TRUE); + } else if (statusAnim == B_ANIM_STATUS_PRZ) { + STATUS_ICON(opponent, paralysis: TRUE); + } + } + } +} +#endif diff --git a/test/battle/move_effect_secondary/will_o_wisp.c b/test/battle/move_effect_secondary/will_o_wisp.c new file mode 100644 index 000000000000..08b1bd6c69e7 --- /dev/null +++ b/test/battle/move_effect_secondary/will_o_wisp.c @@ -0,0 +1,4 @@ +#include "global.h" +#include "test/battle.h" + +TO_DO_BATTLE_TEST("TODO: Write Will-O-Wisp (Move Effect) test titles") diff --git a/test/battle/move_effect_secondary/wrap.c b/test/battle/move_effect_secondary/wrap.c index 2e430871fd7b..2a1d860930e7 100644 --- a/test/battle/move_effect_secondary/wrap.c +++ b/test/battle/move_effect_secondary/wrap.c @@ -98,3 +98,40 @@ SINGLE_BATTLE_TEST("Wrap can damage the wrapped mon 5 turns (Gen4) or 7 turns (G NOT HP_BAR(opponent); // Residual Damage } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Wrap can damage the wrapped mon 5 turns (Gen4) or 7 turns (Gen5+) while holding a Grip Claw (Multi)") +{ + u32 config; + PARAMETRIZE { config = GEN_4; } + PARAMETRIZE { config = GEN_5; } + GIVEN { + WITH_CONFIG(CONFIG_BINDING_TURNS, config); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_GRIP_CLAW); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_WRAP); } + TURN {} + TURN {} + TURN {} + TURN {} + TURN {} + TURN {} + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WRAP, player); + HP_BAR(opponent); // Direct damage + + HP_BAR(opponent); // Residual Damage + HP_BAR(opponent); // Residual Damage + HP_BAR(opponent); // Residual Damage + HP_BAR(opponent); // Residual Damage + HP_BAR(opponent); // Residual Damage + if (config >= GEN_5) { + HP_BAR(opponent); // Residual Damage + HP_BAR(opponent); // Residual Damage + } + NOT HP_BAR(opponent); // Residual Damage + } +} +#endif diff --git a/test/battle/move_effects_combined/mind_blown.c b/test/battle/move_effects_combined/mind_blown.c index 79ce35d126f7..4dd5857dc17e 100644 --- a/test/battle/move_effects_combined/mind_blown.c +++ b/test/battle/move_effects_combined/mind_blown.c @@ -186,3 +186,51 @@ SINGLE_BATTLE_TEST("Mind Blown does not cause the user to lose HP if there is no MESSAGE("2 sent out Wobbuffet!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Mind Blown hp loss is prevented by Magic Guard (Traits)") +{ + GIVEN { + PLAYER(SPECIES_CLEFAIRY) { Ability(ABILITY_FRIEND_GUARD); Innates(ABILITY_MAGIC_GUARD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_MIND_BLOWN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIND_BLOWN, player); + HP_BAR(opponent); + NOT HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Mind Blown is blocked by Damp (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(400); MaxHP(400); } + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_SWIFT_SWIM); Innates(ABILITY_DAMP); } + } WHEN { + TURN { MOVE(player, MOVE_MIND_BLOWN); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIND_BLOWN, player); + HP_BAR(player, damage: 200); + } + ABILITY_POPUP(opponent, ABILITY_DAMP); + MESSAGE("The opposing Golduck's Damp prevents Wobbuffet from using Mind Blown!"); + } +} + +SINGLE_BATTLE_TEST("Mind Blown makes the user lose HP even if it is absorbed by Flash Fire (Traits)") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_MIND_BLOWN) == TYPE_FIRE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CYNDAQUIL) { Ability(ABILITY_BLAZE); Innates(ABILITY_FLASH_FIRE); } + } WHEN { + TURN { MOVE(player, MOVE_MIND_BLOWN); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_FLASH_FIRE); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_MIND_BLOWN, player); + HP_BAR(player); + } +} +#endif diff --git a/test/battle/move_effects_combined/relic_song.c b/test/battle/move_effects_combined/relic_song.c index dec92f069e79..b39c0fc3c696 100644 --- a/test/battle/move_effects_combined/relic_song.c +++ b/test/battle/move_effects_combined/relic_song.c @@ -67,3 +67,37 @@ SINGLE_BATTLE_TEST("Relic Song is blocked by Throat Chop") NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_RELIC_SONG, player); } } + + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Relic Song is prevented by Soundproof (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_VOLTORB) { Ability(ABILITY_STATIC); Innates(ABILITY_SOUNDPROOF); } + } WHEN { + TURN { MOVE(player, MOVE_RELIC_SONG); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_SOUNDPROOF); + MESSAGE("The opposing Voltorb's Soundproof blocks Relic Song!"); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_RELIC_SONG, player); + HP_BAR(opponent); + } + } +} + +SINGLE_BATTLE_TEST("Relic Song will become a Water-type move when used by a Pokémon with the Ability Liquid Voice (Traits)") +{ + GIVEN { + PLAYER(SPECIES_VULPIX); + OPPONENT(SPECIES_POPPLIO) { Ability(ABILITY_TORRENT); Innates(ABILITY_LIQUID_VOICE); } + } WHEN { + TURN { MOVE(opponent, MOVE_RELIC_SONG); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_RELIC_SONG, opponent); + HP_BAR(player); + MESSAGE("It's super effective!"); + } +} +#endif diff --git a/test/battle/move_effects_combined/toxic_thread.c b/test/battle/move_effects_combined/toxic_thread.c index ec27f4636588..30bd3dce5c2a 100644 --- a/test/battle/move_effects_combined/toxic_thread.c +++ b/test/battle/move_effects_combined/toxic_thread.c @@ -163,3 +163,38 @@ SINGLE_BATTLE_TEST("Toxic Thread fails if speed can't be lowered and target is a NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_THREAD, player); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Toxic Thread still inflicts Poison if speed can't be lowered (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_REGICE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_CLEAR_BODY); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC_THREAD); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_THREAD, player); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, poison: TRUE); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Toxic Thread fails if speed can't be lowered due to Clear Body and status can't be inflicted (Traits)") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_POISON_POWDER) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_POISON_POWDER) == MOVE_EFFECT_POISON); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_REGICE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_CLEAR_BODY); } + } WHEN { + TURN { MOVE(player, MOVE_POISON_POWDER); } + TURN { MOVE(player, MOVE_TOXIC_THREAD); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_POWDER, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_THREAD, player); + } +} +#endif diff --git a/test/battle/move_effects_combined/triple_arrows.c b/test/battle/move_effects_combined/triple_arrows.c index f9e0e550b8a3..f040f7f50b95 100644 --- a/test/battle/move_effects_combined/triple_arrows.c +++ b/test/battle/move_effects_combined/triple_arrows.c @@ -96,3 +96,58 @@ SINGLE_BATTLE_TEST("Triple Arrows's flinching is prevented by Inner Focus") ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Triple Arrows may lower Defense by one stage (Traits)") +{ + enum Ability ability; + u32 chance; + PARAMETRIZE { ability = ABILITY_HUSTLE; chance = 50; } + PARAMETRIZE { ability = ABILITY_SERENE_GRACE; chance = 100; } + PASSES_RANDOMLY(chance, 100, RNG_SECONDARY_EFFECT); + GIVEN { + PLAYER(SPECIES_TOGEPI) { Ability(ABILITY_SUPER_LUCK); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TRIPLE_ARROWS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRIPLE_ARROWS, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wobbuffet's Defense fell!"); + } +} + +SINGLE_BATTLE_TEST("Triple Arrows makes the foe flinch 30% of the time (Traits)") +{ + enum Ability ability; + u32 chance; + PARAMETRIZE { ability = ABILITY_HUSTLE; chance = 30; } + PARAMETRIZE { ability = ABILITY_SERENE_GRACE; chance = 60; } + PASSES_RANDOMLY(chance, 100, RNG_SECONDARY_EFFECT_2); + GIVEN { + PLAYER(SPECIES_TOGEPI) { Ability(ABILITY_SUPER_LUCK); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TRIPLE_ARROWS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRIPLE_ARROWS, player); + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + } +} + +SINGLE_BATTLE_TEST("Triple Arrows's flinching is prevented by Inner Focus (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_RIOLU) { Ability(ABILITY_PRANKSTER); Innates(ABILITY_INNER_FOCUS); } + } WHEN { + TURN { MOVE(player, MOVE_TRIPLE_ARROWS); + MOVE(opponent, MOVE_SCRATCH); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRIPLE_ARROWS, player); + NONE_OF { MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); } + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + } +} +#endif diff --git a/test/battle/move_flags/ignores_target_ability.c b/test/battle/move_flags/ignores_target_ability.c index 4a533e7a0835..f1a177f6ca78 100644 --- a/test/battle/move_flags/ignores_target_ability.c +++ b/test/battle/move_flags/ignores_target_ability.c @@ -94,3 +94,92 @@ SINGLE_BATTLE_TEST("ignoresTargetAbility allows Pokémon with Battle Armor and S MESSAGE("A critical hit!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("ignoresTargetAbility moves do not ignore the attacker's own ability (Traits)", s16 damage) +{ + enum Ability ability; + u32 move; + + PARAMETRIZE { move = MOVE_SUNSTEEL_STRIKE; ability = ABILITY_MAGIC_GUARD; } + PARAMETRIZE { move = MOVE_SUNSTEEL_STRIKE; ability = ABILITY_UNAWARE; } + PARAMETRIZE { move = MOVE_MOONGEIST_BEAM; ability = ABILITY_MAGIC_GUARD; } + PARAMETRIZE { move = MOVE_MOONGEIST_BEAM; ability = ABILITY_UNAWARE; } + PARAMETRIZE { move = MOVE_PHOTON_GEYSER; ability = ABILITY_MAGIC_GUARD; } + PARAMETRIZE { move = MOVE_PHOTON_GEYSER; ability = ABILITY_UNAWARE; } + + ASSUME(gAbilitiesInfo[ABILITY_UNAWARE].breakable); + ASSUME(GetMoveEffect(MOVE_IRON_DEFENSE) == EFFECT_DEFENSE_UP_2); + ASSUME(GetMoveEffect(MOVE_AMNESIA) == EFFECT_SPECIAL_DEFENSE_UP_2); + + GIVEN { + PLAYER(SPECIES_CLEFABLE) { Speed(1); Ability(ABILITY_FRIEND_GUARD); Innates(ability); } + OPPONENT(SPECIES_ARON) { Speed(2); } + } WHEN { + if (GetMoveCategory(move) == DAMAGE_CATEGORY_PHYSICAL) + TURN { MOVE(opponent, MOVE_IRON_DEFENSE); MOVE(player, move); } + else + TURN { MOVE(opponent, MOVE_AMNESIA); MOVE(player, move); } + } SCENE { + if (GetMoveCategory(move) == DAMAGE_CATEGORY_PHYSICAL) + ANIMATION(ANIM_TYPE_MOVE, MOVE_IRON_DEFENSE, opponent); + else + ANIMATION(ANIM_TYPE_MOVE, MOVE_AMNESIA, opponent); + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage); + EXPECT_MUL_EQ(results[2].damage, UQ_4_12(2.0), results[3].damage); + EXPECT_MUL_EQ(results[4].damage, UQ_4_12(2.0), results[5].damage); + } +} + +SINGLE_BATTLE_TEST("ignoresTargetAbility moves do ignore target's abilities (Traits)", s16 damage) +{ + enum Ability ability; + u32 move; + + PARAMETRIZE { move = MOVE_SUNSTEEL_STRIKE; ability = ABILITY_INNER_FOCUS; } + PARAMETRIZE { move = MOVE_SUNSTEEL_STRIKE; ability = ABILITY_MULTISCALE; } + PARAMETRIZE { move = MOVE_MOONGEIST_BEAM; ability = ABILITY_INNER_FOCUS; } + PARAMETRIZE { move = MOVE_MOONGEIST_BEAM; ability = ABILITY_MULTISCALE; } + PARAMETRIZE { move = MOVE_PHOTON_GEYSER; ability = ABILITY_INNER_FOCUS; } + PARAMETRIZE { move = MOVE_PHOTON_GEYSER; ability = ABILITY_MULTISCALE; } + + ASSUME(gAbilitiesInfo[ABILITY_MULTISCALE].breakable); + + GIVEN { + PLAYER(SPECIES_AZUMARILL); + OPPONENT(SPECIES_DRAGONITE) { Ability(ABILITY_INNER_FOCUS); Innates(ability); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_EQ(results[2].damage, results[3].damage); + EXPECT_EQ(results[4].damage, results[5].damage); + } +} + +SINGLE_BATTLE_TEST("ignoresTargetAbility allows Pokémon with Battle Armor and Shell Armor to receive critical hits (Traits)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_KINGLER; ability = ABILITY_SHELL_ARMOR; } + PARAMETRIZE { species = SPECIES_ARMALDO; ability = ABILITY_BATTLE_ARMOR; } + + GIVEN { + ASSUME(MoveIgnoresTargetAbility(MOVE_SUNSTEEL_STRIKE)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SUNSTEEL_STRIKE, criticalHit: TRUE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNSTEEL_STRIKE, player); + MESSAGE("A critical hit!"); + } +} +#endif diff --git a/test/battle/move_flags/recoil.c b/test/battle/move_flags/recoil.c index 2593a0e1f612..1550ef42e7be 100644 --- a/test/battle/move_flags/recoil.c +++ b/test/battle/move_flags/recoil.c @@ -137,3 +137,61 @@ SINGLE_BATTLE_TEST("Recoil: No recoil is taken if the move is blocked by Disguis EXPECT_EQ(player->hp, player->maxHP); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Recoil: Flare Blitz is absorbed by Flash Fire and no recoil damage is dealt (Traits)") +{ + GIVEN { + ASSUME(GetMoveRecoil(MOVE_FLARE_BLITZ) > 0); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_VULPIX) { Ability(ABILITY_DROUGHT); Innates(ABILITY_FLASH_FIRE); }; + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_FLARE_BLITZ); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLARE_BLITZ, player); + HP_BAR(opponent); + HP_BAR(player); + } + } +} + +SINGLE_BATTLE_TEST("Recoil: No recoil is taken if the move is blocked by Disguise (Traits)") +{ + GIVEN { + ASSUME(GetMoveRecoil(MOVE_FLARE_BLITZ) > 0); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MIMIKYU) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DISGUISE); } + } WHEN { + TURN { MOVE(player, MOVE_FLARE_BLITZ); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLARE_BLITZ, player); + } THEN { + EXPECT_EQ(player->hp, player->maxHP); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Recoil: The correct amount of recoil damage is dealt after targets recovery berry proc (Multi)") +{ + s16 directDamage; + s16 recoilDamage; + + GIVEN { + ASSUME(GetMoveRecoil(MOVE_TAKE_DOWN) == 25); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Items(ITEM_PECHA_BERRY, ITEM_SITRUS_BERRY); }; + } WHEN { + TURN { MOVE(player, MOVE_TAKE_DOWN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAKE_DOWN, player); + HP_BAR(opponent, captureDamage: &directDamage); + HP_BAR(player, captureDamage: &recoilDamage); + } THEN { + EXPECT_MUL_EQ(directDamage, UQ_4_12(0.25), recoilDamage); + } +} +#endif diff --git a/test/battle/multi_abilities.c b/test/battle/multi_abilities.c new file mode 100644 index 000000000000..6f79e79ef747 --- /dev/null +++ b/test/battle/multi_abilities.c @@ -0,0 +1,1420 @@ +#include "global.h" +#include "test/battle.h" + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Multi - Contrary causes Competitive or Defiant to sharply lower stats", s16 damage) +{ + u32 ability, attack; + PARAMETRIZE { ability = ABILITY_OVERGROW; attack = MOVE_WATER_GUN;} + PARAMETRIZE { ability = ABILITY_COMPETITIVE; attack = MOVE_WATER_GUN;} + PARAMETRIZE { ability = ABILITY_OVERGROW; attack = MOVE_TACKLE;} + PARAMETRIZE { ability = ABILITY_DEFIANT; attack = MOVE_TACKLE;} + GIVEN { + PLAYER(SPECIES_SUNFLORA) { Speed(3); } + OPPONENT(SPECIES_SNIVY) { Ability(ABILITY_CONTRARY); Innates(ability); Speed(2); } + } WHEN { + TURN { MOVE(player, MOVE_FLOWER_SHIELD); MOVE(opponent, attack); } + } SCENE { + MESSAGE("Sunflora used Flower Shield!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLOWER_SHIELD, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Sunflora's Defense rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLOWER_SHIELD, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Snivy's Defense fell!"); + //ABILITY_POPUP(opponent, ABILITY_CONTRARY); //No popup in vanilla + + if (ability == ABILITY_COMPETITIVE) { + ABILITY_POPUP(opponent, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Snivy's Sp. Atk harshly fell!"); + //ABILITY_POPUP(opponent, ABILITY_CONTRARY); //No popup in vanilla + } + else if (ability == ABILITY_DEFIANT){ + ABILITY_POPUP(opponent, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Snivy's Attack harshly fell!"); + //ABILITY_POPUP(opponent, ABILITY_CONTRARY); //No popup in vanilla + } + + HP_BAR(player, captureDamage: &results[i].damage); + } + FINALLY { + EXPECT_MUL_EQ(results[1].damage, Q_4_12(2), results[0].damage); + EXPECT_MUL_EQ(results[3].damage, Q_4_12(2), results[2].damage); + } +} + +SINGLE_BATTLE_TEST("Multi - Contrary causes Competitive and Defiant to sharply lower both stats", s16 damage) +{ + u32 ability1, ability2, attack; + PARAMETRIZE { ability1 = ABILITY_OVERGROW; ability2 = ABILITY_OVERGROW; attack = MOVE_WATER_GUN;} + PARAMETRIZE { ability1 = ABILITY_DEFIANT; ability2 = ABILITY_COMPETITIVE; attack = MOVE_WATER_GUN;} + PARAMETRIZE { ability1 = ABILITY_OVERGROW; ability2 = ABILITY_OVERGROW; attack = MOVE_TACKLE;} + PARAMETRIZE { ability1 = ABILITY_DEFIANT; ability2 = ABILITY_COMPETITIVE; attack = MOVE_TACKLE;} + + GIVEN { + PLAYER(SPECIES_SUNFLORA) { Speed(3); } + OPPONENT(SPECIES_SNIVY) { Ability(ABILITY_CONTRARY); Innates(ability1, ability2); Speed(2); } + } WHEN { + TURN { MOVE(player, MOVE_FLOWER_SHIELD); MOVE(opponent, attack); } + } SCENE { + MESSAGE("Sunflora used Flower Shield!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLOWER_SHIELD, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Sunflora's Defense rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLOWER_SHIELD, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Snivy's Defense fell!"); + //ABILITY_POPUP(opponent, ABILITY_CONTRARY); //No popup in vanilla + + if (ability2 == ABILITY_DEFIANT){ + ABILITY_POPUP(opponent, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Snivy's Attack harshly fell!"); + //ABILITY_POPUP(opponent, ABILITY_CONTRARY); //No popup in vanilla + ABILITY_POPUP(opponent, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Snivy's Sp. Atk harshly fell!"); + //ABILITY_POPUP(opponent, ABILITY_CONTRARY); //No popup in vanilla + } + ANIMATION(ANIM_TYPE_MOVE, attack, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } + FINALLY { + EXPECT_MUL_EQ(results[1].damage, Q_4_12(2), results[0].damage); + EXPECT_MUL_EQ(results[3].damage, Q_4_12(2), results[2].damage); + } +} + +SINGLE_BATTLE_TEST("Multi - Multiple stat changing abilities activating together") +{ + GIVEN { + PLAYER(SPECIES_EKANS) { Ability(ABILITY_UNNERVE); Innates(ABILITY_SUPERSWEET_SYRUP, ABILITY_PRESSURE, ABILITY_INTIMIDATE); Speed(3); } + OPPONENT(SPECIES_SNIVY) { Ability(ABILITY_CONTRARY); Innates(ABILITY_DEFIANT, ABILITY_COMPETITIVE, ABILITY_RATTLED); Speed(2); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_PRESSURE); + MESSAGE("Ekans is exerting its pressure!"); + ABILITY_POPUP(player, ABILITY_UNNERVE); + MESSAGE("The opposing team is too nervous to eat Berries!"); + ABILITY_POPUP(player, ABILITY_SUPERSWEET_SYRUP); + MESSAGE("A supersweet aroma is wafting from the syrup covering Ekans!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Snivy's evasiveness rose!"); + //ABILITY_POPUP(opponent, ABILITY_CONTRARY); //No popup in vanilla + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Snivy's Attack rose!"); + //ABILITY_POPUP(opponent, ABILITY_CONTRARY); //No popup in vanilla + ABILITY_POPUP(opponent, ABILITY_RATTLED); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Snivy's Speed fell!"); + //ABILITY_POPUP(opponent, ABILITY_CONTRARY); //No popup in vanilla + ABILITY_POPUP(opponent, ABILITY_DEFIANT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Snivy's Attack harshly fell!"); + //ABILITY_POPUP(opponent, ABILITY_CONTRARY); //No popup in vanilla + ABILITY_POPUP(opponent, ABILITY_COMPETITIVE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Snivy's Sp. Atk harshly fell!"); + //ABILITY_POPUP(opponent, ABILITY_CONTRARY); //No popup in vanilla + + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); // Intimidate +1, Defiant -2 + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE - 2); // Competitive -2 + EXPECT_EQ(opponent->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); // Rattled -1 + EXPECT_EQ(opponent->statStages[STAT_EVASION], DEFAULT_STAT_STAGE + 1); // Supersweet Aroma +1 + } +} + +SINGLE_BATTLE_TEST("Multi - Contrary Intimidate still triggers Rattled") +{ + u32 ability1, ability2, innate1; + PARAMETRIZE { ability1 = ABILITY_UNNERVE; ability2 = ABILITY_OVERGROW; innate1 = ABILITY_OVERGROW;} + PARAMETRIZE { ability1 = ABILITY_INTIMIDATE; ability2 = ABILITY_OVERGROW; innate1 = ABILITY_OVERGROW;} + PARAMETRIZE { ability1 = ABILITY_INTIMIDATE; ability2 = ABILITY_CONTRARY; innate1 = ABILITY_OVERGROW;} + PARAMETRIZE { ability1 = ABILITY_INTIMIDATE; ability2 = ABILITY_CONTRARY; innate1 = ABILITY_RATTLED;} + + GIVEN { + PLAYER(SPECIES_EKANS) { Ability(ability1); } + OPPONENT(SPECIES_SNIVY) { Ability(ability2); Innates(innate1); } + } WHEN { + TURN { } + } SCENE { + if (ability1 == ABILITY_UNNERVE) + { + ABILITY_POPUP(player, ABILITY_UNNERVE); + MESSAGE("The opposing team is too nervous to eat Berries!"); + } + else if (ability1 == ABILITY_INTIMIDATE && ability2 != ABILITY_CONTRARY) + { + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Ekans's Intimidate cuts the opposing Snivy's Attack!"); + } + else if (ability1 == ABILITY_INTIMIDATE && ability2 == ABILITY_CONTRARY) + { + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Snivy's Attack rose!"); + //ABILITY_POPUP(opponent, ABILITY_CONTRARY); //No popup in vanilla + } + if (innate1 == ABILITY_RATTLED) + { + ABILITY_POPUP(opponent, ABILITY_RATTLED); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Snivy's Speed fell!"); + //ABILITY_POPUP(opponent, ABILITY_CONTRARY); //No popup in vanilla + } + + } THEN { + if (ability1 == ABILITY_INTIMIDATE && ability2 != ABILITY_CONTRARY) + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + if (ability1 == ABILITY_INTIMIDATE && ability2 == ABILITY_CONTRARY) + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + if (ability1 == ABILITY_INTIMIDATE && ability2 == ABILITY_CONTRARY && innate1 == ABILITY_RATTLED) + EXPECT_EQ(opponent->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + } +} + +// Ability immunities should trigger first with specific protections in rarity ordder, then wide protection last to keep activations a little more diverse. +// Insomnia should activate before Purifying Salt for example because Insomnia can only activate for sleep while Purifying Salt can still activate for other ailments. +// Good as Gold is an outlier that always goes first. +SINGLE_BATTLE_TEST("Multi - Sleep protection abilities trigger specific protection first and wide protection last") +{ + u32 ability, innate1, innate2, innate3; + PARAMETRIZE { ability = ABILITY_PURIFYING_SALT; innate1 = ABILITY_LIGHT_METAL; innate2 = ABILITY_LIGHT_METAL; innate3 = ABILITY_LIGHT_METAL;} // Purifying Salt + PARAMETRIZE { ability = ABILITY_PURIFYING_SALT; innate1 = ABILITY_COMATOSE; innate2 = ABILITY_LIGHT_METAL; innate3 = ABILITY_LIGHT_METAL;} // Comatose + PARAMETRIZE { ability = ABILITY_PURIFYING_SALT; innate1 = ABILITY_COMATOSE; innate2 = ABILITY_GOOD_AS_GOLD; innate3 = ABILITY_LIGHT_METAL;} // Good as Gold + + PARAMETRIZE { ability = ABILITY_PURIFYING_SALT; innate1 = ABILITY_SWEET_VEIL; innate2 = ABILITY_LIGHT_METAL; innate3 = ABILITY_LIGHT_METAL;} // Sweet Veil + PARAMETRIZE { ability = ABILITY_PURIFYING_SALT; innate1 = ABILITY_SWEET_VEIL; innate2 = ABILITY_VITAL_SPIRIT; innate3 = ABILITY_LIGHT_METAL;} // Vital Spirit + PARAMETRIZE { ability = ABILITY_PURIFYING_SALT; innate1 = ABILITY_SWEET_VEIL; innate2 = ABILITY_VITAL_SPIRIT; innate3 = ABILITY_INSOMNIA;} // Insomnia + PARAMETRIZE { ability = ABILITY_GOOD_AS_GOLD; innate1 = ABILITY_SWEET_VEIL; innate2 = ABILITY_VITAL_SPIRIT; innate3 = ABILITY_INSOMNIA;} // Good as Gold + + GIVEN { + PLAYER(SPECIES_BUTTERFREE); + OPPONENT(SPECIES_KOMALA) { Ability(ability); Innates(innate1, innate2, innate3); } + } WHEN { + TURN { MOVE(player, MOVE_SLEEP_POWDER); } + } SCENE { + // Sleep Ability activation priority should be Good as Gold > Insomnia > Vital Spirit > Sweet Veil > Comatose > Purifying Salt + + if (innate2 == ABILITY_GOOD_AS_GOLD || ability == ABILITY_GOOD_AS_GOLD) + { + ABILITY_POPUP(opponent, ABILITY_GOOD_AS_GOLD); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_INSOMNIA); + ABILITY_POPUP(opponent, ABILITY_VITAL_SPIRIT); + ABILITY_POPUP(opponent, ABILITY_SWEET_VEIL); + ABILITY_POPUP(opponent, ABILITY_COMATOSE); + ABILITY_POPUP(opponent, ABILITY_PURIFYING_SALT); + STATUS_ICON(opponent, sleep: TRUE); + } + MESSAGE("It doesn't affect the opposing Komala…"); + } + else if (innate3 == ABILITY_INSOMNIA) + { + ABILITY_POPUP(opponent, ABILITY_INSOMNIA); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_GOOD_AS_GOLD); + ABILITY_POPUP(opponent, ABILITY_VITAL_SPIRIT); + ABILITY_POPUP(opponent, ABILITY_SWEET_VEIL); + ABILITY_POPUP(opponent, ABILITY_COMATOSE); + ABILITY_POPUP(opponent, ABILITY_PURIFYING_SALT); + STATUS_ICON(opponent, sleep: TRUE); + MESSAGE("It doesn't affect the opposing Komala…"); + } + } + else if (innate2 == ABILITY_VITAL_SPIRIT) + { + ABILITY_POPUP(opponent, ABILITY_VITAL_SPIRIT); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_GOOD_AS_GOLD); + ABILITY_POPUP(opponent, ABILITY_INSOMNIA); + ABILITY_POPUP(opponent, ABILITY_SWEET_VEIL); + ABILITY_POPUP(opponent, ABILITY_COMATOSE); + ABILITY_POPUP(opponent, ABILITY_PURIFYING_SALT); + STATUS_ICON(opponent, sleep: TRUE); + } + MESSAGE("The opposing Komala's Vital Spirit made it ineffective!"); + } + else if (innate1 == ABILITY_SWEET_VEIL) + { + ABILITY_POPUP(opponent, ABILITY_SWEET_VEIL); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_GOOD_AS_GOLD); + ABILITY_POPUP(opponent, ABILITY_INSOMNIA); + ABILITY_POPUP(opponent, ABILITY_VITAL_SPIRIT); + ABILITY_POPUP(opponent, ABILITY_COMATOSE); + ABILITY_POPUP(opponent, ABILITY_PURIFYING_SALT); + STATUS_ICON(opponent, sleep: TRUE); + } + MESSAGE("It doesn't affect the opposing Komala…"); + } + else if (innate1 == ABILITY_COMATOSE) + { + MESSAGE("The opposing Komala is drowsing!"); // Appears on switch-in + ABILITY_POPUP(opponent, ABILITY_COMATOSE); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_GOOD_AS_GOLD); + ABILITY_POPUP(opponent, ABILITY_INSOMNIA); + ABILITY_POPUP(opponent, ABILITY_VITAL_SPIRIT); + ABILITY_POPUP(opponent, ABILITY_SWEET_VEIL); + ABILITY_POPUP(opponent, ABILITY_PURIFYING_SALT); + STATUS_ICON(opponent, sleep: TRUE); + } + MESSAGE("It doesn't affect the opposing Komala…"); + } + else if (ability == ABILITY_PURIFYING_SALT) + { + ABILITY_POPUP(opponent, ABILITY_PURIFYING_SALT); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_GOOD_AS_GOLD); + ABILITY_POPUP(opponent, ABILITY_INSOMNIA); + ABILITY_POPUP(opponent, ABILITY_VITAL_SPIRIT); + ABILITY_POPUP(opponent, ABILITY_SWEET_VEIL); + ABILITY_POPUP(opponent, ABILITY_COMATOSE); + STATUS_ICON(opponent, sleep: TRUE); + } + MESSAGE("It doesn't affect the opposing Komala…"); + } + } +} + +SINGLE_BATTLE_TEST("Multi - Poison protection abilities trigger specific protection first and wide protection last") +{ + u32 ability, innate1; + PARAMETRIZE { ability = ABILITY_PASTEL_VEIL; innate1 = ABILITY_PASTEL_VEIL; } // Pastel Veil + PARAMETRIZE { ability = ABILITY_PASTEL_VEIL; innate1 = ABILITY_IMMUNITY; } // Immunity + + GIVEN { + PLAYER(SPECIES_BUTTERFREE); + OPPONENT(SPECIES_WOBBUFFET) { Ability(ability); Innates(innate1); } + } WHEN { + TURN { MOVE(player, MOVE_POISONPOWDER); } + } SCENE { + // Sleep Ability activation priority should be Immunity > Pastel Veil + + if (innate1 == ABILITY_PASTEL_VEIL) + { + ABILITY_POPUP(opponent, ABILITY_PASTEL_VEIL); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_CORROSION); + ABILITY_POPUP(opponent, ABILITY_IMMUNITY); + STATUS_ICON(opponent, poison: TRUE); + } + MESSAGE("It doesn't affect the opposing Wobbuffet…"); + } + else if (ability == ABILITY_CORROSION) + { + //ABILITY_POPUP(opponent, ABILITY_CORROSION); //No popup in vanilla + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_PASTEL_VEIL); + ABILITY_POPUP(opponent, ABILITY_IMMUNITY); + STATUS_ICON(opponent, poison: TRUE); + } + MESSAGE("It doesn't affect the opposing Wobbuffet…"); + } + } +} + +SINGLE_BATTLE_TEST("Multi - Burn protection abilities trigger specific protection first and wide protection last") +{ + u32 ability, innate1, innate2; + PARAMETRIZE { ability = ABILITY_THERMAL_EXCHANGE; innate1 = ABILITY_LIGHT_METAL; innate2 = ABILITY_LIGHT_METAL; } // Thermal Exchange + PARAMETRIZE { ability = ABILITY_THERMAL_EXCHANGE; innate1 = ABILITY_WATER_BUBBLE; innate2 = ABILITY_LIGHT_METAL; } // Water Bubble + PARAMETRIZE { ability = ABILITY_THERMAL_EXCHANGE; innate1 = ABILITY_WATER_BUBBLE; innate2 = ABILITY_WATER_VEIL; } // Water Veil + + GIVEN { + PLAYER(SPECIES_LITWICK); + OPPONENT(SPECIES_BAXCALIBUR) { Ability(ability); Innates(innate1, innate2); } + } WHEN { + TURN { MOVE(player, MOVE_WILL_O_WISP); } + } SCENE { + // Sleep Ability activation priority should be Immunity > Pastel Veil > Corrosion + + if (innate2 == ABILITY_WATER_VEIL) + { + ABILITY_POPUP(opponent, ABILITY_WATER_VEIL); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_THERMAL_EXCHANGE); + ABILITY_POPUP(opponent, ABILITY_WATER_BUBBLE); + STATUS_ICON(opponent, burn: TRUE); + } + MESSAGE("It doesn't affect the opposing Baxcalibur…"); + } + else if (innate1 == ABILITY_WATER_BUBBLE) + { + ABILITY_POPUP(opponent, ABILITY_WATER_BUBBLE); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_THERMAL_EXCHANGE); + ABILITY_POPUP(opponent, ABILITY_WATER_VEIL); + STATUS_ICON(opponent, burn: TRUE); + } + MESSAGE("It doesn't affect the opposing Baxcalibur…"); + } + else if (ability == ABILITY_THERMAL_EXCHANGE) + { + ABILITY_POPUP(opponent, ABILITY_THERMAL_EXCHANGE); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_WATER_BUBBLE); + ABILITY_POPUP(opponent, ABILITY_WATER_VEIL); + STATUS_ICON(opponent, burn: TRUE); + } + MESSAGE("It doesn't affect the opposing Baxcalibur…"); + } + } +} + +SINGLE_BATTLE_TEST("Multi - Weight changing abilities can stack ", s16 damage3) +{ + u32 ability1, ability2; + PARAMETRIZE { ability1 = ABILITY_LEVITATE; ability2 = ABILITY_LEVITATE; } + PARAMETRIZE { ability1 = ABILITY_LIGHT_METAL; ability2 = ABILITY_LEVITATE; } + PARAMETRIZE { ability1 = ABILITY_HEAVY_METAL; ability2 = ABILITY_LEVITATE; } + PARAMETRIZE { ability1 = ABILITY_HEAVY_METAL; ability2 = ABILITY_LIGHT_METAL; } + + GIVEN { + PLAYER(SPECIES_TANGELA); + OPPONENT(SPECIES_BELDUM) { Ability(ability1); Innates(ability2); } + } WHEN { + TURN { MOVE(player, MOVE_GRASS_KNOT); } + } SCENE { + MESSAGE("Tangela used Grass Knot!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASS_KNOT, player); + HP_BAR(opponent, captureDamage: &results[i].damage3); + } + FINALLY { + EXPECT_LT(results[1].damage3, results[0].damage3); + EXPECT_GT(results[2].damage3, results[0].damage3); + EXPECT_EQ(results[3].damage3, results[0].damage3); + } +} + +SINGLE_BATTLE_TEST("Multi - Damage boosting abilities can stack (attacker)", s16 damage4) +{ + u32 attack, ability, innate1, innate2, innate3, status; + PARAMETRIZE { attack = MOVE_BULLET_PUNCH; ability = ABILITY_LEVITATE; innate1 = ABILITY_LEVITATE; innate2 = ABILITY_LEVITATE; innate3 = ABILITY_LEVITATE; status = STATUS1_NONE; } + PARAMETRIZE { attack = MOVE_BULLET_PUNCH; ability = ABILITY_IRON_FIST; innate1 = ABILITY_TECHNICIAN; innate2 = ABILITY_TOXIC_BOOST; innate3 = ABILITY_TOUGH_CLAWS; status = STATUS1_POISON; } + PARAMETRIZE { attack = MOVE_BULLET_PUNCH; ability = ABILITY_STEELWORKER; innate1 = ABILITY_STEELY_SPIRIT; innate2 = ABILITY_RIVALRY; innate3 = ABILITY_IRON_FIST; status = STATUS1_NONE; } + + GIVEN { + PLAYER(SPECIES_NIDOKING) { Ability(ability); Innates(innate1, innate2, innate3); Status1(status); Speed(2); } + OPPONENT(SPECIES_NIDOKING) { Speed(3);} + } WHEN { + TURN { MOVE(player, attack); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, attack, player); + HP_BAR(opponent, captureDamage: &results[i].damage4); + } + FINALLY { + EXPECT_MUL_EQ(results[0].damage4, Q_4_12(3.51), results[1].damage4); //1.2 * 1.5 * 1.5 * 1.3 + EXPECT_MUL_EQ(results[0].damage4, Q_4_12(3.375), results[2].damage4); //1.5 * 1.5 * 1.25 * 1.2 + } +} + +SINGLE_BATTLE_TEST("Multi - Damage boosting abilities can stack (target)", s16 damage5) +{ + u32 ability, innate1, innate2; + PARAMETRIZE { ability = ABILITY_LEVITATE; innate1 = ABILITY_LEVITATE; innate2 = ABILITY_LEVITATE; } + PARAMETRIZE { ability = ABILITY_HEATPROOF; innate1 = ABILITY_WATER_BUBBLE; innate2 = ABILITY_DRY_SKIN; } + + GIVEN { + PLAYER(SPECIES_PONYTA) { }; + OPPONENT(SPECIES_WOBBUFFET) { Ability(ability); Innates(innate1, innate2); } + } WHEN { + TURN { MOVE(player, MOVE_EMBER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, player); + HP_BAR(opponent, captureDamage: &results[i].damage5); + } + FINALLY { + EXPECT_MUL_EQ(results[0].damage5, Q_4_12(0.31), results[1].damage5); //0.5 * 0.5 * 1.25 + } +} + +SINGLE_BATTLE_TEST("Multi - Attack boosting abilities can stack", s16 damage6) +{ + u32 attack, ability, innate1, innate2, innate3, status; + PARAMETRIZE { attack = MOVE_FIRE_FANG; ability = ABILITY_LEVITATE; innate1 = ABILITY_LEVITATE; innate2 = ABILITY_LEVITATE; innate3 = ABILITY_LEVITATE; status = STATUS1_NONE; } + PARAMETRIZE { attack = MOVE_FIRE_FANG; ability = ABILITY_BLAZE; innate1 = ABILITY_GORILLA_TACTICS; innate2 = ABILITY_HUGE_POWER; innate3 = ABILITY_HUSTLE; } + PARAMETRIZE { attack = MOVE_BULLET_PUNCH; ability = ABILITY_STEELWORKER; innate1 = ABILITY_STEELY_SPIRIT; innate2 = ABILITY_RIVALRY; innate3 = ABILITY_IRON_FIST; status = STATUS1_NONE; } + + GIVEN { + PLAYER(SPECIES_CHARIZARD) { Ability(ability); Innates(innate1, innate2, innate3); Status1(status); MaxHP(99); HP(33); } + OPPONENT(SPECIES_GOLEM) { } + } WHEN { + TURN { MOVE(player, attack); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, attack, player); + HP_BAR(opponent, captureDamage: &results[i].damage6); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage6, Q_4_12(6.75), results[1].damage6); //1.5 * 2 * 1.5 * 1.5 + } +} + +SINGLE_BATTLE_TEST("Multi - Switch-In abilities display text correctly 1") +{ + GIVEN { + PLAYER(SPECIES_RESHIRAM) { Ability(ABILITY_TERAVOLT); Innates(ABILITY_SUPERSWEET_SYRUP, ABILITY_COMATOSE, ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_ZEKROM) { Ability(ABILITY_TURBOBLAZE); Innates(ABILITY_SLOW_START, ABILITY_UNNERVE, ABILITY_PRESSURE); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_COMATOSE); + MESSAGE("Reshiram is drowsing!"); + ABILITY_POPUP(player, ABILITY_TERAVOLT); + MESSAGE("Reshiram is radiating a bursting aura!"); + ABILITY_POPUP(player, ABILITY_SUPERSWEET_SYRUP); + MESSAGE("A supersweet aroma is wafting from the syrup covering Reshiram!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Zekrom's evasiveness fell!"); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Reshiram's Intimidate cuts the opposing Zekrom's Attack!"); + ABILITY_POPUP(opponent, ABILITY_PRESSURE); + MESSAGE("The opposing Zekrom is exerting its pressure!"); + ABILITY_POPUP(opponent, ABILITY_UNNERVE); + MESSAGE("Your team is too nervous to eat Berries!"); + ABILITY_POPUP(opponent, ABILITY_SLOW_START); + MESSAGE("The opposing Zekrom is slow to get going!"); + ABILITY_POPUP(opponent, ABILITY_TURBOBLAZE); + MESSAGE("The opposing Zekrom is radiating a blazing aura!"); + } +} + +SINGLE_BATTLE_TEST("Multi - Switch-In abilities display text correctly 2") +{ + GIVEN { + PLAYER(SPECIES_XERNEAS) { Ability(ABILITY_FAIRY_AURA); Innates(ABILITY_AS_ONE_ICE_RIDER, ABILITY_PASTEL_VEIL, ABILITY_DOWNLOAD); + Moves(MOVE_DAZZLING_GLEAM, MOVE_CELEBRATE); Item(ITEM_ORAN_BERRY); Status1(STATUS1_POISON); } + OPPONENT(SPECIES_YVELTAL) { Ability(ABILITY_DARK_AURA); Innates(ABILITY_ANTICIPATION, ABILITY_FRISK, ABILITY_FOREWARN); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_FAIRY_AURA); + MESSAGE("Xerneas is radiating a fairy aura!"); + ABILITY_POPUP(player, ABILITY_DOWNLOAD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Xerneas's Download raised its Attack!"); + ABILITY_POPUP(player, ABILITY_PASTEL_VEIL); + MESSAGE("Xerneas was cured of its poisoning!"); + ABILITY_POPUP(player, ABILITY_AS_ONE_ICE_RIDER); + MESSAGE("Xerneas has two Abilities!"); + ABILITY_POPUP(player, ABILITY_UNNERVE); + MESSAGE("The opposing team is too nervous to eat Berries!"); + ABILITY_POPUP(opponent, ABILITY_DARK_AURA); + MESSAGE("The opposing Yveltal is radiating a dark aura!"); + ABILITY_POPUP(opponent, ABILITY_FOREWARN); + MESSAGE("Forewarn alerted the opposing Yveltal to Xerneas's Dazzling Gleam!"); + ABILITY_POPUP(opponent, ABILITY_FRISK); + MESSAGE("The opposing Yveltal frisked Xerneas and found its Oran Berry!"); + ABILITY_POPUP(opponent, ABILITY_ANTICIPATION); + MESSAGE("The opposing Yveltal shuddered!"); + } +} + +// Mirror Armor only activates if a stat drop would land. Protection abilities prevent Mirror Armor from triggering. + +SINGLE_BATTLE_TEST("Multi - Guard Dog gets priority over Intimidate negation abilities") +{ + u32 ability, innate1, innate2, innate3; + PARAMETRIZE { ability = ABILITY_INNER_FOCUS; innate1 = ABILITY_LEVITATE; innate2 = ABILITY_LEVITATE; innate3 = ABILITY_LEVITATE; } + PARAMETRIZE { ability = ABILITY_INNER_FOCUS; innate1 = ABILITY_GUARD_DOG; innate2 = ABILITY_SCRAPPY; innate3 = ABILITY_OWN_TEMPO; } + GIVEN { + PLAYER(SPECIES_MABOSSTIFF) { Ability(ability); Innates(innate1, innate2, innate3); } + OPPONENT(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE);} + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_INTIMIDATE); + if (innate1 == ABILITY_GUARD_DOG) + { + ABILITY_POPUP(player, ABILITY_GUARD_DOG); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mabosstiff's Attack rose!"); + } + else + { + ABILITY_POPUP(player, ABILITY_INNER_FOCUS); + MESSAGE("Mabosstiff's Inner Focus prevents stat loss!"); + } + } +} + +// Conflicting battlescripts block +SINGLE_BATTLE_TEST("Multi - Dry Skin, Rain Dish, and Solar Power can stack") +{ + u32 ability, innate1, species; + PARAMETRIZE { ability = ABILITY_DRY_SKIN; innate1 = ABILITY_LEVITATE; species = SPECIES_KYOGRE; } + PARAMETRIZE { ability = ABILITY_DRY_SKIN; innate1 = ABILITY_LEVITATE; species = SPECIES_GROUDON;} + PARAMETRIZE { ability = ABILITY_DRY_SKIN; innate1 = ABILITY_RAIN_DISH; species = SPECIES_KYOGRE;} + PARAMETRIZE { ability = ABILITY_DRY_SKIN; innate1 = ABILITY_SOLAR_POWER; species = SPECIES_GROUDON;} + + GIVEN { + PLAYER(SPECIES_PARASECT) { Ability(ability); Innates(innate1); HP(50); MaxHP(100); } + OPPONENT(species); + } WHEN { + TURN { } + } SCENE { + if (innate1 == ABILITY_LEVITATE && species == SPECIES_KYOGRE) + { + ABILITY_POPUP(player, ABILITY_DRY_SKIN); + MESSAGE("Parasect's Dry Skin restored its HP a little!"); + HP_BAR(player, damage: -100 / 8); + + } + else if (innate1 == ABILITY_LEVITATE && species == SPECIES_GROUDON) + { + ABILITY_POPUP(player, ABILITY_DRY_SKIN); + HP_BAR(player, damage: 100 / 8); + MESSAGE("Parasect's Dry Skin takes its toll!"); + } + else if (innate1 == ABILITY_RAIN_DISH) + { + ABILITY_POPUP(player, ABILITY_RAIN_DISH); + MESSAGE("Parasect's Rain Dish restored its HP a little!"); + HP_BAR(player, damage: -300 / 16); // 1/16 + 1/8 + } + else if (innate1 == ABILITY_SOLAR_POWER) + { + ABILITY_POPUP(player, ABILITY_SOLAR_POWER); + HP_BAR(player, damage: 200 / 8); // 1/8 + 1/8 + MESSAGE("Parasect's Solar Power takes its toll!"); + } + } +} + +SINGLE_BATTLE_TEST("Multi - Gooey and Tangling Hair can stack") +{ + GIVEN { + PLAYER(SPECIES_GOOMY) { Ability(ABILITY_GOOEY); Innates(ABILITY_TANGLING_HAIR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ABILITY_POPUP(player, ABILITY_TANGLING_HAIR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wobbuffet's Speed fell!"); + ABILITY_POPUP(player, ABILITY_GOOEY); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wobbuffet's Speed fell!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 2); + } +} + +SINGLE_BATTLE_TEST("Multi - Return damage abilities stack correctly", s16 damage7) +{ + u32 ability, innate1, innate2, innate3; + PARAMETRIZE { ability = ABILITY_ROUGH_SKIN; innate1 = ABILITY_LEVITATE; innate2 = ABILITY_LEVITATE; innate3 = ABILITY_LEVITATE; } + PARAMETRIZE { ability = ABILITY_ROUGH_SKIN; innate1 = ABILITY_IRON_BARBS; innate2 = ABILITY_LEVITATE; innate3 = ABILITY_LEVITATE; } + PARAMETRIZE { ability = ABILITY_ROUGH_SKIN; innate1 = ABILITY_IRON_BARBS; innate2 = ABILITY_AFTERMATH; innate3 = ABILITY_LEVITATE; } + PARAMETRIZE { ability = ABILITY_ROUGH_SKIN; innate1 = ABILITY_IRON_BARBS; innate2 = ABILITY_AFTERMATH; innate3 = ABILITY_INNARDS_OUT; } + + GIVEN { + PLAYER(SPECIES_FERROSEED) { Ability(ability); Innates(innate1, innate2, innate3); HP(25);} + OPPONENT(SPECIES_INFERNAPE) {MaxHP(100); HP(100); }; + } WHEN { + TURN { MOVE(opponent, MOVE_FIRE_PUNCH); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ROUGH_SKIN); + HP_BAR(opponent, captureDamage: &results[i].damage7); + MESSAGE("The opposing Infernape was hurt by Ferroseed's Rough Skin!"); + if (innate1 == ABILITY_IRON_BARBS) + { + ABILITY_POPUP(player, ABILITY_IRON_BARBS); + MESSAGE("The opposing Infernape was hurt by Ferroseed's Iron Barbs!"); + } + if (innate2 == ABILITY_AFTERMATH) + { + ABILITY_POPUP(player, ABILITY_AFTERMATH); + MESSAGE("The opposing Infernape was hurt!"); + } + if (innate3 == ABILITY_INNARDS_OUT) + { + ABILITY_POPUP(player, ABILITY_INNARDS_OUT); + MESSAGE("The opposing Infernape was hurt!"); + } + } FINALLY { + EXPECT_GT(results[1].damage7, results[0].damage7); + EXPECT_GT(results[2].damage7, results[1].damage7); + EXPECT_GT(results[3].damage7, results[2].damage7); + } +} + +SINGLE_BATTLE_TEST("Multi - Status ailment abilities don't conflict with each other (Sleep)") +{ + // (11%) Sleep has the highest priority and Effect Spore is the only ability that inflicts sleep + PASSES_RANDOMLY(11, 100, RNG_EFFECT_SPORE); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_EFFECT_SPORE); Innates(ABILITY_STATIC, ABILITY_POISON_POINT, ABILITY_FLAME_BODY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, player); + MESSAGE("The opposing Breloom's Effect Spore made Wobbuffet sleep!"); + STATUS_ICON(player, sleep: TRUE); + } +} + +SINGLE_BATTLE_TEST("Multi - Status ailment abilities don't conflict with each other (Paralysis 1)") +{ + + PASSES_RANDOMLY(10, 100, RNG_EFFECT_SPORE); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_EFFECT_SPORE); Innates(ABILITY_LIGHT_METAL, ABILITY_POISON_POINT, ABILITY_FLAME_BODY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, player); + MESSAGE("The opposing Breloom's Effect Spore paralyzed Wobbuffet, so it may be unable to move!"); + STATUS_ICON(player, paralysis: TRUE); + } +} + +SINGLE_BATTLE_TEST("Multi - Status ailment abilities don't conflict with each other (Paralysis 2)") +{ + + PASSES_RANDOMLY(30, 100, RNG_STATIC); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_STATIC, ABILITY_POISON_POINT, ABILITY_FLAME_BODY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_STATIC); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, player); + MESSAGE("The opposing Breloom's Static paralyzed Wobbuffet, so it may be unable to move!"); + STATUS_ICON(player, paralysis: TRUE); + } +} + +SINGLE_BATTLE_TEST("Multi - Status ailment abilities don't conflict with each other (Burn)") +{ + + PASSES_RANDOMLY(30, 100, RNG_FLAME_BODY); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_LIGHT_METAL, ABILITY_POISON_POINT, ABILITY_FLAME_BODY); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_FLAME_BODY); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, player); + MESSAGE("The opposing Breloom's Flame Body burned Wobbuffet!"); + STATUS_ICON(player, burn: TRUE); + } +} + +SINGLE_BATTLE_TEST("Multi - Status ailment abilities don't conflict with each other (Poison)") +{ + + PASSES_RANDOMLY(30, 100, RNG_POISON_POINT); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_EFFECT_SPORE); Innates(ABILITY_LIGHT_METAL, ABILITY_POISON_POINT, ABILITY_LIGHT_METAL); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_POISON_POINT); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + MESSAGE("Wobbuffet was poisoned by the opposing Breloom's Poison Point!"); + STATUS_ICON(player, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Multi - Ruin Abilities stack correctly") +{ + s16 damage8[8]; + + GIVEN { + PLAYER(SPECIES_TING_LU) { Ability(ABILITY_LIGHT_METAL); } + PLAYER(SPECIES_TING_LU) { Ability(ABILITY_VESSEL_OF_RUIN); Innates(ABILITY_TABLETS_OF_RUIN, ABILITY_SWORD_OF_RUIN, ABILITY_BEADS_OF_RUIN); } + OPPONENT(SPECIES_WOBBUFFET) { Attack(100); SpAttack(100); } + } WHEN { + TURN { MOVE(opponent, MOVE_GUST); MOVE(player, MOVE_GUST); } + TURN { MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_SCRATCH); } + TURN { SWITCH(player, 1); } + TURN { MOVE(opponent, MOVE_GUST); MOVE(player, MOVE_GUST); } + TURN { MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GUST, opponent); + HP_BAR(player, captureDamage: &damage8[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GUST, player); + HP_BAR(opponent, captureDamage: &damage8[1]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player, captureDamage: &damage8[2]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &damage8[3]); + ABILITY_POPUP(player, ABILITY_BEADS_OF_RUIN); + MESSAGE("Ting-Lu's Beads of Ruin weakened the Sp. Def of all surrounding Pokémon!"); + ABILITY_POPUP(player, ABILITY_TABLETS_OF_RUIN); + MESSAGE("Ting-Lu's Tablets of Ruin weakened the Attack of all surrounding Pokémon!"); + ABILITY_POPUP(player, ABILITY_SWORD_OF_RUIN); + MESSAGE("Ting-Lu's Sword of Ruin weakened the Defense of all surrounding Pokémon!"); + ABILITY_POPUP(player, ABILITY_VESSEL_OF_RUIN); + MESSAGE("Ting-Lu's Vessel of Ruin weakened the Sp. Atk of all surrounding Pokémon!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GUST, opponent); + HP_BAR(player, captureDamage: &damage8[4]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GUST, player); + HP_BAR(opponent, captureDamage: &damage8[5]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player, captureDamage: &damage8[6]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &damage8[7]); + } + THEN { + EXPECT_MUL_EQ(damage8[4], Q_4_12(1.33), damage8[0]); + EXPECT_MUL_EQ(damage8[1], Q_4_12(1.33), damage8[5]); + EXPECT_MUL_EQ(damage8[6], Q_4_12(1.33), damage8[2]); + EXPECT_MUL_EQ(damage8[3], Q_4_12(1.33), damage8[7]); + } +} + +SINGLE_BATTLE_TEST("Multi - Intrepid Sword and Dauntless Shield don't conflict") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); Innates(ABILITY_DAUNTLESS_SHIELD); } + } WHEN { + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_DAUNTLESS_SHIELD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Zacian's Dauntless Shield raised its Defense!"); + ABILITY_POPUP(opponent, ABILITY_INTREPID_SWORD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Zacian's Intrepid Sword raised its Attack!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + + +SINGLE_BATTLE_TEST("Multi - Embody Aspect can raise multiple stats") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_OGERPON) { Ability(ABILITY_EMBODY_ASPECT_TEAL_MASK); Innates(ABILITY_EMBODY_ASPECT_HEARTHFLAME_MASK, ABILITY_EMBODY_ASPECT_WELLSPRING_MASK, ABILITY_EMBODY_ASPECT_CORNERSTONE_MASK); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_EMBODY_ASPECT_CORNERSTONE_MASK); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Ogerpon's Embody Aspect raised its Defense!"); + ABILITY_POPUP(opponent, ABILITY_EMBODY_ASPECT_WELLSPRING_MASK); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Ogerpon's Embody Aspect raised its Sp. Def!"); + ABILITY_POPUP(opponent, ABILITY_EMBODY_ASPECT_HEARTHFLAME_MASK); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Ogerpon's Embody Aspect raised its Attack!"); + 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 + 1); + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponent->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + } +} + + +SINGLE_BATTLE_TEST("Multi - Wind Rider negates Wind Power") +{ + s16 dmgBefore, dmgAfter; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_LIMBER); Speed(5) ;} // Limber, so it doesn't get paralyzed. + OPPONENT(SPECIES_WATTREL) { Ability(ABILITY_WIND_POWER); Innates(ABILITY_WIND_RIDER); Speed(10); } + } WHEN { + TURN { MOVE(player, MOVE_AIR_CUTTER); MOVE(opponent, MOVE_TAILWIND);} + TURN { MOVE(opponent, MOVE_THUNDERBOLT); } + TURN { MOVE(opponent, MOVE_THUNDERBOLT); } + } SCENE { + + MESSAGE("The opposing Wattrel used Tailwind!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAILWIND, opponent); + MESSAGE("The Tailwind blew from behind the opposing team!"); + ABILITY_POPUP(opponent, ABILITY_WIND_RIDER); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wattrel's Attack rose!"); + MESSAGE("Wobbuffet used Air Cutter!"); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AIR_CUTTER, player); + ABILITY_POPUP(opponent, ABILITY_WIND_POWER); + MESSAGE("Being hit by Air Cutter charged the opposing Wattrel with power!"); + } + ABILITY_POPUP(opponent, ABILITY_WIND_RIDER); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wattrel's Attack rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, opponent); + HP_BAR(player, captureDamage: &dmgBefore); + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, opponent); + HP_BAR(player, captureDamage: &dmgAfter); + } + THEN { + { + EXPECT_EQ(dmgAfter, dmgBefore); // First Thunderbolt is not charged + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); //Wind Rider activated twice + } + } +} + +SINGLE_BATTLE_TEST("Multi - Wind Power takes priority over Electromorphosis") +{ + s16 dmgBefore, dmgAfter; + u16 move; + + PARAMETRIZE {move = MOVE_SCRATCH; } + PARAMETRIZE {move = MOVE_GUST; } + + GIVEN { + ASSUME(!IsBattleMoveStatus(MOVE_SCRATCH)); + ASSUME(!IsBattleMoveStatus(MOVE_GUST)); + ASSUME(GetMoveCategory(MOVE_GUST) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(GetMoveCategory(MOVE_SCRATCH) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(!IsBattleMoveStatus(MOVE_THUNDER_SHOCK)); + ASSUME(GetMoveType(MOVE_THUNDER_SHOCK) == TYPE_ELECTRIC); + + PLAYER(SPECIES_BELLIBOLT) { Ability(ABILITY_ELECTROMORPHOSIS); Innates(ABILITY_WIND_POWER); Speed(10); } + OPPONENT(SPECIES_WOBBUFFET) {Ability(ABILITY_LIMBER); Speed(5) ;} // Limber, so it doesn't get paralyzed. + } + WHEN { + TURN { MOVE(player, MOVE_THUNDER_SHOCK), MOVE(opponent, move); } + TURN { MOVE(player, MOVE_THUNDER_SHOCK), MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_SHOCK, player); + HP_BAR(opponent, captureDamage: &dmgBefore); + + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + HP_BAR(player); + + if (move == MOVE_SCRATCH) { + ABILITY_POPUP(player, ABILITY_ELECTROMORPHOSIS); + MESSAGE("Being hit by Scratch charged Bellibolt with power!"); + } + else { + ABILITY_POPUP(player, ABILITY_WIND_POWER); + MESSAGE("Being hit by Gust charged Bellibolt with power!"); + } + + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_SHOCK, player); + HP_BAR(opponent, captureDamage: &dmgAfter); + + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + HP_BAR(player); + if (move == MOVE_SCRATCH) { + ABILITY_POPUP(player, ABILITY_ELECTROMORPHOSIS); + MESSAGE("Being hit by Scratch charged Bellibolt with power!"); + } + else { + ABILITY_POPUP(player, ABILITY_WIND_POWER); + MESSAGE("Being hit by Gust charged Bellibolt with power!"); + } + } + THEN { + EXPECT_MUL_EQ(dmgBefore, Q_4_12(2.0), dmgAfter); + } +} + +SINGLE_BATTLE_TEST("Multi - Toxic Chain gets priority over Poison Touch and does not conflict") +{ + GIVEN { + ASSUME(GetMovePower(MOVE_SCRATCH) > 0); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_GRIMER) { Ability(ABILITY_POISON_TOUCH); Innates(ABILITY_TOXIC_CHAIN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ABILITY_POPUP(player, ABILITY_TOXIC_CHAIN); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("The opposing Wobbuffet was badly poisoned!"); + STATUS_ICON(opponent, badPoison: TRUE); + } THEN { + EXPECT(opponent->status1 & STATUS1_TOXIC_POISON); + } +} + +SINGLE_BATTLE_TEST("Multi - Stat raising abilities do not conflict") +{ + GIVEN { + PLAYER(SPECIES_PORYGON) { Ability(ABILITY_DOWNLOAD); Innates(ABILITY_SPEED_BOOST, ABILITY_MOODY, ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WOBBUFFET) {Defense(200); SpDefense(100);} + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_DOWNLOAD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Porygon's Download raised its Sp. Atk!"); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Porygon's Intimidate cuts the opposing Wobbuffet's Attack!"); + ABILITY_POPUP(player, ABILITY_SPEED_BOOST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Porygon's Speed Boost raised its Speed!"); + ABILITY_POPUP(player, ABILITY_MOODY); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + NONE_OF { + MESSAGE("Porygon's Speed rose!"); + } + } +} + +SINGLE_BATTLE_TEST("Multi - ABILITYEFFECT_ON_SWITCHIN abilities do not conflict 1") +{ + GIVEN { + PLAYER(SPECIES_DITTO) { Ability(ABILITY_IMPOSTER); Innates(ABILITY_MOLD_BREAKER, ABILITY_UNNERVE, ABILITY_DOWNLOAD); } + OPPONENT(SPECIES_MEWTWO) {Ability(ABILITY_PRESSURE); Innates(ABILITY_DARK_AURA, ABILITY_DRIZZLE, ABILITY_PSYCHIC_SURGE); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_DOWNLOAD); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Ditto's Download raised its Sp. Atk!"); + ABILITY_POPUP(player, ABILITY_UNNERVE); + MESSAGE("The opposing team is too nervous to eat Berries!"); + ABILITY_POPUP(player, ABILITY_MOLD_BREAKER); + MESSAGE("Ditto breaks the mold!"); + ABILITY_POPUP(player, ABILITY_IMPOSTER); + MESSAGE("Ditto transformed into the opposing Mewtwo using Imposter!"); + ABILITY_POPUP(opponent, ABILITY_PSYCHIC_SURGE); + MESSAGE("The battlefield got weird!"); + ABILITY_POPUP(opponent, ABILITY_DRIZZLE); + MESSAGE("The opposing Mewtwo's Drizzle made it rain!"); + ABILITY_POPUP(opponent, ABILITY_DARK_AURA); + MESSAGE("The opposing Mewtwo is radiating a dark aura!"); + ABILITY_POPUP(opponent, ABILITY_PRESSURE); + MESSAGE("The opposing Mewtwo is exerting its pressure!"); + } +} + +DOUBLE_BATTLE_TEST("Multi - ABILITYEFFECT_ON_SWITCHIN abilities do not conflict 2") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SCOLIPEDE) { HP(50); MaxHP(100); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_SLOWKING_GALAR) { Ability(ABILITY_CURIOUS_MEDICINE); Innates(ABILITY_UNNERVE, ABILITY_INTIMIDATE, ABILITY_HOSPITALITY); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_QUIVER_DANCE); MOVE(playerLeft, MOVE_CHARM, target: opponentLeft); } + TURN { SWITCH(opponentRight, 2); MOVE(playerLeft, MOVE_CELEBRATE); } + } SCENE { + // Turn 1 - buff up + MESSAGE("The opposing Scolipede used Quiver Dance!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + // Turn 2 - Switch into Slowking + MESSAGE("2 sent out Slowking!"); + ABILITY_POPUP(opponentRight, ABILITY_HOSPITALITY); + MESSAGE("The opposing Scolipede drank down all the matcha that the opposing Slowking made!"); + HP_BAR(opponentLeft); + ABILITY_POPUP(opponentRight, ABILITY_CURIOUS_MEDICINE); + MESSAGE("The opposing Scolipede's stat changes were removed!"); + ABILITY_POPUP(opponentRight, ABILITY_UNNERVE); + MESSAGE("Your team is too nervous to eat Berries!"); + ABILITY_POPUP(opponentRight, ABILITY_INTIMIDATE); + MESSAGE("The opposing Slowking's Intimidate cuts Wobbuffet's Attack!"); + MESSAGE("The opposing Slowking's Intimidate cuts Wobbuffet's Attack!"); + } THEN { + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentLeft->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentLeft->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentLeft->statStages[STAT_ACC], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentLeft->statStages[STAT_EVASION], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Multi - ABILITYEFFECT_ENDTURN abilities do not conflict") +{ + GIVEN { + PLAYER(SPECIES_BLAZIKEN_MEGA) { Ability(ABILITY_SPEED_BOOST); Innates(ABILITY_MOODY, ABILITY_HARVEST, ABILITY_BAD_DREAMS); Item(ITEM_ORAN_BERRY); HP(20); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); } + } WHEN { + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_HARVEST); + MESSAGE("Blaziken harvested its Oran Berry!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_SPEED_BOOST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Blaziken's Speed Boost raised its Speed!"); + ABILITY_POPUP(player, ABILITY_MOODY); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ABILITY_POPUP(player, ABILITY_BAD_DREAMS); + MESSAGE("The opposing Wobbuffet is tormented!"); + HP_BAR(opponent); + } +} + +DOUBLE_BATTLE_TEST("Multi - ABILITYEFFECT_ENDTURN_STATUS_CURE abilities do not conflict (only one activates at a time)") +{ + u32 ability; + PARAMETRIZE { ability = ABILITY_HYDRATION; } + PARAMETRIZE { ability = ABILITY_LEVITATE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KYOGRE) { Status1(STATUS1_BURN); } + OPPONENT(SPECIES_CHANSEY) { Ability(ABILITY_HEALER); Innates(ability, ABILITY_SHED_SKIN); Status1(STATUS1_BURN); } + } WHEN { + TURN { } + } SCENE { + if (ability == ABILITY_HYDRATION) + { + MESSAGE("The opposing Chansey's Hydration cured its burn problem!"); + } + else + { + MESSAGE("The opposing Chansey's Shed Skin cured its burn problem!"); + } + ABILITY_POPUP(opponentRight, ABILITY_HEALER); + MESSAGE("The opposing Chansey's Healer cured the opposing Kyogre's problem!"); + NONE_OF { + STATUS_ICON(opponentLeft, burn: TRUE); + STATUS_ICON(opponentRight, burn: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Multi - ABILITYEFFECT_MOVE_END_ATTACKER abilities do not conflict (only one activates at a time)") +{ + u32 ability, innate1, innate2, innate3; + PARAMETRIZE { ability = ABILITY_POISON_TOUCH; innate1 = ABILITY_TOXIC_CHAIN; innate2 = ABILITY_STENCH; innate3 = ABILITY_POISON_PUPPETEER; } + PARAMETRIZE { ability = ABILITY_LEVITATE; innate1 = ABILITY_LEVITATE; innate2 = ABILITY_STENCH; innate3 = ABILITY_POISON_PUPPETEER; } + PARAMETRIZE { ability = ABILITY_LEVITATE; innate1 = ABILITY_LEVITATE; innate2 = ABILITY_STENCH; innate3 = ABILITY_LEVITATE; } + GIVEN { + PLAYER(SPECIES_PECHARUNT) { Ability(ability); Innates(innate1, innate2, innate3); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_POISON_STING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_STING, player); + if (innate1 == ABILITY_TOXIC_CHAIN) + { + ABILITY_POPUP(player, ABILITY_TOXIC_CHAIN); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("The opposing Wobbuffet was badly poisoned!"); + STATUS_ICON(opponent, badPoison: TRUE); + NONE_OF { + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ABILITY_POPUP(player, ABILITY_POISON_PUPPETEER); + } + } + else if (innate3 == ABILITY_POISON_PUPPETEER) + { + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("The opposing Wobbuffet was poisoned!"); + ABILITY_POPUP(player, ABILITY_POISON_PUPPETEER); + MESSAGE("The opposing Wobbuffet became confused!"); + } + else if (innate2 == ABILITY_STENCH) + { + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + } + } +} + +SINGLE_BATTLE_TEST("Multi - ABILITYEFFECT_MOVE_END abilities do not conflict 1") +{ + GIVEN { + PLAYER(SPECIES_MUDSDALE) { Ability(ABILITY_STAMINA); Innates(ABILITY_WEAK_ARMOR, ABILITY_CURSED_BODY, ABILITY_GOOEY); } + OPPONENT(SPECIES_DUGTRIO_ALOLA) { Ability(ABILITY_TANGLING_HAIR); Innates(ABILITY_IRON_BARBS, ABILITY_FLAME_BODY, ABILITY_COTTON_DOWN); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(player, ABILITY_GOOEY); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Dugtrio's Speed fell!"); + ABILITY_POPUP(player, ABILITY_CURSED_BODY); + MESSAGE("The opposing Dugtrio's Scratch was disabled by Mudsdale's Cursed Body!"); + ABILITY_POPUP(player, ABILITY_WEAK_ARMOR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mudsdale's Weak Armor lowered its Defense!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mudsdale's Weak Armor sharply raised its Speed!"); + ABILITY_POPUP(player, ABILITY_STAMINA); + MESSAGE("Mudsdale's Defense rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ABILITY_POPUP(opponent, ABILITY_COTTON_DOWN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mudsdale's Speed fell!"); + ABILITY_POPUP(opponent, ABILITY_FLAME_BODY); + MESSAGE("The opposing Dugtrio's Flame Body burned Mudsdale!"); + STATUS_ICON(player, burn: TRUE); + ABILITY_POPUP(opponent, ABILITY_IRON_BARBS); + MESSAGE("Mudsdale was hurt by the opposing Dugtrio's Iron Barbs!"); + ABILITY_POPUP(opponent, ABILITY_TANGLING_HAIR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Mudsdale's Speed fell!"); + } +} + +SINGLE_BATTLE_TEST("Multi - ABILITYEFFECT_MOVE_END abilities do not conflict 2") +{ + GIVEN { + PLAYER(SPECIES_NIDOQUEEN) { Ability(ABILITY_JUSTIFIED); Innates(ABILITY_RATTLED, ABILITY_CUTE_CHARM, ABILITY_SEED_SOWER); } + OPPONENT(SPECIES_ZOROARK) { Ability(ABILITY_WANDERING_SPIRIT); Innates(ABILITY_STEAM_ENGINE, ABILITY_ROUGH_SKIN, ABILITY_ILLUSION); } + OPPONENT(SPECIES_DITTO); + } WHEN { + TURN { MOVE(opponent, MOVE_ASSURANCE); MOVE(player, MOVE_FLAME_WHEEL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ASSURANCE, opponent); + ABILITY_POPUP(player, ABILITY_SEED_SOWER); + MESSAGE("Grass grew to cover the battlefield!"); + ABILITY_POPUP(player, ABILITY_CUTE_CHARM); + MESSAGE("Nidoqueen's Cute Charm infatuated the opposing Ditto!"); + ABILITY_POPUP(player, ABILITY_RATTLED); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Nidoqueen's Speed rose!"); + ABILITY_POPUP(player, ABILITY_JUSTIFIED); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Nidoqueen's Attack rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLAME_WHEEL, player); + ABILITY_POPUP(opponent, ABILITY_STEAM_ENGINE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Ditto's Speed drastically rose!"); + MESSAGE("The opposing Zoroark's illusion wore off!"); + ABILITY_POPUP(opponent, ABILITY_ROUGH_SKIN); + HP_BAR(player); + MESSAGE("Nidoqueen was hurt by the opposing Zoroark's Rough Skin!"); + MESSAGE("The opposing Zoroark swapped Abilities with its target!"); + } +} + +SINGLE_BATTLE_TEST("Multi - ABILITYEFFECT_COLOR_CHANGE abilities do not conflict") +{ + GIVEN { + PLAYER(SPECIES_CHARIZARD) { Ability(ABILITY_COLOR_CHANGE); Innates(ABILITY_BERSERK, ABILITY_ANGER_SHELL); } + OPPONENT(SPECIES_BLASTOISE) { SpAttack(300); } + } WHEN { + TURN { MOVE(opponent, MOVE_WATER_GUN); } + TURN { MOVE(opponent, MOVE_WATER_GUN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + MESSAGE("It's super effective!"); + ABILITY_POPUP(player, ABILITY_COLOR_CHANGE); + MESSAGE("Charizard's Color Change made it the Water type!"); + ABILITY_POPUP(player, ABILITY_ANGER_SHELL); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Charizard's Defense fell!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Charizard's Sp. Def fell!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Charizard's Attack rose!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Charizard's Sp. Atk rose!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Charizard's Speed rose!"); + ABILITY_POPUP(player, ABILITY_BERSERK); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Charizard's Sp. Atk rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + MESSAGE("It's not very effective…"); + } +} + +// Both blocks only activate one ability but might be possible to adjust them to activate multiple. +TO_DO_BATTLE_TEST("Multi - ABILITYEFFECT_ON_WEATHER abilities do not conflict") +TO_DO_BATTLE_TEST("Multi - ABILITYEFFECT_ON_TERRAIN abilities do not conflict") + +// Per the above, exception made for Protosynthesis since the rest of the weather abilities are transformations. +SINGLE_BATTLE_TEST("Multi - Protosynthesis doe not conflict with other weather abilities") +{ + GIVEN { + PLAYER(SPECIES_CHERRIM) { Ability(ABILITY_FLOWER_GIFT); Innates(ABILITY_PROTOSYNTHESIS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + MESSAGE("The harsh sunlight activated Cherrim's Protosynthesis!"); + MESSAGE("Cherrim's Sp. Atk was heightened!"); + ABILITY_POPUP(player, ABILITY_FLOWER_GIFT); + MESSAGE("Cherrim transformed!"); + } +} + +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Multi - ABILITYEFFECT_ENDTURN item Harvest and Pickup can work on the same turn") +{ + // Ball Fetch and Cud Chew here to make sure they don't conflict + GIVEN { + PLAYER(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); Innates(ABILITY_PICKUP, ABILITY_BALL_FETCH, ABILITY_CUD_CHEW); MaxHP(500); HP(251); Items(ITEM_NONE, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_NINETALES){ Ability(ABILITY_DROUGHT); Items(ITEM_PECHA_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_POISON_STING); MOVE(opponent, MOVE_SCRATCH);} + TURN { MOVE(opponent, MOVE_POISON_STING); } + TURN { } + } SCENE { + MESSAGE("The opposing Ninetales's Drought intensified the sun's rays!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_STING, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Ninetales's Pecha Berry cured its poison!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Exeggutor restored its health using its Sitrus Berry!"); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_HARVEST); + MESSAGE("Exeggutor harvested its Sitrus Berry!"); + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Exeggutor found one Pecha Berry!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_STING, opponent); + MESSAGE("Exeggutor's Pecha Berry cured its poison!"); + ABILITY_POPUP(player, ABILITY_HARVEST); + MESSAGE("Exeggutor harvested its Pecha Berry!"); + } THEN { + EXPECT_EQ(player->item, ITEM_PECHA_BERRY); + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + } +} + +WILD_BATTLE_TEST("Multi - ABILITYEFFECT_ENDTURN Ball Fetch does not conflict with Harvest or Pickup") +{ + // Ball Fetch and Cud Chew here to make sure they don't conflict + u32 ability; + + PARAMETRIZE { ability = ABILITY_HARVEST; } + PARAMETRIZE { ability = ABILITY_PICKUP; } + + GIVEN { + PLAYER(SPECIES_YAMPER) { Ability(ABILITY_BALL_FETCH); Innates(ability, ABILITY_CUD_CHEW); MaxHP(500); HP(251); Items(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_NINETALES){ Ability(ABILITY_DROUGHT); Items(ITEM_NORMAL_GEM); } + } WHEN { + TURN { USE_ITEM(player, ITEM_GREAT_BALL, WITH_RNG(RNG_BALLTHROW_SHAKE, MAX_u16) ); MOVE(opponent, MOVE_SCRATCH);} + } SCENE { + MESSAGE("The wild Ninetales's Drought intensified the sun's rays!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The Normal Gem strengthened the wild Ninetales's power!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Yamper restored its health using its Sitrus Berry!"); + HP_BAR(player); + if (ability == ABILITY_HARVEST) + { + ABILITY_POPUP(player, ABILITY_HARVEST); + MESSAGE("Yamper harvested its Sitrus Berry!"); + } + else + { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Yamper found one Normal Gem!"); + } + ABILITY_POPUP(player, ABILITY_BALL_FETCH); + MESSAGE("Yamper found a Great Ball!"); + } THEN { + if (ability == ABILITY_HARVEST) + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + else + EXPECT_EQ(player->item, ITEM_NORMAL_GEM); + EXPECT_EQ(player->item, ITEM_GREAT_BALL); + } +} + +WILD_BATTLE_TEST("Multi - ABILITYEFFECT_ENDTURN Harvest and Pickup take priority over Ball Fetch") +{ + // Ball Fetch and Cud Chew here to make sure they don't conflict + u32 ability; + + PARAMETRIZE { ability = ABILITY_HARVEST; } + PARAMETRIZE { ability = ABILITY_PICKUP; } + + GIVEN { + PLAYER(SPECIES_YAMPER) { Ability(ABILITY_BALL_FETCH); Innates(ability, ABILITY_CUD_CHEW); MaxHP(500); HP(251); Items(ITEM_NUGGET, ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_NINETALES){ Ability(ABILITY_DROUGHT); Items(ITEM_NUGGET, ITEM_NORMAL_GEM); } + } WHEN { + TURN { USE_ITEM(player, ITEM_GREAT_BALL, WITH_RNG(RNG_BALLTHROW_SHAKE, MAX_u16) ); MOVE(opponent, MOVE_SCRATCH);} + } SCENE { + MESSAGE("The wild Ninetales's Drought intensified the sun's rays!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The Normal Gem strengthened the wild Ninetales's power!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Yamper restored its health using its Sitrus Berry!"); + HP_BAR(player); + if (ability == ABILITY_HARVEST) + { + ABILITY_POPUP(player, ABILITY_HARVEST); + MESSAGE("Yamper harvested its Sitrus Berry!"); + } + else + { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Yamper found one Normal Gem!"); + } + NONE_OF { + ABILITY_POPUP(player, ABILITY_BALL_FETCH); + MESSAGE("Yamper found a Great Ball!"); + } + + } THEN { + if (ability == ABILITY_HARVEST) + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + else + EXPECT_EQ(player->item, ITEM_NORMAL_GEM); + } +} + +SINGLE_BATTLE_TEST("Multi - ABILITYEFFECT_ENDTURN item Harvest and Pickup takes priority over Pickup if both activate on the same item slot") +{ + // Ball Fetch and Cud Chew here to make sure they don't conflict + GIVEN { + PLAYER(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); Innates(ABILITY_PICKUP, ABILITY_BALL_FETCH, ABILITY_CUD_CHEW); MaxHP(500); HP(251); Items(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_NINETALES){ Ability(ABILITY_DROUGHT); Items(ITEM_PECHA_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_POISON_STING); MOVE(opponent, MOVE_SCRATCH);} + } SCENE { + MESSAGE("The opposing Ninetales's Drought intensified the sun's rays!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_STING, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Ninetales's Pecha Berry cured its poison!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Exeggutor restored its health using its Sitrus Berry!"); + HP_BAR(player); + ABILITY_POPUP(player, ABILITY_HARVEST); + MESSAGE("Exeggutor harvested its Sitrus Berry!"); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Exeggutor found one Pecha Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + } +} +#endif \ No newline at end of file diff --git a/test/battle/multi_items.c b/test/battle/multi_items.c new file mode 100644 index 000000000000..931ba5e1a947 --- /dev/null +++ b/test/battle/multi_items.c @@ -0,0 +1,1233 @@ +#include "global.h" +#include "test/battle.h" + +#if MAX_MON_ITEMS > 1 +// Generally one item activation per timing window. +SINGLE_BATTLE_TEST("Multi - IsOnSwitchInFirstTurnActivation") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, ITEM_PECHA_BERRY); HP(20); MaxHP(100); Status1(STATUS1_POISON); } + OPPONENT(SPECIES_WOBBUFFET){ Items(ITEM_BERSERK_GENE, ITEM_PERSIM_BERRY); } + } WHEN { + TURN { } + } SCENE { + // Since only one item activates per timing window, both battlers activate one item on initial switch-in then one more after their turns. + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet restored its health using its Oran Berry!"); + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Using Berserk Gene, the Attack of the opposing Wobbuffet sharply rose!"); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet's Pecha Berry cured its poison!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Wobbuffet's Persim Berry snapped it out of its confusion!"); + } +} + +SINGLE_BATTLE_TEST("Multi - Life Orb and Shell Bell stack") +{ + u32 maxHp, heldItem1, heldItem2; + PARAMETRIZE {heldItem1 = ITEM_LIFE_ORB; heldItem2 = ITEM_SHELL_BELL; maxHp = 50; } // Equal heal and damage = no effect + PARAMETRIZE {heldItem1 = ITEM_LIFE_ORB; heldItem2 = ITEM_SHELL_BELL; maxHp = 20; } // Greater healing = Shell Bell + PARAMETRIZE {heldItem1 = ITEM_LIFE_ORB; heldItem2 = ITEM_SHELL_BELL; maxHp = 80; } // Greater damage = Life Orb + PARAMETRIZE {heldItem1 = ITEM_SHELL_BELL; heldItem2 = ITEM_LIFE_ORB; maxHp = 50; } // Equal heal and damage = no effect + PARAMETRIZE {heldItem1 = ITEM_SHELL_BELL; heldItem2 = ITEM_LIFE_ORB; maxHp = 20; } // Greater healing = Shell Bell + PARAMETRIZE {heldItem1 = ITEM_SHELL_BELL; heldItem2 = ITEM_LIFE_ORB; maxHp = 80; } // Greater damage = Life Orb + + GIVEN { + ASSUME(GetMoveEffect(MOVE_DRAGON_RAGE) == EFFECT_FIXED_HP_DAMAGE); + ASSUME(GetMoveFixedHPDamage(MOVE_DRAGON_RAGE) == 40); + PLAYER(SPECIES_WOBBUFFET) { Items(heldItem1, heldItem2); HP(10); MaxHP(maxHp); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, player); + if (maxHp == 50) // No Effect + { + NONE_OF { + HP_BAR(player); + } + } + else if (maxHp == 20) // Shell Bell + { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet restored a little HP using its Shell Bell!"); + HP_BAR(player); + } + else if (maxHp == 100) // Life Orb + { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet was hurt by its Life Orb!"); + HP_BAR(player); + } + } THEN { + EXPECT_EQ(player->hp, 10 + 5 - (maxHp/10)); + } +} + +DOUBLE_BATTLE_TEST("Multi - Unburden only activates partial effect if more than one item is held") +{ + // Unburden gives a 2x speed boost if the user has no held item + + GIVEN { + PLAYER(SPECIES_DRIFBLIM) { Items(ITEM_GREAT_BALL, ITEM_SHELL_BELL); Ability(ABILITY_UNBURDEN); Speed(10); } + PLAYER(SPECIES_WOBBUFFET) { Speed(19); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(12); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(16); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_KNOCK_OFF, target: playerLeft); } + TURN { MOVE(opponentLeft, MOVE_KNOCK_OFF, target: playerLeft); } + TURN { MOVE(opponentLeft, MOVE_KNOCK_OFF, target: playerLeft); } + } SCENE { + // No bonus, Driftlim goes last + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft); // Driftlim + // Partial bonus, Driftlim goes third + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft); // Driftlim + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, opponentLeft); + // Full bonus, Driftlim goes first + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft); // Driftlim + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, opponentLeft); + } +} + +SINGLE_BATTLE_TEST("Multi - Acrobatics only activates partial effect if more than one item is held", s16 damage) +{ + u32 heldItem1, heldItem2; + PARAMETRIZE { heldItem1 = ITEM_POTION; heldItem2 = ITEM_POTION; } + PARAMETRIZE { heldItem1 = ITEM_POTION; heldItem2 = ITEM_NONE; } + PARAMETRIZE { heldItem1 = ITEM_NONE; heldItem2 = ITEM_NONE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(heldItem1, heldItem2); } + } WHEN { + TURN { MOVE(opponent, MOVE_ACROBATICS); } + } SCENE { + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.33), results[1].damage); // Partial bonus + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2), results[2].damage); // Full bonus + } +} + +SINGLE_BATTLE_TEST("Multi - Leftovers and Black Sludge can stack") +{ + u32 item1, item2, species; + + PARAMETRIZE {item1 = ITEM_LEFTOVERS; item2 = ITEM_NONE; species = SPECIES_WOBBUFFET;} + PARAMETRIZE {item1 = ITEM_LEFTOVERS; item2 = ITEM_BLACK_SLUDGE; species = SPECIES_WOBBUFFET;} + PARAMETRIZE {item1 = ITEM_LEFTOVERS; item2 = ITEM_BLACK_SLUDGE; species = SPECIES_GRIMER;} + PARAMETRIZE {item1 = ITEM_NONE; item2 = ITEM_LEFTOVERS; species = SPECIES_WOBBUFFET;} + PARAMETRIZE {item1 = ITEM_BLACK_SLUDGE; item2 = ITEM_LEFTOVERS; species = SPECIES_WOBBUFFET;} + PARAMETRIZE {item1 = ITEM_BLACK_SLUDGE; item2 = ITEM_LEFTOVERS; species = SPECIES_GRIMER;} + + GIVEN { + PLAYER(species) { Items(item1, item2); HP(50); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { } + } SCENE { + if((item1 == ITEM_BLACK_SLUDGE || item2 == ITEM_BLACK_SLUDGE) && species != SPECIES_GRIMER) //Black Sludge damages + MESSAGE("Wobbuffet was hurt by the Black Sludge!"); + else if ((item1 == ITEM_BLACK_SLUDGE || item2 == ITEM_BLACK_SLUDGE) && species == SPECIES_GRIMER) //Black Sludge heals + MESSAGE("Grimer restored a little HP using its Black Sludge!"); + else + MESSAGE("Wobbuffet restored a little HP using its Leftovers!"); + } THEN { + if((item1 == ITEM_BLACK_SLUDGE || item2 == ITEM_BLACK_SLUDGE) && species != SPECIES_GRIMER) + EXPECT_EQ(player->hp, 44); //Black Sludge damage + Leftovers heal + else if ((item1 == ITEM_BLACK_SLUDGE || item2 == ITEM_BLACK_SLUDGE) && species == SPECIES_GRIMER) + EXPECT_EQ(player->hp, 62); //Black Sludge + Leftovers heal + else + EXPECT_EQ(player->hp, 56); // Leftovers heal + } +} + +SINGLE_BATTLE_TEST("Multi - Metronome, Expert Belt, and Life Orb stack") +{ + s16 damage[9]; + + GIVEN { + PLAYER(SPECIES_GOLEM) { Items(ITEM_LIFE_ORB, ITEM_EXPERT_BELT); } + OPPONENT(SPECIES_WOBBUFFET); { } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_METRONOME, ITEM_EXPERT_BELT); } + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_LIFE_ORB, ITEM_METRONOME); } + } WHEN { + TURN { MOVE(opponent, MOVE_BULLET_PUNCH); MOVE(player, MOVE_BESTOW);} + TURN { MOVE(opponent, MOVE_BULLET_PUNCH); MOVE(player, MOVE_BESTOW);} + TURN { MOVE(opponent, MOVE_BULLET_PUNCH); } + TURN { SWITCH(opponent, 1); } + TURN { MOVE(opponent, MOVE_BULLET_PUNCH); } + TURN { MOVE(opponent, MOVE_BULLET_PUNCH); } + TURN { MOVE(opponent, MOVE_BULLET_PUNCH); } + TURN { SWITCH(opponent, 2); } + TURN { MOVE(opponent, MOVE_BULLET_PUNCH); } + TURN { MOVE(opponent, MOVE_BULLET_PUNCH); } + TURN { MOVE(opponent, MOVE_BULLET_PUNCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_PUNCH, opponent); //Base damage + HP_BAR(player, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BESTOW, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_PUNCH, opponent); //Expert Belt + HP_BAR(player, captureDamage: &damage[1]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BESTOW, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_PUNCH, opponent); //Expert Belt + Life Orb + HP_BAR(player, captureDamage: &damage[2]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_PUNCH, opponent); //Expert Belt + Metronome(0) + HP_BAR(player, captureDamage: &damage[3]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_PUNCH, opponent); //Expert Belt + Metronome(1) + HP_BAR(player, captureDamage: &damage[4]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_PUNCH, opponent); //Expert Belt + Metronome(2) + HP_BAR(player, captureDamage: &damage[5]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_PUNCH, opponent); //Life Orb + Metronome(0) + HP_BAR(player, captureDamage: &damage[6]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_PUNCH, opponent); //Life Orb + Metronome(1) + HP_BAR(player, captureDamage: &damage[7]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_PUNCH, opponent); //Expert Belt + Metronome(2) + HP_BAR(player, captureDamage: &damage[8]); + + } THEN { + EXPECT_MUL_EQ(damage[0], UQ_4_12(1.2), damage[1]); //Expert Belt + EXPECT_MUL_EQ(damage[1], UQ_4_12(1.3), damage[2]); //Expert Belt + Life Orb + EXPECT_MUL_EQ(damage[1], UQ_4_12(1), damage[3]); //Expert Belt + Metronome(0) + EXPECT_MUL_EQ(damage[3], UQ_4_12(1.2), damage[4]); //Expert Belt + Metronome(1) + EXPECT_MUL_EQ(damage[3], UQ_4_12(1.4), damage[5]); //Expert Belt + Metronome(2) + EXPECT_MUL_EQ(damage[0], UQ_4_12(1.3), damage[6]); //Life Orb + Metronome(0) + EXPECT_MUL_EQ(damage[6], UQ_4_12(1.2), damage[7]); //Life Orb + Metronome(1) + EXPECT_MUL_EQ(damage[6], UQ_4_12(1.4), damage[8]); //Life Orb + Metronome(2) + } +} + +SINGLE_BATTLE_TEST("Multi - Burn Orb and Toxic Orb don't crash") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_TOXIC_ORB, ITEM_FLAME_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { } + } SCENE { + MESSAGE("Wobbuffet was badly poisoned!"); // Toxic Orb has priority + STATUS_ICON(player, badPoison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Multi - OnTargetAfterHit Air Balloon popping has the highest priority, Rocky Helmet has the lowest") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_ROCKY_HELMET, ITEM_AIR_BALLOON); } + OPPONENT(SPECIES_WOBBUFFET){ Items(ITEM_ABSORB_BULB, ITEM_AIR_BALLOON); } + OPPONENT(SPECIES_WOBBUFFET){ Items(ITEM_LUMINOUS_MOSS, ITEM_ROCKY_HELMET); } + } WHEN { + TURN { MOVE(player, MOVE_AQUA_JET); MOVE(opponent, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_AQUA_JET); MOVE(opponent, MOVE_SCRATCH); } + TURN { SWITCH(opponent, 1); MOVE(player, MOVE_AQUA_JET); } + TURN { MOVE(player, MOVE_AQUA_JET); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_JET, player); + MESSAGE("The opposing Wobbuffet's Air Balloon popped!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + MESSAGE("Wobbuffet's Air Balloon popped!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_JET, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Using Absorb Bulb, the Sp. Atk of the opposing Wobbuffet rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(opponent); + MESSAGE("The opposing Wobbuffet was hurt by Wobbuffet's Rocky Helmet!"); + //switch + ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_JET, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Using Luminous Moss, the Sp. Def of the opposing Wobbuffet rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_JET, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the opposing Wobbuffet's Rocky Helmet!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + HP_BAR(opponent); + MESSAGE("The opposing Wobbuffet was hurt by Wobbuffet's Rocky Helmet!"); + } THEN { + EXPECT(player->items[0] == ITEM_ROCKY_HELMET); + EXPECT(player->item == ITEM_NONE); + EXPECT(opponent->items[0] == ITEM_NONE); + EXPECT(opponent->item == ITEM_ROCKY_HELMET); + } +} + +SINGLE_BATTLE_TEST("Multi - OnTargetAfterHit general item check") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_CELL_BATTERY, ITEM_ABSORB_BULB); } + OPPONENT(SPECIES_WOBBUFFET){ Items(ITEM_SNOWBALL, ITEM_JABOCA_BERRY); } + OPPONENT(SPECIES_WOBBUFFET){ Items(ITEM_SNOWBALL, ITEM_JABOCA_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_AQUA_JET); MOVE(opponent, MOVE_AQUA_JET); } + TURN { MOVE(player, MOVE_ICY_WIND); MOVE(opponent, MOVE_THUNDERSHOCK); } + TURN { SWITCH(opponent, 1); MOVE(player, MOVE_ICY_WIND); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_JET, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the opposing Wobbuffet's Jaboca Berry!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_JET, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Absorb Bulb, the Sp. Atk of Wobbuffet rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ICY_WIND, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Using Snowball, the Attack of the opposing Wobbuffet rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERSHOCK, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Cell Battery, the Attack of Wobbuffet rose!"); + //switch + ANIMATION(ANIM_TYPE_MOVE, MOVE_ICY_WIND, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Using Snowball, the Attack of the opposing Wobbuffet rose!"); + } THEN { + EXPECT(player->items[0] == ITEM_NONE); + EXPECT(player->item == ITEM_NONE); + EXPECT(opponent->items[0] == ITEM_NONE); + EXPECT(opponent->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Multi - onAttackerAfterHit first item takes priority but only if it activates") +{ + PASSES_RANDOMLY(10, 100, RNG_HOLD_EFFECT_FLINCH); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_KINGS_ROCK, ITEM_THROAT_SPRAY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HYPER_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, player); + NONE_OF { + MESSAGE("Using Throat Spray, the Sp. Atk of Wobbuffet rose!"); + } + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + NONE_OF { + MESSAGE("Using Throat Spray, the Sp. Atk of Wobbuffet rose!"); + } + } +} + +//onEffect appears to not have a 1 item limit due to more complicated activation logic in the battlescripts +SINGLE_BATTLE_TEST("Multi - onEffect effects do not conflict") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_ELECTRIC_SEED, ITEM_ROOM_SERVICE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TRICK_ROOM); MOVE(opponent, MOVE_ELECTRIC_TERRAIN); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Electric Seed, the Defense of Wobbuffet rose!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Using Room Service, the Speed of Wobbuffet fell!"); + } +} + +#if B_HELD_ITEM_CATEGORIZATION == TRUE +//If B_HELD_ITEM_CATEGORIZATION is set, pokeball should only be able to go into the specified slot even if another slot is available. +WILD_BATTLE_TEST("Multi - Ball Fetch follows Item Categorization") +{ + u32 item; + PARAMETRIZE {item = ITEM_NONE; } + PARAMETRIZE {item = ITEM_NUGGET; } + + GIVEN { + PLAYER(SPECIES_YAMPER) { Ability(ABILITY_BALL_FETCH); Items(item); } + OPPONENT(SPECIES_METAGROSS); + } WHEN { + TURN { USE_ITEM(player, ITEM_POKE_BALL, WITH_RNG(RNG_BALLTHROW_SHAKE, MAX_u16) );} + TURN {} + } SCENE { + if (item == ITEM_NONE) + ABILITY_POPUP(player, ABILITY_BALL_FETCH); + else + NOT ABILITY_POPUP(player, ABILITY_BALL_FETCH); + } THEN { + if (item == ITEM_NONE) + EXPECT_EQ(player->items[0], ITEM_POKE_BALL); + else + EXPECT_EQ(player->items[0], item); + } +} +#endif + +#if B_MULTI_ITEM_ORDER == 0 +WILD_BATTLE_TEST("Multi - B_MULTI_ITEM_ORDER targets latest to earliest item slot") +{ + u32 move; + PARAMETRIZE { move = MOVE_THIEF; } + PARAMETRIZE { move = MOVE_COVET; } + PARAMETRIZE { move = MOVE_KNOCK_OFF; } + PARAMETRIZE { move = MOVE_BUG_BITE; } + PARAMETRIZE { move = MOVE_PLUCK; } + PARAMETRIZE { move = MOVE_INCINERATE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, ITEM_PECHA_BERRY); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} +#endif + +// Note, there's a catch in GetSlot that forces tests to Item Order 0 to keep tests from breaking. +// Disable that catch to use this test. +#if B_MULTI_ITEM_ORDER == 1 +WILD_BATTLE_TEST("Multi - B_MULTI_ITEM_ORDER targets latest to earliest item slot") +{ + u32 move; + PARAMETRIZE { move = MOVE_THIEF; } + PARAMETRIZE { move = MOVE_COVET; } + PARAMETRIZE { move = MOVE_KNOCK_OFF; } + PARAMETRIZE { move = MOVE_BUG_BITE; } + PARAMETRIZE { move = MOVE_PLUCK; } + PARAMETRIZE { move = MOVE_INCINERATE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, ITEM_PECHA_BERRY); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); + } THEN { + EXPECT_EQ(opponent->items[0], ITEM_NONE); + } +} +#endif + +static const u16 sMoveItemTable[][18] = +{ + { TYPE_NORMAL, MOVE_SCRATCH, ITEM_SILK_SCARF }, + { TYPE_FIGHTING, MOVE_KARATE_CHOP, ITEM_BLACK_BELT }, + { TYPE_FLYING, MOVE_WING_ATTACK, ITEM_SHARP_BEAK }, + { TYPE_POISON, MOVE_POISON_STING, ITEM_POISON_BARB }, + { TYPE_GROUND, MOVE_MUD_SHOT, ITEM_SOFT_SAND }, + { TYPE_ROCK, MOVE_ROCK_THROW, ITEM_HARD_STONE }, + { TYPE_BUG, MOVE_BUG_BITE, ITEM_SILVER_POWDER }, + { TYPE_GHOST, MOVE_SHADOW_PUNCH, ITEM_SPELL_TAG }, + { TYPE_STEEL, MOVE_METAL_CLAW, ITEM_METAL_COAT }, + { TYPE_FIRE, MOVE_EMBER, ITEM_CHARCOAL }, + { TYPE_WATER, MOVE_WATER_GUN, ITEM_MYSTIC_WATER }, + { TYPE_GRASS, MOVE_VINE_WHIP, ITEM_MIRACLE_SEED }, + { TYPE_ELECTRIC, MOVE_THUNDER_SHOCK, ITEM_MAGNET }, + { TYPE_PSYCHIC, MOVE_CONFUSION, ITEM_TWISTED_SPOON }, + { TYPE_ICE, MOVE_AURORA_BEAM, ITEM_NEVER_MELT_ICE }, + { TYPE_DRAGON, MOVE_DRAGON_BREATH, ITEM_DRAGON_FANG }, + { TYPE_DARK, MOVE_BITE, ITEM_BLACK_GLASSES }, + { TYPE_FAIRY, MOVE_DISARMING_VOICE, ITEM_FAIRY_FEATHER }, +}; + + +SINGLE_BATTLE_TEST("Multi - Duplicate type-enhancing items can stack when enabled", s16 damage) +{ + u32 move = 0, item = 0, type = 0; + bool16 dupe = FALSE; + + for (u32 j = 0; j < ARRAY_COUNT(sMoveItemTable); j++) { + PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; item = ITEM_NONE; dupe = FALSE; } + PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; item = sMoveItemTable[j][2]; dupe = FALSE; } + PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; item = ITEM_NONE; dupe = TRUE; } + PARAMETRIZE { type = sMoveItemTable[j][0]; move = sMoveItemTable[j][1]; item = sMoveItemTable[j][2]; dupe = TRUE; } + } + + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, dupe); + ASSUME(GetMovePower(move) > 0); + if (item != ITEM_NONE) { + ASSUME(GetItemHoldEffect(item) == HOLD_EFFECT_TYPE_POWER); + ASSUME(GetItemSecondaryId(item) == type); + } + PLAYER(SPECIES_WOBBUFFET) { Items(item, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + for (u32 j = 0; j < ARRAY_COUNT(sMoveItemTable); j++) { + if (I_TYPE_BOOST_POWER >= GEN_4) + { + EXPECT_MUL_EQ(results[j*4].damage, Q_4_12(1.2), results[(j*4)+1].damage); + EXPECT_MUL_EQ(results[(j*4)+2].damage, Q_4_12(1.44), results[(j*4)+3].damage); + } + else + { + EXPECT_MUL_EQ(results[j*4].damage, Q_4_12(1.1), results[(j*4)+1].damage); + EXPECT_MUL_EQ(results[(j*4)+2].damage, Q_4_12(1.21), results[(j*4)+3].damage); + } + } + } +} + +SINGLE_BATTLE_TEST("Multi - Duplicate damage boosting items can stack", s16 damage, s16 itemDamage) +{ + u32 species = 0, move = 0, item = 0, itemType = 0; + bool16 dupe = FALSE; + + PARAMETRIZE { species = SPECIES_WOBBUFFET; move = MOVE_SCRATCH; item = ITEM_MUSCLE_BAND; itemType = HOLD_EFFECT_MUSCLE_BAND; dupe = FALSE; } + PARAMETRIZE { species = SPECIES_WOBBUFFET; move = MOVE_SCRATCH; item = ITEM_MUSCLE_BAND; itemType = HOLD_EFFECT_MUSCLE_BAND; dupe = TRUE; } + PARAMETRIZE { species = SPECIES_WOBBUFFET; move = MOVE_BUBBLE; item = ITEM_WISE_GLASSES; itemType = HOLD_EFFECT_WISE_GLASSES; dupe = FALSE; } + PARAMETRIZE { species = SPECIES_WOBBUFFET; move = MOVE_BUBBLE; item = ITEM_WISE_GLASSES; itemType = HOLD_EFFECT_WISE_GLASSES; dupe = TRUE; } + PARAMETRIZE { species = SPECIES_PALKIA; move = MOVE_BUBBLE; item = ITEM_LUSTROUS_ORB; itemType = HOLD_EFFECT_LUSTROUS_ORB; dupe = FALSE; } + PARAMETRIZE { species = SPECIES_PALKIA; move = MOVE_BUBBLE; item = ITEM_LUSTROUS_ORB; itemType = HOLD_EFFECT_LUSTROUS_ORB; dupe = TRUE; } + PARAMETRIZE { species = SPECIES_DIALGA; move = MOVE_METAL_CLAW; item = ITEM_ADAMANT_ORB; itemType = HOLD_EFFECT_ADAMANT_ORB; dupe = FALSE; } + PARAMETRIZE { species = SPECIES_DIALGA; move = MOVE_METAL_CLAW; item = ITEM_ADAMANT_ORB; itemType = HOLD_EFFECT_ADAMANT_ORB; dupe = TRUE; } + PARAMETRIZE { species = SPECIES_GIRATINA; move = MOVE_SHADOW_CLAW; item = ITEM_GRISEOUS_ORB; itemType = HOLD_EFFECT_GRISEOUS_ORB; dupe = FALSE; } + PARAMETRIZE { species = SPECIES_GIRATINA; move = MOVE_SHADOW_CLAW; item = ITEM_GRISEOUS_ORB; itemType = HOLD_EFFECT_GRISEOUS_ORB; dupe = TRUE; } + PARAMETRIZE { species = SPECIES_LATIAS; move = MOVE_PSYSHOCK; item = ITEM_SOUL_DEW; itemType = HOLD_EFFECT_SOUL_DEW; dupe = FALSE; } + PARAMETRIZE { species = SPECIES_LATIAS; move = MOVE_PSYSHOCK; item = ITEM_SOUL_DEW; itemType = HOLD_EFFECT_SOUL_DEW; dupe = TRUE; } + PARAMETRIZE { species = SPECIES_WOBBUFFET; move = MOVE_MEGA_PUNCH; item = ITEM_PUNCHING_GLOVE; itemType = HOLD_EFFECT_PUNCHING_GLOVE; dupe = FALSE; } + PARAMETRIZE { species = SPECIES_WOBBUFFET; move = MOVE_MEGA_PUNCH; item = ITEM_PUNCHING_GLOVE; itemType = HOLD_EFFECT_PUNCHING_GLOVE; dupe = TRUE; } + PARAMETRIZE { species = SPECIES_OGERPON; move = MOVE_SCRATCH; item = ITEM_CORNERSTONE_MASK; itemType = HOLD_EFFECT_OGERPON_MASK; dupe = FALSE; } + PARAMETRIZE { species = SPECIES_OGERPON; move = MOVE_SCRATCH; item = ITEM_CORNERSTONE_MASK; itemType = HOLD_EFFECT_OGERPON_MASK; dupe = TRUE; } + PARAMETRIZE { species = SPECIES_WOBBUFFET; move = MOVE_SHADOW_CLAW; item = ITEM_EXPERT_BELT; itemType = HOLD_EFFECT_EXPERT_BELT; dupe = FALSE; } + PARAMETRIZE { species = SPECIES_WOBBUFFET; move = MOVE_SHADOW_CLAW; item = ITEM_EXPERT_BELT; itemType = HOLD_EFFECT_EXPERT_BELT; dupe = TRUE; } + + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, dupe); + ASSUME(GetMovePower(move) > 0); + if (item != ITEM_NONE) { + ASSUME(GetItemHoldEffect(item) == itemType); + } + PLAYER(species) { Items(item, item); Speed(1); } + OPPONENT(species) { Speed(10); } + } WHEN { + TURN { MOVE(player, move); MOVE(opponent, move); } + } SCENE { + HP_BAR(player, captureDamage: &results[i].damage); + HP_BAR(opponent, captureDamage: &results[i].itemDamage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.1), results[0].itemDamage); // Muscle Bandd + EXPECT_MUL_EQ(results[1].damage, Q_4_12(1.21), results[2].itemDamage); + EXPECT_MUL_EQ(results[2].damage, Q_4_12(1.1), results[2].itemDamage); // Wise Glasses + EXPECT_MUL_EQ(results[3].damage, Q_4_12(1.21), results[3].itemDamage); + EXPECT_MUL_EQ(results[4].damage, Q_4_12(1.2), results[4].itemDamage); // Lustrous Orb + EXPECT_MUL_EQ(results[5].damage, Q_4_12(1.44), results[5].itemDamage); + EXPECT_MUL_EQ(results[6].damage, Q_4_12(1.2), results[6].itemDamage); // Adamant Orb + EXPECT_MUL_EQ(results[7].damage, Q_4_12(1.44), results[7].itemDamage); + EXPECT_MUL_EQ(results[8].damage, Q_4_12(1.2), results[8].itemDamage); // Griseous Orb + EXPECT_MUL_EQ(results[9].damage, Q_4_12(1.43), results[9].itemDamage); + EXPECT_MUL_EQ(results[10].damage, Q_4_12(1.2), results[10].itemDamage); // Soul Dew + EXPECT_MUL_EQ(results[11].damage, Q_4_12(1.44), results[11].itemDamage); + EXPECT_MUL_EQ(results[12].damage, Q_4_12(1.1), results[12].itemDamage); // Punching Glove + EXPECT_MUL_EQ(results[13].damage, Q_4_12(1.21), results[13].itemDamage); + EXPECT_MUL_EQ(results[14].damage, Q_4_12(1.2), results[14].itemDamage); // Ogerpon Mask + EXPECT_MUL_EQ(results[15].damage, Q_4_12(1.44), results[15].itemDamage); + EXPECT_MUL_EQ(results[16].damage, Q_4_12(1.2), results[16].itemDamage); // Expert Belt + EXPECT_MUL_EQ(results[17].damage, Q_4_12(1.44), results[17].itemDamage); + } +} + +SINGLE_BATTLE_TEST("Multi - Kings Rock effect stack when dupes enabled") +{ + PASSES_RANDOMLY(19, 100, RNG_HOLD_EFFECT_FLINCH); + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, TRUE); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_KINGS_ROCK, ITEM_KINGS_ROCK); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + } +} + +SINGLE_BATTLE_TEST("Multi - Kings Rock effect don't stack when dupes disabled") +{ + PASSES_RANDOMLY(10, 100, RNG_HOLD_EFFECT_FLINCH); + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, FALSE); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_KINGS_ROCK, ITEM_KINGS_ROCK); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + } +} + +SINGLE_BATTLE_TEST("Multi - Rocky Helmet effect stack only when dupes enabled") +{ + bool16 dupe = FALSE; + s16 damage; + + PARAMETRIZE { dupe = FALSE; } + PARAMETRIZE { dupe = TRUE; } + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, dupe); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_ROCKY_HELMET, ITEM_ROCKY_HELMET); } + OPPONENT(SPECIES_WOBBUFFET){ MaxHP(60); HP(60); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(opponent, captureDamage: &damage); + } FINALLY { + if (dupe) + EXPECT_EQ(damage, 20); + else + EXPECT_EQ(damage, 10); + } +} + +SINGLE_BATTLE_TEST("Multi - Shell Bell can stack if dupes enabled") +{ + u32 dupe; + s16 damage; + PARAMETRIZE { dupe = FALSE; } + PARAMETRIZE { dupe = TRUE; } + + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, dupe); + ASSUME(GetMoveEffect(MOVE_DRAGON_RAGE) == EFFECT_FIXED_HP_DAMAGE); + ASSUME(GetMoveFixedHPDamage(MOVE_DRAGON_RAGE) == 40); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_SHELL_BELL, ITEM_SHELL_BELL); HP(10); MaxHP(20); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_DRAGON_RAGE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_RAGE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet restored a little HP using its Shell Bell!"); + HP_BAR(player, captureDamage: &damage); + } FINALLY { + if (dupe) + EXPECT_EQ(damage, -10); + else + EXPECT_EQ(damage, -5); + } +} + +SINGLE_BATTLE_TEST("Multi - Life Orb can stack if dupes enabled", s16 damage, s16 itemDamage, s16 selfDamage) +{ + bool16 dupe = FALSE; + + PARAMETRIZE { dupe = FALSE; } + PARAMETRIZE { dupe = TRUE; } + + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, dupe); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_LIFE_ORB, ITEM_LIFE_ORB); Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player, captureDamage: &results[i].damage); // Basic attack + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &results[i].itemDamage); + HP_BAR(player, captureDamage: &results[i].selfDamage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.3), results[0].itemDamage); + EXPECT_MUL_EQ(results[1].damage, Q_4_12(1.69), results[1].itemDamage); + EXPECT_MUL_EQ(results[0].selfDamage, Q_4_12(2), results[1].selfDamage); // Self damage is doubled with 2 orbs + } +} + +SINGLE_BATTLE_TEST("Multi - Leftovers can stack if dupes enabled") +{ + bool16 dupe = FALSE; + + PARAMETRIZE { dupe = FALSE; } + PARAMETRIZE { dupe = TRUE; } + + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, dupe); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_LEFTOVERS, ITEM_LEFTOVERS); HP(50); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { } + } SCENE { + MESSAGE("Wobbuffet restored a little HP using its Leftovers!"); + } FINALLY { + if (dupe) + EXPECT_EQ(player->hp, 62); + else + EXPECT_EQ(player->hp, 56); + } +} + +SINGLE_BATTLE_TEST("Multi - Black Sludge can stack if dupes enabled") +{ + bool16 dupe = FALSE; + u32 species = SPECIES_NONE; + + PARAMETRIZE { species = SPECIES_WOBBUFFET; dupe = FALSE; } + PARAMETRIZE { species = SPECIES_WOBBUFFET; dupe = TRUE; } + PARAMETRIZE { species = SPECIES_GRIMER; dupe = FALSE; } + PARAMETRIZE { species = SPECIES_GRIMER; dupe = TRUE; } + + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, dupe); + PLAYER(species) { Items(ITEM_BLACK_SLUDGE, ITEM_BLACK_SLUDGE); HP(50); MaxHP(100); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { } + } SCENE { + if (species == SPECIES_GRIMER) + MESSAGE("Grimer restored a little HP using its Black Sludge!"); + else + MESSAGE("Wobbuffet was hurt by the Black Sludge!"); + } FINALLY { + if (species == SPECIES_GRIMER) + { + if (dupe) + EXPECT_EQ(player->hp, 62); + else + EXPECT_EQ(player->hp, 56); + } + else + { + if (dupe) + EXPECT_EQ(player->hp, 26); + else + EXPECT_EQ(player->hp, 38); + } + } +} + +SINGLE_BATTLE_TEST("Multi - Metronome can stack if dupes enabled", s16 damage1, s16 damage2, s16 damage3, s16 damage4) +{ + bool16 dupe = FALSE; + + PARAMETRIZE { dupe = FALSE; } + PARAMETRIZE { dupe = TRUE; } + + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, dupe); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_METRONOME, ITEM_METRONOME); Attack(180); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage1); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage2); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage3); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage4); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage1, UQ_4_12(1.2), results[0].damage2); + EXPECT_MUL_EQ(results[0].damage1, UQ_4_12(1.4), results[0].damage3); + EXPECT_MUL_EQ(results[0].damage1, UQ_4_12(1.6), results[0].damage4); + EXPECT_MUL_EQ(results[1].damage1, UQ_4_12(1.42), results[1].damage2); // Multiplier lower than expected due to roundings in calculations + EXPECT_MUL_EQ(results[1].damage1, UQ_4_12(1.79), results[1].damage3); // Multiplier lower than expected due to roundings in calculations + EXPECT_MUL_EQ(results[1].damage1, UQ_4_12(2.19), results[1].damage4); // Multiplier lower than expected due to roundings in calculations + } +} + +#if B_BINDING_DAMAGE >= GEN_6 +SINGLE_BATTLE_TEST("Multi - Binding Band can stack if dupes enabled (Gen 6)", s16 damage) +{ + bool16 dupe = FALSE; + u16 item = ITEM_NONE; + + PARAMETRIZE {item = ITEM_NONE; dupe = FALSE; } + PARAMETRIZE {item = ITEM_BINDING_BAND; dupe = FALSE; } + PARAMETRIZE {item = ITEM_BINDING_BAND; dupe = TRUE; } + + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, dupe); + PLAYER(SPECIES_WOBBUFFET) { Items(item, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_WRAP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WRAP, player); + MESSAGE("The opposing Wobbuffet is hurt by Wrap!"); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(1.32), results[1].damage); // Normal Binding Band multiplier + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(1.64), results[2].damage); // Newer Bind damage math has more starting damage and less Binding Band damage + } +} +#endif + +#if B_BINDING_DAMAGE < GEN_6 +SINGLE_BATTLE_TEST("Multi - Binding Band can stack if dupes enabled (Gen 5)", s16 damage) +{ + bool16 dupe = FALSE; + u16 item = ITEM_NONE, gen = 0; + + PARAMETRIZE {item = ITEM_NONE; dupe = FALSE; } + PARAMETRIZE {item = ITEM_BINDING_BAND; dupe = FALSE; } + PARAMETRIZE {item = ITEM_BINDING_BAND; dupe = TRUE; } + + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, dupe); + WITH_CONFIG(CONFIG_BINDING_DAMAGE, gen); + PLAYER(SPECIES_WOBBUFFET) { Items(item, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_WRAP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WRAP, player); + MESSAGE("The opposing Wobbuffet is hurt by Wrap!"); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2), results[1].damage); // Normal Binding Band multiplier + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(3), results[2].damage); // +1/16 hp damage per Binding Band + } +} +#endif + +SINGLE_BATTLE_TEST("Multi - Speed affecting items can stack if dupes enabled") +{ + bool16 dupe = FALSE; + u16 item = ITEM_NONE, item2 = ITEM_NONE, speed1 = 0, speed2 = 0; + + PARAMETRIZE {item = ITEM_NONE; item2 = ITEM_NONE; dupe = FALSE; speed1 = 99; speed2 = 100; } + PARAMETRIZE {item = ITEM_NONE; item2 = ITEM_MACHO_BRACE; dupe = FALSE; speed1 = 99; speed2 = 200; } + PARAMETRIZE {item = ITEM_NONE; item2 = ITEM_MACHO_BRACE; dupe = TRUE; speed1 = 51; speed2 = 200; } + PARAMETRIZE {item = ITEM_NONE; item2 = ITEM_POWER_ANKLET; dupe = FALSE; speed1 = 99; speed2 = 200; } + PARAMETRIZE {item = ITEM_NONE; item2 = ITEM_POWER_ANKLET; dupe = TRUE; speed1 = 51; speed2 = 200; } + PARAMETRIZE {item = ITEM_NONE; item2 = ITEM_IRON_BALL; dupe = FALSE; speed1 = 99; speed2 = 200; } + PARAMETRIZE {item = ITEM_NONE; item2 = ITEM_IRON_BALL; dupe = TRUE; speed1 = 51; speed2 = 200; } + PARAMETRIZE {item = ITEM_CHOICE_SCARF; item2 = ITEM_NONE; dupe = FALSE; speed1 = 100; speed2 = 151; } + PARAMETRIZE {item = ITEM_CHOICE_SCARF; item2 = ITEM_NONE; dupe = TRUE; speed1 = 100; speed2 = 224; } + PARAMETRIZE {item = ITEM_QUICK_POWDER; item2 = ITEM_NONE; dupe = FALSE; speed1 = 100; speed2 = 201; } + PARAMETRIZE {item = ITEM_QUICK_POWDER; item2 = ITEM_NONE; dupe = TRUE; speed1 = 100; speed2 = 399; } + + + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, dupe); + if (item == ITEM_QUICK_POWDER ) + PLAYER(SPECIES_DITTO) { Items(item, item); Speed(speed1); } + else + PLAYER(SPECIES_WOBBUFFET) { Items(item, item); Speed(speed1); } + OPPONENT(SPECIES_WOBBUFFET) { Items(item2, item2); Speed(speed2); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + if (dupe) + { + if (item == ITEM_QUICK_POWDER) + MESSAGE("Ditto used Scratch!"); + else + MESSAGE("Wobbuffet used Scratch!"); + MESSAGE("The opposing Wobbuffet used Scratch!"); + } + else + { + MESSAGE("The opposing Wobbuffet used Scratch!"); + if (item == ITEM_QUICK_POWDER) + MESSAGE("Ditto used Scratch!"); + else + MESSAGE("Wobbuffet used Scratch!"); + } + } +} + +SINGLE_BATTLE_TEST("Multi - Quick Claw effect stacks when dupes enabled") +{ + PASSES_RANDOMLY(36, 100, RNG_QUICK_CLAW); + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, TRUE); + PLAYER(SPECIES_WOBBUFFET) { Speed(1); Items(ITEM_QUICK_CLAW, ITEM_QUICK_CLAW); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(100); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet used Scratch!"); + MESSAGE("The opposing Wobbuffet used Celebrate!"); + } +} + +SINGLE_BATTLE_TEST("Multi - Quick Claw effect don't stack when dupes disabled") +{ + PASSES_RANDOMLY(20, 100, RNG_QUICK_CLAW); + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, FALSE); + PLAYER(SPECIES_WOBBUFFET) { Speed(1); Items(ITEM_QUICK_CLAW, ITEM_QUICK_CLAW); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(100); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet used Scratch!"); + MESSAGE("The opposing Wobbuffet used Celebrate!"); + } +} + +SINGLE_BATTLE_TEST("Multi - Scope Lens effect stacks when dupes enabled") +{ + u32 genConfig = 0, passes, trials; + PARAMETRIZE { genConfig = GEN_1; passes = 2; trials = 4; } // 50% with Wobbuffet's base speed + for (u32 j = GEN_2; j <= GEN_5; j++) + PARAMETRIZE { genConfig = j; passes = 2; trials = 8; } // 25% + for (u32 j = GEN_6; j <= GEN_9; j++) + PARAMETRIZE { genConfig = j; passes = 4; trials = 8; } // 50% + PASSES_RANDOMLY(passes, trials, RNG_CRITICAL_HIT); + GIVEN { + WITH_CONFIG(CONFIG_CRIT_CHANCE, genConfig); + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, TRUE); + ASSUME(gItemsInfo[ITEM_SCOPE_LENS].holdEffect == HOLD_EFFECT_SCOPE_LENS); + ASSUME(GetSpeciesBaseSpeed(SPECIES_WOBBUFFET) == 33); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_SCOPE_LENS, ITEM_SCOPE_LENS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("A critical hit!"); + } +} + +SINGLE_BATTLE_TEST("Multi - Scope Lens effect doesn't stack when dupes disabled") +{ + u32 genConfig = 0, passes, trials; + PARAMETRIZE { genConfig = GEN_1; passes = 1; trials = 4; } // 50% with Wobbuffet's base speed + for (u32 j = GEN_2; j <= GEN_9; j++) + PARAMETRIZE { genConfig = j; passes = 1; trials = 8; } // 25% + + PASSES_RANDOMLY(passes, trials, RNG_CRITICAL_HIT); + GIVEN { + WITH_CONFIG(CONFIG_CRIT_CHANCE, genConfig); + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, FALSE); + ASSUME(gItemsInfo[ITEM_SCOPE_LENS].holdEffect == HOLD_EFFECT_SCOPE_LENS); + ASSUME(GetSpeciesBaseSpeed(SPECIES_WOBBUFFET) == 33); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_SCOPE_LENS, ITEM_SCOPE_LENS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("A critical hit!"); + } +} + +SINGLE_BATTLE_TEST("Multi - Lucky Punch effect stacks when dupes enabled") +{ + u32 genConfig = 0, passes, trials; + PARAMETRIZE { genConfig = GEN_1; passes = 4; trials = 4; } // 50% with Wobbuffet's base speed + for (u32 j = GEN_2; j <= GEN_5; j++) + PARAMETRIZE { genConfig = j; passes = 4; trials = 8; } // 50% + for (u32 j = GEN_6; j <= GEN_9; j++) + PARAMETRIZE { genConfig = j; passes = 8; trials = 8; } // 100% + PASSES_RANDOMLY(passes, trials, RNG_CRITICAL_HIT); + GIVEN { + WITH_CONFIG(CONFIG_CRIT_CHANCE, genConfig); + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, TRUE); + ASSUME(gItemsInfo[ITEM_LUCKY_PUNCH].holdEffect == HOLD_EFFECT_LUCKY_PUNCH); + PLAYER(SPECIES_CHANSEY) { Items(ITEM_LUCKY_PUNCH, ITEM_LUCKY_PUNCH); Speed(30);} + OPPONENT(SPECIES_WOBBUFFET) { Speed(30); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("A critical hit!"); + } +} + +SINGLE_BATTLE_TEST("Multi - Lucky Punch effect doesn't stack when dupes disabled") +{ + u32 genConfig = 0, passes, trials; + PARAMETRIZE { genConfig = GEN_1; passes = 25; trials = 32; } // ~78.1% with Chansey's base speed + for (u32 j = GEN_2; j <= GEN_5; j++) + PARAMETRIZE { genConfig = j; passes = 1; trials = 4; } // 25% + for (u32 j = GEN_6; j <= GEN_9; j++) + PARAMETRIZE { genConfig = j; passes = 1; trials = 2; } // 50% + PASSES_RANDOMLY(passes, trials, RNG_CRITICAL_HIT); + GIVEN { + WITH_CONFIG(CONFIG_CRIT_CHANCE, genConfig); + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, FALSE); + ASSUME(gItemsInfo[ITEM_SCOPE_LENS].holdEffect == HOLD_EFFECT_SCOPE_LENS); + PLAYER(SPECIES_CHANSEY) { Items(ITEM_LUCKY_PUNCH, ITEM_LUCKY_PUNCH); Speed(30);} + OPPONENT(SPECIES_WOBBUFFET) { Speed(30); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("A critical hit!"); + } +} + +SINGLE_BATTLE_TEST("Multi - Scope Lens effect stacks when dupes enabled") +{ + u32 genConfig = 0, passes, trials; + PARAMETRIZE { genConfig = GEN_1; passes = 2; trials = 4; } // 50% with Wobbuffet's base speed + for (u32 j = GEN_2; j <= GEN_5; j++) + PARAMETRIZE { genConfig = j; passes = 2; trials = 8; } // 25% + for (u32 j = GEN_6; j <= GEN_9; j++) + PARAMETRIZE { genConfig = j; passes = 4; trials = 8; } // 50% + PASSES_RANDOMLY(passes, trials, RNG_CRITICAL_HIT); + GIVEN { + WITH_CONFIG(CONFIG_CRIT_CHANCE, genConfig); + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, TRUE); + ASSUME(gItemsInfo[ITEM_SCOPE_LENS].holdEffect == HOLD_EFFECT_SCOPE_LENS); + ASSUME(GetSpeciesBaseSpeed(SPECIES_WOBBUFFET) == 33); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_SCOPE_LENS, ITEM_SCOPE_LENS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("A critical hit!"); + } +} + +// Manual tests pass, 2 Focus Bands is 19% chance +TO_DO_BATTLE_TEST("Multi - Focus Band effect stacks when dupes enabled") +TO_DO_BATTLE_TEST("Multi - Focus Band effect don't stack when dupes disabled") +// SINGLE_BATTLE_TEST("Multi - BAND") +// { +// GIVEN { +// WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, TRUE); +// PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_FOCUS_BAND, ITEM_FOCUS_BAND); MaxHP(100); HP(3); } +// OPPONENT(SPECIES_WOBBUFFET){ Items(ITEM_FOCUS_BAND); MaxHP(100); HP(3); } +// } WHEN { +// TURN { MOVE(player, MOVE_HYPER_BEAM); MOVE(opponent, MOVE_HYPER_BEAM); } +// } +// } + +SINGLE_BATTLE_TEST("Multi - Light Clay effect stacks when dupes enabled") +{ + bool16 dupe = FALSE; + + PARAMETRIZE {dupe = FALSE; } + PARAMETRIZE {dupe = TRUE; } + + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, dupe); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_LIGHT_CLAY, ITEM_LIGHT_CLAY); } + OPPONENT(SPECIES_ABOMASNOW) { Ability(ABILITY_SNOW_WARNING); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_LIGHT_SCREEN); MOVE(opponent, MOVE_LIGHT_SCREEN); } // Light Screen start + TURN { MOVE(player, MOVE_REFLECT); MOVE(opponent, MOVE_REFLECT); } + TURN { MOVE(player, MOVE_AURORA_VEIL); MOVE(opponent, MOVE_AURORA_VEIL); } + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); } // 5 turns, opponent wear off + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); } // 8 turns + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); } // 11 turns + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_LIGHT_SCREEN, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_LIGHT_SCREEN, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_REFLECT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_REFLECT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_AURORA_VEIL, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_AURORA_VEIL, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("The opposing team's Light Screen wore off!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("The opposing team's Reflect wore off!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("The opposing team's Aurora Veil wore off!"); + if (!dupe) + { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("Your team's Light Screen wore off!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("Your team's Reflect wore off!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("Your team's Aurora Veil wore off!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + } + else + { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("Your team's Light Screen wore off!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("Your team's Reflect wore off!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("Your team's Aurora Veil wore off!"); + } + } +} + +SINGLE_BATTLE_TEST("Multi - Grip Claw effect adds 2 turns when dupes enabled") +{ + u32 config, move; + bool16 dupe = FALSE; + + PARAMETRIZE { config = GEN_4; dupe = FALSE; move = MOVE_WRAP; } + PARAMETRIZE { config = GEN_5; dupe = FALSE; move = MOVE_WRAP; } + PARAMETRIZE { config = GEN_4; dupe = TRUE; move = MOVE_WRAP; } + PARAMETRIZE { config = GEN_5; dupe = TRUE; move = MOVE_WRAP; } + PARAMETRIZE { config = GEN_4; dupe = FALSE; move = MOVE_FIRE_SPIN; } + PARAMETRIZE { config = GEN_5; dupe = FALSE; move = MOVE_FIRE_SPIN; } + PARAMETRIZE { config = GEN_4; dupe = TRUE; move = MOVE_FIRE_SPIN; } + PARAMETRIZE { config = GEN_5; dupe = TRUE; move = MOVE_FIRE_SPIN; } + + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, dupe); + WITH_CONFIG(CONFIG_BINDING_TURNS, config); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_GRIP_CLAW, ITEM_GRIP_CLAW); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + TURN {} + TURN {} + TURN {} + TURN {} + TURN { MOVE(opponent, MOVE_RECOVER); } + TURN {} + TURN {} + TURN {} + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent); // Direct damage + + HP_BAR(opponent); // Residual Damage + HP_BAR(opponent); // Residual Damage + HP_BAR(opponent); // Residual Damage + HP_BAR(opponent); // Residual Damage + HP_BAR(opponent); // Residual Damage + HP_BAR(opponent); // Heal to continue test + if (config >= GEN_5) { + HP_BAR(opponent); // Residual Damage + HP_BAR(opponent); // Residual Damage + } + if (dupe) { + HP_BAR(opponent); // Residual Damage + HP_BAR(opponent); // Residual Damage + } + NOT HP_BAR(opponent); // Residual Damage + } +} + +WILD_BATTLE_TEST("Multi - Lucky Egg effect adds 2 turns when dupes enabled", s32 exp) +{ + bool16 dupe = FALSE; + + PARAMETRIZE {dupe = FALSE; } + PARAMETRIZE {dupe = TRUE; } + + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, dupe); + PLAYER(SPECIES_WOBBUFFET) { Level(20); Items(ITEM_LUCKY_EGG, ITEM_LUCKY_EGG); } + OPPONENT(SPECIES_CATERPIE) { Level(10); HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet used Scratch!"); + MESSAGE("The wild Caterpie fainted!"); + EXPERIENCE_BAR(player, captureGainedExp: &results[i].exp); + } FINALLY { + EXPECT_MUL_EQ(results[0].exp, Q_4_12(1.5), results[1].exp); + } +} + +WILD_BATTLE_TEST("Multi - Macho Brace effect does not stack when dupes disabled") +{ + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, FALSE); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_MACHO_BRACE, ITEM_MACHO_BRACE); } + OPPONENT(SPECIES_CATERPIE) { HP(1); } + ASSUME(gSpeciesInfo[SPECIES_CATERPIE].evYield_HP == 1); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet used Scratch!"); + MESSAGE("The wild Caterpie fainted!"); + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_HP_EV), 2); + } +} + +WILD_BATTLE_TEST("Multi - Macho Brace effect stacks when dupes enabled") +{ + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, TRUE); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_MACHO_BRACE, ITEM_MACHO_BRACE); } + OPPONENT(SPECIES_CATERPIE) { HP(1); } + ASSUME(gSpeciesInfo[SPECIES_CATERPIE].evYield_HP == 1); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet used Scratch!"); + MESSAGE("The wild Caterpie fainted!"); + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_HP_EV), 3); + } +} + +WILD_BATTLE_TEST("Multi - Power Weight effect stacks regardless of dupe setting") +{ + bool16 dupe = FALSE; + + PARAMETRIZE {dupe = FALSE; } + PARAMETRIZE {dupe = TRUE; } + + GIVEN { + WITH_CONFIG(CONFIG_ALLOW_HELD_DUPES, dupe); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_POWER_WEIGHT, ITEM_POWER_WEIGHT); } + OPPONENT(SPECIES_CATERPIE) { HP(1); } + ASSUME(gSpeciesInfo[SPECIES_CATERPIE].evYield_HP == 1); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet used Scratch!"); + MESSAGE("The wild Caterpie fainted!"); + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_HP_EV), 17); // 1 + 8 + 8 + } +} + +WILD_BATTLE_TEST("Multi - Power Weight effect stacks with Macho Brace") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_POWER_WEIGHT, ITEM_MACHO_BRACE); } + OPPONENT(SPECIES_CATERPIE) { HP(1); } + ASSUME(gSpeciesInfo[SPECIES_CATERPIE].evYield_HP == 1); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + MESSAGE("Wobbuffet used Scratch!"); + MESSAGE("The wild Caterpie fainted!"); + } THEN { + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_HP_EV), 18); // (1 + 8) * 2 + } +} + +#endif + diff --git a/test/battle/sleep_clause.c b/test/battle/sleep_clause.c index ba14671b4370..b6fefac9abac 100644 --- a/test/battle/sleep_clause.c +++ b/test/battle/sleep_clause.c @@ -1875,3 +1875,640 @@ DOUBLE_BATTLE_TEST("Sleep Clause: Opponent Spore'ing player's partner after part MESSAGE("Sleep Clause kept Zigzagoon awake!"); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Sleep Clause: Effect Spore causes sleep 11% of the time with sleep clause active (Traits)") +{ + PASSES_RANDOMLY(11, 100, RNG_EFFECT_SPORE); + GIVEN { + FLAG_SET(B_FLAG_SLEEP_CLAUSE); + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_TECHNICIAN); Innates(ABILITY_EFFECT_SPORE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SPORE); } + TURN { SWITCH(player, 1); } + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, player); + MESSAGE("The opposing Breloom's Effect Spore made Wobbuffet sleep!"); + STATUS_ICON(player, sleep: TRUE); + } +} + +DOUBLE_BATTLE_TEST("Sleep Clause: Effect Spore causes sleep 11% of the time with sleep clause active (Doubles) (Traits)") +{ + PASSES_RANDOMLY(11, 100, RNG_EFFECT_SPORE); + GIVEN { + FLAG_SET(B_FLAG_SLEEP_CLAUSE); + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_TECHNICIAN); Innates(ABILITY_EFFECT_SPORE); } + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_TECHNICIAN); Innates(ABILITY_EFFECT_SPORE); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SPORE, target:playerRight); MOVE(playerLeft, MOVE_SCRATCH, target:opponentLeft);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponentLeft); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, playerRight); + MESSAGE("Wobbuffet fell asleep!"); + STATUS_ICON(playerRight, sleep: TRUE); + ABILITY_POPUP(opponentLeft, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, playerLeft); + MESSAGE("The opposing Breloom's Effect Spore made Wobbuffet sleep!"); + STATUS_ICON(playerLeft, sleep: TRUE); + } +} + +SINGLE_BATTLE_TEST("Sleep Clause: Sleep from Effect Spore will not activate sleep clause (Traits)") +{ + PASSES_RANDOMLY(11, 100, RNG_EFFECT_SPORE); + GIVEN { + FLAG_SET(B_FLAG_SLEEP_CLAUSE); + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_TECHNICIAN); Innates(ABILITY_EFFECT_SPORE); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN {} + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_SPORE); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, player); + MESSAGE("The opposing Breloom's Effect Spore made Wobbuffet sleep!"); + STATUS_ICON(player, sleep: TRUE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, player); + MESSAGE("Wobbuffet fell asleep!"); + STATUS_ICON(player, sleep: TRUE); + } +} + +DOUBLE_BATTLE_TEST("Sleep Clause: Sleep from Effect Spore will not activate sleep clause (Doubles) (Traits)") +{ + PASSES_RANDOMLY(11, 100, RNG_EFFECT_SPORE); + GIVEN { + FLAG_SET(B_FLAG_SLEEP_CLAUSE); + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_TECHNICIAN); Innates(ABILITY_EFFECT_SPORE); } + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_TECHNICIAN); Innates(ABILITY_EFFECT_SPORE); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target:opponentLeft); MOVE(opponentLeft, MOVE_SPORE, target:playerRight); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, playerLeft); + MESSAGE("The opposing Breloom's Effect Spore made Wobbuffet sleep!"); + STATUS_ICON(playerLeft, sleep: TRUE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponentLeft); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, playerRight); + MESSAGE("Wobbuffet fell asleep!"); + STATUS_ICON(playerRight, sleep: TRUE); + } +} + +SINGLE_BATTLE_TEST("Sleep Clause: Sleep clause is deactivated when a sleeping mon is woken up by Hydration in the rain (Traits)") +{ + GIVEN { + FLAG_SET(B_FLAG_SLEEP_CLAUSE); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + PLAYER(SPECIES_PELIPPER) { Ability(ABILITY_KEEN_EYE); Innates(ABILITY_DRIZZLE); } + OPPONENT(SPECIES_LUVDISC) { Ability(ABILITY_SWIFT_SWIM); Innates(ABILITY_HYDRATION); } + } WHEN { + TURN { MOVE(player, MOVE_SPORE); } + TURN { MOVE(player, MOVE_SPORE); } + } SCENE { + MESSAGE("Pelipper used Spore!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + MESSAGE("The opposing Luvdisc fell asleep!"); + MESSAGE("The opposing Luvdisc's Hydration cured its sleep problem!"); + STATUS_ICON(opponent, sleep: FALSE); + MESSAGE("Pelipper used Spore!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + MESSAGE("The opposing Luvdisc fell asleep!"); + } +} + +SINGLE_BATTLE_TEST("Sleep Clause: Sleep clause is deactivated when a sleeping mon is woken up by Natural Cure (Traits)") +{ + GIVEN { + FLAG_SET(B_FLAG_SLEEP_CLAUSE); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + PLAYER(SPECIES_ZIGZAGOON); + OPPONENT(SPECIES_SWABLU) { Ability(ABILITY_CLOUD_NINE); Innates(ABILITY_NATURAL_CURE); } + OPPONENT(SPECIES_ZIGZAGOON); + } WHEN { + TURN { MOVE(player, MOVE_SPORE); } + TURN { SWITCH(opponent, 1); } + TURN { SWITCH(opponent, 0); MOVE(player, MOVE_SPORE); } + } SCENE { + MESSAGE("Zigzagoon used Spore!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + MESSAGE("The opposing Swablu fell asleep!"); + MESSAGE("2 withdrew Swablu!"); + MESSAGE("2 sent out Swablu!"); + MESSAGE("Zigzagoon used Spore!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + MESSAGE("The opposing Swablu fell asleep!"); + } +} + +SINGLE_BATTLE_TEST("Sleep Clause: Sleep clause is deactivated when a sleeping mon is woken up by Shed Skin (Traits)") +{ + if (B_ABILITY_TRIGGER_CHANCE == GEN_4) + PASSES_RANDOMLY(30, 100, RNG_SHED_SKIN); + else + PASSES_RANDOMLY(33, 100, RNG_SHED_SKIN); + GIVEN { + FLAG_SET(B_FLAG_SLEEP_CLAUSE); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + PLAYER(SPECIES_ZIGZAGOON); + OPPONENT(SPECIES_DRATINI) { Ability(ABILITY_MARVEL_SCALE); Innates(ABILITY_SHED_SKIN); } + } WHEN { + TURN { MOVE(player, MOVE_SPORE); } + TURN { MOVE(player, MOVE_SPORE); } + } SCENE { + MESSAGE("Zigzagoon used Spore!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + MESSAGE("The opposing Dratini fell asleep!"); + MESSAGE("The opposing Dratini's Shed Skin cured its sleep problem!"); + MESSAGE("Zigzagoon used Spore!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + MESSAGE("The opposing Dratini fell asleep!"); + } +} + +DOUBLE_BATTLE_TEST("Sleep Clause: Sleep clause is deactivated when a sleeping mon is woken up by Healer (Traits)") +{ + PASSES_RANDOMLY(30, 100, RNG_HEALER); + GIVEN { + FLAG_SET(B_FLAG_SLEEP_CLAUSE); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + PLAYER(SPECIES_ZIGZAGOON); + PLAYER(SPECIES_ZIGZAGOON); + OPPONENT(SPECIES_ZIGZAGOON); + OPPONENT(SPECIES_CHANSEY) { Ability(ABILITY_SERENE_GRACE); Innates(ABILITY_HEALER); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SPORE, target:opponentLeft); } + TURN { MOVE(playerLeft, MOVE_SPORE, target:opponentLeft); } + } SCENE { + MESSAGE("Zigzagoon used Spore!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, playerLeft); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponentLeft); + MESSAGE("The opposing Zigzagoon fell asleep!"); + MESSAGE("The opposing Chansey's Healer cured the opposing Zigzagoon's problem!"); + MESSAGE("Zigzagoon used Spore!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, playerLeft); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponentLeft); + MESSAGE("The opposing Zigzagoon fell asleep!"); + } +} + +SINGLE_BATTLE_TEST("Sleep Clause: Sleep clause is deactivated when a sleeping mon is sent out and transforms into a mon with Insomnia / Vital spirit (Traits)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_VITAL_SPIRIT; } + PARAMETRIZE { ability = ABILITY_INSOMNIA; } + KNOWN_FAILING; // Sleep Clause parts work, but Imposter seems broken with battle messages / targeting. Issue #5565 https://github.com/rh-hideout/pokeemerald-expansion/issues/5565 + GIVEN { + FLAG_SET(B_FLAG_SLEEP_CLAUSE); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + ASSUME(gItemsInfo[ITEM_LAGGING_TAIL].holdEffect == HOLD_EFFECT_LAGGING_TAIL); + PLAYER(SPECIES_ZIGZAGOON) + PLAYER(SPECIES_DELIBIRD) { Ability(ABILITY_HUSTLE); Innates(ability); } + OPPONENT(SPECIES_DITTO) { Ability(ABILITY_LIMBER); Innates(ABILITY_IMPOSTER); } + OPPONENT(SPECIES_ZIGZAGOON); + } WHEN { + TURN { MOVE(player, MOVE_SPORE); } + TURN { SWITCH(player, 1); SWITCH(opponent, 1); } + TURN { SWITCH(opponent, 0); } + TURN { SWITCH(opponent, 1); MOVE(player, MOVE_SPORE); } + } SCENE { + MESSAGE("The opposing Ditto transformed into Zigzagoon using Imposter!"); + MESSAGE("Zigzagoon used Spore!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + MESSAGE("The opposing Ditto fell asleep!"); + MESSAGE("2 sent out Zigzagoon!"); + MESSAGE("2 sent out Ditto!"); + if (ability == ABILITY_VITAL_SPIRIT) + MESSAGE("The opposing Ditto's Vital Spirit cured its sleep problem!"); + else + MESSAGE("The opposing Ditto's Insomnia cured its sleep problem!"); + MESSAGE("2 sent out Zigzagoon!"); + MESSAGE("Delibird used Spore!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + MESSAGE("The opposing Zigzagoon fell asleep!"); + } +} + +SINGLE_BATTLE_TEST("Sleep Clause: Sleep caused by Effect Spore does not prevent sleep clause from ever activating (Traits)") // checks that sleepClauseEffectExempt works properly +{ + PASSES_RANDOMLY(11, 100, RNG_EFFECT_SPORE); + GIVEN { + FLAG_SET(B_FLAG_SLEEP_CLAUSE); + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + ASSUME(GetMoveEffect(MOVE_AROMATHERAPY) == EFFECT_HEAL_BELL); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_ZIGZAGOON); + PLAYER(SPECIES_ZIGZAGOON); + PLAYER(SPECIES_ZIGZAGOON); + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_TECHNICIAN); Innates(ABILITY_EFFECT_SPORE); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_SPORE); } + TURN { SWITCH(player, 2); MOVE(opponent, MOVE_SPORE); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, player); + MESSAGE("The opposing Breloom's Effect Spore made Zigzagoon sleep!"); + STATUS_ICON(player, sleep: TRUE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, player); + MESSAGE("Zigzagoon fell asleep!"); + STATUS_ICON(player, sleep: TRUE); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, player); + MESSAGE("Zigzagoon fell asleep!"); + STATUS_ICON(player, sleep: TRUE); + } + MESSAGE("Sleep Clause kept Zigzagoon awake!"); + } +} + +SINGLE_BATTLE_TEST("Sleep Clause: Waking up after Effect Spore doesn't deactivate sleep clause (Traits)") +{ + PASSES_RANDOMLY(11, 100, RNG_EFFECT_SPORE); + GIVEN { + FLAG_SET(B_FLAG_SLEEP_CLAUSE); + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_ZIGZAGOON); + PLAYER(SPECIES_ZIGZAGOON); + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_TECHNICIAN); Innates(ABILITY_EFFECT_SPORE); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + TURN {} + TURN {} + TURN {} + TURN { MOVE(opponent, MOVE_SPORE); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_SPORE); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, player); + MESSAGE("The opposing Breloom's Effect Spore made Zigzagoon sleep!"); + STATUS_ICON(player, sleep: TRUE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, player); + MESSAGE("Zigzagoon fell asleep!"); + STATUS_ICON(player, sleep: TRUE); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, player); + MESSAGE("Zigzagoon fell asleep!"); + STATUS_ICON(player, sleep: TRUE); + } + MESSAGE("Sleep Clause kept Zigzagoon awake!"); + } +} + +DOUBLE_BATTLE_TEST("Sleep Clause: Waking up after Effect Spore doesn't deactivate sleep clause (Doubles) (Traits)") +{ + PASSES_RANDOMLY(11, 100, RNG_EFFECT_SPORE); + GIVEN { + FLAG_SET(B_FLAG_SLEEP_CLAUSE); + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + ASSUME(GetMoveEffect(MOVE_AROMATHERAPY) == EFFECT_HEAL_BELL); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_ZIGZAGOON); + PLAYER(SPECIES_ZIGZAGOON); + PLAYER(SPECIES_ZIGZAGOON); + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_TECHNICIAN); Innates(ABILITY_EFFECT_SPORE); } + OPPONENT(SPECIES_ZIGZAGOON); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); MOVE(opponentRight, MOVE_SPORE, target:playerRight); } + TURN { SWITCH(playerLeft, 2); } + TURN { MOVE(playerLeft, MOVE_AROMATHERAPY); MOVE(opponentRight, MOVE_SPORE, target:playerRight); MOVE(opponentLeft, MOVE_SPORE, target:playerLeft); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, playerLeft); + MESSAGE("The opposing Breloom's Effect Spore made Zigzagoon sleep!"); + STATUS_ICON(playerLeft, sleep: TRUE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponentRight); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, playerRight); + MESSAGE("Zigzagoon fell asleep!"); + STATUS_ICON(playerRight, sleep: TRUE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_AROMATHERAPY, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponentRight); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, playerRight); + MESSAGE("Zigzagoon fell asleep!"); + STATUS_ICON(playerRight, sleep: TRUE); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponentLeft); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, playerLeft); + MESSAGE("Zigzagoon fell asleep!"); + STATUS_ICON(playerLeft, sleep: TRUE); + } + MESSAGE("Sleep Clause kept Zigzagoon awake!"); + } +} + +SINGLE_BATTLE_TEST("Sleep Clause: Mold Breaker Pokémon sleeping Vital Spirit / Insomnia activates sleep clause (Traits)") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_VITAL_SPIRIT; } + PARAMETRIZE { ability = ABILITY_INSOMNIA; } + GIVEN { + FLAG_SET(B_FLAG_SLEEP_CLAUSE); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + PLAYER(SPECIES_PANCHAM) { Ability(ABILITY_SCRAPPY); Innates(ABILITY_MOLD_BREAKER); } + OPPONENT(SPECIES_DELIBIRD) { Ability(ABILITY_HUSTLE); Innates(ability); } + OPPONENT(SPECIES_ZIGZAGOON); + } WHEN { + TURN { MOVE(player, MOVE_SPORE); } + TURN { SWITCH(opponent, 1); MOVE(player, MOVE_SPORE); } + TURN { SWITCH(opponent, 0); } + TURN { SWITCH(opponent, 1); MOVE(player, MOVE_SPORE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + MESSAGE("The opposing Delibird fell asleep!"); + STATUS_ICON(opponent, sleep: TRUE); + ABILITY_POPUP(opponent, ability); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + MESSAGE("The opposing Zigzagoon fell asleep!"); + STATUS_ICON(opponent, sleep: TRUE); + } +} + +SINGLE_BATTLE_TEST("Sleep Clause: Yawn'd Pokémon slept due to Effect Spore before Yawn triggers does not activate sleep clause (Traits)") +{ + PASSES_RANDOMLY(11, 100, RNG_EFFECT_SPORE); + GIVEN { + FLAG_SET(B_FLAG_SLEEP_CLAUSE); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + ASSUME(GetMoveEffect(MOVE_YAWN) == EFFECT_YAWN); + ASSUME(MoveMakesContact(MOVE_SCRATCH)); + PLAYER(SPECIES_BRELOOM) { Ability(ABILITY_TECHNICIAN); Innates(ABILITY_EFFECT_SPORE); } + OPPONENT(SPECIES_ZIGZAGOON); + OPPONENT(SPECIES_ZIGZAGOON); + } WHEN { + TURN { MOVE(player, MOVE_YAWN); } + TURN { MOVE(opponent, MOVE_SCRATCH); } + TURN { SWITCH(opponent, 1); MOVE(player, MOVE_SPORE); } + } SCENE { + MESSAGE("The opposing Zigzagoon grew drowsy!"); + ABILITY_POPUP(player, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + MESSAGE("Breloom's Effect Spore made the opposing Zigzagoon sleep!"); + STATUS_ICON(opponent, sleep: TRUE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + MESSAGE("The opposing Zigzagoon fell asleep!"); + STATUS_ICON(opponent, sleep: TRUE); + } +} + +SINGLE_BATTLE_TEST("Sleep Clause: Magic Bounce'ing a sleep move activates sleep clause, and fails if sleep clause is active (Traits)") +{ + GIVEN { + FLAG_SET(B_FLAG_SLEEP_CLAUSE); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + PLAYER(SPECIES_ESPEON) { Ability(ABILITY_SYNCHRONIZE); Innates(ABILITY_MAGIC_BOUNCE); } + PLAYER(SPECIES_ZIGZAGOON); + OPPONENT(SPECIES_ZIGZAGOON); + OPPONENT(SPECIES_ZIGZAGOON); + } WHEN { + TURN { MOVE(opponent, MOVE_SPORE); } + TURN { SWITCH(opponent, 1); } + TURN { MOVE(opponent, MOVE_SPORE); } + } SCENE { + MESSAGE("The opposing Zigzagoon's Spore was bounced back by Espeon's Magic Bounce!"); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + MESSAGE("The opposing Zigzagoon fell asleep!"); + STATUS_ICON(opponent, sleep: TRUE); + MESSAGE("The opposing Zigzagoon's Spore was bounced back by Espeon's Magic Bounce!"); + NONE_OF { + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + MESSAGE("The opposing Zigzagoon fell asleep!"); + STATUS_ICON(opponent, sleep: TRUE); + } + MESSAGE("Sleep Clause kept the opposing Zigzagoon awake!"); + } +} + +DOUBLE_BATTLE_TEST("Sleep Clause: Magic Bounce reflecting Dark Void only sleeps one opposing Pokémon (Traits)") +{ + // Source: https://bulbapedia.bulbagarden.net/wiki/Dark_Void_(move) + GIVEN { + FLAG_SET(B_FLAG_SLEEP_CLAUSE); + ASSUME(GetMoveEffect(MOVE_DARK_VOID) == EFFECT_DARK_VOID); + PLAYER(SPECIES_ESPEON) { Ability(ABILITY_SYNCHRONIZE); Innates(ABILITY_MAGIC_BOUNCE); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_DARKRAI); + OPPONENT(SPECIES_DARKRAI); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_DARK_VOID); } + } SCENE { + MESSAGE("The opposing Darkrai's Dark Void was bounced back by Espeon's Magic Bounce!"); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponentLeft); + MESSAGE("The opposing Darkrai fell asleep!"); + STATUS_ICON(opponentLeft, sleep: TRUE); + NONE_OF { + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponentRight); + STATUS_ICON(opponentRight, sleep: TRUE); + MESSAGE("The opposing Darkrai fell asleep!"); + } + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Sleep Clause: Sleep clause is deactivated when a sleeping mon is woken up by using Sleep Talk into a status curing move (Multi)") +{ + u32 move; + PARAMETRIZE { move = MOVE_PSYCHO_SHIFT; } + PARAMETRIZE { move = MOVE_JUNGLE_HEALING; } + PARAMETRIZE { move = MOVE_LUNAR_BLESSING; } + PARAMETRIZE { move = MOVE_TAKE_HEART; } + PARAMETRIZE { move = MOVE_AROMATHERAPY; } + GIVEN { + FLAG_SET(B_FLAG_SLEEP_CLAUSE); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + ASSUME(GetMoveEffect(MOVE_SLEEP_TALK) == EFFECT_SLEEP_TALK); + ASSUME(GetMoveEffect(MOVE_PSYCHO_SHIFT) == EFFECT_PSYCHO_SHIFT); + ASSUME(GetMoveEffect(MOVE_JUNGLE_HEALING) == EFFECT_JUNGLE_HEALING); + ASSUME(GetMoveEffect(MOVE_LUNAR_BLESSING) == EFFECT_JUNGLE_HEALING); + ASSUME(GetMoveEffect(MOVE_PURIFY) == EFFECT_PURIFY); + ASSUME(GetMoveEffect(MOVE_TAKE_HEART) == EFFECT_TAKE_HEART); + ASSUME(GetMoveEffect(MOVE_AROMATHERAPY) == EFFECT_HEAL_BELL); + ASSUME(gItemsInfo[ITEM_CHESTO_BERRY].holdEffect == HOLD_EFFECT_CURE_SLP); + PLAYER(SPECIES_ZIGZAGOON) { Items(ITEM_ORAN_BERRY, ITEM_CHESTO_BERRY); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SLEEP_TALK, move); } + } WHEN { + TURN { MOVE(player, MOVE_SPORE); MOVE(opponent, MOVE_SLEEP_TALK); } + TURN { MOVE(player, MOVE_SPORE); MOVE(opponent, move); } + } SCENE { + MESSAGE("Zigzagoon used Spore!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + MESSAGE("The opposing Zigzagoon fell asleep!"); + MESSAGE("The opposing Zigzagoon used Sleep Talk!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SLEEP_TALK, opponent); + if (move == MOVE_PSYCHO_SHIFT) + { + MESSAGE("The opposing Zigzagoon used Psycho Shift!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCHO_SHIFT, opponent); + } + else if (move == MOVE_JUNGLE_HEALING) + { + MESSAGE("The opposing Zigzagoon used Jungle Healing!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_JUNGLE_HEALING, opponent); + } + else if (move == MOVE_LUNAR_BLESSING) + { + MESSAGE("The opposing Zigzagoon used Lunar Blessing!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_LUNAR_BLESSING, opponent); + } + else if (move == MOVE_TAKE_HEART) + { + MESSAGE("The opposing Zigzagoon used Take Heart!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAKE_HEART, opponent); + } + else if (move == MOVE_AROMATHERAPY) + { + MESSAGE("The opposing Zigzagoon used Aromatherapy!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_AROMATHERAPY, opponent); + } + MESSAGE("Zigzagoon used Spore!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + } +} + +SINGLE_BATTLE_TEST("Sleep Clause: Sleep clause is deactivated when a sleeping mon is woken up by using a held item (Multi)") +{ + u32 heldItem = ITEM_NONE; + PARAMETRIZE { heldItem = ITEM_CHESTO_BERRY; } + PARAMETRIZE { heldItem = ITEM_LUM_BERRY; } + GIVEN { + FLAG_SET(B_FLAG_SLEEP_CLAUSE); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + ASSUME(gItemsInfo[ITEM_CHESTO_BERRY].holdEffect == HOLD_EFFECT_CURE_SLP); + ASSUME(gItemsInfo[ITEM_LUM_BERRY].holdEffect == HOLD_EFFECT_CURE_STATUS); + PLAYER(SPECIES_ZIGZAGOON); + OPPONENT(SPECIES_ZIGZAGOON) { Items(ITEM_ORAN_BERRY, heldItem); } + } WHEN { + TURN { MOVE(player, MOVE_SPORE); } + TURN { MOVE(player, MOVE_SPORE); } + } SCENE { + MESSAGE("Zigzagoon used Spore!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + MESSAGE("The opposing Zigzagoon fell asleep!"); + if (heldItem == ITEM_CHESTO_BERRY) + MESSAGE("The opposing Zigzagoon's Chesto Berry woke it up!"); + else + MESSAGE("The opposing Zigzagoon's Lum Berry cured its sleep problem!"); + MESSAGE("Zigzagoon used Spore!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + MESSAGE("The opposing Zigzagoon fell asleep!"); + } +} + +DOUBLE_BATTLE_TEST("Sleep Clause: Sleep clause is deactivated when a sleeping mon is woken up by Flinging a held item (Multi)") +{ + u32 heldItem = ITEM_NONE; + PARAMETRIZE { heldItem = ITEM_CHESTO_BERRY; } + PARAMETRIZE { heldItem = ITEM_LUM_BERRY; } + GIVEN { + FLAG_SET(B_FLAG_SLEEP_CLAUSE); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + ASSUME(GetMoveEffect(MOVE_FLING) == EFFECT_FLING); + ASSUME(gItemsInfo[ITEM_CHESTO_BERRY].holdEffect == HOLD_EFFECT_CURE_SLP); + ASSUME(gItemsInfo[ITEM_LUM_BERRY].holdEffect == HOLD_EFFECT_CURE_STATUS); + PLAYER(SPECIES_ZIGZAGOON); + PLAYER(SPECIES_ZIGZAGOON) { Items(ITEM_ORAN_BERRY, heldItem); } + OPPONENT(SPECIES_ZIGZAGOON); + OPPONENT(SPECIES_ZIGZAGOON); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SPORE, target: opponentLeft); MOVE(playerRight, MOVE_FLING, target: opponentLeft); } + TURN { MOVE(playerLeft, MOVE_SPORE, target: opponentLeft); } + } SCENE { + MESSAGE("Zigzagoon used Spore!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, playerLeft); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponentLeft); + MESSAGE("The opposing Zigzagoon fell asleep!"); + MESSAGE("Zigzagoon used Fling!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, playerRight); + if (heldItem == ITEM_CHESTO_BERRY) + MESSAGE("The opposing Zigzagoon's Chesto Berry woke it up!"); + else + MESSAGE("The opposing Zigzagoon's Lum Berry cured its sleep problem!"); + MESSAGE("Zigzagoon used Spore!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, playerLeft); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponentLeft); + MESSAGE("The opposing Zigzagoon fell asleep!"); + } +} + +AI_SINGLE_BATTLE_TEST("Sleep Clause: AI will use sleep moves again when sleep clause has been deactivated (Multi)") +{ + GIVEN { + FLAG_SET(B_FLAG_SLEEP_CLAUSE); + ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP); + ASSUME(gItemsInfo[ITEM_CHESTO_BERRY].holdEffect == HOLD_EFFECT_CURE_SLP); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, ITEM_CHESTO_BERRY); } + OPPONENT(SPECIES_BRELOOM) { Moves(MOVE_SPORE, MOVE_MACH_PUNCH); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); EXPECT_MOVE(opponent, MOVE_SPORE); } + TURN { MOVE(player, MOVE_CELEBRATE); EXPECT_MOVE(opponent, MOVE_SPORE); } + } +} +#endif diff --git a/test/battle/spread_moves.c b/test/battle/spread_moves.c index 477af564c84a..48cb914e5ad8 100644 --- a/test/battle/spread_moves.c +++ b/test/battle/spread_moves.c @@ -455,3 +455,337 @@ DOUBLE_BATTLE_TEST("Spread Moves: Focus Sash activates correctly") MESSAGE("The opposing Wynaut fainted!"); } } + +#if MAX_MON_TRAITS > 1 +DOUBLE_BATTLE_TEST("Spread Moves: Ability and Item effects activate correctly after a multi target move (Traits)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LUM_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_COVERT_CLOAK); } + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_EMERGENCY_EXIT); MaxHP(260); HP(131); }; + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_PIKACHU); + } WHEN { + TURN { + MOVE(opponentRight, MOVE_HEAT_WAVE); + MOVE(playerLeft, MOVE_HYPER_VOICE); + SEND_OUT(opponentRight, 3); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + MESSAGE("The opposing Wobbuffet is switched out with the Eject Button!"); + MESSAGE("2 sent out Pikachu!"); + NONE_OF { + ABILITY_POPUP(opponentLeft, ABILITY_EMERGENCY_EXIT); + MESSAGE("2 sent out Wynaut!"); + } + } +} + +DOUBLE_BATTLE_TEST("Spread Moves: A spread move attack will be weakened by strong winds on both targets (Traits)") +{ + s16 opponentLeftDmg[2]; + s16 opponentRightDmg[2]; + + GIVEN { + PLAYER(SPECIES_GARDEVOIR); + PLAYER(SPECIES_RAYQUAZA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_AIR_LOCK); } + PLAYER(SPECIES_RALTS); + OPPONENT(SPECIES_ZAPDOS) + OPPONENT(SPECIES_RAYQUAZA) { Moves(MOVE_DRAGON_ASCENT, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(opponentRight, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); MOVE(playerLeft, MOVE_ROCK_SLIDE); } + TURN { SWITCH(playerRight, 2); MOVE(opponentRight, MOVE_CELEBRATE); MOVE(playerLeft, MOVE_ROCK_SLIDE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROCK_SLIDE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &opponentLeftDmg[0]); + HP_BAR(opponentRight, captureDamage: &opponentRightDmg[0]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROCK_SLIDE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &opponentLeftDmg[1]); + HP_BAR(opponentRight, captureDamage: &opponentRightDmg[1]); + } THEN { + EXPECT_MUL_EQ(opponentLeftDmg[0], Q_4_12(0.5), opponentLeftDmg[1]); + EXPECT_MUL_EQ(opponentRightDmg[0], Q_4_12(0.5), opponentRightDmg[1]); + } +} + +DOUBLE_BATTLE_TEST("Spread Moves: A spread move attack will be weakened by strong winds on one of the targets (Traits)") +{ + s16 opponentLeftDmg[2]; + s16 opponentRightDmg[2]; + + GIVEN { + PLAYER(SPECIES_GARDEVOIR); + PLAYER(SPECIES_RAYQUAZA) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_AIR_LOCK); } + PLAYER(SPECIES_RALTS); + OPPONENT(SPECIES_DONPHAN) + OPPONENT(SPECIES_RAYQUAZA) { Moves(MOVE_DRAGON_ASCENT, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(opponentRight, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); MOVE(playerLeft, MOVE_ROCK_SLIDE); } + TURN { SWITCH(playerRight, 2); MOVE(opponentRight, MOVE_CELEBRATE); MOVE(playerLeft, MOVE_ROCK_SLIDE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROCK_SLIDE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &opponentLeftDmg[0]); + HP_BAR(opponentRight, captureDamage: &opponentRightDmg[0]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROCK_SLIDE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &opponentLeftDmg[1]); + HP_BAR(opponentRight, captureDamage: &opponentRightDmg[1]); + } THEN { + EXPECT_EQ(opponentLeftDmg[1], opponentLeftDmg[0]); + EXPECT_MUL_EQ(opponentRightDmg[0], Q_4_12(0.5), opponentRightDmg[1]); + } +} + +DOUBLE_BATTLE_TEST("Spread Moves: AOE move vs Disguise, Volt Absorb (right) and Lightning Rod (left) (Traits)") +{ + GIVEN { + ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(GetMoveType(MOVE_DISCHARGE) == TYPE_ELECTRIC); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_MIMIKYU); + OPPONENT(SPECIES_RAICHU) { Ability(ABILITY_STATIC); Innates(ABILITY_LIGHTNING_ROD); } + OPPONENT(SPECIES_LANTURN) { Ability(ABILITY_ILLUMINATE); Innates(ABILITY_VOLT_ABSORB); HP(1); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_DISCHARGE); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_LIGHTNING_ROD); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DISCHARGE, playerLeft); + ABILITY_POPUP(playerRight, ABILITY_DISGUISE); + ABILITY_POPUP(opponentRight, ABILITY_VOLT_ABSORB); + } +} + +DOUBLE_BATTLE_TEST("Spread Moves: AOE move vs Disguise, Volt Absorb (left) and Lightning Rod (right) (Traits)") +{ + GIVEN { + ASSUME(GetMoveTarget(MOVE_DISCHARGE) == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(GetMoveType(MOVE_DISCHARGE) == TYPE_ELECTRIC); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_MIMIKYU); + OPPONENT(SPECIES_LANTURN) { Ability(ABILITY_ILLUMINATE); Innates(ABILITY_VOLT_ABSORB); HP(1); } + OPPONENT(SPECIES_RAICHU) { Ability(ABILITY_STATIC); Innates(ABILITY_LIGHTNING_ROD); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_DISCHARGE); } + } SCENE { + ABILITY_POPUP(opponentRight, ABILITY_LIGHTNING_ROD); + ABILITY_POPUP(opponentLeft, ABILITY_VOLT_ABSORB); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DISCHARGE, playerLeft); + ABILITY_POPUP(playerRight, ABILITY_DISGUISE); + } +} + +DOUBLE_BATTLE_TEST("Spread Moves: AOE move vs Eiscue and Mimikyu (Based on vanilla games) (Traits)") +{ + GIVEN { + ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(GetMoveCategory(MOVE_EARTHQUAKE) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_EISCUE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ICE_FACE); } + OPPONENT(SPECIES_MIMIKYU) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DISGUISE); } + OPPONENT(SPECIES_EISCUE) { Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ICE_FACE); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_EARTHQUAKE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_DISGUISE); + ABILITY_POPUP(playerRight, ABILITY_ICE_FACE); + ABILITY_POPUP(opponentRight, ABILITY_ICE_FACE); + } +} + +DOUBLE_BATTLE_TEST("Spread Moves: Spread move vs Eiscue and Mimikyu with 1 Eject Button (Traits)") +{ + GIVEN { + ASSUME(GetMoveTarget(MOVE_RAZOR_LEAF) == MOVE_TARGET_BOTH); + ASSUME(GetMoveCategory(MOVE_RAZOR_LEAF) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_WOBBUFFET) { Speed(40); } + PLAYER(SPECIES_WYNAUT) { Speed(30); } + OPPONENT(SPECIES_MIMIKYU) { Speed(20); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DISGUISE); Item(ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_EISCUE) { Speed(10); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_ICE_FACE); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(100); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_RAZOR_LEAF); SEND_OUT(opponentLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAZOR_LEAF, playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_DISGUISE); + ABILITY_POPUP(opponentRight, ABILITY_ICE_FACE); + MESSAGE("The opposing Mimikyu is switched out with the Eject Button!"); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +DOUBLE_BATTLE_TEST("Spread Moves: Ability and Item effects activate correctly after a multi target move (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, ITEM_LUM_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, ITEM_COVERT_CLOAK); } + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(260); HP(131); }; + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_PIKACHU); + } WHEN { + TURN { + MOVE(opponentRight, MOVE_HEAT_WAVE); + MOVE(playerLeft, MOVE_HYPER_VOICE); + SEND_OUT(opponentRight, 3); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + MESSAGE("The opposing Wobbuffet is switched out with the Eject Button!"); + MESSAGE("2 sent out Pikachu!"); + NONE_OF { + ABILITY_POPUP(opponentLeft, ABILITY_EMERGENCY_EXIT); + MESSAGE("2 sent out Wynaut!"); + } + } +} + +DOUBLE_BATTLE_TEST("Spread Moves: A spread move attack will activate both resist berries (Multi)") +{ + s16 opponentLeftDmg[2]; + s16 opponentRightDmg[2]; + + GIVEN { + PLAYER(SPECIES_GARDEVOIR); + PLAYER(SPECIES_RALTS); + OPPONENT(SPECIES_RAICHU) { Items(ITEM_ORAN_BERRY, ITEM_CHILAN_BERRY); } + OPPONENT(SPECIES_SANDSLASH) { Items(ITEM_ORAN_BERRY, ITEM_CHILAN_BERRY); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_HYPER_VOICE); } + TURN { MOVE(playerLeft, MOVE_HYPER_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + MESSAGE("The Chilan Berry weakened the damage to the opposing Raichu!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + MESSAGE("The Chilan Berry weakened the damage to the opposing Sandslash!"); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &opponentLeftDmg[0]); + HP_BAR(opponentRight, captureDamage: &opponentRightDmg[0]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &opponentLeftDmg[1]); + HP_BAR(opponentRight, captureDamage: &opponentRightDmg[1]); + } THEN { + EXPECT_MUL_EQ(opponentLeftDmg[1], Q_4_12(0.5), opponentLeftDmg[0]); + EXPECT_MUL_EQ(opponentRightDmg[1], Q_4_12(0.5), opponentRightDmg[0]); + } +} + +DOUBLE_BATTLE_TEST("Spread Moves: If a spread move attack will activate a resist berries on one Pokémon, only the damage for that mon will be reduced (Multi)") +{ + s16 opponentLeftDmg[2]; + s16 opponentRightDmg[2]; + + GIVEN { + PLAYER(SPECIES_GARDEVOIR); + PLAYER(SPECIES_RALTS); + OPPONENT(SPECIES_RAICHU) + OPPONENT(SPECIES_SANDSLASH) { Items(ITEM_ORAN_BERRY, ITEM_CHILAN_BERRY); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_HYPER_VOICE); } + TURN { MOVE(playerLeft, MOVE_HYPER_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + MESSAGE("The Chilan Berry weakened the damage to the opposing Sandslash!"); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &opponentLeftDmg[0]); + HP_BAR(opponentRight, captureDamage: &opponentRightDmg[0]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &opponentLeftDmg[1]); + HP_BAR(opponentRight, captureDamage: &opponentRightDmg[1]); + } THEN { + EXPECT_EQ(opponentLeftDmg[1], opponentLeftDmg[0]); + EXPECT_MUL_EQ(opponentRightDmg[1], Q_4_12(0.5), opponentRightDmg[0]); + } +} + +DOUBLE_BATTLE_TEST("Spread Moves: Spread move, Gem Boosted, vs Resist Berries (Multi)") +{ + GIVEN { + ASSUME(GetMoveTarget(MOVE_HYPER_VOICE) == MOVE_TARGET_BOTH); + PLAYER(SPECIES_WOBBUFFET) { Speed(40); Items(ITEM_ORAN_BERRY, ITEM_NORMAL_GEM); } + PLAYER(SPECIES_WYNAUT) { Speed(30); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(20); Items(ITEM_ORAN_BERRY, ITEM_CHILAN_BERRY); } + OPPONENT(SPECIES_WYNAUT) { Speed(10); Items(ITEM_ORAN_BERRY, ITEM_CHILAN_BERRY); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_HYPER_VOICE); } + } SCENE { + MESSAGE("The Normal Gem strengthened Wobbuffet's power!"); + MESSAGE("The Chilan Berry weakened the damage to the opposing Wobbuffet!"); + MESSAGE("The Chilan Berry weakened the damage to the opposing Wynaut!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerLeft); + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Spread Moves: Explosion, Gem Boosted, vs Resist Berries (Multi)") +{ + GIVEN { + ASSUME(GetMoveTarget(MOVE_EXPLOSION) == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(SPECIES_WOBBUFFET) { Speed(40); Items(ITEM_ORAN_BERRY, ITEM_NORMAL_GEM); } + PLAYER(SPECIES_MISDREAVUS) { Speed(30); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(20); Items(ITEM_ORAN_BERRY, ITEM_CHILAN_BERRY); } + OPPONENT(SPECIES_WYNAUT) { Speed(10); Items(ITEM_ORAN_BERRY, ITEM_CHILAN_BERRY); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_EXPLOSION); } + } SCENE { + MESSAGE("The Normal Gem strengthened Wobbuffet's power!"); + MESSAGE("The Chilan Berry weakened the damage to the opposing Wobbuffet!"); + MESSAGE("The Chilan Berry weakened the damage to the opposing Wynaut!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, playerLeft); + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + MESSAGE("It doesn't affect Misdreavus…"); + } +} + +DOUBLE_BATTLE_TEST("Spread Moves: Spread move vs Eiscue and Mimikyu with 1 Eject Button (Multi)") +{ + GIVEN { + ASSUME(GetMoveTarget(MOVE_RAZOR_LEAF) == MOVE_TARGET_BOTH); + ASSUME(GetMoveCategory(MOVE_RAZOR_LEAF) == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_WOBBUFFET) { Speed(40); } + PLAYER(SPECIES_WYNAUT) { Speed(30); } + OPPONENT(SPECIES_MIMIKYU) { Speed(20); Items(ITEM_ORAN_BERRY, ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_EISCUE) { Speed(10); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(100); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_RAZOR_LEAF); SEND_OUT(opponentLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAZOR_LEAF, playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_DISGUISE); + ABILITY_POPUP(opponentRight, ABILITY_ICE_FACE); + MESSAGE("The opposing Mimikyu is switched out with the Eject Button!"); + } +} + +DOUBLE_BATTLE_TEST("Spread Moves: Focus Sash activates correctly (Multi)") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { HP(2); MaxHP(2); Items(ITEM_ORAN_BERRY, ITEM_FOCUS_SASH); } + OPPONENT(SPECIES_WOBBUFFET) { HP(2); MaxHP(2); Items(ITEM_ORAN_BERRY, ITEM_FOCUS_SASH); } + OPPONENT(SPECIES_WYNAUT) { HP(2); MaxHP(2); Items(ITEM_ORAN_BERRY, ITEM_FOCUS_SASH); } + } WHEN { + TURN { MOVE(playerRight, MOVE_HYPER_VOICE); MOVE(playerLeft, MOVE_EXPLOSION); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerRight); + MESSAGE("The opposing Wynaut hung on using its Focus Sash!"); + MESSAGE("The opposing Wobbuffet hung on using its Focus Sash!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, playerLeft); + MESSAGE("The opposing Wobbuffet fainted!"); + MESSAGE("Wynaut hung on using its Focus Sash!"); + MESSAGE("The opposing Wynaut fainted!"); + } +} +#endif diff --git a/test/battle/starting_status/terrain.c b/test/battle/starting_status/terrain.c index 7c4fd6d29e07..30f822bc7cb1 100644 --- a/test/battle/starting_status/terrain.c +++ b/test/battle/starting_status/terrain.c @@ -111,4 +111,60 @@ SINGLE_BATTLE_TEST("Terrain started after the one which started the battle lasts } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Terrain started after the one which started the battle lasts only 5 turns (Traits)") +{ + bool32 viaMove; + + PARAMETRIZE { viaMove = TRUE; } + PARAMETRIZE { viaMove = FALSE; } + + VarSet(B_VAR_STARTING_STATUS, STARTING_STATUS_ELECTRIC_TERRAIN); + VarSet(B_VAR_STARTING_STATUS_TIMER, 0); + + GIVEN { + PLAYER(SPECIES_TAPU_BULU) { Ability(ABILITY_LIGHT_METAL); Innates(viaMove == TRUE ? ABILITY_TELEPATHY : ABILITY_GRASSY_SURGE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + // More than 5 turns + TURN { MOVE(player, viaMove == TRUE ? MOVE_GRASSY_TERRAIN : MOVE_CELEBRATE); } + TURN { ; } + TURN { ; } + TURN { ; } + TURN { ; } + TURN { ; } + TURN { ; } + } SCENE { + // Electric Terrain at battle's start + MESSAGE("An electric current is running across the battlefield!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_RESTORE_BG); + // Player uses Grassy Terrain + if (viaMove) { + MESSAGE("Tapu Bulu used Grassy Terrain!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASSY_TERRAIN, player); + MESSAGE("Grass grew to cover the battlefield!"); + } else { + ABILITY_POPUP(player, ABILITY_GRASSY_SURGE); + MESSAGE("Grass grew to cover the battlefield!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_RESTORE_BG); + } + + // 5 turns + MESSAGE("Tapu Bulu used Celebrate!"); + MESSAGE("The opposing Wobbuffet used Celebrate!"); + + MESSAGE("Tapu Bulu used Celebrate!"); + MESSAGE("The opposing Wobbuffet used Celebrate!"); + + MESSAGE("Tapu Bulu used Celebrate!"); + MESSAGE("The opposing Wobbuffet used Celebrate!"); + + MESSAGE("The grass disappeared from the battlefield."); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_RESTORE_BG); + } THEN { + VarSet(B_VAR_STARTING_STATUS, 0); + } +} #endif // B_VAR_STARTING_STATUS +#endif diff --git a/test/battle/status1/burn.c b/test/battle/status1/burn.c index d28330759000..67294c02efca 100644 --- a/test/battle/status1/burn.c +++ b/test/battle/status1/burn.c @@ -95,3 +95,26 @@ AI_SINGLE_BATTLE_TEST("AI avoids Will-o-Wisp when it can not burn target") TURN { SCORE_EQ(opponent, MOVE_CELEBRATE, MOVE_WILL_O_WISP); } // Both get -10 } } + +#if MAX_MON_TRAITS > 1 +AI_SINGLE_BATTLE_TEST("AI avoids Will-o-Wisp when it can not burn target (Traits)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_BUIZEL; ability = ABILITY_WATER_VEIL; } + PARAMETRIZE { species = SPECIES_DEWPIDER; ability = ABILITY_WATER_BUBBLE; } + PARAMETRIZE { species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } + PARAMETRIZE { species = SPECIES_ARCTIBAX; ability = ABILITY_THERMAL_EXCHANGE; } + PARAMETRIZE { species = SPECIES_NACLI; ability = ABILITY_PURIFYING_SALT; } + PARAMETRIZE { species = SPECIES_CHARMANDER; ability = ABILITY_BLAZE; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_WILL_O_WISP); } + } WHEN { + TURN { SCORE_EQ(opponent, MOVE_CELEBRATE, MOVE_WILL_O_WISP); } // Both get -10 + } +} +#endif diff --git a/test/battle/status1/paralysis.c b/test/battle/status1/paralysis.c index fa78387af779..4193f15651fe 100644 --- a/test/battle/status1/paralysis.c +++ b/test/battle/status1/paralysis.c @@ -105,3 +105,25 @@ SINGLE_BATTLE_TEST("Thunder Wave doesn't print an effectiveness message") NOT MESSAGE("It's super effective!"); } } + +#if MAX_MON_TRAITS > 1 +AI_SINGLE_BATTLE_TEST("AI avoids Thunder Wave when it can not paralyse target (Traits)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_HITMONLEE; ability = ABILITY_LIMBER; } + PARAMETRIZE { species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } + PARAMETRIZE { species = SPECIES_NACLI; ability = ABILITY_PURIFYING_SALT; } + PARAMETRIZE { species = SPECIES_PIKACHU; ability = ABILITY_STATIC; } + + GIVEN { + WITH_CONFIG(CONFIG_PARALYZE_ELECTRIC, GEN_6); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_THUNDER_WAVE); } + } WHEN { + TURN { SCORE_EQ(opponent, MOVE_CELEBRATE, MOVE_THUNDER_WAVE); } // Both get -10 + } +} +#endif diff --git a/test/battle/status1/sleep.c b/test/battle/status1/sleep.c index 5cb60668b9d3..91edf99312c4 100644 --- a/test/battle/status1/sleep.c +++ b/test/battle/status1/sleep.c @@ -68,3 +68,24 @@ AI_SINGLE_BATTLE_TEST("AI avoids hypnosis when it can not put target to sleep") TURN { SCORE_EQ(opponent, MOVE_CELEBRATE, MOVE_HYPNOSIS); } // Both get -10 } } + +#if MAX_MON_TRAITS > 1 +AI_SINGLE_BATTLE_TEST("AI avoids hypnosis when it can not put target to sleep (Traits)") +{ + u32 species; + enum Ability ability; + + PARAMETRIZE { species = SPECIES_HOOTHOOT; ability = ABILITY_INSOMNIA; } + PARAMETRIZE { species = SPECIES_MANKEY; ability = ABILITY_VITAL_SPIRIT; } + PARAMETRIZE { species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } + PARAMETRIZE { species = SPECIES_NACLI; ability = ABILITY_PURIFYING_SALT; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(species) { Ability(ABILITY_LIGHT_METAL); Innates(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_HYPNOSIS); } + } WHEN { + TURN { SCORE_EQ(opponent, MOVE_CELEBRATE, MOVE_HYPNOSIS); } // Both get -10 + } +} +#endif diff --git a/test/battle/switch_in_abilities.c b/test/battle/switch_in_abilities.c index 1fc5e8a9352a..8c76dad6c4aa 100644 --- a/test/battle/switch_in_abilities.c +++ b/test/battle/switch_in_abilities.c @@ -247,3 +247,252 @@ ONE_VS_TWO_BATTLE_TEST("Switch-in abilities trigger in Speed Order after post-KO } } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Switch-in abilities trigger in Speed Order at the battle's start - Single Battle (Traits)") +{ + u32 spdPlayer, spdOpponent; + + PARAMETRIZE { spdPlayer = 5; spdOpponent = 1; } + PARAMETRIZE { spdOpponent = 5; spdPlayer = 1; } + + GIVEN { + PLAYER(SPECIES_EKANS) { Speed(spdPlayer); Ability(ABILITY_SHED_SKIN); Innates(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_NINETALES) { Speed(spdOpponent); Ability(ABILITY_FLASH_FIRE); Innates(ABILITY_DROUGHT); } + } WHEN { + TURN { ; } + } SCENE { + if (spdPlayer > spdOpponent) { + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + } else { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + } + } +} + +DOUBLE_BATTLE_TEST("Switch-in abilities trigger in Speed Order at the battle's start - Double Battle (Traits)") +{ + u32 spdPlayer1, spdPlayer2, spdOpponent1, spdOpponent2; + + PARAMETRIZE { spdPlayer1 = 5; spdPlayer2 = 4; spdOpponent1 = 3; spdOpponent2 = 2; } + PARAMETRIZE { spdPlayer1 = 2; spdPlayer2 = 3; spdOpponent1 = 4; spdOpponent2 = 5; } + PARAMETRIZE { spdPlayer1 = 4; spdPlayer2 = 3; spdOpponent1 = 5; spdOpponent2 = 2; } + + GIVEN { + PLAYER(SPECIES_KYOGRE) { Speed(spdPlayer1); Ability(ABILITY_LIGHT_METAL); Innates(ABILITY_DRIZZLE); } + PLAYER(SPECIES_GYARADOS) { Speed(spdPlayer2); Ability(ABILITY_MOXIE); Innates(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_PORYGON2) { Speed(spdOpponent1); Ability(ABILITY_TRACE); Innates(ABILITY_DOWNLOAD); } + OPPONENT(SPECIES_PINSIR) { Speed(spdOpponent2); Ability(ABILITY_HYPER_CUTTER); Innates(ABILITY_MOLD_BREAKER); } + } WHEN { + TURN { ; } + } SCENE { + if (spdPlayer1 == 5) { + ABILITY_POPUP(playerLeft, ABILITY_DRIZZLE); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponentLeft, ABILITY_DOWNLOAD); + ABILITY_POPUP(opponentRight, ABILITY_MOLD_BREAKER); + } else if (spdOpponent2 == 5) { + ABILITY_POPUP(opponentRight, ABILITY_MOLD_BREAKER); + ABILITY_POPUP(opponentLeft, ABILITY_DOWNLOAD); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(playerLeft, ABILITY_DRIZZLE); + } else { + ABILITY_POPUP(opponentLeft, ABILITY_DOWNLOAD); + ABILITY_POPUP(playerLeft, ABILITY_DRIZZLE); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponentRight, ABILITY_MOLD_BREAKER); + } + } +} + +SINGLE_BATTLE_TEST("Switch-in abilities trigger in Speed Order after post-KO switch - Single Battle (Traits)") +{ + u32 spdPlayer, spdOpponent; + + PARAMETRIZE { spdPlayer = 5; spdOpponent = 1; } + PARAMETRIZE { spdOpponent = 5; spdPlayer = 1; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + PLAYER(SPECIES_EKANS) { Speed(spdPlayer); Ability(ABILITY_SHED_SKIN); Innates(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + OPPONENT(SPECIES_PORYGON2) { Speed(spdOpponent); Ability(ABILITY_TRACE); Innates(ABILITY_DOWNLOAD); } + } WHEN { + TURN { MOVE(player, MOVE_EXPLOSION); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + TURN { ; } + } SCENE { + MESSAGE("Wobbuffet used Explosion!"); + if (spdPlayer > spdOpponent) { + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponent, ABILITY_DOWNLOAD); + } else { + ABILITY_POPUP(opponent, ABILITY_DOWNLOAD); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + } + } +} + +DOUBLE_BATTLE_TEST("Switch-in abilities trigger in Speed Order after post-KO switch - Double Battle (Traits)") +{ + u32 spdPlayer1, spdPlayer2, spdOpponent1, spdOpponent2; + + PARAMETRIZE { spdPlayer1 = 5; spdPlayer2 = 4; spdOpponent1 = 3; spdOpponent2 = 2; } + PARAMETRIZE { spdPlayer1 = 2; spdPlayer2 = 3; spdOpponent1 = 4; spdOpponent2 = 5; } + PARAMETRIZE { spdPlayer1 = 4; spdPlayer2 = 3; spdOpponent1 = 5; spdOpponent2 = 2; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + PLAYER(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + PLAYER(SPECIES_TYRANITAR) { Speed(spdPlayer1); Ability(ABILITY_UNNERVE); Innates(ABILITY_SAND_STREAM); } + PLAYER(SPECIES_GYARADOS) { Speed(spdPlayer2); Ability(ABILITY_MOXIE); Innates(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + OPPONENT(SPECIES_WEEZING_GALAR) { Speed(spdOpponent1); Ability(ABILITY_LEVITATE); Innates(ABILITY_MISTY_SURGE); } + OPPONENT(SPECIES_VULPIX_ALOLA) { Speed(spdOpponent2); Ability(ABILITY_SNOW_CLOAK); Innates(ABILITY_SNOW_WARNING); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_EXPLOSION); SEND_OUT(playerLeft, 2); SEND_OUT(opponentLeft, 2); SEND_OUT(playerRight, 3); SEND_OUT(opponentRight, 3); } + TURN { ; } + } SCENE { + MESSAGE("Wobbuffet used Explosion!"); + if (spdPlayer1 == 5) { + ABILITY_POPUP(playerLeft, ABILITY_SAND_STREAM); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponentLeft, ABILITY_MISTY_SURGE); + ABILITY_POPUP(opponentRight, ABILITY_SNOW_WARNING); + } else if (spdOpponent2 == 5) { + ABILITY_POPUP(opponentRight, ABILITY_SNOW_WARNING); + ABILITY_POPUP(opponentLeft, ABILITY_MISTY_SURGE); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(playerLeft, ABILITY_SAND_STREAM); + } else { + ABILITY_POPUP(opponentLeft, ABILITY_MISTY_SURGE); + ABILITY_POPUP(playerLeft, ABILITY_SAND_STREAM); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponentRight, ABILITY_SNOW_WARNING); + } + } +} + +MULTI_BATTLE_TEST("Switch-in abilities trigger in Speed Order after post-KO switch - multibattle (Traits)") +{ + u32 spdPlayer1, spdPlayer2, spdOpponent1, spdOpponent2; + + PARAMETRIZE { spdPlayer1 = 5; spdPlayer2 = 4; spdOpponent1 = 3; spdOpponent2 = 2; } + PARAMETRIZE { spdPlayer1 = 2; spdPlayer2 = 3; spdOpponent1 = 4; spdOpponent2 = 5; } + PARAMETRIZE { spdPlayer1 = 4; spdPlayer2 = 3; spdOpponent1 = 5; spdOpponent2 = 2; } + + GIVEN { + MULTI_PLAYER(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + MULTI_PLAYER(SPECIES_TYRANITAR) { Speed(spdPlayer1); Ability(ABILITY_UNNERVE); Innates(ABILITY_SAND_STREAM); } + MULTI_PARTNER(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + MULTI_PARTNER(SPECIES_GYARADOS) { Speed(spdPlayer2); Ability(ABILITY_INTIMIDATE); } + MULTI_OPPONENT_A(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + MULTI_OPPONENT_A(SPECIES_WEEZING_GALAR) { Speed(spdOpponent1); Ability(ABILITY_LEVITATE); Innates(ABILITY_MISTY_SURGE); } + MULTI_OPPONENT_B(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + MULTI_OPPONENT_B(SPECIES_VULPIX_ALOLA) { Speed(spdOpponent2); Ability(ABILITY_SNOW_WARNING); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_EXPLOSION); SEND_OUT(playerLeft, 1); SEND_OUT(opponentLeft, 1); SEND_OUT(playerRight, 4); SEND_OUT(opponentRight, 4); } + TURN { ; } + } SCENE { + MESSAGE("Wobbuffet used Explosion!"); + if (spdPlayer1 == 5) { + ABILITY_POPUP(playerLeft, ABILITY_SAND_STREAM); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponentLeft, ABILITY_MISTY_SURGE); + ABILITY_POPUP(opponentRight, ABILITY_SNOW_WARNING); + } else if (spdOpponent2 == 5) { + ABILITY_POPUP(opponentRight, ABILITY_SNOW_WARNING); + ABILITY_POPUP(opponentLeft, ABILITY_MISTY_SURGE); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(playerLeft, ABILITY_SAND_STREAM); + } else { + ABILITY_POPUP(opponentLeft, ABILITY_MISTY_SURGE); + ABILITY_POPUP(playerLeft, ABILITY_SAND_STREAM); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponentRight, ABILITY_SNOW_WARNING); + } + } +} + +TWO_VS_ONE_BATTLE_TEST("Switch-in abilities trigger in Speed Order after post-KO switch - 2v1 (Traits)") +{ + u32 spdPlayer1, spdPlayer2, spdOpponent1, spdOpponent2; + + PARAMETRIZE { spdPlayer1 = 5; spdPlayer2 = 4; spdOpponent1 = 3; spdOpponent2 = 2; } + PARAMETRIZE { spdPlayer1 = 2; spdPlayer2 = 3; spdOpponent1 = 4; spdOpponent2 = 5; } + PARAMETRIZE { spdPlayer1 = 4; spdPlayer2 = 3; spdOpponent1 = 5; spdOpponent2 = 2; } + + GIVEN { + MULTI_PLAYER(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + MULTI_PLAYER(SPECIES_TYRANITAR) { Speed(spdPlayer1); Ability(ABILITY_UNNERVE); Innates(ABILITY_SAND_STREAM); } + MULTI_PARTNER(SPECIES_WYNAUT) { HP(1); Speed(1); } + MULTI_PARTNER(SPECIES_GYARADOS) { Speed(spdPlayer2); Ability(ABILITY_STENCH); Innates(ABILITY_INTIMIDATE); } + MULTI_OPPONENT_A(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + MULTI_OPPONENT_A(SPECIES_WYNAUT) { HP(1); Speed(1); } + MULTI_OPPONENT_A(SPECIES_WEEZING_GALAR) { Speed(spdOpponent1); Ability(ABILITY_LEVITATE); Innates(ABILITY_MISTY_SURGE); } + MULTI_OPPONENT_A(SPECIES_VULPIX_ALOLA) { Speed(spdOpponent2); Ability(ABILITY_SNOW_CLOAK); Innates(ABILITY_SNOW_WARNING); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_EXPLOSION); SEND_OUT(playerLeft, 1); SEND_OUT(opponentLeft, 2); SEND_OUT(playerRight, 4); SEND_OUT(opponentRight, 3); } + } SCENE { + MESSAGE("Wobbuffet used Explosion!"); + if (spdPlayer1 == 5) { + ABILITY_POPUP(playerLeft, ABILITY_SAND_STREAM); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponentLeft, ABILITY_MISTY_SURGE); + ABILITY_POPUP(opponentRight, ABILITY_SNOW_WARNING); + } else if (spdOpponent2 == 5) { + ABILITY_POPUP(opponentRight, ABILITY_SNOW_WARNING); + ABILITY_POPUP(opponentLeft, ABILITY_MISTY_SURGE); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(playerLeft, ABILITY_SAND_STREAM); + } else { + ABILITY_POPUP(opponentLeft, ABILITY_MISTY_SURGE); + ABILITY_POPUP(playerLeft, ABILITY_SAND_STREAM); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponentRight, ABILITY_SNOW_WARNING); + } + } +} + +ONE_VS_TWO_BATTLE_TEST("Switch-in abilities trigger in Speed Order after post-KO switch - 1v2 (Traits)") +{ + u32 spdPlayer1, spdPlayer2, spdOpponent1, spdOpponent2; + + PARAMETRIZE { spdPlayer1 = 5; spdPlayer2 = 4; spdOpponent1 = 3; spdOpponent2 = 2; } + PARAMETRIZE { spdPlayer1 = 2; spdPlayer2 = 3; spdOpponent1 = 4; spdOpponent2 = 5; } + PARAMETRIZE { spdPlayer1 = 4; spdPlayer2 = 3; spdOpponent1 = 5; spdOpponent2 = 2; } + + GIVEN { + MULTI_PLAYER(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + MULTI_PLAYER(SPECIES_WYNAUT) { HP(1); Speed(1); } + MULTI_PLAYER(SPECIES_TYRANITAR) { Speed(spdPlayer1); Ability(ABILITY_UNNERVE); Innates(ABILITY_SAND_STREAM); } + MULTI_PLAYER(SPECIES_GYARADOS) { Speed(spdPlayer2); Ability(ABILITY_MOXIE); Innates(ABILITY_INTIMIDATE); } + MULTI_OPPONENT_A(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + MULTI_OPPONENT_A(SPECIES_WEEZING_GALAR) { Speed(spdOpponent1); Ability(ABILITY_LEVITATE); Innates(ABILITY_MISTY_SURGE); } + MULTI_OPPONENT_B(SPECIES_WYNAUT) { HP(1); Speed(1); } + MULTI_OPPONENT_B(SPECIES_VULPIX_ALOLA) { Speed(spdOpponent2); Ability(ABILITY_SNOW_WARNING); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_EXPLOSION); SEND_OUT(playerLeft, 2); SEND_OUT(opponentLeft, 1); SEND_OUT(playerRight, 3); SEND_OUT(opponentRight, 4); } + } SCENE { + MESSAGE("Wobbuffet used Explosion!"); + if (spdPlayer1 == 5) { + ABILITY_POPUP(playerLeft, ABILITY_SAND_STREAM); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponentLeft, ABILITY_MISTY_SURGE); + ABILITY_POPUP(opponentRight, ABILITY_SNOW_WARNING); + } else if (spdOpponent2 == 5) { + ABILITY_POPUP(opponentRight, ABILITY_SNOW_WARNING); + ABILITY_POPUP(opponentLeft, ABILITY_MISTY_SURGE); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(playerLeft, ABILITY_SAND_STREAM); + } else { + ABILITY_POPUP(opponentLeft, ABILITY_MISTY_SURGE); + ABILITY_POPUP(playerLeft, ABILITY_SAND_STREAM); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponentRight, ABILITY_SNOW_WARNING); + } + } +} +#endif diff --git a/test/battle/test_runner_features.c b/test/battle/test_runner_features.c index d76740a08e23..a30da7f1c0e1 100644 --- a/test/battle/test_runner_features.c +++ b/test/battle/test_runner_features.c @@ -94,3 +94,42 @@ MULTI_BATTLE_TEST("Multi Battle Tests register partner's status1") STATUS_ICON(playerRight, STATUS1_BURN); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Forced abilities activate on switch-in (Traits)") +{ + GIVEN { + PLAYER(SPECIES_ALAKAZAM); + PLAYER(SPECIES_KADABRA) { Ability(ABILITY_INNER_FOCUS); Innates(ABILITY_QUARK_DRIVE); SpAttack(400);} + OPPONENT(SPECIES_ARON); + OPPONENT(SPECIES_ALAKAZAM) { Ability(ABILITY_INNER_FOCUS); Innates(ABILITY_ELECTRIC_SURGE); }; + } WHEN { + TURN { SWITCH(player, 1); SWITCH(opponent, 1);} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_ELECTRIC_SURGE); + ABILITY_POPUP(player, ABILITY_QUARK_DRIVE); + MESSAGE("The Electric Terrain activated Kadabra's Quark Drive!"); + MESSAGE("Kadabra's Sp. Atk was heightened!"); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Changing forms doesn't overwrite set stats (not HP) (Multi)") +{ + GIVEN { + PLAYER(SPECIES_DIANCIE) {Attack(10); Defense(10); Speed(10); SpAttack(10); SpDefense(10); Items(ITEM_ORAN_BERRY, ITEM_DIANCITE);} + OPPONENT(SPECIES_WOBBUFFET) {Speed(1);} + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, player); + } THEN { + EXPECT_EQ(player->attack, 10); + EXPECT_EQ(player->defense, 10); + EXPECT_EQ(player->speed, 10); + EXPECT_EQ(player->spAttack, 10); + EXPECT_EQ(player->spDefense, 10); + } +} +#endif diff --git a/test/battle/trainer_slides.c b/test/battle/trainer_slides.c index 8f7bae9b0955..79b0c33d752c 100644 --- a/test/battle/trainer_slides.c +++ b/test/battle/trainer_slides.c @@ -212,3 +212,37 @@ SINGLE_BATTLE_TEST("Trainer Slide: Dynamax") ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_DYNAMAX_GROWTH, opponent); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Trainer Slide: Mega Evolution (Multi)") +{ + gBattleTestRunnerState->data.recordedBattle.opponentA = TRAINER_SLIDE_MEGA_EVOLUTION; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_LOPUNNY) {Items(ITEM_ORAN_BERRY, ITEM_LOPUNNITE); }; + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + } SCENE { + MESSAGE("This message plays before the enemy activates the Mega Evolution gimmick.{PAUSE_UNTIL_PRESS}"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponent); + MESSAGE("The opposing Lopunny has Mega Evolved into Mega Lopunny!"); + } +} + +SINGLE_BATTLE_TEST("Trainer Slide: Z Move (Multi)") +{ + gBattleTestRunnerState->data.recordedBattle.opponentA = TRAINER_SLIDE_Z_MOVE; + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Items(ITEM_ORAN_BERRY, ITEM_NORMALIUM_Z); } + } WHEN { + TURN { MOVE(opponent, MOVE_QUICK_ATTACK, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + MESSAGE("This message plays before the enemy activates the Z-Move gimmick.{PAUSE_UNTIL_PRESS}"); + MESSAGE("The opposing Wobbuffet surrounded itself with its Z-Power!"); + MESSAGE("The opposing Wobbuffet unleashes its full-force Z-Move!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, opponent); + } +} +#endif diff --git a/test/battle/volatiles/confusion.c b/test/battle/volatiles/confusion.c index b9a12379554c..330eeef7386e 100644 --- a/test/battle/volatiles/confusion.c +++ b/test/battle/volatiles/confusion.c @@ -52,3 +52,28 @@ SINGLE_BATTLE_TEST("Confusion self hit does not consume Gems") MESSAGE("It hurt itself in its confusion!"); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Confusion self hit does not consume Gems (Multi)") +{ + u32 genConfig, pctChance; + + PARAMETRIZE { genConfig = GEN_6; pctChance = 50; } + PARAMETRIZE { genConfig = GEN_7; pctChance = 33; } + PASSES_RANDOMLY(pctChance, 100, RNG_CONFUSION); + GIVEN { + WITH_CONFIG(CONFIG_CONFUSION_SELF_DMG_CHANCE, genConfig); + PLAYER(SPECIES_WOBBUFFET) { Items(ITEM_PECHA_BERRY, ITEM_NORMAL_GEM); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_CONFUSE_RAY); MOVE(player, MOVE_SCRATCH); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Normal Gem strengthened Wobbuffet's power!"); + } + MESSAGE("It hurt itself in its confusion!"); + } +} +#endif diff --git a/test/battle/weather/hail.c b/test/battle/weather/hail.c index c38370a8914f..f2e28f7e966e 100644 --- a/test/battle/weather/hail.c +++ b/test/battle/weather/hail.c @@ -104,3 +104,45 @@ SINGLE_BATTLE_TEST("Hail doesn't do damage when weather is negated") NOT HP_BAR(player); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Hail doesn't do damage when weather is negated (Traits)") +{ + GIVEN { + ASSUME(GetSpeciesType(SPECIES_WOBBUFFET, 0) != TYPE_ICE); + ASSUME(GetSpeciesType(SPECIES_WOBBUFFET, 1) != TYPE_ICE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_SWIFT_SWIM); Innates(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(player, MOVE_HAIL); } + } SCENE { + NOT HP_BAR(player); + } +} +#endif + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Hail fails if Desolate Land or Primordial Sea are active (Multi)") +{ + u32 species; + u32 item; + + PARAMETRIZE { species = SPECIES_WOBBUFFET; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_GROUDON; item = ITEM_RED_ORB; } + PARAMETRIZE { species = SPECIES_KYOGRE; item = ITEM_BLUE_ORB; } + + GIVEN { + PLAYER(species) { Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_HAIL); } + } SCENE { + if (item == ITEM_RED_ORB || item == ITEM_BLUE_ORB) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_HAIL, opponent); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HAIL, opponent); + } + } +} +#endif diff --git a/test/battle/weather/sandstorm.c b/test/battle/weather/sandstorm.c index 55b5907ffb55..c43cb98513b1 100644 --- a/test/battle/weather/sandstorm.c +++ b/test/battle/weather/sandstorm.c @@ -114,3 +114,22 @@ SINGLE_BATTLE_TEST("Sandstorm doesn't do damage when weather is negated") NOT HP_BAR(player); } } + +#if MAX_MON_TRAITS > 1 +SINGLE_BATTLE_TEST("Sandstorm doesn't do damage when weather is negated (Traits)") +{ + enum Type type1 = GetSpeciesType(SPECIES_STOUTLAND, 0); + enum Type type2 = GetSpeciesType(SPECIES_STOUTLAND, 1); + GIVEN { + ASSUME(type1 != TYPE_ROCK && type2 != TYPE_ROCK); + ASSUME(type1 != TYPE_GROUND && type2 != TYPE_GROUND); + ASSUME(type1 != TYPE_STEEL && type2 != TYPE_STEEL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_SWIFT_SWIM); Innates(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(player, MOVE_SANDSTORM); } + } SCENE { + NOT HP_BAR(player); + } +} +#endif diff --git a/test/battle/weather/strong_winds.c b/test/battle/weather/strong_winds.c index f319a7a229ef..e54c40a2426b 100644 --- a/test/battle/weather/strong_winds.c +++ b/test/battle/weather/strong_winds.c @@ -247,3 +247,25 @@ SINGLE_BATTLE_TEST("Strong winds can be replaced by Primordial Sea") EXPECT(gBattleWeather & B_WEATHER_RAIN_PRIMAL); } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Strong winds prevent Weakness Policy from activating on Flying-type weaknesses (Multi)") +{ + GIVEN { + ASSUME(GetItemHoldEffect(ITEM_WEAKNESS_POLICY) == HOLD_EFFECT_WEAKNESS_POLICY); + ASSUME(GetMoveType(MOVE_THUNDER_SHOCK) == TYPE_ELECTRIC); + ASSUME(GetSpeciesType(SPECIES_PIDGEY, 0) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_PIDGEY, 1) == TYPE_FLYING); + PLAYER(SPECIES_RAYQUAZA) { Ability(ABILITY_DELTA_STREAM); Moves(MOVE_THUNDER_SHOCK); } + OPPONENT(SPECIES_PIDGEY) { Items(ITEM_PECHA_BERRY, ITEM_WEAKNESS_POLICY); } + } WHEN { + TURN { MOVE(player, MOVE_THUNDER_SHOCK); } + } SCENE { + MESSAGE("Rayquaza used Thunder Shock!"); + MESSAGE("The mysterious strong winds weakened the attack!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_SHOCK, player); + HP_BAR(opponent); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} +#endif diff --git a/test/battle/weather/sunlight.c b/test/battle/weather/sunlight.c index 796ad3c3af24..36793c76fa78 100644 --- a/test/battle/weather/sunlight.c +++ b/test/battle/weather/sunlight.c @@ -67,3 +67,27 @@ SINGLE_BATTLE_TEST("Sunny Day fails if Primordial Sea is active") } } } + +#if MAX_MON_ITEMS > 1 +SINGLE_BATTLE_TEST("Sunny Day fails if Primordial Sea is active (Multi)") +{ + u32 item; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_BLUE_ORB; } + + GIVEN { + PLAYER(SPECIES_KYOGRE) { Items(ITEM_PECHA_BERRY, item); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + if (item == ITEM_BLUE_ORB) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_PRIMAL_REVERSION, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + } + } +} +#endif diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index 72b28d4ee2bc..3a9fd9f0b982 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -29,6 +29,7 @@ #undef TestRunner_Battle_AfterLastTurn #undef TestRunner_Battle_CheckBattleRecordActionType #undef TestRunner_Battle_GetForcedAbility +#undef TestRunner_Battle_GetForcedInnates #endif #define INVALID(fmt, ...) Test_ExitWithResult(TEST_RESULT_INVALID, sourceLine, ":L%s:%d: " fmt, gTestRunnerState.test->filename, sourceLine, ##__VA_ARGS__) @@ -2034,6 +2035,20 @@ void Ability_(u32 sourceLine, enum Ability ability) } } +void Innates_(u32 sourceLine, enum Ability innates[MAX_MON_INNATES]) +{ + s32 i; + INVALID_IF(!DATA.currentMon, "Innates outside of PLAYER/OPPONENT"); + + // Overwrites the target pokemon with the given Innate list. + // If the list is empty, the pokemon will have no Innates to remain compatible with vanilla tests. + for (i = 0; i < MAX_MON_INNATES; i++) + { + INVALID_IF(innates[i] >= ABILITIES_COUNT, "Illegal ability id: %d", innates[i]); + DATA.forcedInnates[DATA.currentPosition][DATA.currentPartyIndex][i] = innates[i]; + } +} + void Level_(u32 sourceLine, u32 level) { // TODO: Preserve any explicitly-set stats. @@ -3224,6 +3239,11 @@ u32 TestRunner_Battle_GetForcedAbility(u32 array, u32 partyIndex) return DATA.forcedAbilities[array][partyIndex]; } +u32 TestRunner_Battle_GetForcedInnates(u32 array, u32 partyIndex, s32 i) +{ + return DATA.forcedInnates[array][partyIndex][i]; +} + u32 TestRunner_Battle_GetForcedEnvironment(void) { return DATA.forcedEnvironment; diff --git a/test/text.c b/test/text.c index ed458954e6f8..f38fe62bb89f 100644 --- a/test/text.c +++ b/test/text.c @@ -752,7 +752,7 @@ TEST("Battle strings fit on the battle message window") PREPARE_MON_NICK_BUFFER(gBattleTextBuff2, 1, 0); break; // Buffer Item name to B_BUFF1 - case STRINGID_PKMNHURTSWITH: + case STRINGID_PKMNHURTSWITHITEM: case STRINGID_PKMNCURIOUSABOUTX: case STRINGID_PKMNENTHRALLEDBYX: case STRINGID_PKMNIGNOREDX: