diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index de99ba749..b4ca471ce 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1670,6 +1670,10 @@ callnative BS_DamageToQuarterTargetHP .endm + .macro ficklebeamdamagecalculation + callnative BS_FickleBeamDamageCalculation + .endm + @ various command changed to more readable macros .macro cancelmultiturnmoves battler:req various \battler, VARIOUS_CANCEL_MULTI_TURN_MOVES @@ -2519,5 +2523,3 @@ printstring STRINGID_EMPTYSTRING3 waitmessage 1 .endm - - diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index ee02db7b6..2bce79732 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -22,62 +22,6 @@ .section script_data, "aw", %progbits .align 2 -BattleScript_LocalTrainerBattleWon:: - jumpifbattletype BATTLE_TYPE_TWO_OPPONENTS, BattleScript_LocalTwoTrainersDefeated - printstring STRINGID_PLAYERDEFEATEDTRAINER1 - goto BattleScript_LocalBattleWonLoseTexts -BattleScript_LocalTwoTrainersDefeated:: - printstring STRINGID_TWOENEMIESDEFEATED -BattleScript_LocalBattleWonLoseTexts:: - trainerslidein BS_ATTACKER - waitstate - printstring STRINGID_TRAINER1LOSETEXT - jumpifnotbattletype BATTLE_TYPE_TWO_OPPONENTS, BattleScript_LocalTrainerBattleWonGotMoney - trainerslideout B_POSITION_OPPONENT_LEFT - waitstate - trainerslidein BS_FAINTED - waitstate - printstring STRINGID_TRAINER2LOSETEXT -BattleScript_LocalTrainerBattleWonGotMoney:: - getmoneyreward - printstring STRINGID_PLAYERGOTMONEY - waitmessage B_WAIT_TIME_LONG -BattleScript_PayDayMoneyAndPickUpItems:: - givepaydaymoney - pickup - end2 - -BattleScript_LocalBattleLost:: - jumpifbattletype BATTLE_TYPE_INGAME_PARTNER, BattleScript_LocalBattleLostPrintWhiteOut - jumpifbattletype BATTLE_TYPE_TRAINER_TOWER, BattleScript_BattleTowerLost - jumpifbattletype BATTLE_TYPE_EREADER_TRAINER, BattleScript_LocalBattleLostEnd - jumpifhalfword CMP_EQUAL, gTrainerBattleOpponent_A, TRAINER_SECRET_BASE, BattleScript_LocalBattleLostEnd - jumpifbyte CMP_NOT_EQUAL, cMULTISTRING_CHOOSER, 0, BattleScript_RivalBattleLost -BattleScript_LocalBattleLostPrintWhiteOut:: -.if B_WHITEOUT_MONEY >= GEN_4 - jumpifbattletype BATTLE_TYPE_TRAINER, BattleScript_LocalBattleLostEnd - printstring STRINGID_PLAYERWHITEOUT - waitmessage B_WAIT_TIME_LONG - getmoneyreward - printstring STRINGID_PLAYERWHITEOUT2 - waitmessage B_WAIT_TIME_LONG - end2 -BattleScript_LocalBattleLostEnd:: - printstring STRINGID_PLAYERLOSTAGAINSTENEMYTRAINER - waitmessage B_WAIT_TIME_LONG - getmoneyreward - printstring STRINGID_PLAYERPAIDPRIZEMONEY - waitmessage B_WAIT_TIME_LONG - end2 -.else - printstring STRINGID_PLAYERWHITEOUT - waitmessage B_WAIT_TIME_LONG - printstring STRINGID_PLAYERWHITEOUT2 - waitmessage B_WAIT_TIME_LONG -BattleScript_LocalBattleLostEnd:: - end2 -.endif - BattleScript_RivalBattleLost:: jumpifhasnohp BS_ATTACKER, BattleScript_RivalBattleLostSkipMonRecall printstring STRINGID_TRAINER1MON1COMEBACK @@ -128,11 +72,6 @@ BattleScript_BattleTowerEtcTrainerBattleWonSkipText:: pickup end2 -BattleScript_PrintCantRunFromTrainer:: - jumpifbattletype BATTLE_TYPE_FIRST_BATTLE, BattleScript_LeftoverBirchString - printstring STRINGID_NORUNNINGFROMTRAINERS - end2 - BattleScript_LeftoverBirchString:: printstring STRINGID_DONTLEAVEBIRCH end2 @@ -170,6 +109,19 @@ BattleScript_DamageToQuarterTargetHP:: damagetoquartertargethp goto BattleScript_HitFromAtkAnimation +BattleScript_EffectFickleBeam:: + attackcanceler + attackstring + ppreduce + accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE + ficklebeamdamagecalculation + goto BattleScript_HitFromCritCalc +BattleScript_FickleBeamDoubled:: + pause B_WAIT_TIME_SHORTEST + printstring STRINGID_FICKLEBEAMDOUBLED + waitmessage B_WAIT_TIME_LONG + goto BattleScript_HitFromCritCalc + BattleScript_Terastallization:: @ TODO: no string prints in S/V, but right now this helps with clarity printstring STRINGID_PKMNSTORINGENERGY @@ -283,6 +235,7 @@ BattleScript_EffectShedTail:: ppreduce waitstate jumpifstatus2 BS_ATTACKER, STATUS2_SUBSTITUTE, BattleScript_AlreadyHasSubstitute + jumpifbattletype BATTLE_TYPE_ARENA, BattleScript_ButItFailed jumpifcantswitch SWITCH_IGNORE_ESCAPE_PREVENTION | BS_ATTACKER, BattleScript_ButItFailed setsubstitute jumpifbyte CMP_EQUAL, cMULTISTRING_CHOOSER, B_MSG_SUBSTITUTE_FAILED, BattleScript_SubstituteString @@ -415,6 +368,7 @@ BattleScript_EffectChillyReceptionBlockedByStrongWinds: call BattleScript_MysteriousAirCurrentBlowsOnRet goto BattleScript_MoveSwitch BattleScript_EffectChillyReceptionTrySwitchWeatherFailed: + jumpifbattletype BATTLE_TYPE_ARENA, BattleScript_FailedFromAtkString jumpifcantswitch SWITCH_IGNORE_ESCAPE_PREVENTION | BS_ATTACKER, BattleScript_FailedFromAtkString call BattleScript_EffectChillyReceptionPlayAnimation return @@ -426,6 +380,7 @@ BattleScript_CheckPrimalWeather: return BattleScript_MoveSwitch: + jumpifbattletype BATTLE_TYPE_ARENA, BattleScript_MoveSwitchEnd jumpifcantswitch SWITCH_IGNORE_ESCAPE_PREVENTION | BS_ATTACKER, BattleScript_MoveSwitchEnd printstring STRINGID_PKMNWENTBACK waitmessage B_WAIT_TIME_SHORT @@ -702,7 +657,7 @@ BattleScript_Teatimerod: statbuffchange STAT_CHANGE_ALLOW_PTR, BattleScript_TeatimeBuffer jumpifbyte CMP_EQUAL, cMULTISTRING_CHOOSER, 0x2, BattleScript_TeatimeBuffer printfromtable gStatUpStringIds - waitmessage 0x40 + waitmessage B_WAIT_TIME_LONG moveendto MOVEEND_NEXT_TARGET jumpifnexttargetvalid BattleScript_TeatimeLoop moveendcase MOVEEND_CLEAR_BITS @@ -714,7 +669,7 @@ BattleScript_Teatimemotor: statbuffchange STAT_CHANGE_ALLOW_PTR, BattleScript_TeatimeBuffer jumpifbyte CMP_EQUAL, cMULTISTRING_CHOOSER, 0x2, BattleScript_TeatimeBuffer printfromtable gStatUpStringIds - waitmessage 0x40 + waitmessage B_WAIT_TIME_LONG moveendto MOVEEND_NEXT_TARGET jumpifnexttargetvalid BattleScript_TeatimeLoop moveendcase MOVEEND_CLEAR_BITS @@ -1002,6 +957,7 @@ BattleScript_OctlockTurnDmgEnd: BattleScript_EffectPoltergeist:: attackcanceler + accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE attackstring ppreduce checkpoltergeist BS_TARGET, BattleScript_ButItFailed @@ -1051,19 +1007,10 @@ BattleScript_BothCanNoLongerEscape:: return BattleScript_EffectHyperspaceFury:: - jumpifspecies BS_ATTACKER, SPECIES_HOOPA_UNBOUND, BattleScript_EffectHyperspaceFuryUnbound + jumpifspecies BS_ATTACKER, SPECIES_HOOPA_UNBOUND, BattleScript_EffectHit jumpifspecies BS_ATTACKER, SPECIES_HOOPA_CONFINED, BattleScript_ButHoopaCantUseIt goto BattleScript_PokemonCantUseTheMove -BattleScript_EffectHyperspaceFuryUnbound:: - attackcanceler - accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE - attackstring - pause B_WAIT_TIME_LONG - ppreduce - seteffectprimary MOVE_EFFECT_FEINT - goto BattleScript_HitFromCritCalc - BattleScript_ButHoopaCantUseIt: printstring STRINGID_BUTHOOPACANTUSEIT waitmessage B_WAIT_TIME_LONG @@ -3401,6 +3348,7 @@ BattleScript_EffectRoar:: jumpiftargetdynamaxed BattleScript_RoarBlockedByDynamax accuracycheck BattleScript_ButItFailed, NO_ACC_CALC_CHECK_LOCK_ON accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE + jumpifbattletype BATTLE_TYPE_ARENA, BattleScript_ButItFailed forcerandomswitch BattleScript_ButItFailed BattleScript_RoarBlockedByDynamax: @@ -4553,6 +4501,7 @@ BattleScript_EffectBatonPass:: attackcanceler attackstring ppreduce + jumpifbattletype BATTLE_TYPE_ARENA, BattleScript_ButItFailed jumpifcantswitch SWITCH_IGNORE_ESCAPE_PREVENTION | BS_ATTACKER, BattleScript_ButItFailed attackanimation waitanimation @@ -5724,6 +5673,62 @@ BattleScript_HandleFaintedMonMultipleEnd:: switchineffects BS_FAINTED_MULTIPLE_2 end2 +BattleScript_LocalTrainerBattleWon:: + jumpifbattletype BATTLE_TYPE_TWO_OPPONENTS, BattleScript_LocalTwoTrainersDefeated + printstring STRINGID_PLAYERDEFEATEDTRAINER1 + goto BattleScript_LocalBattleWonLoseTexts +BattleScript_LocalTwoTrainersDefeated:: + printstring STRINGID_TWOENEMIESDEFEATED +BattleScript_LocalBattleWonLoseTexts:: + trainerslidein BS_ATTACKER + waitstate + printstring STRINGID_TRAINER1LOSETEXT + jumpifnotbattletype BATTLE_TYPE_TWO_OPPONENTS, BattleScript_LocalBattleWonReward + trainerslideout B_POSITION_OPPONENT_LEFT + waitstate + trainerslidein BS_FAINTED + waitstate + printstring STRINGID_TRAINER2LOSETEXT +BattleScript_LocalBattleWonReward:: + getmoneyreward + printstring STRINGID_PLAYERGOTMONEY + waitmessage B_WAIT_TIME_LONG +BattleScript_PayDayMoneyAndPickUpItems:: + givepaydaymoney + pickup + end2 + +BattleScript_LocalBattleLost:: + jumpifbattletype BATTLE_TYPE_INGAME_PARTNER, BattleScript_LocalBattleLostPrintWhiteOut + jumpifbattletype BATTLE_TYPE_TRAINER_TOWER, BattleScript_BattleTowerLost + jumpifbattletype BATTLE_TYPE_EREADER_TRAINER, BattleScript_LocalBattleLostEnd + jumpifhalfword CMP_EQUAL, gTrainerBattleOpponent_A, TRAINER_SECRET_BASE, BattleScript_LocalBattleLostEnd + jumpifbyte CMP_NOT_EQUAL, cMULTISTRING_CHOOSER, 0, BattleScript_RivalBattleLost +BattleScript_LocalBattleLostPrintWhiteOut:: +.if B_WHITEOUT_MONEY >= GEN_4 + jumpifbattletype BATTLE_TYPE_TRAINER, BattleScript_LocalBattleLostEnd + printstring STRINGID_PLAYERWHITEOUT + waitmessage B_WAIT_TIME_LONG + getmoneyreward + printstring STRINGID_PLAYERWHITEOUT2 + waitmessage B_WAIT_TIME_LONG + end2 +BattleScript_LocalBattleLostEnd:: + printstring STRINGID_PLAYERLOSTAGAINSTENEMYTRAINER + waitmessage B_WAIT_TIME_LONG + getmoneyreward + printstring STRINGID_PLAYERPAIDPRIZEMONEY + waitmessage B_WAIT_TIME_LONG + end2 +.else + printstring STRINGID_PLAYERWHITEOUT + waitmessage B_WAIT_TIME_LONG + printstring STRINGID_PLAYERWHITEOUT2 + waitmessage B_WAIT_TIME_LONG +BattleScript_LocalBattleLostEnd:: + end2 +.endif + BattleScript_SmokeBallEscape:: playanimation BS_ATTACKER, B_ANIM_SMOKEBALL_ESCAPE printstring STRINGID_PKMNFLEDUSINGITS @@ -5745,6 +5750,10 @@ BattleScript_WildMonFled:: waitmessage B_WAIT_TIME_LONG end2 +BattleScript_PrintCantRunFromTrainer:: + printstring STRINGID_NORUNNINGFROMTRAINERS + end2 + BattleScript_PrintFailedToRunString:: printfromtable gNoEscapeStringIds waitmessage B_WAIT_TIME_LONG @@ -7118,7 +7127,7 @@ BattleScript_TargetFormChangeWithStringNoPopup:: BattleScript_BattlerFormChangeWithStringEnd3:: pause 5 - call BattleScript_AbilityPopUp + call BattleScript_AbilityPopUpScripting flushtextbox handleformchange BS_SCRIPTING, 0 handleformchange BS_SCRIPTING, 1 @@ -7722,6 +7731,15 @@ BattleScript_CheekPouchActivates:: copybyte gBattlerAttacker, sSAVED_BATTLER return +BattleScript_PickupActivates:: + pause 5 + tryrecycleitem BattleScript_PickupActivatesEnd + call BattleScript_AbilityPopUp + printstring STRINGID_XFOUNDONEY + waitmessage B_WAIT_TIME_LONG +BattleScript_PickupActivatesEnd: + end3 + BattleScript_HarvestActivates:: pause 5 tryrecycleitem BattleScript_HarvestActivatesEnd @@ -7969,7 +7987,7 @@ BattleScript_DeltaStreamActivates:: end3 BattleScript_ProtosynthesisActivates:: - call BattleScript_AbilityPopUp + call BattleScript_AbilityPopUpScripting printstring STRINGID_SUNLIGHTACTIVATEDABILITY waitmessage B_WAIT_TIME_MED printstring STRINGID_STATWASHEIGHTENED diff --git a/include/battle.h b/include/battle.h index 89023cd05..26272cea8 100644 --- a/include/battle.h +++ b/include/battle.h @@ -574,12 +574,10 @@ struct LostItem u16 stolen:1; }; -#if HQ_RANDOM == TRUE struct BattleVideo { u32 battleTypeFlags; rng_value_t rngSeed; }; -#endif struct BattleStruct { @@ -645,17 +643,13 @@ struct BattleStruct u16 chosenItem[MAX_BATTLERS_COUNT]; u16 choicedMove[MAX_BATTLERS_COUNT]; u16 changedItems[MAX_BATTLERS_COUNT]; + u8 canPickupItem; u8 switchInBattlerCounter; u8 turnSideTracker; u16 lastTakenMoveFrom[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT]; // a 2-D array [target][attacker] union { struct LinkBattlerHeader linkBattlerHeader; - - #if HQ_RANDOM == FALSE - u32 battleVideo[2]; - #else struct BattleVideo battleVideo; - #endif } multiBuffer; u8 wishPerishSongState; u8 wishPerishSongBattlerId; @@ -724,7 +718,6 @@ struct BattleStruct bool8 effectsBeforeUsingMoveDone:1; // Mega Evo and Focus Punch/Shell Trap effects. u8 targetsDone[MAX_BATTLERS_COUNT]; // Each battler as a bit. u16 overwrittenAbilities[MAX_BATTLERS_COUNT]; // abilities overwritten during battle (keep separate from battle history in case of switching) - bool8 allowedToChangeFormInWeather[PARTY_SIZE][NUM_BATTLE_SIDES]; // For each party member and side, used by Ice Face. u8 battleBondTransformed[NUM_BATTLE_SIDES]; // Bitfield for each party. u8 storedHealingWish:4; // Each battler as a bit. u8 storedLunarDance:4; // Each battler as a bit. @@ -761,6 +754,7 @@ struct BattleStruct u8 distortedTypeMatchups; u8 categoryOverride; // for Z-Moves and Max Moves u32 stellarBoostFlags[NUM_BATTLE_SIDES]; // stored as a bitfield of flags for all types for each side + u8 fickleBeamBoosted:1; // pokefirered u8 field_DA; // battle tower related diff --git a/include/battle_script_commands.h b/include/battle_script_commands.h index 9ee073660..b3f5ae1ed 100644 --- a/include/battle_script_commands.h +++ b/include/battle_script_commands.h @@ -31,8 +31,8 @@ bool32 NoAliveMonsForPlayer(void); bool32 NoAliveMonsForEitherParty(void); void SetMoveEffect(bool32 primary, bool32 certain); bool32 CanBattlerSwitch(u32 battlerId); -void BattleDestroyYesNoCursorAt(void); -void BattleCreateYesNoCursorAt(void); +void BattleDestroyYesNoCursorAt(u8 cursorPosition); +void BattleCreateYesNoCursorAt(u8 cursorPosition); void BufferMoveToLearnIntoBattleTextBuff2(void); void HandleBattleWindow(u8 xStart, u8 yStart, u8 xEnd, u8 yEnd, u8 flags); bool8 UproarWakeUpCheck(u8 battlerId); diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 46b06f0f1..588efa0cf 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -737,6 +737,7 @@ extern const u8 BattleScript_WeakArmorActivates[]; extern const u8 BattleScript_FellStingerRaisesStat[]; extern const u8 BattleScript_SnowWarningActivatesHail[]; extern const u8 BattleScript_SnowWarningActivatesSnow[]; +extern const u8 BattleScript_PickupActivates[]; extern const u8 BattleScript_HarvestActivates[]; extern const u8 BattleScript_ImposterActivates[]; extern const u8 BattleScript_SelectingNotAllowedMoveAssaultVest[]; @@ -1274,6 +1275,8 @@ extern const u8 BattleScript_EffectUpperHand[]; extern const u8 BattleScript_EffectTidyUp[]; extern const u8 BattleScript_EffectSpicyExtract[]; extern const u8 BattleScript_DamageToQuarterTargetHP[]; +extern const u8 BattleScript_EffectFickleBeam[]; +extern const u8 BattleScript_FickleBeamDoubled[]; #endif // GUARD_BATTLE_SCRIPTS_H diff --git a/include/battle_util.h b/include/battle_util.h index afd5082da..b5413645d 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -232,6 +232,8 @@ bool32 TryRoomService(u32 battler); void BufferStatChange(u32 battler, u8 statId, u8 stringId); bool32 BlocksPrankster(u16 move, u32 battlerPrankster, u32 battlerDef, bool32 checkTarget); u16 GetUsedHeldItem(u32 battler); +bool32 PickupHasValidTarget(u32 battler); +bool32 CantPickupItem(u32 battler); bool32 IsBattlerWeatherAffected(u32 battler, u32 weatherFlags); u32 GetBattlerMoveTargetType(u32 battler, u32 move); bool32 CanTargetBattler(u32 battlerAtk, u32 battlerDef, u16 move); diff --git a/include/config/battle.h b/include/config/battle.h index 27792d529..b616ed3c8 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -110,7 +110,7 @@ #define B_BURN_HIT_THAW GEN_LATEST // In Gen6+, damaging moves with a chance of burn will thaw the target, regardless if they're fire-type moves or not. #define B_HEALING_WISH_SWITCH GEN_LATEST // In Gen5+, the mon receiving Healing Wish is sent out at the end of the turn. // Additionally, in gen8+ the Healing Wish's effect will be stored until the user switches into a statused or hurt mon. -#define B_DEFOG_CLEARS_TERRAIN GEN_LATEST // In Gen8+, Defog also clears active Terrain. +#define B_DEFOG_EFFECT_CLEARING GEN_LATEST // In Gen6+, Defog clears Spikes, Toxic Spikes, Stealth Rock and Sticky Web from both sides. In Gen8+, Defog also clears active Terrain. #define B_STOCKPILE_RAISES_DEFS GEN_LATEST // In Gen4+, Stockpile also raises Defense and Sp. Defense stats. Once Spit Up / Swallow is used, these stat changes are lost. #define B_TRANSFORM_SHINY GEN_LATEST // In Gen4+, Transform will copy the shiny state of the opponent instead of maintaining its own shiny state. #define B_TRANSFORM_FORM_CHANGES GEN_LATEST // In Gen5+, Transformed Pokemon cannot change forms. @@ -219,7 +219,7 @@ #define B_FAST_HP_DRAIN TRUE // If set to TRUE, HP bars will move faster. #define B_FAST_EXP_GROW TRUE // If set to TRUE, EXP bars will move faster. #define B_SHOW_TARGETS TRUE // If set to TRUE, all available targets, for moves hitting 2 or 3 Pokémon, will be shown before selecting a move. -#define B_SHOW_CATEGORY_ICON TRUE // If set to TRUE, it will show an icon in the summary showing the move's category. +#define B_SHOW_CATEGORY_ICON TRUE // If set to TRUE, it will show an icon in the summary and move relearner showing the move's category. #define B_HIDE_HEALTHBOX_IN_ANIMS TRUE // If set to TRUE, hides healthboxes during move animations. #define B_EXPANDED_MOVE_NAMES TRUE // If set to FALSE, move names are decreased from 16 characters to 12 characters. #define B_WAIT_TIME_MULTIPLIER 16 // This determines how long text pauses in battle last. Vanilla is 16. Lower values result in faster battles. @@ -251,26 +251,31 @@ #define B_TRY_CATCH_TRAINER_BALL GEN_LATEST // In Gen4+, trying to catch a Trainer's Pokémon does not consume the Poké Ball. // Animation Settings -#define B_NEW_SWORD_PARTICLE TRUE // If set to TRUE, it updates Swords Dance's particle. -#define B_NEW_LEECH_SEED_PARTICLE TRUE // If set to TRUE, it updates Leech Seed's animation particle. -#define B_NEW_HORN_ATTACK_PARTICLE TRUE // If set to TRUE, it updates Horn Attack's horn particle. -#define B_NEW_ROCKS_PARTICLE TRUE // If set to TRUE, it updates rock particles. -#define B_NEW_LEAF_PARTICLE TRUE // If set to TRUE, it updates leaf particle. -#define B_NEW_EMBER_PARTICLES TRUE // If set to TRUE, it updates Ember's fire particle. -#define B_NEW_MEAN_LOOK_PARTICLE TRUE // If set to TRUE, it updates Mean Look's eye particle. -#define B_NEW_TEETH_PARTICLE TRUE // If set to TRUE, it updates Bite/Crunch teeth particle. -#define B_NEW_HANDS_FEET_PARTICLE TRUE // If set to TRUE, it updates chop/kick/punch particles. -#define B_NEW_SPIKES_PARTICLE TRUE // If set to TRUE, it updates Spikes particle. -#define B_NEW_FLY_BUBBLE_PARTICLE TRUE // If set to TRUE, it updates Fly's 'bubble' particle. -#define B_NEW_CURSE_NAIL_PARTICLE TRUE // If set to TRUE, it updates Curse's nail. -#define B_NEW_BATON_PASS_BALL_PARTICLE TRUE // If set to TRUE, it updates Baton Pass' Poké Ball sprite. -#define B_NEW_MORNING_SUN_STAR_PARTICLE TRUE // If set to TRUE, it updates Morning Sun's star particles. -#define B_NEW_IMPACT_PALETTE TRUE // If set to TRUE, it updates the basic 'hit' palette. -#define B_NEW_SURF_PARTICLE_PALETTE TRUE // If set to TRUE, it updates Surf's wave palette. +#define B_NEW_SWORD_PARTICLE FALSE // If set to TRUE, it updates Swords Dance's particle. +#define B_NEW_LEECH_SEED_PARTICLE FALSE // If set to TRUE, it updates Leech Seed's animation particle. +#define B_NEW_HORN_ATTACK_PARTICLE FALSE // If set to TRUE, it updates Horn Attack's horn particle. +#define B_NEW_ROCKS_PARTICLE FALSE // If set to TRUE, it updates rock particles. +#define B_NEW_LEAF_PARTICLE FALSE // If set to TRUE, it updates leaf particle. +#define B_NEW_EMBER_PARTICLES FALSE // If set to TRUE, it updates Ember's fire particle. +#define B_NEW_MEAN_LOOK_PARTICLE FALSE // If set to TRUE, it updates Mean Look's eye particle. +#define B_NEW_TEETH_PARTICLE FALSE // If set to TRUE, it updates Bite/Crunch teeth particle. +#define B_NEW_HANDS_FEET_PARTICLE FALSE // If set to TRUE, it updates chop/kick/punch particles. +#define B_NEW_SPIKES_PARTICLE FALSE // If set to TRUE, it updates Spikes particle. +#define B_NEW_FLY_BUBBLE_PARTICLE FALSE // If set to TRUE, it updates Fly's 'bubble' particle. +#define B_NEW_CURSE_NAIL_PARTICLE FALSE // If set to TRUE, it updates Curse's nail. +#define B_NEW_BATON_PASS_BALL_PARTICLE FALSE // If set to TRUE, it updates Baton Pass' Poké Ball sprite. +#define B_NEW_MORNING_SUN_STAR_PARTICLE FALSE // If set to TRUE, it updates Morning Sun's star particles. +#define B_NEW_IMPACT_PALETTE FALSE // If set to TRUE, it updates the basic 'hit' palette. +#define B_NEW_SURF_PARTICLE_PALETTE FALSE // If set to TRUE, it updates Surf's wave palette. // Poké Ball animation and sounds #define B_ENEMY_THROW_BALLS GEN_LATEST // In GEN_6+, enemy Trainers throw Poké Balls into battle instead of them just appearing on the ground and opening. #define B_ENEMY_THROW_BALLS_SOUND GEN_LATEST // In GEN_5+, enemy Trainer's Poké Balls make a sound when thrown to send out a Pokémon. This can only be used when B_ENEMY_THROW_BALLS is set to GEN_6 or later. #define B_PLAYER_THROW_BALLS_SOUND GEN_LATEST // In GEN_5+, the player's Poké Balls make a sound when thrown to send out a Pokémon. +#define SHOW_TYPES_NEVER 0 +#define SHOW_TYPES_ALWAYS 1 +#define SHOW_TYPES_CAUGHT 2 +#define B_SHOW_TYPES SHOW_TYPES_NEVER // When defined as SHOW_TYPES_ALWAYS, after selecting "Fight" in battle, the types of all Pokemon are revealed. Whe defined as SHOW_TYPES_OWN, types are only revealed if the player owns the mon in question. + #endif // GUARD_CONFIG_BATTLE_H diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index 7f47727ef..0c457951f 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -1336,8 +1336,9 @@ #define STRINGID_FOGISDEEP 737 #define STRINGID_FOGLIFTED 738 #define STRINGID_PKMNMADESHELLGLEAM 739 +#define STRINGID_FICKLEBEAMDOUBLED 740 -#define BATTLESTRINGS_COUNT 740 +#define BATTLESTRINGS_COUNT 741 // This is the string id that gBattleStringsTable starts with. // String ids before this (e.g. STRINGID_INTROMSG) are not in the table, diff --git a/include/random.h b/include/random.h index 2d34a65ec..b7622b97f 100644 --- a/include/random.h +++ b/include/random.h @@ -7,25 +7,21 @@ #define ISO_RANDOMIZE1(val)(RAND_MULT * (val) + 24691) #define ISO_RANDOMIZE2(val)(RAND_MULT * (val) + 12345) -/* Some functions have been added to support HQ_RANDOM. +/* Some functions have been added to support Expansion's RNG implementation. * -* If using HQ_RANDOM, you cannot call Random() in interrupt handlers safely. -* AdvanceRandom() is provided to handle burning numbers in the VBlank handler -* if you choose to do that, and can be used regardless of HQ_RANDOM setting. +* LocalRandom(*val) provides a higher-quality replacement for uses of +* ISO_RANDOMIZE in vanilla Emerald. You can use LocalRandomSeed(u32) to +* create a local state. +* +* It is no longer possible to call Random() in interrupt handlers safely. +* AdvanceRandom() is provided to handle burning numbers in VBlank handlers. * If you need to use random numbers in the VBlank handler, a local state * should be used instead. * -* LocalRandom(*val) allows you to have local random states that are the same -* type as the global states regardless of HQ_RANDOM setting, which is useful -* if you want to be able to set them from or assign them to gRngValue. -* LocalRandomSeed(u32) returns a properly seeded rng_value_t. -* -* Random2_32() was added to HQ_RANDOM because the output of the generator is -* always 32 bits and Random()/Random2() are just wrappers in that mode. It is -* also available in non-HQ mode for consistency. +* Random2_32() was added, even though it is not used directly, because the +* underlying RNG always outputs 32 bits. */ -#if HQ_RANDOM == TRUE struct Sfc32State { u32 a; u32 b; @@ -71,40 +67,6 @@ static inline u16 Random2(void) } void AdvanceRandom(void); -#else -typedef u32 rng_value_t; - -#define RNG_VALUE_EMPTY 0 - -//Returns a 16-bit pseudorandom number -u16 Random(void); -u16 Random2(void); - -//Sets the initial seed value of the pseudorandom number generator -void SeedRng(u16 seed); -void SeedRng2(u16 seed); - -//Returns a 32-bit pseudorandom number -#define Random32() (Random() | (Random() << 16)) -#define Random2_32() (Random2() | (Random2() << 16)) - -static inline u16 LocalRandom(rng_value_t *val) -{ - *val = ISO_RANDOMIZE1(*val); - return *val >> 16; -} - -static inline void AdvanceRandom(void) -{ - Random(); -} - -static inline rng_value_t LocalRandomSeed(u32 seed) -{ - return seed; -} - -#endif extern rng_value_t gRngValue; extern rng_value_t gRng2Value; @@ -174,12 +136,14 @@ enum RandomTag RNG_G_MAX_BEFUDDLE, RNG_G_MAX_REPLENISH, RNG_G_MAX_SNOOZE, + RNG_HARVEST, RNG_HITS, RNG_HOLD_EFFECT_FLINCH, RNG_INFATUATION, RNG_LOADED_DICE, RNG_METRONOME, RNG_PARALYSIS, + RNG_PICKUP, RNG_POISON_POINT, RNG_POISON_TOUCH, RNG_RAMPAGE_TURNS, diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index bbbc74082..9bd66d5f5 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -6,6 +6,7 @@ #include "battle_ai_util.h" #include "battle_ai_main.h" #include "battle_controllers.h" +// #include "battle_factory.h" #include "battle_setup.h" #include "battle_z_move.h" #include "battle_terastal.h" @@ -107,7 +108,7 @@ void BattleAI_SetupItems(void) // Items are allowed to use in ONLY trainer battles. if ((gBattleTypeFlags & BATTLE_TYPE_TRAINER) && !(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_SAFARI | BATTLE_TYPE_BATTLE_TOWER - | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_FRONTIER + | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_SECRET_BASE | BATTLE_TYPE_FRONTIER | BATTLE_TYPE_INGAME_PARTNER | BATTLE_TYPE_RECORDED_LINK) ) ) @@ -167,13 +168,13 @@ static u32 GetAiFlags(u16 trainerId) flags = AI_FLAG_CHECK_BAD_MOVE; // else if (gBattleTypeFlags & BATTLE_TYPE_FIRST_BATTLE) // flags = AI_FLAG_FIRST_BATTLE; - else if (gBattleTypeFlags & (BATTLE_TYPE_BATTLE_TOWER | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_TRAINER_TOWER)) + else if (gBattleTypeFlags & (BATTLE_TYPE_FRONTIER | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_TRAINER_HILL | BATTLE_TYPE_SECRET_BASE)) flags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT; else flags = GetTrainerAIFlagsFromId(trainerId); } - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) { flags |= AI_FLAG_DOUBLE_BATTLE; } @@ -223,7 +224,7 @@ void BattleAI_SetupFlags(void) { AI_THINKING_STRUCT->aiFlags[B_POSITION_PLAYER_RIGHT] = GetAiFlags(gPartnerTrainerId - TRAINER_PARTNER(PARTNER_NONE)); } - else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && IsAiVsAiBattle()) + else if (IsDoubleBattle() && IsAiVsAiBattle()) { AI_THINKING_STRUCT->aiFlags[B_POSITION_PLAYER_RIGHT] = AI_THINKING_STRUCT->aiFlags[B_POSITION_PLAYER_LEFT]; } @@ -261,7 +262,7 @@ void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler) // Ignore moves that aren't possible to use. for (i = 0; i < MAX_MON_MOVES; i++) { - if (gBitTable[i] & moveLimitations) + if ((1u << i) & moveLimitations) SET_SCORE(battler, i, 0); } @@ -274,7 +275,7 @@ u32 BattleAI_ChooseMoveOrAction(void) { u32 ret; - if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) + if (!IsDoubleBattle()) ret = ChooseMoveOrAction_Singles(sBattler_AI); else ret = ChooseMoveOrAction_Doubles(sBattler_AI); @@ -322,7 +323,7 @@ void Ai_InitPartyStruct(void) // Save first 2 or 4(in doubles) mons CopyBattlerDataToAIParty(B_POSITION_PLAYER_LEFT, B_SIDE_PLAYER); - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) CopyBattlerDataToAIParty(B_POSITION_PLAYER_RIGHT, B_SIDE_PLAYER); // If player's partner is AI, save opponent mons @@ -445,7 +446,7 @@ static void SetBattlerAiMovesData(struct AiLogicData *aiData, u32 battlerAtk, u3 if (move != 0 && move != 0xFFFF //&& gMovesInfo[move].power != 0 /* we want to get effectiveness and accuracy of status moves */ - && !(aiData->moveLimitations[battlerAtk] & gBitTable[i])) + && !(aiData->moveLimitations[battlerAtk] & (1u << i))) { if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_RISKY) dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, TRUE, weather, DMG_ROLL_HIGHEST); @@ -505,12 +506,12 @@ static bool32 AI_SwitchMonIfSuitable(u32 battler, bool32 doubleBattle) if (doubleBattle) { u32 partner = BATTLE_PARTNER(battler); - if (AI_DATA->shouldSwitchMon & gBitTable[partner] && AI_DATA->monToSwitchId[partner] == monToSwitchId) + if (AI_DATA->shouldSwitchMon & (1u << partner) && AI_DATA->monToSwitchId[partner] == monToSwitchId) { return FALSE; } } - AI_DATA->shouldSwitchMon |= gBitTable[battler]; + AI_DATA->shouldSwitchMon |= 1 << battler; AI_DATA->monToSwitchId[battler] = monToSwitchId; return TRUE; } @@ -523,6 +524,7 @@ static bool32 AI_ShouldSwitchIfBadMoves(u32 battler, bool32 doubleBattle) // If can switch. if (CountUsablePartyMons(battler) > 0 && !IsBattlerTrapped(battler, TRUE) + && !(gBattleTypeFlags & (BATTLE_TYPE_ARENA | BATTLE_TYPE_PALACE)) && AI_THINKING_STRUCT->aiFlags[battler] & (AI_FLAG_CHECK_VIABILITY | AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_PREFER_BATON_PASS) && !(AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SEQUENCE_SWITCHING)) { @@ -1734,6 +1736,14 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case EFFECT_HIT_ESCAPE: break; + case EFFECT_CHILLY_RECEPTION: + if (CountUsablePartyMons(battlerAtk) == 0) + ADJUST_SCORE(-10); + else if (weather & (B_WEATHER_SNOW | B_WEATHER_PRIMAL_ANY) || IsMoveEffectWeather(aiData->partnerMove)) + ADJUST_SCORE(-8); + else if (weather & B_WEATHER_HAIL) + ADJUST_SCORE(-2); // mainly to prevent looping between hail and snow + break; case EFFECT_BELLY_DRUM: case EFFECT_FILLET_AWAY: if (aiData->abilities[battlerAtk] == ABILITY_CONTRARY) @@ -2045,7 +2055,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } /*if (AI_THINKING_STRUCT->aiFlags[battlerAtk] == AI_SCRIPT_CHECK_BAD_MOVE //Only basic AI - && IS_DOUBLE_BATTLE) //Make the regular AI know how to use Protect minimally in Doubles + && IsDoubleBattle()) //Make the regular AI know how to use Protect minimally in Doubles { u8 shouldProtect = ShouldProtect(battlerAtk, battlerDef, move); if (shouldProtect == USE_PROTECT || shouldProtect == PROTECT_FROM_FOES) @@ -2768,6 +2778,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_SNOWSCAPE: case EFFECT_RAIN_DANCE: case EFFECT_SANDSTORM: + case EFFECT_CHILLY_RECEPTION: if (IsMoveEffectWeather(move)) ADJUST_SCORE(-10); break; @@ -2847,6 +2858,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } break; case EFFECT_SNOWSCAPE: + case EFFECT_CHILLY_RECEPTION: if (IsBattlerAlive(battlerAtkPartner) && ShouldSetSnow(battlerAtkPartner, atkPartnerAbility, atkPartnerHoldEffect)) { @@ -3616,6 +3628,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) //fallthrough case EFFECT_HIT_ESCAPE: case EFFECT_PARTING_SHOT: + case EFFECT_CHILLY_RECEPTION: if (!IsDoubleBattle()) { switch (ShouldPivot(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, movesetIndex)) @@ -4051,7 +4064,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(DECENT_EFFECT); // Force 'em out next turn break; default: - if (move != MOVE_BESTOW && aiData->items[battlerAtk] == ITEM_NONE) + if (gMovesInfo[gCurrentMove].effect != EFFECT_BESTOW && aiData->items[battlerAtk] == ITEM_NONE) { switch (aiData->holdEffects[battlerDef]) { @@ -4874,6 +4887,7 @@ static s32 AI_SetupFirstTurn(u32 battlerAtk, u32 battlerDef, u32 move, s32 score case EFFECT_SANDSTORM: case EFFECT_HAIL: case EFFECT_SNOWSCAPE: + case EFFECT_CHILLY_RECEPTION: case EFFECT_GEOMANCY: case EFFECT_VICTORY_DANCE: ADJUST_SCORE(DECENT_EFFECT); @@ -5042,7 +5056,7 @@ static s32 AI_PreferBatonPass(u32 battlerAtk, u32 battlerDef, u32 move, s32 scor static s32 AI_HPAware(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { u32 effect = gMovesInfo[move].effect; - u32 moveType = TYPE_NONE; + u32 moveType = 0; SetTypeBeforeUsingMove(move, battlerAtk); moveType = GetMoveType(move); diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 05ab59036..e2d0d85a3 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -40,7 +40,7 @@ static void InitializeSwitchinCandidate(struct Pokemon *mon) static bool32 IsAceMon(u32 battler, u32 monPartyId) { if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_ACE_POKEMON - && !(gBattleStruct->forcedSwitch & gBitTable[battler]) + && !(gBattleStruct->forcedSwitch & (1u << battler)) && monPartyId == CalculateEnemyPartyCount()-1) return TRUE; return FALSE; @@ -82,7 +82,7 @@ static bool32 HasBadOdds(u32 battler, bool32 emitResult) return FALSE; // Double Battles aren't included in AI_FLAG_SMART_MON_CHOICE. Defaults to regular switch in logic - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) return FALSE; opposingPosition = BATTLE_OPPOSITE(GetBattlerPosition(battler)); @@ -218,9 +218,9 @@ static bool32 HasBadOdds(u32 battler, bool32 emitResult) static bool32 ShouldSwitchIfAllBadMoves(u32 battler, bool32 emitResult) { - if (AI_DATA->shouldSwitchMon & gBitTable[battler]) + if (AI_DATA->shouldSwitchMon & (1u << battler)) { - AI_DATA->shouldSwitchMon &= ~(gBitTable[battler]); + AI_DATA->shouldSwitchMon &= ~(1u << battler); gBattleStruct->AI_monToSwitchIntoId[battler] = AI_DATA->monToSwitchId[battler]; if (emitResult) BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0); @@ -242,7 +242,7 @@ static bool32 ShouldSwitchIfWonderGuard(u32 battler, bool32 emitResult) struct Pokemon *party = NULL; u16 move; - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) return FALSE; opposingPosition = BATTLE_OPPOSITE(GetBattlerPosition(battler)); @@ -318,10 +318,10 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler, bool32 emitResult) if (IS_MOVE_STATUS(gLastLandedMoves[battler])) return FALSE; - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) { battlerIn1 = battler; - if (gAbsentBattlerFlags & gBitTable[GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler)))]) + if (gAbsentBattlerFlags & (1u << GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler))))) battlerIn2 = battler; else battlerIn2 = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler))); @@ -690,7 +690,7 @@ static bool32 HasSuperEffectiveMoveAgainstOpponents(u32 battler, bool32 noRng) u32 opposingPosition = BATTLE_OPPOSITE(GetBattlerPosition(battler)); u32 opposingBattler = GetBattlerAtPosition(opposingPosition); - if (!(gAbsentBattlerFlags & gBitTable[opposingBattler])) + if (!(gAbsentBattlerFlags & (1u << opposingBattler))) { for (i = 0; i < MAX_MON_MOVES; i++) { @@ -707,12 +707,12 @@ static bool32 HasSuperEffectiveMoveAgainstOpponents(u32 battler, bool32 noRng) } } } - if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) + if (!IsDoubleBattle()) return FALSE; opposingBattler = GetBattlerAtPosition(BATTLE_PARTNER(opposingPosition)); - if (!(gAbsentBattlerFlags & gBitTable[opposingBattler])) + if (!(gAbsentBattlerFlags & (1u << opposingBattler))) { for (i = 0; i < MAX_MON_MOVES; i++) { @@ -765,10 +765,10 @@ static bool32 FindMonWithFlagsAndSuperEffective(u32 battler, u16 flags, u32 modu if (IS_MOVE_STATUS(gLastLandedMoves[battler])) return FALSE; - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) { battlerIn1 = battler; - if (gAbsentBattlerFlags & gBitTable[GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler)))]) + if (gAbsentBattlerFlags & (1u << GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler))))) battlerIn2 = battler; else battlerIn2 = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler))); @@ -846,10 +846,10 @@ static bool32 CanMonSurviveHazardSwitchin(u32 battler) // Battler will faint to hazards, check to see if another mon can clear them if (hazardDamage > battlerHp) { - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) { battlerIn1 = battler; - if (gAbsentBattlerFlags & gBitTable[GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler)))]) + if (gAbsentBattlerFlags & (1u << GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler))))) battlerIn2 = battler; else battlerIn2 = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler))); @@ -881,7 +881,9 @@ static bool32 CanMonSurviveHazardSwitchin(u32 battler) for (j = 0; j < MAX_MON_MOVES; j++) { aiMove = GetMonData(&party[i], MON_DATA_MOVE1 + j, NULL); - if (aiMove == MOVE_RAPID_SPIN || aiMove == MOVE_DEFOG || aiMove == MOVE_MORTAL_SPIN || aiMove == MOVE_TIDY_UP) + if (MoveHasAdditionalEffectSelf(aiMove, MOVE_EFFECT_RAPID_SPIN) + || (B_DEFOG_EFFECT_CLEARING >= GEN_6 && gMovesInfo[aiMove].effect == EFFECT_DEFOG) + || gMovesInfo[aiMove].effect == EFFECT_TIDY_UP) { // Have a mon that can clear the hazards, so switching out is okay return TRUE; @@ -1014,6 +1016,8 @@ bool32 ShouldSwitch(u32 battler, bool32 emitResult) return FALSE; if (IsAbilityPreventingEscape(battler)) return FALSE; + if (gBattleTypeFlags & BATTLE_TYPE_ARENA) + return FALSE; // Sequence Switching AI never switches mid-battle if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SEQUENCE_SWITCHING) @@ -1021,10 +1025,10 @@ bool32 ShouldSwitch(u32 battler, bool32 emitResult) availableToSwitch = 0; - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) { battlerIn1 = battler; - if (gAbsentBattlerFlags & gBitTable[GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler)))]) + if (gAbsentBattlerFlags & (1u << GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler))))) battlerIn2 = battler; else battlerIn2 = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler))); @@ -1138,7 +1142,7 @@ void AI_TrySwitchOrUseItem(u32 battler) s32 monToSwitchId = AI_DATA->mostSuitableMonId[battler]; if (monToSwitchId == PARTY_SIZE) { - if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) + if (!IsDoubleBattle()) { battlerIn1 = GetBattlerAtPosition(battlerPosition); battlerIn2 = battlerIn1; @@ -1193,14 +1197,14 @@ static u32 GetBestMonBatonPass(struct Pokemon *party, int firstId, int lastId, u for (i = firstId; i < lastId; i++) { - if (invalidMons & gBitTable[i]) + if (invalidMons & (1u << i)) continue; for (j = 0; j < MAX_MON_MOVES; j++) { if (GetMonData(&party[i], MON_DATA_MOVE1 + j, NULL) == MOVE_BATON_PASS) { - bits |= gBitTable[i]; + bits |= 1u << i; break; } } @@ -1211,7 +1215,7 @@ static u32 GetBestMonBatonPass(struct Pokemon *party, int firstId, int lastId, u do { i = (Random() % (lastId - firstId)) + firstId; - } while (!(bits & gBitTable[i])); + } while (!(bits & (1 << i))); return i; } @@ -1229,7 +1233,7 @@ static u32 GetBestMonTypeMatchup(struct Pokemon *party, int firstId, int lastId, // Find the mon whose type is the most suitable defensively. for (i = firstId; i < lastId; i++) { - if (!(gBitTable[i] & invalidMons) && !(gBitTable[i] & bits)) + if (!((1u << i) & invalidMons) && !((1u << i) & bits)) { u16 species = GetMonData(&party[i], MON_DATA_SPECIES); uq4_12_t typeEffectiveness = UQ_4_12(1.0); @@ -1269,7 +1273,7 @@ static u32 GetBestMonTypeMatchup(struct Pokemon *party, int firstId, int lastId, if (i != MAX_MON_MOVES) return bestMonId; // Has both the typing and at least one super effective move. - bits |= gBitTable[bestMonId]; // Sorry buddy, we want something better. + bits |= (1u << bestMonId); // Sorry buddy, we want something better. } else { @@ -1291,7 +1295,7 @@ static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 inva // If we couldn't find the best mon in terms of typing, find the one that deals most damage. for (i = firstId; i < lastId; i++) { - if (gBitTable[i] & invalidMons) + if ((1 << (i)) & invalidMons) continue; InitializeSwitchinCandidate(&party[i]); for (j = 0; j < MAX_MON_MOVES; j++) @@ -1562,9 +1566,9 @@ static u32 GetSwitchinStatusDamage(u32 battler) if ((status & STATUS1_TOXIC_COUNTER) != STATUS1_TOXIC_TURN(15)) // not 16 turns AI_DATA->switchinCandidate.battleMon.status1 += STATUS1_TOXIC_TURN(1); statusDamage = maxHP / 16; - statusDamage *= AI_DATA->switchinCandidate.battleMon.status1 & STATUS1_TOXIC_COUNTER >> 8; if (statusDamage == 0) statusDamage = 1; + statusDamage *= AI_DATA->switchinCandidate.battleMon.status1 & STATUS1_TOXIC_COUNTER >> 8; } } @@ -1731,7 +1735,7 @@ static int GetRandomSwitchinWithBatonPass(int aliveCount, int bits, int firstId, do { return (Random() % (lastId - firstId)) + firstId; - } while (!(bits & gBitTable[currentMonId])); + } while (!(bits & (1 << (currentMonId)))); } // Catch any other cases (such as only one mon alive and it has Baton Pass) @@ -1871,7 +1875,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, // Check for Baton Pass; hitsToKO requirements mean mon can boost and BP without dying whether it's slower or not if (aiMove == MOVE_BATON_PASS && ((hitsToKOAI > hitsToKOAIThreshold + 1 && AI_DATA->switchinCandidate.battleMon.speed < playerMonSpeed) || (hitsToKOAI > hitsToKOAIThreshold && AI_DATA->switchinCandidate.battleMon.speed > playerMonSpeed))) - bits |= gBitTable[i]; + bits |= 1u << i; // Check for mon with resistance and super effective move for GetBestMonTypeMatchup if (aiMove != MOVE_NONE && gMovesInfo[aiMove].power != 0) @@ -1998,7 +2002,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, } // If ace mon is the last available Pokemon and U-Turn/Volt Switch was used - switch to the mon. if (aceMonId != PARTY_SIZE - && (gMovesInfo[gLastUsedMove].effect == EFFECT_HIT_ESCAPE || gMovesInfo[gLastUsedMove].effect == EFFECT_PARTING_SHOT || gMovesInfo[gLastUsedMove].effect == EFFECT_BATON_PASS)) + && (gMovesInfo[gLastUsedMove].effect == EFFECT_HIT_ESCAPE || gMovesInfo[gLastUsedMove].effect == EFFECT_PARTING_SHOT || gMovesInfo[gLastUsedMove].effect == EFFECT_BATON_PASS || gMovesInfo[gLastUsedMove].effect == EFFECT_CHILLY_RECEPTION)) return aceMonId; return PARTY_SIZE; @@ -2035,17 +2039,19 @@ u32 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd) if (*(gBattleStruct->monToSwitchIntoId + battler) != PARTY_SIZE) return *(gBattleStruct->monToSwitchIntoId + battler); + if (gBattleTypeFlags & BATTLE_TYPE_ARENA) + return gBattlerPartyIndexes[battler] + 1; - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) { battlerIn1 = battler; - if (gAbsentBattlerFlags & gBitTable[GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler)))]) + if (gAbsentBattlerFlags & (1u << GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler))))) battlerIn2 = battler; else battlerIn2 = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler))); opposingBattler = BATTLE_OPPOSITE(battlerIn1); - if (gAbsentBattlerFlags & gBitTable[opposingBattler]) + if (gAbsentBattlerFlags & (1u << opposingBattler)) opposingBattler ^= BIT_FLANK; } else @@ -2070,7 +2076,7 @@ u32 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd) // Split ideal mon decision between after previous mon KO'd (prioritize offensive options) and after switching active mon out (prioritize defensive options), and expand the scope of both. // Only use better mon selection if AI_FLAG_SMART_MON_CHOICES is set for the trainer. - if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SMART_MON_CHOICES && !(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) // Double Battles aren't included in AI_FLAG_SMART_MON_CHOICE. Defaults to regular switch in logic + if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SMART_MON_CHOICES && !IsDoubleBattle()) // Double Battles aren't included in AI_FLAG_SMART_MON_CHOICE. Defaults to regular switch in logic { bestMonId = GetBestMonIntegrated(party, firstId, lastId, battler, opposingBattler, battlerIn1, battlerIn2, switchAfterMonKOd); return bestMonId; @@ -2091,12 +2097,12 @@ u32 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd) || 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.) { - invalidMons |= gBitTable[i]; + invalidMons |= 1u << i; } else if (IsAceMon(battler, i))// Save Ace Pokemon for last. { aceMonId = i; - invalidMons |= gBitTable[i]; + invalidMons |= 1u << i; } else { diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 5cec38e56..8dbeab3c7 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -6,6 +6,7 @@ #include "battle_ai_util.h" #include "battle_ai_main.h" #include "battle_ai_switch_items.h" +// #include "battle_factory.h" #include "battle_setup.h" #include "event_data.h" #include "data.h" @@ -350,12 +351,12 @@ bool32 MovesWithCategoryUnusable(u32 attacker, u32 target, u32 category) if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && GetBattleMoveCategory(moves[i]) == category - && !(unusable & gBitTable[i])) + && !(unusable & (1u << i))) { SetTypeBeforeUsingMove(moves[i], attacker); moveType = GetMoveType(moves[i]); if (CalcTypeEffectivenessMultiplier(moves[i], moveType, attacker, target, AI_DATA->abilities[target], FALSE) != 0) - usable |= gBitTable[i]; + usable |= 1u << i; } } @@ -481,7 +482,7 @@ bool32 IsDamageMoveUnusable(u32 move, u32 battlerAtk, u32 battlerDef) return TRUE; break; case EFFECT_POLTERGEIST: - if (AI_DATA->items[battlerDef] == ITEM_NONE) + if (AI_DATA->items[battlerDef] == ITEM_NONE || gFieldStatuses & STATUS_FIELD_MAGIC_ROOM || battlerDefAbility == ABILITY_KLUTZ) return TRUE; break; case EFFECT_FIRST_TURN_ONLY: @@ -575,13 +576,13 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u if (critChanceIndex > 1) // Consider crit damage only if a move has at least +2 crit chance { s32 nonCritDmg = CalculateMoveDamageVars(move, battlerAtk, battlerDef, moveType, fixedBasePower, - effectivenessMultiplier, weather, FALSE, - aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], - aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); + effectivenessMultiplier, weather, FALSE, + aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], + aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); s32 critDmg = CalculateMoveDamageVars(move, battlerAtk, battlerDef, moveType, fixedBasePower, - effectivenessMultiplier, weather, TRUE, - aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], - aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); + effectivenessMultiplier, weather, TRUE, + aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], + aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); u32 critOdds = GetCritHitOdds(critChanceIndex); // With critOdds getting closer to 1, dmg gets closer to critDmg. @@ -594,20 +595,33 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u else if (critChanceIndex == -2) // Guaranteed critical { s32 critDmg = CalculateMoveDamageVars(move, battlerAtk, battlerDef, moveType, fixedBasePower, - effectivenessMultiplier, weather, TRUE, - aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], - aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); + effectivenessMultiplier, weather, TRUE, + aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], + aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); simDamage.expected = GetDamageByRollType(critDmg, rollType); simDamage.minimum = LowestRollDmg(critDmg); } else { - s32 nonCritDmg = CalculateMoveDamageVars(move, battlerAtk, battlerDef, moveType, fixedBasePower, - effectivenessMultiplier, weather, FALSE, - aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], - aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); - + s32 nonCritDmg = 0; + if (gMovesInfo[move].effect == EFFECT_TRIPLE_KICK) + { + for (gMultiHitCounter = gMovesInfo[move].strikeCount; gMultiHitCounter > 0; gMultiHitCounter--) // The global is used to simulate actual damage done + { + nonCritDmg += CalculateMoveDamageVars(move, battlerAtk, battlerDef, moveType, fixedBasePower, + effectivenessMultiplier, weather, FALSE, + aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], + aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); + } + } + else + { + nonCritDmg = CalculateMoveDamageVars(move, battlerAtk, battlerDef, moveType, fixedBasePower, + effectivenessMultiplier, weather, FALSE, + aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef], + aiData->abilities[battlerAtk], aiData->abilities[battlerDef]); + } simDamage.expected = GetDamageByRollType(nonCritDmg, rollType); simDamage.minimum = LowestRollDmg(nonCritDmg); } @@ -1091,7 +1105,7 @@ bool32 CanTargetFaintAi(u32 battlerDef, u32 battlerAtk) for (i = 0; i < MAX_MON_MOVES; i++) { - if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && !(unusable & gBitTable[i]) + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && !(unusable & (1u << i)) && AI_DATA->simulatedDmg[battlerDef][battlerAtk][i].expected >= gBattleMons[battlerAtk].hp) { return TRUE; @@ -1129,7 +1143,7 @@ u32 GetBestDmgMoveFromBattler(u32 battlerAtk, u32 battlerDef) for (i = 0; i < MAX_MON_MOVES; i++) { - if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && !(unusable & gBitTable[i]) + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && !(unusable & (1u << i)) && bestDmg < AI_DATA->simulatedDmg[battlerAtk][battlerDef][i].expected) { bestDmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][i].expected; @@ -1150,7 +1164,7 @@ u32 GetBestDmgFromBattler(u32 battler, u32 battlerTarget) { if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE - && !(unusable & gBitTable[i]) + && !(unusable & (1u << i)) && bestDmg < AI_DATA->simulatedDmg[battler][battlerTarget][i].expected) { bestDmg = AI_DATA->simulatedDmg[battler][battlerTarget][i].expected; @@ -1170,7 +1184,7 @@ bool32 CanAIFaintTarget(u32 battlerAtk, u32 battlerDef, u32 numHits) for (i = 0; i < MAX_MON_MOVES; i++) { - if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && !(moveLimitations & gBitTable[i])) + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && !(moveLimitations & (1u << i))) { // Use the pre-calculated value in simulatedDmg instead of re-calculating it dmg = AI_DATA->simulatedDmg[battlerAtk][battlerDef][i].expected; @@ -1215,7 +1229,7 @@ bool32 CanTargetFaintAiWithMod(u32 battlerDef, u32 battlerAtk, s32 hpMod, s32 dm if (dmgMod) dmg *= dmgMod; - if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && !(unusable & gBitTable[i]) && dmg >= hpCheck) + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && !(unusable & (1u << i)) && dmg >= hpCheck) { return TRUE; } @@ -2077,7 +2091,7 @@ bool32 HasMoveWithLowAccuracy(u32 battlerAtk, u32 battlerDef, u32 accCheck, bool if (moves[i] == MOVE_NONE || moves[i] == MOVE_UNAVAILABLE) continue; - if (!(gBitTable[i] & moveLimitations)) + if (!((1u << i) & moveLimitations)) { if (ignoreStatus && IS_MOVE_STATUS(moves[i])) continue; @@ -2103,7 +2117,7 @@ bool32 HasSleepMoveWithLowAccuracy(u32 battlerAtk, u32 battlerDef) { if (moves[i] == MOVE_NONE) break; - if (!(gBitTable[i] & moveLimitations)) + if (!((1u << i) & moveLimitations)) { if (gMovesInfo[moves[i]].effect == EFFECT_SLEEP && AI_DATA->moveAccuracy[battlerAtk][battlerDef][i] < 85) @@ -2499,7 +2513,7 @@ u32 GetBattlerSecondaryDamage(u32 battlerId) bool32 BattlerWillFaintFromWeather(u32 battler, u32 ability) { if ((BattlerAffectedBySandstorm(battler, ability) || BattlerAffectedByHail(battler, ability)) - && gBattleMons[battler].hp <= gBattleMons[battler].maxHP / 16) + && gBattleMons[battler].hp <= max(1, gBattleMons[battler].maxHP / 16)) return TRUE; return FALSE; @@ -2508,7 +2522,7 @@ bool32 BattlerWillFaintFromWeather(u32 battler, u32 ability) bool32 BattlerWillFaintFromSecondaryDamage(u32 battler, u32 ability) { if (GetBattlerSecondaryDamage(battler) != 0 - && gBattleMons[battler].hp <= gBattleMons[battler].maxHP / 16) + && gBattleMons[battler].hp <= max(1, gBattleMons[battler].maxHP / 16)) return TRUE; return FALSE; } @@ -2778,6 +2792,7 @@ bool32 CanKnockOffItem(u32 battler, u32 item) | BATTLE_TYPE_FRONTIER | BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK + | BATTLE_TYPE_SECRET_BASE | (B_TRAINERS_KNOCK_OFF_ITEMS == TRUE ? BATTLE_TYPE_TRAINER : 0) )) && GetBattlerSide(battler) == B_SIDE_PLAYER) return FALSE; @@ -3024,7 +3039,7 @@ bool32 AnyPartyMemberStatused(u32 battlerId, bool32 checkSoundproof) else party = gEnemyParty; - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) { battlerOnField1 = gBattlerPartyIndexes[battlerId]; battlerOnField2 = gBattlerPartyIndexes[GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battlerId)))]; @@ -3247,7 +3262,8 @@ bool32 IsMoveEffectWeather(u32 move) || gMovesInfo[move].effect == EFFECT_RAIN_DANCE || gMovesInfo[move].effect == EFFECT_SANDSTORM || gMovesInfo[move].effect == EFFECT_HAIL - || gMovesInfo[move].effect == EFFECT_SNOWSCAPE)) + || gMovesInfo[move].effect == EFFECT_SNOWSCAPE + || gMovesInfo[move].effect == EFFECT_CHILLY_RECEPTION)) return TRUE; return FALSE; } @@ -3415,7 +3431,7 @@ s32 CountUsablePartyMons(u32 battlerId) else party = gEnemyParty; - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) { battlerOnField1 = gBattlerPartyIndexes[battlerId]; battlerOnField2 = gBattlerPartyIndexes[GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battlerId)))]; @@ -3826,7 +3842,7 @@ bool32 AI_MoveMakesContact(u32 ability, u32 holdEffect, u32 move) bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove) { // simple logic. just upgrades chosen move to z move if possible, unless regular move would kill opponent - if ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE) && battlerDef == BATTLE_PARTNER(battlerAtk)) + if ((IsDoubleBattle()) && battlerDef == BATTLE_PARTNER(battlerAtk)) return FALSE; // don't use z move on partner if (HasTrainerUsedGimmick(battlerAtk, GIMMICK_Z_MOVE)) return FALSE; // can't use z move twice diff --git a/src/battle_main.c b/src/battle_main.c index d628f40d0..89abd267c 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -2482,11 +2482,13 @@ static void ClearSetBScriptingStruct(void) { // windowsType is set up earlier in BattleInitBgsAndWindows, so we need to save the value u32 temp = gBattleScripting.windowsType; + u32 specialBattleType = gBattleScripting.specialTrainerBattleType; memset(&gBattleScripting, 0, sizeof(gBattleScripting)); gBattleScripting.windowsType = temp; gBattleScripting.battleStyle = gSaveBlock2Ptr->optionsBattleStyle; gBattleScripting.expOnCatch = (B_EXP_CATCH >= GEN_6); + gBattleScripting.specialTrainerBattleType = specialBattleType; } static void BattleStartClearSetData(void) @@ -2593,8 +2595,6 @@ static void BattleStartClearSetData(void) gBattleStruct->itemLost[B_SIDE_PLAYER][i].originalItem = GetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM); gBattleStruct->itemLost[B_SIDE_OPPONENT][i].originalItem = GetMonData(&gEnemyParty[i], MON_DATA_HELD_ITEM); gPartyCriticalHits[i] = 0; - gBattleStruct->allowedToChangeFormInWeather[i][B_SIDE_PLAYER] = FALSE; - gBattleStruct->allowedToChangeFormInWeather[i][B_SIDE_OPPONENT] = FALSE; } gBattleStruct->swapDamageCategory = FALSE; // Photon Geyser, Shell Side Arm, Light That Burns the Sky @@ -2697,8 +2697,9 @@ void SwitchInClearSetData(u32 battler) gBattleStruct->lastTakenMoveFrom[battler][1] = 0; gBattleStruct->lastTakenMoveFrom[battler][2] = 0; gBattleStruct->lastTakenMoveFrom[battler][3] = 0; - gBattleStruct->lastMoveFailed &= ~(gBitTable[battler]); - gBattleStruct->boosterEnergyActivates &= ~(gBitTable[battler]); + gBattleStruct->lastMoveFailed &= ~(1u << battler); + gBattleStruct->boosterEnergyActivates &= ~(1u << battler); + gBattleStruct->canPickupItem &= ~(1u << battler); for (i = 0; i < ARRAY_COUNT(gSideTimers); i++) { @@ -2722,7 +2723,7 @@ void SwitchInClearSetData(u32 battler) // 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; - gBattleStruct->enduredDamage &= ~gBitTable[battler]; + gBattleStruct->enduredDamage &= ~(1u << battler); // Reset G-Max Chi Strike boosts. gBattleStruct->bonusCritStages[battler] = 0; @@ -2825,7 +2826,7 @@ const u8* FaintClearSetData(u32 battler) gBattleStruct->lastTakenMoveFrom[battler][2] = 0; gBattleStruct->lastTakenMoveFrom[battler][3] = 0; - gBattleStruct->boosterEnergyActivates &= ~(gBitTable[battler]); + gBattleStruct->boosterEnergyActivates &= ~(1u << battler); for (i = 0; i < ARRAY_COUNT(gSideTimers); i++) { @@ -3273,7 +3274,7 @@ static void TryDoEventsBeforeFirstTurn(void) struct Pokemon *party = GetBattlerParty(i); struct Pokemon *mon = &party[gBattlerPartyIndexes[i]]; if (!IsBattlerAlive(i) || gBattleMons[i].species == SPECIES_NONE || GetMonData(mon, MON_DATA_IS_EGG)) - gAbsentBattlerFlags |= gBitTable[i]; + gAbsentBattlerFlags |= 1u << i; } } @@ -3406,7 +3407,7 @@ static void TryDoEventsBeforeFirstTurn(void) gBattleMons[i].status2 &= ~STATUS2_FLINCHED; // Record party slots of player's mons that appeared in battle if (!BattlerHasAi(i)) - gBattleStruct->appearedInBattle |= gBitTable[gBattlerPartyIndexes[i]]; + gBattleStruct->appearedInBattle |= 1u << gBattlerPartyIndexes[i]; } *(&gBattleStruct->turnEffectsTracker) = 0; @@ -3515,6 +3516,11 @@ void BattleTurnPassed(void) SetAiLogicDataForTurn(AI_DATA); // get assumed abilities, hold effects, etc of all battlers gBattleMainFunc = HandleTurnActionSelectionState; + if (gSideTimers[B_SIDE_PLAYER].retaliateTimer > 0) + gSideTimers[B_SIDE_PLAYER].retaliateTimer--; + if (gSideTimers[B_SIDE_OPPONENT].retaliateTimer > 0) + gSideTimers[B_SIDE_OPPONENT].retaliateTimer--; + if ((i = ShouldDoTrainerSlide(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT), TRAINER_SLIDE_LAST_LOW_HP))) BattleScriptExecute(i == 1 ? BattleScript_TrainerASlideMsgEnd2 : BattleScript_TrainerBSlideMsgEnd2); else if ((i = ShouldDoTrainerSlide(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT), TRAINER_SLIDE_LAST_HALF_HP))) @@ -3608,7 +3614,7 @@ void SwitchPartyOrder(u32 battler) partyId2 = GetPartyIdFromBattlePartyId(*(gBattleStruct->monToSwitchIntoId + battler)); SwitchPartyMonSlots(partyId1, partyId2); - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) { for (i = 0; i < (int)ARRAY_COUNT(gBattlePartyCurrentOrder); i++) { @@ -3635,6 +3641,7 @@ enum STATE_WAIT_ACTION_CONFIRMED, STATE_SELECTION_SCRIPT, STATE_WAIT_SET_BEFORE_ACTION, + STATE_SELECTION_SCRIPT_MAY_RUN }; static void HandleTurnActionSelectionState(void) @@ -3653,7 +3660,7 @@ static void HandleTurnActionSelectionState(void) // Do AI score computations here so we can use them in AI_TrySwitchOrUseItem if ((gBattleTypeFlags & BATTLE_TYPE_HAS_AI || IsWildMonSmart()) - && (BattlerHasAi(battler))) + && (BattlerHasAi(battler) && !(gBattleTypeFlags & BATTLE_TYPE_PALACE))) { if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_RISKY) // Risky AI switches aggressively even mid battle AI_DATA->mostSuitableMonId[battler] = GetMostSuitableMonToSwitchInto(battler, TRUE); @@ -3666,10 +3673,10 @@ static void HandleTurnActionSelectionState(void) *(gBattleStruct->monToSwitchIntoId + battler) = PARTY_SIZE; if (gBattleTypeFlags & BATTLE_TYPE_MULTI || (position & BIT_FLANK) == B_FLANK_LEFT - || gBattleStruct->absentBattlerFlags & gBitTable[GetBattlerAtPosition(BATTLE_PARTNER(position))] + || gBattleStruct->absentBattlerFlags & (1u << GetBattlerAtPosition(BATTLE_PARTNER(position))) || gBattleCommunication[GetBattlerAtPosition(BATTLE_PARTNER(position))] == STATE_WAIT_ACTION_CONFIRMED) { - if (gBattleStruct->absentBattlerFlags & gBitTable[battler]) + if (gBattleStruct->absentBattlerFlags & (1u << battler)) { gChosenActionByBattler[battler] = B_ACTION_NOTHING_FAINTED; if (!(gBattleTypeFlags & BATTLE_TYPE_MULTI)) @@ -3705,7 +3712,7 @@ static void HandleTurnActionSelectionState(void) } break; case STATE_WAIT_ACTION_CHOSEN: // Try to perform an action. - if (!(gBattleControllerExecFlags & ((gBitTable[battler]) | (0xF << 28) | (gBitTable[battler] << 4) | (gBitTable[battler] << 8) | (gBitTable[battler] << 12)))) + if (!(gBattleControllerExecFlags & (((1u << battler)) | (0xF << 28) | ((1u << battler) << 4) | ((1u << battler) << 8) | ((1u << battler) << 12)))) { RecordedBattle_SetBattlerAction(battler, gBattleResources->bufferB[battler][1]); gChosenActionByBattler[battler] = gBattleResources->bufferB[battler][1]; @@ -3748,7 +3755,7 @@ static void HandleTurnActionSelectionState(void) i); } - BtlController_EmitChooseMove(battler, BUFFER_A, (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) != 0, FALSE, &moveInfo); + BtlController_EmitChooseMove(battler, BUFFER_A, IsDoubleBattle() != 0, FALSE, &moveInfo); MarkBattlerForControllerExec(battler); } break; @@ -3764,7 +3771,7 @@ static void HandleTurnActionSelectionState(void) } if (((gBattleTypeFlags & (BATTLE_TYPE_LINK - | BATTLE_TYPE_FRONTIER + | BATTLE_TYPE_FRONTIER_NO_PYRAMID | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_RECORDED_LINK)) && !gTestRunnerEnabled) @@ -3786,7 +3793,8 @@ static void HandleTurnActionSelectionState(void) break; case B_ACTION_SWITCH: *(gBattleStruct->battlerPartyIndexes + battler) = gBattlerPartyIndexes[battler]; - if (!CanBattlerEscape(battler)) + if (gBattleTypeFlags & BATTLE_TYPE_ARENA + || !CanBattlerEscape(battler)) { BtlController_EmitChoosePokemon(battler, BUFFER_A, PARTY_ACTION_CANT_SWITCH, PARTY_SIZE, ABILITY_NONE, gBattleStruct->battlerPartyOrders[battler]); } @@ -3850,7 +3858,7 @@ static void HandleTurnActionSelectionState(void) RecordedBattle_ClearBattlerAction(GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler))), 3); } - gBattleStruct->gimmick.toActivate &= ~(gBitTable[BATTLE_PARTNER(GetBattlerPosition(battler))]); + gBattleStruct->gimmick.toActivate &= ~((1u << BATTLE_PARTNER(GetBattlerPosition(battler)))); BtlController_EmitEndBounceEffect(battler, BUFFER_A); MarkBattlerForControllerExec(battler); return; @@ -3883,7 +3891,7 @@ static void HandleTurnActionSelectionState(void) } break; case STATE_WAIT_ACTION_CASE_CHOSEN: - if (!(gBattleControllerExecFlags & ((gBitTable[battler]) | (0xF << 28) | (gBitTable[battler] << 4) | (gBitTable[battler] << 8) | (gBitTable[battler] << 12)))) + if (!(gBattleControllerExecFlags & (((1u << battler)) | (0xF << 28) | ((1u << battler) << 4) | ((1u << battler) << 8) | ((1u << battler) << 12)))) { switch (gChosenActionByBattler[battler]) { @@ -3921,8 +3929,12 @@ static void HandleTurnActionSelectionState(void) } else { - RecordedBattle_SetBattlerAction(battler, gBattleResources->bufferB[battler][2]); - RecordedBattle_SetBattlerAction(battler, gBattleResources->bufferB[battler][3]); + if (!(gBattleTypeFlags & BATTLE_TYPE_PALACE)) + { + RecordedBattle_SetBattlerAction(battler, gBattleResources->bufferB[battler][2]); + RecordedBattle_SetBattlerAction(battler, gBattleResources->bufferB[battler][3]); + } + // Get the chosen move position (and thus the chosen move) and target from the returned buffer. gBattleStruct->chosenMovePositions[battler] = gBattleResources->bufferB[battler][2] & ~RET_GIMMICK; gChosenMoveByBattler[battler] = gBattleMons[battler].moves[gBattleStruct->chosenMovePositions[battler]]; @@ -3930,7 +3942,7 @@ static void HandleTurnActionSelectionState(void) // Check to see if any gimmicks need to be prepared. if (gBattleResources->bufferB[battler][2] & RET_GIMMICK) - gBattleStruct->gimmick.toActivate |= gBitTable[battler]; + gBattleStruct->gimmick.toActivate |= 1u << battler; // Max Move check if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX || IsGimmickSelected(battler, GIMMICK_DYNAMAX)) @@ -4000,20 +4012,20 @@ static void HandleTurnActionSelectionState(void) } break; case STATE_WAIT_ACTION_CONFIRMED_STANDBY: - if (!(gBattleControllerExecFlags & ((gBitTable[battler]) + if (!(gBattleControllerExecFlags & ((1u << battler) | (0xF << 28) - | (gBitTable[battler] << 4) - | (gBitTable[battler] << 8) - | (gBitTable[battler] << 12)))) + | (1u << (battler + 4)) + | (1u << (battler + 8)) + | (1u << (battler + 12))))) { if (AllAtActionConfirmed()) i = TRUE; else i = FALSE; - if (((gBattleTypeFlags & BATTLE_TYPE_MULTI) || !(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) + if (((gBattleTypeFlags & BATTLE_TYPE_MULTI) || !IsDoubleBattle()) || (position & BIT_FLANK) != B_FLANK_LEFT - || (*(&gBattleStruct->absentBattlerFlags) & gBitTable[GetBattlerAtPosition(BATTLE_PARTNER(position))])) + || (*(&gBattleStruct->absentBattlerFlags) & (1u << GetBattlerAtPosition(BATTLE_PARTNER(position))))) { BtlController_EmitLinkStandbyMsg(battler, BUFFER_A, LINK_STANDBY_MSG_STOP_BOUNCE, i); } @@ -4026,7 +4038,7 @@ static void HandleTurnActionSelectionState(void) } break; case STATE_WAIT_ACTION_CONFIRMED: - if (!(gBattleControllerExecFlags & ((gBitTable[battler]) | (0xF << 28) | (gBitTable[battler] << 4) | (gBitTable[battler] << 8) | (gBitTable[battler] << 12)))) + if (!(gBattleControllerExecFlags & ((1u << battler) | (0xF << 28) | (1u << (battler + 4)) | (1u << (battler + 8)) | (1u << (battler + 12))))) { gBattleCommunication[ACTIONS_CONFIRMED_COUNT]++; } @@ -4040,7 +4052,7 @@ static void HandleTurnActionSelectionState(void) { gBattlerAttacker = battler; gBattlescriptCurrInstr = gSelectionBattleScripts[battler]; - if (!(gBattleControllerExecFlags & ((gBitTable[battler]) | (0xF << 28) | (gBitTable[battler] << 4) | (gBitTable[battler] << 8) | (gBitTable[battler] << 12)))) + if (!(gBattleControllerExecFlags & ((1u << battler) | (0xF << 28) | (1u << (battler + 4)) | (1u << (battler + 8)) | (1u << (battler + 12))))) { gBattleScriptingCommandsTable[gBattlescriptCurrInstr[0]](); } @@ -4048,37 +4060,37 @@ static void HandleTurnActionSelectionState(void) } break; case STATE_WAIT_SET_BEFORE_ACTION: - if (!(gBattleControllerExecFlags & ((gBitTable[battler]) | (0xF << 28) | (gBitTable[battler] << 4) | (gBitTable[battler] << 8) | (gBitTable[battler] << 12)))) + if (!(gBattleControllerExecFlags & ((1u << battler) | (0xF << 28) | (1u << (battler + 4)) | (1u << (battler + 8)) | (1u << (battler + 12))))) { gBattleCommunication[battler] = STATE_BEFORE_ACTION_CHOSEN; } break; - // case STATE_SELECTION_SCRIPT_MAY_RUN: - // if (*(gBattleStruct->selectionScriptFinished + battler)) - // { - // if (gBattleResources->bufferB[battler][1] == B_ACTION_NOTHING_FAINTED) - // { - // gHitMarker |= HITMARKER_RUN; - // gChosenActionByBattler[battler] = B_ACTION_RUN; - // gBattleCommunication[battler] = STATE_WAIT_ACTION_CONFIRMED_STANDBY; - // } - // else - // { - // RecordedBattle_ClearBattlerAction(battler, 1); - // gBattleCommunication[battler] = *(gBattleStruct->stateIdAfterSelScript + battler); - // } - // } - // else - // { - // gBattlerAttacker = battler; - // gBattlescriptCurrInstr = gSelectionBattleScripts[battler]; - // if (!(gBattleControllerExecFlags & ((gBitTable[battler]) | (0xF << 28) | (gBitTable[battler] << 4) | (gBitTable[battler] << 8) | (gBitTable[battler] << 12)))) - // { - // gBattleScriptingCommandsTable[gBattlescriptCurrInstr[0]](); - // } - // gSelectionBattleScripts[battler] = gBattlescriptCurrInstr; - // } - // break; + case STATE_SELECTION_SCRIPT_MAY_RUN: + if (*(gBattleStruct->selectionScriptFinished + battler)) + { + if (gBattleResources->bufferB[battler][1] == B_ACTION_NOTHING_FAINTED) + { + gHitMarker |= HITMARKER_RUN; + gChosenActionByBattler[battler] = B_ACTION_RUN; + gBattleCommunication[battler] = STATE_WAIT_ACTION_CONFIRMED_STANDBY; + } + else + { + RecordedBattle_ClearBattlerAction(battler, 1); + gBattleCommunication[battler] = *(gBattleStruct->stateIdAfterSelScript + battler); + } + } + else + { + gBattlerAttacker = battler; + gBattlescriptCurrInstr = gSelectionBattleScripts[battler]; + if (!(gBattleControllerExecFlags & ((1u << battler) | (0xF << 28) | (1u << (battler + 4)) | (1u << (battler + 8)) | (1u << (battler + 12))))) + { + gBattleScriptingCommandsTable[gBattlescriptCurrInstr[0]](); + } + gSelectionBattleScripts[battler] = gBattlescriptCurrInstr; + } + break; } } @@ -4158,7 +4170,6 @@ void SwapTurnOrder(u8 id1, u8 id2) u32 GetBattlerTotalSpeedStatArgs(u32 battler, u32 ability, u32 holdEffect) { u32 speed = gBattleMons[battler].speed; - u32 highestStat = GetHighestStatId(battler); // weather abilities if (WEATHER_HAS_EFFECT) @@ -4180,10 +4191,10 @@ u32 GetBattlerTotalSpeedStatArgs(u32 battler, u32 ability, u32 holdEffect) speed *= 2; else if (ability == ABILITY_SLOW_START && gDisableStructs[battler].slowStartTimer != 0) speed /= 2; - else if (ability == ABILITY_PROTOSYNTHESIS && gBattleWeather & B_WEATHER_SUN && highestStat == STAT_SPEED) - speed = (speed * 150) / 100; - else if (ability == ABILITY_QUARK_DRIVE && gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN && highestStat == STAT_SPEED) - speed = (speed * 150) / 100; + else if (ability == ABILITY_PROTOSYNTHESIS && (gBattleWeather & B_WEATHER_SUN || gBattleStruct->boosterEnergyActivates & (1u << battler))) + speed = (GetHighestStatId(battler) == STAT_SPEED) ? (speed * 150) / 100 : speed; + else if (ability == ABILITY_QUARK_DRIVE && (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gBattleStruct->boosterEnergyActivates & (1u << battler))) + speed = (GetHighestStatId(battler) == STAT_SPEED) ? (speed * 150) / 100 : speed; // stat stages speed *= gStatStageRatios[gBattleMons[battler].statStages[STAT_SPEED]][0]; @@ -4556,6 +4567,7 @@ static void TurnValuesCleanUp(bool8 var0) if (gDisableStructs[i].rechargeTimer == 0) gBattleMons[i].status2 &= ~STATUS2_RECHARGE; } + gBattleStruct->canPickupItem &= ~(1u << i); } if (gDisableStructs[i].substituteHP == 0) @@ -4596,10 +4608,10 @@ static bool32 TryDoGimmicksBeforeMoves(void) for (i = 0; i < gBattlersCount; i++) { // Search through each battler and activate their gimmick if they have one prepared. - if ((gBattleStruct->gimmick.toActivate & gBitTable[order[i]]) && !(gProtectStructs[order[i]].noValidMoves)) + if ((gBattleStruct->gimmick.toActivate & (1u << order[i])) && !(gProtectStructs[order[i]].noValidMoves)) { battler = gBattlerAttacker = gBattleScripting.battler = order[i]; - gBattleStruct->gimmick.toActivate &= ~(gBitTable[battler]); + gBattleStruct->gimmick.toActivate &= ~(1u << battler); if (gGimmicksInfo[gBattleStruct->gimmick.usableGimmick[battler]].ActivateGimmick != NULL) { gGimmicksInfo[gBattleStruct->gimmick.usableGimmick[battler]].ActivateGimmick(battler); @@ -4625,12 +4637,12 @@ static bool32 TryDoMoveEffectsBeforeMoves(void) SortBattlersBySpeed(battlers, FALSE); for (i = 0; i < gBattlersCount; i++) { - if (!(gBattleStruct->focusPunchBattlers & gBitTable[battlers[i]]) + if (!(gBattleStruct->focusPunchBattlers & (1u << battlers[i])) && !(gBattleMons[battlers[i]].status1 & STATUS1_SLEEP) && !(gDisableStructs[battlers[i]].truantCounter) && !(gProtectStructs[battlers[i]].noValidMoves)) { - gBattleStruct->focusPunchBattlers |= gBitTable[battlers[i]]; + gBattleStruct->focusPunchBattlers |= 1u << battlers[i]; gBattlerAttacker = battlers[i]; switch (gChosenMoveByBattler[gBattlerAttacker]) { @@ -4810,7 +4822,8 @@ static void HandleEndTurn_BattleWon(void) gBattlescriptCurrInstr = BattleScript_LinkBattleWonOrLost; gBattleOutcome &= ~B_OUTCOME_LINK_BATTLE_RAN; } - else if (gBattleTypeFlags & (BATTLE_TYPE_TRAINER_TOWER | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_BATTLE_TOWER)) + else if (gBattleTypeFlags & BATTLE_TYPE_TRAINER + && gBattleTypeFlags & (BATTLE_TYPE_FRONTIER | BATTLE_TYPE_TRAINER_HILL | BATTLE_TYPE_EREADER_TRAINER)) { BattleStopLowHpSound(); PlayBGM(MUS_VICTORY_TRAINER); @@ -4820,7 +4833,8 @@ static void HandleEndTurn_BattleWon(void) { BattleStopLowHpSound(); gBattlescriptCurrInstr = BattleScript_LocalTrainerBattleWon; - switch (gTrainers[gTrainerBattleOpponent_A].trainerClass) + + switch (GetTrainerClassFromId(gTrainerBattleOpponent_A)) { case TRAINER_CLASS_LEADER: case TRAINER_CLASS_CHAMPION: @@ -4840,6 +4854,7 @@ static void HandleEndTurn_BattleWon(void) { gBattlescriptCurrInstr = BattleScript_PayDayMoneyAndPickUpItems; } + gBattleMainFunc = HandleEndTurn_FinishBattle; } @@ -4870,6 +4885,7 @@ static void HandleEndTurn_BattleLost(void) } gBattlescriptCurrInstr = BattleScript_LocalBattleLost; } + gBattleMainFunc = HandleEndTurn_FinishBattle; } @@ -4894,8 +4910,10 @@ static void HandleEndTurn_RanFromBattle(void) static void HandleEndTurn_MonFled(void) { gCurrentActionFuncId = 0; + PREPARE_MON_NICK_BUFFER(gBattleTextBuff1, gBattlerAttacker, gBattlerPartyIndexes[gBattlerAttacker]); gBattlescriptCurrInstr = BattleScript_WildMonFled; + gBattleMainFunc = HandleEndTurn_FinishBattle; } @@ -5126,8 +5144,10 @@ bool32 TrySetAteType(u32 move, u32 battlerAtk, u32 attackerAbility) break; case EFFECT_HIDDEN_POWER: case EFFECT_WEATHER_BALL: - case EFFECT_CHANGE_TYPE_ON_ITEM: case EFFECT_NATURAL_GIFT: + case EFFECT_CHANGE_TYPE_ON_ITEM: + case EFFECT_REVELATION_DANCE: + case EFFECT_TERRAIN_PULSE: return FALSE; } @@ -5208,16 +5228,20 @@ void SetTypeBeforeUsingMove(u32 move, u32 battlerAtk) { gBattleStruct->dynamicMoveType = ItemId_GetSecondaryId(gBattleMons[battlerAtk].item) | F_DYNAMIC_TYPE_SET; } - else if (gMovesInfo[move].effect == EFFECT_REVELATION_DANCE) + else if (gMovesInfo[move].effect == EFFECT_REVELATION_DANCE && GetActiveGimmick(battlerAtk) != GIMMICK_Z_MOVE) { if (GetActiveGimmick(battlerAtk) == GIMMICK_TERA && GetBattlerTeraType(battlerAtk) != TYPE_STELLAR) gBattleStruct->dynamicMoveType = GetBattlerTeraType(battlerAtk); - else if (gBattleMons[battlerAtk].types[0] != TYPE_MYSTERY) + else if (gBattleMons[battlerAtk].types[0] != TYPE_MYSTERY && !(gBattleResources->flags->flags[battlerAtk] & RESOURCE_FLAG_ROOST && gBattleMons[battlerAtk].types[0] == TYPE_FLYING)) gBattleStruct->dynamicMoveType = gBattleMons[battlerAtk].types[0] | F_DYNAMIC_TYPE_SET; - else if (gBattleMons[battlerAtk].types[1] != TYPE_MYSTERY) + else if (gBattleMons[battlerAtk].types[1] != TYPE_MYSTERY && !(gBattleResources->flags->flags[battlerAtk] & RESOURCE_FLAG_ROOST && gBattleMons[battlerAtk].types[1] == TYPE_FLYING)) gBattleStruct->dynamicMoveType = gBattleMons[battlerAtk].types[1] | F_DYNAMIC_TYPE_SET; + else if (gBattleResources->flags->flags[battlerAtk] & RESOURCE_FLAG_ROOST) + gBattleStruct->dynamicMoveType = (B_ROOST_PURE_FLYING >= GEN_5 ? TYPE_NORMAL : TYPE_MYSTERY) | F_DYNAMIC_TYPE_SET; else if (gBattleMons[battlerAtk].types[2] != TYPE_MYSTERY) gBattleStruct->dynamicMoveType = gBattleMons[battlerAtk].types[2] | F_DYNAMIC_TYPE_SET; + else + gBattleStruct->dynamicMoveType = TYPE_MYSTERY | F_DYNAMIC_TYPE_SET; } else if (gMovesInfo[move].effect == EFFECT_RAGING_BULL && (gBattleMons[battlerAtk].species == SPECIES_TAUROS_PALDEAN_COMBAT_BREED diff --git a/src/battle_message.c b/src/battle_message.c index 2af7120e3..9c4e1c54b 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -973,6 +973,7 @@ const u8 gText_Unused_6F[] = _("6F"); const u8 gText_Unused_7F[] = _("7F"); const u8 gText_Unused_8F[] = _("8F"); static const u8 sText_PkmnTerastallizedInto[] = _("{B_ATK_NAME_WITH_PREFIX} terastallized\ninto the {B_BUFF1} type!"); +static const u8 sText_FickleBeamDoubled[] = _("{B_ATK_NAME_WITH_PREFIX} is going all\nout for this attack!"); const u8 *const gStatNamesTable[] = { @@ -994,7 +995,9 @@ const u8 *const gPokeblockWasTooXStringTable[] = { sText_PokeblockWasTooSour }; -const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT - BATTLESTRINGS_TABLE_START] = { +const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT - BATTLESTRINGS_TABLE_START] = +{ + [STRINGID_FICKLEBEAMDOUBLED - BATTLESTRINGS_TABLE_START] = sText_FickleBeamDoubled, [STRINGID_PKMNTERASTALLIZEDINTO - BATTLESTRINGS_TABLE_START] = sText_PkmnTerastallizedInto, [STRINGID_TIDYINGUPCOMPLETE - BATTLESTRINGS_TABLE_START] = sText_TidyingUpComplete, [STRINGID_SUPERSWEETAROMAWAFTS - BATTLESTRINGS_TABLE_START] = sText_SupersweetAromaWafts, diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 5f1d43f72..2ead564f3 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1077,7 +1077,7 @@ bool32 ProteanTryChangeType(u32 battler, u32 ability, u32 move, u32 moveType) bool32 ShouldTeraShellDistortTypeMatchups(u32 move, u32 battlerDef) { - if (!(gBattleStruct->distortedTypeMatchups & gBitTable[battlerDef]) + if (!(gBattleStruct->distortedTypeMatchups & (1u << battlerDef)) && GetBattlerAbility(battlerDef) == ABILITY_TERA_SHELL && gBattleMons[battlerDef].species == SPECIES_TERAPAGOS_TERASTAL && !IS_MOVE_STATUS(move) @@ -1146,7 +1146,7 @@ static void Cmd_attackcanceler(void) if (gSpecialStatuses[gBattlerAttacker].parentalBondState == PARENTAL_BOND_OFF && GetBattlerAbility(gBattlerAttacker) == ABILITY_PARENTAL_BOND && IsMoveAffectedByParentalBond(gCurrentMove, gBattlerAttacker) - && !(gAbsentBattlerFlags & gBitTable[gBattlerTarget]) + && !(gAbsentBattlerFlags & (1u << gBattlerTarget)) && GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE) { gSpecialStatuses[gBattlerAttacker].parentalBondState = PARENTAL_BOND_1ST_HIT; @@ -1268,9 +1268,8 @@ static void Cmd_attackcanceler(void) } // Z-moves and Max Moves bypass protection, but deal reduced damage (factored in AccumulateOtherModifiers) - if ((IsZMove(gCurrentMove) - || IsMaxMove(gCurrentMove)) - && IS_BATTLER_PROTECTED(gBattlerTarget)) + if ((IsZMove(gCurrentMove) || IsMaxMove(gCurrentMove)) + && IS_BATTLER_PROTECTED(gBattlerTarget)) { BattleScriptPush(cmd->nextInstr); gBattlescriptCurrInstr = BattleScript_CouldntFullyProtect; @@ -1396,14 +1395,14 @@ static bool32 AccuracyCalcHelper(u16 move) return 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 (GetBattlerAbility(gBattlerAttacker) == ABILITY_NO_GUARD && (move != MOVE_SKY_DROP || gBattleStruct->skyDropTargets[gBattlerTarget] == 0xFF)) + else if (GetBattlerAbility(gBattlerAttacker) == ABILITY_NO_GUARD && (gMovesInfo[move].effect != EFFECT_SKY_DROP || gBattleStruct->skyDropTargets[gBattlerTarget] == 0xFF)) { if (!JumpIfMoveFailed(7, move)) RecordAbilityBattle(gBattlerAttacker, ABILITY_NO_GUARD); return TRUE; } // 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 (GetBattlerAbility(gBattlerTarget) == ABILITY_NO_GUARD && (move != MOVE_SKY_DROP || gBattleStruct->skyDropTargets[gBattlerTarget] == 0xFF)) + else if (GetBattlerAbility(gBattlerTarget) == ABILITY_NO_GUARD && (gMovesInfo[move].effect != EFFECT_SKY_DROP || gBattleStruct->skyDropTargets[gBattlerTarget] == 0xFF)) { if (!JumpIfMoveFailed(7, move)) RecordAbilityBattle(gBattlerTarget, ABILITY_NO_GUARD); @@ -1671,7 +1670,7 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u return; } - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && + if (IsDoubleBattle() && (moveTarget == MOVE_TARGET_BOTH || moveTarget == MOVE_TARGET_FOES_AND_ALLY)) gBattleCommunication[MISS_TYPE] = B_MSG_AVOIDED_ATK; else @@ -1767,7 +1766,7 @@ static void Cmd_ppreduce(void) if (ShouldTeraShellDistortTypeMatchups(gCurrentMove, gBattlerTarget)) { - gBattleStruct->distortedTypeMatchups |= gBitTable[gBattlerTarget]; + gBattleStruct->distortedTypeMatchups |= 1u << gBattlerTarget; gBattlerAbility = gBattlerTarget; BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_TeraShellDistortingTypeMatchups; @@ -1908,7 +1907,7 @@ static void Cmd_adjustdamage(void) goto END; if (DoesDisguiseBlockMove(gBattlerTarget, gCurrentMove)) { - gBattleStruct->enduredDamage |= gBitTable[gBattlerTarget]; + gBattleStruct->enduredDamage |= 1u << gBattlerTarget; goto END; } if (GetBattlerAbility(gBattlerTarget) == ABILITY_ICE_FACE && IS_MOVE_PHYSICAL(gCurrentMove) && gBattleMons[gBattlerTarget].species == SPECIES_EISCUE) @@ -1962,7 +1961,7 @@ static void Cmd_adjustdamage(void) // Handle reducing the dmg to 1 hp. gBattleMoveDamage = gBattleMons[gBattlerTarget].hp - 1; - gBattleStruct->enduredDamage |= gBitTable[gBattlerTarget]; + gBattleStruct->enduredDamage |= 1u << gBattlerTarget; if (gProtectStructs[gBattlerTarget].endured) { @@ -1990,7 +1989,7 @@ END: gBattlescriptCurrInstr = cmd->nextInstr; if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) && gBattleMoveDamage >= 1) - gSpecialStatuses[gBattlerAttacker].damagedMons |= gBitTable[gBattlerTarget]; + gSpecialStatuses[gBattlerAttacker].damagedMons |= (1 << (gBattlerTarget)); // Check gems and damage reducing berries. if (gSpecialStatuses[gBattlerTarget].berryReduced @@ -2065,7 +2064,7 @@ static void Cmd_multihitresultmessage(void) if (gSpecialStatuses[gBattlerTarget].berryReduced && !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)) { - gBattleStruct->ateBerry[gBattlerTarget & BIT_SIDE] |= gBitTable[gBattlerPartyIndexes[gBattlerTarget]]; + gBattleStruct->ateBerry[gBattlerTarget & BIT_SIDE] |= 1u << gBattlerPartyIndexes[gBattlerTarget]; gSpecialStatuses[gBattlerTarget].berryReduced = FALSE; BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_PrintBerryReduceString; @@ -2086,7 +2085,7 @@ static void Cmd_attackanimation(void) && gCurrentMove != MOVE_SUBSTITUTE && gCurrentMove != MOVE_ALLY_SWITCH // In a wild double battle gotta use the teleport animation if two wild pokemon are alive. - && !(gCurrentMove == MOVE_TELEPORT && WILD_DOUBLE_BATTLE && GetBattlerSide(gBattlerAttacker) == B_SIDE_OPPONENT && IsBattlerAlive(BATTLE_PARTNER(gBattlerAttacker)))) + && !(gMovesInfo[gCurrentMove].effect == EFFECT_TELEPORT && WILD_DOUBLE_BATTLE && GetBattlerSide(gBattlerAttacker) == B_SIDE_OPPONENT && IsBattlerAlive(BATTLE_PARTNER(gBattlerAttacker)))) { BattleScriptPush(cmd->nextInstr); gBattlescriptCurrInstr = BattleScript_Pausex20; @@ -2418,7 +2417,7 @@ static void Cmd_resultmessage(void) if (gMoveResultFlags & MOVE_RESULT_MISSED && (!(gMoveResultFlags & MOVE_RESULT_DOESNT_AFFECT_FOE) || gBattleCommunication[MISS_TYPE] > B_MSG_AVOIDED_ATK)) { if (gBattleCommunication[MISS_TYPE] > B_MSG_AVOIDED_ATK) // Wonder Guard or Levitate - show the ability pop-up - CreateAbilityPopUp(gBattlerTarget, gBattleMons[gBattlerTarget].ability, (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) != 0); + CreateAbilityPopUp(gBattlerTarget, gBattleMons[gBattlerTarget].ability, (IsDoubleBattle()) != 0); stringId = gMissStringIds[gBattleCommunication[MISS_TYPE]]; gBattleCommunication[MSG_DISPLAY] = 1; } @@ -2526,7 +2525,7 @@ static void Cmd_resultmessage(void) if (gSpecialStatuses[gBattlerTarget].berryReduced && !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)) { - gBattleStruct->ateBerry[gBattlerTarget & BIT_SIDE] |= gBitTable[gBattlerPartyIndexes[gBattlerTarget]]; + gBattleStruct->ateBerry[gBattlerTarget & BIT_SIDE] |= 1u << gBattlerPartyIndexes[gBattlerTarget]; gSpecialStatuses[gBattlerTarget].berryReduced = FALSE; BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_PrintBerryReduceString; @@ -3412,7 +3411,7 @@ void SetMoveEffect(bool32 primary, bool32 certain) && GetBattlerAbility(BATTLE_PARTNER(gBattlerTarget)) != ABILITY_MAGIC_GUARD) { gBattleScripting.savedBattler = BATTLE_PARTNER(gBattlerTarget); - gBattleMoveDamage = gBattleMons[BATTLE_PARTNER(gBattlerTarget)].hp / 16; + gBattleMoveDamage = gBattleMons[BATTLE_PARTNER(gBattlerTarget)].maxHP / 16; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; gBattlescriptCurrInstr = BattleScript_MoveEffectFlameBurst; @@ -3451,7 +3450,7 @@ void SetMoveEffect(bool32 primary, bool32 certain) { bool32 byTwo = FALSE; - gBattleStruct->stolenStats[0] |= gBitTable[i]; + gBattleStruct->stolenStats[0] |= (1 << (i)); // Store by how many stages to raise the stat. gBattleStruct->stolenStats[i] = gBattleMons[gBattlerTarget].statStages[i] - DEFAULT_STAT_STAGE; while (gBattleMons[gBattlerAttacker].statStages[i] + gBattleStruct->stolenStats[i] > MAX_STAT_STAGE) @@ -3809,7 +3808,7 @@ static void Cmd_tryfaintmon(void) destinyBondBattler = gBattlerAttacker; faintScript = BattleScript_FaintTarget; } - if (!(gAbsentBattlerFlags & gBitTable[battler]) + if (!(gAbsentBattlerFlags & (1u << battler)) && !IsBattlerAlive(battler)) { gHitMarker |= HITMARKER_FAINTED(battler); @@ -3883,9 +3882,12 @@ static void Cmd_cleareffectsonfaint(void) { u32 battler = GetBattlerForBattleScript(cmd->battler); const u8 *clearDataResult = NULL; - gBattleMons[battler].status1 = 0; - BtlController_EmitSetMonData(battler, BUFFER_A, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[battler].status1), &gBattleMons[battler].status1); - MarkBattlerForControllerExec(battler); + if (!(gBattleTypeFlags & BATTLE_TYPE_ARENA) || !IsBattlerAlive(battler)) + { + gBattleMons[battler].status1 = 0; + BtlController_EmitSetMonData(battler, BUFFER_A, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[battler].status1), &gBattleMons[battler].status1); + MarkBattlerForControllerExec(battler); + } clearDataResult = FaintClearSetData(battler); // Effects like attractions, trapping, etc. if (clearDataResult) @@ -4055,7 +4057,7 @@ static bool32 BattleTypeAllowsExp(void) else if (gBattleTypeFlags & ( BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK - | BATTLE_TYPE_TRAINER_TOWER + | BATTLE_TYPE_TRAINER_HILL | BATTLE_TYPE_FRONTIER | BATTLE_TYPE_SAFARI | BATTLE_TYPE_BATTLE_TOWER @@ -4104,7 +4106,7 @@ static void Cmd_getexp(void) else { gBattleScripting.getexpState++; - gBattleStruct->givenExpMons |= gBitTable[gBattlerPartyIndexes[gBattlerFainted]]; + gBattleStruct->givenExpMons |= (1u << gBattlerPartyIndexes[gBattlerFainted]); } break; case 1: // calculate experience points to redistribute @@ -4121,25 +4123,25 @@ static void Cmd_getexp(void) { if (!IsValidForBattle(&gPlayerParty[i])) continue; - if (gBitTable[i] & sentInBits) + if ((1u << i) & sentInBits) viaSentIn++; holdEffect = GetMonHoldEffect(&gPlayerParty[i]); if (holdEffect == HOLD_EFFECT_EXP_SHARE || IsGen6ExpShareEnabled()) { - expShareBits |= gBitTable[i]; + expShareBits |= 1u << i; viaExpShare++; } } // Get order of mons getting exp: 1. all mons via sent in, 2. all mons via exp share for (i = 0; i < PARTY_SIZE; i++) { - if (gBitTable[i] & sentInBits) + if ((1u << i) & sentInBits) gBattleStruct->expGettersOrder[orderId++] = i; } for (i = 0; i < PARTY_SIZE; i++) { - if (!(gBitTable[i] & sentInBits) && gBitTable[i] & expShareBits) + if (!((1u << i) & sentInBits) && (1u << i) & expShareBits) gBattleStruct->expGettersOrder[orderId++] = i; } if (orderId < PARTY_SIZE) @@ -4191,7 +4193,7 @@ static void Cmd_getexp(void) case 2: // set exp value to the poke in expgetter_id and print message if (gBattleControllerExecFlags == 0) { - bool32 wasSentOut = ((gBattleStruct->expSentInMons & gBitTable[*expMonId]) != 0); + bool32 wasSentOut = (gBattleStruct->expSentInMons & (1u << *expMonId)) != 0; holdEffect = GetMonHoldEffect(&gPlayerParty[*expMonId]); if ((holdEffect != HOLD_EFFECT_EXP_SHARE && !wasSentOut && !IsGen6ExpShareEnabled()) @@ -4211,9 +4213,8 @@ static void Cmd_getexp(void) else { // Music change in a wild battle after fainting opposing pokemon. - if (!(gBattleTypeFlags & (BATTLE_TYPE_TRAINER - | BATTLE_TYPE_POKEDUDE)) // pokedude is handled in pokedude_controller - && (gBattleMons[0].hp || (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && gBattleMons[2].hp)) + if (!(gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_POKEDUDE)) // pokedude is handled in pokedude_controller + && (gBattleMons[0].hp || (IsDoubleBattle() && gBattleMons[2].hp)) && !IsBattlerAlive(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT)) && !IsBattlerAlive(GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT)) && !gBattleStruct->wildVictorySong) @@ -4264,13 +4265,13 @@ static void Cmd_getexp(void) } // get exp getter battler - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) { - if (gBattlerPartyIndexes[2] == *expMonId && !(gAbsentBattlerFlags & gBitTable[2])) + if (gBattlerPartyIndexes[2] == *expMonId && !(gAbsentBattlerFlags & 4)) gBattleStruct->expGetterBattlerId = 2; else { - if (!(gAbsentBattlerFlags & gBitTable[0])) + if (!(gAbsentBattlerFlags & 1)) gBattleStruct->expGetterBattlerId = 0; else gBattleStruct->expGetterBattlerId = 2; @@ -4336,7 +4337,7 @@ static void Cmd_getexp(void) PREPARE_BYTE_NUMBER_BUFFER(gBattleTextBuff2, 3, GetMonData(&gPlayerParty[*expMonId], MON_DATA_LEVEL)); BattleScriptPushCursor(); - gLeveledUpInBattle |= gBitTable[*expMonId]; + gLeveledUpInBattle |= 1 << *expMonId; gBattlescriptCurrInstr = BattleScript_LevelUp; gBattleMoveDamage = T1_READ_32(&gBattleResources->bufferB[expBattler][2]); AdjustFriendship(&gPlayerParty[*expMonId], FRIENDSHIP_EVENT_GROW_LEVEL); @@ -4344,7 +4345,7 @@ static void Cmd_getexp(void) // update battle mon structure after level up if (gBattlerPartyIndexes[0] == *expMonId && gBattleMons[0].hp) battler = 0; - else if (gBattlerPartyIndexes[2] == *expMonId && gBattleMons[2].hp && (gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) + else if (gBattlerPartyIndexes[2] == *expMonId && gBattleMons[2].hp && (IsDoubleBattle())) battler = 2; if (battler != 0xFF) @@ -4877,6 +4878,9 @@ static void Cmd_end(void) { CMD_ARGS(); + // if (gBattleTypeFlags & BATTLE_TYPE_ARENA) + // BattleArena_AddSkillPoints(gBattlerAttacker); + gMoveResultFlags = 0; gCurrentActionFuncId = B_ACTION_TRY_FINISH; } @@ -5195,7 +5199,7 @@ static bool32 TryKnockOffBattleScript(u32 battlerDef) } else { - gWishFutureKnock.knockedOffMons[side] |= gBitTable[gBattlerPartyIndexes[battlerDef]]; + gWishFutureKnock.knockedOffMons[side] |= 1u << gBattlerPartyIndexes[battlerDef]; } BattleScriptPushCursor(); @@ -5223,7 +5227,7 @@ static u32 GetNextTarget(u32 moveTarget, bool32 excludeCurrent) if (i != gBattlerAttacker && !(excludeCurrent && i == gBattlerTarget) && IsBattlerAlive(i) - && !(gBattleStruct->targetsDone[gBattlerAttacker] & gBitTable[i]) + && !(gBattleStruct->targetsDone[gBattlerAttacker] & (1u << i)) && (GetBattlerSide(i) != GetBattlerSide(gBattlerAttacker) || moveTarget == MOVE_TARGET_FOES_AND_ALLY)) break; } @@ -5670,10 +5674,12 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_UPDATE_LAST_MOVES: - if (gMoveResultFlags & (MOVE_RESULT_FAILED | MOVE_RESULT_DOESNT_AFFECT_FOE)) - gBattleStruct->lastMoveFailed |= gBitTable[gBattlerAttacker]; + if ((gMoveResultFlags & (MOVE_RESULT_FAILED | MOVE_RESULT_DOESNT_AFFECT_FOE)) + || (gBattleMons[gBattlerAttacker].status2 & (STATUS2_FLINCHED)) + || gProtectStructs[gBattlerAttacker].prlzImmobility) + gBattleStruct->lastMoveFailed |= 1u << gBattlerAttacker; else - gBattleStruct->lastMoveFailed &= ~(gBitTable[gBattlerAttacker]); + gBattleStruct->lastMoveFailed &= ~(1u << gBattlerAttacker); // Set ShellTrap to activate after the attacker's turn if target was hit by a physical move. if (gMovesInfo[gChosenMoveByBattler[gBattlerTarget]].effect == EFFECT_SHELL_TRAP @@ -5699,7 +5705,7 @@ static void Cmd_moveend(void) } if (!gSpecialStatuses[gBattlerAttacker].dancerUsedMove) { - gDisableStructs[gBattlerAttacker].usedMoves |= gBitTable[gCurrMovePos]; + gDisableStructs[gBattlerAttacker].usedMoves |= 1u << gCurrMovePos; gBattleStruct->lastMoveTarget[gBattlerAttacker] = gBattlerTarget; if (gHitMarker & HITMARKER_ATTACKSTRING_PRINTED) { @@ -5709,8 +5715,8 @@ static void Cmd_moveend(void) gBattleStruct->dynamax.lastUsedBaseMove = gBattleStruct->dynamax.baseMoves[gBattlerAttacker]; } } - if (!(gAbsentBattlerFlags & gBitTable[gBattlerAttacker]) - && !(gBattleStruct->absentBattlerFlags & gBitTable[gBattlerAttacker]) + if (!(gAbsentBattlerFlags & (1u << gBattlerAttacker)) + && !(gBattleStruct->absentBattlerFlags & (1u << gBattlerAttacker)) && gMovesInfo[originallyUsedMove].effect != EFFECT_BATON_PASS && gMovesInfo[originallyUsedMove].effect != EFFECT_HEALING_WISH) { @@ -5752,8 +5758,8 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_MIRROR_MOVE: // mirror move - if (!(gAbsentBattlerFlags & gBitTable[gBattlerAttacker]) - && !(gBattleStruct->absentBattlerFlags & gBitTable[gBattlerAttacker]) + if (!(gAbsentBattlerFlags & (1u << gBattlerAttacker)) + && !(gBattleStruct->absentBattlerFlags & (1u << gBattlerAttacker)) && !gMovesInfo[originallyUsedMove].mirrorMoveBanned && gHitMarker & HITMARKER_OBEYS && gBattlerAttacker != gBattlerTarget @@ -5773,9 +5779,9 @@ static void Cmd_moveend(void) && !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)) gProtectStructs[gBattlerAttacker].targetAffected = TRUE; - gBattleStruct->targetsDone[gBattlerAttacker] |= gBitTable[gBattlerTarget]; + gBattleStruct->targetsDone[gBattlerAttacker] |= 1u << gBattlerTarget; if (!(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) - && gBattleTypeFlags & BATTLE_TYPE_DOUBLE + && IsDoubleBattle() && !gProtectStructs[gBattlerAttacker].chargingTurn && (moveTarget == MOVE_TARGET_BOTH || moveTarget == MOVE_TARGET_FOES_AND_ALLY) @@ -5804,7 +5810,7 @@ static void Cmd_moveend(void) u8 originalBounceTarget = gBattlerAttacker; gBattleStruct->bouncedMoveIsUsed = FALSE; gBattlerAttacker = gBattleStruct->attackerBeforeBounce; - gBattleStruct->targetsDone[gBattlerAttacker] |= gBitTable[originalBounceTarget]; + gBattleStruct->targetsDone[gBattlerAttacker] |= 1u << originalBounceTarget; gBattleStruct->targetsDone[originalBounceTarget] = 0; nextTarget = GetNextTarget(moveTarget, FALSE); @@ -5907,7 +5913,7 @@ static void Cmd_moveend(void) && TARGET_TURN_DAMAGED && CanStealItem(gBattlerAttacker, gBattlerTarget, gBattleMons[gBattlerTarget].item) && !gSpecialStatuses[gBattlerAttacker].gemBoost // In base game, gems are consumed after magician would activate. - && !(gWishFutureKnock.knockedOffMons[GetBattlerSide(gBattlerTarget)] & gBitTable[gBattlerPartyIndexes[gBattlerTarget]]) + && !(gWishFutureKnock.knockedOffMons[GetBattlerSide(gBattlerTarget)] & (1u << gBattlerPartyIndexes[gBattlerTarget])) && !DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove) && !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) && (GetBattlerAbility(gBattlerTarget) != ABILITY_STICKY_HOLD || !IsBattlerAlive(gBattlerTarget))) @@ -5931,9 +5937,9 @@ static void Cmd_moveend(void) u32 holdEffect; holdEffect = GetBattlerHoldEffect(i, TRUE); if (holdEffect == HOLD_EFFECT_EJECT_BUTTON) - ejectButtonBattlers |= gBitTable[i]; + ejectButtonBattlers |= 1u << i; else if (holdEffect == HOLD_EFFECT_EJECT_PACK) - ejectPackBattlers |= gBitTable[i]; + ejectPackBattlers |= 1u << i; } if (ejectButtonBattlers || ejectPackBattlers) { @@ -5944,7 +5950,7 @@ static void Cmd_moveend(void) { u32 battler = battlers[i]; - if (battler != gBattlerAttacker && ejectButtonBattlers & gBitTable[battler]) + if (battler != gBattlerAttacker && ejectButtonBattlers & (1u << battler)) { if (TestIfSheerForceAffected(gBattlerAttacker, gCurrentMove)) // Apparently Sheer Force blocks Eject Button, but not Eject Pack continue; @@ -5953,7 +5959,7 @@ static void Cmd_moveend(void) if (!BATTLER_TURN_DAMAGED(battler)) continue; } - else if (ejectPackBattlers & gBitTable[battler]) + else if (ejectPackBattlers & (1u << battler)) { if (!gProtectStructs[battler].statFell || gProtectStructs[battler].disableEjectPack) continue; @@ -5975,7 +5981,7 @@ static void Cmd_moveend(void) gBattlescriptCurrInstr = BattleScript_MoveEnd; // Prevent user switch-in selection effect = TRUE; BattleScriptPushCursor(); - if (ejectButtonBattlers & gBitTable[battler]) + if (ejectButtonBattlers & (1u << battler)) { gBattlescriptCurrInstr = BattleScript_EjectButtonActivates; } @@ -6014,7 +6020,7 @@ static void Cmd_moveend(void) if (i == gBattlerAttacker) continue; if (GetBattlerHoldEffect(i, TRUE) == HOLD_EFFECT_RED_CARD) - redCardBattlers |= gBitTable[i]; + redCardBattlers |= (1u << i); } if (redCardBattlers && (gMovesInfo[gCurrentMove].effect != EFFECT_HIT_SWITCH_TARGET || gBattleStruct->hitSwitchTargetFailed) @@ -6031,7 +6037,7 @@ static void Cmd_moveend(void) u32 battler = battlers[i]; // Search for fastest hit pokemon with a red card // Attacker is the one to be switched out, battler is one with red card - if (redCardBattlers & gBitTable[battler] + if (redCardBattlers & (1u << battler) && IsBattlerAlive(battler) && !DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove) && BATTLER_TURN_DAMAGED(battler) @@ -6062,7 +6068,7 @@ static void Cmd_moveend(void) case MOVEEND_PICKPOCKET: if (IsBattlerAlive(gBattlerAttacker) && gBattleMons[gBattlerAttacker].item != ITEM_NONE // Attacker must be holding an item - && !(gWishFutureKnock.knockedOffMons[GetBattlerSide(gBattlerAttacker)] & gBitTable[gBattlerPartyIndexes[gBattlerAttacker]]) // But not knocked off + && !(gWishFutureKnock.knockedOffMons[GetBattlerSide(gBattlerAttacker)] & (1u << gBattlerPartyIndexes[gBattlerAttacker])) // But not knocked off && !(TestIfSheerForceAffected(gBattlerAttacker, gCurrentMove)) // Pickpocket doesn't activate for sheer force && IsMoveMakingContact(gCurrentMove, gBattlerAttacker) // Pickpocket requires contact && !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)) // Obviously attack needs to have worked @@ -6102,7 +6108,7 @@ static void Cmd_moveend(void) u32 battler, nextDancer = 0; bool32 turnOnHitmarker = FALSE; - for (battler = 0; battler < MAX_BATTLERS_COUNT; battler++) + for (battler = 0; battler < gBattlersCount; battler++) { if (gSpecialStatuses[battler].dancerUsedMove) { @@ -6112,9 +6118,8 @@ static void Cmd_moveend(void) } } - if (!(gBattleStruct->lastMoveFailed & gBitTable[gBattlerAttacker] - || (!gSpecialStatuses[gBattlerAttacker].dancerUsedMove - && gBattleStruct->bouncedMoveIsUsed))) + if (!(gMoveResultFlags & (MOVE_RESULT_FAILED | MOVE_RESULT_DOESNT_AFFECT_FOE) + || (!gSpecialStatuses[gBattlerAttacker].dancerUsedMove && gBattleStruct->bouncedMoveIsUsed))) { // Dance move succeeds // Set target for other Dancer mons; set bit so that mon cannot activate Dancer off of its own move if (!gSpecialStatuses[gBattlerAttacker].dancerUsedMove) @@ -6123,7 +6128,7 @@ static void Cmd_moveend(void) gBattleScripting.savedBattler |= (gBattlerAttacker << 4); gSpecialStatuses[gBattlerAttacker].dancerUsedMove = TRUE; } - for (battler = 0; battler < MAX_BATTLERS_COUNT; battler++) + for (battler = 0; battler < gBattlersCount; battler++) { if (GetBattlerAbility(battler) == ABILITY_DANCER && !gSpecialStatuses[battler].dancerUsedMove) { @@ -6247,6 +6252,7 @@ static void Cmd_moveend(void) gBattleStruct->enduredDamage = 0; gBattleStruct->additionalEffectsCounter = 0; gBattleStruct->poisonPuppeteerConfusion = FALSE; + gBattleStruct->fickleBeamBoosted = FALSE; if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE) SetActiveGimmick(gBattlerAttacker, GIMMICK_NONE); gBattleStruct->distortedTypeMatchups = 0; @@ -6306,7 +6312,7 @@ static void Cmd_getswitchedmondata(void) gBattlerPartyIndexes[battler] = gBattleStruct->monToSwitchIntoId[battler]; - BtlController_EmitGetMonData(battler, BUFFER_A, REQUEST_ALL_BATTLE, gBitTable[gBattlerPartyIndexes[battler]]); + BtlController_EmitGetMonData(battler, BUFFER_A, REQUEST_ALL_BATTLE, 1u << gBattlerPartyIndexes[battler]); MarkBattlerForControllerExec(battler); gBattlescriptCurrInstr = cmd->nextInstr; @@ -6352,7 +6358,7 @@ static void Cmd_switchindataupdate(void) if (i != PARTY_SIZE) { gBattlerPartyIndexes[battler] = gBattleStruct->monToSwitchIntoId[battler] = i; - BtlController_EmitGetMonData(battler, BUFFER_A, REQUEST_ALL_BATTLE, gBitTable[gBattlerPartyIndexes[battler]]); + BtlController_EmitGetMonData(battler, BUFFER_A, REQUEST_ALL_BATTLE, 1u << gBattlerPartyIndexes[battler]); MarkBattlerForControllerExec(battler); return; } @@ -6375,7 +6381,7 @@ static void Cmd_switchindataupdate(void) // check knocked off item i = GetBattlerSide(battler); - if (gWishFutureKnock.knockedOffMons[i] & gBitTable[gBattlerPartyIndexes[battler]]) + if (gWishFutureKnock.knockedOffMons[i] & (1u << gBattlerPartyIndexes[battler])) { gBattleMons[battler].item = ITEM_NONE; } @@ -6391,6 +6397,14 @@ static void Cmd_switchindataupdate(void) SwitchInClearSetData(battler); + // if (gBattleTypeFlags & BATTLE_TYPE_PALACE + // && gBattleMons[battler].maxHP / 2 >= gBattleMons[battler].hp + // && IsBattlerAlive(battler) + // && !(gBattleMons[battler].status1 & STATUS1_SLEEP)) + // { + // gBattleStruct->palaceFlags |= 1u << battler; + // } + gBattleScripting.battler = battler; PREPARE_MON_NICK_BUFFER(gBattleTextBuff1, battler, gBattlerPartyIndexes[battler]); @@ -6411,23 +6425,25 @@ static void Cmd_switchinanim(void) if (GetBattlerSide(battler) == B_SIDE_OPPONENT && !(gBattleTypeFlags & (BATTLE_TYPE_LINK - | BATTLE_TYPE_LEGENDARY + | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_RECORDED_LINK + | BATTLE_TYPE_TRAINER_HILL + | BATTLE_TYPE_FRONTIER + | BATTLE_TYPE_LEGENDARY | BATTLE_TYPE_OLD_MAN_TUTORIAL | BATTLE_TYPE_POKEDUDE - | BATTLE_TYPE_EREADER_TRAINER - | BATTLE_TYPE_GHOST - | BATTLE_TYPE_RECORDED_LINK - | BATTLE_TYPE_TRAINER_TOWER - | BATTLE_TYPE_FRONTIER))) + | BATTLE_TYPE_GHOST))) HandleSetPokedexFlag(SpeciesToNationalDexNum(gBattleMons[battler].species), FLAG_SET_SEEN, gBattleMons[battler].personality); - gAbsentBattlerFlags &= ~(gBitTable[battler]); + gAbsentBattlerFlags &= ~(1u << battler); BtlController_EmitSwitchInAnim(battler, BUFFER_A, gBattlerPartyIndexes[battler], cmd->dontClearSubstitute); MarkBattlerForControllerExec(battler); gBattlescriptCurrInstr = cmd->nextInstr; + + // if (gBattleTypeFlags & BATTLE_TYPE_ARENA) + // BattleArena_InitPoints(); } bool32 CanBattlerSwitch(u32 battler) @@ -6539,7 +6555,7 @@ bool32 CanBattlerSwitch(u32 battler) { battlerIn1 = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT); - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) battlerIn2 = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT); else battlerIn2 = battlerIn1; @@ -6551,7 +6567,7 @@ bool32 CanBattlerSwitch(u32 battler) // Check if attacker side has mon to switch into battlerIn1 = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT); - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) battlerIn2 = GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT); else battlerIn2 = battlerIn1; @@ -6598,7 +6614,7 @@ static void ChooseMonToSendOut(u32 battler, u8 slotId) { gBattleStruct->battlerPartyIndexes[battler] = gBattlerPartyIndexes[battler]; gBattleStruct->monToSwitchIntoId[battler] = PARTY_SIZE; - gBattleStruct->field_93 &= ~(gBitTable[battler]); + gBattleStruct->field_93 &= ~(1u << battler); BtlController_EmitChoosePokemon(battler, BUFFER_A, PARTY_ACTION_SEND_OUT, slotId, ABILITY_NONE, gBattleStruct->battlerPartyOrders[battler]); MarkBattlerForControllerExec(battler); @@ -6615,7 +6631,7 @@ static void Cmd_openpartyscreen(void) if (cmd->battler == BS_FAINTED_MULTIPLE_1) { - if ((gBattleTypeFlags & BATTLE_TYPE_MULTI) || !(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) + if ((gBattleTypeFlags & BATTLE_TYPE_MULTI) || !(IsDoubleBattle())) { for (battler = 0; battler < gBattlersCount; battler++) { @@ -6623,7 +6639,7 @@ static void Cmd_openpartyscreen(void) { if (HasNoMonsToSwitch(battler, PARTY_SIZE, PARTY_SIZE)) { - gAbsentBattlerFlags |= gBitTable[battler]; + gAbsentBattlerFlags |= 1u << battler; gHitMarker &= ~HITMARKER_FAINTED(battler); BtlController_EmitLinkStandbyMsg(battler, BUFFER_A, LINK_STANDBY_MSG_ONLY, FALSE); MarkBattlerForControllerExec(battler); @@ -6641,18 +6657,18 @@ static void Cmd_openpartyscreen(void) } } } - else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + else if (IsDoubleBattle()) { bool8 hasReplacement_0, hasReplacement_1, hasReplacement_2, hasReplacement_3; hitmarkerFaintBits = gHitMarker >> 28; - if (gBitTable[0] & hitmarkerFaintBits) + if (1u & hitmarkerFaintBits) { battler = 0; if (HasNoMonsToSwitch(battler, PARTY_SIZE, PARTY_SIZE)) { - gAbsentBattlerFlags |= gBitTable[battler]; + gAbsentBattlerFlags |= 1u << battler; gHitMarker &= ~HITMARKER_FAINTED(battler); BtlController_EmitCantSwitch(battler, BUFFER_A); MarkBattlerForControllerExec(battler); @@ -6669,12 +6685,12 @@ static void Cmd_openpartyscreen(void) flags |= 1; } } - if (gBitTable[2] & hitmarkerFaintBits && !(gBitTable[0] & hitmarkerFaintBits)) + if (4u & hitmarkerFaintBits && !(1u & hitmarkerFaintBits)) { battler = 2; if (HasNoMonsToSwitch(battler, PARTY_SIZE, PARTY_SIZE)) { - gAbsentBattlerFlags |= gBitTable[battler]; + gAbsentBattlerFlags |= 1u << battler; gHitMarker &= ~HITMARKER_FAINTED(battler); BtlController_EmitCantSwitch(battler, BUFFER_A); MarkBattlerForControllerExec(battler); @@ -6690,12 +6706,12 @@ static void Cmd_openpartyscreen(void) MarkBattlerForControllerExec(battler); } } - if (gBitTable[1] & hitmarkerFaintBits) + if (2 & hitmarkerFaintBits) { battler = 1; if (HasNoMonsToSwitch(battler, PARTY_SIZE, PARTY_SIZE)) { - gAbsentBattlerFlags |= gBitTable[battler]; + gAbsentBattlerFlags |= 1u << battler; gHitMarker &= ~HITMARKER_FAINTED(battler); BtlController_EmitCantSwitch(battler, BUFFER_A); MarkBattlerForControllerExec(battler); @@ -6712,12 +6728,12 @@ static void Cmd_openpartyscreen(void) flags |= 2; } } - if (gBitTable[3] & hitmarkerFaintBits && !(gBitTable[1] & hitmarkerFaintBits)) + if (8 & hitmarkerFaintBits && !(2 & hitmarkerFaintBits)) { battler = 3; if (HasNoMonsToSwitch(battler, PARTY_SIZE, PARTY_SIZE)) { - gAbsentBattlerFlags |= gBitTable[battler]; + gAbsentBattlerFlags |= 1u << battler; gHitMarker &= ~HITMARKER_FAINTED(battler); BtlController_EmitCantSwitch(battler, BUFFER_A); MarkBattlerForControllerExec(battler); @@ -6740,7 +6756,7 @@ static void Cmd_openpartyscreen(void) hasReplacement_2 = gSpecialStatuses[2].faintedHasReplacement; if (!hasReplacement_2 && hitmarkerFaintBits != 0) { - if (gAbsentBattlerFlags & gBitTable[0]) + if (gAbsentBattlerFlags & 1) battler = 2; else battler = 0; @@ -6756,7 +6772,7 @@ static void Cmd_openpartyscreen(void) hasReplacement_3 = gSpecialStatuses[3].faintedHasReplacement; if (!hasReplacement_3 && hitmarkerFaintBits != 0) { - if (gAbsentBattlerFlags & gBitTable[1]) + if (gAbsentBattlerFlags & 2) battler = 3; else battler = 1; @@ -6772,15 +6788,15 @@ static void Cmd_openpartyscreen(void) { if (!(gBattleTypeFlags & BATTLE_TYPE_MULTI)) { - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) { hitmarkerFaintBits = gHitMarker >> 28; - if (gBitTable[2] & hitmarkerFaintBits && gBitTable[0] & hitmarkerFaintBits) + if (4 & hitmarkerFaintBits && 1 & hitmarkerFaintBits) { battler = 2; if (HasNoMonsToSwitch(battler, gBattleResources->bufferB[0][1], PARTY_SIZE)) { - gAbsentBattlerFlags |= gBitTable[battler]; + gAbsentBattlerFlags |= 1u << battler; gHitMarker &= ~HITMARKER_FAINTED(battler); BtlController_EmitCantSwitch(battler, BUFFER_A); MarkBattlerForControllerExec(battler); @@ -6791,12 +6807,12 @@ static void Cmd_openpartyscreen(void) gSpecialStatuses[battler].faintedHasReplacement = TRUE; } } - if (gBitTable[3] & hitmarkerFaintBits && hitmarkerFaintBits & gBitTable[1]) + if (8u & hitmarkerFaintBits && hitmarkerFaintBits & 2u) { battler = 3; if (HasNoMonsToSwitch(battler, gBattleResources->bufferB[1][1], PARTY_SIZE)) { - gAbsentBattlerFlags |= gBitTable[battler]; + gAbsentBattlerFlags |= 1u << battler; gHitMarker &= ~HITMARKER_FAINTED(battler); BtlController_EmitCantSwitch(battler, BUFFER_A); MarkBattlerForControllerExec(battler); @@ -6824,7 +6840,7 @@ static void Cmd_openpartyscreen(void) hitmarkerFaintBits = gHitMarker >> 28; gBattlerFainted = 0; - while (!(gBitTable[gBattlerFainted] & hitmarkerFaintBits) + while (!((1u << gBattlerFainted) & hitmarkerFaintBits) && gBattlerFainted < gBattlersCount) gBattlerFainted++; @@ -6845,7 +6861,7 @@ static void Cmd_openpartyscreen(void) } else if (HasNoMonsToSwitch(battler, PARTY_SIZE, PARTY_SIZE)) { - gAbsentBattlerFlags |= gBitTable[battler]; + gAbsentBattlerFlags |= 1u << battler; gHitMarker &= ~HITMARKER_FAINTED(battler); gBattlescriptCurrInstr = failInstr; } @@ -6853,7 +6869,7 @@ static void Cmd_openpartyscreen(void) { *(gBattleStruct->battlerPartyIndexes + battler) = gBattlerPartyIndexes[battler]; *(gBattleStruct->monToSwitchIntoId + battler) = PARTY_SIZE; - gBattleStruct->field_93 &= ~(gBitTable[battler]); + gBattleStruct->field_93 &= ~(1u << battler); BtlController_EmitChoosePokemon(battler, BUFFER_A, hitmarkerFaintBits, *(gBattleStruct->monToSwitchIntoId + BATTLE_PARTNER(battler)), ABILITY_NONE, gBattleStruct->battlerPartyOrders[battler]); MarkBattlerForControllerExec(battler); @@ -6877,7 +6893,7 @@ static void Cmd_openpartyscreen(void) else { u32 battlerOpposite = GetBattlerAtPosition(BATTLE_OPPOSITE(GetBattlerPosition(battler))); - if (gAbsentBattlerFlags & gBitTable[battlerOpposite]) + if (gAbsentBattlerFlags & (1u << battlerOpposite)) battlerOpposite ^= BIT_FLANK; // Make sure we're checking a valid battler. In edge case scenarios - battler could be absent and battlerOpposite would become a non-existent one softlocking the game. @@ -6909,10 +6925,10 @@ static void Cmd_switchhandleorder(void) if (gBattleResources->bufferB[i][0] == CONTROLLER_CHOSENMONRETURNVALUE) { *(gBattleStruct->monToSwitchIntoId + i) = gBattleResources->bufferB[i][1]; - if (!(gBattleStruct->field_93 & gBitTable[i])) + if (!(gBattleStruct->field_93 & (1u << i))) { RecordedBattle_SetBattlerAction(i, gBattleResources->bufferB[i][1]); - gBattleStruct->field_93 |= gBitTable[i]; + gBattleStruct->field_93 |= 1u << i; } } } @@ -6922,10 +6938,10 @@ static void Cmd_switchhandleorder(void) SwitchPartyOrder(battler); break; case 2: - if (!(gBattleStruct->field_93 & gBitTable[battler])) + if (!(gBattleStruct->field_93 & (1u << battler))) { RecordedBattle_SetBattlerAction(battler, gBattleResources->bufferB[battler][1]); - gBattleStruct->field_93 |= gBitTable[battler]; + gBattleStruct->field_93 |= 1u << battler; } // fall through case 3: @@ -6993,7 +7009,7 @@ static void UpdateSentMonFlags(u32 battler) gSpecialStatuses[battler].faintedHasReplacement = FALSE; if (!BattlerHasAi(battler)) - gBattleStruct->appearedInBattle |= gBitTable[gBattlerPartyIndexes[battler]]; + gBattleStruct->appearedInBattle |= 1u << gBattlerPartyIndexes[battler]; } static bool32 DoSwitchInEffectsForBattler(u32 battler) @@ -7010,20 +7026,20 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler) } // Healing Wish activates before hazards. // Starting from Gen8 - it heals only pokemon which can be healed. In gens 5,6,7 the effect activates anyways. - else if (((gBattleStruct->storedHealingWish & gBitTable[battler]) || (gBattleStruct->storedLunarDance & gBitTable[battler])) + else if (((gBattleStruct->storedHealingWish & (1u << battler)) || (gBattleStruct->storedLunarDance & (1u << battler))) && (gBattleMons[battler].hp != gBattleMons[battler].maxHP || gBattleMons[battler].status1 != 0 || B_HEALING_WISH_SWITCH < GEN_8)) { - if (gBattleStruct->storedHealingWish & gBitTable[battler]) + if (gBattleStruct->storedHealingWish & (1u << battler)) { BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_HealingWishActivates; - gBattleStruct->storedHealingWish &= ~(gBitTable[battler]); + gBattleStruct->storedHealingWish &= ~(1u << battler); } else // Lunar Dance { BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_LunarDanceActivates; - gBattleStruct->storedLunarDance &= ~(gBitTable[battler]); + gBattleStruct->storedLunarDance &= ~(1u << battler); } } else if (!(gDisableStructs[battler].spikesDone) @@ -7130,19 +7146,32 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler) gDisableStructs[battler].truantSwitchInHack = 0; - for (i = 0; i < gBattlersCount; i++) - { - if (i != battler - && GetBattlerAbility(i) == ABILITY_TRACE - && AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, i, 0, 0, 0)) - return TRUE; - } - if (DoSwitchInAbilities(battler) || ItemBattleEffects(ITEMEFFECT_ON_SWITCH_IN, battler, FALSE)) return TRUE; else if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, 0, 0, 0)) return TRUE; + for (i = 0; i < gBattlersCount; i++) + { + if (i == battler) + continue; + + switch (GetBattlerAbility(i)) + { + case ABILITY_TRACE: + if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, i, 0, 0, 0)) + return TRUE; + break; + case ABILITY_FORECAST: + case ABILITY_FLOWER_GIFT: + case ABILITY_ICE_FACE: + case ABILITY_PROTOSYNTHESIS: + if (AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, i, 0, 0, 0)) + return TRUE; + break; + } + } + gDisableStructs[battler].stickyWebDone = FALSE; gDisableStructs[battler].spikesDone = FALSE; gDisableStructs[battler].toxicSpikesDone = FALSE; @@ -7157,7 +7186,7 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler) gBattleStruct->hpOnSwitchout[GetBattlerSide(i)] = gBattleMons[i].hp; } - gBattleStruct->forcedSwitch &= ~(gBitTable[battler]); + gBattleStruct->forcedSwitch &= ~(1u << battler); return FALSE; } @@ -7173,7 +7202,7 @@ static void Cmd_switchineffects(void) { // Multiple mons fainted and are being switched-in. Their abilities/hazards will play according to speed ties. case BS_FAINTED_MULTIPLE_1: // Saves the battlers. - gBattleStruct->multipleSwitchInBattlers |= gBitTable[battler]; + gBattleStruct->multipleSwitchInBattlers |= 1 << battler; UpdateSentMonFlags(battler); // Increment fainted battler. @@ -7182,7 +7211,7 @@ static void Cmd_switchineffects(void) gBattlerFainted++; if (gBattlerFainted >= gBattlersCount) break; - if (gHitMarker & HITMARKER_FAINTED(gBattlerFainted) && !(gAbsentBattlerFlags & gBitTable[gBattlerFainted])) + if (gHitMarker & HITMARKER_FAINTED(gBattlerFainted) && !(gAbsentBattlerFlags & (1u << gBattlerFainted))) break; } while (1); @@ -7202,7 +7231,7 @@ static void Cmd_switchineffects(void) for (; gBattleStruct->multipleSwitchInCursor < gBattlersCount; gBattleStruct->multipleSwitchInCursor++) { gBattlerFainted = gBattleStruct->multipleSwitchInSortedBattlers[gBattleStruct->multipleSwitchInCursor]; - if (gBattleStruct->multipleSwitchInBattlers & gBitTable[gBattlerFainted]) + if (gBattleStruct->multipleSwitchInBattlers & (1 << (gBattlerFainted))) { if (DoSwitchInEffectsForBattler(gBattlerFainted)) return; @@ -7316,7 +7345,7 @@ static void Cmd_handlelearnnewmove(void) { GiveMoveToBattleMon(&gBattleMons[battler], learnMove); } - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) { battler = GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT); if (gBattlerPartyIndexes[battler] == monId @@ -7341,22 +7370,22 @@ static void Cmd_yesnoboxlearnmove(void) BattlePutTextOnWindow(gText_BattleYesNoChoice, B_WIN_YESNO); gBattleScripting.learnMoveState++; gBattleCommunication[CURSOR_POSITION] = 0; - BattleCreateYesNoCursorAt(); + BattleCreateYesNoCursorAt(0); break; case 1: if (JOY_NEW(DPAD_UP) && gBattleCommunication[CURSOR_POSITION] != 0) { PlaySE(SE_SELECT); - BattleDestroyYesNoCursorAt(); + BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]); gBattleCommunication[CURSOR_POSITION] = 0; - BattleCreateYesNoCursorAt(); + BattleCreateYesNoCursorAt(0); } if (JOY_NEW(DPAD_DOWN) && gBattleCommunication[CURSOR_POSITION] == 0) { PlaySE(SE_SELECT); - BattleDestroyYesNoCursorAt(); + BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]); gBattleCommunication[CURSOR_POSITION] = 1; - BattleCreateYesNoCursorAt(); + BattleCreateYesNoCursorAt(1); } if (JOY_NEW(A_BUTTON)) { @@ -7422,7 +7451,7 @@ static void Cmd_yesnoboxlearnmove(void) RemoveBattleMonPPBonus(&gBattleMons[0], movePosition); SetBattleMonMoveSlot(&gBattleMons[0], gMoveToLearn, movePosition); } - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE + if (IsDoubleBattle() && gBattlerPartyIndexes[2] == gBattleStruct->expGetterMonId && MOVE_IS_PERMANENT(2, movePosition)) { @@ -7457,22 +7486,22 @@ static void Cmd_yesnoboxstoplearningmove(void) BattlePutTextOnWindow(gText_BattleYesNoChoice, B_WIN_YESNO); gBattleScripting.learnMoveState++; gBattleCommunication[CURSOR_POSITION] = 0; - BattleCreateYesNoCursorAt(); + BattleCreateYesNoCursorAt(0); break; case 1: if (JOY_NEW(DPAD_UP) && gBattleCommunication[CURSOR_POSITION] != 0) { PlaySE(SE_SELECT); - BattleDestroyYesNoCursorAt(); + BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]); gBattleCommunication[CURSOR_POSITION] = 0; - BattleCreateYesNoCursorAt(); + BattleCreateYesNoCursorAt(0); } if (JOY_NEW(DPAD_DOWN) && gBattleCommunication[CURSOR_POSITION] == 0) { PlaySE(SE_SELECT); - BattleDestroyYesNoCursorAt(); + BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]); gBattleCommunication[CURSOR_POSITION] = 1; - BattleCreateYesNoCursorAt(); + BattleCreateYesNoCursorAt(1); } if (JOY_NEW(A_BUTTON)) { @@ -7536,7 +7565,7 @@ static u32 GetTrainerMoneyToGive(u16 trainerId) if (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) moneyReward = 4 * lastMonLevel * gBattleStruct->moneyMultiplier * trainerMoney; - else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + else if (IsDoubleBattle()) moneyReward = 4 * lastMonLevel * gBattleStruct->moneyMultiplier * 2 * trainerMoney; else moneyReward = 4 * lastMonLevel * gBattleStruct->moneyMultiplier * trainerMoney; @@ -7764,22 +7793,22 @@ static void Cmd_yesnobox(void) BattlePutTextOnWindow(gText_BattleYesNoChoice, B_WIN_YESNO); gBattleCommunication[0]++; gBattleCommunication[CURSOR_POSITION] = 0; - BattleCreateYesNoCursorAt(); + BattleCreateYesNoCursorAt(0); break; case 1: if (JOY_NEW(DPAD_UP) && gBattleCommunication[CURSOR_POSITION] != 0) { PlaySE(SE_SELECT); - BattleDestroyYesNoCursorAt(); + BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]); gBattleCommunication[CURSOR_POSITION] = 0; - BattleCreateYesNoCursorAt(); + BattleCreateYesNoCursorAt(0); } if (JOY_NEW(DPAD_DOWN) && gBattleCommunication[CURSOR_POSITION] == 0) { PlaySE(SE_SELECT); - BattleDestroyYesNoCursorAt(); + BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]); gBattleCommunication[CURSOR_POSITION] = 1; - BattleCreateYesNoCursorAt(); + BattleCreateYesNoCursorAt(1); } if (JOY_NEW(B_BUTTON)) { @@ -7831,7 +7860,7 @@ static bool32 TryCheekPouch(u32 battler, u32 itemId) if (ItemId_GetPocket(itemId) == POCKET_BERRIES && GetBattlerAbility(battler) == ABILITY_CHEEK_POUCH && !(gStatuses3[battler] & STATUS3_HEAL_BLOCK) - && gBattleStruct->ateBerry[GetBattlerSide(battler)] & gBitTable[gBattlerPartyIndexes[battler]] + && gBattleStruct->ateBerry[GetBattlerSide(battler)] & (1u << gBattlerPartyIndexes[battler]) && !BATTLER_MAX_HP(battler)) { gBattleMoveDamage = GetNonDynamaxMaxHP(battler) / 3; @@ -7909,6 +7938,7 @@ static void Cmd_removeitem(void) gBattleStruct->usedHeldItems[gBattlerPartyIndexes[battler]][GetBattlerSide(battler)] = itemId; // Remember if switched out gBattleMons[battler].item = ITEM_NONE; + gBattleStruct->canPickupItem |= (1u << battler); CheckSetUnburden(battler); BtlController_EmitSetMonData(battler, BUFFER_A, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[battler].item), &gBattleMons[battler].item); @@ -8202,7 +8232,7 @@ bool32 IsMonGettingExpSentOut(void) { if (gBattlerPartyIndexes[0] == gBattleStruct->expGetterMonId) return TRUE; - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && gBattlerPartyIndexes[2] == gBattleStruct->expGetterMonId) + if (IsDoubleBattle() && gBattlerPartyIndexes[2] == gBattleStruct->expGetterMonId) return TRUE; return FALSE; @@ -8275,7 +8305,7 @@ static void Cmd_hpthresholds(void) { CMD_ARGS(u8 battler); - if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) + if (!(IsDoubleBattle())) { u32 battler = GetBattlerForBattleScript(cmd->battler); u32 opposingBattler = BATTLE_OPPOSITE(battler); @@ -8301,7 +8331,7 @@ static void Cmd_hpthresholds2(void) { CMD_ARGS(u8 battler); - if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) + if (!(IsDoubleBattle())) { u32 battler = GetBattlerForBattleScript(cmd->battler); u32 opposingBattler = BATTLE_OPPOSITE(battler); @@ -8364,7 +8394,7 @@ bool32 CanUseLastResort(u8 battler) { if (gBattleMons[battler].moves[i] != MOVE_NONE) knownMovesCount++; - if (i != gCurrMovePos && gDisableStructs[battler].usedMoves & gBitTable[i]) // Increment used move count for all moves except current Last Resort. + if (i != gCurrMovePos && gDisableStructs[battler].usedMoves & (1u << i)) // Increment used move count for all moves except current Last Resort. usedMovesCount++; } @@ -8455,11 +8485,14 @@ static bool32 TryDefogClear(u32 battlerAtk, bool32 clear) DEFOG_CLEAR(SIDE_STATUS_AURORA_VEIL, auroraVeilTimer, BattleScript_SideStatusWoreOffReturn, MOVE_AURORA_VEIL); DEFOG_CLEAR(SIDE_STATUS_SAFEGUARD, safeguardTimer, BattleScript_SideStatusWoreOffReturn, MOVE_SAFEGUARD); } - DEFOG_CLEAR(SIDE_STATUS_SPIKES, spikesAmount, BattleScript_SpikesDefog, 0); - DEFOG_CLEAR(SIDE_STATUS_STEALTH_ROCK, stealthRockAmount, BattleScript_StealthRockDefog, 0); - DEFOG_CLEAR(SIDE_STATUS_TOXIC_SPIKES, toxicSpikesAmount, BattleScript_ToxicSpikesDefog, 0); - DEFOG_CLEAR(SIDE_STATUS_STICKY_WEB, stickyWebAmount, BattleScript_StickyWebDefog, 0); - DEFOG_CLEAR(SIDE_STATUS_STEELSURGE, steelsurgeAmount, BattleScript_SteelsurgeDefog, 0); + if (B_DEFOG_EFFECT_CLEARING >= GEN_6) + { + DEFOG_CLEAR(SIDE_STATUS_SPIKES, spikesAmount, BattleScript_SpikesDefog, 0); + DEFOG_CLEAR(SIDE_STATUS_STEALTH_ROCK, stealthRockAmount, BattleScript_StealthRockDefog, 0); + DEFOG_CLEAR(SIDE_STATUS_TOXIC_SPIKES, toxicSpikesAmount, BattleScript_ToxicSpikesDefog, 0); + DEFOG_CLEAR(SIDE_STATUS_STICKY_WEB, stickyWebAmount, BattleScript_StickyWebDefog, 0); + DEFOG_CLEAR(SIDE_STATUS_STEELSURGE, steelsurgeAmount, BattleScript_SteelsurgeDefog, 0); + } if (gBattleWeather & B_WEATHER_FOG) { gBattleWeather &= ~B_WEATHER_FOG; @@ -8467,7 +8500,7 @@ static bool32 TryDefogClear(u32 battlerAtk, bool32 clear) gBattlescriptCurrInstr = BattleScript_FogEnded_Ret; return TRUE; } - if (B_DEFOG_CLEARS_TERRAIN >= GEN_8 && (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)) + if (B_DEFOG_EFFECT_CLEARING >= GEN_8 && (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)) { RemoveAllTerrains(); BattleScriptPushCursor(); @@ -8693,7 +8726,7 @@ static void HandleScriptMegaPrimalBurst(u32 caseId, u32 battler, u32 type) PREPARE_SPECIES_BUFFER(gBattleTextBuff1, gBattleMons[battler].species); - BtlController_EmitSetMonData(battler, BUFFER_A, REQUEST_SPECIES_BATTLE, gBitTable[gBattlerPartyIndexes[battler]], sizeof(gBattleMons[battler].species), &gBattleMons[battler].species); + BtlController_EmitSetMonData(battler, BUFFER_A, REQUEST_SPECIES_BATTLE, 1u << gBattlerPartyIndexes[battler], sizeof(gBattleMons[battler].species), &gBattleMons[battler].species); MarkBattlerForControllerExec(battler); } // Update healthbox and elevation and play cry. @@ -9011,9 +9044,9 @@ static void Cmd_various(void) // Raise stats for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++) { - if (gBattleStruct->stolenStats[0] & gBitTable[i]) + if (gBattleStruct->stolenStats[0] & (1u << i)) { - gBattleStruct->stolenStats[0] &= ~(gBitTable[i]); + gBattleStruct->stolenStats[0] &= ~(1u << i); SET_STATCHANGER(i, gBattleStruct->stolenStats[i], FALSE); if (ChangeStatBuffs(GET_STAT_BUFF_VALUE_WITH_SIGN(gBattleScripting.statChanger), i, MOVE_EFFECT_CERTAIN | MOVE_EFFECT_AFFECTS_USER, NULL) == STAT_CHANGE_WORKED) { @@ -9038,7 +9071,7 @@ static void Cmd_various(void) for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++) { if (CompareStat(battler, i, MAX_STAT_STAGE, CMP_LESS_THAN)) - bits |= gBitTable[i]; + bits |= 1u << i; } if (bits) { @@ -9046,7 +9079,7 @@ static void Cmd_various(void) do { statId = (Random() % (NUM_BATTLE_STATS - 1)) + 1; - } while (!(bits & gBitTable[statId])); + } while (!(bits & (1u << statId))); SET_STATCHANGER(statId, 2, FALSE); gBattlescriptCurrInstr = cmd->nextInstr; @@ -9201,7 +9234,7 @@ static void Cmd_various(void) case VARIOUS_SET_ALREADY_STATUS_MOVE_ATTEMPT: { VARIOUS_ARGS(); - gBattleStruct->alreadyStatusedMoveAttempt |= gBitTable[battler]; + gBattleStruct->alreadyStatusedMoveAttempt |= 1u << battler; break; } case VARIOUS_PALACE_TRY_ESCAPE_STATUS: // unused from pokeemerald @@ -9216,7 +9249,7 @@ static void Cmd_various(void) // and its partner is still alive. if (GetBattlerSide(battler) == B_SIDE_OPPONENT && IsBattlerAlive(BATTLE_PARTNER(battler))) { - gAbsentBattlerFlags |= gBitTable[battler]; + gAbsentBattlerFlags |= 1u << battler; gHitMarker |= HITMARKER_FAINTED(battler); gBattleMons[battler].hp = 0; SetMonData(&gEnemyParty[gBattlerPartyIndexes[battler]], MON_DATA_HP, &gBattleMons[battler].hp); @@ -9591,7 +9624,7 @@ static void Cmd_various(void) if (!gBattleTextBuff1) PREPARE_SPECIES_BUFFER(gBattleTextBuff1, gBattleMons[battler].species); */ - BtlController_EmitSetMonData(battler, BUFFER_A, REQUEST_SPECIES_BATTLE, gBitTable[gBattlerPartyIndexes[battler]], sizeof(gBattleMons[battler].species), &gBattleMons[battler].species); + BtlController_EmitSetMonData(battler, BUFFER_A, REQUEST_SPECIES_BATTLE, 1u << gBattlerPartyIndexes[battler], sizeof(gBattleMons[battler].species), &gBattleMons[battler].species); MarkBattlerForControllerExec(battler); } // Change stats. @@ -9696,7 +9729,7 @@ static void Cmd_various(void) case VARIOUS_ABILITY_POPUP: { VARIOUS_ARGS(); - CreateAbilityPopUp(battler, gBattleMons[battler].ability, (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) != 0); + CreateAbilityPopUp(battler, gBattleMons[battler].ability, (IsDoubleBattle()) != 0); break; } case VARIOUS_UPDATE_ABILITY_POPUP: @@ -9795,7 +9828,7 @@ static void Cmd_various(void) || gBattleMons[gBattlerTarget].item != ITEM_NONE || !CanBattlerGetOrLoseItem(gBattlerAttacker, gBattleMons[gBattlerAttacker].item) || !CanBattlerGetOrLoseItem(gBattlerTarget, gBattleMons[gBattlerAttacker].item) - || gWishFutureKnock.knockedOffMons[GetBattlerSide(gBattlerTarget)] & gBitTable[gBattlerPartyIndexes[gBattlerTarget]]) + || gWishFutureKnock.knockedOffMons[GetBattlerSide(gBattlerTarget)] & (1u << gBattlerPartyIndexes[gBattlerTarget])) { gBattlescriptCurrInstr = cmd->failInstr; } @@ -9890,7 +9923,7 @@ static void Cmd_various(void) gSideTimers[GetBattlerSide(battler)].auroraVeilTimer = 5; gSideTimers[GetBattlerSide(battler)].auroraVeilBattlerId = battler; - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && CountAliveMonsInBattle(BATTLE_ALIVE_SIDE, gBattlerAttacker) == 2) + if (IsDoubleBattle() && CountAliveMonsInBattle(BATTLE_ALIVE_SIDE, gBattlerAttacker) == 2) gBattleCommunication[MULTISTRING_CHOOSER] = 5; else gBattleCommunication[MULTISTRING_CHOOSER] = 5; @@ -10040,7 +10073,7 @@ static void Cmd_various(void) ConvertIntToDecimalStringN(gBattleTextBuff2, ppToDeduct, STR_CONV_MODE_LEFT_ALIGN, 1); PREPARE_BYTE_NUMBER_BUFFER(gBattleTextBuff2, 1, ppToDeduct) gBattleMons[battler].pp[i] -= ppToDeduct; - if (!(gDisableStructs[battler].mimickedMoves & gBitTable[i]) + if (!(gDisableStructs[battler].mimickedMoves & (1u << i)) && !(gBattleMons[battler].status2 & STATUS2_TRANSFORMED)) { BtlController_EmitSetMonData(battler, BUFFER_A, REQUEST_PPMOVE1_BATTLE + i, 0, sizeof(gBattleMons[battler].pp[i]), &gBattleMons[battler].pp[i]); @@ -10066,7 +10099,7 @@ static void Cmd_various(void) case VARIOUS_JUMP_IF_TEAM_HEALTHY: { VARIOUS_ARGS(const u8 *jumpInstr); - if ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE) && IsBattlerAlive(BATTLE_PARTNER(battler))) + if ((IsDoubleBattle()) && IsBattlerAlive(BATTLE_PARTNER(battler))) { u8 partner = BATTLE_PARTNER(battler); if ((gBattleMons[battler].hp == gBattleMons[battler].maxHP && !(gBattleMons[battler].status1 & STATUS1_ANY)) @@ -10279,9 +10312,9 @@ static void Cmd_various(void) if (gBattleMons[gBattlerAttacker].species == SPECIES_GRENINJA_BATTLE_BOND && HasAttackerFaintedTarget() && CalculateBattlerPartyCount(gBattlerTarget) > 1 - && !(gBattleStruct->battleBondTransformed[GetBattlerSide(gBattlerAttacker)] & gBitTable[gBattlerPartyIndexes[gBattlerAttacker]])) + && !(gBattleStruct->battleBondTransformed[GetBattlerSide(gBattlerAttacker)] & (1u << gBattlerPartyIndexes[gBattlerAttacker]))) { - gBattleStruct->battleBondTransformed[GetBattlerSide(gBattlerAttacker)] |= gBitTable[gBattlerPartyIndexes[gBattlerAttacker]]; + gBattleStruct->battleBondTransformed[GetBattlerSide(gBattlerAttacker)] |= 1u << gBattlerPartyIndexes[gBattlerAttacker]; PREPARE_SPECIES_BUFFER(gBattleTextBuff1, gBattleMons[gBattlerAttacker].species); gBattleStruct->changedSpecies[GetBattlerSide(gBattlerAttacker)][gBattlerPartyIndexes[gBattlerAttacker]] = gBattleMons[gBattlerAttacker].species; gBattleMons[gBattlerAttacker].species = SPECIES_GRENINJA_ASH; @@ -10303,7 +10336,7 @@ static void Cmd_various(void) if (cmd->fromBattler) gLastUsedItem = gBattleMons[battler].item; - gBattleStruct->ateBerry[battler & BIT_SIDE] |= gBitTable[gBattlerPartyIndexes[battler]]; + gBattleStruct->ateBerry[battler & BIT_SIDE] |= 1u << gBattlerPartyIndexes[battler]; gBattleScripting.battler = gEffectBattler = gBattlerTarget = battler; // Cover all berry effect battler cases. e.g. ChangeStatBuffs uses target ID if (ItemBattleEffects(ITEMEFFECT_USE_LAST_ITEM, battler, FALSE)) return; @@ -10643,9 +10676,9 @@ static void Cmd_various(void) { VARIOUS_ARGS(); if (gCurrentMove == MOVE_LUNAR_DANCE) - gBattleStruct->storedLunarDance |= gBitTable[battler]; + gBattleStruct->storedLunarDance |= 1u << battler; else - gBattleStruct->storedHealingWish |= gBitTable[battler]; + gBattleStruct->storedHealingWish |= 1u << battler; break; } case VARIOUS_HIT_SWITCH_TARGET_FAILED: @@ -10674,12 +10707,12 @@ static void Cmd_various(void) struct Pokemon *party = GetSideParty(side); u16 hp = GetMonData(&party[gSelectedMonPartyId], MON_DATA_MAX_HP) / 2; - BtlController_EmitSetMonData(gBattlerAttacker, BUFFER_A, REQUEST_HP_BATTLE, gBitTable[gSelectedMonPartyId], sizeof(hp), &hp); + BtlController_EmitSetMonData(gBattlerAttacker, BUFFER_A, REQUEST_HP_BATTLE, 1u << gSelectedMonPartyId, sizeof(hp), &hp); MarkBattlerForControllerExec(gBattlerAttacker); PREPARE_SPECIES_BUFFER(gBattleTextBuff1, GetMonData(&party[gSelectedMonPartyId], MON_DATA_SPECIES)); // If an on-field battler is revived, it needs to be sent out again. - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && + if (IsDoubleBattle() && gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerAttacker)] == gSelectedMonPartyId) { gBattleScripting.battler = BATTLE_PARTNER(gBattlerAttacker); @@ -11071,7 +11104,7 @@ static void Cmd_setreflect(void) gSideTimers[GetBattlerSide(gBattlerAttacker)].reflectTimer = 5; gSideTimers[GetBattlerSide(gBattlerAttacker)].reflectBattlerId = gBattlerAttacker; - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && CountAliveMonsInBattle(BATTLE_ALIVE_SIDE, gBattlerAttacker) == 2) + if (IsDoubleBattle() && CountAliveMonsInBattle(BATTLE_ALIVE_SIDE, gBattlerAttacker) == 2) gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_REFLECT_DOUBLE; else gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_REFLECT_SINGLE; @@ -11935,7 +11968,7 @@ static void Cmd_forcerandomswitch(void) battler2PartyId = gBattlerPartyIndexes[gBattlerTarget]; battler1PartyId = gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerTarget)]; } - else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + else if (IsDoubleBattle()) { firstMonId = 0; lastMonId = PARTY_SIZE; @@ -11970,7 +12003,7 @@ static void Cmd_forcerandomswitch(void) { *(gBattleStruct->battlerPartyIndexes + gBattlerTarget) = gBattlerPartyIndexes[gBattlerTarget]; gBattlescriptCurrInstr = BattleScript_RoarSuccessSwitch; - gBattleStruct->forcedSwitch |= gBitTable[gBattlerTarget]; + gBattleStruct->forcedSwitch |= 1u << gBattlerTarget; *(gBattleStruct->monToSwitchIntoId + gBattlerTarget) = validMons[RandomUniform(RNG_FORCE_RANDOM_SWITCH, 0, validMonsCount - 1)]; if (!IsMultiBattle()) @@ -12133,7 +12166,7 @@ static void Cmd_setlightscreen(void) gSideTimers[GetBattlerSide(gBattlerAttacker)].lightscreenTimer = 5; gSideTimers[GetBattlerSide(gBattlerAttacker)].lightscreenBattlerId = gBattlerAttacker; - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && CountAliveMonsInBattle(BATTLE_ALIVE_SIDE, gBattlerAttacker) == 2) + if (IsDoubleBattle() && CountAliveMonsInBattle(BATTLE_ALIVE_SIDE, gBattlerAttacker) == 2) gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_LIGHTSCREEN_DOUBLE; else gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_LIGHTSCREEN_SINGLE; @@ -12298,15 +12331,15 @@ static void Cmd_updatestatusicon(void) else { battler = gBattlerAttacker; - if (!(gAbsentBattlerFlags & gBitTable[battler])) + if (!(gAbsentBattlerFlags & (1u << battler))) { BtlController_EmitStatusIconUpdate(battler, BUFFER_A, gBattleMons[battler].status1, gBattleMons[battler].status2); MarkBattlerForControllerExec(battler); } - if ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) + if ((IsDoubleBattle())) { battler = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerAttacker))); - if (!(gAbsentBattlerFlags & gBitTable[battler])) + if (!(gAbsentBattlerFlags & (1u << battler))) { BtlController_EmitStatusIconUpdate(battler, BUFFER_A, gBattleMons[battler].status1, gBattleMons[battler].status2); MarkBattlerForControllerExec(battler); @@ -12340,8 +12373,8 @@ static void Cmd_setfocusenergy(void) CMD_ARGS(u8 battler); u8 battler = GetBattlerForBattleScript(cmd->battler); - if ((gMovesInfo[gCurrentMove].effect == EFFECT_DRAGON_CHEER && (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE) || (gAbsentBattlerFlags & gBitTable[battler]))) - || gBattleMons[battler].status2 & STATUS2_FOCUS_ENERGY_ANY) + if ((gMovesInfo[gCurrentMove].effect == EFFECT_DRAGON_CHEER && (!(IsDoubleBattle()) || (gAbsentBattlerFlags & (1u << battler)))) + || gBattleMons[battler].status2 & STATUS2_FOCUS_ENERGY_ANY) { gMoveResultFlags |= MOVE_RESULT_FAILED; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FOCUS_ENERGY_FAILED; @@ -12484,7 +12517,7 @@ static void Cmd_mimicattackcopy(void) PREPARE_MOVE_BUFFER(gBattleTextBuff1, gLastMoves[gBattlerTarget]) - gDisableStructs[gBattlerAttacker].mimickedMoves |= gBitTable[gCurrMovePos]; + gDisableStructs[gBattlerAttacker].mimickedMoves |= 1u << gCurrMovePos; gBattlescriptCurrInstr = cmd->nextInstr; } else @@ -12725,7 +12758,7 @@ static void Cmd_settypetorandomresistance(void) { gBattlescriptCurrInstr = cmd->failInstr; } - else if (gLastHitByType[gBattlerAttacker] == TYPE_STELLAR) + else if (gLastHitByType[gBattlerAttacker] == TYPE_STELLAR || gLastHitByType[gBattlerAttacker] == TYPE_MYSTERY) { gBattlescriptCurrInstr = cmd->failInstr; } @@ -12740,7 +12773,7 @@ static void Cmd_settypetorandomresistance(void) { case UQ_4_12(0): case UQ_4_12(0.5): - resistTypes |= gBitTable[i]; + resistTypes |= 1u << i; break; } } @@ -12748,11 +12781,11 @@ static void Cmd_settypetorandomresistance(void) while (resistTypes != 0) { i = Random() % NUMBER_OF_MON_TYPES; - if (resistTypes & gBitTable[i]) + if (resistTypes & (1u << i)) { if (IS_BATTLER_OF_TYPE(gBattlerAttacker, i)) { - resistTypes &= ~(gBitTable[i]); // Type resists, but the user is already of this type. + resistTypes &= ~(1u << i); // Type resists, but the user is already of this type. } else { @@ -12842,7 +12875,7 @@ static void Cmd_trychoosesleeptalkmove(void) if (gMovesInfo[gBattleMons[gBattlerAttacker].moves[i]].sleepTalkBanned || gBattleMoveEffects[gMovesInfo[gBattleMons[gBattlerAttacker].moves[i]].effect].twoTurnEffect) { - unusableMovesBits |= gBitTable[i]; + unusableMovesBits |= (1 << (i)); } } @@ -12854,11 +12887,11 @@ static void Cmd_trychoosesleeptalkmove(void) else // at least one move can be chosen { // Set Sleep Talk as used move, so it works with Last Resort. - gDisableStructs[gBattlerAttacker].usedMoves |= gBitTable[gCurrMovePos]; + gDisableStructs[gBattlerAttacker].usedMoves |= 1u << gCurrMovePos; do { movePosition = MOD(Random(), MAX_MON_MOVES); - } while ((gBitTable[movePosition] & unusableMovesBits)); + } while ((1u << movePosition) & unusableMovesBits); if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE && !IS_MOVE_STATUS(gBattleMons[gBattlerAttacker].moves[movePosition])) { @@ -12969,7 +13002,7 @@ static void Cmd_tryspiteppreduce(void) gBattleMons[gBattlerTarget].pp[i] -= ppToDeduct; // if (MOVE_IS_PERMANENT(gBattlerTarget, i)), but backwards - if (!(gDisableStructs[gBattlerTarget].mimickedMoves & gBitTable[i]) + if (!(gDisableStructs[gBattlerTarget].mimickedMoves & (1u << i)) && !(gBattleMons[gBattlerTarget].status2 & STATUS2_TRANSFORMED)) { BtlController_EmitSetMonData(gBattlerTarget, BUFFER_A, REQUEST_PPMOVE1_BATTLE + i, 0, sizeof(gBattleMons[gBattlerTarget].pp[i]), &gBattleMons[gBattlerTarget].pp[i]); @@ -13083,8 +13116,8 @@ static void Cmd_healpartystatus(void) gBattleMons[gBattlerAttacker].status1 = 0; gBattleMons[gBattlerAttacker].status2 &= ~STATUS2_NIGHTMARE; - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE - && !(gAbsentBattlerFlags & gBitTable[partner])) + if (IsDoubleBattle() + && !(gAbsentBattlerFlags & (1u <failInstr; } @@ -14451,7 +14486,7 @@ static void Cmd_switchoutabilities(void) case ABILITY_NATURAL_CURE: gBattleMons[battler].status1 = 0; BtlController_EmitSetMonData(battler, BUFFER_A, REQUEST_STATUS_BATTLE, - gBitTable[*(gBattleStruct->battlerPartyIndexes + battler)], + 1u << *(gBattleStruct->battlerPartyIndexes + battler), sizeof(gBattleMons[battler].status1), &gBattleMons[battler].status1); MarkBattlerForControllerExec(battler); @@ -14462,7 +14497,7 @@ static void Cmd_switchoutabilities(void) if (gBattleMoveDamage > gBattleMons[battler].maxHP) gBattleMoveDamage = gBattleMons[battler].maxHP; BtlController_EmitSetMonData(battler, BUFFER_A, REQUEST_HP_BATTLE, - gBitTable[*(gBattleStruct->battlerPartyIndexes + battler)], + 1u << *(gBattleStruct->battlerPartyIndexes + battler), sizeof(gBattleMoveDamage), &gBattleMoveDamage); MarkBattlerForControllerExec(battler); @@ -14665,7 +14700,10 @@ static void Cmd_tryrecycleitem(void) u16 *usedHeldItem; - usedHeldItem = &gBattleStruct->usedHeldItems[gBattlerPartyIndexes[gBattlerAttacker]][GetBattlerSide(gBattlerAttacker)]; + if (gCurrentMove == MOVE_NONE && GetBattlerAbility(gBattlerAttacker) == ABILITY_PICKUP) + usedHeldItem = &gBattleStruct->usedHeldItems[gBattlerPartyIndexes[gBattlerTarget]][GetBattlerSide(gBattlerTarget)]; + else + usedHeldItem = &gBattleStruct->usedHeldItems[gBattlerPartyIndexes[gBattlerAttacker]][GetBattlerSide(gBattlerAttacker)]; if (*usedHeldItem != ITEM_NONE && gBattleMons[gBattlerAttacker].item == ITEM_NONE) { gLastUsedItem = *usedHeldItem; @@ -14734,8 +14772,8 @@ static void Cmd_pursuitdoubles(void) u32 battler = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerAttacker))); - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE - && !(gAbsentBattlerFlags & gBitTable[battler]) + if (IsDoubleBattle() + && !(gAbsentBattlerFlags & (1u << battler)) && gChosenActionByBattler[battler] == B_ACTION_USE_MOVE && gMovesInfo[gChosenMoveByBattler[battler]].effect == EFFECT_PURSUIT) { @@ -15295,22 +15333,23 @@ void HandleBattleWindow(u8 xStart, u8 yStart, u8 xEnd, u8 yEnd, u8 flags) CopyBgTilemapBufferToVram(1); } -void BattleCreateYesNoCursorAt(void) +void BattleCreateYesNoCursorAt(u8 cursorPosition) { u16 src[2]; src[0] = 1; src[1] = 2; - CopyToBgTilemapBufferRect_ChangePalette(0, src, 0x18, 9 + (2 * gBattleCommunication[1]), 1, 2, 0x11); + + CopyToBgTilemapBufferRect_ChangePalette(0, src, 0x18, 9 + (2 * cursorPosition), 1, 2, 0x11); CopyBgTilemapBufferToVram(0); } -void BattleDestroyYesNoCursorAt(void) +void BattleDestroyYesNoCursorAt(u8 cursorPosition) { u16 src[2]; src[0] = 32; src[1] = 32; - CopyToBgTilemapBufferRect_ChangePalette(0, src, 0x18, 9 + (2 * gBattleCommunication[1]), 1, 2, 0x11); + CopyToBgTilemapBufferRect_ChangePalette(0, src, 0x18, 9 + (2 * cursorPosition), 1, 2, 0x11); CopyBgTilemapBufferToVram(0); } @@ -15325,22 +15364,22 @@ static void Cmd_trygivecaughtmonnick(void) BattlePutTextOnWindow(gText_BattleYesNoChoice, B_WIN_YESNO); gBattleCommunication[MULTIUSE_STATE]++; gBattleCommunication[CURSOR_POSITION] = 0; - BattleCreateYesNoCursorAt(); + BattleCreateYesNoCursorAt(0); break; case 1: if (JOY_NEW(DPAD_UP) && gBattleCommunication[CURSOR_POSITION] != 0) { PlaySE(SE_SELECT); - BattleDestroyYesNoCursorAt(); + BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]); gBattleCommunication[CURSOR_POSITION] = 0; - BattleCreateYesNoCursorAt(); + BattleCreateYesNoCursorAt(0); } if (JOY_NEW(DPAD_DOWN) && gBattleCommunication[CURSOR_POSITION] == 0) { PlaySE(SE_SELECT); - BattleDestroyYesNoCursorAt(); + BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]); gBattleCommunication[CURSOR_POSITION] = 1; - BattleCreateYesNoCursorAt(); + BattleCreateYesNoCursorAt(1); } if (JOY_NEW(A_BUTTON)) { @@ -15486,17 +15525,26 @@ static void Cmd_swapstatstages(void) gBattlescriptCurrInstr = cmd->nextInstr; } +static u16 *GetBattlerStat(struct BattlePokemon *battler, u32 stat) +{ + switch (stat) + { + case STAT_ATK: return &battler->attack; + case STAT_DEF: return &battler->defense; + case STAT_SPATK: return &battler->spAttack; + case STAT_SPDEF: return &battler->spDefense; + default: return NULL; + } +} + static void Cmd_averagestats(void) { CMD_ARGS(u8 stat); - u8 stat = cmd->stat; - u16 atkStat = *(u16 *)((&gBattleMons[gBattlerAttacker].attack) + (stat - 1)); - u16 defStat = *(u16 *)((&gBattleMons[gBattlerTarget].attack) + (stat - 1)); - u16 average = (atkStat + defStat) / 2; - - *(u16 *)((&gBattleMons[gBattlerAttacker].attack) + (stat - 1)) = average; - *(u16 *)((&gBattleMons[gBattlerTarget].attack) + (stat - 1)) = average; + u16 *stat1 = GetBattlerStat(&gBattleMons[gBattlerAttacker], cmd->stat); + u16 *stat2 = GetBattlerStat(&gBattleMons[gBattlerTarget], cmd->stat); + u16 avg = (*stat1 + *stat2) / 2; + *stat1 = *stat2 = avg; gBattlescriptCurrInstr = cmd->nextInstr; } @@ -15741,7 +15789,7 @@ bool32 IsMoveAffectedByParentalBond(u32 move, u32 battler) && gMovesInfo[move].strikeCount < 2 && gMovesInfo[move].effect != EFFECT_MULTI_HIT) { - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) { switch (GetBattlerMoveTargetType(battler, move)) { @@ -15858,7 +15906,7 @@ void BS_SetZEffect(void) static void TryUpdateRoundTurnOrder(void) { - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) { u32 i; u32 j = 0; @@ -15996,8 +16044,7 @@ void BS_ItemRestoreHP(void) // Check if the recipient is an active battler. if (gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[gBattlerAttacker]) battler = gBattlerAttacker; - else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE - && gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerAttacker)]) + else if (IsDoubleBattle() && gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerAttacker)]) battler = BATTLE_PARTNER(gBattlerAttacker); // Get amount to heal. @@ -16034,9 +16081,9 @@ void BS_ItemRestoreHP(void) SetMonData(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], MON_DATA_HP, &hp); // Revived battlers on the field need to be brought back. - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && battler != MAX_BATTLERS_COUNT) + if (IsDoubleBattle() && battler != MAX_BATTLERS_COUNT) { - gAbsentBattlerFlags &= ~gBitTable[battler]; + gAbsentBattlerFlags &= ~(1u << battler); gBattleMons[battler].hp = hp; gBattleCommunication[MULTIUSE_STATE] = TRUE; } @@ -16060,7 +16107,7 @@ void BS_ItemCureStatus(void) previousStatus2 = gBattleMons[battler].status2; gBattleMons[gBattlerAttacker].status2 &= ~GetItemStatus2Mask(gLastUsedItem); } - else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE + else if (IsDoubleBattle() && gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerAttacker)]) { battler = BATTLE_PARTNER(gBattlerAttacker); @@ -16125,7 +16172,7 @@ void BS_ItemRestorePP(void) // Check if the recipient is an active battler. if (gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[gBattlerAttacker]) battler = gBattlerAttacker; - else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE + else if (IsDoubleBattle() && gBattleStruct->itemPartyIndex[gBattlerAttacker] == gBattlerPartyIndexes[BATTLE_PARTNER(gBattlerAttacker)]) battler = BATTLE_PARTNER(gBattlerAttacker); @@ -16456,7 +16503,7 @@ void BS_SetPledge(void) gBattleCommunication[MSG_DISPLAY] = 0; } else if ((gChosenActionByBattler[partner] == B_ACTION_USE_MOVE) - && gBattleTypeFlags & BATTLE_TYPE_DOUBLE + && IsDoubleBattle() && IsBattlerAlive(partner) && gCurrentMove != partnerMove && gMovesInfo[partnerMove].effect == EFFECT_PLEDGE) @@ -16731,7 +16778,7 @@ static void TryUpdateEvolutionTracker(u32 evolutionMethod, u32 upAmount, u16 use && !(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_RECORDED_LINK - | BATTLE_TYPE_TRAINER_TOWER + | BATTLE_TYPE_TRAINER_HILL | BATTLE_TYPE_FRONTIER))) { const struct Evolution *evolutions = GetSpeciesEvolutions(gBattleMons[gBattlerAttacker].species); @@ -16954,3 +17001,19 @@ void BS_DamageToQuarterTargetHP(void) gBattlescriptCurrInstr = cmd->nextInstr; } + +void BS_FickleBeamDamageCalculation(void) +{ + NATIVE_ARGS(); + gBattleStruct->fickleBeamBoosted = FALSE; + + if (RandomPercentage(RNG_FICKLE_BEAM, 30)) + { + gBattleStruct->fickleBeamBoosted = TRUE; + gBattlescriptCurrInstr = BattleScript_FickleBeamDoubled; + } + else + { + gBattlescriptCurrInstr = cmd->nextInstr; + } +} diff --git a/src/battle_util.c b/src/battle_util.c index 7a6729230..5eead8957 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -107,7 +107,7 @@ void HandleAction_UseMove(void) u16 moveTarget; gBattlerAttacker = gBattlerByTurnOrder[gCurrentTurnActionNumber]; - if (gBattleStruct->absentBattlerFlags & gBitTable[gBattlerAttacker] || !IsBattlerAlive(gBattlerAttacker)) + if (gBattleStruct->absentBattlerFlags & (1u << gBattlerAttacker) || !IsBattlerAlive(gBattlerAttacker)) { gCurrentActionFuncId = B_ACTION_FINISHED; return; @@ -198,7 +198,7 @@ void HandleAction_UseMove(void) { gBattleStruct->moveTarget[gBattlerAttacker] = gBattlerTarget = gSideTimers[side].followmeTarget; // follow me moxie fix } - else if ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + else if (IsDoubleBattle() && gSideTimers[side].followmeTimer == 0 && (gMovesInfo[gCurrentMove].power != 0 || (moveTarget != MOVE_TARGET_USER && moveTarget != MOVE_TARGET_ALL_BATTLERS)) && ((GetBattlerAbility(*(gBattleStruct->moveTarget + gBattlerAttacker)) != ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC) @@ -282,8 +282,7 @@ void HandleAction_UseMove(void) gBattlerTarget = battler; } } - else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE - && moveTarget & MOVE_TARGET_RANDOM) + else if (IsDoubleBattle() && moveTarget & MOVE_TARGET_RANDOM) { if (GetBattlerSide(gBattlerAttacker) == B_SIDE_PLAYER) { @@ -300,7 +299,7 @@ void HandleAction_UseMove(void) gBattlerTarget = GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT); } - if (gAbsentBattlerFlags & gBitTable[gBattlerTarget] + if (gAbsentBattlerFlags & (1u << gBattlerTarget) && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget)) { gBattlerTarget = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerTarget))); @@ -313,8 +312,7 @@ void HandleAction_UseMove(void) else gBattlerTarget = gBattlerAttacker; } - else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE - && moveTarget == MOVE_TARGET_FOES_AND_ALLY) + else if (IsDoubleBattle() && moveTarget == MOVE_TARGET_FOES_AND_ALLY) { for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount; gBattlerTarget++) { @@ -428,10 +426,10 @@ bool32 TryRunFromBattle(u32 battler) if (GetBattlerSide(battler) == B_SIDE_PLAYER) effect++; } - // else if (gBattleTypeFlags & (BATTLE_TYPE_FRONTIER | BATTLE_TYPE_TRAINER_HILL) && gBattleTypeFlags & BATTLE_TYPE_TRAINER) - // { - // effect++; - // } + else if (gBattleTypeFlags & (BATTLE_TYPE_FRONTIER | BATTLE_TYPE_TRAINER_HILL) && gBattleTypeFlags & BATTLE_TYPE_TRAINER) + { + effect++; + } else { u8 runningFromBattler = BATTLE_OPPOSITE(battler); @@ -912,29 +910,29 @@ static void UNUSED MarkAllBattlersForControllerExec(void) if (gBattleTypeFlags & BATTLE_TYPE_LINK) { for (i = 0; i < gBattlersCount; i++) - gBattleControllerExecFlags |= gBitTable[i] << (32 - MAX_BATTLERS_COUNT); + gBattleControllerExecFlags |= 1u << (i + 32 - MAX_BATTLERS_COUNT); } else { for (i = 0; i < gBattlersCount; i++) - gBattleControllerExecFlags |= gBitTable[i]; + gBattleControllerExecFlags |= 1 << i; } } bool32 IsBattlerMarkedForControllerExec(u32 battler) { if (gBattleTypeFlags & BATTLE_TYPE_LINK) - return (gBattleControllerExecFlags & (gBitTable[battler] << 0x1C)) != 0; + return (gBattleControllerExecFlags & (1 << (battler + 28))) != 0; else - return (gBattleControllerExecFlags & (gBitTable[battler])) != 0; + return (gBattleControllerExecFlags & (1 << battler)) != 0; } void MarkBattlerForControllerExec(u32 battler) { if (gBattleTypeFlags & BATTLE_TYPE_LINK) - gBattleControllerExecFlags |= gBitTable[battler] << (32 - MAX_BATTLERS_COUNT); + gBattleControllerExecFlags |= 1u << (32 - MAX_BATTLERS_COUNT); else - gBattleControllerExecFlags |= gBitTable[battler]; + gBattleControllerExecFlags |= 1u << battler; } void MarkBattlerReceivedLinkData(u32 battler) @@ -942,9 +940,9 @@ void MarkBattlerReceivedLinkData(u32 battler) s32 i; for (i = 0; i < GetLinkPlayerCount(); i++) - gBattleControllerExecFlags |= gBitTable[battler] << (i << 2); + gBattleControllerExecFlags |= 1u << (battler + (i << 2)); - gBattleControllerExecFlags &= ~((1 << 28) << battler); + gBattleControllerExecFlags &= ~(1u << (28 + battler)); } const u8* CancelMultiTurnMoves(u32 battler) @@ -1120,7 +1118,7 @@ void ResetSentPokesToOpponentValue(void) gSentPokesToOpponent[1] = 0; for (i = 0; i < gBattlersCount; i += 2) - bits |= gBitTable[gBattlerPartyIndexes[i]]; + bits |= 1u << gBattlerPartyIndexes[i]; for (i = 1; i < gBattlersCount; i += 2) gSentPokesToOpponent[(i & BIT_FLANK) >> 1] = bits; @@ -1138,8 +1136,8 @@ void OpponentSwitchInResetSentPokesToOpponentValue(u32 battler) for (i = 0; i < gBattlersCount; i += 2) { - if (!(gAbsentBattlerFlags & gBitTable[i])) - bits |= gBitTable[gBattlerPartyIndexes[i]]; + if (!(gAbsentBattlerFlags & (1u << i))) + bits |= 1u << gBattlerPartyIndexes[i]; } gSentPokesToOpponent[flank] = bits; } @@ -1155,7 +1153,7 @@ void UpdateSentPokesToOpponentValue(u32 battler) { s32 i; for (i = 1; i < gBattlersCount; i++) - gSentPokesToOpponent[(i & BIT_FLANK) >> 1] |= gBitTable[gBattlerPartyIndexes[battler]]; + gSentPokesToOpponent[(i & BIT_FLANK) >> 1] |= 1u << gBattlerPartyIndexes[battler]; } } @@ -1196,7 +1194,7 @@ bool32 IsBelchPreventingMove(u32 battler, u32 move) if (gMovesInfo[move].effect != EFFECT_BELCH) return FALSE; - return !(gBattleStruct->ateBerry[battler & BIT_SIDE] & gBitTable[gBattlerPartyIndexes[battler]]); + return !(gBattleStruct->ateBerry[battler & BIT_SIDE] & (1u << gBattlerPartyIndexes[battler])); } // Dynamax bypasses all selection prevention except Taunt and Assault Vest. @@ -1342,55 +1340,55 @@ u8 CheckMoveLimitations(u32 battler, u8 unusableMoves, u16 check) moveEffect = gMovesInfo[move].effect; // No move if (check & MOVE_LIMITATION_ZEROMOVE && move == MOVE_NONE) - unusableMoves |= gBitTable[i]; + unusableMoves |= 1u << i; // No PP else if (check & MOVE_LIMITATION_PP && gBattleMons[battler].pp[i] == 0) - unusableMoves |= gBitTable[i]; + unusableMoves |= 1u << i; // Placeholder else if (check & MOVE_LIMITATION_PLACEHOLDER && moveEffect == EFFECT_PLACEHOLDER) - unusableMoves |= gBitTable[i]; + unusableMoves |= 1u << i; // Disable else if (check & MOVE_LIMITATION_DISABLED && move == gDisableStructs[battler].disabledMove) - unusableMoves |= gBitTable[i]; + unusableMoves |= 1u << i; // Torment else if (check & MOVE_LIMITATION_TORMENTED && move == gLastMoves[battler] && gBattleMons[battler].status2 & STATUS2_TORMENT) - unusableMoves |= gBitTable[i]; + unusableMoves |= 1u << i; // Taunt else if (check & MOVE_LIMITATION_TAUNT && gDisableStructs[battler].tauntTimer && IS_MOVE_STATUS(move)) - unusableMoves |= gBitTable[i]; + unusableMoves |= 1u << i; // Imprison else if (check & MOVE_LIMITATION_IMPRISON && GetImprisonedMovesCount(battler, move)) - unusableMoves |= gBitTable[i]; + unusableMoves |= 1u << i; // Encore else if (check & MOVE_LIMITATION_ENCORE && gDisableStructs[battler].encoreTimer && gDisableStructs[battler].encoredMove != move) - unusableMoves |= gBitTable[i]; + unusableMoves |= 1u << i; // Choice Items else if (check & MOVE_LIMITATION_CHOICE_ITEM && HOLD_EFFECT_CHOICE(holdEffect) && *choicedMove != MOVE_NONE && *choicedMove != MOVE_UNAVAILABLE && *choicedMove != move) - unusableMoves |= gBitTable[i]; + unusableMoves |= 1u << i; // Assault Vest else if (check & MOVE_LIMITATION_ASSAULT_VEST && holdEffect == HOLD_EFFECT_ASSAULT_VEST && IS_MOVE_STATUS(move) && gMovesInfo[move].effect != EFFECT_ME_FIRST) - unusableMoves |= gBitTable[i]; + unusableMoves |= 1u << i; // Gravity else if (check & MOVE_LIMITATION_GRAVITY && IsGravityPreventingMove(move)) - unusableMoves |= gBitTable[i]; + unusableMoves |= 1u << i; // Heal Block else if (check & MOVE_LIMITATION_HEAL_BLOCK && IsHealBlockPreventingMove(battler, move)) - unusableMoves |= gBitTable[i]; + unusableMoves |= 1u << i; // Belch else if (check & MOVE_LIMITATION_BELCH && IsBelchPreventingMove(battler, move)) - unusableMoves |= gBitTable[i]; + unusableMoves |= 1u << i; // Throat Chop else if (check & MOVE_LIMITATION_THROAT_CHOP && gDisableStructs[battler].throatChopTimer && gMovesInfo[move].soundMove) - unusableMoves |= gBitTable[i]; + unusableMoves |= 1u << i; // Stuff Cheeks else if (check & MOVE_LIMITATION_STUFF_CHEEKS && moveEffect == EFFECT_STUFF_CHEEKS && ItemId_GetPocket(gBattleMons[battler].item) != POCKET_BERRIES) - unusableMoves |= gBitTable[i]; + 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) - unusableMoves |= gBitTable[i]; + unusableMoves |= 1u << i; // Can't Use Twice flag else if (check & MOVE_LIMITATION_CANT_USE_TWICE && gMovesInfo[move].cantUseTwice && move == gLastResultingMoves[battler]) - unusableMoves |= gBitTable[i]; + unusableMoves |= 1u << i; } return unusableMoves; } @@ -1447,9 +1445,10 @@ u32 GetBattlerAffectionHearts(u32 battler) return AFFECTION_NO_HEARTS; else if (gSpeciesInfo[species].isMegaEvolution || (gBattleTypeFlags & (BATTLE_TYPE_EREADER_TRAINER - | BATTLE_TYPE_BATTLE_TOWER + | BATTLE_TYPE_FRONTIER + | BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK - | BATTLE_TYPE_LINK))) + | BATTLE_TYPE_SECRET_BASE))) return AFFECTION_NO_HEARTS; return GetMonAffectionHearts(&party[gBattlerPartyIndexes[battler]]); @@ -1497,8 +1496,6 @@ enum ENDTURN_PSYCHIC_TERRAIN, ENDTURN_ION_DELUGE, ENDTURN_FAIRY_LOCK, - ENDTURN_RETALIATE, - ENDTURN_WEATHER_FORM, ENDTURN_STATUS_HEAL, ENDTURN_RAINBOW, ENDTURN_SEA_OF_FIRE, @@ -1531,10 +1528,10 @@ u8 DoFieldEndTurnEffects(void) { u8 effect = 0; - for (gBattlerAttacker = 0; gBattlerAttacker < gBattlersCount && gAbsentBattlerFlags & gBitTable[gBattlerAttacker]; gBattlerAttacker++) + for (gBattlerAttacker = 0; gBattlerAttacker < gBattlersCount && gAbsentBattlerFlags & (1u << gBattlerAttacker); gBattlerAttacker++) { } - for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount && gAbsentBattlerFlags & gBitTable[gBattlerTarget]; gBattlerTarget++) + for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount && gAbsentBattlerFlags & (1u << gBattlerTarget); gBattlerTarget++) { } @@ -1991,25 +1988,6 @@ u8 DoFieldEndTurnEffects(void) } gBattleStruct->turnCountersTracker++; break; - case ENDTURN_RETALIATE: - if (gSideTimers[B_SIDE_PLAYER].retaliateTimer > 0) - gSideTimers[B_SIDE_PLAYER].retaliateTimer--; - if (gSideTimers[B_SIDE_OPPONENT].retaliateTimer > 0) - gSideTimers[B_SIDE_OPPONENT].retaliateTimer--; - gBattleStruct->turnCountersTracker++; - break; - case ENDTURN_WEATHER_FORM: - for (i = 0; i < gBattlersCount; i++) - { - if (AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, i, 0, 0, 0)) - { - effect++; - break; - } - } - if (effect == 0) - gBattleStruct->turnCountersTracker++; - break; case ENDTURN_STATUS_HEAL: for (gBattlerAttacker = 0; gBattlerAttacker < gBattlersCount; gBattlerAttacker++) { @@ -2198,7 +2176,7 @@ u8 DoBattlerEndTurnEffects(void) while (gBattleStruct->turnEffectsBattlerId < gBattlersCount && gBattleStruct->turnEffectsTracker <= ENDTURN_BATTLER_COUNT) { battler = gBattlerAttacker = gBattlerByTurnOrder[gBattleStruct->turnEffectsBattlerId]; - if (gAbsentBattlerFlags & gBitTable[battler]) + if (gAbsentBattlerFlags & (1u << battler)) { gBattleStruct->turnEffectsBattlerId++; continue; @@ -2227,6 +2205,8 @@ u8 DoBattlerEndTurnEffects(void) { gBattleScripting.battler = battler; gBattleMoveDamage = GetNonDynamaxMaxHP(battler) / 16; + if (gBattleMoveDamage == 0) + gBattleMoveDamage = 1; BattleScriptExecute(BattleScript_DamagingWeather); effect++; } @@ -2237,7 +2217,7 @@ u8 DoBattlerEndTurnEffects(void) && !(gStatuses3[battler] & STATUS3_HEAL_BLOCK)) { gBattleScripting.battler = battler; - gBattleMoveDamage = -1 * (GetNonDynamaxMaxHP(battler) / 16); + gBattleMoveDamage = -1 * max(1, GetNonDynamaxMaxHP(battler) / 16); BattleScriptExecute(BattleScript_IceBodyHeal); effect++; } @@ -2251,6 +2231,8 @@ u8 DoBattlerEndTurnEffects(void) { gBattleScripting.battler = battler; gBattleMoveDamage = GetNonDynamaxMaxHP(battler) / 16; + if (gBattleMoveDamage == 0) + gBattleMoveDamage = 1; BattleScriptExecute(BattleScript_DamagingWeather); effect++; } @@ -2860,7 +2842,7 @@ bool32 HandleWishPerishSongOnTurnEnd(void) if (gWishFutureKnock.futureSightCounter[battler] != 0 && --gWishFutureKnock.futureSightCounter[battler] == 0 - && !(gAbsentBattlerFlags & gBitTable[battler])) + && !(gAbsentBattlerFlags & (1u << battler))) { struct Pokemon *party; @@ -2901,7 +2883,7 @@ bool32 HandleWishPerishSongOnTurnEnd(void) while (gBattleStruct->wishPerishSongBattlerId < gBattlersCount) { battler = gBattlerAttacker = gBattlerByTurnOrder[gBattleStruct->wishPerishSongBattlerId]; - if (gAbsentBattlerFlags & gBitTable[battler]) + if (gAbsentBattlerFlags & (1u << battler)) { gBattleStruct->wishPerishSongBattlerId++; continue; @@ -2951,8 +2933,8 @@ bool32 HandleFaintedMonActions(void) gBattleStruct->faintedActionsState++; for (i = 0; i < gBattlersCount; i++) { - if (gAbsentBattlerFlags & gBitTable[i] && !HasNoMonsToSwitch(i, PARTY_SIZE, PARTY_SIZE)) - gAbsentBattlerFlags &= ~(gBitTable[i]); + if (gAbsentBattlerFlags & (1u << i) && !HasNoMonsToSwitch(i, PARTY_SIZE, PARTY_SIZE)) + gAbsentBattlerFlags &= ~(1u << i); } // fall through case 1: @@ -2960,8 +2942,8 @@ bool32 HandleFaintedMonActions(void) { gBattlerFainted = gBattlerTarget = gBattleStruct->faintedActionsBattlerId; if (gBattleMons[gBattleStruct->faintedActionsBattlerId].hp == 0 - && !(gBattleStruct->givenExpMons & gBitTable[gBattlerPartyIndexes[gBattleStruct->faintedActionsBattlerId]]) - && !(gAbsentBattlerFlags & gBitTable[gBattleStruct->faintedActionsBattlerId])) + && !(gBattleStruct->givenExpMons & (1u << gBattlerPartyIndexes[gBattleStruct->faintedActionsBattlerId])) + && !(gAbsentBattlerFlags & (1u << gBattleStruct->faintedActionsBattlerId))) { BattleScriptExecute(BattleScript_GiveExp); gBattleStruct->faintedActionsState = 2; @@ -2982,7 +2964,7 @@ bool32 HandleFaintedMonActions(void) && !NoAliveMonsForEitherParty() && gCurrentTurnActionNumber != gBattlersCount) { - gAbsentBattlerFlags |= gBitTable[gBattlerFainted]; + gAbsentBattlerFlags |= 1u << gBattlerFainted; if (gBattleStruct->faintedActionsState != 1) return FALSE; } @@ -3004,7 +2986,7 @@ bool32 HandleFaintedMonActions(void) { gBattlerFainted = gBattlerTarget = gBattleStruct->faintedActionsBattlerId; if (gBattleMons[gBattleStruct->faintedActionsBattlerId].hp == 0 - && !(gAbsentBattlerFlags & gBitTable[gBattleStruct->faintedActionsBattlerId])) + && !(gAbsentBattlerFlags & (1u << gBattleStruct->faintedActionsBattlerId))) { BattleScriptExecute(BattleScript_HandleFaintedMon); gBattleStruct->faintedActionsState = 5; @@ -3322,7 +3304,7 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType) gCurrentMove = MOVE_BIDE; gBattleScripting.bideDmg = gBideDmg[gBattlerAttacker] * 2; gBattlerTarget = gBideTarget[gBattlerAttacker]; - if (gAbsentBattlerFlags & gBitTable[gBattlerTarget]) + if (gAbsentBattlerFlags & (1u << gBattlerTarget)) gBattlerTarget = GetMoveTarget(MOVE_BIDE, MOVE_TARGET_SELECTED + 1); gBattlescriptCurrInstr = BattleScript_BideAttack; } @@ -3481,13 +3463,14 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType) else { gMultiHitCounter = gMovesInfo[gCurrentMove].strikeCount; - PREPARE_BYTE_NUMBER_BUFFER(gBattleScripting.multihitString, 3, 0) if (gMovesInfo[gCurrentMove].effect == EFFECT_DRAGON_DARTS && CanTargetPartner(gBattlerAttacker, gBattlerTarget) && TargetFullyImmuneToCurrMove(gBattlerAttacker, gBattlerTarget)) gBattlerTarget = BATTLE_PARTNER(gBattlerTarget); } + + PREPARE_BYTE_NUMBER_BUFFER(gBattleScripting.multihitString, 3, 0) } else if (B_BEAT_UP >= GEN_5 && gMovesInfo[gCurrentMove].effect == EFFECT_BEAT_UP) { @@ -3566,7 +3549,7 @@ bool32 HasNoMonsToSwitch(u32 battler, u8 partyIdBattlerOn1, u8 partyIdBattlerOn2 u32 i, side, playerId, flankId; struct Pokemon *party; - if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) + if (!IsDoubleBattle()) return FALSE; side = GetBattlerSide(battler); @@ -3717,21 +3700,6 @@ static const u16 sWeatherFlagsInfo[][3] = [ENUM_WEATHER_FOG] = {B_WEATHER_FOG_TEMPORARY, B_WEATHER_FOG_PERMANENT, HOLD_EFFECT_NONE}, }; -static void ShouldChangeFormInWeather(u32 battler) -{ - int i; - int side = GetBattlerSide(battler); - struct Pokemon *party = GetSideParty(side); - - for (i = 0; i < PARTY_SIZE; i++) - { - if (GetMonData(&party[i], MON_DATA_SPECIES) == SPECIES_EISCUE_NOICE_FACE) - gBattleStruct->allowedToChangeFormInWeather[i][side] = TRUE; - else - gBattleStruct->allowedToChangeFormInWeather[i][side] = FALSE; - } -} - bool32 TryChangeBattleWeather(u32 battler, u32 weatherEnumId, bool32 viaAbility) { u16 battlerAbility = GetBattlerAbility(battler); @@ -3745,7 +3713,6 @@ bool32 TryChangeBattleWeather(u32 battler, u32 weatherEnumId, bool32 viaAbility) else if (B_ABILITY_WEATHER < GEN_6 && viaAbility && !(gBattleWeather & sWeatherFlagsInfo[weatherEnumId][1])) { gBattleWeather = (sWeatherFlagsInfo[weatherEnumId][0] | sWeatherFlagsInfo[weatherEnumId][1]); - ShouldChangeFormInWeather(battler); return TRUE; } else if (!(gBattleWeather & (sWeatherFlagsInfo[weatherEnumId][0] | sWeatherFlagsInfo[weatherEnumId][1]))) @@ -3755,7 +3722,6 @@ bool32 TryChangeBattleWeather(u32 battler, u32 weatherEnumId, bool32 viaAbility) gWishFutureKnock.weatherDuration = 8; else gWishFutureKnock.weatherDuration = 5; - ShouldChangeFormInWeather(battler); return TRUE; } return FALSE; @@ -3883,6 +3849,54 @@ static inline bool32 HadMoreThanHalfHpNowDoesnt(u32 battler) && gBattleMons[battler].hp <= cutoff); } +#define ANIM_STAT_HP 0 +#define ANIM_STAT_ATK 1 +#define ANIM_STAT_DEF 2 +#define ANIM_STAT_SPATK 3 +#define ANIM_STAT_SPDEF 4 +#define ANIM_STAT_SPEED 5 +#define ANIM_STAT_ACC 6 +#define ANIM_STAT_EVASION 7 +static void ChooseStatBoostAnimation(u32 battler) +{ + u32 stat; + bool32 statBuffMoreThan1 = FALSE; + u32 static const statsOrder[NUM_BATTLE_STATS] = + { + [ANIM_STAT_HP] = STAT_HP, + [ANIM_STAT_ATK] = STAT_ATK, + [ANIM_STAT_DEF] = STAT_DEF, + [ANIM_STAT_SPATK] = STAT_SPATK, + [ANIM_STAT_SPDEF] = STAT_SPDEF, + [ANIM_STAT_SPEED] = STAT_SPEED, + [ANIM_STAT_ACC] = STAT_ACC, + [ANIM_STAT_EVASION] = STAT_EVASION, + }; + gBattleScripting.animArg1 = 0; + + for (stat = 1; stat < NUM_BATTLE_STATS; stat++) // Start loop at 1 to avoid STAT_HP + { + if ((gQueuedStatBoosts[battler].stats & (1 << statsOrder[stat])) == 0) + continue; + + if (!statBuffMoreThan1) + statBuffMoreThan1 = ((gQueuedStatBoosts[battler].stats & (1 << statsOrder[stat])) > 1); + + if (gBattleScripting.animArg1 != 0) // Already set in a different stat so now boosting multiple stats + gBattleScripting.animArg1 = (statBuffMoreThan1 ? STAT_ANIM_MULTIPLE_PLUS2 : STAT_ANIM_MULTIPLE_PLUS1); + else + gBattleScripting.animArg1 = GET_STAT_BUFF_ID((statsOrder[stat] + 1)) + (statBuffMoreThan1 ? STAT_ANIM_PLUS2 : STAT_ANIM_PLUS1); + } +} +#undef ANIM_STAT_HP +#undef ANIM_STAT_ATK +#undef ANIM_STAT_DEF +#undef ANIM_STAT_SPATK +#undef ANIM_STAT_SPDEF +#undef ANIM_STAT_SPEED +#undef ANIM_STAT_ACC +#undef ANIM_STAT_EVASION + u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 moveArg) { u32 effect = 0; @@ -4150,7 +4164,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 target1 = GetBattlerAtPosition(side); target2 = GetBattlerAtPosition(side + BIT_FLANK); gSpecialStatuses[battler].switchInAbilityDone = TRUE; - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) { if (!gAbilitiesInfo[gBattleMons[target1].ability].cantBeTraced && gBattleMons[target1].hp != 0 && !gAbilitiesInfo[gBattleMons[target2].ability].cantBeTraced && gBattleMons[target2].hp != 0) @@ -4507,11 +4521,11 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 break; case ABILITY_SUPERSWEET_SYRUP: if (!gSpecialStatuses[battler].switchInAbilityDone - && !(gBattleStruct->supersweetSyrup[GetBattlerSide(battler)] & gBitTable[gBattlerPartyIndexes[battler]])) + && !(gBattleStruct->supersweetSyrup[GetBattlerSide(battler)] & (1u << gBattlerPartyIndexes[battler]))) { gBattlerAttacker = battler; gSpecialStatuses[battler].switchInAbilityDone = TRUE; - gBattleStruct->supersweetSyrup[GetBattlerSide(battler)] |= gBitTable[gBattlerPartyIndexes[battler]]; + gBattleStruct->supersweetSyrup[GetBattlerSide(battler)] |= (1u << gBattlerPartyIndexes[battler]); BattleScriptPushCursorAndCallback(BattleScript_SupersweetSyrupActivates); effect++; } @@ -4548,12 +4562,12 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 break; case ABILITY_INTREPID_SWORD: if (!gSpecialStatuses[battler].switchInAbilityDone && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN) - && !(gBattleStruct->intrepidSwordBoost[GetBattlerSide(battler)] & gBitTable[gBattlerPartyIndexes[battler]])) + && !(gBattleStruct->intrepidSwordBoost[GetBattlerSide(battler)] & (1u << gBattlerPartyIndexes[battler]))) { gBattleScripting.savedBattler = gBattlerAttacker; gBattlerAttacker = battler; if (B_INTREPID_SWORD == GEN_9) - gBattleStruct->intrepidSwordBoost[GetBattlerSide(battler)] |= gBitTable[gBattlerPartyIndexes[battler]]; + gBattleStruct->intrepidSwordBoost[GetBattlerSide(battler)] |= 1u << gBattlerPartyIndexes[battler]; gSpecialStatuses[battler].switchInAbilityDone = TRUE; SET_STATCHANGER(STAT_ATK, 1, FALSE); BattleScriptPushCursorAndCallback(BattleScript_BattlerAbilityStatRaiseOnSwitchIn); @@ -4562,18 +4576,31 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 break; case ABILITY_DAUNTLESS_SHIELD: if (!gSpecialStatuses[battler].switchInAbilityDone && CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN) - && !(gBattleStruct->dauntlessShieldBoost[GetBattlerSide(battler)] & gBitTable[gBattlerPartyIndexes[battler]])) + && !(gBattleStruct->dauntlessShieldBoost[GetBattlerSide(battler)] & (1u << gBattlerPartyIndexes[battler]))) { gBattleScripting.savedBattler = gBattlerAttacker; gBattlerAttacker = battler; if (B_DAUNTLESS_SHIELD == GEN_9) - gBattleStruct->dauntlessShieldBoost[GetBattlerSide(battler)] |= gBitTable[gBattlerPartyIndexes[battler]]; + gBattleStruct->dauntlessShieldBoost[GetBattlerSide(battler)] |= 1u << gBattlerPartyIndexes[battler]; gSpecialStatuses[battler].switchInAbilityDone = TRUE; 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) + && gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_TAILWIND) + { + gBattleScripting.savedBattler = gBattlerAttacker; + gBattlerAttacker = battler; + gSpecialStatuses[battler].switchInAbilityDone = TRUE; + SET_STATCHANGER(STAT_ATK, 1, FALSE); + BattleScriptPushCursorAndCallback(BattleScript_BattlerAbilityStatRaiseOnSwitchIn); + effect++; + } + break; case ABILITY_DESOLATE_LAND: if (TryChangeBattleWeather(battler, ENUM_WEATHER_SUN_PRIMAL, TRUE)) { @@ -4671,11 +4698,11 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (!gSpecialStatuses[battler].switchInAbilityDone && GetMonData(mon, MON_DATA_SPECIES) == SPECIES_PALAFIN_HERO - && !(gBattleStruct->transformZeroToHero[side] & gBitTable[gBattlerPartyIndexes[battler]])) + && !(gBattleStruct->transformZeroToHero[side] & (1u << gBattlerPartyIndexes[battler]))) { gSpecialStatuses[battler].switchInAbilityDone = TRUE; gBattlerAttacker = battler; - gBattleStruct->transformZeroToHero[side] |= gBitTable[gBattlerPartyIndexes[battler]]; + gBattleStruct->transformZeroToHero[side] |= 1u << gBattlerPartyIndexes[battler]; BattleScriptPushCursorAndCallback(BattleScript_ZeroToHeroActivates); effect++; } @@ -4736,6 +4763,17 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 effect++; } break; + case ABILITY_ICE_FACE: + if (IsBattlerWeatherAffected(battler, B_WEATHER_HAIL | B_WEATHER_SNOW) + && gBattleMons[battler].species == SPECIES_EISCUE_NOICE_FACE + && !(gBattleMons[battler].status2 & STATUS2_TRANSFORMED)) + { + // TODO: Convert this to a proper FORM_CHANGE type. + gBattleMons[battler].species = SPECIES_EISCUE_ICE_FACE; + BattleScriptPushCursorAndCallback(BattleScript_BattlerFormChangeWithStringEnd3); + effect++; + } + break; } break; case ABILITYEFFECT_ENDTURN: @@ -4744,8 +4782,19 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 gBattlerAttacker = battler; switch (gLastUsedAbility) { + case ABILITY_PICKUP: + if (gBattleMons[battler].item == ITEM_NONE + && gBattleStruct->changedItems[battler] == ITEM_NONE // Will not inherit an item + && PickupHasValidTarget(battler)) + { + gBattlerTarget = RandomUniformExcept(RNG_PICKUP, 0, gBattlersCount - 1, CantPickupItem); + gLastUsedItem = GetUsedHeldItem(gBattlerTarget); + BattleScriptPushCursorAndCallback(BattleScript_PickupActivates); + effect++; + } + break; case ABILITY_HARVEST: - if ((IsBattlerWeatherAffected(battler, B_WEATHER_SUN) || Random() % 2 == 0) + 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 && ItemId_GetPocket(GetUsedHeldItem(battler)) == POCKET_BERRIES) @@ -4822,9 +4871,9 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 for (i = STAT_ATK; i < statsNum; i++) { if (CompareStat(battler, i, MIN_STAT_STAGE, CMP_GREATER_THAN)) - validToLower |= gBitTable[i]; + validToLower |= 1u << i; if (CompareStat(battler, i, MAX_STAT_STAGE, CMP_LESS_THAN)) - validToRaise |= gBitTable[i]; + validToRaise |= 1u << i; } if (validToLower != 0 || validToRaise != 0) // Can lower one stat, or can raise one stat @@ -4835,16 +4884,16 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 do { i = (Random() % statsNum) + STAT_ATK; - } while (!(validToRaise & gBitTable[i])); + } while (!(validToRaise & (1u << i))); SET_STATCHANGER(i, 2, FALSE); - validToLower &= ~(gBitTable[i]); // Can't lower the same stat as raising. + validToLower &= ~(1u << i); // Can't lower the same stat as raising. } if (validToLower != 0) // Find stat to lower { do { i = (Random() % statsNum) + STAT_ATK; - } while (!(validToLower & gBitTable[i])); + } while (!(validToLower & (1u << i))); SET_STATCHANGER2(gBattleScripting.savedStatChanger, i, 1, TRUE); } BattleScriptPushCursorAndCallback(BattleScript_MoodyActivates); @@ -4999,7 +5048,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && BlocksPrankster(move, gBattlerAttacker, gBattlerTarget, TRUE) && !(IS_MOVE_STATUS(move) && (gLastUsedAbility == ABILITY_MAGIC_BOUNCE || gProtectStructs[gBattlerTarget].bounceMove))) { - if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE) || !(moveTarget & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY))) + if (!IsDoubleBattle() || !(moveTarget & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY))) CancelMultiTurnMoves(gBattlerAttacker); // Don't cancel moves that can hit two targets bc one target might not be protected gBattleScripting.battler = gBattlerAbility = gBattlerTarget; battleScriptBlocksMove = BattleScript_DarkTypePreventsPrankster; @@ -5228,6 +5277,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && (gMultiHitCounter == 0 || gMultiHitCounter == 1) && !(TestIfSheerForceAffected(gBattlerAttacker, gCurrentMove)) && (CanBattlerSwitch(battler) || !(gBattleTypeFlags & BATTLE_TYPE_TRAINER)) + && !(gBattleTypeFlags & BATTLE_TYPE_ARENA) && CountUsablePartyMons(battler) > 0 // Not currently held by Sky Drop && !(gStatuses3[battler] & STATUS3_SKY_DROPPED)) @@ -5339,6 +5389,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && TARGET_TURN_DAMAGED && !IS_BATTLER_OF_TYPE(battler, moveType) && moveType != TYPE_STELLAR + && moveType != TYPE_MYSTERY && IsBattlerAlive(battler)) { SET_BATTLER_TYPE(battler, moveType); @@ -5799,6 +5850,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 gBattlescriptCurrInstr = BattleScript_AbilityStatusEffect; effect++; } + break; } break; case ABILITYEFFECT_MOVE_END_OTHER: // Abilities that activate on *another* battler's moveend: Dancer, Soul-Heart, Receiver, Symbiosis @@ -5823,6 +5875,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 // Edge case for dance moves that hit multiply targets gHitMarker &= ~HITMARKER_NO_ATTACKSTRING; + SetTypeBeforeUsingMove(gCalledMove, battler); // Make sure that the target isn't an ally - if it is, target the original user if (GetBattlerSide(gBattlerTarget) == GetBattlerSide(gBattlerAttacker)) @@ -5846,40 +5899,10 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITY_OPPORTUNIST: if (gProtectStructs[battler].activateOpportunist == 2) { - bool32 statBuffMoreThan1 = FALSE; - bool32 handleSpeedAnimLater = FALSE; gBattleScripting.animArg1 = 0; gBattleScripting.battler = battler; gProtectStructs[battler].activateOpportunist--; - - for (i = 0; i < (NUM_BATTLE_STATS - 1); i++) - { - if ((gQueuedStatBoosts[battler].stats & (1 << i)) == 0) - continue; - - if (i == STAT_SPEED) - { - handleSpeedAnimLater = TRUE; - continue; - } - - if (!statBuffMoreThan1) - statBuffMoreThan1 = ((gQueuedStatBoosts[battler].stats & (1 << i)) > 1); - - if (gBattleScripting.animArg1 != 0) //Already set in a different stat so now boosting multiple stats - gBattleScripting.animArg1 = (!statBuffMoreThan1 ? STAT_ANIM_MULTIPLE_PLUS1 : STAT_ANIM_MULTIPLE_PLUS2); - else - gBattleScripting.animArg1 = GET_STAT_BUFF_ID((i + 1)) + (!statBuffMoreThan1 ? STAT_ANIM_PLUS1 : STAT_ANIM_PLUS2); - - } - if (handleSpeedAnimLater) - { - if (gBattleScripting.animArg1 != 0) //Already set in a different stat so now boosting multiple stats - gBattleScripting.animArg1 = (!statBuffMoreThan1 ? STAT_ANIM_MULTIPLE_PLUS1 : STAT_ANIM_MULTIPLE_PLUS2); - else - gBattleScripting.animArg1 = GET_STAT_BUFF_ID((STAT_SPEED + 1)) + (!statBuffMoreThan1 ? STAT_ANIM_PLUS1 : STAT_ANIM_PLUS2); - } - + ChooseStatBoostAnimation(battler); BattleScriptPushCursorAndCallback(BattleScript_OpportunistCopyStatChange); effect = 1; } @@ -6069,17 +6092,17 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITYEFFECT_ON_WEATHER: // For ability effects that activate when the battle weather changes. - battler = gBattlerAbility = gBattleScripting.battler; gLastUsedAbility = GetBattlerAbility(battler); switch (gLastUsedAbility) { case ABILITY_FORECAST: case ABILITY_FLOWER_GIFT: if ((IsBattlerWeatherAffected(battler, gBattleWeather) - || gBattleWeather == B_WEATHER_NONE - || !WEATHER_HAS_EFFECT) // Air Lock active - && TryBattleFormChange(battler, FORM_CHANGE_BATTLE_WEATHER)) + || gBattleWeather == B_WEATHER_NONE + || !WEATHER_HAS_EFFECT) // Air Lock active + && TryBattleFormChange(battler, FORM_CHANGE_BATTLE_WEATHER)) { + gBattleScripting.battler = battler; BattleScriptPushCursorAndCallback(BattleScript_BattlerFormChangeWithStringEnd3); effect++; } @@ -6087,11 +6110,10 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITY_ICE_FACE: if (IsBattlerWeatherAffected(battler, B_WEATHER_HAIL | B_WEATHER_SNOW) && gBattleMons[battler].species == SPECIES_EISCUE_NOICE_FACE - && !(gBattleMons[battler].status2 & STATUS2_TRANSFORMED) - && gBattleStruct->allowedToChangeFormInWeather[gBattlerPartyIndexes[battler]][GetBattlerSide(battler)]) + && !(gBattleMons[battler].status2 & STATUS2_TRANSFORMED)) { // TODO: Convert this to a proper FORM_CHANGE type. - gBattleStruct->allowedToChangeFormInWeather[gBattlerPartyIndexes[battler]][GetBattlerSide(battler)] = FALSE; + gBattleScripting.battler = battler; gBattleMons[battler].species = SPECIES_EISCUE_ICE_FACE; BattleScriptPushCursorAndCallback(BattleScript_BattlerFormChangeWithStringEnd3); effect++; @@ -6102,7 +6124,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 { gDisableStructs[battler].weatherAbilityDone = TRUE; PREPARE_STAT_BUFFER(gBattleTextBuff1, GetHighestStatId(battler)); - gBattlerAbility = gBattleScripting.battler = battler; + gBattleScripting.battler = battler; BattleScriptPushCursorAndCallback(BattleScript_ProtosynthesisActivates); effect++; } @@ -6828,43 +6850,11 @@ static u8 TryConsumeMirrorHerb(u32 battler, bool32 execute) if (gProtectStructs[battler].eatMirrorHerb) { - u32 i; - bool32 statBuffMoreThan1 = FALSE; - bool32 handleSpeedAnimLater = FALSE; - gBattleScripting.animArg1 = 0; gLastUsedItem = gBattleMons[battler].item; gBattleScripting.battler = battler; gProtectStructs[battler].eatMirrorHerb = 0; - - for (i = 0; i < (NUM_BATTLE_STATS - 1); i++) - { - if ((gQueuedStatBoosts[battler].stats & (1 << i)) == 0) - continue; - - if (i == STAT_SPEED) - { - handleSpeedAnimLater = TRUE; - continue; - } - - if (!statBuffMoreThan1) - statBuffMoreThan1 = ((gQueuedStatBoosts[battler].stats & (1 << i)) > 1); - - if (gBattleScripting.animArg1 != 0) //Already set in a different stat so now boosting multiple stats - gBattleScripting.animArg1 = (!statBuffMoreThan1 ? STAT_ANIM_MULTIPLE_PLUS1 : STAT_ANIM_MULTIPLE_PLUS2); - else - gBattleScripting.animArg1 = GET_STAT_BUFF_ID((i + 1)) + (!statBuffMoreThan1 ? STAT_ANIM_PLUS1 : STAT_ANIM_PLUS2); - - } - if (handleSpeedAnimLater) - { - if (gBattleScripting.animArg1 != 0) //Already set in a different stat so now boosting multiple stats - gBattleScripting.animArg1 = (!statBuffMoreThan1 ? STAT_ANIM_MULTIPLE_PLUS1 : STAT_ANIM_MULTIPLE_PLUS2); - else - gBattleScripting.animArg1 = GET_STAT_BUFF_ID((STAT_SPEED + 1)) + (!statBuffMoreThan1 ? STAT_ANIM_PLUS1 : STAT_ANIM_PLUS2); - } - + ChooseStatBoostAnimation(battler); if (execute) { BattleScriptExecute(BattleScript_MirrorHerbCopyStatChangeEnd2); @@ -7395,13 +7385,13 @@ u8 ItemBattleEffects(u8 caseID, u32 battler, bool32 moveTurn) effect = TryConsumeMirrorHerb(battler, TRUE); break; case HOLD_EFFECT_BOOSTER_ENERGY: - if (!(gBattleStruct->boosterEnergyActivates & gBitTable[battler]) + if (!(gBattleStruct->boosterEnergyActivates & (1u << battler)) && (((GetBattlerAbility(battler) == ABILITY_PROTOSYNTHESIS) && !(gBattleWeather & B_WEATHER_SUN)) || ((GetBattlerAbility(battler) == ABILITY_QUARK_DRIVE) && !(gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN)))) { PREPARE_STAT_BUFFER(gBattleTextBuff1, GetHighestStatId(battler)); gBattleScripting.battler = battler; - gBattleStruct->boosterEnergyActivates |= gBitTable[battler]; + gBattleStruct->boosterEnergyActivates |= 1u << battler; BattleScriptExecute(BattleScript_BoosterEnergyEnd2); effect = ITEM_EFFECT_OTHER; } @@ -7662,13 +7652,13 @@ u8 ItemBattleEffects(u8 caseID, u32 battler, bool32 moveTurn) effect = TryConsumeMirrorHerb(battler, TRUE); break; case HOLD_EFFECT_BOOSTER_ENERGY: - if (!(gBattleStruct->boosterEnergyActivates & gBitTable[battler]) + if (!(gBattleStruct->boosterEnergyActivates & (1u << battler)) && (((GetBattlerAbility(battler) == ABILITY_PROTOSYNTHESIS) && !(gBattleWeather & B_WEATHER_SUN)) || ((GetBattlerAbility(battler) == ABILITY_QUARK_DRIVE) && !(gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN)))) { PREPARE_STAT_BUFFER(gBattleTextBuff1, GetHighestStatId(battler)); gBattlerAbility = gBattleScripting.battler = battler; - gBattleStruct->boosterEnergyActivates |= gBitTable[battler]; + gBattleStruct->boosterEnergyActivates |= 1u << battler; BattleScriptExecute(BattleScript_BoosterEnergyEnd2); effect = ITEM_EFFECT_OTHER; } @@ -8045,7 +8035,7 @@ u8 ItemBattleEffects(u8 caseID, u32 battler, bool32 moveTurn) // Berry was successfully used on a Pokemon. if (effect && (gLastUsedItem >= FIRST_BERRY_INDEX && gLastUsedItem <= LAST_BERRY_INDEX)) - gBattleStruct->ateBerry[battler & BIT_SIDE] |= gBitTable[gBattlerPartyIndexes[battler]]; + gBattleStruct->ateBerry[battler & BIT_SIDE] |= 1u << gBattlerPartyIndexes[battler]; return effect; } @@ -8073,7 +8063,7 @@ u32 SetRandomTarget(u32 battler) [B_SIDE_OPPONENT] = {B_POSITION_PLAYER_LEFT, B_POSITION_PLAYER_RIGHT}, }; - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (IsDoubleBattle()) { target = GetBattlerAtPosition(targets[GetBattlerSide(battler)][Random() % 2]); if (!IsBattlerAlive(target)) @@ -8091,6 +8081,7 @@ u32 GetMoveTarget(u16 move, u8 setTarget) { u8 targetBattler = 0; u32 moveTarget, side; + u32 moveType = GetMoveType(move); if (setTarget != NO_TARGET_OVERRIDE) moveTarget = setTarget - 1; @@ -8108,7 +8099,7 @@ u32 GetMoveTarget(u16 move, u8 setTarget) else { targetBattler = SetRandomTarget(gBattlerAttacker); - if (gMovesInfo[move].type == TYPE_ELECTRIC + if (moveType == TYPE_ELECTRIC && IsAbilityOnOpposingSide(gBattlerAttacker, ABILITY_LIGHTNING_ROD) && GetBattlerAbility(targetBattler) != ABILITY_LIGHTNING_ROD) { @@ -8116,7 +8107,7 @@ u32 GetMoveTarget(u16 move, u8 setTarget) RecordAbilityBattle(targetBattler, gBattleMons[targetBattler].ability); gSpecialStatuses[targetBattler].lightningRodRedirected = TRUE; } - else if (gMovesInfo[move].type == TYPE_WATER + else if (moveType == TYPE_WATER && IsAbilityOnOpposingSide(gBattlerAttacker, ABILITY_STORM_DRAIN) && GetBattlerAbility(targetBattler) != ABILITY_STORM_DRAIN) { @@ -8138,7 +8129,7 @@ u32 GetMoveTarget(u16 move, u8 setTarget) side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker)); if (IsAffectedByFollowMe(gBattlerAttacker, side, move)) targetBattler = gSideTimers[side].followmeTarget; - else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && moveTarget & MOVE_TARGET_RANDOM) + else if (IsDoubleBattle() && moveTarget & MOVE_TARGET_RANDOM) targetBattler = SetRandomTarget(gBattlerAttacker); else targetBattler = GetBattlerAtPosition(BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker))); @@ -8219,7 +8210,7 @@ u8 IsMonDisobedient(void) // is not obedient if (gCurrentMove == MOVE_RAGE) gBattleMons[gBattlerAttacker].status2 &= ~STATUS2_RAGE; - if (gBattleMons[gBattlerAttacker].status1 & STATUS1_SLEEP && (gCurrentMove == MOVE_SNORE || gCurrentMove == MOVE_SLEEP_TALK)) + if (gBattleMons[gBattlerAttacker].status1 & STATUS1_SLEEP && (gMovesInfo[gCurrentMove].effect == EFFECT_SNORE || gMovesInfo[gCurrentMove].effect == EFFECT_SLEEP_TALK)) { gBattlescriptCurrInstr = BattleScript_IgnoresWhileAsleep; return 1; @@ -8229,7 +8220,7 @@ u8 IsMonDisobedient(void) calc = (levelReferenced + obedienceLevel) * rnd >> 8; if (calc < obedienceLevel) { - calc = CheckMoveLimitations(gBattlerAttacker, gBitTable[gCurrMovePos], MOVE_LIMITATIONS_ALL); + calc = CheckMoveLimitations(gBattlerAttacker, 1u << gCurrMovePos, MOVE_LIMITATIONS_ALL); if (calc == ALL_MOVES_MASK) // all moves cannot be used { // Randomly select, then print a disobedient string @@ -8243,7 +8234,7 @@ u8 IsMonDisobedient(void) do { gCurrMovePos = gChosenMovePos = MOD(Random(), MAX_MON_MOVES); - } while (gBitTable[gCurrMovePos] & calc); + } while ((1u << gCurrMovePos) & calc); gCalledMove = gBattleMons[gBattlerAttacker].moves[gCurrMovePos]; SetAtkCancellerForCalledMove(); @@ -8385,16 +8376,20 @@ bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move) // Protective Pads doesn't stop Unseen Fist from bypassing Protect effects, so IsMoveMakingContact() isn't used here. // This means extra logic is needed to handle Shell Side Arm. if (GetBattlerAbility(gBattlerAttacker) == ABILITY_UNSEEN_FIST - && (gMovesInfo[move].makesContact - || (gMovesInfo[move].effect == EFFECT_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] == DAMAGE_CATEGORY_PHYSICAL)) - && !gProtectStructs[battlerDef].maxGuarded) // Max Guard cannot be bypassed by Unseen Fist + && (gMovesInfo[move].makesContact + || (gMovesInfo[move].effect == EFFECT_SHELL_SIDE_ARM + && gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] == DAMAGE_CATEGORY_PHYSICAL)) + && !gProtectStructs[battlerDef].maxGuarded) // Max Guard cannot be bypassed by Unseen Fist return FALSE; + else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_CRAFTY_SHIELD + && IS_MOVE_STATUS(move)) + return TRUE; else if (gMovesInfo[move].ignoresProtect) return FALSE; else if (gProtectStructs[battlerDef].protected) return TRUE; else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_WIDE_GUARD - && GetBattlerMoveTargetType(gBattlerAttacker, move) & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY)) + && GetBattlerMoveTargetType(gBattlerAttacker, move) & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY)) return TRUE; else if (gProtectStructs[battlerDef].banefulBunkered) return TRUE; @@ -8411,11 +8406,8 @@ bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move) else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_QUICK_GUARD && GetChosenMovePriority(gBattlerAttacker) > 0) return TRUE; - else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_CRAFTY_SHIELD - && IS_MOVE_STATUS(move)) - return TRUE; else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_MAT_BLOCK - && !IS_MOVE_STATUS(move)) + && !IS_MOVE_STATUS(move)) return TRUE; else return FALSE; @@ -8458,7 +8450,7 @@ bool32 IsBattlerAlive(u32 battler) return FALSE; else if (battler >= gBattlersCount) return FALSE; - else if (gAbsentBattlerFlags & gBitTable[battler]) + else if (gAbsentBattlerFlags & (1u << battler)) return FALSE; else return TRUE; @@ -8531,12 +8523,12 @@ u32 GetMoveTargetCount(u32 move, u32 battlerAtk, u32 battlerDef) switch (GetBattlerMoveTargetType(gBattlerAttacker, move)) { case MOVE_TARGET_BOTH: - return !(gAbsentBattlerFlags & gBitTable[battlerDef]) - + !(gAbsentBattlerFlags & gBitTable[BATTLE_PARTNER(battlerDef)]); + return !(gAbsentBattlerFlags & (1u << battlerDef)) + + !(gAbsentBattlerFlags & (1u << BATTLE_PARTNER(battlerDef))); case MOVE_TARGET_FOES_AND_ALLY: - return !(gAbsentBattlerFlags & gBitTable[battlerDef]) - + !(gAbsentBattlerFlags & gBitTable[BATTLE_PARTNER(battlerDef)]) - + !(gAbsentBattlerFlags & gBitTable[BATTLE_PARTNER(battlerAtk)]); + return !(gAbsentBattlerFlags & (1u << battlerDef)) + + !(gAbsentBattlerFlags & (1u << BATTLE_PARTNER(battlerDef))) + + !(gAbsentBattlerFlags & (1u << BATTLE_PARTNER(battlerAtk))); case MOVE_TARGET_OPPONENTS_FIELD: return 1; case MOVE_TARGET_DEPENDS: @@ -8714,10 +8706,7 @@ static inline u32 CalcMoveBasePower(u32 move, u32 battlerAtk, u32 battlerDef, u3 basePower = gBattleStruct->presentBasePower; break; case EFFECT_TRIPLE_KICK: - if (gMultiHitCounter == 0) // Calc damage with max BP for move consideration - basePower *= 6; - else - basePower *= 1 + gMovesInfo[move].strikeCount - gMultiHitCounter; + basePower *= 1 + gMovesInfo[move].strikeCount - gMultiHitCounter; break; case EFFECT_SPIT_UP: basePower = 100 * gDisableStructs[battlerAtk].stockpileCounter; @@ -8902,7 +8891,7 @@ static inline u32 CalcMoveBasePower(u32 move, u32 battlerAtk, u32 battlerDef, u3 basePower = (basePower > 350) ? 350 : basePower; break; case EFFECT_FICKLE_BEAM: - if (RandomPercentage(RNG_FICKLE_BEAM, 30)) + if (gBattleStruct->fickleBeamBoosted) basePower *= 2; break; case EFFECT_TERA_BLAST: @@ -8957,7 +8946,7 @@ static inline u32 CalcMoveBasePowerAfterModifiers(u32 move, u32 battlerAtk, u32 modifier = uq4_12_multiply(modifier, UQ_4_12(0.5)); break; case EFFECT_STOMPING_TANTRUM: - if (gBattleStruct->lastMoveFailed & gBitTable[battlerAtk]) + if (gBattleStruct->lastMoveFailed & (1u << battlerAtk)) modifier = uq4_12_multiply(modifier, UQ_4_12(2.0)); break; case EFFECT_MAGNITUDE: @@ -9112,7 +9101,7 @@ static inline u32 CalcMoveBasePowerAfterModifiers(u32 move, u32 battlerAtk, u32 case ABILITY_PROTOSYNTHESIS: { u8 atkHighestStat = GetHighestStatId(battlerAtk); - if ((weather & B_WEATHER_SUN || gBattleStruct->boosterEnergyActivates & gBitTable[battlerAtk]) + if ((weather & B_WEATHER_SUN || gBattleStruct->boosterEnergyActivates & (1u << battlerAtk)) && ((IS_MOVE_PHYSICAL(move) && atkHighestStat == STAT_ATK) || (IS_MOVE_SPECIAL(move) && atkHighestStat == STAT_SPATK))) modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); } @@ -9120,7 +9109,7 @@ static inline u32 CalcMoveBasePowerAfterModifiers(u32 move, u32 battlerAtk, u32 case ABILITY_QUARK_DRIVE: { u8 atkHighestStat = GetHighestStatId(battlerAtk); - if ((gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gBattleStruct->boosterEnergyActivates & gBitTable[battlerAtk]) + if ((gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gBattleStruct->boosterEnergyActivates & (1u << battlerAtk)) && ((IS_MOVE_PHYSICAL(move) && atkHighestStat == STAT_ATK) || (IS_MOVE_SPECIAL(move) && atkHighestStat == STAT_SPATK))) modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); } @@ -9190,7 +9179,7 @@ static inline u32 CalcMoveBasePowerAfterModifiers(u32 move, u32 battlerAtk, u32 case ABILITY_PROTOSYNTHESIS: { u8 defHighestStat = GetHighestStatId(battlerDef); - if ((weather & B_WEATHER_SUN || gBattleStruct->boosterEnergyActivates & gBitTable[battlerDef]) + if ((weather & B_WEATHER_SUN || gBattleStruct->boosterEnergyActivates & (1u << battlerDef)) && ((IS_MOVE_PHYSICAL(move) && defHighestStat == STAT_DEF) || (IS_MOVE_SPECIAL(move) && defHighestStat == STAT_SPDEF))) modifier = uq4_12_multiply(modifier, UQ_4_12(0.7)); } @@ -9198,7 +9187,7 @@ static inline u32 CalcMoveBasePowerAfterModifiers(u32 move, u32 battlerAtk, u32 case ABILITY_QUARK_DRIVE: { u8 defHighestStat = GetHighestStatId(battlerDef); - if ((gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gBattleStruct->boosterEnergyActivates & gBitTable[battlerDef]) + if ((gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN || gBattleStruct->boosterEnergyActivates & (1u << battlerDef)) && ((IS_MOVE_PHYSICAL(move) && defHighestStat == STAT_DEF) || (IS_MOVE_SPECIAL(move) && defHighestStat == STAT_SPDEF))) modifier = uq4_12_multiply(modifier, UQ_4_12(0.7)); } @@ -9602,7 +9591,7 @@ static inline u32 CalcDefenseStat(u32 move, u32 battlerAtk, u32 battlerDef, u32 modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; case ABILITY_PURIFYING_SALT: - if (gMovesInfo[move].type == TYPE_GHOST) + if (moveType == TYPE_GHOST) modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0)); break; } @@ -9693,7 +9682,9 @@ static inline uq4_12_t GetParentalBondModifier(u32 battlerAtk) static inline uq4_12_t GetSameTypeAttackBonusModifier(u32 battlerAtk, u32 moveType, u32 move, u32 abilityAtk) { - if (gBattleStruct->pledgeMove && IS_BATTLER_OF_TYPE(BATTLE_PARTNER(battlerAtk), moveType)) + if (moveType == TYPE_MYSTERY) + return UQ_4_12(1.0); + else if (gBattleStruct->pledgeMove && IS_BATTLER_OF_TYPE(BATTLE_PARTNER(battlerAtk), moveType)) return (abilityAtk == ABILITY_ADAPTABILITY) ? UQ_4_12(2.0) : UQ_4_12(1.5); else if (!IS_BATTLER_OF_TYPE(battlerAtk, moveType) || move == MOVE_STRUGGLE || move == MOVE_NONE) return UQ_4_12(1.0); @@ -9798,7 +9789,7 @@ static inline uq4_12_t GetScreensModifier(u32 move, u32 battlerAtk, u32 battlerD if (isCrit || abilityAtk == ABILITY_INFILTRATOR || gProtectStructs[battlerAtk].confusionSelfDmg) return UQ_4_12(1.0); if (reflect || lightScreen || auroraVeil) - return (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) ? UQ_4_12(0.667) : UQ_4_12(0.5); + return (IsDoubleBattle()) ? UQ_4_12(0.667) : UQ_4_12(0.5); return UQ_4_12(1.0); } @@ -10164,7 +10155,7 @@ static inline void MulByTypeEffectiveness(uq4_12_t *modifier, u32 move, u32 move mod = UQ_4_12(1.0); } - if (gBattleStruct->distortedTypeMatchups & gBitTable[battlerDef] || (gBattleStruct->aiCalcInProgress && ShouldTeraShellDistortTypeMatchups(move, battlerDef))) + if (gBattleStruct->distortedTypeMatchups & (1u << battlerDef) || (gBattleStruct->aiCalcInProgress && ShouldTeraShellDistortTypeMatchups(move, battlerDef))) { mod = UQ_4_12(0.5); if (recordAbilities) @@ -10296,7 +10287,7 @@ uq4_12_t CalcTypeEffectivenessMultiplier(u32 move, u32 moveType, u32 battlerAtk, uq4_12_t CalcPartyMonTypeEffectivenessMultiplier(u16 move, u16 speciesDef, u16 abilityDef) { uq4_12_t modifier = UQ_4_12(1.0); - u8 moveType = gMovesInfo[move].type; + u32 moveType = GetMoveType(move); if (move != MOVE_STRUGGLE && moveType != TYPE_MYSTERY) { @@ -11052,7 +11043,7 @@ bool32 CanStealItem(u32 battlerStealing, u32 battlerItem, u16 item) { u8 stealerSide = GetBattlerSide(battlerStealing); - if (gBattleTypeFlags & BATTLE_TYPE_TRAINER_TOWER) + if (gBattleTypeFlags & BATTLE_TYPE_TRAINER_HILL) return FALSE; // Check if the battler trying to steal should be able to @@ -11062,6 +11053,7 @@ bool32 CanStealItem(u32 battlerStealing, u32 battlerItem, u16 item) | BATTLE_TYPE_FRONTIER | BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK + | BATTLE_TYPE_SECRET_BASE | (B_TRAINERS_KNOCK_OFF_ITEMS == TRUE ? BATTLE_TYPE_TRAINER : 0) ))) { @@ -11071,8 +11063,9 @@ bool32 CanStealItem(u32 battlerStealing, u32 battlerItem, u16 item) (BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_FRONTIER | BATTLE_TYPE_LINK - | BATTLE_TYPE_RECORDED_LINK)) - && (gWishFutureKnock.knockedOffMons[stealerSide] & gBitTable[gBattlerPartyIndexes[battlerStealing]])) + | BATTLE_TYPE_RECORDED_LINK + | BATTLE_TYPE_SECRET_BASE)) + && (gWishFutureKnock.knockedOffMons[stealerSide] & (1u << gBattlerPartyIndexes[battlerStealing]))) { return FALSE; } @@ -11238,6 +11231,25 @@ u16 GetUsedHeldItem(u32 battler) return gBattleStruct->usedHeldItems[gBattlerPartyIndexes[battler]][GetBattlerSide(battler)]; } +bool32 CantPickupItem(u32 battler) +{ + // Used by RandomUniformExcept() for RNG_PICKUP + if (battler == gBattlerAttacker && gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_LINK)) + return TRUE; + return !(IsBattlerAlive(battler) && GetUsedHeldItem(battler) && gBattleStruct->canPickupItem & (1u << battler)); +} + +bool32 PickupHasValidTarget(u32 battler) +{ + u32 i; + for (i = 0; i < gBattlersCount; i++) + { + if (!CantPickupItem(i)) + return TRUE; + } + return FALSE; +} + bool32 IsBattlerWeatherAffected(u32 battler, u32 weatherFlags) { if (gBattleWeather & weatherFlags && WEATHER_HAS_EFFECT) @@ -11600,7 +11612,7 @@ void SetShellSideArmCategory(void) bool32 CanTargetPartner(u32 battlerAtk, u32 battlerDef) { - return (gBattleTypeFlags & BATTLE_TYPE_DOUBLE + return (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(battlerDef)) && battlerDef != BATTLE_PARTNER(battlerAtk)); } diff --git a/src/data/battle_move_effects.h b/src/data/battle_move_effects.h index 38192ebd1..0372a28be 100644 --- a/src/data/battle_move_effects.h +++ b/src/data/battle_move_effects.h @@ -1831,7 +1831,7 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] = [EFFECT_FICKLE_BEAM] = { - .battleScript = BattleScript_EffectHit, + .battleScript = BattleScript_EffectFickleBeam, }, [EFFECT_BLIZZARD] = diff --git a/src/evolution_scene.c b/src/evolution_scene.c index 059f7d891..5b57593c3 100644 --- a/src/evolution_scene.c +++ b/src/evolution_scene.c @@ -930,7 +930,7 @@ static void Task_EvolutionScene(u8 taskId) BattlePutTextOnWindow(gText_BattleYesNoChoice, B_WIN_YESNO); gTasks[taskId].tLearnMoveState++; sEvoCursorPos = 0; - BattleCreateYesNoCursorAt(); + BattleCreateYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]); } break; case MVSTATE_HANDLE_YES_NO: @@ -941,17 +941,17 @@ static void Task_EvolutionScene(u8 taskId) { // Moved onto YES PlaySE(SE_SELECT); - BattleDestroyYesNoCursorAt(); + BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]); sEvoCursorPos = 0; - BattleCreateYesNoCursorAt(); + BattleCreateYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]); } if (JOY_NEW(DPAD_DOWN) && sEvoCursorPos == 0) { // Moved onto NO PlaySE(SE_SELECT); - BattleDestroyYesNoCursorAt(); + BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]); sEvoCursorPos = 1; - BattleCreateYesNoCursorAt(); + BattleCreateYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]); } if (JOY_NEW(A_BUTTON)) { diff --git a/src/main.c b/src/main.c index 1f902f519..8a6789a4d 100644 --- a/src/main.c +++ b/src/main.c @@ -248,12 +248,8 @@ void SetMainCallback2(MainCallback callback) void StartTimer1(void) { - if (HQ_RANDOM) - { - REG_TM2CNT_L = 0; - REG_TM2CNT_H = TIMER_ENABLE | TIMER_COUNTUP; - } - + REG_TM2CNT_L = 0; + REG_TM2CNT_H = TIMER_ENABLE | TIMER_COUNTUP; REG_TM1CNT_H = TIMER_ENABLE; } @@ -261,22 +257,12 @@ void SeedRngAndSetTrainerId(void) { u32 val; - if (HQ_RANDOM) - { - REG_TM1CNT_H = 0; - REG_TM2CNT_H = 0; - val = ((u32)REG_TM2CNT_L) << 16; - val |= REG_TM1CNT_L; - SeedRng(val); - gTrainerId = Random(); - } - else - { - u16 val = REG_TM1CNT_L; - SeedRng(val); - REG_TM1CNT_H = 0; - gTrainerId = val; - } + REG_TM1CNT_H = 0; + REG_TM2CNT_H = 0; + val = ((u32)REG_TM2CNT_L) << 16; + val |= REG_TM1CNT_L; + SeedRng(val); + gTrainerId = Random(); } u16 GetGeneratedTrainerIdLower(void) diff --git a/src/random.c b/src/random.c index 9d43ae374..969aa6d40 100644 --- a/src/random.c +++ b/src/random.c @@ -8,7 +8,6 @@ rng_value_t gRngValue; rng_value_t gRng2Value; -#if HQ_RANDOM == TRUE EWRAM_DATA static volatile bool8 sRngLoopUnlocked; @@ -112,39 +111,6 @@ void AdvanceRandom(void) #define LOOP_RANDOM ((u16)(_SFC32_Next(state) >> 16)) -#else -EWRAM_DATA static u32 sRandCount = 0; - -u16 Random(void) -{ - gRngValue = ISO_RANDOMIZE1(gRngValue); - sRandCount++; - return gRngValue >> 16; -} - -void SeedRng(u16 seed) -{ - gRngValue = seed; -} - -void SeedRng2(u16 seed) -{ - gRng2Value = seed; -} - -u16 Random2(void) -{ - gRng2Value = ISO_RANDOMIZE1(gRng2Value); - return gRng2Value >> 16; -} - -#define LOOP_RANDOM_START -#define LOOP_RANDOM_END - -#define LOOP_RANDOM (Random()) - -#endif - #define SHUFFLE_IMPL \ u32 tmp; \ LOOP_RANDOM_START; \ diff --git a/test/battle/ability/adaptability.c b/test/battle/ability/adaptability.c new file mode 100644 index 000000000..4c1c8439b --- /dev/null +++ b/test/battle/ability/adaptability.c @@ -0,0 +1,64 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Adaptability increases same-type attack bonus from x1.5 to x2", s16 damage) +{ + u32 ability; + PARAMETRIZE { ability = ABILITY_HYPER_CUTTER; } + PARAMETRIZE { ability = ABILITY_ADAPTABILITY; } + GIVEN { + PLAYER(SPECIES_CRAWDAUNT) { Ability(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", s16 damage) +{ + bool32 tera; + PARAMETRIZE { tera = GIMMICK_NONE; } + PARAMETRIZE { tera = GIMMICK_TERA; } + GIVEN { + PLAYER(SPECIES_CRAWDAUNT) { Ability(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", s16 damage) +{ + bool32 tera; + PARAMETRIZE { tera = GIMMICK_NONE; } + PARAMETRIZE { tera = GIMMICK_TERA; } + GIVEN { + PLAYER(SPECIES_CRAWDAUNT) { Ability(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"); diff --git a/test/battle/ability/aerilate.c b/test/battle/ability/aerilate.c new file mode 100644 index 000000000..4386034a5 --- /dev/null +++ b/test/battle/ability/aerilate.c @@ -0,0 +1,56 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_TACKLE].type == TYPE_NORMAL); + ASSUME(gMovesInfo[MOVE_TACKLE].power > 0); +} + +SINGLE_BATTLE_TEST("Aerilate turns a Normal-type move into Flying-type move") +{ + GIVEN { + PLAYER(SPECIES_MEGANIUM); + OPPONENT(SPECIES_SALAMENCE) { Item(ITEM_SALAMENCITE); } + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE, gimmick: GIMMICK_MEGA); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Aerilate can not turn certain moves into Flying type moves") +{ + 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) { Item(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!"); + } + } +} + +TO_DO_BATTLE_TEST("Aerilate boosts power of affected moves by 20% (Gen7+)"); +TO_DO_BATTLE_TEST("Aerilate boosts power of affected moves by 30% (Gen6)"); + +// Gen 6-7 +TO_DO_BATTLE_TEST("Aerilate overrides Electrify (Gen6-7)"); +TO_DO_BATTLE_TEST("Aerilate overrides Ion Deluge (Gen6-7)"); +// Gen 8+ +//TO_DO_BATTLE_TEST("Aerilate doesn't override Electrify (Gen8+)"); // 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 (Gen8+)"); // Ion Deluge doesn't exist in Gen 8+, but we probably could assume it behaves similar to under Electrify. TODO: Test by hacking SV. diff --git a/test/battle/ability/aftermath.c b/test/battle/ability/aftermath.c new file mode 100644 index 000000000..5cfd5ef03 --- /dev/null +++ b/test/battle/ability/aftermath.c @@ -0,0 +1,22 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Aftermath damages the attacker by 1/4th of its max HP if fainted by a contact move") +{ + s16 aftermathDamage; + + GIVEN { + PLAYER(SPECIES_VOLTORB) { HP(1); Ability(ABILITY_AFTERMATH); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN {MOVE(opponent, MOVE_TACKLE);} + } SCENE { + MESSAGE("Foe Wobbuffet used Tackle!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + MESSAGE("Voltorb fainted!"); + ABILITY_POPUP(player, ABILITY_AFTERMATH); + HP_BAR(opponent, captureDamage: &aftermathDamage); + } THEN { + EXPECT_EQ(aftermathDamage, opponent->maxHP / 4); + } +} diff --git a/test/battle/ability/air_lock.c b/test/battle/ability/air_lock.c new file mode 100644 index 000000000..0201e86a2 --- /dev/null +++ b/test/battle/ability/air_lock.c @@ -0,0 +1,4 @@ +#include "global.h" +#include "test/battle.h" + +// Tests for Air Lock are handled in test/battle/ability/cloud_nine.c diff --git a/test/battle/ability/analytic.c b/test/battle/ability/analytic.c new file mode 100644 index 000000000..44c42ae2d --- /dev/null +++ b/test/battle/ability/analytic.c @@ -0,0 +1,11 @@ +#include "global.h" +#include "test/battle.h" + +TO_DO_BATTLE_TEST("Analytic increases the power of moves by 30% if it's the last one that uses its move"); +TO_DO_BATTLE_TEST("Analytic takes into account modifications to speeed an priority (Gen 5-8)"); //Eg. Paralysis, Power Weight, Stall +TO_DO_BATTLE_TEST("Analytic does not take into account modifications to speeed an priority (Gen 8)"); //Eg. Paralysis, Power Weight, Stall +TO_DO_BATTLE_TEST("Analytic takes into account the turn order of what fainted Pokémon would've moved"); + +// 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"); diff --git a/test/battle/ability/anger_point.c b/test/battle/ability/anger_point.c index 0b13b9df4..270d66f28 100644 --- a/test/battle/ability/anger_point.c +++ b/test/battle/ability/anger_point.c @@ -37,7 +37,7 @@ SINGLE_BATTLE_TEST("Anger Point does not trigger when already at maximum Attack MESSAGE("Primeape cut its own HP and maximized ATTACK!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_FROST_BREATH, opponent); MESSAGE("A critical hit!"); - NONE_OF { + 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!"); @@ -47,7 +47,9 @@ SINGLE_BATTLE_TEST("Anger Point does not trigger when already at maximum Attack } } -SINGLE_BATTLE_TEST("Anger Point does not trigger when a substitute takes the hit") +TO_DO_BATTLE_TEST("Anger Point triggers when a substitute takes the hit (Gen4)"); + +SINGLE_BATTLE_TEST("Anger Point does not trigger when a substitute takes the hit (Gen5+)") { ASSUME(gMovesInfo[MOVE_FROST_BREATH].alwaysCriticalHit); ASSUME(gMovesInfo[MOVE_SUBSTITUTE].effect == EFFECT_SUBSTITUTE); @@ -62,7 +64,7 @@ SINGLE_BATTLE_TEST("Anger Point does not trigger when a substitute takes the hit MESSAGE("Primeape made a SUBSTITUTE!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_FROST_BREATH, opponent); MESSAGE("A critical hit!"); - NONE_OF { + 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!"); diff --git a/test/battle/ability/anger_shell.c b/test/battle/ability/anger_shell.c index f0d11d757..c5b490216 100644 --- a/test/battle/ability/anger_shell.c +++ b/test/battle/ability/anger_shell.c @@ -1,7 +1,7 @@ #include "global.h" #include "test/battle.h" -SINGLE_BATTLE_TEST("Anger Shell activates only if the target had more than 50% of its hp") +SINGLE_BATTLE_TEST("Anger Shell activates only if the target had more than 50% of its HP") { bool32 activates = FALSE; u16 maxHp = 500, hp = 0; diff --git a/test/battle/ability/anticipation.c b/test/battle/ability/anticipation.c new file mode 100644 index 000000000..a15ff153d --- /dev/null +++ b/test/battle/ability/anticipation.c @@ -0,0 +1,24 @@ +#include "global.h" +#include "test/battle.h" + +TO_DO_BATTLE_TEST("Anticipation causes notifies if an opponent has a super-effective move"); +TO_DO_BATTLE_TEST("Anticipation causes notifies if an opponent has a One-hit KO move"); +TO_DO_BATTLE_TEST("Anticipation causes notifies if an opponent has a Self-Destruct or Explosion (Gen4)"); +TO_DO_BATTLE_TEST("Anticipation treats Self-Destruct and Explosion like all other Normal types (Gen5+)"); + +TO_DO_BATTLE_TEST("Anticipation considers Scrappy and Normalize into their effectiveness (Gen4)"); +TO_DO_BATTLE_TEST("Anticipation doesn't consider Scrappy and Normalize into their effectiveness (Gen5+)"); +TO_DO_BATTLE_TEST("Anticipation considers Gravity into their effectiveness (Gen4)"); +TO_DO_BATTLE_TEST("Anticipation doesn't consider Gravity into their effectiveness (Gen5+)"); +TO_DO_BATTLE_TEST("Anticipation doesn't trigger from Counter, Metal Burst or Mirror Coat (Gen4)"); +TO_DO_BATTLE_TEST("Anticipation counts Counter, Metal Burst or Mirror Coat as attacking moves of their types (Gen5+)"); +TO_DO_BATTLE_TEST("Anticipation considers Synchronoise as an ordinary Psychic-type move"); +TO_DO_BATTLE_TEST("Anticipation considers Freeze-Dry as an ordinary Ice-type move"); +TO_DO_BATTLE_TEST("Anticipation considers Flying Press as an ordinary Fighting-type move"); +TO_DO_BATTLE_TEST("Anticipation considers Aura Wheel as an ordinary Electric-type move"); +TO_DO_BATTLE_TEST("Anticipation considers Inverse Battle types"); //Check with Normal-type moves +TO_DO_BATTLE_TEST("Anticipation treats dynamic move types as their base type (Normal)"); // Judgment, Weather Ball, Natural Gift, Techno Blast, Revelation Dance, Multi Attack +TO_DO_BATTLE_TEST("Anticipation treats Hidden Power as Normal Type (Gen4-5)"); +TO_DO_BATTLE_TEST("Anticipation treats Hidden Power as its dynamic type (Gen6+)"); +TO_DO_BATTLE_TEST("Anticipation does not consider Strong Winds on type matchups"); +TO_DO_BATTLE_TEST("Anticipation does not consider ate-abilities"); diff --git a/test/battle/ability/arena_trap.c b/test/battle/ability/arena_trap.c new file mode 100644 index 000000000..ef0d5b7d8 --- /dev/null +++ b/test/battle/ability/arena_trap.c @@ -0,0 +1,11 @@ +#include "global.h" +#include "test/battle.h" + +TO_DO_BATTLE_TEST("Arena Trap prevents grounded adjacent opponents from switching out"); +TO_DO_BATTLE_TEST("Arena Trap doesn't prevent switch outs if the Pokémon is switched in the same turn the opponent decided to switch out"); +TO_DO_BATTLE_TEST("Arena Trap doesn't prevent switch outs via moves that switch out"); // Baton Pass, U-Turn, Volt Switch, Flip Turn, Parting Shot +TO_DO_BATTLE_TEST("Arena Trap doesn't prevent switch outs via Shed Shell, but not via Teleport"); +TO_DO_BATTLE_TEST("Arena Trap doesn't prevent switch outs via Run Away"); +TO_DO_BATTLE_TEST("Arena Trap doesn't prevent switch outs via Smoke Ball"); +TO_DO_BATTLE_TEST("Arena Trap prevents switch outs from Ghost-type Pokémon (Gen3-5)"); +TO_DO_BATTLE_TEST("Arena Trap doesn't prevent switch outs from Ghost-type Pokémon (Gen6+)"); diff --git a/test/battle/ability/aroma_veil.c b/test/battle/ability/aroma_veil.c new file mode 100644 index 000000000..dc53fa149 --- /dev/null +++ b/test/battle/ability/aroma_veil.c @@ -0,0 +1,15 @@ +#include "global.h" +#include "test/battle.h" + +TO_DO_BATTLE_TEST("Aroma Veil protects the Pokémon's side from Taunt"); +TO_DO_BATTLE_TEST("Aroma Veil protects the Pokémon's side from Torment"); +TO_DO_BATTLE_TEST("Aroma Veil protects the Pokémon's side from Encore"); +TO_DO_BATTLE_TEST("Aroma Veil protects the Pokémon's side from Disable"); +TO_DO_BATTLE_TEST("Aroma Veil protects the Pokémon's side from Cursed Body"); +TO_DO_BATTLE_TEST("Aroma Veil protects the Pokémon's side from Heal Block"); +TO_DO_BATTLE_TEST("Aroma Veil protects the Pokémon's side from Infatuation"); +TO_DO_BATTLE_TEST("Aroma Veil does not protect the Pokémon's side from Imprison"); + +// Marked in Bulbapedia as need of research +//TO_DO_BATTLE_TEST("Aroma Veil prevents G-Max Meltdown's effect"); +//TO_DO_BATTLE_TEST("Aroma Veil prevents Psychic Noise's effect"); diff --git a/test/battle/ability/as_one.c b/test/battle/ability/as_one.c new file mode 100644 index 000000000..0d5367bf3 --- /dev/null +++ b/test/battle/ability/as_one.c @@ -0,0 +1,7 @@ +#include "global.h" +#include "test/battle.h" + +// Tests for the individual ability effects are handled in the following times: +// - Unnerve: test/battle/ability/unnerve.c +// - Chilling Neigh: test/battle/ability/chilling_neigh.c +// - Grim Neigh: test/battle/ability/grim_neigh.c diff --git a/test/battle/ability/aura_break.c b/test/battle/ability/aura_break.c new file mode 100644 index 000000000..93b21421e --- /dev/null +++ b/test/battle/ability/aura_break.c @@ -0,0 +1,6 @@ +#include "global.h" +#include "test/battle.h" + +TO_DO_BATTLE_TEST("Aura Break inverts Fairy Aura's effect"); +TO_DO_BATTLE_TEST("Aura Break inverts Dark Aura's effect"); +TO_DO_BATTLE_TEST("Aura Break ignores Mold Breaker abilities"); diff --git a/test/battle/ability/chilling_neigh.c b/test/battle/ability/chilling_neigh.c new file mode 100644 index 000000000..03be77000 --- /dev/null +++ b/test/battle/ability/chilling_neigh.c @@ -0,0 +1,4 @@ +#include "global.h" +#include "test/battle.h" + +// Tests for Chilling Neigh are handled in test/battle/ability/moxie.c diff --git a/test/battle/ability/chlorophyll.c b/test/battle/ability/chlorophyll.c new file mode 100644 index 000000000..493a19763 --- /dev/null +++ b/test/battle/ability/chlorophyll.c @@ -0,0 +1,6 @@ +#include "global.h" +#include "test/battle.h" + +TO_DO_BATTLE_TEST("Chlorophyll doubles speed if it's sunny"); +TO_DO_BATTLE_TEST("Chlorophyll doesn't double speed if Cloud Nine/Air Lock is on the field"); +TO_DO_BATTLE_TEST("Chlorophyll doesn't double speed if they have an Utility Umbrella"); diff --git a/test/battle/ability/clear_body.c b/test/battle/ability/clear_body.c index 1379506a7..dc82be67b 100644 --- a/test/battle/ability/clear_body.c +++ b/test/battle/ability/clear_body.c @@ -127,7 +127,7 @@ SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent s } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPERPOWER, opponent); NONE_OF { - ABILITY_POPUP(opponent, ability); + ABILITY_POPUP(opponent, ability); MESSAGE("Foe Solgaleo's Full Metal Body prevents stat loss!"); MESSAGE("Foe Torkoal's White Smoke prevents stat loss!"); MESSAGE("Foe Metang's Clear Body prevents stat loss!"); @@ -186,7 +186,7 @@ SINGLE_BATTLE_TEST("Mold Breaker, Teravolt, and Turboblaze ignore Clear Body and else{ ANIMATION(ANIM_TYPE_MOVE, move, player); NONE_OF { - ABILITY_POPUP(opponent, ability); + ABILITY_POPUP(opponent, ability); MESSAGE("Foe Solgaleo's Full Metal Body prevents stat loss!"); MESSAGE("Foe Torkoal's White Smoke prevents stat loss!"); MESSAGE("Foe Metang's Clear Body prevents stat loss!"); diff --git a/test/battle/ability/cloud_nine.c b/test/battle/ability/cloud_nine.c index dfe5611fc..887769a5e 100644 --- a/test/battle/ability/cloud_nine.c +++ b/test/battle/ability/cloud_nine.c @@ -1,16 +1,41 @@ #include "global.h" #include "test/battle.h" -SINGLE_BATTLE_TEST("Cloud Nine prevents weather effects") +SINGLE_BATTLE_TEST("Cloud Nine/Air Lock prevent basic weather effects, but without them disappearing - Sandstorm") { + u32 species = 0, ability = 0; + PARAMETRIZE { species = SPECIES_PSYDUCK; ability = ABILITY_CLOUD_NINE; } + PARAMETRIZE { species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } GIVEN { ASSUME(gMovesInfo[MOVE_SANDSTORM].effect == EFFECT_SANDSTORM); - PLAYER(SPECIES_PSYDUCK) { Ability(ABILITY_CLOUD_NINE); } + PLAYER(species) { Ability(ability); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(opponent, MOVE_SANDSTORM); } TURN {} } SCENE { - NONE_OF { HP_BAR(player); } + ABILITY_POPUP(player, ability); + MESSAGE("The effects of weather disappeared."); + MESSAGE("Foe Wobbuffet used Sandstorm!"); + MESSAGE("The sandstorm rages."); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SANDSTORM_CONTINUES); + NONE_OF { + HP_BAR(player); + HP_BAR(opponent); + MESSAGE("Foe Wobbuffet is buffeted by the sandstorm!"); + } + MESSAGE("The sandstorm rages."); + 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"); +TO_DO_BATTLE_TEST("Cloud Nine/Air Lock prevent basic weather effects, but without them disappearing - Rain"); +TO_DO_BATTLE_TEST("Cloud Nine/Air Lock prevent basic weather effects, but without them disappearing - Hail"); +TO_DO_BATTLE_TEST("Cloud Nine/Air Lock prevent basic weather effects, but without them disappearing - Snow"); +TO_DO_BATTLE_TEST("Cloud Nine/Air Lock prevent basic weather effects, but without them disappearing - Fog"); +TO_DO_BATTLE_TEST("Cloud Nine/Air Lock prevent basic weather effects, but without them disappearing - Primal Sun"); +TO_DO_BATTLE_TEST("Cloud Nine/Air Lock prevent basic weather effects, but without them disappearing - Primal Rain"); +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 diff --git a/test/battle/ability/damp.c b/test/battle/ability/damp.c index 9a3b795f2..b567293aa 100644 --- a/test/battle/ability/damp.c +++ b/test/battle/ability/damp.c @@ -57,7 +57,7 @@ SINGLE_BATTLE_TEST("Damp prevents explosion-like moves from self") } } -SINGLE_BATTLE_TEST("Damp prevents damage from aftermath") +SINGLE_BATTLE_TEST("Damp prevents damage from Aftermath") { GIVEN { ASSUME(gMovesInfo[MOVE_TACKLE].makesContact); diff --git a/test/battle/ability/dancer.c b/test/battle/ability/dancer.c index f54f7ab76..660a719c6 100644 --- a/test/battle/ability/dancer.c +++ b/test/battle/ability/dancer.c @@ -126,3 +126,22 @@ DOUBLE_BATTLE_TEST("Dancer still triggers if another dancer flinches") ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); } } + +SINGLE_BATTLE_TEST("Dancer-called attacks have their type updated") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_REVELATION_DANCE].danceMove == TRUE); + ASSUME(gMovesInfo[MOVE_REVELATION_DANCE].effect == EFFECT_REVELATION_DANCE); + PLAYER(SPECIES_TANGROWTH); + OPPONENT(SPECIES_ORICORIO_BAILE); + } WHEN { + TURN { MOVE(player, MOVE_REVELATION_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_REVELATION_DANCE, player); + MESSAGE("It's not very effective…"); + ABILITY_POPUP(opponent, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_REVELATION_DANCE, opponent); + NOT MESSAGE("It's not very effective…"); + MESSAGE("It's super effective!"); + } +} diff --git a/test/battle/ability/disguise.c b/test/battle/ability/disguise.c index c4f79d8fe..5ab203532 100644 --- a/test/battle/ability/disguise.c +++ b/test/battle/ability/disguise.c @@ -134,3 +134,42 @@ SINGLE_BATTLE_TEST("Disguised Mimikyu is ignored by Mold Breaker") NOT ABILITY_POPUP(player, ABILITY_DISGUISE); } } + +SINGLE_BATTLE_TEST("Disguised Mimikyu's types revert back to Ghost/Fairy when Disguise is broken") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_SHADOW_CLAW].type == TYPE_GHOST); + PLAYER(SPECIES_MIMIKYU_DISGUISED) { Ability(ABILITY_DISGUISE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SOAK); } + TURN { MOVE(opponent, MOVE_TACKLE); } + TURN { MOVE(opponent, MOVE_SHADOW_CLAW); } + } SCENE { + MESSAGE("Foe Wobbuffet used Soak!"); + MESSAGE("Mimikyu transformed into the Water type!"); + MESSAGE("Foe Wobbuffet used Tackle!"); + ABILITY_POPUP(player, ABILITY_DISGUISE); + MESSAGE("Foe Wobbuffet used Shadow Claw!"); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Disguised Mimikyu blocks a move after getting Gastro Acid Batton Passed") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_BATON_PASS].effect == EFFECT_BATON_PASS); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_MIMIKYU_DISGUISED) { Ability(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); + } +} diff --git a/test/battle/ability/drought.c b/test/battle/ability/drought.c new file mode 100644 index 000000000..bbe4dbe85 --- /dev/null +++ b/test/battle/ability/drought.c @@ -0,0 +1,5 @@ +#include "global.h" +#include "test/battle.h" + +TO_DO_BATTLE_TEST("Drought sets up sun for 5 turns (Gen6+)"); +TO_DO_BATTLE_TEST("Drought sets up permanent sun (Gen3-5)"); diff --git a/test/battle/ability/dry_skin.c b/test/battle/ability/dry_skin.c index 368e4b804..5709a58a9 100644 --- a/test/battle/ability/dry_skin.c +++ b/test/battle/ability/dry_skin.c @@ -15,6 +15,8 @@ SINGLE_BATTLE_TEST("Dry Skin causes 1/8th Max HP damage in Sun") } } +TO_DO_BATTLE_TEST("Dry Skin doesn't get damaged in Sun if Cloud Nine/Air Lock is on the field"); + SINGLE_BATTLE_TEST("Dry Skin heals 1/8th Max HP in Rain") { GIVEN { @@ -29,6 +31,8 @@ SINGLE_BATTLE_TEST("Dry Skin heals 1/8th Max HP in Rain") } } +TO_DO_BATTLE_TEST("Dry Skin doesn't heal in Rain if Cloud Nine/Air Lock is on the field"); + SINGLE_BATTLE_TEST("Dry Skin increases damage taken from Fire-type moves by 25%", s16 damage) { u32 ability; diff --git a/test/battle/ability/flower_gift.c b/test/battle/ability/flower_gift.c index 69a76b8e8..68712641e 100644 --- a/test/battle/ability/flower_gift.c +++ b/test/battle/ability/flower_gift.c @@ -17,6 +17,8 @@ SINGLE_BATTLE_TEST("Flower Gift transforms Cherrim in harsh sunlight") } } +TO_DO_BATTLE_TEST("Flower Gift doesn't transform Cherrim if Cloud Nine/Air Lock is on the field"); + SINGLE_BATTLE_TEST("Flower Gift transforms Cherrim back to normal when weather changes") { GIVEN { @@ -61,6 +63,8 @@ SINGLE_BATTLE_TEST("Flower Gift transforms Cherrim back to normal when its abili } } +TO_DO_BATTLE_TEST("Forecast transforms Castform back to normal under Cloud Nine/Air Lock"); + DOUBLE_BATTLE_TEST("Flower Gift increases the attack of Cherrim and its allies by 1.5x", s16 damageL, s16 damageR) { bool32 sunny; diff --git a/test/battle/ability/forecast.c b/test/battle/ability/forecast.c index 427a8e680..24a0eed95 100644 --- a/test/battle/ability/forecast.c +++ b/test/battle/ability/forecast.c @@ -264,12 +264,15 @@ SINGLE_BATTLE_TEST("Forecast transforms Castform back to normal when Sandstorm i } } -SINGLE_BATTLE_TEST("Forecast transforms Castform back to normal under Air Lock") +SINGLE_BATTLE_TEST("Forecast transforms Castform back to normal under Cloud Nine/Air Lock") { + u32 species = 0, 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_FORECAST); } OPPONENT(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_RAYQUAZA); + OPPONENT(species) { Ability(ability); } } WHEN { TURN { MOVE(player, MOVE_RAIN_DANCE); } TURN { SWITCH(opponent, 1); } @@ -279,7 +282,7 @@ SINGLE_BATTLE_TEST("Forecast transforms Castform back to normal under Air Lock") ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); MESSAGE("Castform transformed!"); // back to normal - ABILITY_POPUP(opponent, ABILITY_AIR_LOCK); + ABILITY_POPUP(opponent, ability); ABILITY_POPUP(player, ABILITY_FORECAST); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); MESSAGE("Castform transformed!"); @@ -394,3 +397,25 @@ SINGLE_BATTLE_TEST("Forecast transforms Castform back when it uses a move that f EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), SPECIES_CASTFORM); } } + +SINGLE_BATTLE_TEST("Forecast transforms Castform when Cloud Nine ability user leaves the field") +{ + u32 species = 0, ability = 0; + PARAMETRIZE { species = SPECIES_PSYDUCK; ability = ABILITY_CLOUD_NINE; } + PARAMETRIZE { species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } + + GIVEN { + PLAYER(SPECIES_CASTFORM) { Ability(ABILITY_FORECAST); } + OPPONENT(species) { Ability(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!"); + } +} diff --git a/test/battle/ability/ate_abilities.c b/test/battle/ability/galvanize.c similarity index 50% rename from test/battle/ability/ate_abilities.c rename to test/battle/ability/galvanize.c index 203452cc0..55dfd7287 100644 --- a/test/battle/ability/ate_abilities.c +++ b/test/battle/ability/galvanize.c @@ -7,6 +7,19 @@ ASSUMPTIONS ASSUME(gMovesInfo[MOVE_TACKLE].power > 0); } +SINGLE_BATTLE_TEST("Galvanize turns a normal type move into Electric") +{ + GIVEN { + PLAYER(SPECIES_KRABBY); + OPPONENT(SPECIES_GEODUDE_ALOLAN) { Ability(ABILITY_GALVANIZE); } + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + MESSAGE("It's super effective!"); + } +} + SINGLE_BATTLE_TEST("Galvanize can not turn certain moves into Electric type moves") { u32 move; @@ -29,56 +42,7 @@ SINGLE_BATTLE_TEST("Galvanize can not turn certain moves into Electric type move } } -SINGLE_BATTLE_TEST("Galvanize turns a normal type move into Electric") -{ - GIVEN { - PLAYER(SPECIES_KRABBY); - OPPONENT(SPECIES_GEODUDE_ALOLAN) { Ability(ABILITY_GALVANIZE); } - } WHEN { - TURN { MOVE(opponent, MOVE_TACKLE); } - } SCENE { - ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); - MESSAGE("It's super effective!"); - } -} - -SINGLE_BATTLE_TEST("Pixilate turns a normal type move into Fairy") -{ - GIVEN { - PLAYER(SPECIES_DRAGONITE); - OPPONENT(SPECIES_ALTARIA) { Item(ITEM_ALTARIANITE); } - } WHEN { - TURN { MOVE(opponent, MOVE_TACKLE, gimmick: GIMMICK_MEGA); } - } SCENE { - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponent); - ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); - MESSAGE("It's super effective!"); - } -} - -SINGLE_BATTLE_TEST("Refrigerate turns a normal type move into Ice") -{ - GIVEN { - PLAYER(SPECIES_MEGANIUM); - OPPONENT(SPECIES_AMAURA) { Ability(ABILITY_REFRIGERATE); } - } WHEN { - TURN { MOVE(opponent, MOVE_TACKLE); } - } SCENE { - ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); - MESSAGE("It's super effective!"); - } -} - -SINGLE_BATTLE_TEST("Aerilate turns a normal type move into Flying") -{ - GIVEN { - PLAYER(SPECIES_MEGANIUM); - OPPONENT(SPECIES_SALAMENCE) { Item(ITEM_SALAMENCITE); } - } WHEN { - TURN { MOVE(opponent, MOVE_TACKLE, gimmick: GIMMICK_MEGA); } - } SCENE { - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponent); - ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); - MESSAGE("It's super effective!"); - } -} +TO_DO_BATTLE_TEST("Galvanize boosts power of affected moves by 20% (Gen7+)"); +TO_DO_BATTLE_TEST("Galvanize boosts power of affected moves by 30% (Gen6)"); +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. diff --git a/test/battle/ability/grim_neigh.c b/test/battle/ability/grim_neigh.c new file mode 100644 index 000000000..b4d82487d --- /dev/null +++ b/test/battle/ability/grim_neigh.c @@ -0,0 +1,109 @@ +#include "global.h" +#include "test/battle.h" + +DOUBLE_BATTLE_TEST("Grim Neigh raises Sp. Attack by one stage after directly causing a Pokemon to faint") +{ + u32 species = 0, ability = 0, abilityPopUp = 0; + PARAMETRIZE { species = SPECIES_SPECTRIER; ability = ABILITY_GRIM_NEIGH; abilityPopUp = ABILITY_GRIM_NEIGH; } + PARAMETRIZE { species = SPECIES_CALYREX_SHADOW_RIDER; ability = ABILITY_AS_ONE_SHADOW_RIDER; abilityPopUp = ABILITY_GRIM_NEIGH; } + GIVEN { + ASSUME(gMovesInfo[MOVE_DISCHARGE].target == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(species) { Ability(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 { + int i; + + ANIMATION(ANIM_TYPE_MOVE, MOVE_DISCHARGE, playerLeft); + for (i = 0; i < 3; i++) { + ONE_OF { + MESSAGE("Snorunt fainted!"); + MESSAGE("Foe Glalie fainted!"); + MESSAGE("Foe Abra fainted!"); + } + ABILITY_POPUP(playerLeft, abilityPopUp); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + if (species == SPECIES_SPECTRIER) + MESSAGE("Spectrier's Grim Neigh raised its Sp. Atk!"); + else + MESSAGE("Calyrex's Grim Neigh raised its Sp. Atk!"); + } + } 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") +{ + u32 species = 0, ability = 0, abilityPopUp = 0; + PARAMETRIZE { species = SPECIES_SPECTRIER; ability = ABILITY_GRIM_NEIGH; abilityPopUp = ABILITY_GRIM_NEIGH; } + PARAMETRIZE { species = SPECIES_CALYREX_SHADOW_RIDER; ability = ABILITY_AS_ONE_SHADOW_RIDER; abilityPopUp = ABILITY_GRIM_NEIGH; } + GIVEN { + PLAYER(species) { Ability(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("Foe Glalie fainted!"); + MESSAGE("Foe 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") +{ + s16 damage[2]; + u32 species = 0, ability = 0, abilityPopUp = 0; + PARAMETRIZE { species = SPECIES_SPECTRIER; ability = ABILITY_GRIM_NEIGH; abilityPopUp = ABILITY_GRIM_NEIGH; } + PARAMETRIZE { species = SPECIES_CALYREX_SHADOW_RIDER; ability = ABILITY_AS_ONE_SHADOW_RIDER; abilityPopUp = ABILITY_GRIM_NEIGH; } + + KNOWN_FAILING; // Requires simultaneous damage implementation + GIVEN { + ASSUME(gMovesInfo[MOVE_DISCHARGE].target == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(species) { Ability(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); + MESSAGE("Abra fainted!"); + ABILITY_POPUP(playerLeft, abilityPopUp); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + if (species == SPECIES_SPECTRIER) + MESSAGE("Spectrier's Grim Neigh raised its Sp. Atk!"); + else + MESSAGE("Calyrex's Grim Neigh raised its Sp. Atk!"); + HP_BAR(opponentRight, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(damage[0], damage[1]); + } +} diff --git a/test/battle/ability/gulp_missile.c b/test/battle/ability/gulp_missile.c index 2ec9acc61..7ccf1063b 100644 --- a/test/battle/ability/gulp_missile.c +++ b/test/battle/ability/gulp_missile.c @@ -116,7 +116,7 @@ SINGLE_BATTLE_TEST("(Gulp Missile) Cramorant in Gorging paralyzes the target if } SINGLE_BATTLE_TEST("(Gulp Missile) triggers even if the user is fainted by opposing mon") -{ +{ GIVEN { PLAYER(SPECIES_CRAMORANT) { HP(1); MaxHP(250); Ability(ABILITY_GULP_MISSILE); } PLAYER(SPECIES_WOBBUFFET); diff --git a/test/battle/ability/harvest.c b/test/battle/ability/harvest.c index 9fb969b03..f19e0715d 100644 --- a/test/battle/ability/harvest.c +++ b/test/battle/ability/harvest.c @@ -1,17 +1,264 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Harvest has a 50% chance to restore a Berry at the end of the turn"); -TO_DO_BATTLE_TEST("Harvest always restores a Berry in Sunlight"); -TO_DO_BATTLE_TEST("Harvest restores a Berry even after being switched out and back in"); -TO_DO_BATTLE_TEST("Harvest restores a Berry consumed by Fling"); -TO_DO_BATTLE_TEST("Harvest restores a Berry consumed by Natural Gift"); +ASSUMPTIONS +{ + ASSUME(gItemsInfo[ITEM_SITRUS_BERRY].holdEffect == HOLD_EFFECT_RESTORE_PCT_HP); + ASSUME(I_SITRUS_BERRY_HEAL >= GEN_4); + ASSUME(gMovesInfo[MOVE_SUNNY_DAY].effect == EFFECT_SUNNY_DAY); +} + +SINGLE_BATTLE_TEST("Harvest has a 50% chance to restore a Berry at the end of the turn") +{ + PASSES_RANDOMLY(1, 2, RNG_HARVEST); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Harvest always restores a Berry in Sunlight") +{ + PASSES_RANDOMLY(1, 1, RNG_HARVEST); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, 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") +{ + PASSES_RANDOMLY(1, 2, RNG_HARVEST); + GIVEN { + PLAYER(SPECIES_GOLDUCK) { Ability(ABILITY_CLOUD_NINE); } + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, 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") +{ + ASSUME(gMovesInfo[MOVE_PARTING_SHOT].effect == EFFECT_PARTING_SHOT); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); 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_TACKLE, 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") +{ + ASSUME(gMovesInfo[MOVE_FLING].effect == EFFECT_FLING); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(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") +{ + ASSUME(gMovesInfo[MOVE_NATURAL_GIFT].effect == EFFECT_NATURAL_GIFT); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(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"); // Check for berries that are consumed immediately, like Pecha Berry -TO_DO_BATTLE_TEST("Harvest doesn't restore a Berry when destroyed by Incinerate"); -TO_DO_BATTLE_TEST("Harvest doesn't restore a Berry when knocked off by Knock Off"); -TO_DO_BATTLE_TEST("Harvest doesn't restore a Berry when eaten by Bug Bite/Pluck"); -TO_DO_BATTLE_TEST("Harvest doesn't restore a Berry that's collected via Pickup"); -TO_DO_BATTLE_TEST("Harvest order is affected by speed"); -TO_DO_BATTLE_TEST("Harvest doesn't restore a Berry when transfered to another Pokémon"); -TO_DO_BATTLE_TEST("Harvest can restore a Berry that was transferred from another Pokémon"); -TO_DO_BATTLE_TEST("Harvest can only restore the newest berry consumed that was transferred from another Pokémon instead of its original Berry"); + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry when destroyed by Incinerate") +{ + PASSES_RANDOMLY(1, 1, RNG_HARVEST); + ASSUME(MoveHasAdditionalEffect(MOVE_INCINERATE, MOVE_EFFECT_INCINERATE)); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(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") +{ + PASSES_RANDOMLY(1, 1, RNG_HARVEST); + ASSUME(MoveHasAdditionalEffect(MOVE_KNOCK_OFF, MOVE_EFFECT_KNOCK_OFF)); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(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") +{ + PASSES_RANDOMLY(1, 1, RNG_HARVEST); + ASSUME(MoveHasAdditionalEffect(MOVE_BUG_BITE, MOVE_EFFECT_BUG_BITE)); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(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") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Speed(50); Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_EXEGGUTOR) { Speed(10); Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, 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") +{ + GIVEN { + PLAYER(SPECIES_EXEGGUTOR) { Speed(2); Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_EXEGGUTOR) { Speed(10); Ability(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") +{ + ASSUME(gMovesInfo[MOVE_TRICK].effect == EFFECT_TRICK); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(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") +{ + ASSUME(gMovesInfo[MOVE_TRICK].effect == EFFECT_TRICK); + GIVEN { + PLAYER(SPECIES_TORKOAL) { Ability(ABILITY_DROUGHT); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); HP(100); MaxHP(500); } + } WHEN { + TURN { MOVE(opponent, MOVE_TRICK); MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, 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") +{ + ASSUME(gMovesInfo[MOVE_TRICK].effect == EFFECT_TRICK); + ASSUME(gItemsInfo[ITEM_APICOT_BERRY].holdEffect == HOLD_EFFECT_SP_DEFENSE_UP); + GIVEN { + PLAYER(SPECIES_TORKOAL) { Ability(ABILITY_DROUGHT); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); HP(100); MaxHP(500); Item(ITEM_APICOT_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_TRICK); MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, 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 + } +} diff --git a/test/battle/ability/hydration.c b/test/battle/ability/hydration.c index 2dca490c0..7d16ad04a 100644 --- a/test/battle/ability/hydration.c +++ b/test/battle/ability/hydration.c @@ -14,3 +14,5 @@ SINGLE_BATTLE_TEST("Hydration cures non-volatile Status conditions if it is rain STATUS_ICON(player, none: TRUE); } } + +TO_DO_BATTLE_TEST("Hydration doesn't cure status conditions if Cloud Nine/Air Lock is on the field"); diff --git a/test/battle/ability/ice_body.c b/test/battle/ability/ice_body.c index 1aa7c80a4..3f278a50c 100644 --- a/test/battle/ability/ice_body.c +++ b/test/battle/ability/ice_body.c @@ -1,13 +1,21 @@ #include "global.h" #include "test/battle.h" +ASSUMPTIONS { + ASSUME(gMovesInfo[MOVE_HAIL].effect == EFFECT_HAIL); + ASSUME(gMovesInfo[MOVE_SNOWSCAPE].effect == EFFECT_SNOWSCAPE); +} + SINGLE_BATTLE_TEST("Ice Body prevents damage from hail") { + u32 move; + PARAMETRIZE { move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_GLALIE) { Ability(ABILITY_ICE_BODY); } } WHEN { - TURN { MOVE(player, MOVE_HAIL); MOVE(opponent, MOVE_SKILL_SWAP); } + TURN { MOVE(player, move); MOVE(opponent, MOVE_SKILL_SWAP); } } SCENE { NONE_OF { HP_BAR(player); } } @@ -15,14 +23,32 @@ SINGLE_BATTLE_TEST("Ice Body prevents damage from hail") SINGLE_BATTLE_TEST("Ice Body recovers 1/16th of Max HP in hail.") { + u32 move; + PARAMETRIZE { move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } GIVEN { PLAYER(SPECIES_GLALIE) { Ability(ABILITY_ICE_BODY); HP(1); MaxHP(100); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(opponent, MOVE_HAIL); } + 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") +{ + u32 move; + PARAMETRIZE { move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + GIVEN { + PLAYER(SPECIES_GLALIE) { Ability(ABILITY_ICE_BODY); HP(1); MaxHP(100); } + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ICE_BODY); + } +} diff --git a/test/battle/ability/ice_face.c b/test/battle/ability/ice_face.c new file mode 100644 index 000000000..54a307754 --- /dev/null +++ b/test/battle/ability/ice_face.c @@ -0,0 +1,137 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Ice Face blocks physical moves, changing Eiscue into its Noice Face form") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_EISCUE); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + } 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") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(gMovesInfo[MOVE_EMBER].category == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_EISCUE); + 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") +{ + u32 move; + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + PARAMETRIZE { move = MOVE_HAIL; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(gMovesInfo[MOVE_SNOWSCAPE].effect == EFFECT_SNOWSCAPE); + ASSUME(gMovesInfo[MOVE_HAIL].effect == EFFECT_HAIL); + PLAYER(SPECIES_EISCUE); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + TURN { MOVE(opponent, move); } + TURN { MOVE(opponent, MOVE_TACKLE); } + } 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") +{ + u32 move; + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + PARAMETRIZE { move = MOVE_HAIL; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(gMovesInfo[MOVE_SNOWSCAPE].effect == EFFECT_SNOWSCAPE); + ASSUME(gMovesInfo[MOVE_HAIL].effect == EFFECT_HAIL); + PLAYER(SPECIES_EISCUE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_TACKLE); } + TURN { SWITCH(player, 1); MOVE(opponent, move); } + TURN { SWITCH(player, 0); MOVE(opponent, MOVE_TACKLE); } + } 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") +{ + u32 move; + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + PARAMETRIZE { move = MOVE_HAIL; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(gMovesInfo[MOVE_SNOWSCAPE].effect == EFFECT_SNOWSCAPE); + ASSUME(gMovesInfo[MOVE_HAIL].effect == EFFECT_HAIL); + PLAYER(SPECIES_EISCUE) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); MOVE(opponent, MOVE_TACKLE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_TACKLE); } + } 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") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_EISCUE) { HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_CELEBRATE); } + TURN { SWITCH(player, 0); MOVE(opponent, MOVE_TACKLE); 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") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_EISCUE) { HP(1); } + OPPONENT(SPECIES_RAYQUAZA) { Ability(ABILITY_AIR_LOCK); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_TACKLE); } + TURN { MOVE(player, MOVE_SNOWSCAPE); MOVE(opponent, MOVE_TACKLE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + MESSAGE("Eiscue fainted!"); + } +} diff --git a/test/battle/ability/leaf_guard.c b/test/battle/ability/leaf_guard.c index 6c559ea48..2c55236de 100644 --- a/test/battle/ability/leaf_guard.c +++ b/test/battle/ability/leaf_guard.c @@ -27,6 +27,8 @@ SINGLE_BATTLE_TEST("Leaf Guard prevents non-volatile status conditions in sun") } } +TO_DO_BATTLE_TEST("Leaf Guard doesn't prevent non-volatile status conditions if Cloud Nine/Air Lock is on the field"); + SINGLE_BATTLE_TEST("Leaf Guard prevents status conditions from Flame Orb and Toxic Orb") { u32 item; @@ -49,6 +51,8 @@ SINGLE_BATTLE_TEST("Leaf Guard prevents status conditions from Flame Orb and Tox } } +TO_DO_BATTLE_TEST("Leaf Guard doesn't prevent status conditions from Flame Orb and Toxic Orb if Cloud Nine/Air Lock is on the field"); + SINGLE_BATTLE_TEST("Leaf Guard prevents Rest during sun") { GIVEN { @@ -66,3 +70,5 @@ SINGLE_BATTLE_TEST("Leaf Guard prevents Rest during sun") } } } + +TO_DO_BATTLE_TEST("Leaf Guard doesn't prevent Rest if Cloud Nine/Air Lock is on the field"); diff --git a/test/battle/ability/magic_guard.c b/test/battle/ability/magic_guard.c index 69e8bac9c..557965226 100644 --- a/test/battle/ability/magic_guard.c +++ b/test/battle/ability/magic_guard.c @@ -13,5 +13,5 @@ SINGLE_BATTLE_TEST("Magic Guard prevents recoil damage to the user") ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_EDGE, player); HP_BAR(opponent); NOT HP_BAR(player); - } + } } diff --git a/test/battle/ability/moxie.c b/test/battle/ability/moxie.c index b60a11507..79b1fc989 100644 --- a/test/battle/ability/moxie.c +++ b/test/battle/ability/moxie.c @@ -1,12 +1,15 @@ #include "global.h" #include "test/battle.h" -DOUBLE_BATTLE_TEST("Moxie raises Attack by one stage after directly causing a Pokemon to faint") +DOUBLE_BATTLE_TEST("Moxie/Chilling Neigh raises Attack by one stage after directly causing a Pokemon to faint") { - ASSUME(gMovesInfo[MOVE_EARTHQUAKE].target == MOVE_TARGET_FOES_AND_ALLY); - + u32 species = 0, ability = 0, abilityPopUp = 0; + 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_RIDER; ability = ABILITY_AS_ONE_ICE_RIDER; abilityPopUp = ABILITY_CHILLING_NEIGH; } GIVEN { - PLAYER(SPECIES_SALAMENCE) { Ability(ABILITY_MOXIE); } + ASSUME(gMovesInfo[MOVE_EARTHQUAKE].target == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(species) { Ability(ability); } PLAYER(SPECIES_SNORUNT) { HP(1); } OPPONENT(SPECIES_GLALIE) { HP(1); } OPPONENT(SPECIES_ABRA) { HP(1); } @@ -23,19 +26,28 @@ DOUBLE_BATTLE_TEST("Moxie raises Attack by one stage after directly causing a Po MESSAGE("Foe Glalie fainted!"); MESSAGE("Foe Abra fainted!"); } - ABILITY_POPUP(playerLeft, ABILITY_MOXIE); + ABILITY_POPUP(playerLeft, abilityPopUp); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); - MESSAGE("Salamence's Moxie raised its Attack!"); + if (species == SPECIES_SALAMENCE) + MESSAGE("Salamence's Moxie raised its Attack!"); + else if (species == SPECIES_GLASTRIER) + MESSAGE("Glastrier's Chilling Neigh raised its Attack!"); + else + MESSAGE("Calyrex's Chilling Neigh raised its Attack!"); } } THEN { EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 3); } } -DOUBLE_BATTLE_TEST("Moxie does not trigger if Pokemon faint to indirect damage or damage from other Pokemon") +DOUBLE_BATTLE_TEST("Moxie/Chilling Neigh does not trigger if Pokemon faint to indirect damage or damage from other Pokemon") { + u32 species = 0, ability = 0, abilityPopUp = 0; + 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_RIDER; ability = ABILITY_AS_ONE_ICE_RIDER; abilityPopUp = ABILITY_CHILLING_NEIGH; } GIVEN { - PLAYER(SPECIES_SALAMENCE) { Ability(ABILITY_MOXIE); } + PLAYER(species) { Ability(ability); } PLAYER(SPECIES_SNORUNT) { HP(1); Status1(STATUS1_POISON); } OPPONENT(SPECIES_GLALIE) { HP(1); Status1(STATUS1_BURN); } OPPONENT(SPECIES_ABRA) { HP(1); } @@ -53,9 +65,11 @@ DOUBLE_BATTLE_TEST("Moxie does not trigger if Pokemon faint to indirect damage o MESSAGE("Foe Abra fainted!"); } NONE_OF { - ABILITY_POPUP(playerLeft, ABILITY_MOXIE); + 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 { @@ -63,12 +77,15 @@ DOUBLE_BATTLE_TEST("Moxie does not trigger if Pokemon faint to indirect damage o } } -SINGLE_BATTLE_TEST("Moxie does not trigger when already at maximum Attack stage") +SINGLE_BATTLE_TEST("Moxie/Chilling Neigh does not trigger when already at maximum Attack stage") { - ASSUME(gMovesInfo[MOVE_BELLY_DRUM].effect == EFFECT_BELLY_DRUM); - + u32 species = 0, ability = 0, abilityPopUp = 0; + 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_RIDER; ability = ABILITY_AS_ONE_ICE_RIDER; abilityPopUp = ABILITY_CHILLING_NEIGH; } GIVEN { - PLAYER(SPECIES_SALAMENCE) { Ability(ABILITY_MOXIE); } + ASSUME(gMovesInfo[MOVE_BELLY_DRUM].effect == EFFECT_BELLY_DRUM); + PLAYER(species) { Ability(ability); } OPPONENT(SPECIES_SNORUNT) { HP(1); } OPPONENT(SPECIES_SNORUNT); } WHEN { @@ -77,28 +94,38 @@ SINGLE_BATTLE_TEST("Moxie does not trigger when already at maximum Attack stage" } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_BELLY_DRUM, player); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); - MESSAGE("Salamence cut its own HP and maximized ATTACK!"); + if (species == SPECIES_SALAMENCE) + MESSAGE("Salamence cut its own HP and maximized ATTACK!"); + else if (species == SPECIES_GLASTRIER) + MESSAGE("Glastrier cut its own HP and maximized ATTACK!"); + else + MESSAGE("Calyrex cut its own HP and maximized ATTACK!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player); MESSAGE("Foe Snorunt fainted!"); - NONE_OF { - ABILITY_POPUP(player, ABILITY_MOXIE); + 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 does not increase damage done by the same move that causes another Pokemon to faint") +DOUBLE_BATTLE_TEST("Moxie/Chilling Neigh does not increase damage done by the same move that causes another Pokemon to faint") { s16 damage[2]; - - ASSUME(gMovesInfo[MOVE_EARTHQUAKE].target == MOVE_TARGET_FOES_AND_ALLY); + u32 species = 0, ability = 0, abilityPopUp = 0; + 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_RIDER; ability = ABILITY_AS_ONE_ICE_RIDER; abilityPopUp = ABILITY_CHILLING_NEIGH; } KNOWN_FAILING; // Requires simultaneous damage implementation GIVEN { - PLAYER(SPECIES_SALAMENCE) { Ability(ABILITY_MOXIE); } + ASSUME(gMovesInfo[MOVE_EARTHQUAKE].target == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(species) { Ability(ability); } PLAYER(SPECIES_ABRA) { HP(1); } OPPONENT(SPECIES_GLALIE); OPPONENT(SPECIES_GLALIE); @@ -110,9 +137,14 @@ DOUBLE_BATTLE_TEST("Moxie does not increase damage done by the same move that ca HP_BAR(opponentLeft, captureDamage: &damage[0]); HP_BAR(playerRight); MESSAGE("Abra fainted!"); - ABILITY_POPUP(playerLeft, ABILITY_MOXIE); + ABILITY_POPUP(playerLeft, abilityPopUp); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); - MESSAGE("Salamence's Moxie raised its Attack!"); + if (species == SPECIES_SALAMENCE) + MESSAGE("Salamence's Moxie raised its Attack!"); + else if (species == SPECIES_GLASTRIER) + MESSAGE("Glastrier's Chilling Neigh raised its Attack!"); + else + MESSAGE("Calyrex's Chilling Neigh raised its Attack!"); HP_BAR(opponentRight, captureDamage: &damage[1]); } THEN { EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); diff --git a/test/battle/ability/normalize.c b/test/battle/ability/normalize.c new file mode 100644 index 000000000..d3e8df6be --- /dev/null +++ b/test/battle/ability/normalize.c @@ -0,0 +1,16 @@ +#include "global.h" +#include "test/battle.h" + +TO_DO_BATTLE_TEST("Normalize tuns a move into a Normal-type move"); +TO_DO_BATTLE_TEST("Normalize boosts power of both affected and originally Normal-type moves by 20% (Gen7+)"); +TO_DO_BATTLE_TEST("Normalize affects status moves"); // Eg. Thunder Wave can affect Ground types +TO_DO_BATTLE_TEST("Normalize makes Flying Press do Normal/Flying damage"); +TO_DO_BATTLE_TEST("Normalize still makes Freeze-Dry do super effective damage to Water-type Pokémon"); +TO_DO_BATTLE_TEST("Normalize-affected moves become Electric-type under Electrify's effect"); +TO_DO_BATTLE_TEST("Normalize-affected moves become Electric-type under Ion Deluge's effect"); +TO_DO_BATTLE_TEST("Normalize doesn't affect Hidden Power's type"); +TO_DO_BATTLE_TEST("Normalize doesn't affect Weather Ball's type"); +TO_DO_BATTLE_TEST("Normalize doesn't affect Natural Gift's type"); +TO_DO_BATTLE_TEST("Normalize doesn't affect Judgment/Techno Blast/Multi-Attack's type"); +TO_DO_BATTLE_TEST("Normalize doesn't affect Terrain Pulse's type"); +TO_DO_BATTLE_TEST("Normalize doesn't affect damaging Z-Move types"); diff --git a/test/battle/ability/orichalcum_pulse.c b/test/battle/ability/orichalcum_pulse.c new file mode 100644 index 000000000..295e0db23 --- /dev/null +++ b/test/battle/ability/orichalcum_pulse.c @@ -0,0 +1,5 @@ +#include "global.h" +#include "test/battle.h" + +TO_DO_BATTLE_TEST("Orichalcum Pulse sets up sun for 5 turns"); +TO_DO_BATTLE_TEST("Orichalcum Pulse boosts the Pokémon's Attack by 33% in sun, even if it's holding an Utility Umbrella"); diff --git a/test/battle/ability/pickup.c b/test/battle/ability/pickup.c new file mode 100644 index 000000000..57f6429fe --- /dev/null +++ b/test/battle/ability/pickup.c @@ -0,0 +1,295 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gItemsInfo[ITEM_SITRUS_BERRY].holdEffect == HOLD_EFFECT_RESTORE_PCT_HP); + ASSUME(I_SITRUS_BERRY_HEAL >= GEN_4); +} + +SINGLE_BATTLE_TEST("Pickup grants an item used by another Pokémon") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + 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") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, 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") +{ + ASSUME(gItemsInfo[ITEM_AIR_BALLOON].holdEffect == HOLD_EFFECT_AIR_BALLOON); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_AIR_BALLOON); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, 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") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, 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") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(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_TACKLE); MOVE(opponent, MOVE_MEMENTO); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, 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") +{ + ASSUME(gMovesInfo[MOVE_PARTING_SHOT].effect == EFFECT_PARTING_SHOT); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ZIGZAGOON) { Ability(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_TACKLE); 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_TACKLE, 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") +{ + ASSUME(MoveHasAdditionalEffect(MOVE_INCINERATE, MOVE_EFFECT_INCINERATE)); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(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") +{ + ASSUME(MoveHasAdditionalEffect(MOVE_KNOCK_OFF, MOVE_EFFECT_KNOCK_OFF)); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(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") +{ + ASSUME(MoveHasAdditionalEffect(MOVE_BUG_BITE, MOVE_EFFECT_BUG_BITE)); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(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") +{ + ASSUME(gMovesInfo[MOVE_RECYCLE].effect == EFFECT_RECYCLE); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_RECYCLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, 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") +{ + ASSUME(gMovesInfo[MOVE_FLING].effect == EFFECT_FLING); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(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") +{ + ASSUME(gMovesInfo[MOVE_NATURAL_GIFT].effect == EFFECT_NATURAL_GIFT); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(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") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Speed(1); Ability(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_PICKUP); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, 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") +{ + PASSES_RANDOMLY(1, 3, RNG_PICKUP); + ASSUME(gItemsInfo[ITEM_WHITE_HERB].holdEffect == HOLD_EFFECT_RESTORE_STATS); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(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") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { HP(1); Ability(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); + } +} diff --git a/test/battle/ability/pixilate.c b/test/battle/ability/pixilate.c new file mode 100644 index 000000000..97c9c37a0 --- /dev/null +++ b/test/battle/ability/pixilate.c @@ -0,0 +1,35 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_TACKLE].type == TYPE_NORMAL); + ASSUME(gMovesInfo[MOVE_TACKLE].power > 0); +} + +SINGLE_BATTLE_TEST("Pixilate turns a Normal-type move into a Fairy-type move") +{ + GIVEN { + PLAYER(SPECIES_DRAGONITE); + OPPONENT(SPECIES_ALTARIA) { Item(ITEM_ALTARIANITE); } + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE, gimmick: GIMMICK_MEGA); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + MESSAGE("It's super effective!"); + } +} + +TO_DO_BATTLE_TEST("Pixilate can not turn certain moves into Fairy type moves"); +TO_DO_BATTLE_TEST("Pixilate boosts power of affected moves by 20% (Gen7+)"); +TO_DO_BATTLE_TEST("Pixilate boosts power of affected moves by 30% (Gen6)"); +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"); + +// Gen 6-7 +TO_DO_BATTLE_TEST("Pixilate overrides Electrify (Gen6-7)"); +TO_DO_BATTLE_TEST("Pixilate overrides Ion Deluge (Gen6-7)"); +// Gen 8+ +TO_DO_BATTLE_TEST("Pixilate doesn't override Electrify (Gen8+)"); +//TO_DO_BATTLE_TEST("Pixilate doesn't override Ion Deluge (Gen8+)"); // Ion Deluge doesn't exist in Gen 8+, but we probably could assume it behaves similar to under Electrify. TODO: Test by hacking SV. diff --git a/test/battle/ability/protosynthesis.c b/test/battle/ability/protosynthesis.c index 3cb35164e..58f10b366 100644 --- a/test/battle/ability/protosynthesis.c +++ b/test/battle/ability/protosynthesis.c @@ -99,3 +99,7 @@ SINGLE_BATTLE_TEST("Protosynthesis activates on switch-in") MESSAGE("Roaring Moon's Attack was heightened!"); } } + +TO_DO_BATTLE_TEST("Protosynthesis activates in sun before Booster Energy"); +TO_DO_BATTLE_TEST("Protosynthesis activates even if the Pokémon is holding an Utility Umbrella"); +TO_DO_BATTLE_TEST("Protosynthesis doesn't activate if Cloud Nine/Air Lock is on the field"); diff --git a/test/battle/ability/purifying_salt.c b/test/battle/ability/purifying_salt.c index e18826249..4c25321e5 100644 --- a/test/battle/ability/purifying_salt.c +++ b/test/battle/ability/purifying_salt.c @@ -19,6 +19,24 @@ SINGLE_BATTLE_TEST("Purifying Salt halves damage from Ghost-type moves", s16 dam } } +SINGLE_BATTLE_TEST("Purifying Salt halves damage from dynamic Ghost-type moves", s16 damage) +{ + u16 ability; + PARAMETRIZE { ability = ABILITY_STURDY; } + PARAMETRIZE { ability = ABILITY_PURIFYING_SALT; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TERA_BLAST].effect == EFFECT_TERA_BLAST); + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_GHOST); } + OPPONENT(SPECIES_GARGANACL) { Ability(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") { GIVEN { diff --git a/test/battle/ability/rain_dish.c b/test/battle/ability/rain_dish.c index dd647f0eb..93f642c63 100644 --- a/test/battle/ability/rain_dish.c +++ b/test/battle/ability/rain_dish.c @@ -1,6 +1,10 @@ #include "global.h" #include "test/battle.h" +ASSUMPTIONS { + ASSUME(gMovesInfo[MOVE_RAIN_DANCE].effect == EFFECT_RAIN_DANCE); +} + SINGLE_BATTLE_TEST("Rain Dish recovers 1/16th of Max HP in Rain") { GIVEN { @@ -11,6 +15,18 @@ SINGLE_BATTLE_TEST("Rain Dish recovers 1/16th of Max HP in Rain") } SCENE { ABILITY_POPUP(player, ABILITY_RAIN_DISH); MESSAGE("Ludicolo's Rain Dish restored its HP a little!"); - HP_BAR(player, damage: -(100 / 16)); + HP_BAR(player, damage: -(100 / 16)); + } +} + +SINGLE_BATTLE_TEST("Rain Dish doesn't recover HP if Cloud Nine/Air Lock is on the field") +{ + GIVEN { + PLAYER(SPECIES_LUDICOLO) { Ability(ABILITY_RAIN_DISH); HP(1); MaxHP(100); } + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(opponent, MOVE_RAIN_DANCE); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_RAIN_DISH); } } diff --git a/test/battle/ability/refrigerate.c b/test/battle/ability/refrigerate.c new file mode 100644 index 000000000..dbbaa30eb --- /dev/null +++ b/test/battle/ability/refrigerate.c @@ -0,0 +1,34 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_TACKLE].type == TYPE_NORMAL); + ASSUME(gMovesInfo[MOVE_TACKLE].power > 0); +} + +SINGLE_BATTLE_TEST("Refrigerate turns a Normal-type move into a Ice-type move") +{ + GIVEN { + PLAYER(SPECIES_MEGANIUM); + OPPONENT(SPECIES_AMAURA) { Ability(ABILITY_REFRIGERATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + MESSAGE("It's super effective!"); + } +} + +TO_DO_BATTLE_TEST("Refrigerate can not turn certain moves into Ice type moves"); +TO_DO_BATTLE_TEST("Refrigerate boosts power of affected moves by 20% (Gen7+)"); +TO_DO_BATTLE_TEST("Refrigerate boosts power of affected moves by 30% (Gen6)"); +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. + +// Gen 6-7 +TO_DO_BATTLE_TEST("Refrigerate overrides Electrify (Gen6-7)"); +TO_DO_BATTLE_TEST("Refrigerate overrides Ion Deluge (Gen6-7)"); +// Gen 8+ +//TO_DO_BATTLE_TEST("Refrigerate doesn't override Electrify (Gen8+)"); // Bulbapedia doesn't list this effect, so it assumes it behaves like Pixilate. +//TO_DO_BATTLE_TEST("Refrigerate doesn't override Ion Deluge (Gen8+)"); // Ion Deluge doesn't exist in Gen 8+, but we probably could assume it behaves similar to under Electrify. TODO: Test by hacking SV. diff --git a/test/battle/ability/sand_force.c b/test/battle/ability/sand_force.c new file mode 100644 index 000000000..e17722a88 --- /dev/null +++ b/test/battle/ability/sand_force.c @@ -0,0 +1,6 @@ +#include "global.h" +#include "test/battle.h" + +TO_DO_BATTLE_TEST("Sand Force prevents damage from sandstorm"); +TO_DO_BATTLE_TEST("Sand Force increases the power of Rock-, Ground- and Steel-type moves by 30% in sandstorm"); +TO_DO_BATTLE_TEST("Sand Force increases move power if Cloud Nine/Air Lock is on the field"); diff --git a/test/battle/ability/sand_rush.c b/test/battle/ability/sand_rush.c new file mode 100644 index 000000000..fa7695c12 --- /dev/null +++ b/test/battle/ability/sand_rush.c @@ -0,0 +1,6 @@ +#include "global.h" +#include "test/battle.h" + +TO_DO_BATTLE_TEST("Sand Rush prevents damage from sandstorm"); +TO_DO_BATTLE_TEST("Sand Rush doubles speed from sandstorm"); +TO_DO_BATTLE_TEST("Sand Rush doesn't double speed if Cloud Nine/Air Lock is on the field"); diff --git a/test/battle/ability/sand_veil.c b/test/battle/ability/sand_veil.c index 9e4a634de..f42c26727 100644 --- a/test/battle/ability/sand_veil.c +++ b/test/battle/ability/sand_veil.c @@ -28,3 +28,6 @@ SINGLE_BATTLE_TEST("Sand Veil increases evasion during sandstorm") HP_BAR(player); } } + +TO_DO_BATTLE_TEST("Sand Veil doesn't prevent Sandstorm damage if Cloud Nine/Air Lock is on the field"); +TO_DO_BATTLE_TEST("Sand Veil doesn't increase evasion if Cloud Nine/Air Lock is on the field"); diff --git a/test/battle/ability/slush_rush.c b/test/battle/ability/slush_rush.c new file mode 100644 index 000000000..8ae62454b --- /dev/null +++ b/test/battle/ability/slush_rush.c @@ -0,0 +1,6 @@ +#include "global.h" +#include "test/battle.h" + +TO_DO_BATTLE_TEST("Slush Rush doubles speed from hail"); +TO_DO_BATTLE_TEST("Slush Rush doubles speed from snow"); +TO_DO_BATTLE_TEST("Slush Rush doesn't double speed if Cloud Nine/Air Lock is on the field"); diff --git a/test/battle/ability/snow_cloak.c b/test/battle/ability/snow_cloak.c index f1bfbcb9b..a4d1acadb 100644 --- a/test/battle/ability/snow_cloak.c +++ b/test/battle/ability/snow_cloak.c @@ -27,3 +27,6 @@ SINGLE_BATTLE_TEST("Snow Cloak increases evasion during hail") HP_BAR(player); } } + +TO_DO_BATTLE_TEST("Snow Cloak doesn't prevent hail damage if Cloud Nine/Air Lock is on the field"); +TO_DO_BATTLE_TEST("Snow Cloak doesn't increase evasion if Cloud Nine/Air Lock is on the field"); diff --git a/test/battle/ability/solar_power.c b/test/battle/ability/solar_power.c new file mode 100644 index 000000000..f14ea11ee --- /dev/null +++ b/test/battle/ability/solar_power.c @@ -0,0 +1,7 @@ +#include "global.h" +#include "test/battle.h" + +TO_DO_BATTLE_TEST("Solar Power increases a Sp. Attack by x1.5 in Sun"); +TO_DO_BATTLE_TEST("Solar Power doesn't increases a Sp. Attack if Cloud Nine/Air Lock is on the field"); +TO_DO_BATTLE_TEST("Solar Power causes the Pokémon to lose 1/8 max HP in Sun"); +TO_DO_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"); diff --git a/test/battle/ability/swift_swim.c b/test/battle/ability/swift_swim.c new file mode 100644 index 000000000..4282ac274 --- /dev/null +++ b/test/battle/ability/swift_swim.c @@ -0,0 +1,6 @@ +#include "global.h" +#include "test/battle.h" + +TO_DO_BATTLE_TEST("Swift Swim doubles speed if it's raining"); +TO_DO_BATTLE_TEST("Swift Swim doesn't double speed if Cloud Nine/Air Lock is on the field"); +TO_DO_BATTLE_TEST("Swift Swim doesn't double speed if they have an Utility Umbrella"); diff --git a/test/battle/ability/unnerve.c b/test/battle/ability/unnerve.c new file mode 100644 index 000000000..9ad4ee7e5 --- /dev/null +++ b/test/battle/ability/unnerve.c @@ -0,0 +1,6 @@ +#include "global.h" +#include "test/battle.h" + +// Remember to add a PARAMETRIZE for As One in the following tests: +TO_DO_BATTLE_TEST("Unnerve prevents opposing Pokémon from eating their own berries"); +TO_DO_BATTLE_TEST("Unnerve doesn't prevent opposing Pokémon from using Natural Gift"); diff --git a/test/battle/ability/wind_rider.c b/test/battle/ability/wind_rider.c new file mode 100644 index 000000000..57e6f0d27 --- /dev/null +++ b/test/battle/ability/wind_rider.c @@ -0,0 +1,127 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_TAILWIND].effect == EFFECT_TAILWIND); + ASSUME(gMovesInfo[MOVE_TAILWIND].windMove == TRUE); +} + +SINGLE_BATTLE_TEST("Wind Rider raises Attack by one stage if it sets up Tailwind") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRAMBLIN) { Ability(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("Foe 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") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRAMBLIN) { Ability(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("Foe 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") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRAMBLIN) { Ability(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("Foe 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") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_BRAMBLIN) { Ability(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("Foe 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 activates when it's no longer effected by Neutralizing Gas") +{ + GIVEN { + PLAYER(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRAMBLIN) { Ability(ABILITY_WIND_RIDER); } + } WHEN { + TURN { MOVE(opponent, MOVE_TAILWIND); } + TURN { SWITCH(player, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_NEUTRALIZING_GAS); + MESSAGE("Neutralizing Gas filled the area!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAILWIND, opponent); + SWITCH_OUT_MESSAGE("Weezing"); + MESSAGE("The effects of Neutralizing Gas wore off!"); + ABILITY_POPUP(opponent, ABILITY_WIND_RIDER); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Bramblin's Wind Rider raised its Attack!"); + } 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") +{ + ASSUME(gMovesInfo[MOVE_GUST].windMove == TRUE); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRAMBLIN) { Ability(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("Foe Bramblin's Attack rose!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} diff --git a/test/battle/ai/ai_flag_risky.c b/test/battle/ai/ai_flag_risky.c index e6156de5a..e1ceeb216 100644 --- a/test/battle/ai/ai_flag_risky.c +++ b/test/battle/ai/ai_flag_risky.c @@ -13,7 +13,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: AI will blindly Mirror Coat against specia ASSUME(gSpeciesInfo[SPECIES_GROVYLE].baseSpAttack == 85); ASSUME(gSpeciesInfo[SPECIES_GROVYLE].baseAttack == 65); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiRiskyFlag); - PLAYER(SPECIES_GROVYLE) { Level(20); Moves(MOVE_ENERGY_BALL); } + PLAYER(SPECIES_GROVYLE) { Level(20); Moves(MOVE_ENERGY_BALL); } OPPONENT(SPECIES_CASTFORM) { Level(20); Moves(MOVE_TACKLE, MOVE_MIRROR_COAT); } } WHEN { TURN { MOVE(player, MOVE_ENERGY_BALL) ; EXPECT_MOVE(opponent, aiRiskyFlag ? MOVE_MIRROR_COAT : MOVE_TACKLE); } @@ -32,7 +32,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: AI will blindly Counter against physical a ASSUME(gSpeciesInfo[SPECIES_MARSHTOMP].baseAttack == 85); ASSUME(gSpeciesInfo[SPECIES_MARSHTOMP].baseSpAttack == 60); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiRiskyFlag); - PLAYER(SPECIES_MARSHTOMP) { Level(20); Moves(MOVE_WATERFALL); } + PLAYER(SPECIES_MARSHTOMP) { Level(20); Moves(MOVE_WATERFALL); } OPPONENT(SPECIES_CASTFORM) { Level(20); Moves(MOVE_TACKLE, MOVE_COUNTER); } } WHEN { TURN { MOVE(player, MOVE_WATERFALL) ; EXPECT_MOVE(opponent, aiRiskyFlag ? MOVE_COUNTER : MOVE_TACKLE); } @@ -49,7 +49,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: AI will prioritize Revenge if slower") GIVEN { ASSUME(gMovesInfo[MOVE_REVENGE].effect == EFFECT_REVENGE); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiRiskyFlag); - PLAYER(SPECIES_GROVYLE) { Level(20); Speed(4); Moves(MOVE_ENERGY_BALL); } + PLAYER(SPECIES_GROVYLE) { Level(20); Speed(4); Moves(MOVE_ENERGY_BALL); } OPPONENT(SPECIES_CASTFORM) { Level(19); Speed(3); Moves(MOVE_TACKLE, MOVE_REVENGE); } } WHEN { TURN { MOVE(player, MOVE_ENERGY_BALL) ; EXPECT_MOVE(opponent, aiRiskyFlag ? MOVE_REVENGE : MOVE_TACKLE); } @@ -83,7 +83,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: AI prefers high damage moves at the expens GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiRiskyFlag); - PLAYER(SPECIES_PSYDUCK) { Level(5); Moves(MOVE_TACKLE); } + PLAYER(SPECIES_PSYDUCK) { Level(5); Moves(MOVE_TACKLE); } OPPONENT(SPECIES_CASTFORM) { Level(20); Moves(MOVE_THUNDER, MOVE_THUNDERBOLT); } } WHEN { TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, aiRiskyFlag ? MOVE_THUNDER : MOVE_THUNDERBOLT); } diff --git a/test/battle/ai/ai_flag_sequence_switching.c b/test/battle/ai/ai_flag_sequence_switching.c index 1b4a264a2..7a0f3528b 100644 --- a/test/battle/ai/ai_flag_sequence_switching.c +++ b/test/battle/ai/ai_flag_sequence_switching.c @@ -64,7 +64,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SEQUENCE_SWITCHING: Roar and Dragon Tail still fo } } -AI_SINGLE_BATTLE_TEST("AI_FLAG_SEQUENCE_SWITCHING: AI will always switch into lowest party index after U-Turn, Parting Shot, and Baton Pass") +AI_SINGLE_BATTLE_TEST("AI_FLAG_SEQUENCE_SWITCHING: AI will always switch into lowest party index after U-Turn, Parting Shot, Baton Pass, and Chilly Reception") { u32 j, aiSequenceSwitchingFlag = 0, move = MOVE_NONE; @@ -72,6 +72,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SEQUENCE_SWITCHING: AI will always switch into lo MOVE_U_TURN, MOVE_PARTING_SHOT, MOVE_BATON_PASS, + MOVE_CHILLY_RECEPTION, }; for (j = 0; j < ARRAY_COUNT(switchMoves); j++) @@ -84,6 +85,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SEQUENCE_SWITCHING: AI will always switch into lo ASSUME(gMovesInfo[MOVE_U_TURN].effect == EFFECT_HIT_ESCAPE); ASSUME(gMovesInfo[MOVE_PARTING_SHOT].effect == EFFECT_PARTING_SHOT); ASSUME(gMovesInfo[MOVE_BATON_PASS].effect == EFFECT_BATON_PASS); + ASSUME(gMovesInfo[MOVE_CHILLY_RECEPTION].effect == EFFECT_CHILLY_RECEPTION); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSequenceSwitchingFlag); PLAYER(SPECIES_SWELLOW) { Level (50); } OPPONENT(SPECIES_MACHOP) { Level(1); Moves(move); } diff --git a/test/battle/gimmick/dynamax.c b/test/battle/gimmick/dynamax.c index dca175c63..b865ef2f1 100644 --- a/test/battle/gimmick/dynamax.c +++ b/test/battle/gimmick/dynamax.c @@ -1234,7 +1234,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Replenish recycles allies' berries 50\% of t GIVEN { ASSUME(gMovesInfo[MOVE_G_MAX_REPLENISH].argument == MAX_EFFECT_RECYCLE_BERRIES); PLAYER(SPECIES_SNORLAX) { Item(ITEM_APICOT_BERRY); GigantamaxFactor(TRUE); } - PLAYER(SPECIES_MUNCHLAX) { Item(ITEM_APICOT_BERRY); } + PLAYER(SPECIES_MUNCHLAX) { Item(ITEM_APICOT_BERRY); Ability(ABILITY_THICK_FAT); } OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_APICOT_BERRY); } OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_APICOT_BERRY); } } WHEN { diff --git a/test/battle/gimmick/terastal.c b/test/battle/gimmick/terastal.c index f14c398b6..1b50bc4bc 100644 --- a/test/battle/gimmick/terastal.c +++ b/test/battle/gimmick/terastal.c @@ -86,46 +86,6 @@ SINGLE_BATTLE_TEST("(TERA) Terastallizing into the same type gives that type 2x } } -SINGLE_BATTLE_TEST("(TERA) Terastallizing into a different type with Adaptability gives 2.0x STAB", s16 damage) -{ - bool32 tera; - PARAMETRIZE { tera = GIMMICK_NONE; } - PARAMETRIZE { tera = GIMMICK_TERA; } - GIVEN { - PLAYER(SPECIES_CRAWDAUNT) { Ability(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", s16 damage) -{ - bool32 tera; - PARAMETRIZE { tera = GIMMICK_NONE; } - PARAMETRIZE { tera = GIMMICK_TERA; } - GIVEN { - PLAYER(SPECIES_CRAWDAUNT) { Ability(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); - } -} - SINGLE_BATTLE_TEST("(TERA) Terastallizing boosts moves of the same type to 60 BP", s16 damage) { bool32 tera; @@ -686,7 +646,7 @@ SINGLE_BATTLE_TEST("(TERA) Protean cannot change the type of a Terastallized Pok PLAYER(SPECIES_GRENINJA) { Ability(ABILITY_PROTEAN); TeraType(TYPE_GRASS); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_BUBBLE, gimmick: GIMMICK_TERA); + TURN { MOVE(player, MOVE_BUBBLE, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_EMBER); } } SCENE { MESSAGE("Greninja used Bubble!"); diff --git a/test/battle/gimmick/zmove.c b/test/battle/gimmick/zmove.c index bde241bfd..51c651610 100644 --- a/test/battle/gimmick/zmove.c +++ b/test/battle/gimmick/zmove.c @@ -10,7 +10,7 @@ SINGLE_BATTLE_TEST("(Z-MOVE) Z-Moves do not retain priority") PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(opponent, MOVE_TACKLE); + TURN { MOVE(opponent, MOVE_TACKLE); MOVE(player, MOVE_QUICK_ATTACK, gimmick: GIMMICK_Z_MOVE); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); @@ -597,5 +597,24 @@ SINGLE_BATTLE_TEST("(Z-MOVE) Searing Sunraze Smash ignores the target's abilitie 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") +{ + 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(gMovesInfo[MOVE_REVELATION_DANCE].type == TYPE_NORMAL); + PLAYER(species) { Item(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); + } } diff --git a/test/battle/hold_effect/ability_shield.c b/test/battle/hold_effect/ability_shield.c index ee84b2c5e..e10c3e088 100644 --- a/test/battle/hold_effect/ability_shield.c +++ b/test/battle/hold_effect/ability_shield.c @@ -69,7 +69,7 @@ SINGLE_BATTLE_TEST("Ability Shield protects against Mycelium Might") } WHEN { TURN { MOVE(opponent, MOVE_SPORE); MOVE(player, MOVE_SPORE); } } SCENE { - + if (item == ITEM_ABILITY_SHIELD) { NONE_OF { ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponent); diff --git a/test/battle/ability/booster_energy.c b/test/battle/hold_effect/booster_energy.c similarity index 100% rename from test/battle/ability/booster_energy.c rename to test/battle/hold_effect/booster_energy.c diff --git a/test/battle/hold_effect/utility_umbrella.c b/test/battle/hold_effect/utility_umbrella.c index e02a4fdef..f04a77378 100644 --- a/test/battle/hold_effect/utility_umbrella.c +++ b/test/battle/hold_effect/utility_umbrella.c @@ -52,3 +52,5 @@ SINGLE_BATTLE_TEST("Utility Umbrella blocks Rain damage modifiers", s16 damage) EXPECT_MUL_EQ(results[2].damage, Q_4_12(1.5), results[3].damage); } } + +// Moves and abilities affected by Utility Umbrella have their tests in the respective files diff --git a/test/battle/move.c b/test/battle/move.c index 228a09a7c..081a91712 100644 --- a/test/battle/move.c +++ b/test/battle/move.c @@ -80,57 +80,57 @@ SINGLE_BATTLE_TEST("Turn order is determined randomly if priority and Speed tie } } -DOUBLE_BATTLE_TEST("Turn order is determined randomly if priority and Speed tie [doubles]") +DOUBLE_BATTLE_TEST("Turn order is determined randomly if priority and Speed tie [doubles]", u32 permutations) { - struct BattlePokemon *order[4] = { NULL, NULL, NULL, NULL }; - u32 a, b, c, d; - - // TODO: Test all of these in a single PASSES_RANDOMLY pass rather - // than 24 PARAMETRIZEd passes. - PARAMETRIZE { a = 0; b = 1; c = 2; d = 3; } - PARAMETRIZE { a = 0; b = 1; c = 3; d = 2; } - PARAMETRIZE { a = 0; b = 2; c = 1; d = 3; } - PARAMETRIZE { a = 0; b = 2; c = 3; d = 1; } - PARAMETRIZE { a = 0; b = 3; c = 1; d = 2; } - PARAMETRIZE { a = 0; b = 3; c = 2; d = 1; } - PARAMETRIZE { a = 1; b = 0; c = 2; d = 3; } - PARAMETRIZE { a = 1; b = 0; c = 3; d = 2; } - PARAMETRIZE { a = 1; b = 2; c = 0; d = 3; } - PARAMETRIZE { a = 1; b = 2; c = 3; d = 0; } - PARAMETRIZE { a = 1; b = 3; c = 0; d = 2; } - PARAMETRIZE { a = 1; b = 3; c = 2; d = 0; } - PARAMETRIZE { a = 2; b = 0; c = 1; d = 3; } - PARAMETRIZE { a = 2; b = 0; c = 3; d = 1; } - PARAMETRIZE { a = 2; b = 1; c = 0; d = 3; } - PARAMETRIZE { a = 2; b = 1; c = 3; d = 0; } - PARAMETRIZE { a = 2; b = 3; c = 0; d = 1; } - PARAMETRIZE { a = 2; b = 3; c = 1; d = 0; } - PARAMETRIZE { a = 3; b = 0; c = 1; d = 2; } - PARAMETRIZE { a = 3; b = 0; c = 2; d = 1; } - PARAMETRIZE { a = 3; b = 1; c = 0; d = 2; } - PARAMETRIZE { a = 3; b = 1; c = 2; d = 0; } - PARAMETRIZE { a = 3; b = 2; c = 0; d = 1; } - PARAMETRIZE { a = 3; b = 2; c = 1; d = 0; } - - order[a] = playerLeft; - order[b] = playerRight; - order[c] = opponentLeft; - order[d] = opponentRight; - - PASSES_RANDOMLY(1, 24, RNG_SPEED_TIE); + PARAMETRIZE {} // Hack to make permutations legal. + PASSES_RANDOMLY(24, 24, RNG_SPEED_TIE); + ASSUME(gMovesInfo[MOVE_ENDEAVOR].effect == EFFECT_ENDEAVOR); + ASSUME(gMovesInfo[MOVE_LIFE_DEW].effect == EFFECT_JUNGLE_HEALING); + ASSUME(gMovesInfo[MOVE_CRUSH_GRIP].effect == EFFECT_VARY_POWER_BASED_ON_HP); + ASSUME(gMovesInfo[MOVE_SUPER_FANG].effect == EFFECT_SUPER_FANG); GIVEN { - PLAYER(SPECIES_WOBBUFFET) { Speed(1); } + PLAYER(SPECIES_WOBBUFFET) { MaxHP(480); HP(360); Defense(100); Speed(1); } PLAYER(SPECIES_WYNAUT) { Speed(1); } - OPPONENT(SPECIES_WOBBUFFET) { Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { Attack(100); Speed(1); } OPPONENT(SPECIES_WYNAUT) { Speed(1); } } WHEN { - TURN { MOVE(playerLeft, MOVE_SPLASH); MOVE(playerRight, MOVE_SPLASH); MOVE(opponentLeft, MOVE_SPLASH); MOVE(opponentRight, MOVE_SPLASH); } - } SCENE { - ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, order[0]); - ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, order[1]); - ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, order[2]); - ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, order[3]); + TURN { MOVE(playerLeft, MOVE_ENDEAVOR, target: opponentLeft); MOVE(playerRight, MOVE_LIFE_DEW); MOVE(opponentLeft, MOVE_CRUSH_GRIP, target: playerLeft, WITH_RNG(RNG_DAMAGE_MODIFIER, 0)); MOVE(opponentRight, MOVE_SUPER_FANG, target: playerLeft); } + } THEN { + // This tests for unique combinatins of HP values depending on which order the moves are executed in + // The unique outcomes arise from the specific attacks and HP, Def, and Atk values chosen + // The switch is then set up in such a way that the only way for this test to pass exactly one is for each HP combination to occur exactly once +#define HP_PAIR(a, b) ((a) * 1000 + (b)) + switch (HP_PAIR(playerLeft->hp, opponentLeft->hp)) + { + case HP_PAIR(188, 360): results[i].permutations += 1 << 0; break; + case HP_PAIR(189, 360): results[i].permutations += 1 << 1; break; + case HP_PAIR(261, 360): results[i].permutations += 1 << 2; break; + case HP_PAIR(235, 360): results[i].permutations += 1 << 3; break; + case HP_PAIR(262, 360): results[i].permutations += 1 << 4; break; + case HP_PAIR(202, 360): results[i].permutations += 1 << 5; break; + case HP_PAIR(189, 378): results[i].permutations += 1 << 6; break; + case HP_PAIR(189, 189): results[i].permutations += 1 << 7; break; + case HP_PAIR(189, 480): results[i].permutations += 1 << 8; break; + case HP_PAIR(188, 480): results[i].permutations += 1 << 9; break; + case HP_PAIR(188, 240): results[i].permutations += 1 << 10; break; + case HP_PAIR(188, 188): results[i].permutations += 1 << 11; break; + case HP_PAIR(262, 262): results[i].permutations += 1 << 12; break; + case HP_PAIR(262, 142): results[i].permutations += 1 << 13; break; + case HP_PAIR(202, 403): results[i].permutations += 1 << 14; break; + case HP_PAIR(202, 202): results[i].permutations += 1 << 15; break; + case HP_PAIR(262, 283): results[i].permutations += 1 << 16; break; + case HP_PAIR(202, 283): results[i].permutations += 1 << 17; break; + case HP_PAIR(235, 180): results[i].permutations += 1 << 18; break; + case HP_PAIR(261, 180): results[i].permutations += 1 << 19; break; + case HP_PAIR(235, 235): results[i].permutations += 1 << 20; break; + case HP_PAIR(235, 300): results[i].permutations += 1 << 21; break; + case HP_PAIR(261, 141): results[i].permutations += 1 << 22; break; + case HP_PAIR(261, 261): results[i].permutations += 1 << 23; break; + } +#undef HP_PAIR + } FINALLY { + EXPECT_EQ(results[i].permutations, (1 << 24) - 1); } } diff --git a/test/battle/move_effect/defog.c b/test/battle/move_effect/defog.c index 418d28d97..80d500d2e 100644 --- a/test/battle/move_effect/defog.c +++ b/test/battle/move_effect/defog.c @@ -131,7 +131,7 @@ DOUBLE_BATTLE_TEST("Defog lowers evasiveness by 1 and removes Mist and Safeguard } } -DOUBLE_BATTLE_TEST("Defog lowers evasiveness by 1 and removes Stealth Rock and Sticky Web from player's side") +DOUBLE_BATTLE_TEST("Defog lowers evasiveness by 1 and removes Stealth Rock and Sticky Web from player's side (Gen 6+)") { u16 move; @@ -154,13 +154,15 @@ DOUBLE_BATTLE_TEST("Defog lowers evasiveness by 1 and removes Stealth Rock and S if (move == MOVE_DEFOG) { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); MESSAGE("Foe Wobbuffet's evasiveness fell!"); - MESSAGE("The pointed stones disappeared from around your team!"); - MESSAGE("The sticky web has disappeared from the ground around your team!"); + if (B_DEFOG_EFFECT_CLEARING >= GEN_6) { + MESSAGE("The pointed stones disappeared from around your team!"); + MESSAGE("The sticky web has disappeared from the ground around your team!"); + } } // Switch happens SWITCH_OUT_MESSAGE("Wobbuffet"); SEND_IN_MESSAGE("Wobbuffet"); - if (move != MOVE_DEFOG) { + if (move != MOVE_DEFOG || B_DEFOG_EFFECT_CLEARING <= GEN_5) { HP_BAR(playerLeft); MESSAGE("Pointed stones dug into Wobbuffet!"); MESSAGE("Wobbuffet was caught in a Sticky Web!"); @@ -198,12 +200,13 @@ SINGLE_BATTLE_TEST("Defog lowers evasiveness by 1 and removes Spikes from player if (move == MOVE_DEFOG) { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); MESSAGE("Foe Wobbuffet's evasiveness fell!"); - MESSAGE("The spikes disappeared from the ground around your team!"); + if (B_DEFOG_EFFECT_CLEARING >= GEN_6) + MESSAGE("The spikes disappeared from the ground around your team!"); } // Switch happens SWITCH_OUT_MESSAGE("Wobbuffet"); SEND_IN_MESSAGE("Wobbuffet"); - if (move != MOVE_DEFOG) { + if (move != MOVE_DEFOG || B_DEFOG_EFFECT_CLEARING <= GEN_5) { HP_BAR(player); MESSAGE("Wobbuffet is hurt by spikes!"); } @@ -216,7 +219,7 @@ SINGLE_BATTLE_TEST("Defog lowers evasiveness by 1 and removes Spikes from player } } -SINGLE_BATTLE_TEST("Defog lowers evasiveness by 1 and removes terrain") +SINGLE_BATTLE_TEST("Defog lowers evasiveness by 1 and removes terrain (Gen 8+)") { u16 move; @@ -225,7 +228,6 @@ SINGLE_BATTLE_TEST("Defog lowers evasiveness by 1 and removes terrain") PARAMETRIZE { move = MOVE_MISTY_TERRAIN; } PARAMETRIZE { move = MOVE_GRASSY_TERRAIN; } GIVEN { - ASSUME(B_DEFOG_CLEARS_TERRAIN >= GEN_8); PLAYER(SPECIES_WOBBUFFET) { Speed(50); } OPPONENT(SPECIES_WOBBUFFET) { Speed(5); } } WHEN { @@ -235,19 +237,29 @@ SINGLE_BATTLE_TEST("Defog lowers evasiveness by 1 and removes terrain") ANIMATION(ANIM_TYPE_MOVE, MOVE_DEFOG, opponent); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); MESSAGE("Wobbuffet's evasiveness fell!"); - if (move == MOVE_PSYCHIC_TERRAIN) { - MESSAGE("The weirdness disappeared from the battlefield."); + if (B_DEFOG_EFFECT_CLEARING >= GEN_8) { + if (move == MOVE_PSYCHIC_TERRAIN) { + MESSAGE("The weirdness disappeared from the battlefield."); + } + else if (move == MOVE_ELECTRIC_TERRAIN) { + MESSAGE("The electricity disappeared from the battlefield."); + } + else if (move == MOVE_MISTY_TERRAIN) { + MESSAGE("The mist disappeared from the battlefield."); + } + else if (move == MOVE_GRASSY_TERRAIN) { + MESSAGE("The grass disappeared from the battlefield."); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_RESTORE_BG, player); + } else { + NONE_OF { + MESSAGE("The weirdness disappeared from the battlefield."); + MESSAGE("The electricity disappeared from the battlefield."); + MESSAGE("The mist disappeared from the battlefield."); + MESSAGE("The grass disappeared from the battlefield."); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_RESTORE_BG, player); + } } - else if (move == MOVE_ELECTRIC_TERRAIN) { - MESSAGE("The electricity disappeared from the battlefield."); - } - else if (move == MOVE_MISTY_TERRAIN) { - MESSAGE("The mist disappeared from the battlefield."); - } - else if (move == MOVE_GRASSY_TERRAIN) { - MESSAGE("The grass disappeared from the battlefield."); - } - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_RESTORE_BG, player); } } @@ -270,11 +282,12 @@ SINGLE_BATTLE_TEST("Defog lowers evasiveness by 1 and removes Toxic Spikes from if (move == MOVE_DEFOG) { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); MESSAGE("Wobbuffet's evasiveness fell!"); - MESSAGE("The poison spikes disappeared from the ground around the opposing team!"); + if (B_DEFOG_EFFECT_CLEARING >= GEN_6) + MESSAGE("The poison spikes disappeared from the ground around the opposing team!"); } // Switch happens MESSAGE("2 sent out Wobbuffet!"); - if (move != MOVE_DEFOG) { + if (move != MOVE_DEFOG || B_DEFOG_EFFECT_CLEARING <= GEN_5) { MESSAGE("Foe Wobbuffet was poisoned!"); ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); STATUS_ICON(opponent, poison: TRUE); @@ -356,15 +369,17 @@ DOUBLE_BATTLE_TEST("Defog lowers evasiveness by 1 and removes everything it can" MESSAGE("Ally's Aurora Veil wore off!"); MESSAGE("Ally's Safeguard wore off!"); - MESSAGE("The spikes disappeared from the ground around your team!"); - MESSAGE("The pointed stones disappeared from around your team!"); - MESSAGE("The poison spikes disappeared from the ground around your team!"); - MESSAGE("The sticky web has disappeared from the ground around your team!"); + if (B_DEFOG_EFFECT_CLEARING >= GEN_6) { + MESSAGE("The spikes disappeared from the ground around your team!"); + MESSAGE("The pointed stones disappeared from around your team!"); + MESSAGE("The poison spikes disappeared from the ground around your team!"); + MESSAGE("The sticky web has disappeared from the ground around your team!"); - // Opponent side - MESSAGE("The spikes disappeared from the ground around the opposing team!"); - MESSAGE("The pointed stones disappeared from around the opposing team!"); - MESSAGE("The poison spikes disappeared from the ground around the opposing team!"); - MESSAGE("The sticky web has disappeared from the ground around the opposing team!"); + // Opponent side + MESSAGE("The spikes disappeared from the ground around the opposing team!"); + MESSAGE("The pointed stones disappeared from around the opposing team!"); + MESSAGE("The poison spikes disappeared from the ground around the opposing team!"); + MESSAGE("The sticky web has disappeared from the ground around the opposing team!"); + } } } diff --git a/test/battle/move_effect/guard_split.c b/test/battle/move_effect/guard_split.c new file mode 100644 index 000000000..3f012ab6e --- /dev/null +++ b/test/battle/move_effect/guard_split.c @@ -0,0 +1,22 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_GUARD_SPLIT].effect == EFFECT_GUARD_SPLIT); +} + +SINGLE_BATTLE_TEST("Guard Split averages users and targets Def and Sp. Def stats") +{ + GIVEN { + PLAYER(SPECIES_BULBASAUR); + OPPONENT(SPECIES_IVYSAUR); + } WHEN { + TURN { MOVE(player, MOVE_GUARD_SPLIT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GUARD_SPLIT, player); + } THEN { + EXPECT_EQ(player->defense, opponent->defense); + EXPECT_EQ(player->spDefense, opponent->spDefense); + } +} diff --git a/test/battle/move_effect/heal_bell.c b/test/battle/move_effect/heal_bell.c index 8bd6c1f45..743af905c 100644 --- a/test/battle/move_effect/heal_bell.c +++ b/test/battle/move_effect/heal_bell.c @@ -10,7 +10,7 @@ ASSUMPTIONS DOUBLE_BATTLE_TEST("Heal Bell cures the entire party") { u32 move; - + PARAMETRIZE { move = MOVE_HEAL_BELL; } PARAMETRIZE { move = MOVE_AROMATHERAPY; } @@ -39,10 +39,10 @@ DOUBLE_BATTLE_TEST("Heal Bell cures the entire party") DOUBLE_BATTLE_TEST("Heal Bell does not cure soundproof partners") { u32 ability; - + PARAMETRIZE { ability = ABILITY_SCRAPPY; } PARAMETRIZE { ability = ABILITY_SOUNDPROOF; } - + ASSUME(B_HEAL_BELL_SOUNDPROOF != GEN_5); GIVEN { @@ -65,10 +65,10 @@ DOUBLE_BATTLE_TEST("Heal Bell does not cure soundproof partners") SINGLE_BATTLE_TEST("Heal Bell cures inactive soundproof Pokemon") { u32 ability; - + PARAMETRIZE { ability = ABILITY_SCRAPPY; } PARAMETRIZE { ability = ABILITY_SOUNDPROOF; } - + ASSUME(B_HEAL_BELL_SOUNDPROOF >= GEN_5); GIVEN { @@ -87,7 +87,7 @@ SINGLE_BATTLE_TEST("Heal Bell cures inactive soundproof Pokemon") SINGLE_BATTLE_TEST("Heal Bell cures a soundproof user") -{ +{ ASSUME(B_HEAL_BELL_SOUNDPROOF == GEN_5 || B_HEAL_BELL_SOUNDPROOF >= GEN_8); GIVEN { diff --git a/test/battle/move_effect/knock_off.c b/test/battle/move_effect/knock_off.c index ba6f9b6fe..50d8aaa77 100644 --- a/test/battle/move_effect/knock_off.c +++ b/test/battle/move_effect/knock_off.c @@ -90,7 +90,7 @@ SINGLE_BATTLE_TEST("Knock Off does not remove items through Substitute") PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LEFTOVERS); }; } WHEN { - TURN { MOVE(opponent, MOVE_SUBSTITUTE); + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_KNOCK_OFF); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); @@ -112,7 +112,7 @@ SINGLE_BATTLE_TEST("Recycle cannot recover an item removed by Knock Off") ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); MESSAGE("Wobbuffet knocked off Foe Wobbuffet's Leftovers!"); - + MESSAGE("Foe Wobbuffet used Recycle!"); MESSAGE("But it failed!"); } THEN { diff --git a/test/battle/move_effect/power_split.c b/test/battle/move_effect/power_split.c new file mode 100644 index 000000000..70d1bfd5e --- /dev/null +++ b/test/battle/move_effect/power_split.c @@ -0,0 +1,22 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_POWER_SPLIT].effect == EFFECT_POWER_SPLIT); +} + +SINGLE_BATTLE_TEST("Power Split averages user and targets Atk and Sp. Atk stats") +{ + GIVEN { + PLAYER(SPECIES_BULBASAUR); + OPPONENT(SPECIES_IVYSAUR); + } WHEN { + TURN { MOVE(player, MOVE_POWER_SPLIT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POWER_SPLIT, player); + } THEN { + EXPECT_EQ(player->attack, opponent->attack); + EXPECT_EQ(player->spAttack, opponent->spAttack); + } +} diff --git a/test/battle/move_effect/protect.c b/test/battle/move_effect/protect.c index 973033bc3..aca5ef859 100644 --- a/test/battle/move_effect/protect.c +++ b/test/battle/move_effect/protect.c @@ -482,3 +482,68 @@ DOUBLE_BATTLE_TEST("Crafty Shield protects self and ally from status moves") } } } + +SINGLE_BATTLE_TEST("Protect does not block Confide") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_CONFIDE].effect == EFFECT_SPECIAL_ATTACK_DOWN); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_PROTECT); MOVE(player, MOVE_CONFIDE); } + } SCENE { + MESSAGE("Wobbuffet used Confide!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFIDE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + NOT MESSAGE("Foe Wobbuffet protected itself!"); + } +} + +DOUBLE_BATTLE_TEST("Crafty Shield protects self and ally from Confide") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_CONFIDE].effect == EFFECT_SPECIAL_ATTACK_DOWN); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_CRAFTY_SHIELD); MOVE(playerLeft, MOVE_CONFIDE, target: opponentLeft); MOVE(playerRight, MOVE_CONFIDE, target: opponentRight); } + } SCENE { + MESSAGE("Wobbuffet used Confide!"); + MESSAGE("Foe Wobbuffet protected itself!"); + MESSAGE("Wynaut used Confide!"); + MESSAGE("Foe Wynaut protected itself!"); + } +} + +DOUBLE_BATTLE_TEST("Crafty Shield does not protect against moves that target all battlers") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_FLOWER_SHIELD].target == MOVE_TARGET_ALL_BATTLERS); + ASSUME(gSpeciesInfo[SPECIES_TANGELA].types[0] == TYPE_GRASS); + ASSUME(gSpeciesInfo[SPECIES_TANGROWTH].types[0] == TYPE_GRASS); + ASSUME(gSpeciesInfo[SPECIES_SUNKERN].types[0] == TYPE_GRASS); + ASSUME(gSpeciesInfo[SPECIES_SUNFLORA].types[0] == TYPE_GRASS); + PLAYER(SPECIES_TANGELA); + PLAYER(SPECIES_TANGROWTH); + OPPONENT(SPECIES_SUNKERN); + OPPONENT(SPECIES_SUNFLORA); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_CRAFTY_SHIELD); MOVE(opponentRight, MOVE_CELEBRATE); MOVE(playerLeft, MOVE_FLOWER_SHIELD); MOVE(playerRight, MOVE_CELEBRATE); } + } SCENE { + MESSAGE("Tangela used Flower Shield!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLOWER_SHIELD, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Tangela's Defense rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLOWER_SHIELD, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("Foe Sunkern's Defense rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLOWER_SHIELD, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Tangrowth's Defense rose!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLOWER_SHIELD, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("Foe Sunflora's Defense rose!"); + } +} diff --git a/test/battle/move_effect/retaliate.c b/test/battle/move_effect/retaliate.c new file mode 100644 index 000000000..581793e85 --- /dev/null +++ b/test/battle/move_effect/retaliate.c @@ -0,0 +1,137 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_RETALIATE].effect == EFFECT_RETALIATE); +} + +SINGLE_BATTLE_TEST("Retaliate doubles in base power the turn after an ally faints") +{ + s16 damage[2]; + GIVEN { + PLAYER(SPECIES_WYNAUT) { HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); 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]); + } +} + +SINGLE_BATTLE_TEST("Retaliate doubles in base power the turn after an ally faints (opponent)") +{ + s16 damage[2]; + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); SEND_OUT(opponent, 1); } + TURN { MOVE(opponent, MOVE_RETALIATE); } + TURN { MOVE(opponent, MOVE_RETALIATE); } + } SCENE { + HP_BAR(player, captureDamage: &damage[0]); + HP_BAR(player, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[1], Q_4_12(2), damage[0]); + } +} + +DOUBLE_BATTLE_TEST("Retaliate works with passive damage") +{ + 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(gMovesInfo[MOVE_TOXIC].effect == EFFECT_TOXIC); + ASSUME(gMovesInfo[MOVE_POISON_POWDER].effect == EFFECT_POISON); + ASSUME(gMovesInfo[MOVE_WILL_O_WISP].effect == EFFECT_WILL_O_WISP); + #if B_USE_FROSTBITE == TRUE + ASSUME(gMovesInfo[MOVE_ICE_BEAM].additionalEffects[0].moveEffect == MOVE_EFFECT_FREEZE_OR_FROSTBITE); + #endif + ASSUME(gMovesInfo[MOVE_SANDSTORM].effect == EFFECT_SANDSTORM); + ASSUME(gMovesInfo[MOVE_HAIL].effect == EFFECT_HAIL); + ASSUME(gMovesInfo[MOVE_LEECH_SEED].effect == EFFECT_LEECH_SEED); + ASSUME(gMovesInfo[MOVE_MAGMA_STORM].additionalEffects[0].moveEffect == MOVE_EFFECT_WRAP); + ASSUME(gMovesInfo[MOVE_FLAME_BURST].additionalEffects[0].moveEffect == MOVE_EFFECT_FLAME_BURST); + PLAYER(SPECIES_WYNAUT) { Ability(ABILITY_SHADOW_TAG); HP(18); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CLEFABLE) { Ability(ABILITY_MAGIC_GUARD); Level(1); } + OPPONENT(SPECIES_CLEFABLE) { Ability(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") +{ + s16 damage[2]; + GIVEN { + ASSUME(gMovesInfo[MOVE_PERISH_SONG].effect == EFFECT_PERISH_SONG); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KOMMO_O) { Ability(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]); + } +} + +SINGLE_BATTLE_TEST("Retaliate works with self-inflicted fainting") +{ + s16 damage[2]; + GIVEN { + ASSUME(gMovesInfo[MOVE_HEALING_WISH].effect == EFFECT_HEALING_WISH); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HEALING_WISH); 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]); + } +} diff --git a/test/battle/move_effect/revelation_dance.c b/test/battle/move_effect/revelation_dance.c new file mode 100644 index 000000000..9ab5d4a8e --- /dev/null +++ b/test/battle/move_effect/revelation_dance.c @@ -0,0 +1,149 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_REVELATION_DANCE].effect == EFFECT_REVELATION_DANCE); + ASSUME(gMovesInfo[MOVE_REVELATION_DANCE].danceMove == TRUE); + ASSUME(MoveHasAdditionalEffectSelfArg(MOVE_BURN_UP, MOVE_EFFECT_REMOVE_ARG_TYPE, TYPE_FIRE)); + ASSUME(gMovesInfo[MOVE_FORESTS_CURSE].effect == EFFECT_THIRD_TYPE); + ASSUME(gMovesInfo[MOVE_FORESTS_CURSE].argument == TYPE_GRASS); + ASSUME(gMovesInfo[MOVE_ROOST].effect == EFFECT_ROOST); +} + +SINGLE_BATTLE_TEST("Revelation Dance changes its type depending on the user's 1st Type") +{ + u16 speciesPlayer; + u16 speciesOpponent; + + PARAMETRIZE { speciesPlayer = SPECIES_ORICORIO_POM_POM; speciesOpponent = SPECIES_DUGTRIO; } + PARAMETRIZE { speciesPlayer = SPECIES_ORICORIO_BAILE; speciesOpponent = SPECIES_BLASTOISE; } + PARAMETRIZE { speciesPlayer = SPECIES_ORICORIO_PAU; speciesOpponent = SPECIES_LIEPARD; } + PARAMETRIZE { speciesPlayer = SPECIES_ORICORIO_SENSU; speciesOpponent = SPECIES_PERSIAN; } + + GIVEN { + PLAYER(speciesPlayer); + OPPONENT(speciesOpponent); + } WHEN { + TURN { MOVE(player, MOVE_REVELATION_DANCE); } + } SCENE { + if (speciesPlayer == SPECIES_ORICORIO_BAILE) { + 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…"); + } + } + } +} + +SINGLE_BATTLE_TEST("Revelation Dance changes its type depending on the user's 2nd Type if it has no 1st type") +{ + GIVEN { + PLAYER(SPECIES_ORICORIO_BAILE); + OPPONENT(SPECIES_EMPOLEON); + } WHEN { + TURN { MOVE(player, MOVE_BURN_UP); } + TURN { MOVE(player, MOVE_REVELATION_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BURN_UP, player); + HP_BAR(opponent); + NONE_OF { + MESSAGE("It's not very effective…"); + MESSAGE("It's super effective!"); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_REVELATION_DANCE, player); + HP_BAR(opponent); + MESSAGE("It's not very effective…"); + } +} + +SINGLE_BATTLE_TEST("Revelation Dance changes its type depending on the user's 3rd Type if it has no 1st or 2nd type") +{ + GIVEN { + PLAYER(SPECIES_GROWLITHE); + OPPONENT(SPECIES_TREVENANT); + } WHEN { + TURN { MOVE(player, MOVE_BURN_UP); MOVE(opponent, MOVE_FORESTS_CURSE); } + TURN { MOVE(player, MOVE_REVELATION_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BURN_UP, player); + HP_BAR(opponent); + MESSAGE("It's super effective!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FORESTS_CURSE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_REVELATION_DANCE, player); + HP_BAR(opponent); + MESSAGE("It's not very effective…"); + } +} + +SINGLE_BATTLE_TEST("Revelation Dance becomes Typeless if its user is Typeless") +{ + u16 speciesOpponent; + + PARAMETRIZE { speciesOpponent = SPECIES_BLISSEY; } + PARAMETRIZE { speciesOpponent = SPECIES_BLASTOISE; } + PARAMETRIZE { speciesOpponent = SPECIES_CHARIZARD; } + PARAMETRIZE { speciesOpponent = SPECIES_VENUSAUR; } + PARAMETRIZE { speciesOpponent = SPECIES_GOLEM; } + PARAMETRIZE { speciesOpponent = SPECIES_AEGISLASH; } + + GIVEN { + PLAYER(SPECIES_GROWLITHE); + OPPONENT(speciesOpponent); + } WHEN { + TURN { MOVE(player, MOVE_BURN_UP); } + TURN { MOVE(player, MOVE_REVELATION_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BURN_UP, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_REVELATION_DANCE, player); + HP_BAR(opponent); + NONE_OF { + MESSAGE("It's not very effective…"); + MESSAGE("It's super effective!"); + } + } +} + +SINGLE_BATTLE_TEST("Revelation Dance becomes Normal type if used by a Typeless Pokemon due to Roost") +{ + u16 speciesOpponent; + + PARAMETRIZE { speciesOpponent = SPECIES_SABLEYE; } + PARAMETRIZE { speciesOpponent = SPECIES_AGGRON; } + + ASSUME(B_ROOST_PURE_FLYING >= GEN_5); + + GIVEN { + PLAYER(SPECIES_ORICORIO_BAILE) { Ability(ABILITY_DANCER); } + OPPONENT(speciesOpponent); + } WHEN { + TURN { MOVE(player, MOVE_BURN_UP); MOVE(opponent, MOVE_TACKLE); } + 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…"); + } + } + } +} diff --git a/test/battle/move_effect/round.c b/test/battle/move_effect/round.c new file mode 100644 index 000000000..c911c96b6 --- /dev/null +++ b/test/battle/move_effect/round.c @@ -0,0 +1,115 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_ROUND].effect == EFFECT_ROUND); +} + +DOUBLE_BATTLE_TEST("Round allows other battlers which also selected the moves to immediately use the move, ignoring turn order") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LAGGING_TAIL].holdEffect == HOLD_EFFECT_LAGGING_TAIL); + ASSUME(gMovesInfo[MOVE_IRON_HEAD].additionalEffects[0].moveEffect == MOVE_EFFECT_FLINCH); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(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 usages beyond the first one has double base power") +{ + s16 damage[2]; + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_ROUND, target: playerLeft); + MOVE(opponentRight, MOVE_ROUND, target: playerLeft); + } + } SCENE { + HP_BAR(playerLeft, captureDamage: &damage[0]); + HP_BAR(playerLeft, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[0], Q_4_12(2.0), damage[1]); + } +} + +DOUBLE_BATTLE_TEST("Round still preserves the turn order outside of the other Round users moving immediately") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_ROUND, target: playerLeft); + MOVE(playerRight, MOVE_CELEBRATE); + MOVE(playerLeft, MOVE_CELEBRATE); + MOVE(opponentRight, MOVE_ROUND, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft); + } +} +DOUBLE_BATTLE_TEST("Round still preserves the turn order outside of the other Round users moving immediately with switch") +{ + KNOWN_FAILING; // #5148 + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + SWITCH(playerRight, 2); + MOVE(opponentLeft, MOVE_ROUND, target: playerLeft); + MOVE(playerLeft, MOVE_CELEBRATE); + MOVE(opponentRight, MOVE_ROUND, target: playerLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft); + } +} + +DOUBLE_BATTLE_TEST("Round causes opposing pokemon to use Round immediately") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LAGGING_TAIL].holdEffect == HOLD_EFFECT_LAGGING_TAIL); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(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); + } +} diff --git a/test/battle/move_effect/sleep_talk.c b/test/battle/move_effect/sleep_talk.c index 0a42eb90e..8ecd600f3 100644 --- a/test/battle/move_effect/sleep_talk.c +++ b/test/battle/move_effect/sleep_talk.c @@ -14,7 +14,7 @@ SINGLE_BATTLE_TEST("Sleep Talk fails if not asleep") u32 status; PARAMETRIZE { status = STATUS1_SLEEP; } PARAMETRIZE { status = STATUS1_NONE; } - + GIVEN { PLAYER(SPECIES_WOBBUFFET) { Status1(status); Moves(MOVE_SLEEP_TALK, MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH); } OPPONENT(SPECIES_WOBBUFFET); @@ -35,7 +35,7 @@ SINGLE_BATTLE_TEST("Sleep Talk fails if not asleep") SINGLE_BATTLE_TEST("Sleep Talk works if user has Comatose") { - + GIVEN { PLAYER(SPECIES_KOMALA) { Moves(MOVE_SLEEP_TALK, MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH); } OPPONENT(SPECIES_WOBBUFFET); diff --git a/test/battle/move_effect/stomping_tantrum.c b/test/battle/move_effect/stomping_tantrum.c new file mode 100644 index 000000000..1a5870fdd --- /dev/null +++ b/test/battle/move_effect/stomping_tantrum.c @@ -0,0 +1,134 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_STOMPING_TANTRUM].effect == EFFECT_STOMPING_TANTRUM); +} + +SINGLE_BATTLE_TEST("Stomping Tatrum will deal double damage if user flinched on the previous turn") +{ + s16 damage[3]; + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STOMPING_TANTRUM); SWITCH(opponent, 1); } + TURN { MOVE(opponent, MOVE_FAKE_OUT); MOVE(player, MOVE_CELEBRATE); } + 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_FAKE_OUT, opponent); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + + 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 Tatrum will deal double damage if user failed to attack due to paralysis") +{ + s16 damage[3]; + PASSES_RANDOMLY(25, 100, RNG_PARALYSIS); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(100); Item(ITEM_POTION); }; + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); Item(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 Tatrum will not deal double damage if target protects") +{ + s16 damage[2]; + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STOMPING_TANTRUM); } + TURN { MOVE(opponent, MOVE_PROTECT); 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_PROTECT, opponent); + MESSAGE("Foe Wobbuffet protected itself!"); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_STOMPING_TANTRUM, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} + +SINGLE_BATTLE_TEST("Stomping Tatrum will not deal double damage if it failed on the previous turn cause of Protect") +{ + s16 damage[2]; + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(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_EQ(damage[0], damage[1]); + } +} + +SINGLE_BATTLE_TEST("Stomping Tatrum will deal double damage if user was immune to previous move") +{ + s16 damage[2]; + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIDGEY); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STOMPING_TANTRUM); SWITCH(opponent, 1); } + TURN { MOVE(player, MOVE_STOMPING_TANTRUM); SWITCH(opponent, 0); } + TURN { MOVE(player, MOVE_STOMPING_TANTRUM); SWITCH(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STOMPING_TANTRUM, player); + HP_BAR(opponent, captureDamage: &damage[0]); + MESSAGE("It doesn't affect Foe Pidgey…"); + 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]); + } +} diff --git a/test/battle/move_effect/telekinesis.c b/test/battle/move_effect/telekinesis.c index 4210b1865..3da96a08b 100644 --- a/test/battle/move_effect/telekinesis.c +++ b/test/battle/move_effect/telekinesis.c @@ -71,4 +71,4 @@ SINGLE_BATTLE_TEST("Telekinesis makes the target immune to Ground-type attacks") TO_DO_BATTLE_TEST("Baton Pass passes Telekinesis' effect"); //Bulbapedia doesn't confirm what happens with Diglett, Dugtrio, Sandygast and Palossand, so it needs to be tested in-game. -TO_DO_BATTLE_TEST("Baton Pass removes Telekinesis' effect disappears if the switching-in mon is Mega Gengar"); +TO_DO_BATTLE_TEST("Baton Pass removes Telekinesis' effect disappears if the switching-in mon is Mega Gengar"); diff --git a/test/battle/move_effect/weather_ball.c b/test/battle/move_effect/weather_ball.c index 1656b60d8..432e5f79f 100644 --- a/test/battle/move_effect/weather_ball.c +++ b/test/battle/move_effect/weather_ball.c @@ -78,3 +78,5 @@ SINGLE_BATTLE_TEST("Weather Ball doubles its power and turns to an Ice-type move EXPECT_MUL_EQ(results[0].damage, Q_4_12(4.0), results[1].damage); // double base power + type effectiveness. } } + +TO_DO_BATTLE_TEST("Weather Ball doesn't double its power or change type if Cloud Nine/Air Lock is on the field"); diff --git a/test/battle/weather/hail.c b/test/battle/weather/hail.c index 5caeb8489..4210a22d9 100644 --- a/test/battle/weather/hail.c +++ b/test/battle/weather/hail.c @@ -70,3 +70,15 @@ DOUBLE_BATTLE_TEST("Hail deals damage based on turn order") HP_BAR(playerRight); } } + +SINGLE_BATTLE_TEST("Hail damage rounds properly when maxHP < 16") +{ + GIVEN { + PLAYER(SPECIES_MAGIKARP) { Level(1); MaxHP(11); HP(11); } + OPPONENT(SPECIES_GLALIE); + } WHEN { + TURN { MOVE(opponent, MOVE_HAIL); } + } SCENE { + HP_BAR(player, damage: 1); + } +} diff --git a/test/battle/weather/sandstorm.c b/test/battle/weather/sandstorm.c index 63b24afd7..1673c26f9 100644 --- a/test/battle/weather/sandstorm.c +++ b/test/battle/weather/sandstorm.c @@ -82,3 +82,15 @@ DOUBLE_BATTLE_TEST("Sandstorm deals damage based on turn order") HP_BAR(playerRight); } } + +SINGLE_BATTLE_TEST("Sandstorm damage rounds properly when maxHP < 16") +{ + GIVEN { + PLAYER(SPECIES_MAGIKARP) { Level(1); MaxHP(11); HP(11); } + OPPONENT(SPECIES_SANDSLASH); + } WHEN { + TURN { MOVE(opponent, MOVE_SANDSTORM); } + } SCENE { + HP_BAR(player, damage: 1); + } +} diff --git a/test/random.c b/test/random.c index 0232ff154..238a76467 100644 --- a/test/random.c +++ b/test/random.c @@ -196,13 +196,8 @@ TEST("RandomElement generates a uniform distribution") TEST("RandomUniform mul-based faster than mod-based (compile-time)") { - #if HQ_RANDOM == TRUE - const u32 expectedMulSum = 6; - const u32 expectedModSum = 4; - #else - const u32 expectedMulSum = 3; - const u32 expectedModSum = 4; - #endif + const u32 expectedMulSum = 6; + const u32 expectedModSum = 4; struct Benchmark mulBenchmark, modBenchmark; u32 mulSum = 0, modSum = 0; @@ -234,13 +229,8 @@ TEST("RandomUniform mul-based faster than mod-based (compile-time)") TEST("RandomUniform mul-based faster than mod-based (run-time)") { - #if HQ_RANDOM == TRUE - const u32 expectedMulSum = 289; - const u32 expectedModSum = 205; - #else - const u32 expectedMulSum = 232; - const u32 expectedModSum = 249; - #endif + const u32 expectedMulSum = 289; + const u32 expectedModSum = 205; u32 i; struct Benchmark mulBenchmark, modBenchmark; u32 mulSum = 0, modSum = 0; @@ -264,7 +254,6 @@ TEST("RandomUniform mul-based faster than mod-based (run-time)") EXPECT_EQ(modSum, expectedModSum); } -#if HQ_RANDOM == TRUE TEST("Thumb and C SFC32 implementations produce the same results") { u32 thumbSum; @@ -285,5 +274,4 @@ TEST("Thumb and C SFC32 implementations produce the same results") } EXPECT_EQ(thumbSum, cSum); -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index f27f858e5..844602ac1 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -35,20 +35,12 @@ #define STATE gBattleTestRunnerState #define DATA gBattleTestRunnerState->data -#if HQ_RANDOM == TRUE #define RNG_SEED_DEFAULT {0, 0, 0, 0} static inline bool32 RngSeedNotDefault(const rng_value_t *seed) { return (seed->a | seed->b | seed->c | seed->ctr) != 0; } -#else -#define RNG_SEED_DEFAULT 0x00000000 -static inline bool32 RngSeedNotDefault(const rng_value_t *seed) -{ - return *seed != RNG_SEED_DEFAULT; -} -#endif #undef Q_4_12 #define Q_4_12(n) (s32)((n) * 4096) @@ -410,7 +402,7 @@ u32 RandomUniformExcept(enum RandomTag tag, u32 lo, u32 hi, bool32 (*reject)(u32 if (STATE->trials == 1) { u32 n = 0, i; - for (i = lo; i < hi; i++) + for (i = lo; i <= hi; i++) if (!reject(i)) n++; STATE->trials = n; @@ -769,7 +761,7 @@ static u32 CountAiExpectMoves(struct ExpectedAIAction *expectedAction, u32 battl u32 i, countExpected = 0; for (i = 0; i < MAX_MON_MOVES; i++) { - if (gBitTable[i] & expectedAction->moveSlots) + if ((1u << i) & expectedAction->moveSlots) { if (printLog) PrintAiMoveLog(battlerId, i, gBattleMons[battlerId].moves[i], gBattleStruct->aiFinalScore[battlerId][expectedAction->target][i]); @@ -801,7 +793,7 @@ void TestRunner_Battle_CheckChosenMove(u32 battlerId, u32 moveId, u32 target) for (i = 0; i < MAX_MON_MOVES; i++) { - if (gBitTable[i] & expectedAction->moveSlots) + if ((1u << i) & expectedAction->moveSlots) { expectedMoveId = gBattleMons[battlerId].moves[i]; if (!expectedAction->notMove) @@ -917,8 +909,8 @@ static void CheckIfMaxScoreEqualExpectMove(u32 battlerId, s32 target, struct Exp // We expect move 'i', but it has the same best score as another move that we didn't expect. if (scores[i] == scores[bestScoreId] && !aiAction->notMove - && (aiAction->moveSlots & gBitTable[i]) - && !(aiAction->moveSlots & gBitTable[bestScoreId])) + && (aiAction->moveSlots & (1u << i)) + && !(aiAction->moveSlots & (1u << bestScoreId))) { Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: EXPECT_MOVE %S has the same best score(%d) as not expected MOVE %S", filename, aiAction->sourceLine, GetMoveName(moves[i]), scores[i], GetMoveName(moves[bestScoreId])); @@ -926,8 +918,8 @@ static void CheckIfMaxScoreEqualExpectMove(u32 battlerId, s32 target, struct Exp // We DO NOT expect move 'i', but it has the same best score as another move. if (scores[i] == scores[bestScoreId] && aiAction->notMove - && (aiAction->moveSlots & gBitTable[i]) - && !(aiAction->moveSlots & gBitTable[bestScoreId])) + && (aiAction->moveSlots & (1u << i)) + && !(aiAction->moveSlots & (1u << bestScoreId))) { Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: NOT_EXPECT_MOVE %S has the same best score(%d) as MOVE %S", filename, aiAction->sourceLine, GetMoveName(moves[i]), scores[i], GetMoveName(moves[bestScoreId])); @@ -940,9 +932,9 @@ static void PrintAiMoveLog(u32 battlerId, u32 moveSlot, u32 moveId, s32 totalSco s32 i, scoreFromLogs = 0; if (!DATA.logAI) return; - if (DATA.aiLogPrintedForMove[battlerId] & gBitTable[moveSlot]) return; + if (DATA.aiLogPrintedForMove[battlerId] & (1u << moveSlot)) return; - DATA.aiLogPrintedForMove[battlerId] |= gBitTable[moveSlot]; + DATA.aiLogPrintedForMove[battlerId] |= 1u << moveSlot; Test_MgbaPrintf("Score Log for move %S:\n", GetMoveName(moveId)); for (i = 0; i < MAX_AI_LOG_LINES; i++) { @@ -1327,7 +1319,7 @@ void TestRunner_Battle_AfterLastTurn(void) } STATE->runThen = TRUE; - STATE->runFinally = STATE->runParameter + 1 == STATE->parameters; + STATE->runFinally = STATE->runParameter + 1 == STATE->parameters && STATE->runTrial + 1 >= STATE->trials; InvokeTestFunction(test); STATE->runThen = FALSE; STATE->runFinally = FALSE; @@ -1357,18 +1349,15 @@ static void CB2_BattleTest_NextParameter(void) static inline rng_value_t MakeRngValue(const u16 seed) { - #if HQ_RANDOM == TRUE - int i; - rng_value_t result = {0, 0, seed, 1}; - for (i = 0; i < 16; i++) - { + int i; + rng_value_t result = {0, 0, seed, 1}; + for (i = 0; i < 16; i++) + { _SFC32_Next(&result); - } - return result; - #else - return ISO_RANDOMIZE1(seed); - #endif + } + return result; } + static void CB2_BattleTest_NextTrial(void) { ClearFlagAfterTest(); @@ -1452,7 +1441,7 @@ void Randomly(u32 sourceLine, u32 passes, u32 trials, struct RandomlyContext ctx { const struct BattleTest *test = GetBattleTest(); INVALID_IF(STATE->trials != 0, "PASSES_RANDOMLY can only be used once per test"); - INVALID_IF(test->resultsSize > 0, "PASSES_RANDOMLY is incompatible with results"); + INVALID_IF(test->resultsSize > 0 && STATE->parametersCount > 1, "PASSES_RANDOMLY is incompatible with results"); INVALID_IF(passes > trials, "%d passes specified, but only %d trials", passes, trials); STATE->rngTag = ctx.tag; STATE->rngTrialOffset = 0; @@ -2186,7 +2175,7 @@ static void TryMarkExpectMove(u32 sourceLine, struct BattlePokemon *battler, str id = DATA.expectedAiActionIndex[battlerId]; DATA.expectedAiActions[battlerId][id].type = B_ACTION_USE_MOVE; - DATA.expectedAiActions[battlerId][id].moveSlots |= gBitTable[moveSlot]; + DATA.expectedAiActions[battlerId][id].moveSlots |= 1 << moveSlot; DATA.expectedAiActions[battlerId][id].target = target; DATA.expectedAiActions[battlerId][id].explicitTarget = ctx->explicitTarget; DATA.expectedAiActions[battlerId][id].sourceLine = sourceLine;