diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 244976ea1e..874ebdd590 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -26,8 +26,18 @@ jobs: run: | cd docs mdbook build + - name: Check if Pages is enabled + uses: octokit/request-action@v2.x + id: check_pages + continue-on-error: true + with: + route: GET /repos/{repo}/pages + repo: ${{ github.repository }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Setup Pages uses: actions/configure-pages@v4 + if: steps.check_pages.outcome == 'success' - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: @@ -35,3 +45,4 @@ jobs: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 + if: steps.check_pages.outcome == 'success' diff --git a/INSTALL.md b/INSTALL.md index fe8ea2e391..ed7d5bad08 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -475,6 +475,13 @@ devkitARM is now installed. Then proceed to [Choosing where to store pokeemerald Expansion (Linux)](#choosing-where-to-store-pokeemerald-expansion-linux). +### NixOS +Run the following command to start an interactive shell with the necessary packages: +```bash +nix-shell -p pkgsCross.arm-embedded.stdenv.cc git pkg-config libpng +``` +Then proceed to [Choosing where to store pokeemerald Expansion (Linux)](#choosing-where-to-store-pokeemerald-expansion-linux). + ### Other distributions _(Specific instructions for other distributions would be greatly appreciated!)_ diff --git a/Makefile b/Makefile index 74e6449236..283df7ac0f 100644 --- a/Makefile +++ b/Makefile @@ -80,7 +80,7 @@ MODERN_ELF_NAME := $(MODERN_ROM_NAME:.gba=.elf) MODERN_MAP_NAME := $(MODERN_ROM_NAME:.gba=.map) MODERN_OBJ_DIR_NAME := build/modern -SHELL := /bin/bash -o pipefail +SHELL := bash -o pipefail ELF = $(ROM:.gba=.elf) MAP = $(ROM:.gba=.map) diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index a28613e32b..19bb0b5a0f 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1672,6 +1672,10 @@ callnative BS_ApplyTerastallization .endm + .macro damagetoquartertargethp + callnative BS_DamageToQuarterTargetHP + .endm + @ various command changed to more readable macros .macro cancelmultiturnmoves battler:req various \battler, VARIOUS_CANCEL_MULTI_TURN_MOVES diff --git a/asmdiff.sh b/asmdiff.sh index f5a7010747..aca670e324 100755 --- a/asmdiff.sh +++ b/asmdiff.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash if [[ -d "$DEVKITARM/bin/" ]]; then OBJDUMP_BIN="$DEVKITARM/bin/arm-none-eabi-objdump" diff --git a/data/battle_anim_scripts.s b/data/battle_anim_scripts.s index 0c0ede09e3..e98274d2f6 100644 --- a/data/battle_anim_scripts.s +++ b/data/battle_anim_scripts.s @@ -1,4 +1,4 @@ -#include "config.h" +#include "config/general.h" #include "config/battle.h" #include "constants/battle.h" #include "constants/battle_anim.h" @@ -4221,7 +4221,6 @@ Move_SMACK_DOWN:: createvisualtask AnimTask_SmokescreenImpact, 0x8, 0x400, 0x1902 fadetobg BG_IN_AIR waitbgfadeout - createvisualtask AnimTask_StartSlidingBg, 5, 0x0, 0x0, 0x0, 0xffff createvisualtask AnimTask_SeismicTossBgAccelerateDownAtEnd, 3 goto SeismicTossWeak diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 31c44e10de..21536f3de4 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -1,4 +1,4 @@ -#include "config.h" +#include "config/general.h" #include "config/battle.h" #include "constants/global.h" #include "constants/battle.h" @@ -20,6 +20,16 @@ .section script_data, "aw", %progbits +BattleScript_DamageToQuarterTargetHP:: + attackcanceler + accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE + attackstring + ppreduce + typecalc + bichalfword gMoveResultFlags, MOVE_RESULT_SUPER_EFFECTIVE | MOVE_RESULT_NOT_VERY_EFFECTIVE + damagetoquartertargethp + goto BattleScript_HitFromAtkAnimation + BattleScript_Terastallization:: @ TODO: no string prints in S/V, but right now this helps with clarity printstring STRINGID_PKMNSTORINGENERGY @@ -5496,7 +5506,7 @@ BattleScript_GiveExp:: BattleScript_HandleFaintedMon:: setbyte sSHIFT_SWITCHED, 0 - checkteamslost BattleScript_LinkHandleFaintedMonMultiple + checkteamslost BattleScript_HandleFaintedMonMultiple jumpifbyte CMP_NOT_EQUAL, gBattleOutcome, 0, BattleScript_FaintedMonEnd jumpifbattletype BATTLE_TYPE_TRAINER | BATTLE_TYPE_DOUBLE, BattleScript_FaintedMonTryChoose jumpifword CMP_NO_COMMON_BITS, gHitMarker, HITMARKER_PLAYER_FAINTED, BattleScript_FaintedMonTryChoose @@ -5577,13 +5587,13 @@ BattleScript_FaintedMonShiftSwitched: copybyte gBattlerTarget, sSAVED_BATTLER goto BattleScript_FaintedMonSendOutNewEnd -BattleScript_LinkHandleFaintedMonMultiple:: - openpartyscreen BS_FAINTED_LINK_MULTIPLE_1, BattleScript_LinkHandleFaintedMonMultipleStart -BattleScript_LinkHandleFaintedMonMultipleStart:: +BattleScript_HandleFaintedMonMultiple:: + openpartyscreen BS_FAINTED_MULTIPLE_1, BattleScript_HandleFaintedMonMultipleStart +BattleScript_HandleFaintedMonMultipleStart:: switchhandleorder BS_FAINTED, 0 - openpartyscreen BS_FAINTED_LINK_MULTIPLE_2, BattleScript_LinkHandleFaintedMonMultipleEnd + openpartyscreen BS_FAINTED_MULTIPLE_2, BattleScript_HandleFaintedMonMultipleEnd switchhandleorder BS_FAINTED, 0 -BattleScript_LinkHandleFaintedMonLoop:: +BattleScript_HandleFaintedMonLoop:: switchhandleorder BS_FAINTED, 3 drawpartystatussummary BS_FAINTED getswitchedmondata BS_FAINTED @@ -5595,9 +5605,10 @@ BattleScript_LinkHandleFaintedMonLoop:: hidepartystatussummary BS_FAINTED switchinanim BS_FAINTED, FALSE waitstate - switchineffects BS_FAINTED_LINK_MULTIPLE_1 - jumpifbytenotequal gBattlerFainted, gBattlersCount, BattleScript_LinkHandleFaintedMonLoop -BattleScript_LinkHandleFaintedMonMultipleEnd:: + switchineffects BS_FAINTED_MULTIPLE_1 + jumpifbytenotequal gBattlerFainted, gBattlersCount, BattleScript_HandleFaintedMonLoop +BattleScript_HandleFaintedMonMultipleEnd:: + switchineffects BS_FAINTED_MULTIPLE_2 end2 BattleScript_LocalTrainerBattleWon:: @@ -5828,6 +5839,7 @@ BattleScript_DoSwitchOut:: BattleScript_PursuitDmgOnSwitchOut:: pause B_WAIT_TIME_SHORT + orword gHitMarker, HITMARKER_OBEYS attackstring ppreduce critcalc @@ -5845,11 +5857,12 @@ BattleScript_PursuitDmgOnSwitchOut:: resultmessage waitmessage B_WAIT_TIME_LONG tryfaintmon BS_TARGET - moveendfromto MOVEEND_ABILITIES, MOVEEND_CHOICE_MOVE + moveendfromto MOVEEND_ABILITIES, MOVEEND_ATTACKER_INVISIBLE @ MOVEEND_CHOICE_MOVE has to be included jumpiffainted BS_TARGET, FALSE, BattleScript_PursuitDmgOnSwitchOutRet setbyte sGIVEEXP_STATE, 0 getexp BS_TARGET BattleScript_PursuitDmgOnSwitchOutRet: + bicword gHitMarker, HITMARKER_OBEYS return BattleScript_Pausex20:: @@ -6034,6 +6047,10 @@ BattleScript_MagicRoomEnds:: waitmessage B_WAIT_TIME_LONG end2 +BattleScript_GrassyTerrainEnds:: + call BattleScript_GrassyTerrainHeals_Ret + goto BattleScript_TerrainEnds + BattleScript_TerrainEnds_Ret:: printfromtable gTerrainStringIds waitmessage B_WAIT_TIME_LONG @@ -6526,6 +6543,9 @@ BattleScript_PerishSongCountGoesDown:: waitmessage B_WAIT_TIME_LONG end2 +BattleScript_AllStatsUpZMove:: + printfromtable gZEffectStringIds + waitmessage B_WAIT_TIME_LONG BattleScript_AllStatsUp:: jumpifstat BS_ATTACKER, CMP_LESS_THAN, STAT_ATK, MAX_STAT_STAGE, BattleScript_AllStatsUpAtk jumpifstat BS_ATTACKER, CMP_LESS_THAN, STAT_DEF, MAX_STAT_STAGE, BattleScript_AllStatsUpAtk @@ -6984,13 +7004,13 @@ BattleScript_MegaEvolution:: BattleScript_MegaEvolutionAfterString: waitmessage B_WAIT_TIME_LONG setbyte gIsCriticalHit, 0 - handlemegaevo BS_ATTACKER, 0 - playanimation BS_ATTACKER, B_ANIM_MEGA_EVOLUTION + handlemegaevo BS_SCRIPTING, 0 + playanimation BS_SCRIPTING, B_ANIM_MEGA_EVOLUTION waitanimation - handlemegaevo BS_ATTACKER, 1 + handlemegaevo BS_SCRIPTING, 1 printstring STRINGID_MEGAEVOEVOLVED waitmessage B_WAIT_TIME_LONG - switchinabilities BS_ATTACKER + switchinabilities BS_SCRIPTING end3 BattleScript_WishMegaEvolution:: @@ -7027,13 +7047,13 @@ BattleScript_UltraBurst:: printstring STRINGID_ULTRABURSTREACTING waitmessage B_WAIT_TIME_LONG setbyte gIsCriticalHit, 0 - handleultraburst BS_ATTACKER, 0 - playanimation BS_ATTACKER, B_ANIM_ULTRA_BURST + handleultraburst BS_SCRIPTING, 0 + playanimation BS_SCRIPTING, B_ANIM_ULTRA_BURST waitanimation - handleultraburst BS_ATTACKER, 1 + handleultraburst BS_SCRIPTING, 1 printstring STRINGID_ULTRABURSTCOMPLETED waitmessage B_WAIT_TIME_LONG - switchinabilities BS_ATTACKER + switchinabilities BS_SCRIPTING end3 BattleScript_GulpMissileFormChange:: @@ -8342,6 +8362,10 @@ BattleScript_MoveUsedPsychicTerrainPrevents:: goto BattleScript_MoveEnd BattleScript_GrassyTerrainHeals:: + call BattleScript_GrassyTerrainHeals_Ret + end2 + +BattleScript_GrassyTerrainHeals_Ret:: setbyte gBattleCommunication, 0 BattleScript_GrassyTerrainLoop: copyarraywithindex gBattlerAttacker, gBattlerByTurnOrder, gBattleCommunication, 1 @@ -8359,7 +8383,7 @@ BattleScript_GrassyTerrainLoopEnd:: bicword gHitMarker, HITMARKER_IGNORE_BIDE | HITMARKER_IGNORE_SUBSTITUTE | HITMARKER_PASSIVE_DAMAGE jumpifword CMP_COMMON_BITS, gFieldStatuses, STATUS_FIELD_TERRAIN_PERMANENT, BattleScript_GrassyTerrainHealEnd BattleScript_GrassyTerrainHealEnd: - end2 + return BattleScript_AbilityNoSpecificStatLoss:: pause B_WAIT_TIME_SHORT @@ -8700,7 +8724,7 @@ BattleScript_SynchronizeActivates:: return BattleScript_NoItemSteal:: - pause B_WAIT_TIME_SHORT + call BattleScript_AbilityPopUpTarget printstring STRINGID_PKMNSXMADEYINEFFECTIVE waitmessage B_WAIT_TIME_LONG return diff --git a/data/contest_ai_scripts.s b/data/contest_ai_scripts.s index affaf31d6d..f04e022bb5 100644 --- a/data/contest_ai_scripts.s +++ b/data/contest_ai_scripts.s @@ -1,4 +1,4 @@ -#include "config.h" +#include "config/general.h" #include "constants/global.h" #include "constants/contest.h" .include "asm/macros.inc" diff --git a/data/event_scripts.s b/data/event_scripts.s index 54fc9a213b..c156ab1554 100644 --- a/data/event_scripts.s +++ b/data/event_scripts.s @@ -1,4 +1,4 @@ -#include "config.h" +#include "config/general.h" #include "config/battle.h" #include "config/item.h" #include "constants/global.h" diff --git a/data/maps/MossdeepCity_SpaceCenter_2F/scripts.inc b/data/maps/MossdeepCity_SpaceCenter_2F/scripts.inc index 4ec82003b5..6fbf015bb1 100644 --- a/data/maps/MossdeepCity_SpaceCenter_2F/scripts.inc +++ b/data/maps/MossdeepCity_SpaceCenter_2F/scripts.inc @@ -313,6 +313,9 @@ MossdeepCity_SpaceCenter_2F_EventScript_DefeatedMaxieTabitha:: setobjectmovementtype LOCALID_SCIENTIST, MOVEMENT_TYPE_WANDER_AROUND addobject LOCALID_SCIENTIST fadescreen FADE_FROM_BLACK +#ifdef BUGFIX + releaseall +#endif end MossdeepCity_SpaceCenter_2F_EventScript_StevenFacePlayer:: diff --git a/graphics/battle_interface/burst_trigger.png b/graphics/battle_interface/burst_trigger.png index e3eb6384d4..bbd16ac555 100644 Binary files a/graphics/battle_interface/burst_trigger.png and b/graphics/battle_interface/burst_trigger.png differ diff --git a/graphics/battle_interface/z_move_trigger.png b/graphics/battle_interface/z_move_trigger.png index d719494d9f..7d5fbaba0f 100644 Binary files a/graphics/battle_interface/z_move_trigger.png and b/graphics/battle_interface/z_move_trigger.png differ diff --git a/graphics/pokemon/bounsweet/front.png b/graphics/pokemon/bounsweet/front.png index a31220dcf9..9e09d6d728 100644 Binary files a/graphics/pokemon/bounsweet/front.png and b/graphics/pokemon/bounsweet/front.png differ diff --git a/graphics/pokemon/bounsweet/normal.pal b/graphics/pokemon/bounsweet/normal.pal index 6ba3b9fb34..029ef53c85 100644 --- a/graphics/pokemon/bounsweet/normal.pal +++ b/graphics/pokemon/bounsweet/normal.pal @@ -15,5 +15,5 @@ JASC-PAL 248 136 136 88 104 96 184 192 192 -0 0 0 +248 248 248 0 0 0 diff --git a/graphics/pokemon/bounsweet/shiny.pal b/graphics/pokemon/bounsweet/shiny.pal index 14fc1f5ab3..704d72a19a 100644 --- a/graphics/pokemon/bounsweet/shiny.pal +++ b/graphics/pokemon/bounsweet/shiny.pal @@ -14,6 +14,6 @@ JASC-PAL 248 224 40 248 136 136 88 104 96 -184 192 192 -0 0 0 +200 192 128 +247 240 184 0 0 0 diff --git a/graphics/pokemon/bruxish/back.png b/graphics/pokemon/bruxish/back.png index 32aeed0b52..7969fb7197 100644 Binary files a/graphics/pokemon/bruxish/back.png and b/graphics/pokemon/bruxish/back.png differ diff --git a/graphics/pokemon/bruxish/front.png b/graphics/pokemon/bruxish/front.png index 4447621e55..ce29eb8d5f 100644 Binary files a/graphics/pokemon/bruxish/front.png and b/graphics/pokemon/bruxish/front.png differ diff --git a/graphics/pokemon/bruxish/shiny.pal b/graphics/pokemon/bruxish/shiny.pal index a609040e84..5deb432b19 100644 --- a/graphics/pokemon/bruxish/shiny.pal +++ b/graphics/pokemon/bruxish/shiny.pal @@ -10,8 +10,8 @@ JASC-PAL 120 24 24 232 56 40 136 120 104 -224 216 208 -192 176 160 +248 248 248 +200 192 176 200 160 80 232 208 136 248 248 248 diff --git a/graphics/pokemon/charjabug/anim_front.png b/graphics/pokemon/charjabug/anim_front.png index 163ab2ba2d..58d39c97d2 100644 Binary files a/graphics/pokemon/charjabug/anim_front.png and b/graphics/pokemon/charjabug/anim_front.png differ diff --git a/graphics/pokemon/charjabug/shiny.pal b/graphics/pokemon/charjabug/shiny.pal index 58974fb24f..a5ffc233bc 100644 --- a/graphics/pokemon/charjabug/shiny.pal +++ b/graphics/pokemon/charjabug/shiny.pal @@ -15,5 +15,5 @@ JASC-PAL 248 160 72 112 72 24 176 112 48 -0 0 0 -0 0 0 +8 64 88 +176 168 176 diff --git a/graphics/pokemon/lurantis/back.png b/graphics/pokemon/lurantis/back.png index a080a77f09..d133d63f7a 100644 Binary files a/graphics/pokemon/lurantis/back.png and b/graphics/pokemon/lurantis/back.png differ diff --git a/include/battle.h b/include/battle.h index 6f105fa54c..0e238e61b9 100644 --- a/include/battle.h +++ b/include/battle.h @@ -16,6 +16,7 @@ #include "battle_debug.h" #include "battle_dynamax.h" #include "battle_terastal.h" +#include "battle_gimmick.h" #include "random.h" // for rng_value_t // Helper for accessing command arguments and advancing gBattlescriptCurrInstr. @@ -370,8 +371,6 @@ struct AiLogicData bool8 weatherHasEffect; // The same as WEATHER_HAS_EFFECT. Stored here, so it's called only once. u8 mostSuitableMonId[MAX_BATTLERS_COUNT]; // Stores result of GetMostSuitableMonToSwitchInto, which decides which generic mon the AI would switch into if they decide to switch. This can be overruled by specific mons found in ShouldSwitch; the final resulting mon is stored in AI_monToSwitchIntoId. struct SwitchinCandidate switchinCandidate; // Struct used for deciding which mon to switch to in battle_ai_switch_items.c - bool8 shouldTerastal[MAX_BATTLERS_COUNT]; - bool8 shouldDynamax[MAX_BATTLERS_COUNT]; }; struct AI_ThinkingStruct @@ -558,24 +557,6 @@ struct LinkBattlerHeader struct BattleEnigmaBerry battleEnigmaBerry; }; -struct MegaEvolutionData -{ - u8 toEvolve; // As flags using gBitTable. - bool8 alreadyEvolved[4]; // Array id is used for mon position. - u8 battlerId; - bool8 playerSelect; - u8 triggerSpriteId; -}; - -struct UltraBurstData -{ - u8 toBurst; // As flags using gBitTable. - bool8 alreadyBursted[4]; // Array id is used for mon position. - u8 battlerId; - bool8 playerSelect; - u8 triggerSpriteId; -}; - struct Illusion { u8 on; @@ -587,48 +568,30 @@ struct Illusion struct ZMoveData { - u8 viable:1; // current move can become a z move + u8 viable:1; // current move can become a z move u8 viewing:1; // if player is viewing the z move name instead of regular moves - u8 active:1; // is z move being used this turn - u8 zStatusActive:1; - u8 healReplacement:1; - u8 activeCategory:2; // active z move category - u8 zUnused:1; - u8 triggerSpriteId; + u8 healReplacement:6; u8 possibleZMoves[MAX_BATTLERS_COUNT]; - u16 chosenZMove; // z move of move cursor is on - u8 effect; - u8 used[MAX_BATTLERS_COUNT]; //one per bank for multi-battles - u16 toBeUsed[MAX_BATTLERS_COUNT]; // z moves per battler to be used u16 baseMoves[MAX_BATTLERS_COUNT]; - u8 categories[MAX_BATTLERS_COUNT]; }; struct DynamaxData { - bool8 playerSelect; - u8 triggerSpriteId; - u8 toDynamax; // flags using gBitTable - bool8 alreadyDynamaxed[NUM_BATTLE_SIDES]; - bool8 dynamaxed[MAX_BATTLERS_COUNT]; u8 dynamaxTurns[MAX_BATTLERS_COUNT]; - u8 usingMaxMove[MAX_BATTLERS_COUNT]; - u8 activeCategory; - u8 categories[MAX_BATTLERS_COUNT]; - u16 baseMove[MAX_BATTLERS_COUNT]; // base move of Max Move + u16 baseMoves[MAX_BATTLERS_COUNT]; // base move of Max Move u16 lastUsedBaseMove; u16 levelUpHP; }; -struct TeraData +struct BattleGimmickData { - bool8 toTera; // flags using gBitTable - bool8 isTerastallized[NUM_BATTLE_SIDES]; // stored as a bitfield for each side's party members - bool8 alreadyTerastallized[MAX_BATTLERS_COUNT]; - bool8 playerSelect; - u32 stellarBoostFlags[NUM_BATTLE_SIDES]; // stored as a bitfield of flags for all types for each side + u8 usableGimmick[MAX_BATTLERS_COUNT]; // first usable gimmick that can be selected for each battler + bool8 playerSelect; // used to toggle trigger and update battle UI u8 triggerSpriteId; u8 indicatorSpriteId[MAX_BATTLERS_COUNT]; + u8 toActivate; // stores whether a battler should transform at start of turn as bitfield + u8 activeGimmick[NUM_BATTLE_SIDES][PARTY_SIZE]; // stores the active gimmick for each party member + bool8 activated[MAX_BATTLERS_COUNT][GIMMICKS_COUNT]; // stores whether a trainer has used gimmick }; struct LostItem @@ -703,7 +666,11 @@ struct BattleStruct u16 abilityPreventingSwitchout; u8 hpScale; u16 synchronizeMoveEffect; - bool8 anyMonHasTransformed; + u8 anyMonHasTransformed:1; // Only used in battle_tv.c + u8 multipleSwitchInBattlers:4; // One bit per battler + u8 multipleSwitchInState:2; + u8 multipleSwitchInCursor:3; + u8 multipleSwitchInSortedBattlers[MAX_BATTLERS_COUNT]; void (*savedCallback)(void); u16 usedHeldItems[PARTY_SIZE][NUM_BATTLE_SIDES]; // For each party member and side. For harvest, recycle u16 chosenItem[MAX_BATTLERS_COUNT]; @@ -753,11 +720,9 @@ struct BattleStruct u8 activeAbilityPopUps; // as bits for each battler u8 abilityPopUpSpriteIds[MAX_BATTLERS_COUNT][2]; // two per battler bool8 throwingPokeBall; - struct MegaEvolutionData mega; - struct UltraBurstData burst; struct ZMoveData zmove; struct DynamaxData dynamax; - struct TeraData tera; + struct BattleGimmickData gimmick; const u8 *trainerSlideMsg; bool8 trainerSlideLowHpMsgDone; u8 introState; @@ -779,12 +744,15 @@ struct BattleStruct u16 moveEffect2; // For Knock Off u16 changedSpecies[NUM_BATTLE_SIDES][PARTY_SIZE]; // For forms when multiple mons can change into the same pokemon. u8 quickClawBattlerId; - struct LostItem itemLost[PARTY_SIZE]; // Player's team that had items consumed or stolen (two bytes per party member) + struct LostItem itemLost[NUM_BATTLE_SIDES][PARTY_SIZE]; // Pokemon that had items consumed or stolen (two bytes per party member per side) u8 forcedSwitch:4; // For each battler u8 additionalEffectsCounter:4; // A counter for the additionalEffects applied by the current move in Cmd_setadditionaleffects u8 blunderPolicy:1; // should blunder policy activate u8 swapDamageCategory:1; // Photon Geyser, Shell Side Arm, Light That Burns the Sky u8 bouncedMoveIsUsed:1; + u8 descriptionSubmenu:1; // For Move Description window in move selection screen + u8 ackBallUseBtn:1; // Used for the last used ball feature + u8 ballSwapped:1; // Used for the last used ball feature u8 ballSpriteIds[2]; // item gfx, window gfx u8 appearedInBattle; // Bitfield to track which Pokemon appeared in battle. Used for Burmy's form change u8 skyDropTargets[MAX_BATTLERS_COUNT]; // For Sky Drop, to account for if multiple Pokemon use Sky Drop in a double battle. @@ -829,6 +797,8 @@ struct BattleStruct u8 shellSideArmCategory[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT]; u8 boosterEnergyActivates; 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 }; // The palaceFlags member of struct BattleStruct contains 1 flag per move to indicate which moves the AI should consider, diff --git a/include/battle_ai_main.h b/include/battle_ai_main.h index ce92296b67..60028eb8cd 100644 --- a/include/battle_ai_main.h +++ b/include/battle_ai_main.h @@ -29,8 +29,9 @@ #define STAT_CHANGE_ACC 10 #define STAT_CHANGE_EVASION 11 -#define BEST_DAMAGE_MOVE 1 // Move with the most amount of hits with the best accuracy/effect -#define POWERFUL_STATUS_MOVE 10 // Moves with this score will be chosen over a move that faints target +#define BEST_DAMAGE_MOVE 1 // Move with the most amount of hits with the best accuracy/effect +#define POWERFUL_STATUS_MOVE 10 // Moves with this score will be chosen over a move that faints target +#define NO_DAMAGE_OR_FAILS -20 // Move fails or does no damage // Scores given in AI_CalcMoveEffectScore #define WEAK_EFFECT 1 @@ -64,6 +65,14 @@ score += val; \ } while (0) \ +#define ADJUST_AND_RETURN_SCORE(val) \ + do \ + { \ + TestRunner_Battle_AIAdjustScore(__FILE__, __LINE__, sBattler_AI, AI_THINKING_STRUCT->movesetIndex, val); \ + score += val; \ + return score; \ + } while (0) \ + #define ADJUST_SCORE_PTR(val) \ do \ { \ diff --git a/include/battle_ai_switch_items.h b/include/battle_ai_switch_items.h index 16f6468757..b52e792612 100644 --- a/include/battle_ai_switch_items.h +++ b/include/battle_ai_switch_items.h @@ -3,7 +3,7 @@ void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId); void AI_TrySwitchOrUseItem(u32 battler); -u8 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd); +u32 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd); bool32 ShouldSwitch(u32 battler, bool32 emitResult); #endif // GUARD_BATTLE_AI_SWITCH_ITEMS_H diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index ed30d2ad4e..f344eb986c 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -146,10 +146,8 @@ bool32 HasMagicCoatAffectedMove(u32 battler); bool32 HasSnatchAffectedMove(u32 battler); // status checks -bool32 AI_CanBeBurned(u32 battler, u32 ability); bool32 AI_CanGetFrostbite(u32 battler, u32 ability); bool32 AI_CanBeConfused(u32 battlerAtk, u32 battlerDef, u32 move, u32 ability); -bool32 AI_CanSleep(u32 battler, u32 ability); bool32 IsBattlerIncapacitated(u32 battler, u32 ability); bool32 AI_CanPutToSleep(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move, u32 partnerMove); bool32 ShouldPoisonSelf(u32 battler, u32 ability); diff --git a/include/battle_anim.h b/include/battle_anim.h index c17de9180c..ce9749685e 100644 --- a/include/battle_anim.h +++ b/include/battle_anim.h @@ -203,10 +203,10 @@ u8 GetBattlerSpriteDefault_Y(u8 battlerId); u8 GetSubstituteSpriteDefault_Y(u8 battlerId); // battle_anim_status_effects.c -#define STAT_ANIM_PLUS1 MOVE_EFFECT_ATK_PLUS_1 - 1 -#define STAT_ANIM_PLUS2 MOVE_EFFECT_ATK_PLUS_2 - 1 -#define STAT_ANIM_MINUS1 MOVE_EFFECT_ATK_MINUS_1 - 1 -#define STAT_ANIM_MINUS2 MOVE_EFFECT_ATK_MINUS_2 - 1 +#define STAT_ANIM_PLUS1 (MOVE_EFFECT_ATK_PLUS_1 - 1) +#define STAT_ANIM_PLUS2 (MOVE_EFFECT_ATK_PLUS_2 - 1) +#define STAT_ANIM_MINUS1 (MOVE_EFFECT_ATK_MINUS_1 - 1) +#define STAT_ANIM_MINUS2 (MOVE_EFFECT_ATK_MINUS_2 - 1) #define STAT_ANIM_MULTIPLE_PLUS1 55 #define STAT_ANIM_MULTIPLE_PLUS2 56 #define STAT_ANIM_MULTIPLE_MINUS1 57 diff --git a/include/battle_controllers.h b/include/battle_controllers.h index 5e157a2766..a670113bc7 100644 --- a/include/battle_controllers.h +++ b/include/battle_controllers.h @@ -98,10 +98,7 @@ enum { // Special return values in gBattleBufferB from Battle Controller functions. #define RET_VALUE_LEVELED_UP 11 -#define RET_MEGA_EVOLUTION (1 << 7) -#define RET_ULTRA_BURST (1 << 6) -#define RET_DYNAMAX (1 << 5) -#define RET_TERASTAL (1 << 4) +#define RET_GIMMICK (1 << 7) struct UnusedControllerStruct { @@ -131,8 +128,6 @@ struct ChooseMoveStruct u8 monType1; u8 monType2; u8 monType3; - struct MegaEvolutionData mega; - struct UltraBurstData burst; struct ZMoveData zmove; }; diff --git a/include/battle_dynamax.h b/include/battle_dynamax.h index 1e3f3265b7..b5223f271b 100644 --- a/include/battle_dynamax.h +++ b/include/battle_dynamax.h @@ -56,21 +56,19 @@ enum MaxMoveEffect MAX_EFFECT_BYPASS_PROTECT, }; -bool32 IsDynamaxed(u16 battlerId); -bool32 CanDynamax(u16 battlerId); -bool32 IsGigantamaxed(u16 battlerId); +bool32 CanDynamax(u32 battler); +bool32 IsGigantamaxed(u32 battler); void ApplyDynamaxHPMultiplier(u32 battler, struct Pokemon* mon); -void PrepareBattlerForDynamax(u16 battlerId); -u16 GetNonDynamaxHP(u16 battlerId); -u16 GetNonDynamaxMaxHP(u32 battlerId); -void UndoDynamax(u16 battlerId); -bool32 IsMoveBlockedByMaxGuard(u16 move); -bool32 IsMoveBlockedByDynamax(u16 move); +void ActivateDynamax(u32 battler); +u16 GetNonDynamaxHP(u32 battler); +u16 GetNonDynamaxMaxHP(u32 battler); +void UndoDynamax(u32 battler); +bool32 IsMoveBlockedByMaxGuard(u32 move); +bool32 IsMoveBlockedByDynamax(u32 move); -bool32 ShouldUseMaxMove(u16 battlerId, u16 baseMove); -u16 GetMaxMove(u16 battlerId, u16 baseMove); -u8 GetMaxMovePower(u16 move); -bool32 IsMaxMove(u16 move); +u16 GetMaxMove(u32 battler, u32 baseMove); +u8 GetMaxMovePower(u32 move); +bool32 IsMaxMove(u32 move); void ChooseDamageNonTypesString(u8 type); void BS_UpdateDynamax(void); @@ -83,10 +81,4 @@ void BS_HealOneSixth(void); void BS_TryRecycleBerry(void); void BS_JumpIfDynamaxed(void); -void ChangeDynamaxTriggerSprite(u8 spriteId, u8 animId); -void CreateDynamaxTriggerSprite(u8, bool8); -void HideDynamaxTriggerSprite(void); -bool32 IsDynamaxTriggerSpriteActive(void); -void DestroyDynamaxTriggerSprite(void); - #endif diff --git a/include/battle_gimmick.h b/include/battle_gimmick.h new file mode 100644 index 0000000000..de1352a611 --- /dev/null +++ b/include/battle_gimmick.h @@ -0,0 +1,51 @@ +#ifndef GUARD_BATTLE_GIMMICK_H +#define GUARD_BATTLE_GIMMICK_H + +enum Gimmick +{ + GIMMICK_NONE, + GIMMICK_MEGA, + GIMMICK_ULTRA_BURST, + GIMMICK_Z_MOVE, + GIMMICK_DYNAMAX, + GIMMICK_TERA, + GIMMICKS_COUNT, +}; + +struct GimmickInfo +{ + const struct SpritePalette *triggerPal; // trigger gfx data + const struct SpriteSheet *triggerSheet; + const struct SpriteTemplate *triggerTemplate; + const struct SpritePalette *indicatorPal; // indicator gfx data + const struct SpriteSheet *indicatorSheet; + bool32 (*CanActivate)(u32 battler); + void (*ActivateGimmick)(u32 battler); +}; + +void AssignUsableGimmicks(void); +bool32 CanActivateGimmick(u32 battler, enum Gimmick gimmick); +bool32 IsGimmickSelected(u32 battler, enum Gimmick gimmick); +void SetActiveGimmick(u32 battler, enum Gimmick gimmick); +enum Gimmick GetActiveGimmick(u32 battler); +bool32 ShouldTrainerBattlerUseGimmick(u32 battler, enum Gimmick gimmick); +bool32 HasTrainerUsedGimmick(u32 battler, enum Gimmick gimmick); +void SetGimmickAsActivated(u32 battler, enum Gimmick gimmick); + +void ChangeGimmickTriggerSprite(u32 spriteId, u32 animId); +void CreateGimmickTriggerSprite(u32 battler); +bool32 IsGimmickTriggerSpriteActive(void); +void HideGimmickTriggerSprite(void); +void DestroyGimmickTriggerSprite(void); + +void LoadIndicatorSpritesGfx(void); +u32 GetIndicatorTileTag(u32 battler); +u32 GetIndicatorPalTag(u32 battler); +void UpdateIndicatorVisibilityAndType(u32 healthboxId, bool32 invisible); +void UpdateIndicatorOamPriority(u32 healthboxId, u32 oamPriority); +void UpdateIndicatorLevelData(u32 healthboxId, u32 level); +void CreateIndicatorSprite(u32 battler); + +extern const struct GimmickInfo gGimmicksInfo[]; + +#endif diff --git a/include/battle_interface.h b/include/battle_interface.h index 903483e1d5..6635298dc1 100644 --- a/include/battle_interface.h +++ b/include/battle_interface.h @@ -48,45 +48,38 @@ enum #define TAG_HEALTHBAR_PAL TAG_HEALTHBAR_PLAYER1_TILE #define TAG_HEALTHBOX_PAL TAG_HEALTHBOX_PLAYER1_TILE -#define TAG_MEGA_TRIGGER_TILE 0xD777 +#define TAG_GIMMICK_TRIGGER_TILE 0xD777 #define TAG_MEGA_INDICATOR_TILE 0xD778 #define TAG_ALPHA_INDICATOR_TILE 0xD779 #define TAG_OMEGA_INDICATOR_TILE 0xD77A -#define TAG_ZMOVE_TRIGGER_TILE 0xD77B -#define TAG_BURST_TRIGGER_TILE 0xD77C -#define TAG_DYNAMAX_TRIGGER_TILE 0xD77D -#define TAG_DYNAMAX_INDICATOR_TILE 0xD77E +#define TAG_DYNAMAX_INDICATOR_TILE 0xD77B -#define TAG_NORMAL_INDICATOR_TILE 0xD77F -#define TAG_FIGHTING_INDICATOR_TILE 0xD780 -#define TAG_FLYING_INDICATOR_TILE 0xD781 -#define TAG_POISON_INDICATOR_TILE 0xD782 -#define TAG_GROUND_INDICATOR_TILE 0xD783 -#define TAG_ROCK_INDICATOR_TILE 0xD784 -#define TAG_BUG_INDICATOR_TILE 0xD785 -#define TAG_GHOST_INDICATOR_TILE 0xD786 -#define TAG_STEEL_INDICATOR_TILE 0xD787 +#define TAG_NORMAL_INDICATOR_TILE 0xD77C +#define TAG_FIGHTING_INDICATOR_TILE 0xD77D +#define TAG_FLYING_INDICATOR_TILE 0xD77E +#define TAG_POISON_INDICATOR_TILE 0xD77F +#define TAG_GROUND_INDICATOR_TILE 0xD780 +#define TAG_ROCK_INDICATOR_TILE 0xD781 +#define TAG_BUG_INDICATOR_TILE 0xD782 +#define TAG_GHOST_INDICATOR_TILE 0xD783 +#define TAG_STEEL_INDICATOR_TILE 0xD784 // empty spot for TYPE_MYSTERY -#define TAG_FIRE_INDICATOR_TILE 0xD789 -#define TAG_WATER_INDICATOR_TILE 0xD78A -#define TAG_GRASS_INDICATOR_TILE 0xD78B -#define TAG_ELECTRIC_INDICATOR_TILE 0xD78C -#define TAG_PSYCHIC_INDICATOR_TILE 0xD78D -#define TAG_ICE_INDICATOR_TILE 0xD78E -#define TAG_DRAGON_INDICATOR_TILE 0xD78F -#define TAG_DARK_INDICATOR_TILE 0xD790 -#define TAG_FAIRY_INDICATOR_TILE 0xD791 -#define TAG_STELLAR_INDICATOR_TILE 0xD792 -#define TAG_TERA_TRIGGER_TILE 0xD793 +#define TAG_FIRE_INDICATOR_TILE 0xD786 +#define TAG_WATER_INDICATOR_TILE 0xD787 +#define TAG_GRASS_INDICATOR_TILE 0xD788 +#define TAG_ELECTRIC_INDICATOR_TILE 0xD789 +#define TAG_PSYCHIC_INDICATOR_TILE 0xD78A +#define TAG_ICE_INDICATOR_TILE 0xD78B +#define TAG_DRAGON_INDICATOR_TILE 0xD78C +#define TAG_DARK_INDICATOR_TILE 0xD78D +#define TAG_FAIRY_INDICATOR_TILE 0xD78E +#define TAG_STELLAR_INDICATOR_TILE 0xD78F +#define TAG_TERA_TRIGGER_TILE 0xD790 -#define TAG_MEGA_TRIGGER_PAL 0xD777 +#define TAG_GIMMICK_TRIGGER_PAL 0xD777 #define TAG_MEGA_INDICATOR_PAL 0xD778 #define TAG_MISC_INDICATOR_PAL 0xD779 // Alpha, Omega, and Dynamax indicators use the same palette as each of them only uses 4 different colors. -#define TAG_ZMOVE_TRIGGER_PAL 0xD77B -#define TAG_BURST_TRIGGER_PAL 0xD77C -#define TAG_DYNAMAX_TRIGGER_PAL 0xD77D -#define TAG_TERA_INDICATOR_PAL 0xD77E -#define TAG_TERA_TRIGGER_PAL 0xD77F +#define TAG_TERA_INDICATOR_PAL 0xD77A enum { @@ -116,18 +109,6 @@ void InitBattlerHealthboxCoords(u8 battler); void GetBattlerHealthboxCoords(u8 battler, s16 *x, s16 *y); void UpdateHpTextInHealthbox(u32 healthboxSpriteId, u32 maxOrCurrent, s16 currHp, s16 maxHp); void SwapHpBarsWithHpText(void); -void ChangeMegaTriggerSprite(u8 spriteId, u8 animId); -void CreateMegaTriggerSprite(u8 battlerId, u8 palId); -bool32 IsMegaTriggerSpriteActive(void); -void HideMegaTriggerSprite(void); -void DestroyMegaTriggerSprite(void); -void ChangeBurstTriggerSprite(u8 spriteId, u8 animId); -void CreateBurstTriggerSprite(u8 battlerId, u8 palId); -bool32 IsBurstTriggerSpriteActive(void); -void HideBurstTriggerSprite(void); -void DestroyBurstTriggerSprite(void); -void MegaIndicator_LoadSpritesGfx(void); -void MegaIndicator_SetVisibilities(u32 healthboxId, bool32 invisible); u8 CreatePartyStatusSummarySprites(u8 battler, struct HpAndStatus *partyInfo, bool8 skipPlayer, bool8 isBattleStart); void Task_HidePartyStatusSummary(u8 taskId); void UpdateHealthboxAttribute(u8 healthboxSpriteId, struct Pokemon *mon, u8 elementId); @@ -136,7 +117,6 @@ u8 GetScaledHPFraction(s16 hp, s16 maxhp, u8 scale); u8 GetHPBarLevel(s16 hp, s16 maxhp); void CreateAbilityPopUp(u8 battlerId, u32 ability, bool32 isDoubleBattle); void DestroyAbilityPopUp(u8 battlerId); -void HideTriggerSprites(void); bool32 CanThrowLastUsedBall(void); void TryHideLastUsedBall(void); void TryRestoreLastUsedBall(void); diff --git a/include/battle_script_commands.h b/include/battle_script_commands.h index fe9c7f1e64..4f1593d4d8 100644 --- a/include/battle_script_commands.h +++ b/include/battle_script_commands.h @@ -47,7 +47,7 @@ bool32 IsShieldsDownProtected(u32 battler); u32 IsAbilityStatusProtected(u32 battler); bool32 TryResetBattlerStatChanges(u8 battler); bool32 CanCamouflage(u8 battlerId); -u16 GetNaturePowerMove(void); +u32 GetNaturePowerMove(u32 battler); void StealTargetItem(u8 battlerStealer, u8 battlerItem); u8 GetCatchingBattler(void); u32 GetHighestStatId(u32 battlerId); diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 071084fb5d..8643f62ad9 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -84,6 +84,7 @@ extern const u8 BattleScript_DmgHazardsOnBattlerScripting[]; extern const u8 BattleScript_DmgHazardsOnFaintedBattler[]; extern const u8 BattleScript_PerishSongTakesLife[]; extern const u8 BattleScript_PerishSongCountGoesDown[]; +extern const u8 BattleScript_AllStatsUpZMove[]; extern const u8 BattleScript_AllStatsUp[]; extern const u8 BattleScript_RapidSpinAway[]; extern const u8 BattleScript_WrapFree[]; @@ -268,6 +269,7 @@ extern const u8 BattleScript_WonderRoomEnds[]; extern const u8 BattleScript_MagicRoomEnds[]; extern const u8 BattleScript_TerrainEnds[]; extern const u8 BattleScript_TerrainEnds_Ret[]; +extern const u8 BattleScript_GrassyTerrainEnds[]; extern const u8 BattleScript_MudSportEnds[]; extern const u8 BattleScript_WaterSportEnds[]; extern const u8 BattleScript_SturdiedMsg[]; @@ -838,5 +840,6 @@ extern const u8 BattleScript_EffectShedTail[]; extern const u8 BattleScript_EffectUpperHand[]; extern const u8 BattleScript_EffectTidyUp[]; extern const u8 BattleScript_EffectSpicyExtract[]; +extern const u8 BattleScript_DamageToQuarterTargetHP[]; #endif // GUARD_BATTLE_SCRIPTS_H diff --git a/include/battle_terastal.h b/include/battle_terastal.h index c5428b6418..6b5c385463 100644 --- a/include/battle_terastal.h +++ b/include/battle_terastal.h @@ -1,31 +1,14 @@ #ifndef GUARD_BATTLE_TERASTAL_H #define GUARD_BATTLE_TERASTAL_H -void PrepareBattlerForTera(u32 battler); +void ActivateTera(u32 battler); void ApplyBattlerVisualsForTeraAnim(u32 battler); bool32 CanTerastallize(u32 battler); u32 GetBattlerTeraType(u32 battler); -bool32 IsTerastallized(u32 battler); void ExpendTypeStellarBoost(u32 battler, u32 type); bool32 IsTypeStellarBoosted(u32 battler, u32 type); uq4_12_t GetTeraMultiplier(u32 battler, u32 type); u16 GetTeraTypeRGB(u32 type); -void ChangeTeraTriggerSprite(u8 spriteId, u8 animId); -void CreateTeraTriggerSprite(u8 battler, u8 palId); -bool32 IsTeraTriggerSpriteActive(void); -void HideTeraTriggerSprite(void); -void DestroyTeraTriggerSprite(void); - -void TeraIndicator_LoadSpriteGfx(void); -bool32 TeraIndicator_ShouldBeInvisible(u32 battler); -u8 TeraIndicator_GetSpriteId(u32 healthboxSpriteId); -void TeraIndicator_SetVisibilities(u32 healthboxId, bool32 invisible); -void TeraIndicator_UpdateOamPriorities(u32 healthboxId, u32 oamPriority); -void TeraIndicator_UpdateLevel(u32 healthboxId, u32 level); -void TeraIndicator_CreateSprite(u32 battler, u32 healthboxSpriteId); -void TeraIndicator_DestroySprite(u32 healthboxSpriteId); -void TeraIndicator_UpdateType(u32 battler, u32 healthboxSpriteId); - #endif diff --git a/include/battle_util.h b/include/battle_util.h index 9ab5275d88..30201c167d 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -196,11 +196,13 @@ s32 GetStealthHazardDamage(u8 hazardType, u32 battler); s32 GetStealthHazardDamageByTypesAndHP(u8 hazardType, u8 type1, u8 type2, u32 maxHp); bool32 CanMegaEvolve(u32 battler); bool32 CanUltraBurst(u32 battler); +void ActivateMegaEvolution(u32 battler); +void ActivateUltraBurst(u32 battler); bool32 IsBattlerMegaEvolved(u32 battler); bool32 IsBattlerPrimalReverted(u32 battler); bool32 IsBattlerUltraBursted(u32 battler); u16 GetBattleFormChangeTargetSpecies(u32 battler, u16 method); -bool32 TryBattleFormChange(u32 battler, u16 method); +bool32 TryBattleFormChange(u32 battler, u32 method); bool32 DoBattlersShareType(u32 battler1, u32 battler2); bool32 CanBattlerGetOrLoseItem(u32 battler, u16 itemId); u32 GetIllusionMonSpecies(u32 battler); @@ -247,10 +249,10 @@ bool32 MoveHasChargeTurnAdditionalEffect(u32 move); bool32 CanTargetPartner(u32 battlerAtk, u32 battlerDef); bool32 TargetFullyImmuneToCurrMove(u32 BattlerAtk, u32 battlerDef); -bool32 CanSleep(u32 battler); -bool32 CanBePoisoned(u32 battlerAttacker, u32 battlerTarget); -bool32 CanBeBurned(u32 battler); -bool32 CanBeParalyzed(u32 battler); +bool32 CanBeSlept(u32 battler, u32 ability); +bool32 CanBePoisoned(u32 battlerAtk, u32 battlerDef, u32 defAbility); +bool32 CanBeBurned(u32 battler, u32 ability); +bool32 CanBeParalyzed(u32 battler, u32 ability); bool32 CanBeFrozen(u32 battler); bool32 CanGetFrostbite(u32 battler); bool32 CanBeConfused(u32 battler); diff --git a/include/battle_z_move.h b/include/battle_z_move.h index 92fb685b2f..bfa4c8495a 100644 --- a/include/battle_z_move.h +++ b/include/battle_z_move.h @@ -13,19 +13,17 @@ struct SignatureZMove u16 zmove; }; -bool8 IsZMove(u16 move); -void QueueZMove(u8 battler, u16 baseMove); -bool32 IsViableZMove(u8 battler, u16 move); -bool32 TryChangeZIndicator(u8 battler, u8 moveIndex); -void CreateZMoveTriggerSprite(u8, bool8); -void HideZMoveTriggerSprite(void); -bool32 IsZMoveTriggerSpriteActive(void); -void DestroyZMoveTriggerSprite(void); -u16 GetTypeBasedZMove(u16 move, u8 battler); +bool32 IsZMove(u32 move); +bool32 CanUseZMove(u32 battler); +u32 GetUsableZMove(u32 battler, u32 move); +void ActivateZMove(u32 battler); +bool32 IsViableZMove(u32 battler, u32 move); +bool32 TryChangeZTrigger(u32 battler, u32 moveIndex); +u32 GetTypeBasedZMove(u32 move); +u32 GetSignatureZMove(u32 move, u32 species, u32 item); bool32 MoveSelectionDisplayZMove(u16 zmove, u32 battler); void SetZEffect(void); -bool32 IsZMoveUsable(u8 battler, u16 moveIndex); -void GetUsableZMoves(u8 battler, u16 *moves); -u16 GetZMovePower(u16 move); +void AssignUsableZMoves(u32 battler, u16 *moves); +u32 GetZMovePower(u32 move); #endif // GUARD_BATTLE_Z_MOVE_H diff --git a/include/config/battle.h b/include/config/battle.h index 1af7e0c68a..f2ff9e6ffe 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -147,6 +147,9 @@ #define B_INTREPID_SWORD GEN_LATEST // In Gen9+, Intrepid Sword raises Attack by one stage only once per Battle. #define B_DAUNTLESS_SHIELD GEN_LATEST // In Gen9+, Dauntless Shield raises Defense by one stage only once per Battle. #define B_DISGUISE_HP_LOSS GEN_LATEST // In Gen8+, when a Disguised Mimikyu's Disguise is busted, upon changing to its Busted Form it loses HP equal to 1/8 of its maximum HP. +#define B_ABILITY_TRIGGER_CHANCE GEN_LATEST // In Gen3, Shed Skin, Cute Charm, Flame Body, Static and Poison Point have a 1/3 chance to trigger. In Gen 4+ it's 30%. + // In Gen3, Effect Spore has a 10% chance to sleep, poison or paralyze, with an equal chance. + // In Gen4, it's 30%. In Gen5+ it has 11% to sleep, 9% chance to poison and 10% chance to paralyze. // Item settings #define B_HP_BERRIES GEN_LATEST // In Gen4+, berries which restore HP activate immediately after HP drops to half. In Gen3, the effect occurs at the end of the turn. @@ -220,6 +223,7 @@ #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. #define B_QUICK_MOVE_CURSOR_TO_RUN FALSE // If set to TRUE, pushing B in the battle options against a wild encounter will move the cursor to the run option +#define B_MOVE_DESCRIPTION_BUTTON L_BUTTON // If set to a button other than B_LAST_USED_BALL_BUTTON, pressing this button will open the move description menu // Catching settings #define B_SEMI_INVULNERABLE_CATCH GEN_LATEST // In Gen4+, you cannot throw a ball against a Pokemon that is in a semi-invulnerable state (dig/fly/etc) diff --git a/include/config.h b/include/config/general.h similarity index 92% rename from include/config.h rename to include/config/general.h index 0f1b64bd13..a374ee97a1 100644 --- a/include/config.h +++ b/include/config/general.h @@ -1,5 +1,5 @@ -#ifndef GUARD_CONFIG_H -#define GUARD_CONFIG_H +#ifndef GUARD_CONFIG_GENERAL_H +#define GUARD_CONFIG_GENERAL_H // In the Generation 3 games, Asserts were used in various debug builds. // Ruby/Sapphire and Emerald do not have these asserts while Fire Red @@ -53,7 +53,7 @@ #define POKEMON_EXPANSION #define ITEM_EXPANSION -// Generation constants used in configs to define behavior +// Generation constants used in configs to define behavior. #define GEN_1 0 #define GEN_2 1 #define GEN_3 2 @@ -63,6 +63,7 @@ #define GEN_7 6 #define GEN_8 7 #define GEN_9 8 +// Changing GEN_LATEST's value to a different Generation will change every default setting that uses it at once. #define GEN_LATEST GEN_9 // General settings @@ -79,4 +80,4 @@ #define UNITS UNITS_IMPERIAL #define CHAR_DEC_SEPARATOR CHAR_PERIOD // CHAR_PERIOD is used as a decimal separator only in the UK and the US. The rest of the world uses CHAR_COMMA. -#endif // GUARD_CONFIG_H +#endif // GUARD_CONFIG_GENERAL_H diff --git a/include/config/overworld.h b/include/config/overworld.h index 77ad62f652..6f6aa5cc67 100644 --- a/include/config/overworld.h +++ b/include/config/overworld.h @@ -82,8 +82,11 @@ #define OW_POPUP_BW_COLOR_WHITE 1 // White pop-up from W2 // Configuration -#define OW_POPUP_BW_COLOR OW_POPUP_BW_COLOR_BLACK // B2W2 use different colors for their map pop-ups. +#define OW_POPUP_BW_COLOR OW_POPUP_BW_COLOR_BLACK // B2W2 use different colors for their map pop-ups. #define OW_POPUP_BW_TIME_MODE OW_POPUP_BW_TIME_NONE // Determines what type of time is shown. #define OW_POPUP_BW_ALPHA_BLEND FALSE // Enables alpha blending/transparency for the pop-ups. Mainly intended to be used with the black color option. +// Pokémon Center +#define OW_IGNORE_EGGS_ON_HEAL GEN_LATEST // In Gen 4+, the nurse in the Pokémon Center does not heal Eggs on healing machine. + #endif // GUARD_CONFIG_OVERWORLD_H diff --git a/include/config/pokemon.h b/include/config/pokemon.h index 2697c605c2..d2f12e4abd 100644 --- a/include/config/pokemon.h +++ b/include/config/pokemon.h @@ -39,15 +39,17 @@ #define P_ARCEUS_UNIQUE_FORM_ICONS GEN_LATEST // Since Gen 9, Arceus additionally changes its icon to reflect its current form. // Other settings -#define P_CUSTOM_GENDER_DIFF_ICONS TRUE // If TRUE, will give more Pokémon custom icons for their female forms, i.e. Hippopotas and Hippowdon -#define P_FOOTPRINTS TRUE // If TRUE, Pokémon will have footprints (as was the case up to Gen 5 and in BDSP). Disabling this saves some ROM space. -#define P_CRIES_ENABLED TRUE // If TRUE, Pokémon will have cries. Disabling this saves around a LOT of ROM space (over 25%!), but instead we recommend disabling individual unused Pokémon families in include/config/species_enabled.h. -#define P_LEGENDARY_PERFECT_IVS GEN_LATEST // Since Gen 6, Legendaries, Mythicals and Ultra Beasts found in the wild or given through gifts have at least 3 perfect IVs. -#define P_EV_CAP GEN_LATEST // Since Gen 6, the max EVs per stat is 252 instead of 255. -#define P_SHOW_TERA_TYPE GEN_8 // Since Gen 9, the Tera Type is shown on the summary screen. -#define P_TM_LITERACY GEN_LATEST // Since Gen 6, TM illiterate Pokémon can learn TMs that teach moves that are in their level-up learnsets. -#define P_EGG_CYCLE_LENGTH GEN_LATEST // Since Gen 8, egg cycles take half as many steps as before. -#define P_TWO_FRAME_FRONT_SPRITES TRUE // In Pokémon Emerald, Pokémon front sprites always consist of two frames. This config can revert it to only use the first frame, as is the case in the other Gen 3 games. +#define P_CUSTOM_GENDER_DIFF_ICONS TRUE // If TRUE, will give more Pokémon custom icons for their female forms, i.e. Hippopotas and Hippowdon +#define P_FOOTPRINTS TRUE // If TRUE, Pokémon will have footprints (as was the case up to Gen 5 and in BDSP). Disabling this saves some ROM space. +#define P_CRIES_ENABLED TRUE // If TRUE, Pokémon will have cries. Disabling this saves around a LOT of ROM space (over 25%!), but instead we recommend disabling individual unused Pokémon families in include/config/species_enabled.h. +#define P_LEGENDARY_PERFECT_IVS GEN_LATEST // Since Gen 6, Legendaries, Mythicals and Ultra Beasts found in the wild or given through gifts have at least 3 perfect IVs. +#define P_EV_CAP GEN_LATEST // Since Gen 6, the max EVs per stat is 252 instead of 255. +#define P_SHOW_TERA_TYPE GEN_8 // Since Gen 9, the Tera Type is shown on the summary screen. +#define P_TM_LITERACY GEN_LATEST // Since Gen 6, TM illiterate Pokémon can learn TMs that teach moves that are in their level-up learnsets. +#define P_EGG_CYCLE_LENGTH GEN_LATEST // Since Gen 8, egg cycles take half as many steps as before. +#define P_TWO_FRAME_FRONT_SPRITES TRUE // In Pokémon Emerald, Pokémon front sprites always consist of two frames. This config can revert it to only use the first frame, as is the case in the other Gen 3 games. +#define P_ONLY_OBTAINABLE_SHINIES FALSE // If TRUE, Pokémon encountered in the Battle Pyramid won't be shiny. +#define P_NO_SHINIES_WITHOUT_POKEBALLS FALSE // If TRUE, Pokémon encountered when the player is out of Poké Balls won't be shiny // Learnset helper toggles #define P_LEARNSET_HELPER_TEACHABLE TRUE // If TRUE, teachable_learnsets.h will be populated by tools/learnset_helpers/teachable.py using the included JSON files based on available TMs and tutors. diff --git a/include/config/test.h b/include/config/test.h index 4101f7a559..bd721beb9d 100644 --- a/include/config/test.h +++ b/include/config/test.h @@ -10,4 +10,1129 @@ #undef B_EXPANDED_TYPE_NAMES #define B_EXPANDED_TYPE_NAMES TRUE +#undef P_MEGA_EVOLUTIONS +#define P_MEGA_EVOLUTIONS TRUE +#undef P_PRIMAL_REVERSIONS +#define P_PRIMAL_REVERSIONS TRUE +#undef P_ULTRA_BURST_FORMS +#define P_ULTRA_BURST_FORMS TRUE +#undef P_GIGANTAMAX_FORMS +#define P_GIGANTAMAX_FORMS TRUE +#undef P_TERA_FORMS +#define P_TERA_FORMS TRUE +#undef P_FUSION_FORMS +#define P_FUSION_FORMS TRUE +#undef P_ALOLAN_FORMS +#define P_ALOLAN_FORMS TRUE +#undef P_GALARIAN_FORMS +#define P_GALARIAN_FORMS TRUE +#undef P_HISUIAN_FORMS +#define P_HISUIAN_FORMS TRUE +#undef P_PALDEAN_FORMS +#define P_PALDEAN_FORMS TRUE +#undef P_PIKACHU_EXTRA_FORMS +#define P_PIKACHU_EXTRA_FORMS TRUE +#undef P_COSPLAY_PIKACHU_FORMS +#define P_COSPLAY_PIKACHU_FORMS TRUE +#undef P_CAP_PIKACHU_FORMS +#define P_CAP_PIKACHU_FORMS TRUE +#undef P_GEN_2_CROSS_EVOS +#define P_GEN_2_CROSS_EVOS TRUE +#undef P_GEN_3_CROSS_EVOS +#define P_GEN_3_CROSS_EVOS TRUE +#undef P_GEN_4_CROSS_EVOS +#define P_GEN_4_CROSS_EVOS TRUE +#undef P_GEN_6_CROSS_EVOS +#define P_GEN_6_CROSS_EVOS TRUE +#undef P_GEN_8_CROSS_EVOS +#define P_GEN_8_CROSS_EVOS TRUE +#undef P_GEN_9_CROSS_EVOS +#define P_GEN_9_CROSS_EVOS TRUE + +#undef P_FAMILY_BULBASAUR +#define P_FAMILY_BULBASAUR TRUE +#undef P_FAMILY_CHARMANDER +#define P_FAMILY_CHARMANDER TRUE +#undef P_FAMILY_SQUIRTLE +#define P_FAMILY_SQUIRTLE TRUE +#undef P_FAMILY_CATERPIE +#define P_FAMILY_CATERPIE TRUE +#undef P_FAMILY_WEEDLE +#define P_FAMILY_WEEDLE TRUE +#undef P_FAMILY_PIDGEY +#define P_FAMILY_PIDGEY TRUE +#undef P_FAMILY_RATTATA +#define P_FAMILY_RATTATA TRUE +#undef P_FAMILY_SPEAROW +#define P_FAMILY_SPEAROW TRUE +#undef P_FAMILY_EKANS +#define P_FAMILY_EKANS TRUE +#undef P_FAMILY_PIKACHU +#define P_FAMILY_PIKACHU TRUE +#undef P_FAMILY_SANDSHREW +#define P_FAMILY_SANDSHREW TRUE +#undef P_FAMILY_NIDORAN +#define P_FAMILY_NIDORAN TRUE +#undef P_FAMILY_CLEFAIRY +#define P_FAMILY_CLEFAIRY TRUE +#undef P_FAMILY_VULPIX +#define P_FAMILY_VULPIX TRUE +#undef P_FAMILY_JIGGLYPUFF +#define P_FAMILY_JIGGLYPUFF TRUE +#undef P_FAMILY_ZUBAT +#define P_FAMILY_ZUBAT TRUE +#undef P_FAMILY_ODDISH +#define P_FAMILY_ODDISH TRUE +#undef P_FAMILY_PARAS +#define P_FAMILY_PARAS TRUE +#undef P_FAMILY_VENONAT +#define P_FAMILY_VENONAT TRUE +#undef P_FAMILY_DIGLETT +#define P_FAMILY_DIGLETT TRUE +#undef P_FAMILY_MEOWTH +#define P_FAMILY_MEOWTH TRUE +#undef P_FAMILY_PSYDUCK +#define P_FAMILY_PSYDUCK TRUE +#undef P_FAMILY_MANKEY +#define P_FAMILY_MANKEY TRUE +#undef P_FAMILY_GROWLITHE +#define P_FAMILY_GROWLITHE TRUE +#undef P_FAMILY_POLIWAG +#define P_FAMILY_POLIWAG TRUE +#undef P_FAMILY_ABRA +#define P_FAMILY_ABRA TRUE +#undef P_FAMILY_MACHOP +#define P_FAMILY_MACHOP TRUE +#undef P_FAMILY_BELLSPROUT +#define P_FAMILY_BELLSPROUT TRUE +#undef P_FAMILY_TENTACOOL +#define P_FAMILY_TENTACOOL TRUE +#undef P_FAMILY_GEODUDE +#define P_FAMILY_GEODUDE TRUE +#undef P_FAMILY_PONYTA +#define P_FAMILY_PONYTA TRUE +#undef P_FAMILY_SLOWPOKE +#define P_FAMILY_SLOWPOKE TRUE +#undef P_FAMILY_MAGNEMITE +#define P_FAMILY_MAGNEMITE TRUE +#undef P_FAMILY_FARFETCHD +#define P_FAMILY_FARFETCHD TRUE +#undef P_FAMILY_DODUO +#define P_FAMILY_DODUO TRUE +#undef P_FAMILY_SEEL +#define P_FAMILY_SEEL TRUE +#undef P_FAMILY_GRIMER +#define P_FAMILY_GRIMER TRUE +#undef P_FAMILY_SHELLDER +#define P_FAMILY_SHELLDER TRUE +#undef P_FAMILY_GASTLY +#define P_FAMILY_GASTLY TRUE +#undef P_FAMILY_ONIX +#define P_FAMILY_ONIX TRUE +#undef P_FAMILY_DROWZEE +#define P_FAMILY_DROWZEE TRUE +#undef P_FAMILY_KRABBY +#define P_FAMILY_KRABBY TRUE +#undef P_FAMILY_VOLTORB +#define P_FAMILY_VOLTORB TRUE +#undef P_FAMILY_EXEGGCUTE +#define P_FAMILY_EXEGGCUTE TRUE +#undef P_FAMILY_CUBONE +#define P_FAMILY_CUBONE TRUE +#undef P_FAMILY_HITMONS +#define P_FAMILY_HITMONS TRUE +#undef P_FAMILY_LICKITUNG +#define P_FAMILY_LICKITUNG TRUE +#undef P_FAMILY_KOFFING +#define P_FAMILY_KOFFING TRUE +#undef P_FAMILY_RHYHORN +#define P_FAMILY_RHYHORN TRUE +#undef P_FAMILY_CHANSEY +#define P_FAMILY_CHANSEY TRUE +#undef P_FAMILY_TANGELA +#define P_FAMILY_TANGELA TRUE +#undef P_FAMILY_KANGASKHAN +#define P_FAMILY_KANGASKHAN TRUE +#undef P_FAMILY_HORSEA +#define P_FAMILY_HORSEA TRUE +#undef P_FAMILY_GOLDEEN +#define P_FAMILY_GOLDEEN TRUE +#undef P_FAMILY_STARYU +#define P_FAMILY_STARYU TRUE +#undef P_FAMILY_MR_MIME +#define P_FAMILY_MR_MIME TRUE +#undef P_FAMILY_SCYTHER +#define P_FAMILY_SCYTHER TRUE +#undef P_FAMILY_JYNX +#define P_FAMILY_JYNX TRUE +#undef P_FAMILY_ELECTABUZZ +#define P_FAMILY_ELECTABUZZ TRUE +#undef P_FAMILY_MAGMAR +#define P_FAMILY_MAGMAR TRUE +#undef P_FAMILY_PINSIR +#define P_FAMILY_PINSIR TRUE +#undef P_FAMILY_TAUROS +#define P_FAMILY_TAUROS TRUE +#undef P_FAMILY_MAGIKARP +#define P_FAMILY_MAGIKARP TRUE +#undef P_FAMILY_LAPRAS +#define P_FAMILY_LAPRAS TRUE +#undef P_FAMILY_DITTO +#define P_FAMILY_DITTO TRUE +#undef P_FAMILY_EEVEE +#define P_FAMILY_EEVEE TRUE +#undef P_FAMILY_PORYGON +#define P_FAMILY_PORYGON TRUE +#undef P_FAMILY_OMANYTE +#define P_FAMILY_OMANYTE TRUE +#undef P_FAMILY_KABUTO +#define P_FAMILY_KABUTO TRUE +#undef P_FAMILY_AERODACTYL +#define P_FAMILY_AERODACTYL TRUE +#undef P_FAMILY_SNORLAX +#define P_FAMILY_SNORLAX TRUE +#undef P_FAMILY_ARTICUNO +#define P_FAMILY_ARTICUNO TRUE +#undef P_FAMILY_ZAPDOS +#define P_FAMILY_ZAPDOS TRUE +#undef P_FAMILY_MOLTRES +#define P_FAMILY_MOLTRES TRUE +#undef P_FAMILY_DRATINI +#define P_FAMILY_DRATINI TRUE +#undef P_FAMILY_MEWTWO +#define P_FAMILY_MEWTWO TRUE +#undef P_FAMILY_MEW +#define P_FAMILY_MEW TRUE + +#undef P_FAMILY_CHIKORITA +#define P_FAMILY_CHIKORITA TRUE +#undef P_FAMILY_CYNDAQUIL +#define P_FAMILY_CYNDAQUIL TRUE +#undef P_FAMILY_TOTODILE +#define P_FAMILY_TOTODILE TRUE +#undef P_FAMILY_SENTRET +#define P_FAMILY_SENTRET TRUE +#undef P_FAMILY_HOOTHOOT +#define P_FAMILY_HOOTHOOT TRUE +#undef P_FAMILY_LEDYBA +#define P_FAMILY_LEDYBA TRUE +#undef P_FAMILY_SPINARAK +#define P_FAMILY_SPINARAK TRUE +#undef P_FAMILY_CHINCHOU +#define P_FAMILY_CHINCHOU TRUE +#undef P_FAMILY_TOGEPI +#define P_FAMILY_TOGEPI TRUE +#undef P_FAMILY_NATU +#define P_FAMILY_NATU TRUE +#undef P_FAMILY_MAREEP +#define P_FAMILY_MAREEP TRUE +#undef P_FAMILY_MARILL +#define P_FAMILY_MARILL TRUE +#undef P_FAMILY_SUDOWOODO +#define P_FAMILY_SUDOWOODO TRUE +#undef P_FAMILY_HOPPIP +#define P_FAMILY_HOPPIP TRUE +#undef P_FAMILY_AIPOM +#define P_FAMILY_AIPOM TRUE +#undef P_FAMILY_SUNKERN +#define P_FAMILY_SUNKERN TRUE +#undef P_FAMILY_YANMA +#define P_FAMILY_YANMA TRUE +#undef P_FAMILY_WOOPER +#define P_FAMILY_WOOPER TRUE +#undef P_FAMILY_MURKROW +#define P_FAMILY_MURKROW TRUE +#undef P_FAMILY_MISDREAVUS +#define P_FAMILY_MISDREAVUS TRUE +#undef P_FAMILY_UNOWN +#define P_FAMILY_UNOWN TRUE +#undef P_FAMILY_WOBBUFFET +#define P_FAMILY_WOBBUFFET TRUE +#undef P_FAMILY_GIRAFARIG +#define P_FAMILY_GIRAFARIG TRUE +#undef P_FAMILY_PINECO +#define P_FAMILY_PINECO TRUE +#undef P_FAMILY_DUNSPARCE +#define P_FAMILY_DUNSPARCE TRUE +#undef P_FAMILY_GLIGAR +#define P_FAMILY_GLIGAR TRUE +#undef P_FAMILY_SNUBBULL +#define P_FAMILY_SNUBBULL TRUE +#undef P_FAMILY_QWILFISH +#define P_FAMILY_QWILFISH TRUE +#undef P_FAMILY_SHUCKLE +#define P_FAMILY_SHUCKLE TRUE +#undef P_FAMILY_HERACROSS +#define P_FAMILY_HERACROSS TRUE +#undef P_FAMILY_SNEASEL +#define P_FAMILY_SNEASEL TRUE +#undef P_FAMILY_TEDDIURSA +#define P_FAMILY_TEDDIURSA TRUE +#undef P_FAMILY_SLUGMA +#define P_FAMILY_SLUGMA TRUE +#undef P_FAMILY_SWINUB +#define P_FAMILY_SWINUB TRUE +#undef P_FAMILY_CORSOLA +#define P_FAMILY_CORSOLA TRUE +#undef P_FAMILY_REMORAID +#define P_FAMILY_REMORAID TRUE +#undef P_FAMILY_DELIBIRD +#define P_FAMILY_DELIBIRD TRUE +#undef P_FAMILY_MANTINE +#define P_FAMILY_MANTINE TRUE +#undef P_FAMILY_SKARMORY +#define P_FAMILY_SKARMORY TRUE +#undef P_FAMILY_HOUNDOUR +#define P_FAMILY_HOUNDOUR TRUE +#undef P_FAMILY_PHANPY +#define P_FAMILY_PHANPY TRUE +#undef P_FAMILY_STANTLER +#define P_FAMILY_STANTLER TRUE +#undef P_FAMILY_SMEARGLE +#define P_FAMILY_SMEARGLE TRUE +#undef P_FAMILY_MILTANK +#define P_FAMILY_MILTANK TRUE +#undef P_FAMILY_RAIKOU +#define P_FAMILY_RAIKOU TRUE +#undef P_FAMILY_ENTEI +#define P_FAMILY_ENTEI TRUE +#undef P_FAMILY_SUICUNE +#define P_FAMILY_SUICUNE TRUE +#undef P_FAMILY_LARVITAR +#define P_FAMILY_LARVITAR TRUE +#undef P_FAMILY_LUGIA +#define P_FAMILY_LUGIA TRUE +#undef P_FAMILY_HO_OH +#define P_FAMILY_HO_OH TRUE +#undef P_FAMILY_CELEBI +#define P_FAMILY_CELEBI TRUE + +#undef P_FAMILY_TREECKO +#define P_FAMILY_TREECKO TRUE +#undef P_FAMILY_TORCHIC +#define P_FAMILY_TORCHIC TRUE +#undef P_FAMILY_MUDKIP +#define P_FAMILY_MUDKIP TRUE +#undef P_FAMILY_POOCHYENA +#define P_FAMILY_POOCHYENA TRUE +#undef P_FAMILY_ZIGZAGOON +#define P_FAMILY_ZIGZAGOON TRUE +#undef P_FAMILY_WURMPLE +#define P_FAMILY_WURMPLE TRUE +#undef P_FAMILY_LOTAD +#define P_FAMILY_LOTAD TRUE +#undef P_FAMILY_SEEDOT +#define P_FAMILY_SEEDOT TRUE +#undef P_FAMILY_TAILLOW +#define P_FAMILY_TAILLOW TRUE +#undef P_FAMILY_WINGULL +#define P_FAMILY_WINGULL TRUE +#undef P_FAMILY_RALTS +#define P_FAMILY_RALTS TRUE +#undef P_FAMILY_SURSKIT +#define P_FAMILY_SURSKIT TRUE +#undef P_FAMILY_SHROOMISH +#define P_FAMILY_SHROOMISH TRUE +#undef P_FAMILY_SLAKOTH +#define P_FAMILY_SLAKOTH TRUE +#undef P_FAMILY_NINCADA +#define P_FAMILY_NINCADA TRUE +#undef P_FAMILY_WHISMUR +#define P_FAMILY_WHISMUR TRUE +#undef P_FAMILY_MAKUHITA +#define P_FAMILY_MAKUHITA TRUE +#undef P_FAMILY_NOSEPASS +#define P_FAMILY_NOSEPASS TRUE +#undef P_FAMILY_SKITTY +#define P_FAMILY_SKITTY TRUE +#undef P_FAMILY_SABLEYE +#define P_FAMILY_SABLEYE TRUE +#undef P_FAMILY_MAWILE +#define P_FAMILY_MAWILE TRUE +#undef P_FAMILY_ARON +#define P_FAMILY_ARON TRUE +#undef P_FAMILY_MEDITITE +#define P_FAMILY_MEDITITE TRUE +#undef P_FAMILY_ELECTRIKE +#define P_FAMILY_ELECTRIKE TRUE +#undef P_FAMILY_PLUSLE +#define P_FAMILY_PLUSLE TRUE +#undef P_FAMILY_MINUN +#define P_FAMILY_MINUN TRUE +#undef P_FAMILY_VOLBEAT_ILLUMISE +#define P_FAMILY_VOLBEAT_ILLUMISE TRUE +#undef P_FAMILY_ROSELIA +#define P_FAMILY_ROSELIA TRUE +#undef P_FAMILY_GULPIN +#define P_FAMILY_GULPIN TRUE +#undef P_FAMILY_CARVANHA +#define P_FAMILY_CARVANHA TRUE +#undef P_FAMILY_WAILMER +#define P_FAMILY_WAILMER TRUE +#undef P_FAMILY_NUMEL +#define P_FAMILY_NUMEL TRUE +#undef P_FAMILY_TORKOAL +#define P_FAMILY_TORKOAL TRUE +#undef P_FAMILY_SPOINK +#define P_FAMILY_SPOINK TRUE +#undef P_FAMILY_SPINDA +#define P_FAMILY_SPINDA TRUE +#undef P_FAMILY_TRAPINCH +#define P_FAMILY_TRAPINCH TRUE +#undef P_FAMILY_CACNEA +#define P_FAMILY_CACNEA TRUE +#undef P_FAMILY_SWABLU +#define P_FAMILY_SWABLU TRUE +#undef P_FAMILY_ZANGOOSE +#define P_FAMILY_ZANGOOSE TRUE +#undef P_FAMILY_SEVIPER +#define P_FAMILY_SEVIPER TRUE +#undef P_FAMILY_LUNATONE +#define P_FAMILY_LUNATONE TRUE +#undef P_FAMILY_SOLROCK +#define P_FAMILY_SOLROCK TRUE +#undef P_FAMILY_BARBOACH +#define P_FAMILY_BARBOACH TRUE +#undef P_FAMILY_CORPHISH +#define P_FAMILY_CORPHISH TRUE +#undef P_FAMILY_BALTOY +#define P_FAMILY_BALTOY TRUE +#undef P_FAMILY_LILEEP +#define P_FAMILY_LILEEP TRUE +#undef P_FAMILY_ANORITH +#define P_FAMILY_ANORITH TRUE +#undef P_FAMILY_FEEBAS +#define P_FAMILY_FEEBAS TRUE +#undef P_FAMILY_CASTFORM +#define P_FAMILY_CASTFORM TRUE +#undef P_FAMILY_KECLEON +#define P_FAMILY_KECLEON TRUE +#undef P_FAMILY_SHUPPET +#define P_FAMILY_SHUPPET TRUE +#undef P_FAMILY_DUSKULL +#define P_FAMILY_DUSKULL TRUE +#undef P_FAMILY_TROPIUS +#define P_FAMILY_TROPIUS TRUE +#undef P_FAMILY_CHIMECHO +#define P_FAMILY_CHIMECHO TRUE +#undef P_FAMILY_ABSOL +#define P_FAMILY_ABSOL TRUE +#undef P_FAMILY_SNORUNT +#define P_FAMILY_SNORUNT TRUE +#undef P_FAMILY_SPHEAL +#define P_FAMILY_SPHEAL TRUE +#undef P_FAMILY_CLAMPERL +#define P_FAMILY_CLAMPERL TRUE +#undef P_FAMILY_RELICANTH +#define P_FAMILY_RELICANTH TRUE +#undef P_FAMILY_LUVDISC +#define P_FAMILY_LUVDISC TRUE +#undef P_FAMILY_BAGON +#define P_FAMILY_BAGON TRUE +#undef P_FAMILY_BELDUM +#define P_FAMILY_BELDUM TRUE +#undef P_FAMILY_REGIROCK +#define P_FAMILY_REGIROCK TRUE +#undef P_FAMILY_REGICE +#define P_FAMILY_REGICE TRUE +#undef P_FAMILY_REGISTEEL +#define P_FAMILY_REGISTEEL TRUE +#undef P_FAMILY_LATIAS +#define P_FAMILY_LATIAS TRUE +#undef P_FAMILY_LATIOS +#define P_FAMILY_LATIOS TRUE +#undef P_FAMILY_KYOGRE +#define P_FAMILY_KYOGRE TRUE +#undef P_FAMILY_GROUDON +#define P_FAMILY_GROUDON TRUE +#undef P_FAMILY_RAYQUAZA +#define P_FAMILY_RAYQUAZA TRUE +#undef P_FAMILY_JIRACHI +#define P_FAMILY_JIRACHI TRUE +#undef P_FAMILY_DEOXYS +#define P_FAMILY_DEOXYS TRUE + +#undef P_FAMILY_TURTWIG +#define P_FAMILY_TURTWIG TRUE +#undef P_FAMILY_CHIMCHAR +#define P_FAMILY_CHIMCHAR TRUE +#undef P_FAMILY_PIPLUP +#define P_FAMILY_PIPLUP TRUE +#undef P_FAMILY_STARLY +#define P_FAMILY_STARLY TRUE +#undef P_FAMILY_BIDOOF +#define P_FAMILY_BIDOOF TRUE +#undef P_FAMILY_KRICKETOT +#define P_FAMILY_KRICKETOT TRUE +#undef P_FAMILY_SHINX +#define P_FAMILY_SHINX TRUE +#undef P_FAMILY_CRANIDOS +#define P_FAMILY_CRANIDOS TRUE +#undef P_FAMILY_SHIELDON +#define P_FAMILY_SHIELDON TRUE +#undef P_FAMILY_BURMY +#define P_FAMILY_BURMY TRUE +#undef P_FAMILY_COMBEE +#define P_FAMILY_COMBEE TRUE +#undef P_FAMILY_PACHIRISU +#define P_FAMILY_PACHIRISU TRUE +#undef P_FAMILY_BUIZEL +#define P_FAMILY_BUIZEL TRUE +#undef P_FAMILY_CHERUBI +#define P_FAMILY_CHERUBI TRUE +#undef P_FAMILY_SHELLOS +#define P_FAMILY_SHELLOS TRUE +#undef P_FAMILY_DRIFLOON +#define P_FAMILY_DRIFLOON TRUE +#undef P_FAMILY_BUNEARY +#define P_FAMILY_BUNEARY TRUE +#undef P_FAMILY_GLAMEOW +#define P_FAMILY_GLAMEOW TRUE +#undef P_FAMILY_STUNKY +#define P_FAMILY_STUNKY TRUE +#undef P_FAMILY_BRONZOR +#define P_FAMILY_BRONZOR TRUE +#undef P_FAMILY_CHATOT +#define P_FAMILY_CHATOT TRUE +#undef P_FAMILY_SPIRITOMB +#define P_FAMILY_SPIRITOMB TRUE +#undef P_FAMILY_GIBLE +#define P_FAMILY_GIBLE TRUE +#undef P_FAMILY_RIOLU +#define P_FAMILY_RIOLU TRUE +#undef P_FAMILY_HIPPOPOTAS +#define P_FAMILY_HIPPOPOTAS TRUE +#undef P_FAMILY_SKORUPI +#define P_FAMILY_SKORUPI TRUE +#undef P_FAMILY_CROAGUNK +#define P_FAMILY_CROAGUNK TRUE +#undef P_FAMILY_CARNIVINE +#define P_FAMILY_CARNIVINE TRUE +#undef P_FAMILY_FINNEON +#define P_FAMILY_FINNEON TRUE +#undef P_FAMILY_SNOVER +#define P_FAMILY_SNOVER TRUE +#undef P_FAMILY_ROTOM +#define P_FAMILY_ROTOM TRUE +#undef P_FAMILY_UXIE +#define P_FAMILY_UXIE TRUE +#undef P_FAMILY_MESPRIT +#define P_FAMILY_MESPRIT TRUE +#undef P_FAMILY_AZELF +#define P_FAMILY_AZELF TRUE +#undef P_FAMILY_DIALGA +#define P_FAMILY_DIALGA TRUE +#undef P_FAMILY_PALKIA +#define P_FAMILY_PALKIA TRUE +#undef P_FAMILY_HEATRAN +#define P_FAMILY_HEATRAN TRUE +#undef P_FAMILY_REGIGIGAS +#define P_FAMILY_REGIGIGAS TRUE +#undef P_FAMILY_GIRATINA +#define P_FAMILY_GIRATINA TRUE +#undef P_FAMILY_CRESSELIA +#define P_FAMILY_CRESSELIA TRUE +#undef P_FAMILY_MANAPHY +#define P_FAMILY_MANAPHY TRUE +#undef P_FAMILY_DARKRAI +#define P_FAMILY_DARKRAI TRUE +#undef P_FAMILY_SHAYMIN +#define P_FAMILY_SHAYMIN TRUE +#undef P_FAMILY_ARCEUS +#define P_FAMILY_ARCEUS TRUE +#undef P_FAMILY_VICTINI +#define P_FAMILY_VICTINI TRUE +#undef P_FAMILY_SNIVY +#define P_FAMILY_SNIVY TRUE +#undef P_FAMILY_TEPIG +#define P_FAMILY_TEPIG TRUE +#undef P_FAMILY_OSHAWOTT +#define P_FAMILY_OSHAWOTT TRUE +#undef P_FAMILY_PATRAT +#define P_FAMILY_PATRAT TRUE +#undef P_FAMILY_LILLIPUP +#define P_FAMILY_LILLIPUP TRUE +#undef P_FAMILY_PURRLOIN +#define P_FAMILY_PURRLOIN TRUE +#undef P_FAMILY_PANSAGE +#define P_FAMILY_PANSAGE TRUE +#undef P_FAMILY_PANSEAR +#define P_FAMILY_PANSEAR TRUE +#undef P_FAMILY_PANPOUR +#define P_FAMILY_PANPOUR TRUE +#undef P_FAMILY_MUNNA +#define P_FAMILY_MUNNA TRUE +#undef P_FAMILY_PIDOVE +#define P_FAMILY_PIDOVE TRUE +#undef P_FAMILY_BLITZLE +#define P_FAMILY_BLITZLE TRUE +#undef P_FAMILY_ROGGENROLA +#define P_FAMILY_ROGGENROLA TRUE +#undef P_FAMILY_WOOBAT +#define P_FAMILY_WOOBAT TRUE +#undef P_FAMILY_DRILBUR +#define P_FAMILY_DRILBUR TRUE +#undef P_FAMILY_AUDINO +#define P_FAMILY_AUDINO TRUE +#undef P_FAMILY_TIMBURR +#define P_FAMILY_TIMBURR TRUE +#undef P_FAMILY_TYMPOLE +#define P_FAMILY_TYMPOLE TRUE +#undef P_FAMILY_THROH +#define P_FAMILY_THROH TRUE +#undef P_FAMILY_SAWK +#define P_FAMILY_SAWK TRUE +#undef P_FAMILY_SEWADDLE +#define P_FAMILY_SEWADDLE TRUE +#undef P_FAMILY_VENIPEDE +#define P_FAMILY_VENIPEDE TRUE +#undef P_FAMILY_COTTONEE +#define P_FAMILY_COTTONEE TRUE +#undef P_FAMILY_PETILIL +#define P_FAMILY_PETILIL TRUE +#undef P_FAMILY_BASCULIN +#define P_FAMILY_BASCULIN TRUE +#undef P_FAMILY_SANDILE +#define P_FAMILY_SANDILE TRUE +#undef P_FAMILY_DARUMAKA +#define P_FAMILY_DARUMAKA TRUE +#undef P_FAMILY_MARACTUS +#define P_FAMILY_MARACTUS TRUE +#undef P_FAMILY_DWEBBLE +#define P_FAMILY_DWEBBLE TRUE +#undef P_FAMILY_SCRAGGY +#define P_FAMILY_SCRAGGY TRUE +#undef P_FAMILY_SIGILYPH +#define P_FAMILY_SIGILYPH TRUE +#undef P_FAMILY_YAMASK +#define P_FAMILY_YAMASK TRUE +#undef P_FAMILY_TIRTOUGA +#define P_FAMILY_TIRTOUGA TRUE +#undef P_FAMILY_ARCHEN +#define P_FAMILY_ARCHEN TRUE +#undef P_FAMILY_TRUBBISH +#define P_FAMILY_TRUBBISH TRUE +#undef P_FAMILY_ZORUA +#define P_FAMILY_ZORUA TRUE +#undef P_FAMILY_MINCCINO +#define P_FAMILY_MINCCINO TRUE +#undef P_FAMILY_GOTHITA +#define P_FAMILY_GOTHITA TRUE +#undef P_FAMILY_SOLOSIS +#define P_FAMILY_SOLOSIS TRUE +#undef P_FAMILY_DUCKLETT +#define P_FAMILY_DUCKLETT TRUE +#undef P_FAMILY_VANILLITE +#define P_FAMILY_VANILLITE TRUE +#undef P_FAMILY_DEERLING +#define P_FAMILY_DEERLING TRUE +#undef P_FAMILY_EMOLGA +#define P_FAMILY_EMOLGA TRUE +#undef P_FAMILY_KARRABLAST +#define P_FAMILY_KARRABLAST TRUE +#undef P_FAMILY_FOONGUS +#define P_FAMILY_FOONGUS TRUE +#undef P_FAMILY_FRILLISH +#define P_FAMILY_FRILLISH TRUE +#undef P_FAMILY_ALOMOMOLA +#define P_FAMILY_ALOMOMOLA TRUE +#undef P_FAMILY_JOLTIK +#define P_FAMILY_JOLTIK TRUE +#undef P_FAMILY_FERROSEED +#define P_FAMILY_FERROSEED TRUE +#undef P_FAMILY_KLINK +#define P_FAMILY_KLINK TRUE +#undef P_FAMILY_TYNAMO +#define P_FAMILY_TYNAMO TRUE +#undef P_FAMILY_ELGYEM +#define P_FAMILY_ELGYEM TRUE +#undef P_FAMILY_LITWICK +#define P_FAMILY_LITWICK TRUE +#undef P_FAMILY_AXEW +#define P_FAMILY_AXEW TRUE +#undef P_FAMILY_CUBCHOO +#define P_FAMILY_CUBCHOO TRUE +#undef P_FAMILY_CRYOGONAL +#define P_FAMILY_CRYOGONAL TRUE +#undef P_FAMILY_SHELMET +#define P_FAMILY_SHELMET TRUE +#undef P_FAMILY_STUNFISK +#define P_FAMILY_STUNFISK TRUE +#undef P_FAMILY_MIENFOO +#define P_FAMILY_MIENFOO TRUE +#undef P_FAMILY_DRUDDIGON +#define P_FAMILY_DRUDDIGON TRUE +#undef P_FAMILY_GOLETT +#define P_FAMILY_GOLETT TRUE +#undef P_FAMILY_PAWNIARD +#define P_FAMILY_PAWNIARD TRUE +#undef P_FAMILY_BOUFFALANT +#define P_FAMILY_BOUFFALANT TRUE +#undef P_FAMILY_RUFFLET +#define P_FAMILY_RUFFLET TRUE +#undef P_FAMILY_VULLABY +#define P_FAMILY_VULLABY TRUE +#undef P_FAMILY_HEATMOR +#define P_FAMILY_HEATMOR TRUE +#undef P_FAMILY_DURANT +#define P_FAMILY_DURANT TRUE +#undef P_FAMILY_DEINO +#define P_FAMILY_DEINO TRUE +#undef P_FAMILY_LARVESTA +#define P_FAMILY_LARVESTA TRUE +#undef P_FAMILY_COBALION +#define P_FAMILY_COBALION TRUE +#undef P_FAMILY_TERRAKION +#define P_FAMILY_TERRAKION TRUE +#undef P_FAMILY_VIRIZION +#define P_FAMILY_VIRIZION TRUE +#undef P_FAMILY_TORNADUS +#define P_FAMILY_TORNADUS TRUE +#undef P_FAMILY_THUNDURUS +#define P_FAMILY_THUNDURUS TRUE +#undef P_FAMILY_RESHIRAM +#define P_FAMILY_RESHIRAM TRUE +#undef P_FAMILY_ZEKROM +#define P_FAMILY_ZEKROM TRUE +#undef P_FAMILY_LANDORUS +#define P_FAMILY_LANDORUS TRUE +#undef P_FAMILY_KYUREM +#define P_FAMILY_KYUREM TRUE +#undef P_FAMILY_KELDEO +#define P_FAMILY_KELDEO TRUE +#undef P_FAMILY_MELOETTA +#define P_FAMILY_MELOETTA TRUE +#undef P_FAMILY_GENESECT +#define P_FAMILY_GENESECT TRUE + +#undef P_FAMILY_CHESPIN +#define P_FAMILY_CHESPIN TRUE +#undef P_FAMILY_FENNEKIN +#define P_FAMILY_FENNEKIN TRUE +#undef P_FAMILY_FROAKIE +#define P_FAMILY_FROAKIE TRUE +#undef P_FAMILY_BUNNELBY +#define P_FAMILY_BUNNELBY TRUE +#undef P_FAMILY_FLETCHLING +#define P_FAMILY_FLETCHLING TRUE +#undef P_FAMILY_SCATTERBUG +#define P_FAMILY_SCATTERBUG TRUE +#undef P_FAMILY_LITLEO +#define P_FAMILY_LITLEO TRUE +#undef P_FAMILY_FLABEBE +#define P_FAMILY_FLABEBE TRUE +#undef P_FAMILY_SKIDDO +#define P_FAMILY_SKIDDO TRUE +#undef P_FAMILY_PANCHAM +#define P_FAMILY_PANCHAM TRUE +#undef P_FAMILY_FURFROU +#define P_FAMILY_FURFROU TRUE +#undef P_FAMILY_ESPURR +#define P_FAMILY_ESPURR TRUE +#undef P_FAMILY_HONEDGE +#define P_FAMILY_HONEDGE TRUE +#undef P_FAMILY_SPRITZEE +#define P_FAMILY_SPRITZEE TRUE +#undef P_FAMILY_SWIRLIX +#define P_FAMILY_SWIRLIX TRUE +#undef P_FAMILY_INKAY +#define P_FAMILY_INKAY TRUE +#undef P_FAMILY_BINACLE +#define P_FAMILY_BINACLE TRUE +#undef P_FAMILY_SKRELP +#define P_FAMILY_SKRELP TRUE +#undef P_FAMILY_CLAUNCHER +#define P_FAMILY_CLAUNCHER TRUE +#undef P_FAMILY_HELIOPTILE +#define P_FAMILY_HELIOPTILE TRUE +#undef P_FAMILY_TYRUNT +#define P_FAMILY_TYRUNT TRUE +#undef P_FAMILY_AMAURA +#define P_FAMILY_AMAURA TRUE +#undef P_FAMILY_HAWLUCHA +#define P_FAMILY_HAWLUCHA TRUE +#undef P_FAMILY_DEDENNE +#define P_FAMILY_DEDENNE TRUE +#undef P_FAMILY_CARBINK +#define P_FAMILY_CARBINK TRUE +#undef P_FAMILY_GOOMY +#define P_FAMILY_GOOMY TRUE +#undef P_FAMILY_KLEFKI +#define P_FAMILY_KLEFKI TRUE +#undef P_FAMILY_PHANTUMP +#define P_FAMILY_PHANTUMP TRUE +#undef P_FAMILY_PUMPKABOO +#define P_FAMILY_PUMPKABOO TRUE +#undef P_FAMILY_BERGMITE +#define P_FAMILY_BERGMITE TRUE +#undef P_FAMILY_NOIBAT +#define P_FAMILY_NOIBAT TRUE +#undef P_FAMILY_XERNEAS +#define P_FAMILY_XERNEAS TRUE +#undef P_FAMILY_YVELTAL +#define P_FAMILY_YVELTAL TRUE +#undef P_FAMILY_ZYGARDE +#define P_FAMILY_ZYGARDE TRUE +#undef P_FAMILY_DIANCIE +#define P_FAMILY_DIANCIE TRUE +#undef P_FAMILY_HOOPA +#define P_FAMILY_HOOPA TRUE +#undef P_FAMILY_VOLCANION +#define P_FAMILY_VOLCANION TRUE + +#undef P_FAMILY_ROWLET +#define P_FAMILY_ROWLET TRUE +#undef P_FAMILY_LITTEN +#define P_FAMILY_LITTEN TRUE +#undef P_FAMILY_POPPLIO +#define P_FAMILY_POPPLIO TRUE +#undef P_FAMILY_PIKIPEK +#define P_FAMILY_PIKIPEK TRUE +#undef P_FAMILY_YUNGOOS +#define P_FAMILY_YUNGOOS TRUE +#undef P_FAMILY_GRUBBIN +#define P_FAMILY_GRUBBIN TRUE +#undef P_FAMILY_CRABRAWLER +#define P_FAMILY_CRABRAWLER TRUE +#undef P_FAMILY_ORICORIO +#define P_FAMILY_ORICORIO TRUE +#undef P_FAMILY_CUTIEFLY +#define P_FAMILY_CUTIEFLY TRUE +#undef P_FAMILY_ROCKRUFF +#define P_FAMILY_ROCKRUFF TRUE +#undef P_FAMILY_WISHIWASHI +#define P_FAMILY_WISHIWASHI TRUE +#undef P_FAMILY_MAREANIE +#define P_FAMILY_MAREANIE TRUE +#undef P_FAMILY_MUDBRAY +#define P_FAMILY_MUDBRAY TRUE +#undef P_FAMILY_DEWPIDER +#define P_FAMILY_DEWPIDER TRUE +#undef P_FAMILY_FOMANTIS +#define P_FAMILY_FOMANTIS TRUE +#undef P_FAMILY_MORELULL +#define P_FAMILY_MORELULL TRUE +#undef P_FAMILY_SALANDIT +#define P_FAMILY_SALANDIT TRUE +#undef P_FAMILY_STUFFUL +#define P_FAMILY_STUFFUL TRUE +#undef P_FAMILY_BOUNSWEET +#define P_FAMILY_BOUNSWEET TRUE +#undef P_FAMILY_COMFEY +#define P_FAMILY_COMFEY TRUE +#undef P_FAMILY_ORANGURU +#define P_FAMILY_ORANGURU TRUE +#undef P_FAMILY_PASSIMIAN +#define P_FAMILY_PASSIMIAN TRUE +#undef P_FAMILY_WIMPOD +#define P_FAMILY_WIMPOD TRUE +#undef P_FAMILY_SANDYGAST +#define P_FAMILY_SANDYGAST TRUE +#undef P_FAMILY_PYUKUMUKU +#define P_FAMILY_PYUKUMUKU TRUE +#undef P_FAMILY_TYPE_NULL +#define P_FAMILY_TYPE_NULL TRUE +#undef P_FAMILY_MINIOR +#define P_FAMILY_MINIOR TRUE +#undef P_FAMILY_KOMALA +#define P_FAMILY_KOMALA TRUE +#undef P_FAMILY_TURTONATOR +#define P_FAMILY_TURTONATOR TRUE +#undef P_FAMILY_TOGEDEMARU +#define P_FAMILY_TOGEDEMARU TRUE +#undef P_FAMILY_MIMIKYU +#define P_FAMILY_MIMIKYU TRUE +#undef P_FAMILY_BRUXISH +#define P_FAMILY_BRUXISH TRUE +#undef P_FAMILY_DRAMPA +#define P_FAMILY_DRAMPA TRUE +#undef P_FAMILY_DHELMISE +#define P_FAMILY_DHELMISE TRUE +#undef P_FAMILY_JANGMO_O +#define P_FAMILY_JANGMO_O TRUE +#undef P_FAMILY_TAPU_KOKO +#define P_FAMILY_TAPU_KOKO TRUE +#undef P_FAMILY_TAPU_LELE +#define P_FAMILY_TAPU_LELE TRUE +#undef P_FAMILY_TAPU_BULU +#define P_FAMILY_TAPU_BULU TRUE +#undef P_FAMILY_TAPU_FINI +#define P_FAMILY_TAPU_FINI TRUE +#undef P_FAMILY_COSMOG +#define P_FAMILY_COSMOG TRUE +#undef P_FAMILY_NIHILEGO +#define P_FAMILY_NIHILEGO TRUE +#undef P_FAMILY_BUZZWOLE +#define P_FAMILY_BUZZWOLE TRUE +#undef P_FAMILY_PHEROMOSA +#define P_FAMILY_PHEROMOSA TRUE +#undef P_FAMILY_XURKITREE +#define P_FAMILY_XURKITREE TRUE +#undef P_FAMILY_CELESTEELA +#define P_FAMILY_CELESTEELA TRUE +#undef P_FAMILY_KARTANA +#define P_FAMILY_KARTANA TRUE +#undef P_FAMILY_GUZZLORD +#define P_FAMILY_GUZZLORD TRUE +#undef P_FAMILY_NECROZMA +#define P_FAMILY_NECROZMA TRUE +#undef P_FAMILY_MAGEARNA +#define P_FAMILY_MAGEARNA TRUE +#undef P_FAMILY_MARSHADOW +#define P_FAMILY_MARSHADOW TRUE +#undef P_FAMILY_POIPOLE +#define P_FAMILY_POIPOLE TRUE +#undef P_FAMILY_STAKATAKA +#define P_FAMILY_STAKATAKA TRUE +#undef P_FAMILY_BLACEPHALON +#define P_FAMILY_BLACEPHALON TRUE +#undef P_FAMILY_ZERAORA +#define P_FAMILY_ZERAORA TRUE +#undef P_FAMILY_MELTAN +#define P_FAMILY_MELTAN TRUE + +#undef P_FAMILY_GROOKEY +#define P_FAMILY_GROOKEY TRUE +#undef P_FAMILY_SCORBUNNY +#define P_FAMILY_SCORBUNNY TRUE +#undef P_FAMILY_SOBBLE +#define P_FAMILY_SOBBLE TRUE +#undef P_FAMILY_SKWOVET +#define P_FAMILY_SKWOVET TRUE +#undef P_FAMILY_ROOKIDEE +#define P_FAMILY_ROOKIDEE TRUE +#undef P_FAMILY_BLIPBUG +#define P_FAMILY_BLIPBUG TRUE +#undef P_FAMILY_NICKIT +#define P_FAMILY_NICKIT TRUE +#undef P_FAMILY_GOSSIFLEUR +#define P_FAMILY_GOSSIFLEUR TRUE +#undef P_FAMILY_WOOLOO +#define P_FAMILY_WOOLOO TRUE +#undef P_FAMILY_CHEWTLE +#define P_FAMILY_CHEWTLE TRUE +#undef P_FAMILY_YAMPER +#define P_FAMILY_YAMPER TRUE +#undef P_FAMILY_ROLYCOLY +#define P_FAMILY_ROLYCOLY TRUE +#undef P_FAMILY_APPLIN +#define P_FAMILY_APPLIN TRUE +#undef P_FAMILY_SILICOBRA +#define P_FAMILY_SILICOBRA TRUE +#undef P_FAMILY_CRAMORANT +#define P_FAMILY_CRAMORANT TRUE +#undef P_FAMILY_ARROKUDA +#define P_FAMILY_ARROKUDA TRUE +#undef P_FAMILY_TOXEL +#define P_FAMILY_TOXEL TRUE +#undef P_FAMILY_SIZZLIPEDE +#define P_FAMILY_SIZZLIPEDE TRUE +#undef P_FAMILY_CLOBBOPUS +#define P_FAMILY_CLOBBOPUS TRUE +#undef P_FAMILY_SINISTEA +#define P_FAMILY_SINISTEA TRUE +#undef P_FAMILY_HATENNA +#define P_FAMILY_HATENNA TRUE +#undef P_FAMILY_IMPIDIMP +#define P_FAMILY_IMPIDIMP TRUE +#undef P_FAMILY_MILCERY +#define P_FAMILY_MILCERY TRUE +#undef P_FAMILY_FALINKS +#define P_FAMILY_FALINKS TRUE +#undef P_FAMILY_PINCURCHIN +#define P_FAMILY_PINCURCHIN TRUE +#undef P_FAMILY_SNOM +#define P_FAMILY_SNOM TRUE +#undef P_FAMILY_STONJOURNER +#define P_FAMILY_STONJOURNER TRUE +#undef P_FAMILY_EISCUE +#define P_FAMILY_EISCUE TRUE +#undef P_FAMILY_INDEEDEE +#define P_FAMILY_INDEEDEE TRUE +#undef P_FAMILY_MORPEKO +#define P_FAMILY_MORPEKO TRUE +#undef P_FAMILY_CUFANT +#define P_FAMILY_CUFANT TRUE +#undef P_FAMILY_DRACOZOLT +#define P_FAMILY_DRACOZOLT TRUE +#undef P_FAMILY_ARCTOZOLT +#define P_FAMILY_ARCTOZOLT TRUE +#undef P_FAMILY_DRACOVISH +#define P_FAMILY_DRACOVISH TRUE +#undef P_FAMILY_ARCTOVISH +#define P_FAMILY_ARCTOVISH TRUE +#undef P_FAMILY_DURALUDON +#define P_FAMILY_DURALUDON TRUE +#undef P_FAMILY_DREEPY +#define P_FAMILY_DREEPY TRUE +#undef P_FAMILY_ZACIAN +#define P_FAMILY_ZACIAN TRUE +#undef P_FAMILY_ZAMAZENTA +#define P_FAMILY_ZAMAZENTA TRUE +#undef P_FAMILY_ETERNATUS +#define P_FAMILY_ETERNATUS TRUE +#undef P_FAMILY_KUBFU +#define P_FAMILY_KUBFU TRUE +#undef P_FAMILY_ZARUDE +#define P_FAMILY_ZARUDE TRUE +#undef P_FAMILY_REGIELEKI +#define P_FAMILY_REGIELEKI TRUE +#undef P_FAMILY_REGIDRAGO +#define P_FAMILY_REGIDRAGO TRUE +#undef P_FAMILY_GLASTRIER +#define P_FAMILY_GLASTRIER TRUE +#undef P_FAMILY_SPECTRIER +#define P_FAMILY_SPECTRIER TRUE +#undef P_FAMILY_CALYREX +#define P_FAMILY_CALYREX TRUE +#undef P_FAMILY_ENAMORUS +#define P_FAMILY_ENAMORUS TRUE + +#undef P_FAMILY_SPRIGATITO +#define P_FAMILY_SPRIGATITO TRUE +#undef P_FAMILY_FUECOCO +#define P_FAMILY_FUECOCO TRUE +#undef P_FAMILY_QUAXLY +#define P_FAMILY_QUAXLY TRUE +#undef P_FAMILY_LECHONK +#define P_FAMILY_LECHONK TRUE +#undef P_FAMILY_TAROUNTULA +#define P_FAMILY_TAROUNTULA TRUE +#undef P_FAMILY_NYMBLE +#define P_FAMILY_NYMBLE TRUE +#undef P_FAMILY_PAWMI +#define P_FAMILY_PAWMI TRUE +#undef P_FAMILY_TANDEMAUS +#define P_FAMILY_TANDEMAUS TRUE +#undef P_FAMILY_FIDOUGH +#define P_FAMILY_FIDOUGH TRUE +#undef P_FAMILY_SMOLIV +#define P_FAMILY_SMOLIV TRUE +#undef P_FAMILY_SQUAWKABILLY +#define P_FAMILY_SQUAWKABILLY TRUE +#undef P_FAMILY_NACLI +#define P_FAMILY_NACLI TRUE +#undef P_FAMILY_CHARCADET +#define P_FAMILY_CHARCADET TRUE +#undef P_FAMILY_TADBULB +#define P_FAMILY_TADBULB TRUE +#undef P_FAMILY_WATTREL +#define P_FAMILY_WATTREL TRUE +#undef P_FAMILY_MASCHIFF +#define P_FAMILY_MASCHIFF TRUE +#undef P_FAMILY_SHROODLE +#define P_FAMILY_SHROODLE TRUE +#undef P_FAMILY_BRAMBLIN +#define P_FAMILY_BRAMBLIN TRUE +#undef P_FAMILY_TOEDSCOOL +#define P_FAMILY_TOEDSCOOL TRUE +#undef P_FAMILY_KLAWF +#define P_FAMILY_KLAWF TRUE +#undef P_FAMILY_CAPSAKID +#define P_FAMILY_CAPSAKID TRUE +#undef P_FAMILY_RELLOR +#define P_FAMILY_RELLOR TRUE +#undef P_FAMILY_FLITTLE +#define P_FAMILY_FLITTLE TRUE +#undef P_FAMILY_TINKATINK +#define P_FAMILY_TINKATINK TRUE +#undef P_FAMILY_WIGLETT +#define P_FAMILY_WIGLETT TRUE +#undef P_FAMILY_BOMBIRDIER +#define P_FAMILY_BOMBIRDIER TRUE +#undef P_FAMILY_FINIZEN +#define P_FAMILY_FINIZEN TRUE +#undef P_FAMILY_VAROOM +#define P_FAMILY_VAROOM TRUE +#undef P_FAMILY_CYCLIZAR +#define P_FAMILY_CYCLIZAR TRUE +#undef P_FAMILY_ORTHWORM +#define P_FAMILY_ORTHWORM TRUE +#undef P_FAMILY_GLIMMET +#define P_FAMILY_GLIMMET TRUE +#undef P_FAMILY_GREAVARD +#define P_FAMILY_GREAVARD TRUE +#undef P_FAMILY_FLAMIGO +#define P_FAMILY_FLAMIGO TRUE +#undef P_FAMILY_CETODDLE +#define P_FAMILY_CETODDLE TRUE +#undef P_FAMILY_VELUZA +#define P_FAMILY_VELUZA TRUE +#undef P_FAMILY_DONDOZO +#define P_FAMILY_DONDOZO TRUE +#undef P_FAMILY_TATSUGIRI +#define P_FAMILY_TATSUGIRI TRUE +#undef P_FAMILY_GREAT_TUSK +#define P_FAMILY_GREAT_TUSK TRUE +#undef P_FAMILY_SCREAM_TAIL +#define P_FAMILY_SCREAM_TAIL TRUE +#undef P_FAMILY_BRUTE_BONNET +#define P_FAMILY_BRUTE_BONNET TRUE +#undef P_FAMILY_FLUTTER_MANE +#define P_FAMILY_FLUTTER_MANE TRUE +#undef P_FAMILY_SLITHER_WING +#define P_FAMILY_SLITHER_WING TRUE +#undef P_FAMILY_SANDY_SHOCKS +#define P_FAMILY_SANDY_SHOCKS TRUE +#undef P_FAMILY_IRON_TREADS +#define P_FAMILY_IRON_TREADS TRUE +#undef P_FAMILY_IRON_BUNDLE +#define P_FAMILY_IRON_BUNDLE TRUE +#undef P_FAMILY_IRON_HANDS +#define P_FAMILY_IRON_HANDS TRUE +#undef P_FAMILY_IRON_JUGULIS +#define P_FAMILY_IRON_JUGULIS TRUE +#undef P_FAMILY_IRON_MOTH +#define P_FAMILY_IRON_MOTH TRUE +#undef P_FAMILY_IRON_THORNS +#define P_FAMILY_IRON_THORNS TRUE +#undef P_FAMILY_FRIGIBAX +#define P_FAMILY_FRIGIBAX TRUE +#undef P_FAMILY_GIMMIGHOUL +#define P_FAMILY_GIMMIGHOUL TRUE +#undef P_FAMILY_WO_CHIEN +#define P_FAMILY_WO_CHIEN TRUE +#undef P_FAMILY_CHIEN_PAO +#define P_FAMILY_CHIEN_PAO TRUE +#undef P_FAMILY_TING_LU +#define P_FAMILY_TING_LU TRUE +#undef P_FAMILY_CHI_YU +#define P_FAMILY_CHI_YU TRUE +#undef P_FAMILY_ROARING_MOON +#define P_FAMILY_ROARING_MOON TRUE +#undef P_FAMILY_IRON_VALIANT +#define P_FAMILY_IRON_VALIANT TRUE +#undef P_FAMILY_KORAIDON +#define P_FAMILY_KORAIDON TRUE +#undef P_FAMILY_MIRAIDON +#define P_FAMILY_MIRAIDON TRUE +#undef P_FAMILY_WALKING_WAKE +#define P_FAMILY_WALKING_WAKE TRUE +#undef P_FAMILY_IRON_LEAVES +#define P_FAMILY_IRON_LEAVES TRUE +#undef P_FAMILY_POLTCHAGEIST +#define P_FAMILY_POLTCHAGEIST TRUE +#undef P_FAMILY_SINISTCHA +#define P_FAMILY_SINISTCHA TRUE +#undef P_FAMILY_OKIDOGI +#define P_FAMILY_OKIDOGI TRUE +#undef P_FAMILY_MUNKIDORI +#define P_FAMILY_MUNKIDORI TRUE +#undef P_FAMILY_FEZANDIPITI +#define P_FAMILY_FEZANDIPITI TRUE +#undef P_FAMILY_OGERPON +#define P_FAMILY_OGERPON TRUE +#undef P_FAMILY_GOUGING_FIRE +#define P_FAMILY_GOUGING_FIRE TRUE +#undef P_FAMILY_RAGING_BOLT +#define P_FAMILY_RAGING_BOLT TRUE +#undef P_FAMILY_IRON_BOULDER +#define P_FAMILY_IRON_BOULDER TRUE +#undef P_FAMILY_IRON_CROWN +#define P_FAMILY_IRON_CROWN TRUE +#undef P_FAMILY_TERAPAGOS +#define P_FAMILY_TERAPAGOS TRUE +#undef P_FAMILY_PECHARUNT +#define P_FAMILY_PECHARUNT TRUE + #endif // GUARD_CONFIG_TEST_H diff --git a/include/constants/battle_ai.h b/include/constants/battle_ai.h index db3051b257..bddd03724b 100644 --- a/include/constants/battle_ai.h +++ b/include/constants/battle_ai.h @@ -39,14 +39,14 @@ #define AI_FLAG_NEGATE_UNAWARE (1 << 10) // AI is NOT aware of negating effects like wonder room, mold breaker, etc #define AI_FLAG_WILL_SUICIDE (1 << 11) // AI will use explosion / self destruct / final gambit / etc // New, Trainer Strategy Flags -#define AI_FLAG_HELP_PARTNER (1 << 12) // AI can try to help partner. If not set, will tend not to target partner -#define AI_FLAG_PREFER_STATUS_MOVES (1 << 13) // AI gets a score bonus for status moves. Should be combined with AI_FLAG_CHECK_BAD_MOVE to prevent using only status moves -#define AI_FLAG_STALL (1 << 14) // AI stalls battle and prefers secondary damage/trapping/etc. TODO not finished -#define AI_FLAG_SMART_SWITCHING (1 << 15) // AI includes a lot more switching checks. Automatically includes AI_FLAG_SMART_MON_CHOICES. -#define AI_FLAG_ACE_POKEMON (1 << 16) // AI has an Ace Pokemon. The last Pokemon in the party will not be used until it's the last one remaining. -#define AI_FLAG_OMNISCIENT (1 << 17) // AI has full knowledge of player moves, abilities, hold items -#define AI_FLAG_SMART_MON_CHOICES (1 << 18) // AI will make smarter decisions when choosing which mon to send out mid-battle and after a KO, which are separate decisions. Automatically included by AI_FLAG_SMART_SWITCHING. -#define AI_FLAG_CONSERVATIVE (1 << 19) // AI assumes all moves will low roll damage +#define AI_FLAG_PREFER_STATUS_MOVES (1 << 12) // AI gets a score bonus for status moves. Should be combined with AI_FLAG_CHECK_BAD_MOVE to prevent using only status moves +#define AI_FLAG_STALL (1 << 13) // AI stalls battle and prefers secondary damage/trapping/etc. TODO not finished +#define AI_FLAG_SMART_SWITCHING (1 << 14) // AI includes a lot more switching checks. Automatically includes AI_FLAG_SMART_MON_CHOICES. +#define AI_FLAG_ACE_POKEMON (1 << 15) // AI has an Ace Pokemon. The last Pokemon in the party will not be used until it's the last one remaining. +#define AI_FLAG_OMNISCIENT (1 << 16) // AI has full knowledge of player moves, abilities, hold items +#define AI_FLAG_SMART_MON_CHOICES (1 << 17) // AI will make smarter decisions when choosing which mon to send out mid-battle and after a KO, which are separate decisions. Automatically included by AI_FLAG_SMART_SWITCHING. +#define AI_FLAG_CONSERVATIVE (1 << 18) // AI assumes all moves will low roll damage +#define AI_FLAG_SEQUENCE_SWITCHING (1 << 19) // AI switches in mons in exactly party order, and never switches mid-battle #define AI_FLAG_COUNT 20 diff --git a/include/constants/battle_move_effects.h b/include/constants/battle_move_effects.h index e69987b442..156db2d6af 100644 --- a/include/constants/battle_move_effects.h +++ b/include/constants/battle_move_effects.h @@ -352,6 +352,7 @@ enum { EFFECT_TERA_BLAST, EFFECT_TERA_STARSTORM, EFFECT_DRAGON_DARTS, + EFFECT_GUARDIAN_OF_ALOLA, NUM_BATTLE_MOVE_EFFECTS, }; diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 18d1108431..03173c43cf 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -63,8 +63,8 @@ #define BS_EFFECT_BATTLER 2 #define BS_FAINTED 3 #define BS_ATTACKER_WITH_PARTNER 4 // for Cmd_updatestatusicon -#define BS_FAINTED_LINK_MULTIPLE_1 5 // for openpartyscreen -#define BS_FAINTED_LINK_MULTIPLE_2 6 // for openpartyscreen +#define BS_FAINTED_MULTIPLE_1 5 // for openpartyscreen +#define BS_FAINTED_MULTIPLE_2 6 // for openpartyscreen #define BS_BATTLER_0 7 #define BS_ATTACKER_SIDE 8 // for Cmd_jumpifability #define BS_TARGET_SIDE 9 // for Cmd_jumpifability diff --git a/include/constants/form_change_types.h b/include/constants/form_change_types.h index ac1f53bb51..74bc16cf2f 100644 --- a/include/constants/form_change_types.h +++ b/include/constants/form_change_types.h @@ -129,4 +129,9 @@ // param1: tera type #define FORM_CHANGE_BATTLE_TERASTALLIZATION 22 +// Form change that activates at midnight after a certain amount of days has passed. +// Adding this form change will automatically make the countdown start as soon the Pokémon changes into a species other than the one specified for this form change. +// param1: amount of days +#define FORM_CHANGE_DAYS_PASSED 23 + #endif // GUARD_CONSTANTS_FORM_CHANGE_TYPES_H diff --git a/include/constants/global.h b/include/constants/global.h index f389c44d53..6d2fbe81c7 100644 --- a/include/constants/global.h +++ b/include/constants/global.h @@ -1,6 +1,7 @@ #ifndef GUARD_CONSTANTS_GLOBAL_H #define GUARD_CONSTANTS_GLOBAL_H +#include "config/general.h" #include "config/battle.h" #include "config/debug.h" #include "config/item.h" diff --git a/include/constants/species.h b/include/constants/species.h index 037aae6af9..bdf3bdce1f 100644 --- a/include/constants/species.h +++ b/include/constants/species.h @@ -1629,7 +1629,7 @@ #define SPECIES_URSHIFU_RAPID_STRIKE_STYLE_GIGANTAMAX 1522 #define SPECIES_MIMIKYU_TOTEM_BUSTED 1523 -#define SPECIES_EGG SPECIES_MIMIKYU_TOTEM_BUSTED + 1 +#define SPECIES_EGG (SPECIES_MIMIKYU_TOTEM_BUSTED + 1) #define NUM_SPECIES SPECIES_EGG @@ -1694,7 +1694,7 @@ #define SPECIES_MACHAMP_GMAX SPECIES_MACHAMP_GIGANTAMAX #define SPECIES_MAGEARNA_ORIGINAL SPECIES_MAGEARNA_ORIGINAL_COLOR #define SPECIES_MAROWAK_ALOLA SPECIES_MAROWAK_ALOLAN -#define SPECIES_MAROWAX_ALOLA_TOTEM SPECIES_MAROWAK_ALOLAN_TOTEM +#define SPECIES_MAROWAK_ALOLA_TOTEM SPECIES_MAROWAK_ALOLAN_TOTEM #define SPECIES_MAUSHOLD_FOUR SPECIES_MAUSHOLD_FAMILY_OF_FOUR #define SPECIES_MELMETAL_GMAX SPECIES_MELMETAL_GIGANTAMAX #define SPECIES_MEOWTH_ALOLA SPECIES_MEOWTH_ALOLAN diff --git a/include/data.h b/include/data.h index ef803aa1bb..60824cf986 100644 --- a/include/data.h +++ b/include/data.h @@ -70,11 +70,11 @@ struct TrainerMon u8 nature:5; bool8 gender:2; bool8 isShiny:1; + u8 useGimmick:4; u8 dynamaxLevel:4; u8 teraType:5; bool8 gigantamaxFactor:1; - bool8 shouldDynamax:1; - bool8 shouldTerastal:1; + u8 padding:2; }; #define TRAINER_PARTY(partyArray) partyArray, .partySize = ARRAY_COUNT(partyArray) diff --git a/include/global.h b/include/global.h index 34080edac0..9afcd667f6 100644 --- a/include/global.h +++ b/include/global.h @@ -3,7 +3,7 @@ #include #include -#include "config.h" // we need to define config before gba headers as print stuff needs the functions nulled before defines. +#include "config/general.h" // we need to define config before gba headers as print stuff needs the functions nulled before defines. #include "gba/gba.h" #include "fpmath.h" #include "metaprogram.h" @@ -604,14 +604,15 @@ struct Roamer /*0x08*/ u16 species; /*0x0A*/ u16 hp; /*0x0C*/ u8 level; - /*0x0D*/ u16 status; - /*0x0F*/ u8 cool; - /*0x10*/ u8 beauty; - /*0x11*/ u8 cute; - /*0x12*/ u8 smart; + /*0x0D*/ u8 statusA; + /*0x0E*/ u8 cool; + /*0x0F*/ u8 beauty; + /*0x10*/ u8 cute; + /*0x11*/ u8 smart; + /*0x12*/ u8 tough; /*0x13*/ bool8 active; - /*0x14*/ u8 tough; - /*0x15*/ u8 filler[0x7]; + /*0x14*/ u8 statusB; // Stores frostbite + /*0x14*/ u8 filler[0x7]; }; struct RamScriptData diff --git a/include/item.h b/include/item.h index 0e2c7f8abe..dc1efc68a5 100644 --- a/include/item.h +++ b/include/item.h @@ -44,6 +44,7 @@ u8 *CopyItemNameHandlePlural(u16 itemId, u8 *dst, u32 quantity); bool8 IsBagPocketNonEmpty(u8 pocket); bool8 CheckBagHasItem(u16 itemId, u16 count); bool8 HasAtLeastOneBerry(void); +bool8 HasAtLeastOnePokeBall(void); bool8 CheckBagHasSpace(u16 itemId, u16 count); u32 GetFreeSpaceForItemInBag(u16 itemId); bool8 AddBagItem(u16 itemId, u16 count); diff --git a/include/pokemon.h b/include/pokemon.h index 63a125f536..920aee7e2f 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -26,6 +26,7 @@ enum { MON_DATA_IS_SHINY, MON_DATA_HIDDEN_NATURE, MON_DATA_HP_LOST, + MON_DATA_DAYS_SINCE_FORM_CHANGE, MON_DATA_ENCRYPT_SEPARATOR, MON_DATA_NICKNAME, MON_DATA_NICKNAME10, @@ -246,7 +247,8 @@ struct BoxPokemon u8 hasSpecies:1; u8 isEgg:1; u8 blockBoxRS:1; // Unused, but Pokémon Box Ruby & Sapphire will refuse to deposit a Pokémon with this flag set. - u8 unused_13:4; + u8 daysSinceFormChange:3; // 7 days. + u8 unused_13:1; u8 otName[PLAYER_NAME_LENGTH]; u8 markings:4; u8 compressedStatus:4; @@ -877,5 +879,7 @@ void HealPokemon(struct Pokemon *mon); void HealBoxPokemon(struct BoxPokemon *boxMon); const u8 *GetMoveName(u16 moveId); const u8 *GetMoveAnimationScript(u16 moveId); +void UpdateDaysPassedSinceFormChange(u16 days); +void TrySetDayLimitToFormChange(struct Pokemon *mon); #endif // GUARD_POKEMON_H diff --git a/include/pokemon_storage_system.h b/include/pokemon_storage_system.h index 5ab1b4cb8f..c2825bf779 100644 --- a/include/pokemon_storage_system.h +++ b/include/pokemon_storage_system.h @@ -75,5 +75,6 @@ void SetWaldaPhrase(const u8 *src); bool32 IsWaldaPhraseEmpty(void); void EnterPokeStorage(u8 boxOption); +u32 CountPartyNonEggMons(void); #endif // GUARD_POKEMON_STORAGE_SYSTEM_H diff --git a/include/random.h b/include/random.h index e64fb55487..44be21d61b 100644 --- a/include/random.h +++ b/include/random.h @@ -144,13 +144,16 @@ static inline void Shuffle(void *data, size_t n, size_t size) * probability. The array must be known at compile-time (e.g. a global * const array). * - * RandomPercentage(tag, t) returns FALSE with probability (1-t)/100, + * RandomPercentage(tag, t) returns FALSE with probability 1-t/100, * and TRUE with probability t/100. * * RandomWeighted(tag, w0, w1, ... wN) returns a number from 0 to N * inclusive. The return value is proportional to the weights, e.g. * RandomWeighted(..., 1, 1) returns 50% 0s and 50% 1s. - * RandomWeighted(..., 2, 1) returns 2/3 0s and 1/3 1s. */ + * RandomWeighted(..., 2, 1) returns 2/3 0s and 1/3 1s. + * + * RandomChance(tag, successes, total) returns FALSE with probability + * 1-successes/total, and TRUE with probability successes/total. */ enum RandomTag { @@ -158,9 +161,11 @@ enum RandomTag RNG_ACCURACY, RNG_CONFUSION, RNG_CRITICAL_HIT, + RNG_CURSED_BODY, RNG_CUTE_CHARM, RNG_DAMAGE_MODIFIER, RNG_DIRE_CLAW, + RNG_EFFECT_SPORE, RNG_FLAME_BODY, RNG_FORCE_RANDOM_SWITCH, RNG_FROZEN, @@ -175,14 +180,17 @@ enum RandomTag RNG_METRONOME, RNG_PARALYSIS, RNG_POISON_POINT, + RNG_POISON_TOUCH, RNG_RAMPAGE_TURNS, RNG_SECONDARY_EFFECT, RNG_SECONDARY_EFFECT_2, RNG_SECONDARY_EFFECT_3, + RNG_SHED_SKIN, RNG_SLEEP_TURNS, RNG_SPEED_TIE, RNG_STATIC, RNG_STENCH, + RNG_TOXIC_CHAIN, RNG_TRI_ATTACK, RNG_QUICK_DRAW, RNG_QUICK_CLAW, @@ -202,6 +210,8 @@ enum RandomTag RandomWeightedArray(tag, sum, ARRAY_COUNT(weights), weights); \ }) +#define RandomChance(tag, successes, total) (RandomWeighted(tag, total - successes, successes)) + #define RandomPercentage(tag, t) \ ({ \ u32 r; \ diff --git a/include/test/battle.h b/include/test/battle.h index 7d9ae72725..ecc7a8fe7c 100644 --- a/include/test/battle.h +++ b/include/test/battle.h @@ -326,14 +326,14 @@ * The inference process is naive, if your test contains anything that * modifies the speed of a battler you should specify them explicitly. * - * MOVE(battler, move | moveSlot:, [megaEvolve:], [hit:], [criticalHit:], [target:], [allowed:], [WITH_RNG(tag, value]) + * MOVE(battler, move | moveSlot:, [gimmick:], [hit:], [criticalHit:], [target:], [allowed:], [WITH_RNG(tag, value]) * Used when the battler chooses Fight. Either the move ID or move slot - * must be specified. megaEvolve: TRUE causes the battler to Mega Evolve - * if able, hit: FALSE causes the move to miss, criticalHit: TRUE causes - * the move to land a critical hit, target: is used in double battles to - * choose the target (when necessary), and allowed: FALSE is used to - * reject an illegal move e.g. a Disabled one. WITH_RNG allows the move - * to specify an explicit outcome for an RNG tag. + * must be specified. gimmick: GIMMICK_MEGA causes the battler to Mega + * Evolve if able, hit: FALSE causes the move to miss, criticalHit: TRUE + * causes the move to land a critical hit, target: is used in double + * battles to choose the target (when necessary), and allowed: FALSE is + * used to reject an illegal move e.g. a Disabled one. WITH_RNG allows + * the move to specify an explicit outcome for an RNG tag. * MOVE(playerLeft, MOVE_TACKLE, target: opponentRight); * If the battler does not have an explicit Moves specified the moveset * will be populated based on the MOVEs it uses. @@ -507,7 +507,7 @@ // or loop. #define BATTLE_TEST_STACK_SIZE 1024 #define MAX_TURNS 16 -#define MAX_QUEUED_EVENTS 25 +#define MAX_QUEUED_EVENTS 30 #define MAX_EXPECTED_ACTIONS 10 enum { BATTLE_TEST_SINGLES, BATTLE_TEST_DOUBLES, BATTLE_TEST_WILD, BATTLE_TEST_AI_SINGLES, BATTLE_TEST_AI_DOUBLES }; @@ -662,6 +662,7 @@ struct BattleTestData u8 gender; u8 nature; u16 forcedAbilities[NUM_BATTLE_SIDES][PARTY_SIZE]; + u8 chosenGimmick[NUM_BATTLE_SIDES][PARTY_SIZE]; u8 currentMonIndexes[MAX_BATTLERS_COUNT]; u8 turnState; @@ -695,6 +696,7 @@ struct BattleTestData struct BattleTestRunnerState { u8 battlersCount; + bool8 forceMoveAnim; u16 parametersCount; // Valid only in BattleTest_Setup. u16 parameters; u16 runParameter; @@ -940,15 +942,8 @@ struct MoveContext u16 explicitCriticalHit:1; u16 secondaryEffect:1; u16 explicitSecondaryEffect:1; - u16 megaEvolve:1; - u16 explicitMegaEvolve:1; - u16 ultraBurst:1; - u16 explicitUltraBurst:1; - // TODO: u8 zMove:1; - u16 dynamax:1; - u16 explicitDynamax:1; - u16 tera:1; - u16 explicitTera:1; + u16 gimmick:4; + u16 explicitGimmick:1; u16 allowed:1; u16 explicitAllowed:1; u16 notExpected:1; // Has effect only with EXPECT_MOVE @@ -991,6 +986,8 @@ void SendOut(u32 sourceLine, struct BattlePokemon *, u32 partyIndex); #define NONE_OF for (OpenQueueGroup(__LINE__, QUEUE_GROUP_NONE_OF); gBattleTestRunnerState->data.queueGroupType != QUEUE_GROUP_NONE; CloseQueueGroup(__LINE__)) #define NOT NONE_OF +#define FORCE_MOVE_ANIM(set) gBattleTestRunnerState->forceMoveAnim = (set) + #define ABILITY_POPUP(battler, ...) QueueAbility(__LINE__, battler, (struct AbilityEventContext) { __VA_ARGS__ }) #define ANIMATION(type, id, ...) QueueAnimation(__LINE__, type, id, (struct AnimationEventContext) { __VA_ARGS__ }) #define HP_BAR(battler, ...) QueueHP(__LINE__, battler, (struct HPEventContext) { R_APPEND_TRUE(__VA_ARGS__) }) diff --git a/include/test_runner.h b/include/test_runner.h index 248a0463e5..f61f26b66e 100644 --- a/include/test_runner.h +++ b/include/test_runner.h @@ -24,6 +24,7 @@ void TestRunner_Battle_InvalidNoHPMon(u32 battlerId, u32 partyIndex); void TestRunner_Battle_CheckBattleRecordActionType(u32 battlerId, u32 recordIndex, u32 actionType); u32 TestRunner_Battle_GetForcedAbility(u32 side, u32 partyIndex); +u32 TestRunner_Battle_GetChosenGimmick(u32 side, u32 partyIndex); #else @@ -45,6 +46,8 @@ u32 TestRunner_Battle_GetForcedAbility(u32 side, u32 partyIndex); #define TestRunner_Battle_GetForcedAbility(...) (u32)0 +#define TestRunner_Battle_GetChosenGimmick(...) (u32)0 + #endif #endif diff --git a/include/vs_seeker.h b/include/vs_seeker.h index 723e73bf37..d6795432b0 100644 --- a/include/vs_seeker.h +++ b/include/vs_seeker.h @@ -8,6 +8,7 @@ bool8 UpdateVsSeekerStepCounter(void); void MapResetTrainerRematches(u16 mapGroup, u16 mapNum); void ClearRematchMovementByTrainerId(void); u16 GetRematchTrainerIdVSSeeker(u16 trainerId); +bool32 IsVsSeekerEnabled(void); #define VSSEEKER_RECHARGE_STEPS 100 diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 885e002527..43fdcb5ce9 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -70,7 +70,7 @@ static s32 (*const sBattleAiFuncTable[])(u32, u32, u32, s32) = [9] = AI_PowerfulStatus, // AI_FLAG_POWERFUL_STATUS [10] = NULL, // AI_FLAG_NEGATE_UNAWARE [11] = NULL, // AI_FLAG_WILL_SUICIDE - [12] = NULL, // AI_FLAG_HELP_PARTNER + [12] = NULL, // Unused [13] = NULL, // Unused [14] = NULL, // Unused [15] = NULL, // Unused @@ -399,23 +399,6 @@ static void SetBattlerAiData(u32 battler, struct AiLogicData *aiData) aiData->speedStats[battler] = GetBattlerTotalSpeedStatArgs(battler, ability, holdEffect); } -static void SetBattlerAiGimmickData(u32 battler, struct AiLogicData *aiData) -{ - bool32 isSecondTrainer = (GetBattlerPosition(battler) == B_POSITION_OPPONENT_RIGHT) && (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) && !BATTLE_TWO_VS_ONE_OPPONENT; - u16 trainerId = isSecondTrainer ? gTrainerBattleOpponent_B : gTrainerBattleOpponent_A; - const struct TrainerMon *party = GetTrainerPartyFromId(trainerId); - if (party != NULL) - { - aiData->shouldDynamax[battler] = CanDynamax(battler) && (party[isSecondTrainer ? gBattlerPartyIndexes[battler] - MULTI_PARTY_SIZE : gBattlerPartyIndexes[battler]].shouldDynamax); - aiData->shouldTerastal[battler] = CanTerastallize(battler) && (party[isSecondTrainer ? gBattlerPartyIndexes[battler] - MULTI_PARTY_SIZE : gBattlerPartyIndexes[battler]].shouldTerastal); - } - else - { - aiData->shouldDynamax[battler] = FALSE; - aiData->shouldTerastal[battler] = FALSE; - } -} - static u32 Ai_SetMoveAccuracy(struct AiLogicData *aiData, u32 battlerAtk, u32 battlerDef, u32 move) { u32 accuracy; @@ -495,7 +478,6 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData) continue; SetBattlerAiData(battlerAtk, aiData); - SetBattlerAiGimmickData(battlerAtk, aiData); SetBattlerAiMovesData(aiData, battlerAtk, battlersCount); } } @@ -529,7 +511,8 @@ static bool32 AI_ShouldSwitchIfBadMoves(u32 battler, bool32 doubleBattle) 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_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)) { // Consider switching if all moves are worthless to use. if (GetTotalBaseStat(gBattleMons[battler].species) >= 310 // Mon is not weak. @@ -1458,7 +1441,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); else if (aiData->abilities[battlerDef] == ABILITY_SUCTION_CUPS) ADJUST_SCORE(-10); - else if (IsDynamaxed(battlerDef)) + else if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) ADJUST_SCORE(-10); break; case EFFECT_TOXIC_THREAD: @@ -1491,7 +1474,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) return 0; if (!ShouldTryOHKO(battlerAtk, battlerDef, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], move)) ADJUST_SCORE(-10); - else if (IsDynamaxed(battlerDef)) + else if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) ADJUST_SCORE(-10); break; case EFFECT_MIST: @@ -1530,7 +1513,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-3); break; case EFFECT_DISABLE: - if (IsDynamaxed(battlerDef)) + if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) ADJUST_SCORE(-10); else if (gDisableStructs[battlerDef].disableTimer == 0 && (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB) @@ -1552,7 +1535,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } break; case EFFECT_ENCORE: - if (IsDynamaxed(battlerDef)) + if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) ADJUST_SCORE(-10); else if (gDisableStructs[battlerDef].encoreTimer == 0 && (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB) @@ -1768,7 +1751,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } break; case EFFECT_TORMENT: - if (IsDynamaxed(battlerDef)) + if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) ADJUST_SCORE(-10); else if (gBattleMons[battlerDef].status2 & STATUS2_TORMENT || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) @@ -1893,7 +1876,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); break; case EFFECT_REST: - if (!AI_CanSleep(battlerAtk, aiData->abilities[battlerAtk])) + if (!CanBeSlept(battlerAtk, aiData->abilities[battlerAtk])) ADJUST_SCORE(-10); //fallthrough case EFFECT_RESTORE_HP: @@ -1974,7 +1957,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_DESTINY_BOND: if (gBattleMons[battlerDef].status2 & STATUS2_DESTINY_BOND) ADJUST_SCORE(-10); - else if (IsDynamaxed(battlerDef)) + else if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) ADJUST_SCORE(-10); break; case EFFECT_HEAL_BELL: @@ -2128,7 +2111,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); break; case EFFECT_NATURE_POWER: - return AI_CheckBadMove(battlerAtk, battlerDef, GetNaturePowerMove(), score); + return AI_CheckBadMove(battlerAtk, battlerDef, GetNaturePowerMove(battlerAtk), score); case EFFECT_TAUNT: if (gDisableStructs[battlerDef].tauntTimer > 0 || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) @@ -2172,7 +2155,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) || gAbilitiesInfo[aiData->abilities[battlerDef]].cantBeSwapped || aiData->holdEffects[battlerDef] == HOLD_EFFECT_ABILITY_SHIELD) ADJUST_SCORE(-10); - else if (IsDynamaxed(battlerDef)) + else if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) ADJUST_SCORE(-10); break; case EFFECT_WORRY_SEED: @@ -2192,7 +2175,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) || gAbilitiesInfo[aiData->abilities[battlerDef]].cantBeOverwritten || aiData->holdEffects[battlerAtk] == HOLD_EFFECT_ABILITY_SHIELD) ADJUST_SCORE(-10); - else if (IsDynamaxed(battlerDef)) + else if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) ADJUST_SCORE(-10); break; case EFFECT_SIMPLE_BEAM: @@ -2448,11 +2431,11 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) switch (move) { case MOVE_TRICK_OR_TREAT: - if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GHOST) || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove) || IsTerastallized(battlerDef)) + if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GHOST) || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove) || GetActiveGimmick(battlerDef) == GIMMICK_TERA) ADJUST_SCORE(-10); break; case MOVE_FORESTS_CURSE: - if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove) || IsTerastallized(battlerDef)) + if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove) || GetActiveGimmick(battlerDef) == GIMMICK_TERA) ADJUST_SCORE(-10); break; } @@ -2520,7 +2503,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { ADJUST_SCORE(-10); } - else if (IsDynamaxed(battlerDef)) + else if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) ADJUST_SCORE(-10); else if (isDoubleBattle) { @@ -2672,6 +2655,22 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) return 0; // cannot even select } // move effect checks + // Choice items + if (HOLD_EFFECT_CHOICE(aiData->holdEffects[battlerAtk]) && gBattleMons[battlerAtk].ability != ABILITY_KLUTZ) + { + // Don't use user-target moves ie. Swords Dance, with exceptions + if ((moveTarget & MOVE_TARGET_USER) + && moveEffect != EFFECT_DESTINY_BOND && moveEffect != EFFECT_WISH && moveEffect != EFFECT_HEALING_WISH + && !(moveEffect == EFFECT_AURORA_VEIL && (AI_GetWeather(aiData) & (B_WEATHER_SNOW | B_WEATHER_HAIL)))) + ADJUST_SCORE(-30); + // Don't use a status move if the mon is the last one in the party, has no good switchin, or is trapped + else if (GetBattleMoveCategory(move) == DAMAGE_CATEGORY_STATUS + && (CountUsablePartyMons(battlerAtk) < 1 + || AI_DATA->mostSuitableMonId[battlerAtk] == PARTY_SIZE + || IsBattlerTrapped(battlerAtk, TRUE))) + ADJUST_SCORE(-30); + } + if (score < 0) score = 0; @@ -3013,8 +3012,8 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case EFFECT_SOAK: if (atkPartnerAbility == ABILITY_WONDER_GUARD - && IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_WATER) - && !IsTerastallized(battlerAtkPartner)) + && !IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_WATER) + && GetActiveGimmick(battlerAtkPartner) != GIMMICK_TERA) { RETURN_SCORE_PLUS(WEAK_EFFECT); } @@ -3236,7 +3235,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) u32 i; // The AI should understand that while Dynamaxed, status moves function like Protect. - if (IsDynamaxed(battlerAtk) && gMovesInfo[move].category == DAMAGE_CATEGORY_STATUS) + if (GetActiveGimmick(battlerAtk) == GIMMICK_DYNAMAX && gMovesInfo[move].category == DAMAGE_CATEGORY_STATUS) moveEffect = EFFECT_PROTECT; // check status move preference @@ -3431,7 +3430,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) if ((gMovesInfo[move].soundMove && aiData->abilities[battlerDef] == ABILITY_SOUNDPROOF) || aiData->abilities[battlerDef] == ABILITY_SUCTION_CUPS) break; - else if (IsDynamaxed(battlerDef)) + else if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) break; score += AI_TryToClearStats(battlerAtk, battlerDef, isDoubleBattle); break; @@ -3499,7 +3498,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) } break; case EFFECT_REST: - if (!(AI_CanSleep(battlerAtk, aiData->abilities[battlerAtk]))) + if (!(CanBeSlept(battlerAtk, aiData->abilities[battlerAtk]))) { break; } @@ -3516,7 +3515,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) } break; case EFFECT_OHKO: - if (IsDynamaxed(battlerDef)) + if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) break; else if (gStatuses3[battlerAtk] & STATUS3_ALWAYS_HITS) ADJUST_SCORE(BEST_EFFECT); @@ -3613,7 +3612,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(BEST_EFFECT); break; case EFFECT_DISABLE: - if (IsDynamaxed(battlerDef)) + if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) break; else if (gDisableStructs[battlerDef].disableTimer == 0 && (gLastMoves[battlerDef] != MOVE_NONE) @@ -3626,7 +3625,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) } break; case EFFECT_ENCORE: - if (IsDynamaxed(battlerDef)) + if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) break; else if (gDisableStructs[battlerDef].encoreTimer == 0 && (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB) @@ -3645,7 +3644,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(GOOD_EFFECT); break; case EFFECT_DESTINY_BOND: - if (IsDynamaxed(battlerDef)) + if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) break; else if (AI_IsFaster(battlerAtk, battlerDef, move) && CanTargetFaintAi(battlerDef, battlerAtk)) ADJUST_SCORE(GOOD_EFFECT); @@ -3758,7 +3757,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(GOOD_EFFECT); break; case EFFECT_SANDSTORM: - if (ShouldSetSandstorm(battlerAtk, aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerAtk])) + if (ShouldSetSandstorm(battlerAtk, aiData->abilities[battlerAtk], aiData->holdEffects[battlerAtk])) { ADJUST_SCORE(DECENT_EFFECT); if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_SMOOTH_ROCK) @@ -3979,7 +3978,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(DECENT_EFFECT); break; case HOLD_EFFECT_FLAME_ORB: - if (!ShouldBurnSelf(battlerAtk, aiData->abilities[battlerAtk]) && AI_CanBeBurned(battlerAtk, aiData->abilities[battlerDef])) + if (!ShouldBurnSelf(battlerAtk, aiData->abilities[battlerAtk]) && CanBeBurned(battlerAtk, aiData->abilities[battlerDef])) ADJUST_SCORE(DECENT_EFFECT); break; case HOLD_EFFECT_BLACK_SLUDGE: @@ -4012,7 +4011,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) } break; case HOLD_EFFECT_EJECT_BUTTON: - //if (!IsRaidBattle() && IsDynamaxed(battlerDef) && gNewBS->dynamaxData.timer[battlerDef] > 1 && + //if (!IsRaidBattle() && GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX && gNewBS->dynamaxData.timer[battlerDef] > 1 && if (HasDamagingMove(battlerAtk) || (isDoubleBattle && IsBattlerAlive(BATTLE_PARTNER(battlerAtk)) && HasDamagingMove(BATTLE_PARTNER(battlerAtk)))) ADJUST_SCORE(DECENT_EFFECT); // Force 'em out next turn @@ -4094,7 +4093,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(DECENT_EFFECT); break; case EFFECT_SKILL_SWAP: - if (IsDynamaxed(battlerDef)) + if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) break; else if (gAbilitiesInfo[aiData->abilities[battlerDef]].aiRating > gAbilitiesInfo[aiData->abilities[battlerAtk]].aiRating) ADJUST_SCORE(DECENT_EFFECT); @@ -4106,7 +4105,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(DECENT_EFFECT); break; case EFFECT_ENTRAINMENT: - if (IsDynamaxed(battlerDef)) + if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) break; else if ((IsAbilityOfRating(aiData->abilities[battlerDef], 5) || gAbilitiesInfo[aiData->abilities[battlerAtk]].aiRating <= 0) && (aiData->abilities[battlerDef] != aiData->abilities[battlerAtk] && !(gStatuses3[battlerDef] & STATUS3_GASTRO_ACID))) @@ -4735,17 +4734,17 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score if (gMovesInfo[move].power) { if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex) == 0) - ADJUST_SCORE(-20); + ADJUST_AND_RETURN_SCORE(NO_DAMAGE_OR_FAILS); // No point in checking the move further so return early else { if ((AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_RISKY) && GetBestDmgMoveFromBattler(battlerAtk, battlerDef) == move) - ADJUST_SCORE(1); + ADJUST_SCORE(BEST_DAMAGE_MOVE); else - score += AI_CompareDamagingMoves(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex); + ADJUST_SCORE(AI_CompareDamagingMoves(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex)); } } - score += AI_CalcMoveEffectScore(battlerAtk, battlerDef, move); + ADJUST_SCORE(AI_CalcMoveEffectScore(battlerAtk, battlerDef, move)); return score; } diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 154c251118..6471c25ce6 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -485,7 +485,7 @@ static bool32 ShouldSwitchIfGameStatePrompt(u32 battler, bool32 emitResult) { //Yawn if (gStatuses3[battler] & STATUS3_YAWN - && AI_CanSleep(battler, monAbility) + && CanBeSlept(battler, monAbility) && gBattleMons[battler].hp > gBattleMons[battler].maxHP / 3) { switchMon = TRUE; @@ -919,6 +919,24 @@ static bool32 ShouldSwitchIfEncored(u32 battler, bool32 emitResult) return FALSE; } +static bool32 ShouldSwitchIfBadChoiceLock(u32 battler, bool32 emitResult) +{ + u32 holdEffect = GetBattlerHoldEffect(battler, FALSE); + + if (HOLD_EFFECT_CHOICE(holdEffect) && gBattleMons[battler].ability != ABILITY_KLUTZ) + { + if (gMovesInfo[gLastUsedMove].category == DAMAGE_CATEGORY_STATUS) + { + gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE; + if (emitResult) + BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); + return TRUE; + } + } + + return FALSE; +} + // AI should switch if it's become setup fodder and has something better to switch to static bool32 AreAttackingStatsLowered(u32 battler, bool32 emitResult) { @@ -941,7 +959,8 @@ static bool32 AreAttackingStatsLowered(u32 battler, bool32 emitResult) if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (Random() & 1)) { gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE; - BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); + if (emitResult) + BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); return TRUE; } } @@ -949,7 +968,8 @@ static bool32 AreAttackingStatsLowered(u32 battler, bool32 emitResult) else if (attackingStage < DEFAULT_STAT_STAGE - 2) { gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE; - BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); + if (emitResult) + BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); return TRUE; } } @@ -1000,6 +1020,10 @@ bool32 ShouldSwitch(u32 battler, bool32 emitResult) if (gBattleTypeFlags & BATTLE_TYPE_ARENA) return FALSE; + // Sequence Switching AI never switches mid-battle + if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SEQUENCE_SWITCHING) + return FALSE; + availableToSwitch = 0; if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) @@ -1076,6 +1100,8 @@ bool32 ShouldSwitch(u32 battler, bool32 emitResult) return TRUE; if (ShouldSwitchIfEncored(battler, emitResult)) return TRUE; + if (ShouldSwitchIfBadChoiceLock(battler, emitResult)) + return TRUE; if (AreAttackingStatsLowered(battler, emitResult)) return TRUE; @@ -1312,7 +1338,7 @@ static bool32 IsMonGrounded(u16 heldItemEffect, u32 ability, u8 type1, u8 type2) static u32 GetSwitchinHazardsDamage(u32 battler, struct BattlePokemon *battleMon) { u8 defType1 = battleMon->type1, defType2 = battleMon->type2, tSpikesLayers; - u16 heldItemEffect = gItemsInfo[battleMon->item].holdEffect; + u16 heldItemEffect = ItemId_GetHoldEffect(battleMon->item); u32 maxHP = battleMon->maxHP, ability = battleMon->ability, status = battleMon->status1; u32 spikesDamage = 0, tSpikesDamage = 0, hazardDamage = 0; u32 hazardFlags = gSideStatuses[GetBattlerSide(battler)] & (SIDE_STATUS_SPIKES | SIDE_STATUS_STEALTH_ROCK | SIDE_STATUS_STICKY_WEB | SIDE_STATUS_TOXIC_SPIKES | SIDE_STATUS_SAFEGUARD); @@ -1336,8 +1362,6 @@ static u32 GetSwitchinHazardsDamage(u32 battler, struct BattlePokemon *battleMon hazardDamage += spikesDamage; } - // Toxic Spikes - // TODO: CanBePoisoned compatibility to avoid duplicate code if ((hazardFlags & SIDE_STATUS_TOXIC_SPIKES) && (defType1 != TYPE_POISON && defType2 != TYPE_POISON && defType1 != TYPE_STEEL && defType2 != TYPE_STEEL && ability != ABILITY_IMMUNITY && ability != ABILITY_POISON_HEAL && ability != ABILITY_COMATOSE @@ -1372,7 +1396,7 @@ static u32 GetSwitchinHazardsDamage(u32 battler, struct BattlePokemon *battleMon static s32 GetSwitchinWeatherImpact(void) { s32 weatherImpact = 0, maxHP = AI_DATA->switchinCandidate.battleMon.maxHP, ability = AI_DATA->switchinCandidate.battleMon.ability; - u32 holdEffect = gItemsInfo[AI_DATA->switchinCandidate.battleMon.item].holdEffect; + u32 holdEffect = ItemId_GetHoldEffect(AI_DATA->switchinCandidate.battleMon.item); if (WEATHER_HAS_EFFECT) { @@ -1436,7 +1460,7 @@ static s32 GetSwitchinWeatherImpact(void) static u32 GetSwitchinRecurringHealing(void) { u32 recurringHealing = 0, maxHP = AI_DATA->switchinCandidate.battleMon.maxHP, ability = AI_DATA->switchinCandidate.battleMon.ability; - u32 holdEffect = gItemsInfo[AI_DATA->switchinCandidate.battleMon.item].holdEffect; + u32 holdEffect = ItemId_GetHoldEffect(AI_DATA->switchinCandidate.battleMon.item); // Items if (ability != ABILITY_KLUTZ) @@ -1470,7 +1494,7 @@ static u32 GetSwitchinRecurringHealing(void) static u32 GetSwitchinRecurringDamage(void) { u32 passiveDamage = 0, maxHP = AI_DATA->switchinCandidate.battleMon.maxHP, ability = AI_DATA->switchinCandidate.battleMon.ability; - u32 holdEffect = gItemsInfo[AI_DATA->switchinCandidate.battleMon.item].holdEffect; + u32 holdEffect = ItemId_GetHoldEffect(AI_DATA->switchinCandidate.battleMon.item); // Items if (ability != ABILITY_MAGIC_GUARD && ability != ABILITY_KLUTZ) @@ -1502,7 +1526,7 @@ static u32 GetSwitchinStatusDamage(u32 battler) { u8 defType1 = AI_DATA->switchinCandidate.battleMon.type1, defType2 = AI_DATA->switchinCandidate.battleMon.type2; u8 tSpikesLayers = gSideTimers[GetBattlerSide(battler)].toxicSpikesAmount; - u16 heldItemEffect = gItemsInfo[AI_DATA->switchinCandidate.battleMon.item].holdEffect; + u16 heldItemEffect = ItemId_GetHoldEffect(AI_DATA->switchinCandidate.battleMon.item); u32 status = AI_DATA->switchinCandidate.battleMon.status1, ability = AI_DATA->switchinCandidate.battleMon.ability, maxHP = AI_DATA->switchinCandidate.battleMon.maxHP; u32 statusDamage = 0; @@ -1580,8 +1604,8 @@ static u32 GetSwitchinHitsToKO(s32 damageTaken, u32 battler) u32 recurringHealing = GetSwitchinRecurringHealing(); u32 statusDamage = GetSwitchinStatusDamage(battler); u32 hitsToKO = 0, singleUseItemHeal = 0; - u16 maxHP = AI_DATA->switchinCandidate.battleMon.maxHP, item = AI_DATA->switchinCandidate.battleMon.item, heldItemEffect = gItemsInfo[item].holdEffect; - u8 weatherDuration = gWishFutureKnock.weatherDuration, holdEffectParam = gItemsInfo[item].holdEffectParam; + u16 maxHP = AI_DATA->switchinCandidate.battleMon.maxHP, item = AI_DATA->switchinCandidate.battleMon.item, heldItemEffect = ItemId_GetHoldEffect(item); + u8 weatherDuration = gWishFutureKnock.weatherDuration, holdEffectParam = ItemId_GetHoldEffectParam(item); u32 opposingBattler = GetBattlerAtPosition(BATTLE_OPPOSITE(GetBattlerPosition(battler))); u32 opposingAbility = gBattleMons[opposingBattler].ability; bool32 usedSingleUseHealingItem = FALSE; @@ -1765,7 +1789,7 @@ static bool32 CanAbilityTrapOpponent(u16 ability, u32 opponent) // the Most Damage code will prioritize switching into whatever mon deals the most damage, which is generally not as good as having a good Type Matchup // Everything runs in the same loop to minimize computation time. This makes it harder to read, but hopefully the comments can guide you! -static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, u32 battler, u32 opposingBattler, u8 battlerIn1, u8 battlerIn2, bool32 isSwitchAfterKO) +static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, u32 battler, u32 opposingBattler, u32 battlerIn1, u32 battlerIn2, bool32 isSwitchAfterKO) { int revengeKillerId = PARTY_SIZE, slowRevengeKillerId = PARTY_SIZE, fastThreatenId = PARTY_SIZE, slowThreatenId = PARTY_SIZE, damageMonId = PARTY_SIZE; int batonPassId = PARTY_SIZE, typeMatchupId = PARTY_SIZE, typeMatchupEffectiveId = PARTY_SIZE, defensiveMonId = PARTY_SIZE, aceMonId = PARTY_SIZE, trapperId = PARTY_SIZE; @@ -1803,7 +1827,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, InitializeSwitchinCandidate(&party[i]); - // While not really invalid per say, not really wise to switch into this mon + // While not really invalid per se, not really wise to switch into this mon if (AI_DATA->switchinCandidate.battleMon.ability == ABILITY_TRUANT && IsTruantMonVulnerable(battler, opposingBattler)) continue; @@ -1883,7 +1907,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, // If AI mon outspeeds and doesn't die to hazards if ((((aiMonSpeed > playerMonSpeed && !(gFieldStatuses & STATUS_FIELD_TRICK_ROOM)) || aiMovePriority > 0) // Outspeed if not Trick Room || ((gFieldStatuses & STATUS_FIELD_TRICK_ROOM) // Trick Room - && (aiMonSpeed < playerMonSpeed || (gItemsInfo[AI_DATA->switchinCandidate.battleMon.item].holdEffect == HOLD_EFFECT_ROOM_SERVICE && aiMonSpeed * 2 / 3 < playerMonSpeed)))) // Trick Room speeds + && (aiMonSpeed < playerMonSpeed || (ItemId_GetHoldEffect(AI_DATA->switchinCandidate.battleMon.item) == HOLD_EFFECT_ROOM_SERVICE && aiMonSpeed * 2 / 3 < playerMonSpeed)))) // Trick Room speeds && AI_DATA->switchinCandidate.battleMon.hp > GetSwitchinHazardsDamage(battler, &AI_DATA->switchinCandidate.battleMon)) // Hazards { // We have a revenge killer @@ -1908,7 +1932,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, // If AI mon outspeeds if (((aiMonSpeed > playerMonSpeed && !(gFieldStatuses & STATUS_FIELD_TRICK_ROOM)) || aiMovePriority > 0) // Outspeed if not Trick Room || (((gFieldStatuses & STATUS_FIELD_TRICK_ROOM) && gFieldTimers.trickRoomTimer > 1) // Trick Room has at least 2 turns left - && (aiMonSpeed < playerMonSpeed || (gItemsInfo[AI_DATA->switchinCandidate.battleMon.item].holdEffect == HOLD_EFFECT_ROOM_SERVICE && aiMonSpeed * 2/ 3 < playerMonSpeed)))) // Trick Room speeds + && (aiMonSpeed < playerMonSpeed || (ItemId_GetHoldEffect(AI_DATA->switchinCandidate.battleMon.item) == HOLD_EFFECT_ROOM_SERVICE && aiMonSpeed * 2/ 3 < playerMonSpeed)))) // Trick Room speeds { // If AI mon can't be OHKO'd if (hitsToKOAI > hitsToKOAIThreshold) @@ -1949,53 +1973,59 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, // Different switching priorities depending on switching mid battle vs switching after a KO if (isSwitchAfterKO) { - // Return Trapper > GetBestMonRevengeKiller > GetBestMonTypeMatchup > GetBestMonBatonPass > GetBestMonDmg - if (trapperId != PARTY_SIZE) - return trapperId; - else if (revengeKillerId != PARTY_SIZE) - return revengeKillerId; - else if (slowRevengeKillerId != PARTY_SIZE) - return slowRevengeKillerId; - else if (fastThreatenId != PARTY_SIZE) - return fastThreatenId; - else if (slowThreatenId != PARTY_SIZE) - return slowThreatenId; - else if (typeMatchupEffectiveId != PARTY_SIZE) - return typeMatchupEffectiveId; - else if (typeMatchupId != PARTY_SIZE) - return typeMatchupId; - else if (batonPassId != PARTY_SIZE) - return batonPassId; - else if (damageMonId != PARTY_SIZE) - return damageMonId; + // Return Trapper > Revenge Killer > Type Matchup > Baton Pass > Best Damage + if (trapperId != PARTY_SIZE) return trapperId; + else if (revengeKillerId != PARTY_SIZE) return revengeKillerId; + else if (slowRevengeKillerId != PARTY_SIZE) return slowRevengeKillerId; + else if (fastThreatenId != PARTY_SIZE) return fastThreatenId; + else if (slowThreatenId != PARTY_SIZE) return slowThreatenId; + else if (typeMatchupEffectiveId != PARTY_SIZE) return typeMatchupEffectiveId; + else if (typeMatchupId != PARTY_SIZE) return typeMatchupId; + else if (batonPassId != PARTY_SIZE) return batonPassId; + else if (damageMonId != PARTY_SIZE) return damageMonId; } else { - // Return Trapper > GetBestMonTypeMatchup > GetBestMonDefensive > GetBestMonBatonPass - if (trapperId != PARTY_SIZE) - return trapperId; - else if (typeMatchupEffectiveId != PARTY_SIZE) - return typeMatchupEffectiveId; - else if (typeMatchupId != PARTY_SIZE) - return typeMatchupId; - else if (defensiveMonId != PARTY_SIZE) - return defensiveMonId; - else if (batonPassId != PARTY_SIZE) - return batonPassId; + // Return Trapper > Type Matchup > Best Defensive > Baton Pass + if (trapperId != PARTY_SIZE) return trapperId; + else if (typeMatchupEffectiveId != PARTY_SIZE) return typeMatchupEffectiveId; + else if (typeMatchupId != PARTY_SIZE) return typeMatchupId; + else if (defensiveMonId != PARTY_SIZE) return defensiveMonId; + else if (batonPassId != PARTY_SIZE) return batonPassId; // If ace mon is the last available Pokemon and U-Turn/Volt Switch was used - switch to the mon. else if (aceMonId != PARTY_SIZE - && (gMovesInfo[gLastUsedMove].effect == EFFECT_HIT_ESCAPE || gMovesInfo[gLastUsedMove].effect == EFFECT_PARTING_SHOT)) + && (gMovesInfo[gLastUsedMove].effect == EFFECT_HIT_ESCAPE || gMovesInfo[gLastUsedMove].effect == EFFECT_PARTING_SHOT || gMovesInfo[gLastUsedMove].effect == EFFECT_BATON_PASS)) return aceMonId; } return PARTY_SIZE; } -u8 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd) +static u32 GetNextMonInParty(struct Pokemon *party, int firstId, int lastId, u32 battlerIn1, u32 battlerIn2) +{ + u32 i; + // Iterate through mons + for (i = firstId; i < lastId; i++) + { + // Check mon validity + if (!IsValidForBattle(&party[i]) + || gBattlerPartyIndexes[battlerIn1] == i + || gBattlerPartyIndexes[battlerIn2] == i + || i == gBattleStruct->monToSwitchIntoId[battlerIn1] + || i == gBattleStruct->monToSwitchIntoId[battlerIn2]) + { + continue; + } + return i; + } + return PARTY_SIZE; +} + +u32 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd) { u32 opposingBattler = 0; u32 bestMonId = PARTY_SIZE; - u8 battlerIn1 = 0, battlerIn2 = 0; + u32 battlerIn1 = 0, battlerIn2 = 0; s32 firstId = 0; s32 lastId = 0; // + 1 struct Pokemon *party; @@ -2031,6 +2061,12 @@ u8 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd) else party = gEnemyParty; + if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SEQUENCE_SWITCHING) + { + bestMonId = GetNextMonInParty(party, firstId, lastId, battlerIn1, battlerIn2); + return bestMonId; + } + // 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 @@ -2052,7 +2088,7 @@ u8 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd) || gBattlerPartyIndexes[battlerIn2] == i || i == gBattleStruct->monToSwitchIntoId[battlerIn1] || i == gBattleStruct->monToSwitchIntoId[battlerIn2] - || (GetMonAbility(&party[i]) == ABILITY_TRUANT && IsTruantMonVulnerable(battler, opposingBattler))) // While not really invalid per say, not really wise to switch into this mon.) + || (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]; } diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 731084731b..fe73ab3bce 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -468,7 +468,7 @@ bool32 IsDamageMoveUnusable(u32 move, u32 battlerAtk, u32 battlerDef) break; case EFFECT_LOW_KICK: case EFFECT_HEAT_CRASH: - if (IsDynamaxed(battlerDef)) + if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) return TRUE; break; case EFFECT_FAIL_IF_NOT_ARG_TYPE: @@ -508,35 +508,28 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u s32 moveType; uq4_12_t effectivenessMultiplier; bool32 isDamageMoveUnusable = FALSE; - bool32 toggledDynamax = FALSE; - bool32 toggledTera = FALSE; + bool32 toggledGimmick = FALSE; struct AiLogicData *aiData = AI_DATA; gBattleStruct->aiCalcInProgress = TRUE; - // Temporarily enable Z-Moves for damage calcs - if (considerZPower && IsViableZMove(battlerAtk, move)) + // Temporarily enable gimmicks for damage calcs if planned + if (gBattleStruct->gimmick.usableGimmick[battlerAtk] && GetActiveGimmick(battlerAtk) == GIMMICK_NONE + && !(gBattleStruct->gimmick.usableGimmick[battlerAtk] == GIMMICK_Z_MOVE && !considerZPower)) { - gBattleStruct->zmove.baseMoves[battlerAtk] = move; - gBattleStruct->zmove.active = TRUE; + // Set Z-Move variables if needed + if (gBattleStruct->gimmick.usableGimmick[battlerAtk] == GIMMICK_Z_MOVE && IsViableZMove(battlerAtk, move)) + gBattleStruct->zmove.baseMoves[battlerAtk] = move; + + toggledGimmick = TRUE; + SetActiveGimmick(battlerAtk, gBattleStruct->gimmick.usableGimmick[battlerAtk]); } - else if (gMovesInfo[move].effect == EFFECT_PHOTON_GEYSER) + + if (gMovesInfo[move].effect == EFFECT_PHOTON_GEYSER) gBattleStruct->swapDamageCategory = (GetCategoryBasedOnStats(gBattlerAttacker) == DAMAGE_CATEGORY_PHYSICAL); - else if (move == MOVE_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] == DAMAGE_CATEGORY_SPECIAL) + else if (move == MOVE_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] == DAMAGE_CATEGORY_PHYSICAL) gBattleStruct->swapDamageCategory = TRUE; else if (gMovesInfo[move].effect == EFFECT_NATURE_POWER) - move = GetNaturePowerMove(); - - // Temporarily enable other gimmicks for damage calcs if planned - if (AI_DATA->shouldDynamax[battlerAtk]) - { - toggledDynamax = TRUE; - gBattleStruct->dynamax.dynamaxed[battlerAtk] = TRUE; - } - if (AI_DATA->shouldTerastal[battlerAtk]) - { - toggledTera = TRUE; - gBattleStruct->tera.isTerastallized[GetBattlerSide(battlerAtk)] |= gBitTable[gBattlerPartyIndexes[battlerAtk]]; - } + move = GetNaturePowerMove(battlerAtk); gBattleStruct->dynamicMoveType = 0; @@ -608,7 +601,7 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u simDamage.minimum = LowestRollDmg(nonCritDmg); } - if (!gBattleStruct->zmove.active) + if (GetActiveGimmick(battlerAtk) != GIMMICK_Z_MOVE) { // Handle dynamic move damage switch (gMovesInfo[move].effect) @@ -697,14 +690,12 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u // convert multiper to AI_EFFECTIVENESS_xX *typeEffectiveness = AI_GetEffectiveness(effectivenessMultiplier); + // Undo temporary settings gBattleStruct->aiCalcInProgress = FALSE; gBattleStruct->swapDamageCategory = FALSE; - gBattleStruct->zmove.active = FALSE; gBattleStruct->zmove.baseMoves[battlerAtk] = MOVE_NONE; - if (toggledDynamax) - gBattleStruct->dynamax.dynamaxed[battlerAtk] = FALSE; - if (toggledTera) - gBattleStruct->tera.isTerastallized[GetBattlerSide(battlerAtk)] &= ~(gBitTable[gBattlerPartyIndexes[battlerAtk]]); + if (toggledGimmick) + SetActiveGimmick(battlerAtk, GIMMICK_NONE); return simDamage; } @@ -2740,48 +2731,18 @@ bool32 IsBattlerIncapacitated(u32 battler, u32 ability) return FALSE; } -bool32 AI_CanSleep(u32 battler, u32 ability) -{ - if (ability == ABILITY_INSOMNIA - || ability == ABILITY_VITAL_SPIRIT - || ability == ABILITY_COMATOSE - || gBattleMons[battler].status1 & STATUS1_ANY - || gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SAFEGUARD - || (gFieldStatuses & (STATUS_FIELD_MISTY_TERRAIN | STATUS_FIELD_ELECTRIC_TERRAIN)) - || IsAbilityStatusProtected(battler)) - return FALSE; - return TRUE; -} - bool32 AI_CanPutToSleep(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move, u32 partnerMove) { - if (!AI_CanSleep(battlerDef, defAbility) + if (!CanBeSlept(battlerDef, defAbility) || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) || PartnerMoveEffectIsStatusSameTarget(BATTLE_PARTNER(battlerAtk), battlerDef, partnerMove)) // shouldn't try to sleep mon that partner is trying to make sleep return FALSE; return TRUE; } -static bool32 AI_CanBePoisoned(u32 battlerAtk, u32 battlerDef, u32 move) -{ - u32 ability = AI_DATA->abilities[battlerDef]; - - if (!(CanPoisonType(battlerAtk, battlerDef)) - || gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_SAFEGUARD - || gBattleMons[battlerDef].status1 & STATUS1_ANY - || ability == ABILITY_IMMUNITY - || ability == ABILITY_COMATOSE - || AI_IsAbilityOnSide(battlerDef, ABILITY_PASTEL_VEIL) - || gBattleMons[battlerDef].status1 & STATUS1_ANY - || IsAbilityStatusProtected(battlerDef) - || AI_IsTerrainAffected(battlerDef, STATUS_FIELD_MISTY_TERRAIN)) - return FALSE; - return TRUE; -} - bool32 ShouldPoisonSelf(u32 battler, u32 ability) { - if (AI_CanBePoisoned(battler, battler, 0) && ( + if (CanBePoisoned(battler, battler, GetBattlerAbility(battler)) && ( ability == ABILITY_MARVEL_SCALE || ability == ABILITY_POISON_HEAL || ability == ABILITY_QUICK_FEET @@ -2796,7 +2757,7 @@ bool32 ShouldPoisonSelf(u32 battler, u32 ability) bool32 AI_CanPoison(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move, u32 partnerMove) { - if (!AI_CanBePoisoned(battlerAtk, battlerDef, move) + if (!CanBePoisoned(battlerAtk, battlerDef, GetBattlerAbility(battlerDef)) || AI_DATA->effectiveness[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex] == AI_EFFECTIVENESS_x0 || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) || PartnerMoveEffectIsStatusSameTarget(BATTLE_PARTNER(battlerAtk), battlerDef, partnerMove)) @@ -2809,20 +2770,9 @@ bool32 AI_CanPoison(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move, u3 return TRUE; } -static bool32 AI_CanBeParalyzed(u32 battler, u32 ability) -{ - if (ability == ABILITY_LIMBER - || ability == ABILITY_COMATOSE - || IS_BATTLER_OF_TYPE(battler, TYPE_ELECTRIC) - || gBattleMons[battler].status1 & STATUS1_ANY - || IsAbilityStatusProtected(battler)) - return FALSE; - return TRUE; -} - bool32 AI_CanParalyze(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move, u32 partnerMove) { - if (!AI_CanBeParalyzed(battlerDef, defAbility) + if (!CanBeParalyzed(battlerDef, defAbility) || AI_DATA->effectiveness[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex] == AI_EFFECTIVENESS_x0 || gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_SAFEGUARD || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) @@ -2856,19 +2806,6 @@ bool32 AI_CanConfuse(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 battler return TRUE; } -bool32 AI_CanBeBurned(u32 battler, u32 ability) -{ - if (ability == ABILITY_WATER_VEIL - || ability == ABILITY_WATER_BUBBLE - || ability == ABILITY_COMATOSE - || IS_BATTLER_OF_TYPE(battler, TYPE_FIRE) - || gBattleMons[battler].status1 & STATUS1_ANY - || IsAbilityStatusProtected(battler) - || gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SAFEGUARD) - return FALSE; - return TRUE; -} - bool32 AI_CanGetFrostbite(u32 battler, u32 ability) { if (ability == ABILITY_MAGMA_ARMOR @@ -2883,7 +2820,7 @@ bool32 AI_CanGetFrostbite(u32 battler, u32 ability) bool32 ShouldBurnSelf(u32 battler, u32 ability) { - if (AI_CanBeBurned(battler, ability) && ( + if (CanBeBurned(battler, ability) && ( ability == ABILITY_QUICK_FEET || ability == ABILITY_HEATPROOF || ability == ABILITY_MAGIC_GUARD @@ -2897,7 +2834,7 @@ bool32 ShouldBurnSelf(u32 battler, u32 ability) bool32 AI_CanBurn(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 battlerAtkPartner, u32 move, u32 partnerMove) { - if (!AI_CanBeBurned(battlerDef, defAbility) + if (!CanBeBurned(battlerDef, defAbility) || AI_DATA->effectiveness[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex] == AI_EFFECTIVENESS_x0 || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) || PartnerMoveEffectIsStatusSameTarget(battlerAtkPartner, battlerDef, partnerMove)) @@ -3812,24 +3749,28 @@ 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)) - return FALSE; //don't use z move on partner - if (gBattleStruct->zmove.used[battlerAtk]) - return FALSE; //cant use z move twice + return FALSE; // don't use z move on partner + if (HasTrainerUsedGimmick(battlerAtk, GIMMICK_Z_MOVE)) + return FALSE; // can't use z move twice if (IsViableZMove(battlerAtk, chosenMove)) { u8 effectiveness; + u32 zMove = GetUsableZMove(battlerAtk, chosenMove); struct SimulatedDamage dmg; if (gBattleMons[battlerDef].ability == ABILITY_DISGUISE + && !gMovesInfo[zMove].ignoresTargetAbility && (gBattleMons[battlerDef].species == SPECIES_MIMIKYU_DISGUISED || gBattleMons[battlerDef].species == SPECIES_MIMIKYU_TOTEM_DISGUISED)) return FALSE; // Don't waste a Z-Move busting disguise - if (gBattleMons[battlerDef].ability == ABILITY_ICE_FACE && gBattleMons[battlerDef].species == SPECIES_EISCUE_ICE_FACE && IS_MOVE_PHYSICAL(chosenMove)) + if (gBattleMons[battlerDef].ability == ABILITY_ICE_FACE + && !gMovesInfo[zMove].ignoresTargetAbility + && gBattleMons[battlerDef].species == SPECIES_EISCUE_ICE_FACE && IS_MOVE_PHYSICAL(chosenMove)) return FALSE; // Don't waste a Z-Move busting Ice Face - if (IS_MOVE_STATUS(chosenMove) && !IS_MOVE_STATUS(gBattleStruct->zmove.chosenZMove)) + if (IS_MOVE_STATUS(chosenMove) && !IS_MOVE_STATUS(zMove)) return FALSE; - else if (!IS_MOVE_STATUS(chosenMove) && IS_MOVE_STATUS(gBattleStruct->zmove.chosenZMove)) + else if (!IS_MOVE_STATUS(chosenMove) && IS_MOVE_STATUS(zMove)) return FALSE; dmg = AI_CalcDamageSaveBattlers(chosenMove, battlerAtk, battlerDef, &effectiveness, FALSE, DMG_ROLL_DEFAULT); diff --git a/src/battle_anim.c b/src/battle_anim.c index 33691a2e95..f53e424a4d 100644 --- a/src/battle_anim.c +++ b/src/battle_anim.c @@ -18,6 +18,7 @@ #include "sprite.h" #include "task.h" #include "test_runner.h" +#include "test/battle.h" #include "constants/battle_anim.h" #include "constants/moves.h" @@ -239,6 +240,9 @@ void LaunchBattleAnimation(u32 animType, u32 animId) TestRunner_Battle_RecordAnimation(animType, animId); // Play Transform and Ally Switch even in Headless as these move animations also change mon data. if (gTestRunnerHeadless + #if TESTING // Because gBattleTestRunnerState is not seen outside of test env. + && !gBattleTestRunnerState->forceMoveAnim + #endif // TESTING && !(animType == ANIM_TYPE_MOVE && (animId == MOVE_TRANSFORM || animId == MOVE_ALLY_SWITCH))) { gAnimScriptCallback = Nop; @@ -446,7 +450,7 @@ static u8 GetBattleAnimMoveTargets(u8 battlerArgIndex, u8 *targets) u32 i; u32 ignoredTgt = gBattlerAttacker; u32 target = GetBattlerMoveTargetType(gBattleAnimAttacker, gAnimMoveIndex); - + switch (battlerAnimId) { case ANIM_ATTACKER: @@ -458,7 +462,7 @@ static u8 GetBattleAnimMoveTargets(u8 battlerArgIndex, u8 *targets) ignoredTgt = gBattlerAttacker; break; } - + switch (target) { case MOVE_TARGET_FOES_AND_ALLY: diff --git a/src/battle_anim_effects_1.c b/src/battle_anim_effects_1.c index 7ce0e0c36c..d47c8aeebb 100644 --- a/src/battle_anim_effects_1.c +++ b/src/battle_anim_effects_1.c @@ -6557,8 +6557,8 @@ static void ReloadBattlerSprites(u32 battler, struct Pokemon *party) BattleLoadMonSpriteGfx(mon, battler); CreateBattlerSprite(battler); UpdateHealthboxAttribute(gHealthboxSpriteIds[battler], mon, HEALTHBOX_ALL); - // If battler is mega evolved / primal reversed, hide the sprite until the move animation finishes. - MegaIndicator_SetVisibilities(gHealthboxSpriteIds[battler], TRUE); + // If battler has an indicator for a gimmick, hide the sprite until the move animation finishes. + UpdateIndicatorVisibilityAndType(gHealthboxSpriteIds[battler], TRUE); // Try to recreate shadow sprite if (gBattleSpritesDataPtr->healthBoxesData[battler].shadowSpriteId < MAX_SPRITES) diff --git a/src/battle_controller_opponent.c b/src/battle_controller_opponent.c index 1622b94e9d..a5dc6f3af1 100644 --- a/src/battle_controller_opponent.c +++ b/src/battle_controller_opponent.c @@ -552,22 +552,17 @@ static void OpponentHandleChooseMove(u32 battler) if (gAbsentBattlerFlags & gBitTable[gBattlerTarget]) gBattlerTarget = GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT); } - if (ShouldUseZMove(battler, gBattlerTarget, chosenMove)) - QueueZMove(battler, chosenMove); - // If opponent can Mega Evolve, do it. - if (CanMegaEvolve(battler)) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8)); - // If opponent can Ultra Burst, do it. - else if (CanUltraBurst(battler)) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_ULTRA_BURST) | (gBattlerTarget << 8)); - // If opponent can Dynamax and is allowed in the partydata, do it. - else if (CanDynamax(battler) && AI_DATA->shouldDynamax[battler]) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_DYNAMAX) | (gBattlerTarget << 8)); - // If opponent can Terastal and is allowed in the partydata, do it. - else if (CanTerastallize(battler) && AI_DATA->shouldTerastal[battler]) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_TERASTAL) | (gBattlerTarget << 8)); + // If opponent can and should use a gimmick (considering trainer data), do it + if (gBattleStruct->gimmick.usableGimmick[battler] != GIMMICK_NONE + && !(gBattleStruct->gimmick.usableGimmick[battler] == GIMMICK_Z_MOVE + && !ShouldUseZMove(battler, gBattlerTarget, moveInfo->moves[chosenMoveId]))) + { + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_GIMMICK) | (gBattlerTarget << 8)); + } else + { BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (gBattlerTarget << 8)); + } } break; } diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index 5bf12e2cc7..6dcea9eb98 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -9,6 +9,7 @@ #include "battle_setup.h" #include "battle_tv.h" #include "battle_z_move.h" +#include "battle_gimmick.h" #include "bg.h" #include "data.h" #include "item.h" @@ -162,11 +163,6 @@ static void (*const sPlayerBufferCommands[CONTROLLER_CMDS_COUNT])(u32 battler) = [CONTROLLER_TERMINATOR_NOP] = BtlController_TerminatorNop }; -static EWRAM_DATA bool8 sDescriptionSubmenu = 0; - -static EWRAM_DATA bool8 sAckBallUseBtn = FALSE; -static EWRAM_DATA bool8 sBallSwapped = FALSE; - void SetControllerToPlayer(u32 battler) { gBattlerControllerEndFuncs[battler] = PlayerBufferExecCompleted; @@ -264,61 +260,63 @@ static void HandleInputChooseAction(u32 battler) else gPlayerDpadHoldFrames = 0; -#if B_LAST_USED_BALL == TRUE && B_LAST_USED_BALL_CYCLE == TRUE - if (!gLastUsedBallMenuPresent) + if (B_LAST_USED_BALL == TRUE && B_LAST_USED_BALL_CYCLE == TRUE) { - sAckBallUseBtn = FALSE; + if (!gLastUsedBallMenuPresent) + { + gBattleStruct->ackBallUseBtn = FALSE; + } + else if (JOY_NEW(B_LAST_USED_BALL_BUTTON)) + { + gBattleStruct->ackBallUseBtn = TRUE; + gBattleStruct->ballSwapped = FALSE; + ArrowsChangeColorLastBallCycle(TRUE); + } + + if (gBattleStruct->ackBallUseBtn) + { + if (JOY_HELD(B_LAST_USED_BALL_BUTTON) && (JOY_NEW(DPAD_DOWN) || JOY_NEW(DPAD_RIGHT))) + { + bool32 sameBall = FALSE; + u32 nextBall = GetNextBall(gBallToDisplay); + gBattleStruct->ballSwapped = TRUE; + if (gBallToDisplay == nextBall) + sameBall = TRUE; + else + gBallToDisplay = nextBall; + SwapBallToDisplay(sameBall); + PlaySE(SE_SELECT); + } + else if (JOY_HELD(B_LAST_USED_BALL_BUTTON) && (JOY_NEW(DPAD_UP) || JOY_NEW(DPAD_LEFT))) + { + bool32 sameBall = FALSE; + u32 prevBall = GetPrevBall(gBallToDisplay); + gBattleStruct->ballSwapped = TRUE; + if (gBallToDisplay == prevBall) + sameBall = TRUE; + else + gBallToDisplay = prevBall; + SwapBallToDisplay(sameBall); + PlaySE(SE_SELECT); + } + else if (JOY_NEW(B_BUTTON) || (!JOY_HELD(B_LAST_USED_BALL_BUTTON) && gBattleStruct->ballSwapped)) + { + gBattleStruct->ackBallUseBtn = FALSE; + gBattleStruct->ballSwapped = FALSE; + ArrowsChangeColorLastBallCycle(FALSE); + } + else if (!JOY_HELD(B_LAST_USED_BALL_BUTTON) && CanThrowLastUsedBall()) + { + gBattleStruct->ackBallUseBtn = FALSE; + PlaySE(SE_SELECT); + ArrowsChangeColorLastBallCycle(FALSE); + TryHideLastUsedBall(); + BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_THROW_BALL, 0); + PlayerBufferExecCompleted(battler); + } + return; + } } - else if (JOY_NEW(B_LAST_USED_BALL_BUTTON)) - { - sAckBallUseBtn = TRUE; - sBallSwapped = FALSE; - ArrowsChangeColorLastBallCycle(TRUE); - } - if (sAckBallUseBtn) - { - if (JOY_HELD(B_LAST_USED_BALL_BUTTON) && (JOY_NEW(DPAD_DOWN) || JOY_NEW(DPAD_RIGHT))) - { - bool8 sameBall = FALSE; - u16 nextBall = GetNextBall(gBallToDisplay); - sBallSwapped = TRUE; - if (gBallToDisplay == nextBall) - sameBall = TRUE; - else - gBallToDisplay = nextBall; - SwapBallToDisplay(sameBall); - PlaySE(SE_SELECT); - } - else if (JOY_HELD(B_LAST_USED_BALL_BUTTON) && (JOY_NEW(DPAD_UP) || JOY_NEW(DPAD_LEFT))) - { - bool8 sameBall = FALSE; - u16 prevBall = GetPrevBall(gBallToDisplay); - sBallSwapped = TRUE; - if (gBallToDisplay == prevBall) - sameBall = TRUE; - else - gBallToDisplay = prevBall; - SwapBallToDisplay(sameBall); - PlaySE(SE_SELECT); - } - else if (JOY_NEW(B_BUTTON) || (!JOY_HELD(B_LAST_USED_BALL_BUTTON) && sBallSwapped)) - { - sAckBallUseBtn = FALSE; - sBallSwapped = FALSE; - ArrowsChangeColorLastBallCycle(FALSE); - } - else if (!JOY_HELD(B_LAST_USED_BALL_BUTTON) && CanThrowLastUsedBall()) - { - sAckBallUseBtn = FALSE; - PlaySE(SE_SELECT); - ArrowsChangeColorLastBallCycle(FALSE); - TryHideLastUsedBall(); - BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_THROW_BALL, 0); - PlayerBufferExecCompleted(battler); - } - return; - } -#endif if (JOY_NEW(A_BUTTON)) { @@ -413,22 +411,19 @@ static void HandleInputChooseAction(u32 battler) { SwapHpBarsWithHpText(); } -#if DEBUG_BATTLE_MENU == TRUE - else if (JOY_NEW(SELECT_BUTTON)) + else if (DEBUG_BATTLE_MENU == TRUE && JOY_NEW(SELECT_BUTTON)) { BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_DEBUG, 0); PlayerBufferExecCompleted(battler); } -#endif -#if B_LAST_USED_BALL == TRUE && B_LAST_USED_BALL_CYCLE == FALSE - else if (JOY_NEW(B_LAST_USED_BALL_BUTTON) && CanThrowLastUsedBall()) + else if (B_LAST_USED_BALL == TRUE && B_LAST_USED_BALL_CYCLE == FALSE + && JOY_NEW(B_LAST_USED_BALL_BUTTON) && CanThrowLastUsedBall()) { PlaySE(SE_SELECT); TryHideLastUsedBall(); BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_THROW_BALL, 0); PlayerBufferExecCompleted(battler); } -#endif } static void HandleInputChooseTarget(u32 battler) @@ -454,19 +449,13 @@ static void HandleInputChooseTarget(u32 battler) { PlaySE(SE_SELECT); gSprites[gBattlerSpriteIds[gMultiUsePlayerCursor]].callback = SpriteCB_HideAsMoveTarget; - if (gBattleStruct->mega.playerSelect) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_MEGA_EVOLUTION | (gMultiUsePlayerCursor << 8)); - else if (gBattleStruct->burst.playerSelect) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_ULTRA_BURST | (gMultiUsePlayerCursor << 8)); - else if (gBattleStruct->dynamax.playerSelect) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_DYNAMAX | (gMultiUsePlayerCursor << 8)); - else if (gBattleStruct->tera.playerSelect) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_TERASTAL | (gMultiUsePlayerCursor << 8)); + if (gBattleStruct->gimmick.playerSelect) + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_GIMMICK | (gMultiUsePlayerCursor << 8)); else BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | (gMultiUsePlayerCursor << 8)); EndBounceEffect(gMultiUsePlayerCursor, BOUNCE_HEALTHBOX); TryHideLastUsedBall(); - HideTriggerSprites(); + HideGimmickTriggerSprite(); PlayerBufferExecCompleted(battler); } else if (JOY_NEW(B_BUTTON) || gPlayerDpadHoldFrames > 59) @@ -618,17 +607,11 @@ static void HandleInputShowEntireFieldTargets(u32 battler) { PlaySE(SE_SELECT); HideAllTargets(); - if (gBattleStruct->mega.playerSelect) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_MEGA_EVOLUTION | (gMultiUsePlayerCursor << 8)); - else if (gBattleStruct->burst.playerSelect) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_ULTRA_BURST | (gMultiUsePlayerCursor << 8)); - else if (gBattleStruct->dynamax.playerSelect) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_DYNAMAX | (gMultiUsePlayerCursor << 8)); - else if (gBattleStruct->tera.playerSelect) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_TERASTAL | (gMultiUsePlayerCursor << 8)); + if (gBattleStruct->gimmick.playerSelect) + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_GIMMICK | (gMultiUsePlayerCursor << 8)); else BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | (gMultiUsePlayerCursor << 8)); - HideTriggerSprites(); + HideGimmickTriggerSprite(); PlayerBufferExecCompleted(battler); } else if (JOY_NEW(B_BUTTON) || gPlayerDpadHoldFrames > 59) @@ -652,17 +635,11 @@ static void HandleInputShowTargets(u32 battler) { PlaySE(SE_SELECT); HideShownTargets(battler); - if (gBattleStruct->mega.playerSelect) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_MEGA_EVOLUTION | (gMultiUsePlayerCursor << 8)); - else if (gBattleStruct->burst.playerSelect) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_ULTRA_BURST | (gMultiUsePlayerCursor << 8)); - else if (gBattleStruct->dynamax.playerSelect) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_DYNAMAX | (gMultiUsePlayerCursor << 8)); - else if (gBattleStruct->tera.playerSelect) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_TERASTAL | (gMultiUsePlayerCursor << 8)); + if (gBattleStruct->gimmick.playerSelect) + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_GIMMICK | (gMultiUsePlayerCursor << 8)); else BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | (gMultiUsePlayerCursor << 8)); - HideTriggerSprites(); + HideGimmickTriggerSprite(); TryHideLastUsedBall(); PlayerBufferExecCompleted(battler); } @@ -696,7 +673,7 @@ static void HandleInputChooseMove(u32 battler) else gPlayerDpadHoldFrames = 0; - if (JOY_NEW(A_BUTTON) && !sDescriptionSubmenu) + if (JOY_NEW(A_BUTTON) && !gBattleStruct->descriptionSubmenu) { PlaySE(SE_SELECT); @@ -704,16 +681,13 @@ static void HandleInputChooseMove(u32 battler) if (gBattleStruct->zmove.viewing) { - u16 chosenMove = moveInfo->moves[gMoveSelectionCursor[battler]]; - - QueueZMove(battler, chosenMove); gBattleStruct->zmove.viewing = FALSE; if (gMovesInfo[moveInfo->moves[gMoveSelectionCursor[battler]]].category != DAMAGE_CATEGORY_STATUS) moveTarget = MOVE_TARGET_SELECTED; //damaging z moves always have selected target } // Status moves turn into Max Guard when Dynamaxed, targets user. - if ((IsDynamaxed(battler) || gBattleStruct->dynamax.playerSelect)) + if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX || IsGimmickSelected(battler, GIMMICK_DYNAMAX)) moveTarget = gMovesInfo[GetMaxMove(battler, moveInfo->moves[gMoveSelectionCursor[battler]])].target; if (moveTarget & MOVE_TARGET_USER) @@ -769,17 +743,11 @@ static void HandleInputChooseMove(u32 battler) { case 0: default: - if (gBattleStruct->mega.playerSelect) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_MEGA_EVOLUTION | (gMultiUsePlayerCursor << 8)); - else if (gBattleStruct->burst.playerSelect) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_ULTRA_BURST | (gMultiUsePlayerCursor << 8)); - else if (gBattleStruct->dynamax.playerSelect) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_DYNAMAX | (gMultiUsePlayerCursor << 8)); - else if (gBattleStruct->tera.playerSelect) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_TERASTAL | (gMultiUsePlayerCursor << 8)); + if (gBattleStruct->gimmick.playerSelect) + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_GIMMICK | (gMultiUsePlayerCursor << 8)); else BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | (gMultiUsePlayerCursor << 8)); - HideTriggerSprites(); + HideGimmickTriggerSprite(); TryHideLastUsedBall(); PlayerBufferExecCompleted(battler); break; @@ -803,22 +771,18 @@ static void HandleInputChooseMove(u32 battler) break; } } - else if ((JOY_NEW(B_BUTTON) || gPlayerDpadHoldFrames > 59) && !sDescriptionSubmenu) + else if ((JOY_NEW(B_BUTTON) || gPlayerDpadHoldFrames > 59) && !gBattleStruct->descriptionSubmenu) { PlaySE(SE_SELECT); + gBattleStruct->gimmick.playerSelect = FALSE; if (gBattleStruct->zmove.viewing) { ReloadMoveNames(battler); } else { - gBattleStruct->mega.playerSelect = FALSE; - gBattleStruct->burst.playerSelect = FALSE; - gBattleStruct->dynamax.playerSelect = FALSE; - gBattleStruct->tera.playerSelect = FALSE; - gBattleStruct->zmove.viable = FALSE; BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, 0xFFFF); - HideTriggerSprites(); + HideGimmickTriggerSprite(); PlayerBufferExecCompleted(battler); } } @@ -832,9 +796,9 @@ static void HandleInputChooseMove(u32 battler) MoveSelectionCreateCursorAt(gMoveSelectionCursor[battler], 0); MoveSelectionDisplayPpNumber(battler); MoveSelectionDisplayMoveType(battler); - if (sDescriptionSubmenu) + if (gBattleStruct->descriptionSubmenu) MoveSelectionDisplayMoveDescription(battler); - TryChangeZIndicator(battler, gMoveSelectionCursor[battler]); + TryChangeZTrigger(battler, gMoveSelectionCursor[battler]); } } else if (JOY_NEW(DPAD_RIGHT) && !gBattleStruct->zmove.viewing) @@ -848,9 +812,9 @@ static void HandleInputChooseMove(u32 battler) MoveSelectionCreateCursorAt(gMoveSelectionCursor[battler], 0); MoveSelectionDisplayPpNumber(battler); MoveSelectionDisplayMoveType(battler); - if (sDescriptionSubmenu) + if (gBattleStruct->descriptionSubmenu) MoveSelectionDisplayMoveDescription(battler); - TryChangeZIndicator(battler, gMoveSelectionCursor[battler]); + TryChangeZTrigger(battler, gMoveSelectionCursor[battler]); } } else if (JOY_NEW(DPAD_UP) && !gBattleStruct->zmove.viewing) @@ -863,9 +827,9 @@ static void HandleInputChooseMove(u32 battler) MoveSelectionCreateCursorAt(gMoveSelectionCursor[battler], 0); MoveSelectionDisplayPpNumber(battler); MoveSelectionDisplayMoveType(battler); - if (sDescriptionSubmenu) + if (gBattleStruct->descriptionSubmenu) MoveSelectionDisplayMoveDescription(battler); - TryChangeZIndicator(battler, gMoveSelectionCursor[battler]); + TryChangeZTrigger(battler, gMoveSelectionCursor[battler]); } } else if (JOY_NEW(DPAD_DOWN) && !gBattleStruct->zmove.viewing) @@ -879,12 +843,12 @@ static void HandleInputChooseMove(u32 battler) MoveSelectionCreateCursorAt(gMoveSelectionCursor[battler], 0); MoveSelectionDisplayPpNumber(battler); MoveSelectionDisplayMoveType(battler); - if (sDescriptionSubmenu) + if (gBattleStruct->descriptionSubmenu) MoveSelectionDisplayMoveDescription(battler); - TryChangeZIndicator(battler, gMoveSelectionCursor[battler]); + TryChangeZTrigger(battler, gMoveSelectionCursor[battler]); } } - else if (JOY_NEW(SELECT_BUTTON) && !gBattleStruct->zmove.viewing && !sDescriptionSubmenu) + else if (JOY_NEW(SELECT_BUTTON) && !gBattleStruct->zmove.viewing && !gBattleStruct->descriptionSubmenu) { if (gNumberOfMovesToChoose > 1 && !(gBattleTypeFlags & BATTLE_TYPE_LINK)) { @@ -900,11 +864,11 @@ static void HandleInputChooseMove(u32 battler) gBattlerControllerFuncs[battler] = HandleMoveSwitching; } } - else if (sDescriptionSubmenu) + else if (gBattleStruct->descriptionSubmenu) { - if (JOY_NEW(L_BUTTON) || JOY_NEW(A_BUTTON) || JOY_NEW(B_BUTTON)) + if (JOY_NEW(B_MOVE_DESCRIPTION_BUTTON) || JOY_NEW(A_BUTTON) || JOY_NEW(B_BUTTON)) { - sDescriptionSubmenu = FALSE; + gBattleStruct->descriptionSubmenu = FALSE; if (gCategoryIconSpriteId != 0xFF) { DestroySprite(&gSprites[gCategoryIconSpriteId]); @@ -919,48 +883,18 @@ static void HandleInputChooseMove(u32 battler) MoveSelectionDisplayMoveType(battler); } } - else if (JOY_NEW(L_BUTTON)) + else if (JOY_NEW(B_MOVE_DESCRIPTION_BUTTON) && B_MOVE_DESCRIPTION_BUTTON != B_LAST_USED_BALL_BUTTON) { - sDescriptionSubmenu = TRUE; + gBattleStruct->descriptionSubmenu = TRUE; MoveSelectionDisplayMoveDescription(battler); } else if (JOY_NEW(START_BUTTON)) { - if (CanMegaEvolve(battler)) + if (gBattleStruct->gimmick.usableGimmick[battler] != GIMMICK_NONE) { - gBattleStruct->mega.playerSelect ^= 1; - ChangeMegaTriggerSprite(gBattleStruct->mega.triggerSpriteId, gBattleStruct->mega.playerSelect); - PlaySE(SE_SELECT); - } - else if (CanUltraBurst(battler)) - { - gBattleStruct->burst.playerSelect ^= 1; - ChangeBurstTriggerSprite(gBattleStruct->burst.triggerSpriteId, gBattleStruct->burst.playerSelect); - PlaySE(SE_SELECT); - } - else if (gBattleStruct->zmove.viable) - { - // show z move name / info - //TODO: brighten z move symbol - PlaySE(SE_SELECT); - if (!gBattleStruct->zmove.viewing) - MoveSelectionDisplayZMove(gBattleStruct->zmove.chosenZMove, battler); - else - ReloadMoveNames(battler); - } - else if (CanDynamax(battler)) - { - gBattleStruct->dynamax.playerSelect ^= 1; - MoveSelectionDisplayMoveNames(battler); - MoveSelectionCreateCursorAt(gMoveSelectionCursor[battler], 0); - ChangeDynamaxTriggerSprite(gBattleStruct->dynamax.triggerSpriteId, gBattleStruct->dynamax.playerSelect); - PlaySE(SE_SELECT); - } - else if (CanTerastallize(battler)) - { - gBattleStruct->tera.playerSelect ^= 1; - ChangeTeraTriggerSprite(gBattleStruct->tera.triggerSpriteId, gBattleStruct->tera.playerSelect); - MoveSelectionDisplayMoveType(battler); // For Tera Blast / Tera Starstorm + gBattleStruct->gimmick.playerSelect ^= 1; + ReloadMoveNames(battler); + ChangeGimmickTriggerSprite(gBattleStruct->gimmick.triggerSpriteId, gBattleStruct->gimmick.playerSelect); PlaySE(SE_SELECT); } } @@ -968,16 +902,20 @@ static void HandleInputChooseMove(u32 battler) static void ReloadMoveNames(u32 battler) { - gBattleStruct->mega.playerSelect = FALSE; - gBattleStruct->burst.playerSelect = FALSE; - gBattleStruct->dynamax.playerSelect = FALSE; - gBattleStruct->tera.playerSelect = FALSE; - gBattleStruct->zmove.viewing = FALSE; - MoveSelectionDestroyCursorAt(battler); - MoveSelectionDisplayMoveNames(battler); - MoveSelectionCreateCursorAt(gMoveSelectionCursor[battler], 0); - MoveSelectionDisplayPpNumber(battler); - MoveSelectionDisplayMoveType(battler); + if (gBattleStruct->zmove.viable && !gBattleStruct->zmove.viewing) + { + struct ChooseMoveStruct *moveInfo = (struct ChooseMoveStruct *)(&gBattleResources->bufferA[battler][4]); + MoveSelectionDisplayZMove(GetUsableZMove(battler, moveInfo->moves[gMoveSelectionCursor[battler]]), battler); + } + else + { + gBattleStruct->zmove.viewing = FALSE; + MoveSelectionDestroyCursorAt(battler); + MoveSelectionDisplayMoveNames(battler); + MoveSelectionCreateCursorAt(gMoveSelectionCursor[battler], 0); + MoveSelectionDisplayPpNumber(battler); + MoveSelectionDisplayMoveType(battler); + } } static u32 UNUSED HandleMoveInputUnused(u32 battler) @@ -1130,7 +1068,7 @@ static void HandleMoveSwitching(u32 battler) MoveSelectionDisplayPpString(battler); MoveSelectionDisplayPpNumber(battler); MoveSelectionDisplayMoveType(battler); - GetUsableZMoves(battler, moveInfo->moves); + AssignUsableZMoves(battler, moveInfo->moves); } else if (JOY_NEW(B_BUTTON | SELECT_BUTTON)) { @@ -1501,7 +1439,7 @@ static void Task_GiveExpToMon(u8 taskId) CalculateMonStats(mon); // Reapply Dynamax HP multiplier after stats are recalculated. - if (IsDynamaxed(battler) && monId == gBattlerPartyIndexes[battler]) + if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX && monId == gBattlerPartyIndexes[battler]) { ApplyDynamaxHPMultiplier(battler, mon); gBattleMons[battler].hp = gBattleStruct->dynamax.levelUpHP; @@ -1585,7 +1523,7 @@ static void Task_GiveExpWithExpBar(u8 taskId) CalculateMonStats(&gPlayerParty[monId]); // Reapply Dynamax HP multiplier after stats are recalculated. - if (IsDynamaxed(battler) && monId == gBattlerPartyIndexes[battler]) + if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX && monId == gBattlerPartyIndexes[battler]) { ApplyDynamaxHPMultiplier(battler, &gPlayerParty[monId]); gBattleMons[battler].hp = gBattleStruct->dynamax.levelUpHP; @@ -1738,8 +1676,7 @@ static void MoveSelectionDisplayMoveNames(u32 battler) for (i = 0; i < MAX_MON_MOVES; i++) { MoveSelectionDestroyCursorAt(i); - if ((gBattleStruct->dynamax.playerSelect && CanDynamax(battler)) - || IsDynamaxed(battler)) + if (IsGimmickSelected(battler, GIMMICK_DYNAMAX) || GetActiveGimmick(battler) == GIMMICK_DYNAMAX) StringCopy(gDisplayedStringBattle, GetMoveName(GetMaxMove(battler, moveInfo->moves[i]))); else StringCopy(gDisplayedStringBattle, GetMoveName(moveInfo->moves[i])); @@ -1786,7 +1723,7 @@ static void MoveSelectionDisplayMoveType(u32 battler) if (moveInfo->moves[gMoveSelectionCursor[battler]] == MOVE_TERA_BLAST) { - if (gBattleStruct->tera.playerSelect || IsTerastallized(battler)) + if (IsGimmickSelected(battler, GIMMICK_TERA) || GetActiveGimmick(battler) == GIMMICK_TERA) type = GetBattlerTeraType(battler); } else if (moveInfo->moves[gMoveSelectionCursor[battler]] == MOVE_IVY_CUDGEL) @@ -1798,10 +1735,16 @@ static void MoveSelectionDisplayMoveType(u32 battler) || speciesId == SPECIES_OGERPON_CORNERSTONE_MASK || speciesId == SPECIES_OGERPON_CORNERSTONE_MASK_TERA) type = gBattleMons[battler].type2; } + // Max Guard is a Normal-type move + else if (gMovesInfo[moveInfo->moves[gMoveSelectionCursor[battler]]].category == DAMAGE_CATEGORY_STATUS + && (GetActiveGimmick(battler) == GIMMICK_DYNAMAX || IsGimmickSelected(battler, GIMMICK_DYNAMAX))) + { + type = TYPE_NORMAL; + } else if (moveInfo->moves[gMoveSelectionCursor[battler]] == MOVE_TERA_STARSTORM) { if (gBattleMons[battler].species == SPECIES_TERAPAGOS_STELLAR - || (gBattleStruct->tera.playerSelect && gBattleMons[battler].species == SPECIES_TERAPAGOS_TERASTAL)) + || (IsGimmickSelected(battler, GIMMICK_TERA) && gBattleMons[battler].species == SPECIES_TERAPAGOS_TERASTAL)) type = TYPE_STELLAR; } @@ -1817,7 +1760,7 @@ static void MoveSelectionDisplayMoveDescription(u32 battler) u16 pwr = gMovesInfo[move].power; u16 acc = gMovesInfo[move].accuracy; u8 cat = gMovesInfo[move].category; - + u8 pwr_num[3], acc_num[3]; u8 cat_desc[7] = _("CAT: "); u8 pwr_desc[7] = _("PWR: "); @@ -2155,32 +2098,16 @@ static void PlayerHandleChooseMove(u32 battler) struct ChooseMoveStruct *moveInfo = (struct ChooseMoveStruct *)(&gBattleResources->bufferA[battler][4]); InitMoveSelectionsVarsAndStrings(battler); - gBattleStruct->mega.playerSelect = FALSE; - gBattleStruct->burst.playerSelect = FALSE; - gBattleStruct->dynamax.playerSelect = FALSE; - gBattleStruct->tera.playerSelect = FALSE; - if (!IsMegaTriggerSpriteActive()) - gBattleStruct->mega.triggerSpriteId = 0xFF; - if (CanMegaEvolve(battler)) - CreateMegaTriggerSprite(battler, 0); - if (!IsBurstTriggerSpriteActive()) - gBattleStruct->burst.triggerSpriteId = 0xFF; - if (CanUltraBurst(battler)) - CreateBurstTriggerSprite(battler, 0); - if (!IsDynamaxTriggerSpriteActive()) - gBattleStruct->dynamax.triggerSpriteId = 0xFF; - if (CanDynamax(battler)) - CreateDynamaxTriggerSprite(battler, 0); - if (!IsZMoveTriggerSpriteActive()) - gBattleStruct->zmove.triggerSpriteId = 0xFF; - if (!IsTeraTriggerSpriteActive()) - gBattleStruct->tera.triggerSpriteId = 0xFF; - if (CanTerastallize(battler)) - CreateTeraTriggerSprite(battler, 0); + gBattleStruct->gimmick.playerSelect = FALSE; + + AssignUsableZMoves(battler, moveInfo->moves); + gBattleStruct->zmove.viable = (gBattleStruct->zmove.possibleZMoves[battler] & gBitTable[gMoveSelectionCursor[battler]]) != 0; + + if (!IsGimmickTriggerSpriteActive()) + gBattleStruct->gimmick.triggerSpriteId = 0xFF; + if (!(gBattleStruct->gimmick.usableGimmick[battler] == GIMMICK_Z_MOVE && !gBattleStruct->zmove.viable)) + CreateGimmickTriggerSprite(battler); - GetUsableZMoves(battler, moveInfo->moves); - gBattleStruct->zmove.viable = IsZMoveUsable(battler, gMoveSelectionCursor[battler]); - CreateZMoveTriggerSprite(battler, gBattleStruct->zmove.viable); gBattlerControllerFuncs[battler] = HandleChooseMoveAfterDma3; } } diff --git a/src/battle_controller_player_partner.c b/src/battle_controller_player_partner.c index b817adf097..2dffd4f702 100644 --- a/src/battle_controller_player_partner.c +++ b/src/battle_controller_player_partner.c @@ -368,17 +368,17 @@ static void PlayerPartnerHandleChooseMove(u32 battler) if (gAbsentBattlerFlags & gBitTable[gBattlerTarget]) gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT); } - - if (ShouldUseZMove(battler, gBattlerTarget, moveInfo->moves[chosenMoveId])) - QueueZMove(battler, moveInfo->moves[chosenMoveId]); - - // If partner can mega evolve, do it. - if (CanMegaEvolve(battler)) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8)); - else if (CanUltraBurst(battler)) - BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_ULTRA_BURST) | (gBattlerTarget << 8)); + // If partner can and should use a gimmick (considering trainer data), do it + if (gBattleStruct->gimmick.usableGimmick[battler] != GIMMICK_NONE + && !(gBattleStruct->gimmick.usableGimmick[battler] == GIMMICK_Z_MOVE + && !ShouldUseZMove(battler, gBattlerTarget, moveInfo->moves[chosenMoveId]))) + { + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_GIMMICK) | (gBattlerTarget << 8)); + } else + { BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (gBattlerTarget << 8)); + } } PlayerPartnerBufferExecCompleted(battler); diff --git a/src/battle_controller_recorded_player.c b/src/battle_controller_recorded_player.c index 53860990c4..3a9ca1ec00 100644 --- a/src/battle_controller_recorded_player.c +++ b/src/battle_controller_recorded_player.c @@ -517,7 +517,7 @@ static void RecordedPlayerHandleIntroTrainerBallThrow(u32 battler) else trainerPicId = gSaveBlock2Ptr->playerGender + TRAINER_BACK_PIC_BRENDAN; - trainerPal = gTrainerSprites[trainerPicId].palette.data; + trainerPal = gTrainerBacksprites[trainerPicId].palette.data; BtlController_HandleIntroTrainerBallThrow(battler, 0xD6F9, trainerPal, 24, Intro_TryShinyAnimShowHealthbox); } diff --git a/src/battle_dynamax.c b/src/battle_dynamax.c index 19b9d8a51d..9e00486bc4 100644 --- a/src/battle_dynamax.c +++ b/src/battle_dynamax.c @@ -5,6 +5,7 @@ #include "battle_interface.h" #include "battle_scripts.h" #include "battle_script_commands.h" +#include "battle_gimmick.h" #include "data.h" #include "event_data.h" #include "graphics.h" @@ -22,7 +23,7 @@ #include "constants/items.h" #include "constants/moves.h" -static u8 GetMaxPowerTier(u16 move); +static u8 GetMaxPowerTier(u32 move); struct GMaxMove { @@ -69,35 +70,21 @@ static const struct GMaxMove sGMaxMoveTable[] = {SPECIES_URSHIFU_RAPID_STRIKE_STYLE_GIGANTAMAX, TYPE_WATER, MOVE_G_MAX_RAPID_FLOW}, }; -// forward declarations -static void SpriteCb_DynamaxTrigger(struct Sprite *); - -// Returns whether a battler is Dynamaxed. -bool32 IsDynamaxed(u16 battlerId) -{ - if (gBattleStruct->dynamax.dynamaxed[battlerId] - /*|| IsRaidBoss(battlerId)*/) - return TRUE; - return FALSE; -} - // Returns whether a battler can Dynamax. -bool32 CanDynamax(u16 battlerId) +bool32 CanDynamax(u32 battler) { - u16 species = gBattleMons[battlerId].species; - u16 holdEffect = ItemId_GetHoldEffect(gBattleMons[battlerId].item); - - // Check if Dynamax battle flag is set. This needs to be defined in include/config/battle.h - #if B_FLAG_DYNAMAX_BATTLE != 0 - if (!FlagGet(B_FLAG_DYNAMAX_BATTLE)) - #endif - return FALSE; - + u16 species = gBattleMons[battler].species; + u16 holdEffect = GetBattlerHoldEffect(battler, FALSE); // Check if Player has a Dynamax Band. - if ((GetBattlerPosition(battlerId) == B_POSITION_PLAYER_LEFT || (!(gBattleTypeFlags & BATTLE_TYPE_MULTI) && GetBattlerPosition(battlerId) == B_POSITION_PLAYER_RIGHT)) - && !CheckBagHasItem(ITEM_DYNAMAX_BAND, 1)) - return FALSE; + if (!TESTING && (GetBattlerPosition(battler) == B_POSITION_PLAYER_LEFT + || (!(gBattleTypeFlags & BATTLE_TYPE_MULTI) && GetBattlerPosition(battler) == B_POSITION_PLAYER_RIGHT))) + { + if (!CheckBagHasItem(ITEM_DYNAMAX_BAND, 1)) + return FALSE; + if (B_FLAG_DYNAMAX_BATTLE == 0 || (B_FLAG_DYNAMAX_BATTLE != 0 && !FlagGet(B_FLAG_DYNAMAX_BATTLE))) + return FALSE; + } // Check if species isn't allowed to Dynamax. if (GET_BASE_SPECIES_ID(species) == SPECIES_ZACIAN @@ -105,18 +92,24 @@ bool32 CanDynamax(u16 battlerId) || GET_BASE_SPECIES_ID(species) == SPECIES_ETERNATUS) return FALSE; - // Cannot Dynamax if you can Mega Evolve or use a Z-Move - if (holdEffect == HOLD_EFFECT_MEGA_STONE || holdEffect == HOLD_EFFECT_Z_CRYSTAL) + // Check if Trainer has already Dynamaxed. + if (HasTrainerUsedGimmick(battler, GIMMICK_DYNAMAX)) return FALSE; - // Cannot Dynamax if your side has already or will Dynamax. - if (gBattleStruct->dynamax.alreadyDynamaxed[GetBattlerSide(battlerId)] - || gBattleStruct->dynamax.dynamaxed[BATTLE_PARTNER(battlerId)] - || gBattleStruct->dynamax.toDynamax & gBitTable[BATTLE_PARTNER(battlerId)]) + // Check if AI battler is intended to Dynamaxed. + if (!ShouldTrainerBattlerUseGimmick(battler, GIMMICK_DYNAMAX)) + return FALSE; + + // Check if battler has another gimmick active. + if (GetActiveGimmick(battler) != GIMMICK_NONE) + return FALSE; + + // Check if battler is holding a Z-Crystal or Mega Stone. + if (!TESTING && (holdEffect == HOLD_EFFECT_Z_CRYSTAL || holdEffect == HOLD_EFFECT_MEGA_STONE)) // tests make this check already return FALSE; // TODO: Cannot Dynamax in a Max Raid if you don't have Dynamax Energy. - // if (gBattleTypeFlags & BATTLE_TYPE_RAID && gBattleStruct->raid.dynamaxEnergy != battlerId) + // if (gBattleTypeFlags & BATTLE_TYPE_RAID && gBattleStruct->raid.dynamaxEnergy != battler) // return FALSE; // No checks failed, all set! @@ -124,10 +117,10 @@ bool32 CanDynamax(u16 battlerId) } // Returns whether a battler is transformed into a Gigantamax form. -bool32 IsGigantamaxed(u16 battlerId) +bool32 IsGigantamaxed(u32 battler) { - struct Pokemon *mon = &GetSideParty(GetBattlerSide(battlerId))[gBattlerPartyIndexes[battlerId]]; - if ((gSpeciesInfo[gBattleMons[battlerId].species].isGigantamax) && GetMonData(mon, MON_DATA_GIGANTAMAX_FACTOR)) + struct Pokemon *mon = &GetSideParty(GetBattlerSide(battler))[gBattlerPartyIndexes[battler]]; + if ((gSpeciesInfo[gBattleMons[battler].species].isGigantamax) && GetMonData(mon, MON_DATA_GIGANTAMAX_FACTOR)) return TRUE; return FALSE; } @@ -148,79 +141,80 @@ void ApplyDynamaxHPMultiplier(u32 battler, struct Pokemon* mon) } // Returns the non-Dynamax HP of a Pokemon. -u16 GetNonDynamaxHP(u16 battlerId) +u16 GetNonDynamaxHP(u32 battler) { - if (!IsDynamaxed(battlerId) || gBattleMons[battlerId].species == SPECIES_SHEDINJA) - return gBattleMons[battlerId].hp; + if (GetActiveGimmick(battler) != GIMMICK_DYNAMAX || gBattleMons[battler].species == SPECIES_SHEDINJA) + return gBattleMons[battler].hp; else { u16 mult = UQ_4_12(1.0/1.5); // placeholder - u16 hp = UQ_4_12_TO_INT((gBattleMons[battlerId].hp * mult) + UQ_4_12_ROUND); + u16 hp = UQ_4_12_TO_INT((gBattleMons[battler].hp * mult) + UQ_4_12_ROUND); return hp; } } // Returns the non-Dynamax Max HP of a Pokemon. -u16 GetNonDynamaxMaxHP(u32 battlerId) +u16 GetNonDynamaxMaxHP(u32 battler) { - if (!IsDynamaxed(battlerId) || gBattleMons[battlerId].species == SPECIES_SHEDINJA) - return gBattleMons[battlerId].maxHP; + if (GetActiveGimmick(battler) != GIMMICK_DYNAMAX || gBattleMons[battler].species == SPECIES_SHEDINJA) + return gBattleMons[battler].maxHP; else { u16 mult = UQ_4_12(1.0/1.5); // placeholder - u16 maxHP = UQ_4_12_TO_INT((gBattleMons[battlerId].maxHP * mult) + UQ_4_12_ROUND); + u16 maxHP = UQ_4_12_TO_INT((gBattleMons[battler].maxHP * mult) + UQ_4_12_ROUND); return maxHP; } } // Sets flags used for Dynamaxing and checks Gigantamax forms. -void PrepareBattlerForDynamax(u16 battlerId) +void ActivateDynamax(u32 battler) { - u8 side = GetBattlerSide(battlerId); - - gBattleStruct->dynamax.alreadyDynamaxed[side] = TRUE; - gBattleStruct->dynamax.dynamaxed[battlerId] = TRUE; - gBattleStruct->dynamax.dynamaxTurns[battlerId] = DYNAMAX_TURNS_COUNT; + // Set appropriate use flags. + SetActiveGimmick(battler, GIMMICK_DYNAMAX); + SetGimmickAsActivated(battler, GIMMICK_DYNAMAX); + gBattleStruct->dynamax.dynamaxTurns[battler] = DYNAMAX_TURNS_COUNT; // Substitute is removed upon Dynamaxing. - gBattleMons[battlerId].status2 &= ~STATUS2_SUBSTITUTE; - ClearBehindSubstituteBit(battlerId); + gBattleMons[battler].status2 &= ~STATUS2_SUBSTITUTE; + ClearBehindSubstituteBit(battler); // Choiced Moves are reset upon Dynamaxing. - gBattleStruct->choicedMove[battlerId] = MOVE_NONE; + gBattleStruct->choicedMove[battler] = MOVE_NONE; // Try Gigantamax form change. - if (!(gBattleMons[battlerId].status2 & STATUS2_TRANSFORMED)) // Ditto cannot Gigantamax. - TryBattleFormChange(battlerId, FORM_CHANGE_BATTLE_GIGANTAMAX); + if (!(gBattleMons[battler].status2 & STATUS2_TRANSFORMED)) // Ditto cannot Gigantamax. + TryBattleFormChange(battler, FORM_CHANGE_BATTLE_GIGANTAMAX); + + BattleScriptExecute(BattleScript_DynamaxBegins); } // Unsets the flags used for Dynamaxing and reverts max HP if needed. -void UndoDynamax(u16 battlerId) +void UndoDynamax(u32 battler) { - u8 side = GetBattlerSide(battlerId); - u8 monId = gBattlerPartyIndexes[battlerId]; + u8 side = GetBattlerSide(battler); + u8 monId = gBattlerPartyIndexes[battler]; // Revert HP if battler is still Dynamaxed. - if (IsDynamaxed(battlerId)) + if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX) { struct Pokemon *mon = (side == B_SIDE_PLAYER) ? &gPlayerParty[monId] : &gEnemyParty[monId]; u16 mult = UQ_4_12(1.0/1.5); // placeholder - gBattleMons[battlerId].hp = UQ_4_12_TO_INT((GetMonData(mon, MON_DATA_HP) * mult + 1) + UQ_4_12_ROUND); // round up - SetMonData(mon, MON_DATA_HP, &gBattleMons[battlerId].hp); + gBattleMons[battler].hp = UQ_4_12_TO_INT((GetMonData(mon, MON_DATA_HP) * mult + 1) + UQ_4_12_ROUND); // round up + SetMonData(mon, MON_DATA_HP, &gBattleMons[battler].hp); CalculateMonStats(mon); } // Makes sure there are no Dynamax flags set, including on switch / faint. - gBattleStruct->dynamax.dynamaxed[battlerId] = FALSE; - gBattleStruct->dynamax.dynamaxTurns[battlerId] = 0; + SetActiveGimmick(battler, GIMMICK_NONE); + gBattleStruct->dynamax.dynamaxTurns[battler] = 0; // Undo form change if needed. - if (IsGigantamaxed(battlerId)) - TryBattleFormChange(battlerId, FORM_CHANGE_END_BATTLE); + if (IsGigantamaxed(battler)) + TryBattleFormChange(battler, FORM_CHANGE_END_BATTLE); } // Certain moves are blocked by Max Guard that normally ignore protection. -bool32 IsMoveBlockedByMaxGuard(u16 move) +bool32 IsMoveBlockedByMaxGuard(u32 move) { switch (move) { @@ -239,7 +233,7 @@ bool32 IsMoveBlockedByMaxGuard(u16 move) } // Weight-based moves (and some other moves in Raids) are blocked by Dynamax. -bool32 IsMoveBlockedByDynamax(u16 move) +bool32 IsMoveBlockedByDynamax(u32 move) { // TODO: Certain moves are banned in raids. switch (gMovesInfo[move].effect) @@ -251,24 +245,15 @@ bool32 IsMoveBlockedByDynamax(u16 move) return FALSE; } -// Returns whether a move should be converted into a Max Move. -bool32 ShouldUseMaxMove(u16 battlerId, u16 baseMove) -{ - // TODO: Raid bosses do not always use Max Moves. - // if (IsRaidBoss(battlerId)) - // return !IsRaidBossUsingRegularMove(battlerId, baseMove); - return IsDynamaxed(battlerId) || gBattleStruct->dynamax.toDynamax & gBitTable[battlerId]; -} - -static u16 GetTypeBasedMaxMove(u16 battlerId, u16 type) +static u16 GetTypeBasedMaxMove(u32 battler, u32 type) { // Gigantamax check u32 i; - u16 species = gBattleMons[battlerId].species; - u16 targetSpecies = SPECIES_NONE; + u32 species = gBattleMons[battler].species; + u32 targetSpecies = SPECIES_NONE; if (!gSpeciesInfo[species].isGigantamax) - targetSpecies = GetBattleFormChangeTargetSpecies(battlerId, FORM_CHANGE_BATTLE_GIGANTAMAX); + targetSpecies = GetBattleFormChangeTargetSpecies(battler, FORM_CHANGE_BATTLE_GIGANTAMAX); if (targetSpecies != SPECIES_NONE) species = targetSpecies; @@ -289,9 +274,9 @@ static u16 GetTypeBasedMaxMove(u16 battlerId, u16 type) } // Returns the appropriate Max Move or G-Max Move for a battler to use. -u16 GetMaxMove(u16 battlerId, u16 baseMove) +u16 GetMaxMove(u32 battler, u32 baseMove) { - u16 move = baseMove; + u32 move = baseMove; if (baseMove == MOVE_NONE) // for move display { return MOVE_NONE; @@ -306,13 +291,11 @@ u16 GetMaxMove(u16 battlerId, u16 baseMove) } else if (gBattleStruct->dynamicMoveType) { - move = GetTypeBasedMaxMove(battlerId, gBattleStruct->dynamicMoveType & DYNAMIC_TYPE_MASK); - gBattleStruct->dynamax.categories[battlerId] = gMovesInfo[baseMove].category; + move = GetTypeBasedMaxMove(battler, gBattleStruct->dynamicMoveType & DYNAMIC_TYPE_MASK); } else { - move = GetTypeBasedMaxMove(battlerId, gMovesInfo[baseMove].type); - gBattleStruct->dynamax.categories[battlerId] = gMovesInfo[baseMove].category; + move = GetTypeBasedMaxMove(battler, gMovesInfo[baseMove].type); } return move; @@ -332,7 +315,7 @@ enum }; // Gets the base power of a Max Move. -u8 GetMaxMovePower(u16 move) +u8 GetMaxMovePower(u32 move) { u8 tier; // G-Max Drum Solo, G-Max Hydrosnipe, and G-Max Fireball always have 160 base power. @@ -383,7 +366,7 @@ u8 GetMaxMovePower(u16 move) } } -static u8 GetMaxPowerTier(u16 move) +static u8 GetMaxPowerTier(u32 move) { if (gMovesInfo[move].strikeCount >= 2 && gMovesInfo[move].strikeCount <= 5) { @@ -459,7 +442,7 @@ static u8 GetMaxPowerTier(u16 move) } // Returns whether a move is a Max Move or not. -bool32 IsMaxMove(u16 move) +bool32 IsMaxMove(u32 move) { return move >= FIRST_MAX_MOVE && move <= LAST_MAX_MOVE; } @@ -485,7 +468,7 @@ void ChooseDamageNonTypesString(u8 type) } // Returns the status effect that should be applied by a G-Max Move. -static u32 GetMaxMoveStatusEffect(u16 move) +static u32 GetMaxMoveStatusEffect(u32 move) { u8 maxEffect = gMovesInfo[move].argument; switch (maxEffect) @@ -524,7 +507,7 @@ static u32 GetMaxMoveStatusEffect(u16 move) void BS_UpdateDynamax(void) { NATIVE_ARGS(); - u16 battler = gBattleScripting.battler; + u32 battler = gBattleScripting.battler; struct Pokemon *mon = &GetSideParty(GetBattlerSide(battler))[gBattlerPartyIndexes[battler]]; if (!IsGigantamaxed(battler)) // RecalcBattlerStats will get called on form change. @@ -776,7 +759,7 @@ void BS_SetMaxMoveEffect(void) { static const u8 sSnoozeEffects[] = {TRUE, FALSE}; if (!(gStatuses3[gBattlerTarget] & STATUS3_YAWN) - && CanSleep(gBattlerTarget) + && CanBeSlept(gBattlerTarget, GetBattlerAbility(gBattlerTarget)) && RandomElement(RNG_G_MAX_SNOOZE, sSnoozeEffects)) // 50% chance of success { gStatuses3[gBattlerTarget] |= STATUS3_YAWN_TURN(2); @@ -882,7 +865,7 @@ void BS_TrySetStatus1(void) switch (status1) { case STATUS1_POISON: - if (CanBePoisoned(gBattlerAttacker, gBattlerTarget)) + if (CanBePoisoned(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerTarget))) { gBattleMons[gBattlerTarget].status1 |= STATUS1_POISON; gBattleCommunication[MULTISTRING_CHOOSER] = 0; @@ -890,7 +873,7 @@ void BS_TrySetStatus1(void) } break; case STATUS1_PARALYSIS: - if (CanBeParalyzed(gBattlerTarget)) + if (CanBeParalyzed(gBattlerTarget, GetBattlerAbility(gBattlerTarget))) { gBattleMons[gBattlerTarget].status1 |= STATUS1_PARALYSIS; gBattleCommunication[MULTISTRING_CHOOSER] = 3; @@ -898,7 +881,7 @@ void BS_TrySetStatus1(void) } break; case STATUS1_SLEEP: - if (CanSleep(gBattlerTarget)) + if (CanBeSlept(gBattlerTarget, GetBattlerAbility(gBattlerTarget))) { if (B_SLEEP_TURNS >= GEN_5) gBattleMons[gBattlerTarget].status1 |= STATUS1_SLEEP_TURN((Random() % 3) + 2); @@ -1049,201 +1032,8 @@ void BS_TryRecycleBerry(void) void BS_JumpIfDynamaxed(void) { NATIVE_ARGS(const u8 *jumpInstr); - if (IsDynamaxed(gBattlerTarget)) + if ((GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX)) gBattlescriptCurrInstr = cmd->jumpInstr; else gBattlescriptCurrInstr = cmd->nextInstr; } - -// DYNAMAX TRIGGER: -static const u8 ALIGNED(4) sDynamaxTriggerGfx[] = INCBIN_U8("graphics/battle_interface/dynamax_trigger.4bpp"); -static const u16 sDynamaxTriggerPal[] = INCBIN_U16("graphics/battle_interface/dynamax_trigger.gbapal"); - -static const struct SpriteSheet sSpriteSheet_DynamaxTrigger = -{ - sDynamaxTriggerGfx, sizeof(sDynamaxTriggerGfx), TAG_DYNAMAX_TRIGGER_TILE -}; -static const struct SpritePalette sSpritePalette_DynamaxTrigger = -{ - sDynamaxTriggerPal, TAG_DYNAMAX_TRIGGER_PAL -}; - -static const struct OamData sOamData_DynamaxTrigger = -{ - .y = 0, - .affineMode = 0, - .objMode = 0, - .mosaic = 0, - .bpp = 0, - .shape = ST_OAM_SQUARE, - .x = 0, - .matrixNum = 0, - .size = 2, - .tileNum = 0, - .priority = 1, - .paletteNum = 0, - .affineParam = 0, -}; - -static const union AnimCmd sSpriteAnim_DynamaxTriggerOff[] = -{ - ANIMCMD_FRAME(0, 0), - ANIMCMD_END -}; - -static const union AnimCmd sSpriteAnim_DynamaxTriggerOn[] = -{ - ANIMCMD_FRAME(16, 0), - ANIMCMD_END -}; - -static const union AnimCmd *const sSpriteAnimTable_DynamaxTrigger[] = -{ - sSpriteAnim_DynamaxTriggerOff, - sSpriteAnim_DynamaxTriggerOn, -}; - -static const struct SpriteTemplate sSpriteTemplate_DynamaxTrigger = -{ - .tileTag = TAG_DYNAMAX_TRIGGER_TILE, - .paletteTag = TAG_DYNAMAX_TRIGGER_PAL, - .oam = &sOamData_DynamaxTrigger, - .anims = sSpriteAnimTable_DynamaxTrigger, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_DynamaxTrigger -}; - -// Dynamax Evolution Trigger icon functions. -void ChangeDynamaxTriggerSprite(u8 spriteId, u8 animId) -{ - StartSpriteAnim(&gSprites[spriteId], animId); -} - -#define SINGLES_DYNAMAX_TRIGGER_POS_X_OPTIMAL (30) -#define SINGLES_DYNAMAX_TRIGGER_POS_X_PRIORITY (31) -#define SINGLES_DYNAMAX_TRIGGER_POS_X_SLIDE (15) -#define SINGLES_DYNAMAX_TRIGGER_POS_Y_DIFF (-11) - -#define DOUBLES_DYNAMAX_TRIGGER_POS_X_OPTIMAL (30) -#define DOUBLES_DYNAMAX_TRIGGER_POS_X_PRIORITY (31) -#define DOUBLES_DYNAMAX_TRIGGER_POS_X_SLIDE (15) -#define DOUBLES_DYNAMAX_TRIGGER_POS_Y_DIFF (-4) - -#define tBattler data[0] -#define tHide data[1] - -void CreateDynamaxTriggerSprite(u8 battlerId, u8 palId) -{ - LoadSpritePalette(&sSpritePalette_DynamaxTrigger); - if (GetSpriteTileStartByTag(TAG_DYNAMAX_TRIGGER_TILE) == 0xFFFF) - LoadSpriteSheet(&sSpriteSheet_DynamaxTrigger); - if (gBattleStruct->dynamax.triggerSpriteId == 0xFF) - { - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) - gBattleStruct->dynamax.triggerSpriteId = CreateSprite(&sSpriteTemplate_DynamaxTrigger, - gSprites[gHealthboxSpriteIds[battlerId]].x - DOUBLES_DYNAMAX_TRIGGER_POS_X_SLIDE, - gSprites[gHealthboxSpriteIds[battlerId]].y - DOUBLES_DYNAMAX_TRIGGER_POS_Y_DIFF, 0); - else - gBattleStruct->dynamax.triggerSpriteId = CreateSprite(&sSpriteTemplate_DynamaxTrigger, - gSprites[gHealthboxSpriteIds[battlerId]].x - SINGLES_DYNAMAX_TRIGGER_POS_X_SLIDE, - gSprites[gHealthboxSpriteIds[battlerId]].y - SINGLES_DYNAMAX_TRIGGER_POS_Y_DIFF, 0); - } - gSprites[gBattleStruct->dynamax.triggerSpriteId].tBattler = battlerId; - gSprites[gBattleStruct->dynamax.triggerSpriteId].tHide = FALSE; - - ChangeDynamaxTriggerSprite(gBattleStruct->dynamax.triggerSpriteId, palId); -} - -static void SpriteCb_DynamaxTrigger(struct Sprite *sprite) -{ - s32 xSlide, xPriority, xOptimal; - s32 yDiff; - - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) - { - xSlide = DOUBLES_DYNAMAX_TRIGGER_POS_X_SLIDE; - xPriority = DOUBLES_DYNAMAX_TRIGGER_POS_X_PRIORITY; - xOptimal = DOUBLES_DYNAMAX_TRIGGER_POS_X_OPTIMAL; - yDiff = DOUBLES_DYNAMAX_TRIGGER_POS_Y_DIFF; - } - else - { - xSlide = SINGLES_DYNAMAX_TRIGGER_POS_X_SLIDE; - xPriority = SINGLES_DYNAMAX_TRIGGER_POS_X_PRIORITY; - xOptimal = SINGLES_DYNAMAX_TRIGGER_POS_X_OPTIMAL; - yDiff = SINGLES_DYNAMAX_TRIGGER_POS_Y_DIFF; - } - - if (sprite->tHide) - { - if (sprite->x != gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xSlide) - sprite->x++; - - if (sprite->x >= gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xPriority) - sprite->oam.priority = 2; - else - sprite->oam.priority = 1; - - sprite->y = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y - yDiff; - sprite->y2 = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y2 - yDiff; - if (sprite->x == gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xSlide) - DestroyDynamaxTriggerSprite(); - } - else - { - if (sprite->x != gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xOptimal) - sprite->x--; - - if (sprite->x >= gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xPriority) - sprite->oam.priority = 2; - else - sprite->oam.priority = 1; - - sprite->y = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y - yDiff; - sprite->y2 = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y2 - yDiff; - } -} - -bool32 IsDynamaxTriggerSpriteActive(void) -{ - if (GetSpriteTileStartByTag(TAG_DYNAMAX_TRIGGER_TILE) == 0xFFFF) - return FALSE; - else if (IndexOfSpritePaletteTag(TAG_DYNAMAX_TRIGGER_PAL) != 0xFF) - return TRUE; - else - return FALSE; -} - -void HideDynamaxTriggerSprite(void) -{ - if (gBattleStruct->dynamax.triggerSpriteId >= MAX_SPRITES) - return; - ChangeDynamaxTriggerSprite(gBattleStruct->dynamax.triggerSpriteId, 0); - gSprites[gBattleStruct->dynamax.triggerSpriteId].tHide = TRUE; -} - -void DestroyDynamaxTriggerSprite(void) -{ - FreeSpritePaletteByTag(TAG_DYNAMAX_TRIGGER_PAL); - FreeSpriteTilesByTag(TAG_DYNAMAX_TRIGGER_TILE); - if (gBattleStruct->dynamax.triggerSpriteId != 0xFF) - DestroySprite(&gSprites[gBattleStruct->dynamax.triggerSpriteId]); - gBattleStruct->dynamax.triggerSpriteId = 0xFF; -} - -#undef tBattler -#undef tHide - -// data fields for healthboxMain -// oam.affineParam holds healthboxRight spriteId -#define hMain_DynamaxIndicatorId data[3] -#define hMain_HealthBarSpriteId data[5] -#define hMain_Battler data[6] -#define hMain_Data7 data[7] - -// data fields for healthboxRight -#define hOther_HealthBoxSpriteId data[5] - -// data fields for healthbar -#define hBar_HealthBoxSpriteId data[5] diff --git a/src/battle_gfx_sfx_util.c b/src/battle_gfx_sfx_util.c index 586473781f..3963bccb00 100644 --- a/src/battle_gfx_sfx_util.c +++ b/src/battle_gfx_sfx_util.c @@ -626,7 +626,7 @@ void BattleLoadMonSpriteGfx(struct Pokemon *mon, u32 battler) } // dynamax tint - if (IsDynamaxed(battler)) + if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX) { // Calyrex and its forms have a blue dynamax aura instead of red. if (GET_BASE_SPECIES_ID(species) == SPECIES_CALYREX) @@ -637,7 +637,7 @@ void BattleLoadMonSpriteGfx(struct Pokemon *mon, u32 battler) } // Terastallization's tint - if (IsTerastallized(battler)) + if (GetActiveGimmick(battler) == GIMMICK_TERA) { BlendPalette(paletteOffset, 16, 8, GetTeraTypeRGB(GetBattlerTeraType(battler))); CpuCopy32(gPlttBufferFaded + paletteOffset, gPlttBufferUnfaded + paletteOffset, PLTT_SIZEOF(16)); @@ -706,8 +706,7 @@ bool8 BattleLoadAllHealthBoxesGfx(u8 state) { LoadSpritePalette(&sSpritePalettes_HealthBoxHealthBar[0]); LoadSpritePalette(&sSpritePalettes_HealthBoxHealthBar[1]); - MegaIndicator_LoadSpritesGfx(); - TeraIndicator_LoadSpriteGfx(); + LoadIndicatorSpritesGfx(); CategoryIcons_LoadSpritesGfx(); } else if (!IsDoubleBattle()) diff --git a/src/battle_gimmick.c b/src/battle_gimmick.c new file mode 100644 index 0000000000..e2d622daea --- /dev/null +++ b/src/battle_gimmick.c @@ -0,0 +1,395 @@ +#include "global.h" +#include "battle.h" +#include "battle_anim.h" +#include "battle_controllers.h" +#include "battle_interface.h" +#include "battle_gimmick.h" +#include "battle_z_move.h" +#include "battle_setup.h" +#include "battle_util.h" +#include "item.h" +#include "palette.h" +#include "pokemon.h" +#include "sprite.h" +#include "util.h" +#include "test_runner.h" + +#include "data/gimmicks.h" + +// Populates gBattleStruct->gimmick.usableGimmick for each battler. +void AssignUsableGimmicks(void) +{ + u32 battler, gimmick; + for (battler = 0; battler < gBattlersCount; ++battler) + { + gBattleStruct->gimmick.usableGimmick[battler] = GIMMICK_NONE; + for (gimmick = 0; gimmick < GIMMICKS_COUNT; ++gimmick) + { + if (CanActivateGimmick(battler, gimmick)) + { + gBattleStruct->gimmick.usableGimmick[battler] = gimmick; + break; + } + } + } +} + +// Returns whether a battler is able to use a gimmick. Checks consumption and gimmick specific functions. +bool32 CanActivateGimmick(u32 battler, enum Gimmick gimmick) +{ + return gGimmicksInfo[gimmick].CanActivate != NULL && gGimmicksInfo[gimmick].CanActivate(battler); +} + +// Returns whether the player has a gimmick selected while in the move selection menu. +bool32 IsGimmickSelected(u32 battler, enum Gimmick gimmick) +{ + // There's no player select in tests, but some gimmicks need to test choice before they are fully activated. + if (TESTING) + return (gBattleStruct->gimmick.toActivate & gBitTable[battler]) && gBattleStruct->gimmick.usableGimmick[battler] == gimmick; + else + return gBattleStruct->gimmick.usableGimmick[battler] == gimmick && gBattleStruct->gimmick.playerSelect; +} + +// Sets a battler as having a gimmick active using their party index. +void SetActiveGimmick(u32 battler, enum Gimmick gimmick) +{ + gBattleStruct->gimmick.activeGimmick[GetBattlerSide(battler)][gBattlerPartyIndexes[battler]] = gimmick; +} + +// Returns a battler's active gimmick, if any. +enum Gimmick GetActiveGimmick(u32 battler) +{ + return gBattleStruct->gimmick.activeGimmick[GetBattlerSide(battler)][gBattlerPartyIndexes[battler]]; +} + +// Returns whether a trainer mon is intended to use an unrestrictive gimmick via .useGimmick (i.e Tera). +bool32 ShouldTrainerBattlerUseGimmick(u32 battler, enum Gimmick gimmick) +{ + // There are no trainer party settings in battles, but the AI needs to know which gimmick to use. + if (TESTING) + { + return gimmick == TestRunner_Battle_GetChosenGimmick(GetBattlerSide(battler), gBattlerPartyIndexes[battler]); + } + // The player can bypass these checks because they can choose through the controller. + else if (GetBattlerSide(battler) == B_SIDE_PLAYER + && !((gBattleTypeFlags & BATTLE_TYPE_MULTI) && battler == B_POSITION_PLAYER_RIGHT)) + { + return TRUE; + } + // Check the trainer party data to see if a gimmick is intended. + else + { + bool32 isSecondTrainer = (GetBattlerPosition(battler) == B_POSITION_OPPONENT_RIGHT) && (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) && !BATTLE_TWO_VS_ONE_OPPONENT; + u16 trainerId = isSecondTrainer ? gTrainerBattleOpponent_B : gTrainerBattleOpponent_A; + const struct TrainerMon *mon = &GetTrainerPartyFromId(trainerId)[isSecondTrainer ? gBattlerPartyIndexes[battler] - MULTI_PARTY_SIZE : gBattlerPartyIndexes[battler]]; + return mon->useGimmick == gimmick; + } +} + +// Returns whether a trainer has used a gimmick during a battle. +bool32 HasTrainerUsedGimmick(u32 battler, enum Gimmick gimmick) +{ + // Check whether partner battler has used gimmick or plans to during turn. + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE + && IsPartnerMonFromSameTrainer(battler) + && (gBattleStruct->gimmick.activated[BATTLE_PARTNER(battler)][gimmick] + || ((gBattleStruct->gimmick.toActivate & gBitTable[BATTLE_PARTNER(battler)] + && gBattleStruct->gimmick.usableGimmick[BATTLE_PARTNER(battler)] == gimmick)))) + { + return TRUE; + } + // Otherwise, return whether current battler has used gimmick. + else + { + return gBattleStruct->gimmick.activated[battler][gimmick]; + } +} + +// Sets a gimmick as used by a trainer with checks for Multi Battles. +void SetGimmickAsActivated(u32 battler, enum Gimmick gimmick) +{ + gBattleStruct->gimmick.activated[battler][gimmick] = TRUE; + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && IsPartnerMonFromSameTrainer(battler)) + gBattleStruct->gimmick.activated[BATTLE_PARTNER(battler)][gimmick] = TRUE; +} + +#define SINGLES_GIMMICK_TRIGGER_POS_X_OPTIMAL (30) +#define SINGLES_GIMMICK_TRIGGER_POS_X_PRIORITY (31) +#define SINGLES_GIMMICK_TRIGGER_POS_X_SLIDE (15) +#define SINGLES_GIMMICK_TRIGGER_POS_Y_DIFF (-11) + +#define DOUBLES_GIMMICK_TRIGGER_POS_X_OPTIMAL (30) +#define DOUBLES_GIMMICK_TRIGGER_POS_X_PRIORITY (31) +#define DOUBLES_GIMMICK_TRIGGER_POS_X_SLIDE (15) +#define DOUBLES_GIMMICK_TRIGGER_POS_Y_DIFF (-4) + +#define tBattler data[0] +#define tHide data[1] + +void ChangeGimmickTriggerSprite(u32 spriteId, u32 animId) +{ + StartSpriteAnim(&gSprites[spriteId], animId); +} + +void CreateGimmickTriggerSprite(u32 battler) +{ + const struct GimmickInfo * gimmick = &gGimmicksInfo[gBattleStruct->gimmick.usableGimmick[battler]]; + + // Exit if there shouldn't be a sprite produced. + if (GetBattlerSide(battler) == B_SIDE_OPPONENT + || gBattleStruct->gimmick.usableGimmick[battler] == GIMMICK_NONE + || gimmick->triggerSheet == NULL) + { + return; + } + + LoadSpritePalette(gimmick->triggerPal); + if (GetSpriteTileStartByTag(TAG_GIMMICK_TRIGGER_TILE) == 0xFFFF) + LoadSpriteSheet(gimmick->triggerSheet); + + if (gBattleStruct->gimmick.triggerSpriteId == 0xFF) + { + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + gBattleStruct->gimmick.triggerSpriteId = CreateSprite(gimmick->triggerTemplate, + gSprites[gHealthboxSpriteIds[battler]].x - DOUBLES_GIMMICK_TRIGGER_POS_X_SLIDE, + gSprites[gHealthboxSpriteIds[battler]].y - DOUBLES_GIMMICK_TRIGGER_POS_Y_DIFF, 0); + else + gBattleStruct->gimmick.triggerSpriteId = CreateSprite(gimmick->triggerTemplate, + gSprites[gHealthboxSpriteIds[battler]].x - SINGLES_GIMMICK_TRIGGER_POS_X_SLIDE, + gSprites[gHealthboxSpriteIds[battler]].y - SINGLES_GIMMICK_TRIGGER_POS_Y_DIFF, 0); + } + + gSprites[gBattleStruct->gimmick.triggerSpriteId].tBattler = battler; + gSprites[gBattleStruct->gimmick.triggerSpriteId].tHide = FALSE; + + ChangeGimmickTriggerSprite(gBattleStruct->gimmick.triggerSpriteId, 0); +} + +bool32 IsGimmickTriggerSpriteActive(void) +{ + if (GetSpriteTileStartByTag(TAG_GIMMICK_TRIGGER_TILE) == 0xFFFF) + return FALSE; + else if (IndexOfSpritePaletteTag(TAG_GIMMICK_TRIGGER_PAL) != 0xFF) + return TRUE; + else + return FALSE; +} + +void HideGimmickTriggerSprite(void) +{ + if (gBattleStruct->gimmick.triggerSpriteId != 0xFF) + { + ChangeGimmickTriggerSprite(gBattleStruct->gimmick.triggerSpriteId, 0); + gSprites[gBattleStruct->gimmick.triggerSpriteId].tHide = TRUE; + } +} + +void DestroyGimmickTriggerSprite(void) +{ + FreeSpritePaletteByTag(TAG_GIMMICK_TRIGGER_PAL); + FreeSpriteTilesByTag(TAG_GIMMICK_TRIGGER_TILE); + if (gBattleStruct->gimmick.triggerSpriteId != 0xFF) + DestroySprite(&gSprites[gBattleStruct->gimmick.triggerSpriteId]); + gBattleStruct->gimmick.triggerSpriteId = 0xFF; +} + +static void SpriteCb_GimmickTrigger(struct Sprite *sprite) +{ + s32 xSlide, xPriority, xOptimal; + s32 yDiff; + + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + { + xSlide = DOUBLES_GIMMICK_TRIGGER_POS_X_SLIDE; + xPriority = DOUBLES_GIMMICK_TRIGGER_POS_X_PRIORITY; + xOptimal = DOUBLES_GIMMICK_TRIGGER_POS_X_OPTIMAL; + yDiff = DOUBLES_GIMMICK_TRIGGER_POS_Y_DIFF; + } + else + { + xSlide = SINGLES_GIMMICK_TRIGGER_POS_X_SLIDE; + xPriority = SINGLES_GIMMICK_TRIGGER_POS_X_PRIORITY; + xOptimal = SINGLES_GIMMICK_TRIGGER_POS_X_OPTIMAL; + yDiff = SINGLES_GIMMICK_TRIGGER_POS_Y_DIFF; + } + + if (sprite->tHide) + { + if (sprite->x != gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xSlide) + sprite->x++; + + if (sprite->x >= gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xPriority) + sprite->oam.priority = 2; + else + sprite->oam.priority = 1; + + sprite->y = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y - yDiff; + sprite->y2 = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y2 - yDiff; + if (sprite->x == gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xSlide) + DestroyGimmickTriggerSprite(); + } + else + { + if (sprite->x != gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xOptimal) + sprite->x--; + + if (sprite->x >= gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xPriority) + sprite->oam.priority = 2; + else + sprite->oam.priority = 1; + + sprite->y = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y - yDiff; + sprite->y2 = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y2 - yDiff; + } +} + +#undef tBattler +#undef tHide + +// for sprite data fields +#define tBattler data[0] +#define tPosX data[2] +#define tLevelXDelta data[3] // X position depends whether level has 3, 2 or 1 digit + +// data fields for healthboxMain +// oam.affineParam holds healthboxRight spriteId +#define hMain_Battler data[6] + +void LoadIndicatorSpritesGfx(void) +{ + u32 gimmick; + for (gimmick = 0; gimmick < GIMMICKS_COUNT; ++gimmick) + { + if (gimmick == GIMMICK_TERA) // special case + LoadSpriteSheets(sTeraIndicatorSpriteSheets); + else if (gGimmicksInfo[gimmick].indicatorSheet != NULL) + LoadSpriteSheet(gGimmicksInfo[gimmick].indicatorSheet); + + if (gGimmicksInfo[gimmick].indicatorPal != NULL) + LoadSpritePalette(gGimmicksInfo[gimmick].indicatorPal); + } + // Primal reversion graphics aren't loaded as part of gimmick data + LoadSpriteSheet(&sSpriteSheet_AlphaIndicator); + LoadSpriteSheet(&sSpriteSheet_OmegaIndicator); +} + +static void SpriteCb_GimmickIndicator(struct Sprite *sprite) +{ + u32 battler = sprite->tBattler; + + sprite->x = gSprites[gHealthboxSpriteIds[battler]].x + sprite->tPosX + sprite->tLevelXDelta; + sprite->x2 = gSprites[gHealthboxSpriteIds[battler]].x2; + sprite->y2 = gSprites[gHealthboxSpriteIds[battler]].y2; +} + +static inline u32 GetIndicatorSpriteId(u32 healthboxId) +{ + return gBattleStruct->gimmick.indicatorSpriteId[gSprites[healthboxId].hMain_Battler]; +} + +u32 GetIndicatorTileTag(u32 battler) +{ + u32 gimmick = GetActiveGimmick(battler); + + if (IsBattlerPrimalReverted(battler)) + { + if (gBattleMons[battler].species == SPECIES_GROUDON_PRIMAL) + return TAG_OMEGA_INDICATOR_TILE; + else + return TAG_ALPHA_INDICATOR_TILE; + } + else if (gimmick == GIMMICK_TERA) // special case + { + return sTeraIndicatorSpriteSheets[GetBattlerTeraType(battler)].tag; + } + else if (gGimmicksInfo[gimmick].indicatorSheet != NULL) + { + return gGimmicksInfo[gimmick].indicatorSheet->tag; + } + else + { + return TAG_NONE; + } +} + +u32 GetIndicatorPalTag(u32 battler) +{ + u32 gimmick = GetActiveGimmick(battler); + if (IsBattlerPrimalReverted(battler)) + return TAG_MISC_INDICATOR_PAL; + else if (gGimmicksInfo[gimmick].indicatorPal != NULL) + return gGimmicksInfo[gimmick].indicatorPal->tag; + else + return TAG_NONE; +} + +void UpdateIndicatorVisibilityAndType(u32 healthboxId, bool32 invisible) +{ + u32 battler = gSprites[healthboxId].hMain_Battler; + u32 tileTag = GetIndicatorTileTag(battler); + u32 palTag = GetIndicatorPalTag(battler); + struct Sprite *sprite = &gSprites[GetIndicatorSpriteId(healthboxId)]; + + if (GetIndicatorSpriteId(healthboxId) == 0) // safari zone means the player doesn't have an indicator sprite id + return; + + if (tileTag != TAG_NONE && palTag != TAG_NONE) + { + sprite->oam.tileNum = GetSpriteTileStartByTag(tileTag); + sprite->oam.paletteNum = IndexOfSpritePaletteTag(palTag); + sprite->invisible = invisible; + } + else // in case of error + { + sprite->invisible = TRUE; + } +} + +void UpdateIndicatorOamPriority(u32 healthboxId, u32 oamPriority) +{ + gSprites[GetIndicatorSpriteId(healthboxId)].oam.priority = oamPriority; +} + +void UpdateIndicatorLevelData(u32 healthboxId, u32 level) +{ + s32 xDelta = 0; + + if (level >= 100) + xDelta -= 4; + else if (level < 10) + xDelta += 5; + + gSprites[GetIndicatorSpriteId(healthboxId)].tLevelXDelta = xDelta; +} + +static const s8 sIndicatorPositions[][2] = +{ + [B_POSITION_PLAYER_LEFT] = {53, -9}, + [B_POSITION_OPPONENT_LEFT] = {44, -9}, + [B_POSITION_PLAYER_RIGHT] = {52, -9}, + [B_POSITION_OPPONENT_RIGHT] = {44, -9}, +}; + +void CreateIndicatorSprite(u32 battler) +{ + u32 position, spriteId; + s16 xHealthbox = 0, x = 0, y = 0; + + position = GetBattlerPosition(battler); + GetBattlerHealthboxCoords(battler, &xHealthbox, &y); + + x = sIndicatorPositions[position][0]; + y += sIndicatorPositions[position][1]; + + spriteId = CreateSpriteAtEnd(&sSpriteTemplate_GimmickIndicator, 0, y, 0); + gBattleStruct->gimmick.indicatorSpriteId[battler] = spriteId; + gSprites[spriteId].tBattler = battler; + gSprites[spriteId].tPosX = x; + gSprites[spriteId].invisible = FALSE; +} + +#undef tBattler +#undef tPosX +#undef tLevelXDelta + +#undef hMain_Battler diff --git a/src/battle_interface.c b/src/battle_interface.c index 0dd61d5597..60b555fdcf 100644 --- a/src/battle_interface.c +++ b/src/battle_interface.c @@ -195,13 +195,6 @@ static void SpriteCB_StatusSummaryBalls_Enter(struct Sprite *); static void SpriteCB_StatusSummaryBalls_Exit(struct Sprite *); static void SpriteCB_StatusSummaryBalls_OnSwitchout(struct Sprite *); -static void SpriteCb_MegaTrigger(struct Sprite *); -static void SpriteCb_BurstTrigger(struct Sprite *); -static void MegaIndicator_UpdateLevel(u32 healthboxId, u32 level); -static void MegaIndicator_CreateSprite(u32 battlerId, u32 healthboxSpriteId); -static void MegaIndicator_UpdateOamPriority(u32 healthboxId, u32 oamPriority); -static void SpriteCb_MegaIndicator(struct Sprite *); - static u8 GetStatusIconForBattlerId(u8, u8); static s32 CalcNewBarValue(s32, s32, s32, s32 *, u8, u16); static u8 GetScaledExpFraction(s32, s32, s32, u8); @@ -620,122 +613,6 @@ static const struct WindowTemplate sHealthboxWindowTemplate = { .baseBlock = 0 }; -static const u8 ALIGNED(4) sMegaTriggerGfx[] = INCBIN_U8("graphics/battle_interface/mega_trigger.4bpp"); -static const u16 sMegaTriggerPal[] = INCBIN_U16("graphics/battle_interface/mega_trigger.gbapal"); - -static const struct SpriteSheet sSpriteSheet_MegaTrigger = -{ - sMegaTriggerGfx, sizeof(sMegaTriggerGfx), TAG_MEGA_TRIGGER_TILE -}; -static const struct SpritePalette sSpritePalette_MegaTrigger = -{ - sMegaTriggerPal, TAG_MEGA_TRIGGER_PAL -}; - -static const struct OamData sOamData_MegaTrigger = -{ - .y = 0, - .affineMode = 0, - .objMode = 0, - .mosaic = 0, - .bpp = 0, - .shape = ST_OAM_SQUARE, - .x = 0, - .matrixNum = 0, - .size = 2, - .tileNum = 0, - .priority = 1, - .paletteNum = 0, - .affineParam = 0, -}; - -static const union AnimCmd sSpriteAnim_MegaTriggerOff[] = -{ - ANIMCMD_FRAME(0, 0), - ANIMCMD_END -}; - -static const union AnimCmd sSpriteAnim_MegaTriggerOn[] = -{ - ANIMCMD_FRAME(16, 0), - ANIMCMD_END -}; - -static const union AnimCmd *const sSpriteAnimTable_MegaTrigger[] = -{ - sSpriteAnim_MegaTriggerOff, - sSpriteAnim_MegaTriggerOn, -}; - -static const struct SpriteTemplate sSpriteTemplate_MegaTrigger = -{ - .tileTag = TAG_MEGA_TRIGGER_TILE, - .paletteTag = TAG_MEGA_TRIGGER_PAL, - .oam = &sOamData_MegaTrigger, - .anims = sSpriteAnimTable_MegaTrigger, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_MegaTrigger -}; - -static const u8 ALIGNED(4) sBurstTriggerGfx[] = INCBIN_U8("graphics/battle_interface/burst_trigger.4bpp"); -static const u16 sBurstTriggerPal[] = INCBIN_U16("graphics/battle_interface/burst_trigger.gbapal"); - -static const struct SpriteSheet sSpriteSheet_BurstTrigger = -{ - sBurstTriggerGfx, sizeof(sBurstTriggerGfx), TAG_BURST_TRIGGER_TILE -}; -static const struct SpritePalette sSpritePalette_BurstTrigger = -{ - sBurstTriggerPal, TAG_BURST_TRIGGER_PAL -}; - -static const struct OamData sOamData_BurstTrigger = -{ - .y = 0, - .affineMode = 0, - .objMode = 0, - .mosaic = 0, - .bpp = 0, - .shape = ST_OAM_SQUARE, - .x = 0, - .matrixNum = 0, - .size = 2, - .tileNum = 0, - .priority = 1, - .paletteNum = 0, - .affineParam = 0, -}; - -static const union AnimCmd sSpriteAnim_BurstTriggerOff[] = -{ - ANIMCMD_FRAME(0, 0), - ANIMCMD_END -}; - -static const union AnimCmd sSpriteAnim_BurstTriggerOn[] = -{ - ANIMCMD_FRAME(16, 0), - ANIMCMD_END -}; - -static const union AnimCmd *const sSpriteAnimTable_BurstTrigger[] = -{ - sSpriteAnim_BurstTriggerOff, - sSpriteAnim_BurstTriggerOn, -}; - -static const struct SpriteTemplate sSpriteTemplate_BurstTrigger = -{ - .tileTag = TAG_BURST_TRIGGER_TILE, - .paletteTag = TAG_BURST_TRIGGER_PAL, - .oam = &sOamData_BurstTrigger, - .anims = sSpriteAnimTable_BurstTrigger, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_BurstTrigger -}; - // Because the healthbox is too large to fit into one sprite, it is divided into two sprites. // healthboxLeft or healthboxMain is the left part that is used as the 'main' sprite. // healthboxRight or healthboxOther is the right part of the healthbox. @@ -743,7 +620,6 @@ static const struct SpriteTemplate sSpriteTemplate_BurstTrigger = // data fields for healthboxMain // oam.affineParam holds healthboxRight spriteId -#define hMain_MegaIndicatorId data[3] #define hMain_HealthBarSpriteId data[5] #define hMain_Battler data[6] #define hMain_Data7 data[7] @@ -852,11 +728,7 @@ u8 CreateBattlerHealthboxSprites(u8 battlerId) healthBarSpritePtr->hBar_Data6 = data6; healthBarSpritePtr->invisible = TRUE; - // Create mega indicator sprite. - MegaIndicator_CreateSprite(battlerId, healthboxLeftSpriteId); - - // Create tera indicator sprites. - TeraIndicator_CreateSprite(battlerId, healthboxLeftSpriteId); + CreateIndicatorSprite(battlerId); gBattleStruct->ballSpriteIds[0] = MAX_SPRITES; gBattleStruct->ballSpriteIds[1] = MAX_SPRITES; @@ -940,8 +812,7 @@ void SetHealthboxSpriteInvisible(u8 healthboxSpriteId) gSprites[healthboxSpriteId].invisible = TRUE; gSprites[gSprites[healthboxSpriteId].hMain_HealthBarSpriteId].invisible = TRUE; gSprites[gSprites[healthboxSpriteId].oam.affineParam].invisible = TRUE; - MegaIndicator_SetVisibilities(healthboxSpriteId, TRUE); - TeraIndicator_SetVisibilities(healthboxSpriteId, TRUE); + UpdateIndicatorVisibilityAndType(healthboxSpriteId, TRUE); } void SetHealthboxSpriteVisible(u8 healthboxSpriteId) @@ -949,8 +820,7 @@ void SetHealthboxSpriteVisible(u8 healthboxSpriteId) gSprites[healthboxSpriteId].invisible = FALSE; gSprites[gSprites[healthboxSpriteId].hMain_HealthBarSpriteId].invisible = FALSE; gSprites[gSprites[healthboxSpriteId].oam.affineParam].invisible = FALSE; - MegaIndicator_SetVisibilities(healthboxSpriteId, FALSE); - TeraIndicator_SetVisibilities(healthboxSpriteId, FALSE); + UpdateIndicatorVisibilityAndType(healthboxSpriteId, FALSE); } static void UpdateSpritePos(u8 spriteId, s16 x, s16 y) @@ -976,8 +846,8 @@ static void TryToggleHealboxVisibility(u32 priority, u32 healthboxLeftSpriteId, gSprites[healthboxLeftSpriteId].invisible = invisible; gSprites[healthboxRightSpriteId].invisible = invisible; gSprites[healthbarSpriteId].invisible = invisible; - MegaIndicator_SetVisibilities(healthboxLeftSpriteId, invisible); - TeraIndicator_SetVisibilities(healthboxLeftSpriteId, invisible); + + UpdateIndicatorVisibilityAndType(healthboxLeftSpriteId, invisible); } void UpdateOamPriorityInAllHealthboxes(u8 priority, bool32 hideHPBoxes) @@ -994,8 +864,7 @@ void UpdateOamPriorityInAllHealthboxes(u8 priority, bool32 hideHPBoxes) gSprites[healthboxRightSpriteId].oam.priority = priority; gSprites[healthbarSpriteId].oam.priority = priority; - MegaIndicator_UpdateOamPriority(healthboxLeftSpriteId, priority); - TeraIndicator_UpdateOamPriorities(healthboxLeftSpriteId, priority); + UpdateIndicatorOamPriority(healthboxLeftSpriteId, priority); if (B_HIDE_HEALTHBOX_IN_ANIMS == TRUE && hideHPBoxes && IsBattlerAlive(i)) TryToggleHealboxVisibility(priority, healthboxLeftSpriteId, healthboxRightSpriteId, healthbarSpriteId); @@ -1050,20 +919,13 @@ static void UpdateLvlInHealthbox(u8 healthboxSpriteId, u8 lvl) u8 *objVram; u8 battler = gSprites[healthboxSpriteId].hMain_Battler; - // Don't print Lv char if mon is mega evolved or primal reverted or Dynamaxed. - if (IsBattlerMegaEvolved(battler) || IsBattlerPrimalReverted(battler) || IsDynamaxed(battler)) + // Don't print Lv char if mon has a gimmick with an indicator active. + if (GetIndicatorTileTag(battler) != TAG_NONE) { objVram = ConvertIntToDecimalStringN(text, lvl, STR_CONV_MODE_LEFT_ALIGN, 3); xPos = 5 * (3 - (objVram - (text + 2))) - 1; - MegaIndicator_UpdateLevel(healthboxSpriteId, lvl); - MegaIndicator_SetVisibilities(healthboxSpriteId, FALSE); - } - else if (IsTerastallized(battler)) - { - objVram = ConvertIntToDecimalStringN(text, lvl, STR_CONV_MODE_LEFT_ALIGN, 3); - xPos = 5 * (3 - (objVram - (text + 2))) - 1; - TeraIndicator_UpdateLevel(healthboxSpriteId, lvl); - TeraIndicator_SetVisibilities(healthboxSpriteId, FALSE); + UpdateIndicatorLevelData(healthboxSpriteId, lvl); + UpdateIndicatorVisibilityAndType(healthboxSpriteId, FALSE); } else { @@ -1072,7 +934,7 @@ static void UpdateLvlInHealthbox(u8 healthboxSpriteId, u8 lvl) objVram = ConvertIntToDecimalStringN(text + 2, lvl, STR_CONV_MODE_LEFT_ALIGN, 3); xPos = 5 * (3 - (objVram - (text + 2))); - MegaIndicator_SetVisibilities(healthboxSpriteId, TRUE); + UpdateIndicatorVisibilityAndType(healthboxSpriteId, TRUE); } windowTileData = AddTextPrinterAndCreateWindowOnHealthbox(text, xPos, 3, 2, &windowId); @@ -1373,435 +1235,6 @@ void SwapHpBarsWithHpText(void) } } -// Mega Evolution Trigger icon functions. -void ChangeMegaTriggerSprite(u8 spriteId, u8 animId) -{ - StartSpriteAnim(&gSprites[spriteId], animId); -} - -#define SINGLES_MEGA_TRIGGER_POS_X_OPTIMAL (30) -#define SINGLES_MEGA_TRIGGER_POS_X_PRIORITY (31) -#define SINGLES_MEGA_TRIGGER_POS_X_SLIDE (15) -#define SINGLES_MEGA_TRIGGER_POS_Y_DIFF (-11) - -#define DOUBLES_MEGA_TRIGGER_POS_X_OPTIMAL (30) -#define DOUBLES_MEGA_TRIGGER_POS_X_PRIORITY (31) -#define DOUBLES_MEGA_TRIGGER_POS_X_SLIDE (15) -#define DOUBLES_MEGA_TRIGGER_POS_Y_DIFF (-4) - -#define tBattler data[0] -#define tHide data[1] - -void CreateMegaTriggerSprite(u8 battlerId, u8 palId) -{ - LoadSpritePalette(&sSpritePalette_MegaTrigger); - if (GetSpriteTileStartByTag(TAG_MEGA_TRIGGER_TILE) == 0xFFFF) - LoadSpriteSheet(&sSpriteSheet_MegaTrigger); - if (gBattleStruct->mega.triggerSpriteId == 0xFF) - { - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) - gBattleStruct->mega.triggerSpriteId = CreateSprite(&sSpriteTemplate_MegaTrigger, - gSprites[gHealthboxSpriteIds[battlerId]].x - DOUBLES_MEGA_TRIGGER_POS_X_SLIDE, - gSprites[gHealthboxSpriteIds[battlerId]].y - DOUBLES_MEGA_TRIGGER_POS_Y_DIFF, 0); - else - gBattleStruct->mega.triggerSpriteId = CreateSprite(&sSpriteTemplate_MegaTrigger, - gSprites[gHealthboxSpriteIds[battlerId]].x - SINGLES_MEGA_TRIGGER_POS_X_SLIDE, - gSprites[gHealthboxSpriteIds[battlerId]].y - SINGLES_MEGA_TRIGGER_POS_Y_DIFF, 0); - } - gSprites[gBattleStruct->mega.triggerSpriteId].tBattler = battlerId; - gSprites[gBattleStruct->mega.triggerSpriteId].tHide = FALSE; - - ChangeMegaTriggerSprite(gBattleStruct->mega.triggerSpriteId, palId); -} - -static void SpriteCb_MegaTrigger(struct Sprite *sprite) -{ - s32 xSlide, xPriority, xOptimal; - s32 yDiff; - - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) - { - xSlide = DOUBLES_MEGA_TRIGGER_POS_X_SLIDE; - xPriority = DOUBLES_MEGA_TRIGGER_POS_X_PRIORITY; - xOptimal = DOUBLES_MEGA_TRIGGER_POS_X_OPTIMAL; - yDiff = DOUBLES_MEGA_TRIGGER_POS_Y_DIFF; - } - else - { - xSlide = SINGLES_MEGA_TRIGGER_POS_X_SLIDE; - xPriority = SINGLES_MEGA_TRIGGER_POS_X_PRIORITY; - xOptimal = SINGLES_MEGA_TRIGGER_POS_X_OPTIMAL; - yDiff = SINGLES_MEGA_TRIGGER_POS_Y_DIFF; - } - - if (sprite->tHide) - { - if (sprite->x != gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xSlide) - sprite->x++; - - if (sprite->x >= gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xPriority) - sprite->oam.priority = 2; - else - sprite->oam.priority = 1; - - sprite->y = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y - yDiff; - sprite->y2 = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y2 - yDiff; - if (sprite->x == gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xSlide) - DestroyMegaTriggerSprite(); - } - else - { - if (sprite->x != gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xOptimal) - sprite->x--; - - if (sprite->x >= gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xPriority) - sprite->oam.priority = 2; - else - sprite->oam.priority = 1; - - sprite->y = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y - yDiff; - sprite->y2 = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y2 - yDiff; - } -} - -bool32 IsMegaTriggerSpriteActive(void) -{ - if (GetSpriteTileStartByTag(TAG_MEGA_TRIGGER_TILE) == 0xFFFF) - return FALSE; - else if (IndexOfSpritePaletteTag(TAG_MEGA_TRIGGER_PAL) != 0xFF) - return TRUE; - else - return FALSE; -} - -void HideMegaTriggerSprite(void) -{ - if (gBattleStruct->mega.triggerSpriteId >= MAX_SPRITES) - return; - ChangeMegaTriggerSprite(gBattleStruct->mega.triggerSpriteId, 0); - gSprites[gBattleStruct->mega.triggerSpriteId].tHide = TRUE; -} - -void HideTriggerSprites(void) -{ - HideMegaTriggerSprite(); - HideBurstTriggerSprite(); - HideZMoveTriggerSprite(); - HideDynamaxTriggerSprite(); - HideTeraTriggerSprite(); -} - -void DestroyMegaTriggerSprite(void) -{ - FreeSpritePaletteByTag(TAG_MEGA_TRIGGER_PAL); - FreeSpriteTilesByTag(TAG_MEGA_TRIGGER_TILE); - if (gBattleStruct->mega.triggerSpriteId != 0xFF) - DestroySprite(&gSprites[gBattleStruct->mega.triggerSpriteId]); - gBattleStruct->mega.triggerSpriteId = 0xFF; -} - -#undef tBattler -#undef tHide - -// Ultra Burst Trigger icon functions. -void ChangeBurstTriggerSprite(u8 spriteId, u8 animId) -{ - StartSpriteAnim(&gSprites[spriteId], animId); -} - -#define SINGLES_BURST_TRIGGER_POS_X_OPTIMAL (30) -#define SINGLES_BURST_TRIGGER_POS_X_PRIORITY (31) -#define SINGLES_BURST_TRIGGER_POS_X_SLIDE (15) -#define SINGLES_BURST_TRIGGER_POS_Y_DIFF (-11) - -#define DOUBLES_BURST_TRIGGER_POS_X_OPTIMAL (30) -#define DOUBLES_BURST_TRIGGER_POS_X_PRIORITY (31) -#define DOUBLES_BURST_TRIGGER_POS_X_SLIDE (15) -#define DOUBLES_BURST_TRIGGER_POS_Y_DIFF (-4) - -#define tBattler data[0] -#define tHide data[1] - -void CreateBurstTriggerSprite(u8 battlerId, u8 palId) -{ - LoadSpritePalette(&sSpritePalette_BurstTrigger); - if (GetSpriteTileStartByTag(TAG_BURST_TRIGGER_TILE) == 0xFFFF) - LoadSpriteSheet(&sSpriteSheet_BurstTrigger); - if (gBattleStruct->burst.triggerSpriteId == 0xFF) - { - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) - gBattleStruct->burst.triggerSpriteId = CreateSprite(&sSpriteTemplate_BurstTrigger, - gSprites[gHealthboxSpriteIds[battlerId]].x - DOUBLES_BURST_TRIGGER_POS_X_SLIDE, - gSprites[gHealthboxSpriteIds[battlerId]].y - DOUBLES_BURST_TRIGGER_POS_Y_DIFF, 0); - else - gBattleStruct->burst.triggerSpriteId = CreateSprite(&sSpriteTemplate_BurstTrigger, - gSprites[gHealthboxSpriteIds[battlerId]].x - SINGLES_BURST_TRIGGER_POS_X_SLIDE, - gSprites[gHealthboxSpriteIds[battlerId]].y - SINGLES_BURST_TRIGGER_POS_Y_DIFF, 0); - } - gSprites[gBattleStruct->burst.triggerSpriteId].tBattler = battlerId; - gSprites[gBattleStruct->burst.triggerSpriteId].tHide = FALSE; - - ChangeBurstTriggerSprite(gBattleStruct->burst.triggerSpriteId, palId); -} - -static void SpriteCb_BurstTrigger(struct Sprite *sprite) -{ - s32 xSlide, xPriority, xOptimal; - s32 yDiff; - - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) - { - xSlide = DOUBLES_BURST_TRIGGER_POS_X_SLIDE; - xPriority = DOUBLES_BURST_TRIGGER_POS_X_PRIORITY; - xOptimal = DOUBLES_BURST_TRIGGER_POS_X_OPTIMAL; - yDiff = DOUBLES_BURST_TRIGGER_POS_Y_DIFF; - } - else - { - xSlide = SINGLES_BURST_TRIGGER_POS_X_SLIDE; - xPriority = SINGLES_BURST_TRIGGER_POS_X_PRIORITY; - xOptimal = SINGLES_BURST_TRIGGER_POS_X_OPTIMAL; - yDiff = SINGLES_BURST_TRIGGER_POS_Y_DIFF; - } - - if (sprite->tHide) - { - if (sprite->x != gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xSlide) - sprite->x++; - - if (sprite->x >= gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xPriority) - sprite->oam.priority = 2; - else - sprite->oam.priority = 1; - - sprite->y = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y - yDiff; - sprite->y2 = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y2 - yDiff; - if (sprite->x == gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xSlide) - DestroyBurstTriggerSprite(); - } - else - { - if (sprite->x != gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xOptimal) - sprite->x--; - - if (sprite->x >= gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xPriority) - sprite->oam.priority = 2; - else - sprite->oam.priority = 1; - - sprite->y = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y - yDiff; - sprite->y2 = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y2 - yDiff; - } -} - -bool32 IsBurstTriggerSpriteActive(void) -{ - if (GetSpriteTileStartByTag(TAG_BURST_TRIGGER_TILE) == 0xFFFF) - return FALSE; - else if (IndexOfSpritePaletteTag(TAG_BURST_TRIGGER_PAL) != 0xFF) - return TRUE; - else - return FALSE; -} - -void HideBurstTriggerSprite(void) -{ - if (gBattleStruct->burst.triggerSpriteId >= MAX_SPRITES) - return; - ChangeBurstTriggerSprite(gBattleStruct->burst.triggerSpriteId, 0); - gSprites[gBattleStruct->burst.triggerSpriteId].tHide = TRUE; -} - -void DestroyBurstTriggerSprite(void) -{ - FreeSpritePaletteByTag(TAG_BURST_TRIGGER_PAL); - FreeSpriteTilesByTag(TAG_BURST_TRIGGER_TILE); - if (gBattleStruct->burst.triggerSpriteId != 0xFF) - DestroySprite(&gSprites[gBattleStruct->burst.triggerSpriteId]); - gBattleStruct->burst.triggerSpriteId = 0xFF; -} - -#undef tBattler -#undef tHide - - -// Code for Mega Evolution (And Alpha/Omega) Trigger icon visible on the battler's healthbox. -enum -{ - INDICATOR_MEGA, - INDICATOR_ALPHA, - INDICATOR_OMEGA, - INDICATOR_DYNAMAX, - INDICATOR_COUNT, -}; - -static const u8 ALIGNED(4) sMegaIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/mega_indicator.4bpp"); -static const u16 sMegaIndicatorPal[] = INCBIN_U16("graphics/battle_interface/mega_indicator.gbapal"); -static const u8 ALIGNED(4) sAlphaIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/alpha_indicator.4bpp"); -static const u8 ALIGNED(4) sOmegaIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/omega_indicator.4bpp"); -static const u16 sAlphaOmegaIndicatorPal[] = INCBIN_U16("graphics/battle_interface/misc_indicator.gbapal"); -static const u8 ALIGNED(4) sDynamaxIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/dynamax_indicator.4bpp"); -static const u16 sDynamaxIndicatorPal[] = INCBIN_U16("graphics/battle_interface/misc_indicator.gbapal"); - -static const struct SpriteSheet sMegaIndicator_SpriteSheets[] = -{ - [INDICATOR_MEGA] = {sMegaIndicatorGfx, sizeof(sMegaIndicatorGfx), TAG_MEGA_INDICATOR_TILE}, - [INDICATOR_ALPHA] = {sAlphaIndicatorGfx, sizeof(sAlphaIndicatorGfx), TAG_ALPHA_INDICATOR_TILE}, - [INDICATOR_OMEGA] = {sOmegaIndicatorGfx, sizeof(sOmegaIndicatorGfx), TAG_OMEGA_INDICATOR_TILE}, - [INDICATOR_DYNAMAX] = {sDynamaxIndicatorGfx, sizeof(sDynamaxIndicatorGfx), TAG_DYNAMAX_INDICATOR_TILE}, - [INDICATOR_COUNT] = {0} -}; -static const struct SpritePalette sMegaIndicator_SpritePalettes[] = -{ - [INDICATOR_MEGA] = {sMegaIndicatorPal, TAG_MEGA_INDICATOR_PAL}, - [INDICATOR_ALPHA] = {sAlphaOmegaIndicatorPal, TAG_MISC_INDICATOR_PAL}, - [INDICATOR_OMEGA] = {sAlphaOmegaIndicatorPal, TAG_MISC_INDICATOR_PAL}, - [INDICATOR_DYNAMAX] = {sDynamaxIndicatorPal, TAG_MISC_INDICATOR_PAL}, - [INDICATOR_COUNT] = {0} -}; - -static const struct OamData sOamData_MegaIndicator = -{ - .shape = SPRITE_SHAPE(16x16), - .size = SPRITE_SIZE(16x16), - .priority = 1, -}; - -static const struct SpriteTemplate sSpriteTemplate_MegaIndicator = -{ - .tileTag = TAG_MEGA_INDICATOR_TILE, - .paletteTag = TAG_MEGA_INDICATOR_PAL, - .oam = &sOamData_MegaIndicator, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_MegaIndicator, -}; - -static const u16 sMegaIndicatorTags[][2] = -{ - [INDICATOR_MEGA] = {TAG_MEGA_INDICATOR_TILE, TAG_MEGA_INDICATOR_PAL}, - [INDICATOR_ALPHA] = {TAG_ALPHA_INDICATOR_TILE, TAG_MISC_INDICATOR_PAL}, - [INDICATOR_OMEGA] = {TAG_OMEGA_INDICATOR_TILE, TAG_MISC_INDICATOR_PAL}, - [INDICATOR_DYNAMAX] = {TAG_DYNAMAX_INDICATOR_TILE, TAG_MISC_INDICATOR_PAL}, -}; - -static const s8 sIndicatorPositions[][2] = -{ - [B_POSITION_PLAYER_LEFT] = {52, -9}, - [B_POSITION_OPPONENT_LEFT] = {44, -9}, - [B_POSITION_PLAYER_RIGHT] = {52, -9}, - [B_POSITION_OPPONENT_RIGHT] = {44, -9}, -}; - -// for sprite data fields -#define tBattler data[0] -#define tType data[1] // Indicator type: mega, alpha, omega -#define tPosX data[2] -#define tLevelXDelta data[3] // X position depends whether level has 3, 2 or 1 digit - -void MegaIndicator_LoadSpritesGfx(void) -{ - LoadSpriteSheets(sMegaIndicator_SpriteSheets); - LoadSpritePalettes(sMegaIndicator_SpritePalettes); -} - -static bool32 MegaIndicator_ShouldBeInvisible(u32 battlerId, struct Sprite *sprite) -{ - bool32 megaEvolved = IsBattlerMegaEvolved(battlerId); - bool32 primalReverted = IsBattlerPrimalReverted(battlerId); - bool32 dynamaxed = IsDynamaxed(battlerId); - - if (!megaEvolved && !primalReverted && !dynamaxed) - return TRUE; - - if (megaEvolved) - sprite->tType = INDICATOR_MEGA; - else if (primalReverted && gBattleMons[battlerId].species == SPECIES_KYOGRE_PRIMAL) - sprite->tType = INDICATOR_ALPHA; - else if (primalReverted && gBattleMons[battlerId].species == SPECIES_GROUDON_PRIMAL) - sprite->tType = INDICATOR_OMEGA; - else if (dynamaxed) - sprite->tType = INDICATOR_DYNAMAX; - - sprite->oam.tileNum = GetSpriteTileStartByTag(sMegaIndicatorTags[sprite->tType][0]); - sprite->oam.paletteNum = IndexOfSpritePaletteTag(sMegaIndicatorTags[sprite->tType][1]); - return FALSE; -} - -static u8 *MegaIndicator_GetSpriteId(u32 healthboxSpriteId) -{ - u8 *spriteId = (u8 *)(&gSprites[healthboxSpriteId].hMain_MegaIndicatorId); - return spriteId; -} - -void MegaIndicator_SetVisibilities(u32 healthboxId, bool32 invisible) -{ - u8 *spriteId = MegaIndicator_GetSpriteId(healthboxId); - u32 battlerId = gSprites[healthboxId].hMain_Battler; - - if (GetSafariZoneFlag()) - return; - - if (invisible == TRUE) - gSprites[*spriteId].invisible = TRUE; - else // Try visible. - gSprites[*spriteId].invisible = MegaIndicator_ShouldBeInvisible(battlerId, &gSprites[*spriteId]); -} - -static void MegaIndicator_UpdateOamPriority(u32 healthboxId, u32 oamPriority) -{ - u8 *spriteId = MegaIndicator_GetSpriteId(healthboxId); - gSprites[*spriteId].oam.priority = oamPriority; -} - -static void MegaIndicator_UpdateLevel(u32 healthboxId, u32 level) -{ - s16 xDelta = 0; - u8 *spriteId = MegaIndicator_GetSpriteId(healthboxId); - - if (level >= 100) - xDelta -= 4; - else if (level < 10) - xDelta += 5; - - gSprites[*spriteId].tLevelXDelta = xDelta; -} - -static void MegaIndicator_CreateSprite(u32 battlerId, u32 healthboxSpriteId) -{ - struct SpriteTemplate sprTemplate; - u32 position; - u8 *spriteId; - s16 xHealthbox = 0, y = 0; - s32 x = 0; - - position = GetBattlerPosition(battlerId); - GetBattlerHealthboxCoords(battlerId, &xHealthbox, &y); - - x = sIndicatorPositions[position][0]; - y += sIndicatorPositions[position][1]; - - spriteId = MegaIndicator_GetSpriteId(healthboxSpriteId); - sprTemplate = sSpriteTemplate_MegaIndicator; - sprTemplate.tileTag = sMegaIndicatorTags[INDICATOR_MEGA][0]; - sprTemplate.paletteTag = sMegaIndicatorTags[INDICATOR_MEGA][1]; - *spriteId = CreateSpriteAtEnd(&sprTemplate, 0, y, 0); - gSprites[*spriteId].tType = INDICATOR_MEGA; - gSprites[*spriteId].tBattler = battlerId; - gSprites[*spriteId].tPosX = x; - gSprites[*spriteId].invisible = TRUE; -} - -static void SpriteCb_MegaIndicator(struct Sprite *sprite) -{ - u32 battlerId = sprite->tBattler; - - sprite->x = gSprites[gHealthboxSpriteIds[battlerId]].x + sprite->tPosX + sprite->tLevelXDelta; - sprite->x2 = gSprites[gHealthboxSpriteIds[battlerId]].x2; - sprite->y2 = gSprites[gHealthboxSpriteIds[battlerId]].y2; -} - #undef tBattler #undef tType #undef tPosX @@ -2545,10 +1978,6 @@ void UpdateHealthboxAttribute(u8 healthboxSpriteId, struct Pokemon *mon, u8 elem u32 battlerId = gSprites[healthboxSpriteId].hMain_Battler; s32 maxHp = GetMonData(mon, MON_DATA_MAX_HP); s32 currHp = GetMonData(mon, MON_DATA_HP); - - // This fixes a bug that should likely never happen involving switching between two Teras. - if (elementId == HEALTHBOX_ALL) - TeraIndicator_UpdateType(battlerId, healthboxSpriteId); if (GetBattlerSide(battlerId) == B_SIDE_PLAYER) { diff --git a/src/battle_main.c b/src/battle_main.c index b625ae5b53..ad66b4d2f3 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -13,6 +13,8 @@ #include "battle_setup.h" #include "battle_tower.h" #include "battle_util.h" +#include "battle_z_move.h" +#include "battle_gimmick.h" #include "berry.h" #include "bg.h" #include "data.h" @@ -3097,9 +3099,6 @@ static void BattleStartClearSetData(void) gBattleStruct->arenaLostPlayerMons = 0; gBattleStruct->arenaLostOpponentMons = 0; - gBattleStruct->mega.triggerSpriteId = 0xFF; - gBattleStruct->burst.triggerSpriteId = 0xFF; - for (i = 0; i < ARRAY_COUNT(gSideTimers); i++) { gSideTimers[i].stickyWebBattlerId = 0xFF; @@ -3111,13 +3110,16 @@ static void BattleStartClearSetData(void) { gBattleStruct->usedHeldItems[i][B_SIDE_PLAYER] = 0; gBattleStruct->usedHeldItems[i][B_SIDE_OPPONENT] = 0; - gBattleStruct->itemLost[i].originalItem = GetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM); + 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 + gBattleStruct->categoryOverride = FALSE; // used for Z-Moves and Max Moves + gSelectedMonPartyId = PARTY_SIZE; // Revival Blessing gCategoryIconSpriteId = 0xFF; } @@ -3247,9 +3249,6 @@ void SwitchInClearSetData(u32 battler) // Reset G-Max Chi Strike boosts. gBattleStruct->bonusCritStages[battler] = 0; - // Reset Dynamax flags. - UndoDynamax(battler); - gBattleStruct->overwrittenAbilities[battler] = ABILITY_NONE; // Clear selected party ID so Revival Blessing doesn't get confused. @@ -3416,10 +3415,6 @@ const u8* FaintClearSetData(u32 battler) } } - // Clear Z-Move data - gBattleStruct->zmove.active = FALSE; - gBattleStruct->zmove.toBeUsed[battler] = MOVE_NONE; - gBattleStruct->zmove.effect = EFFECT_HIT; // Clear Dynamax data UndoDynamax(battler); @@ -3881,6 +3876,7 @@ static void TryDoEventsBeforeFirstTurn(void) SpecialStatusesClear(); *(&gBattleStruct->absentBattlerFlags) = gAbsentBattlerFlags; BattlePutTextOnWindow(gText_EmptyString3, B_WIN_MSG); + AssignUsableGimmicks(); gBattleMainFunc = HandleTurnActionSelectionState; ResetSentPokesToOpponentValue(); @@ -3999,6 +3995,7 @@ void BattleTurnPassed(void) *(&gBattleStruct->absentBattlerFlags) = gAbsentBattlerFlags; BattlePutTextOnWindow(gText_EmptyString3, B_WIN_MSG); + AssignUsableGimmicks(); SetShellSideArmCategory(); SetAiLogicDataForTurn(AI_DATA); // get assumed abilities, hold effects, etc of all battlers gBattleMainFunc = HandleTurnActionSelectionState; @@ -4226,7 +4223,6 @@ static void HandleTurnActionSelectionState(void) struct ChooseMoveStruct moveInfo; moveInfo.zmove = gBattleStruct->zmove; - moveInfo.mega = gBattleStruct->mega; moveInfo.species = gBattleMons[battler].species; moveInfo.monType1 = gBattleMons[battler].type1; moveInfo.monType2 = gBattleMons[battler].type2; @@ -4351,12 +4347,7 @@ static void HandleTurnActionSelectionState(void) RecordedBattle_ClearBattlerAction(GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler))), 3); } - gBattleStruct->mega.toEvolve &= ~(gBitTable[BATTLE_PARTNER(GetBattlerPosition(battler))]); - gBattleStruct->burst.toBurst &= ~(gBitTable[BATTLE_PARTNER(GetBattlerPosition(battler))]); - gBattleStruct->dynamax.toDynamax &= ~(gBitTable[BATTLE_PARTNER(GetBattlerPosition(battler))]); - gBattleStruct->tera.toTera &= ~(gBitTable[BATTLE_PARTNER(GetBattlerPosition(battler))]); - gBattleStruct->dynamax.usingMaxMove[BATTLE_PARTNER(GetBattlerPosition(battler))] = FALSE; - gBattleStruct->zmove.toBeUsed[BATTLE_PARTNER(GetBattlerPosition(battler))] = MOVE_NONE; + gBattleStruct->gimmick.toActivate &= ~(gBitTable[BATTLE_PARTNER(GetBattlerPosition(battler))]); BtlController_EmitEndBounceEffect(battler, BUFFER_A); MarkBattlerForControllerExec(battler); return; @@ -4444,25 +4435,18 @@ static void HandleTurnActionSelectionState(void) } // Get the chosen move position (and thus the chosen move) and target from the returned buffer. - gBattleStruct->chosenMovePositions[battler] = gBattleResources->bufferB[battler][2] & ~(RET_MEGA_EVOLUTION | RET_ULTRA_BURST | RET_DYNAMAX | RET_TERASTAL); + gBattleStruct->chosenMovePositions[battler] = gBattleResources->bufferB[battler][2] & ~RET_GIMMICK; gChosenMoveByBattler[battler] = gBattleMons[battler].moves[gBattleStruct->chosenMovePositions[battler]]; gBattleStruct->moveTarget[battler] = gBattleResources->bufferB[battler][3]; // Check to see if any gimmicks need to be prepared. - if (gBattleResources->bufferB[battler][2] & RET_MEGA_EVOLUTION) - gBattleStruct->mega.toEvolve |= gBitTable[battler]; - else if (gBattleResources->bufferB[battler][2] & RET_ULTRA_BURST) - gBattleStruct->burst.toBurst |= gBitTable[battler]; - else if (gBattleResources->bufferB[battler][2] & RET_DYNAMAX) - gBattleStruct->dynamax.toDynamax |= gBitTable[battler]; - else if (gBattleResources->bufferB[battler][2] & RET_TERASTAL) - gBattleStruct->tera.toTera |= gBitTable[battler]; + if (gBattleResources->bufferB[battler][2] & RET_GIMMICK) + gBattleStruct->gimmick.toActivate |= gBitTable[battler]; // Max Move check - if (ShouldUseMaxMove(battler, gChosenMoveByBattler[battler])) + if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX || IsGimmickSelected(battler, GIMMICK_DYNAMAX)) { - gBattleStruct->dynamax.baseMove[battler] = gBattleMons[battler].moves[gBattleStruct->chosenMovePositions[battler]]; - gBattleStruct->dynamax.usingMaxMove[battler] = TRUE; + gBattleStruct->dynamax.baseMoves[battler] = gBattleMons[battler].moves[gBattleStruct->chosenMovePositions[battler]]; } gBattleCommunication[battler]++; @@ -4738,7 +4722,7 @@ u32 GetBattlerTotalSpeedStatArgs(u32 battler, u32 ability, u32 holdEffect) speed /= 2; else if (holdEffect == HOLD_EFFECT_IRON_BALL) speed /= 2; - else if (holdEffect == HOLD_EFFECT_CHOICE_SCARF && !IsDynamaxed(battler)) + else if (holdEffect == HOLD_EFFECT_CHOICE_SCARF && GetActiveGimmick(battler) != GIMMICK_DYNAMAX) speed = (speed * 150) / 100; else if (holdEffect == HOLD_EFFECT_QUICK_POWDER && gBattleMons[battler].species == SPECIES_DITTO && !(gBattleMons[battler].status2 & STATUS2_TRANSFORMED)) speed *= 2; @@ -4784,13 +4768,13 @@ s8 GetMovePriority(u32 battler, u16 move) s8 priority; u16 ability = GetBattlerAbility(battler); - if (gBattleStruct->zmove.toBeUsed[battler] && gMovesInfo[move].power != 0) - move = gBattleStruct->zmove.toBeUsed[battler]; + if (GetActiveGimmick(battler) == GIMMICK_Z_MOVE && gMovesInfo[move].power != 0) + move = GetUsableZMove(battler, move); priority = gMovesInfo[move].priority; // Max Guard check - if (gBattleStruct->dynamax.usingMaxMove[battler] && gMovesInfo[move].category == DAMAGE_CATEGORY_STATUS) + if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX && gMovesInfo[move].category == DAMAGE_CATEGORY_STATUS) return gMovesInfo[MOVE_MAX_GUARD].priority; if (ability == ABILITY_GALE_WINGS @@ -4804,7 +4788,7 @@ s8 GetMovePriority(u32 battler, u16 move) gProtectStructs[battler].pranksterElevated = 1; priority++; } - else if (gMovesInfo[move].effect == EFFECT_GRASSY_GLIDE && gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN && IsBattlerGrounded(battler) && !IsDynamaxed(battler) && !(gBattleStruct->dynamax.toDynamax & gBitTable[battler])) + else if (gMovesInfo[move].effect == EFFECT_GRASSY_GLIDE && gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN && IsBattlerGrounded(battler) && GetActiveGimmick(gBattlerAttacker) != GIMMICK_DYNAMAX && !IsGimmickSelected(battler, GIMMICK_DYNAMAX)) { priority++; } @@ -5069,9 +5053,7 @@ static void PopulateArrayWithBattlers(u8 *battlers) static bool32 TryDoGimmicksBeforeMoves(void) { - if (!(gHitMarker & HITMARKER_RUN) - && (gBattleStruct->mega.toEvolve || gBattleStruct->burst.toBurst - || gBattleStruct->dynamax.toDynamax || gBattleStruct->tera.toTera)) + if (!(gHitMarker & HITMARKER_RUN) && gBattleStruct->gimmick.toActivate) { u32 i, battler; u8 order[MAX_BATTLERS_COUNT]; @@ -5080,52 +5062,16 @@ static bool32 TryDoGimmicksBeforeMoves(void) SortBattlersBySpeed(order, FALSE); for (i = 0; i < gBattlersCount; i++) { - // Tera Check - if (gBattleStruct->tera.toTera & gBitTable[order[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)) { - gBattlerAttacker = order[i]; - gBattleScripting.battler = gBattlerAttacker; - gBattleStruct->tera.toTera &= ~(gBitTable[gBattlerAttacker]); - PrepareBattlerForTera(gBattlerAttacker); - PREPARE_TYPE_BUFFER(gBattleTextBuff1, GetBattlerTeraType(gBattlerAttacker)); - if (TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_TERASTALLIZATION)) - BattleScriptExecute(BattleScript_TeraFormChange); - else - BattleScriptExecute(BattleScript_Terastallization); - return TRUE; - } - // Dynamax Check - if (gBattleStruct->dynamax.toDynamax & gBitTable[order[i]]) - { - gBattlerAttacker = order[i]; - gBattleScripting.battler = gBattlerAttacker; - gBattleStruct->dynamax.toDynamax &= ~(gBitTable[gBattlerAttacker]); - PrepareBattlerForDynamax(gBattlerAttacker); - BattleScriptExecute(BattleScript_DynamaxBegins); - return TRUE; - } - // Mega Evo Check - if (gBattleStruct->mega.toEvolve & gBitTable[order[i]] - && !(gProtectStructs[order[i]].noValidMoves)) - { - gBattlerAttacker = order[i]; - gBattleStruct->mega.toEvolve &= ~(gBitTable[gBattlerAttacker]); - gLastUsedItem = gBattleMons[gBattlerAttacker].item; - if (GetBattleFormChangeTargetSpecies(gBattlerAttacker, FORM_CHANGE_BATTLE_MEGA_EVOLUTION_MOVE) != SPECIES_NONE) - BattleScriptExecute(BattleScript_WishMegaEvolution); - else - BattleScriptExecute(BattleScript_MegaEvolution); - return TRUE; - } - // Ultra Burst Check - if (gBattleStruct->burst.toBurst & gBitTable[order[i]] - && !(gProtectStructs[order[i]].noValidMoves)) - { - battler = gBattlerAttacker = order[i]; - gBattleStruct->burst.toBurst &= ~(gBitTable[battler]); - gLastUsedItem = gBattleMons[battler].item; - BattleScriptExecute(BattleScript_UltraBurst); - return TRUE; + battler = gBattlerAttacker = gBattleScripting.battler = order[i]; + gBattleStruct->gimmick.toActivate &= ~(gBitTable[battler]); + if (gGimmicksInfo[gBattleStruct->gimmick.usableGimmick[battler]].ActivateGimmick != NULL) + { + gGimmicksInfo[gBattleStruct->gimmick.usableGimmick[battler]].ActivateGimmick(battler); + return TRUE; + } } } } @@ -5512,10 +5458,10 @@ static void HandleEndTurn_FinishBattle(void) TryRestoreHeldItems(); // Undo Dynamax HP multiplier before recalculating stats. - for (i = 0; i < gBattlersCount; ++i) + for (battler = 0; battler < gBattlersCount; ++battler) { - if (IsDynamaxed(i)) - UndoDynamax(i); + if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX) + UndoDynamax(battler); } for (i = 0; i < PARTY_SIZE; i++) @@ -5588,8 +5534,9 @@ static void FreeResetData_ReturnToOvOrDoEvolutions(void) } FreeAllWindowBuffers(); - if (!(gBattleTypeFlags & BATTLE_TYPE_LINK)) + if (gBattleStruct != NULL && !(gBattleTypeFlags & BATTLE_TYPE_LINK)) { + ZeroEnemyPartyMons(); FreeMonSpritesGfx(); FreeBattleResources(); FreeBattleSpritesData(); @@ -5699,7 +5646,7 @@ bool32 TrySetAteType(u32 move, u32 battlerAtk, u32 attackerAbility) switch (gMovesInfo[move].effect) { case EFFECT_TERA_BLAST: - if (IsTerastallized(battlerAtk)) + if (GetActiveGimmick(battlerAtk) == GIMMICK_TERA) return FALSE; break; case EFFECT_TERA_STARSTORM: @@ -5733,7 +5680,7 @@ bool32 TrySetAteType(u32 move, u32 battlerAtk, u32 attackerAbility) break; } - if (ateType != TYPE_NONE) + if (ateType != TYPE_NONE && GetActiveGimmick(battlerAtk) != GIMMICK_Z_MOVE) { gBattleStruct->dynamicMoveType = ateType | F_DYNAMIC_TYPE_SET; return TRUE; @@ -5792,7 +5739,7 @@ void SetTypeBeforeUsingMove(u32 move, u32 battlerAtk) } else if (gMovesInfo[move].effect == EFFECT_REVELATION_DANCE) { - if (IsTerastallized(battlerAtk) && GetBattlerTeraType(battlerAtk) != TYPE_STELLAR) + if (GetActiveGimmick(battlerAtk) == GIMMICK_TERA && GetBattlerTeraType(battlerAtk) != TYPE_STELLAR) gBattleStruct->dynamicMoveType = GetBattlerTeraType(battlerAtk); else if (gBattleMons[battlerAtk].type1 != TYPE_MYSTERY) gBattleStruct->dynamicMoveType = gBattleMons[battlerAtk].type1 | F_DYNAMIC_TYPE_SET; @@ -5836,7 +5783,7 @@ void SetTypeBeforeUsingMove(u32 move, u32 battlerAtk) gBattleStruct->dynamicMoveType = TYPE_NORMAL | F_DYNAMIC_TYPE_SET; } } - else if (gMovesInfo[move].effect == EFFECT_TERA_BLAST && IsTerastallized(battlerAtk)) + else if (gMovesInfo[move].effect == EFFECT_TERA_BLAST && GetActiveGimmick(battlerAtk) == GIMMICK_TERA) { gBattleStruct->dynamicMoveType = GetBattlerTeraType(battlerAtk) | F_DYNAMIC_TYPE_SET; } @@ -5846,18 +5793,20 @@ void SetTypeBeforeUsingMove(u32 move, u32 battlerAtk) } attackerAbility = GetBattlerAbility(battlerAtk); - if (gMovesInfo[move].type == TYPE_NORMAL && TrySetAteType(move, battlerAtk, attackerAbility)) + if (gMovesInfo[move].type == TYPE_NORMAL + && TrySetAteType(move, battlerAtk, attackerAbility) + && GetActiveGimmick(battlerAtk) != GIMMICK_DYNAMAX) { - if (!IsDynamaxed(battlerAtk)) gBattleStruct->ateBoost[battlerAtk] = 1; } else if (gMovesInfo[move].type != TYPE_NORMAL && gMovesInfo[move].effect != EFFECT_HIDDEN_POWER && gMovesInfo[move].effect != EFFECT_WEATHER_BALL - && attackerAbility == ABILITY_NORMALIZE) + && attackerAbility == ABILITY_NORMALIZE + && GetActiveGimmick(battlerAtk) != GIMMICK_Z_MOVE) { gBattleStruct->dynamicMoveType = TYPE_NORMAL | F_DYNAMIC_TYPE_SET; - if (!IsDynamaxed(battlerAtk)) + if (GetActiveGimmick(battlerAtk) != GIMMICK_DYNAMAX) gBattleStruct->ateBoost[battlerAtk] = 1; } else if (gMovesInfo[move].soundMove && attackerAbility == ABILITY_LIQUID_VOICE) diff --git a/src/battle_message.c b/src/battle_message.c index 5d416ed4bf..9ae2e6aee0 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -3967,7 +3967,10 @@ void BattlePutTextOnWindow(const u8 *text, u8 windowId) // We cannot check the actual width of the window because // B_WIN_MOVE_NAME_1 and B_WIN_MOVE_NAME_3 are 16 wide for // Z-move details. - printerTemplate.fontId = GetFontIdToFit(text, printerTemplate.fontId, printerTemplate.letterSpacing, 8 * TILE_WIDTH); + if (gBattleStruct->zmove.viewing && windowId == B_WIN_MOVE_NAME_1) + printerTemplate.fontId = GetFontIdToFit(text, printerTemplate.fontId, printerTemplate.letterSpacing, 16 * TILE_WIDTH); + else + printerTemplate.fontId = GetFontIdToFit(text, printerTemplate.fontId, printerTemplate.letterSpacing, 8 * TILE_WIDTH); } if (printerTemplate.x == 0xFF) @@ -4023,7 +4026,7 @@ void SetPpNumbersPaletteInMoveSelection(u32 battler) var = GetCurrentPpToMaxPpState(chooseMoveStruct->currentPp[gMoveSelectionCursor[battler]], chooseMoveStruct->maxPp[gMoveSelectionCursor[battler]]); else - var = GetCurrentPpToMaxPpState(chooseMoveStruct->currentPp[gMoveSelectionCursor[battler]], gMovesInfo[gMoveSelectionCursor[battler]].pp); + var = 3; gPlttBufferUnfaded[BG_PLTT_ID(5) + 12] = palPtr[(var * 2) + 0]; gPlttBufferUnfaded[BG_PLTT_ID(5) + 11] = palPtr[(var * 2) + 1]; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index a44690e85f..63c6fdcf5c 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1163,7 +1163,7 @@ bool32 ProteanTryChangeType(u32 battler, u32 ability, u32 move, u32 moveType) && (gBattleMons[battler].type1 != moveType || gBattleMons[battler].type2 != moveType || (gBattleMons[battler].type3 != moveType && gBattleMons[battler].type3 != TYPE_MYSTERY)) && move != MOVE_STRUGGLE - && !IsTerastallized(battler)) + && GetActiveGimmick(battler) != GIMMICK_TERA) { SET_BATTLER_TYPE(battler, moveType); return TRUE; @@ -1198,7 +1198,7 @@ static void Cmd_attackcanceler(void) GET_MOVE_TYPE(gCurrentMove, moveType); // Weight-based moves are blocked by Dynamax. - if (IsDynamaxed(gBattlerTarget) && IsMoveBlockedByDynamax(gCurrentMove)) + if ((GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX) && IsMoveBlockedByDynamax(gCurrentMove)) { BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_MoveBlockedByDynamax; @@ -1243,7 +1243,7 @@ static void Cmd_attackcanceler(void) && GetBattlerAbility(gBattlerAttacker) == ABILITY_PARENTAL_BOND && IsMoveAffectedByParentalBond(gCurrentMove, gBattlerAttacker) && !(gAbsentBattlerFlags & gBitTable[gBattlerTarget]) - && gBattleStruct->zmove.toBeUsed[gBattlerAttacker] == MOVE_NONE) + && GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE) { gSpecialStatuses[gBattlerAttacker].parentalBondState = PARENTAL_BOND_1ST_HIT; gMultiHitCounter = 2; @@ -1364,7 +1364,8 @@ static void Cmd_attackcanceler(void) } // Z-moves and Max Moves bypass protection, but deal reduced damage (factored in AccumulateOtherModifiers) - if ((gBattleStruct->zmove.active || IsMaxMove(gCurrentMove)) + if ((IsZMove(gCurrentMove) + || IsMaxMove(gCurrentMove)) && IS_BATTLER_PROTECTED(gBattlerTarget)) { BattleScriptPush(cmd->nextInstr); @@ -1513,7 +1514,7 @@ static bool32 AccuracyCalcHelper(u16 move) return TRUE; } - if (gBattleStruct->zmove.active && !(gStatuses3[gBattlerTarget] & STATUS3_SEMI_INVULNERABLE)) + if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE && !(gStatuses3[gBattlerTarget] & STATUS3_SEMI_INVULNERABLE)) { JumpIfMoveFailed(7, move); return TRUE; @@ -1943,7 +1944,7 @@ static void Cmd_critcalc(void) else if (critChance == -2) gIsCriticalHit = TRUE; else - gIsCriticalHit = RandomWeighted(RNG_CRITICAL_HIT, sCriticalHitOdds[critChance] - 1, 1); + gIsCriticalHit = RandomChance(RNG_CRITICAL_HIT, 1, sCriticalHitOdds[critChance]); // Counter for EVO_CRITICAL_HITS. partySlot = gBattlerPartyIndexes[gBattlerAttacker]; @@ -1961,7 +1962,7 @@ static void Cmd_damagecalc(void) u8 moveType; GET_MOVE_TYPE(gCurrentMove, moveType); - if (gBattleStruct->shellSideArmCategory[gBattlerAttacker][gBattlerTarget] == DAMAGE_CATEGORY_SPECIAL && gCurrentMove == MOVE_SHELL_SIDE_ARM) + if (gBattleStruct->shellSideArmCategory[gBattlerAttacker][gBattlerTarget] == DAMAGE_CATEGORY_PHYSICAL && gCurrentMove == MOVE_SHELL_SIDE_ARM) gBattleStruct->swapDamageCategory = TRUE; gBattleMoveDamage = CalculateMoveDamage(gCurrentMove, gBattlerAttacker, gBattlerTarget, moveType, 0, gIsCriticalHit, TRUE, TRUE); gBattlescriptCurrInstr = cmd->nextInstr; @@ -2859,7 +2860,7 @@ void SetMoveEffect(bool32 primary, bool32 certain) if (i != gBattlersCount) break; - if (!CanSleep(gEffectBattler)) + if (!CanBeSlept(gEffectBattler, GetBattlerAbility(gEffectBattler))) break; cancelMultiTurnMovesResult = CancelMultiTurnMoves(gEffectBattler); @@ -2898,7 +2899,7 @@ void SetMoveEffect(bool32 primary, bool32 certain) gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_STATUS_HAD_NO_EFFECT; RESET_RETURN } - if (!CanBePoisoned(gBattleScripting.battler, gEffectBattler)) + if (!CanBePoisoned(gBattleScripting.battler, gEffectBattler, GetBattlerAbility(gEffectBattler))) break; statusChanged = TRUE; @@ -2942,7 +2943,7 @@ void SetMoveEffect(bool32 primary, bool32 certain) break; } - if (!CanBeBurned(gEffectBattler)) + if (!CanBeBurned(gEffectBattler, GetBattlerAbility(gEffectBattler))) break; statusChanged = TRUE; @@ -3007,7 +3008,7 @@ void SetMoveEffect(bool32 primary, bool32 certain) } if (!CanParalyzeType(gBattleScripting.battler, gEffectBattler)) break; - if (!CanBeParalyzed(gEffectBattler)) + if (!CanBeParalyzed(gEffectBattler, GetBattlerAbility(gEffectBattler))) break; statusChanged = TRUE; @@ -3045,7 +3046,7 @@ void SetMoveEffect(bool32 primary, bool32 certain) } if (gBattleMons[gEffectBattler].status1) break; - if (CanBePoisoned(gBattleScripting.battler, gEffectBattler)) + if (CanBePoisoned(gBattleScripting.battler, gEffectBattler, GetBattlerAbility(gEffectBattler))) { // It's redundant, because at this point we know the status1 value is 0. gBattleMons[gEffectBattler].status1 &= ~STATUS1_TOXIC_POISON; @@ -3178,7 +3179,7 @@ void SetMoveEffect(bool32 primary, bool32 certain) } } else if (GetBattlerTurnOrderNum(gEffectBattler) > gCurrentTurnActionNumber - && !IsDynamaxed(gEffectBattler)) + && !(GetActiveGimmick(gEffectBattler) == GIMMICK_DYNAMAX)) { gBattleMons[gEffectBattler].status2 |= sStatusFlagsForMoveEffects[gBattleScripting.moveEffect]; gBattlescriptCurrInstr++; @@ -3296,7 +3297,7 @@ void SetMoveEffect(bool32 primary, bool32 certain) if (NoAliveMonsForEitherParty() || ChangeStatBuffs(SET_STAT_BUFF_VALUE(1), gBattleScripting.moveEffect - MOVE_EFFECT_ATK_PLUS_1 + 1, - affectsUser | STAT_CHANGE_UPDATE_MOVE_EFFECT, 0)) + affectsUser | STAT_CHANGE_UPDATE_MOVE_EFFECT, 0) == STAT_CHANGE_DIDNT_WORK) { gBattlescriptCurrInstr++; } @@ -3326,7 +3327,7 @@ void SetMoveEffect(bool32 primary, bool32 certain) if (ChangeStatBuffs(SET_STAT_BUFF_VALUE(1) | STAT_BUFF_NEGATIVE, gBattleScripting.moveEffect - MOVE_EFFECT_ATK_MINUS_1 + 1, - flags, gBattlescriptCurrInstr + 1)) + flags, gBattlescriptCurrInstr + 1) == STAT_CHANGE_DIDNT_WORK) { if (!mirrorArmorReflected) gBattlescriptCurrInstr++; @@ -3349,7 +3350,7 @@ void SetMoveEffect(bool32 primary, bool32 certain) if (NoAliveMonsForEitherParty() || ChangeStatBuffs(SET_STAT_BUFF_VALUE(2), gBattleScripting.moveEffect - MOVE_EFFECT_ATK_PLUS_2 + 1, - affectsUser | STAT_CHANGE_UPDATE_MOVE_EFFECT, 0)) + affectsUser | STAT_CHANGE_UPDATE_MOVE_EFFECT, 0) == STAT_CHANGE_DIDNT_WORK) { gBattlescriptCurrInstr++; } @@ -3376,7 +3377,7 @@ void SetMoveEffect(bool32 primary, bool32 certain) flags |= STAT_CHANGE_ALLOW_PTR; if (ChangeStatBuffs(SET_STAT_BUFF_VALUE(2) | STAT_BUFF_NEGATIVE, gBattleScripting.moveEffect - MOVE_EFFECT_ATK_MINUS_2 + 1, - flags | STAT_CHANGE_UPDATE_MOVE_EFFECT, gBattlescriptCurrInstr + 1)) + flags | STAT_CHANGE_UPDATE_MOVE_EFFECT, gBattlescriptCurrInstr + 1) == STAT_CHANGE_DIDNT_WORK) { if (!mirrorArmorReflected) gBattlescriptCurrInstr++; @@ -3409,7 +3410,7 @@ void SetMoveEffect(bool32 primary, bool32 certain) } else if (GetBattlerAbility(gBattlerTarget) == ABILITY_STICKY_HOLD) { - BattleScriptPushCursor(); + BattleScriptPush(gBattlescriptCurrInstr + 1); gBattlescriptCurrInstr = BattleScript_NoItemSteal; gLastUsedAbility = gBattleMons[gBattlerTarget].ability; @@ -3532,6 +3533,7 @@ void SetMoveEffect(bool32 primary, bool32 certain) case MOVE_EFFECT_SPECTRAL_THIEF: if (!NoAliveMonsForEitherParty()) { + bool32 contrary = (GetBattlerAbility(gBattlerAttacker) == ABILITY_CONTRARY); gBattleStruct->stolenStats[0] = 0; // Stats to steal. gBattleScripting.animArg1 = 0; for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++) @@ -3553,16 +3555,16 @@ void SetMoveEffect(bool32 primary, bool32 certain) if (gBattleScripting.animArg1 == 0) { if (byTwo) - gBattleScripting.animArg1 = STAT_ANIM_PLUS2 + i; + gBattleScripting.animArg1 = (contrary ? STAT_ANIM_MINUS2 : STAT_ANIM_PLUS2) + i; else - gBattleScripting.animArg1 = STAT_ANIM_PLUS1 + i; + gBattleScripting.animArg1 = (contrary ? STAT_ANIM_MINUS1 : STAT_ANIM_PLUS1) + i; } else { if (byTwo) - gBattleScripting.animArg1 = STAT_ANIM_MULTIPLE_PLUS2; + gBattleScripting.animArg1 = (contrary ? STAT_ANIM_MULTIPLE_MINUS2 : STAT_ANIM_MULTIPLE_PLUS2); else - gBattleScripting.animArg1 = STAT_ANIM_MULTIPLE_PLUS1; + gBattleScripting.animArg1 = (contrary ? STAT_ANIM_MULTIPLE_MINUS1 : STAT_ANIM_MULTIPLE_PLUS1); } } } @@ -3800,7 +3802,7 @@ void SetMoveEffect(bool32 primary, bool32 certain) } break; case MOVE_EFFECT_TERA_BLAST: - if (IsTerastallized(gEffectBattler) + if (GetActiveGimmick(gEffectBattler) == GIMMICK_TERA && GetBattlerTeraType(gEffectBattler) == TYPE_STELLAR && !NoAliveMonsForEitherParty()) { @@ -3977,7 +3979,7 @@ static void Cmd_tryfaintmon(void) gSideTimers[B_SIDE_OPPONENT].retaliateTimer = 2; } if ((gHitMarker & HITMARKER_DESTINYBOND) && IsBattlerAlive(gBattlerAttacker) - && !IsDynamaxed(gBattlerAttacker)) + && !(GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX)) { gHitMarker &= ~HITMARKER_DESTINYBOND; BattleScriptPush(gBattlescriptCurrInstr); @@ -4589,7 +4591,7 @@ bool32 NoAliveMonsForEitherParty(void) return (NoAliveMonsForPlayer() || NoAliveMonsForOpponent()); } -// For battles that aren't BATTLE_TYPE_LINK or BATTLE_TYPE_RECORDED_LINK, the only thing this +// For battles that aren't BATTLE_TYPE_LINK or BATTLE_TYPE_RECORDED_LINK or trainer battles, the only thing this // command does is check whether the player has won/lost by totaling each team's HP. It then // sets gBattleOutcome accordingly, if necessary. static void Cmd_checkteamslost(void) @@ -4605,10 +4607,12 @@ static void Cmd_checkteamslost(void) if (NoAliveMonsForOpponent()) gBattleOutcome |= B_OUTCOME_WON; - // For link battles that haven't ended, count number of empty battler spots - // In link multi battles, jump to pointer if more than 1 spot empty + // Fair switching - everyone has to switch in most at the same time, without knowing which pokemon the other trainer selected. + // In vanilla Emerald this was only used for link battles, in expansion it's also used for regular trainer battles. + // For battles that haven't ended, count number of empty battler spots + // In multi battles, jump to pointer if more than 1 spot empty // In non-multi battles, jump to pointer if 1 spot is missing on both sides - if (gBattleOutcome == 0 && (gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK))) + if (gBattleOutcome == 0 && (gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK | BATTLE_TYPE_TRAINER))) { s32 i, emptyPlayerSpots, emptyOpponentSpots; @@ -5389,7 +5393,6 @@ static void Cmd_moveend(void) bool32 effect = FALSE; u32 moveType = 0; u32 holdEffectAtk = 0; - u16 *choicedMoveAtk = NULL; u32 endMode, endState; u32 originallyUsedMove; @@ -5402,7 +5405,6 @@ static void Cmd_moveend(void) endState = cmd->endState; holdEffectAtk = GetBattlerHoldEffect(gBattlerAttacker, TRUE); - choicedMoveAtk = &gBattleStruct->choicedMove[gBattlerAttacker]; GET_MOVE_TYPE(gCurrentMove, moveType); do @@ -5483,7 +5485,7 @@ static void Cmd_moveend(void) } // Not strictly a protect effect, but works the same way else if (gProtectStructs[gBattlerTarget].beakBlastCharge - && CanBeBurned(gBattlerAttacker) + && CanBeBurned(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker)) && !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)) { gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE; @@ -5612,29 +5614,34 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_CHOICE_MOVE: // update choice band move - if (gHitMarker & HITMARKER_OBEYS - && (HOLD_EFFECT_CHOICE(holdEffectAtk) || GetBattlerAbility(gBattlerAttacker) == ABILITY_GORILLA_TACTICS) - && gChosenMove != MOVE_STRUGGLE - && (*choicedMoveAtk == MOVE_NONE || *choicedMoveAtk == MOVE_UNAVAILABLE)) { - if ((gMovesInfo[gChosenMove].effect == EFFECT_BATON_PASS - || gMovesInfo[gChosenMove].effect == EFFECT_HEALING_WISH) - && !(gMoveResultFlags & MOVE_RESULT_FAILED)) + u16 *choicedMoveAtk = &gBattleStruct->choicedMove[gBattlerAttacker]; + if (gHitMarker & HITMARKER_OBEYS + && (HOLD_EFFECT_CHOICE(holdEffectAtk) || GetBattlerAbility(gBattlerAttacker) == ABILITY_GORILLA_TACTICS) + && gChosenMove != MOVE_STRUGGLE + && (*choicedMoveAtk == MOVE_NONE || *choicedMoveAtk == MOVE_UNAVAILABLE)) { - gBattleScripting.moveendState++; - break; + if ((gMovesInfo[gChosenMove].effect == EFFECT_BATON_PASS + || gMovesInfo[gChosenMove].effect == EFFECT_HEALING_WISH) + && !(gMoveResultFlags & MOVE_RESULT_FAILED)) + { + gBattleScripting.moveendState++; + break; + } + *choicedMoveAtk = gChosenMove; } - *choicedMoveAtk = gChosenMove; + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (gBattleMons[gBattlerAttacker].moves[i] == *choicedMoveAtk) + break; + } + if (i == MAX_MON_MOVES) + { + *choicedMoveAtk = MOVE_NONE; + } + gBattleScripting.moveendState++; + break; } - for (i = 0; i < MAX_MON_MOVES; i++) - { - if (gBattleMons[gBattlerAttacker].moves[i] == *choicedMoveAtk) - break; - } - if (i == MAX_MON_MOVES) - *choicedMoveAtk = MOVE_NONE; - gBattleScripting.moveendState++; - break; case MOVEEND_CHANGED_ITEMS: // changed held items for (i = 0; i < gBattlersCount; i++) { @@ -5854,7 +5861,7 @@ static void Cmd_moveend(void) gLastPrintedMoves[gBattlerAttacker] = gChosenMove; gLastUsedMove = gCurrentMove; if (IsMaxMove(gCurrentMove)) - gBattleStruct->dynamax.lastUsedBaseMove = gBattleStruct->dynamax.baseMove[gBattlerAttacker]; + gBattleStruct->dynamax.lastUsedBaseMove = gBattleStruct->dynamax.baseMoves[gBattlerAttacker]; } } if (!(gAbsentBattlerFlags & gBitTable[gBattlerAttacker]) @@ -6387,17 +6394,16 @@ static void Cmd_moveend(void) gSpecialStatuses[gBattlerAttacker].preventLifeOrbDamage = 0; gSpecialStatuses[gBattlerTarget].berryReduced = FALSE; gBattleScripting.moveEffect = 0; - // clear attacker z move data - gBattleStruct->zmove.active = FALSE; - gBattleStruct->zmove.toBeUsed[gBattlerAttacker] = MOVE_NONE; - gBattleStruct->zmove.effect = EFFECT_HIT; gBattleStruct->hitSwitchTargetFailed = FALSE; gBattleStruct->isAtkCancelerForCalledMove = FALSE; gBattleStruct->swapDamageCategory = FALSE; + gBattleStruct->categoryOverride = FALSE; gBattleStruct->bouncedMoveIsUsed = FALSE; gBattleStruct->enduredDamage = 0; gBattleStruct->additionalEffectsCounter = 0; gBattleStruct->poisonPuppeteerConfusion = FALSE; + if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE) + SetActiveGimmick(gBattlerAttacker, GIMMICK_NONE); gBattleStruct->distortedTypeMatchups = 0; gBattleScripting.moveendState++; break; @@ -6758,7 +6764,7 @@ static void Cmd_openpartyscreen(void) u32 i, battler = 0; const u8 *failInstr = cmd->failInstr; - if (cmd->battler == BS_FAINTED_LINK_MULTIPLE_1) + if (cmd->battler == BS_FAINTED_MULTIPLE_1) { if ((gBattleTypeFlags & BATTLE_TYPE_MULTI) || !(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) { @@ -6913,7 +6919,7 @@ static void Cmd_openpartyscreen(void) } gBattlescriptCurrInstr = cmd->nextInstr; } - else if (cmd->battler == BS_FAINTED_LINK_MULTIPLE_2) + else if (cmd->battler == BS_FAINTED_MULTIPLE_2) { if (!(gBattleTypeFlags & BATTLE_TYPE_MULTI)) { @@ -7131,13 +7137,8 @@ bool32 DoSwitchInAbilities(u32 battler) || AbilityBattleEffects(ABILITYEFFECT_TRACE2, 0, 0, 0, 0)); } -static void Cmd_switchineffects(void) +static void UpdateSentMonFlags(u32 battler) { - CMD_ARGS(u8 battler); - - s32 i; - u32 battler = GetBattlerForBattleScript(cmd->battler); - UpdateSentPokesToOpponentValue(battler); gHitMarker &= ~HITMARKER_FAINTED(battler); @@ -7145,7 +7146,11 @@ static void Cmd_switchineffects(void) if (!BattlerHasAi(battler)) gBattleStruct->appearedInBattle |= gBitTable[gBattlerPartyIndexes[battler]]; +} +static bool32 DoSwitchInEffectsForBattler(u32 battler) +{ + u32 i; // Neutralizing Gas announces itself before hazards if (gBattleMons[battler].ability == ABILITY_NEUTRALIZING_GAS && gSpecialStatuses[battler].announceNeutralizingGas == 0) { @@ -7263,7 +7268,7 @@ static void Cmd_switchineffects(void) BattleScriptPushCursor(); gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_Z_HP_TRAP; gBattlescriptCurrInstr = BattleScript_HealReplacementZMove; - return; + return TRUE; } else { @@ -7278,9 +7283,9 @@ static void Cmd_switchineffects(void) gDisableStructs[battler].truantSwitchInHack = 0; if (DoSwitchInAbilities(battler) || ItemBattleEffects(ITEMEFFECT_ON_SWITCH_IN, battler, FALSE)) - return; + return TRUE; else if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, 0, 0, 0)) - return; + return TRUE; gDisableStructs[battler].stickyWebDone = FALSE; gDisableStructs[battler].spikesDone = FALSE; @@ -7296,22 +7301,68 @@ static void Cmd_switchineffects(void) gBattleStruct->hpOnSwitchout[GetBattlerSide(i)] = gBattleMons[i].hp; } - if (cmd->battler == BS_FAINTED_LINK_MULTIPLE_1) - { - u32 hitmarkerFaintBits = gHitMarker >> 28; - - gBattlerFainted++; - while (1) - { - if (hitmarkerFaintBits & gBitTable[gBattlerFainted] && !(gAbsentBattlerFlags & gBitTable[gBattlerFainted])) - break; - if (gBattlerFainted >= gBattlersCount) - break; - gBattlerFainted++; - } - } gBattleStruct->forcedSwitch &= ~(gBitTable[battler]); + return FALSE; + } + + return TRUE; // Effect's script plays. +} + +static void Cmd_switchineffects(void) +{ + CMD_ARGS(u8 battler); + u32 i, battler = GetBattlerForBattleScript(cmd->battler); + + switch (cmd->battler) + { + // 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]; + UpdateSentMonFlags(battler); + + // Increment fainted battler. + do + { + gBattlerFainted++; + if (gBattlerFainted >= gBattlersCount) + break; + if (gHitMarker & HITMARKER_FAINTED(gBattlerFainted) && !(gAbsentBattlerFlags & gBitTable[gBattlerFainted])) + break; + } while (1); + gBattlescriptCurrInstr = cmd->nextInstr; + return; + case BS_FAINTED_MULTIPLE_2: // Plays hazards/abilities. + switch (gBattleStruct->multipleSwitchInState) + { + case 0: // Sort battlers by speed + for (i = 0; i < gBattlersCount; i++) + gBattleStruct->multipleSwitchInSortedBattlers[i] = i; + SortBattlersBySpeed(gBattleStruct->multipleSwitchInSortedBattlers, FALSE); + gBattleStruct->multipleSwitchInState++; + gBattleStruct->multipleSwitchInCursor = 0; + // Loop through all available battlers + case 1: + for (; gBattleStruct->multipleSwitchInCursor < gBattlersCount; gBattleStruct->multipleSwitchInCursor++) + { + gBattlerFainted = gBattleStruct->multipleSwitchInSortedBattlers[gBattleStruct->multipleSwitchInCursor]; + if (gBattleStruct->multipleSwitchInBattlers & gBitTable[gBattlerFainted]) + { + if (DoSwitchInEffectsForBattler(gBattlerFainted)) + return; + } + } + // All battlers done, end + gBattleStruct->multipleSwitchInBattlers = 0; + gBattleStruct->multipleSwitchInState = 0; + gBattlescriptCurrInstr = cmd->nextInstr; + } + break; + default: + UpdateSentMonFlags(battler); + if (!DoSwitchInEffectsForBattler(battler)) + gBattlescriptCurrInstr = cmd->nextInstr; + break; } } @@ -7958,7 +8009,7 @@ static void BestowItem(u32 battlerAtk, u32 battlerDef) // Called by Cmd_removeitem. itemId represents the item that was removed, not being given. static bool32 TrySymbiosis(u32 battler, u32 itemId) { - if (!gBattleStruct->itemLost[gBattlerPartyIndexes[battler]].stolen + if (!gBattleStruct->itemLost[B_SIDE_PLAYER][gBattlerPartyIndexes[battler]].stolen && gBattleStruct->changedItems[battler] == ITEM_NONE && GetBattlerHoldEffect(battler, TRUE) != HOLD_EFFECT_EJECT_BUTTON && GetBattlerHoldEffect(battler, TRUE) != HOLD_EFFECT_EJECT_PACK @@ -8767,7 +8818,6 @@ static void HandleScriptMegaPrimalBurst(u32 caseId, u32 battler, u32 type) { struct Pokemon *party = GetBattlerParty(battler); struct Pokemon *mon = &party[gBattlerPartyIndexes[battler]]; - u32 position = GetBattlerPosition(battler); u32 side = GetBattlerSide(battler); // Change species. @@ -8795,9 +8845,9 @@ static void HandleScriptMegaPrimalBurst(u32 caseId, u32 battler, u32 type) if (side == B_SIDE_OPPONENT) SetBattlerShadowSpriteCallback(battler, gBattleMons[battler].species); if (type == HANDLE_TYPE_MEGA_EVOLUTION) - gBattleStruct->mega.alreadyEvolved[position] = TRUE; + SetGimmickAsActivated(battler, GIMMICK_MEGA); if (type == HANDLE_TYPE_ULTRA_BURST) - gBattleStruct->burst.alreadyBursted[position] = TRUE; + SetGimmickAsActivated(battler, GIMMICK_ULTRA_BURST); } } @@ -9660,7 +9710,7 @@ static void Cmd_various(void) else { if (gBattleMons[gBattlerTarget].ability == gBattleMons[gBattlerAttacker].ability - || IsDynamaxed(gBattlerTarget)) + || (GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX)) { gBattlescriptCurrInstr = cmd->failInstr; } @@ -9699,7 +9749,15 @@ static void Cmd_various(void) gBattlescriptCurrInstr = cmd->failInstr; else { - gCalledMove = move; + if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE && !IS_MOVE_STATUS(move)) + { + gBattleStruct->zmove.baseMoves[gBattlerAttacker] = move; + gCalledMove = GetTypeBasedZMove(move); + } + else + { + gCalledMove = move; + } gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED; gBattlerTarget = GetMoveTarget(gCalledMove, NO_TARGET_OVERRIDE); gStatuses3[gBattlerAttacker] |= STATUS3_ME_FIRST; @@ -9735,7 +9793,7 @@ static void Cmd_various(void) VARIOUS_ARGS(const u8 *failInstr); if ((GetBattlerType(gBattlerTarget, 0, FALSE) == gMovesInfo[gCurrentMove].type && GetBattlerType(gBattlerTarget, 1, FALSE) == gMovesInfo[gCurrentMove].type) - || IsTerastallized(gBattlerTarget)) + || GetActiveGimmick(gBattlerTarget) == GIMMICK_TERA) { gBattlescriptCurrInstr = cmd->failInstr; } @@ -9832,7 +9890,9 @@ static void Cmd_various(void) if (move == MOVE_NONE || move == MOVE_UNAVAILABLE || MoveHasAdditionalEffectSelf(move, MOVE_EFFECT_RECHARGE) || gMovesInfo[move].instructBanned || gBattleMoveEffects[gMovesInfo[move].effect].twoTurnEffect - || IsDynamaxed(gBattlerTarget)) + || (GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX) + || IsZMove(move) + || IsMaxMove(move)) { gBattlescriptCurrInstr = cmd->failInstr; } @@ -9902,16 +9962,17 @@ static void Cmd_various(void) case VARIOUS_PSYCHO_SHIFT: { VARIOUS_ARGS(const u8 *failInstr); + u32 targetAbility = GetBattlerAbility(gBattlerTarget); // Psycho shift works - if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_POISON) && CanBePoisoned(gBattlerAttacker, gBattlerTarget)) + if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_POISON) && CanBePoisoned(gBattlerAttacker, gBattlerTarget, targetAbility)) gBattleCommunication[MULTISTRING_CHOOSER] = 0; - else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_TOXIC_POISON) && CanBePoisoned(gBattlerAttacker, gBattlerTarget)) + else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_TOXIC_POISON) && CanBePoisoned(gBattlerAttacker, gBattlerTarget, targetAbility)) gBattleCommunication[MULTISTRING_CHOOSER] = 1; - else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_BURN) && CanBeBurned(gBattlerTarget)) + else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_BURN) && CanBeBurned(gBattlerTarget, targetAbility)) gBattleCommunication[MULTISTRING_CHOOSER] = 2; - else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_PARALYSIS) && CanBeParalyzed(gBattlerTarget)) + else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_PARALYSIS) && CanBeParalyzed(gBattlerTarget, targetAbility)) gBattleCommunication[MULTISTRING_CHOOSER] = 3; - else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_SLEEP) && CanSleep(gBattlerTarget)) + else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_SLEEP) && CanBeSlept(gBattlerTarget, targetAbility)) gBattleCommunication[MULTISTRING_CHOOSER] = 4; else if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_FROSTBITE) && CanBeFrozen(gBattlerTarget)) gBattleCommunication[MULTISTRING_CHOOSER] = 5; @@ -10068,7 +10129,7 @@ static void Cmd_various(void) case VARIOUS_TRY_THIRD_TYPE: { VARIOUS_ARGS(const u8 *failInstr); - if (IS_BATTLER_OF_TYPE(battler, gMovesInfo[gCurrentMove].argument) || IsTerastallized(battler)) + if (IS_BATTLER_OF_TYPE(battler, gMovesInfo[gCurrentMove].argument) || GetActiveGimmick(battler) == GIMMICK_TERA) { gBattlescriptCurrInstr = cmd->failInstr; } @@ -11061,10 +11122,10 @@ static void SetMoveForMirrorMove(u32 move) { gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED; // Edge case, we used Z Mirror Move, got the stat boost and now need to use the Z-move - if (gBattleStruct->zmove.toBeUsed[gBattlerAttacker] && !IS_MOVE_STATUS(move)) + if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE && !IS_MOVE_STATUS(move)) { - gCurrentMove = gBattleStruct->zmove.chosenZMove = GetTypeBasedZMove(move, gBattlerAttacker); - QueueZMove(gBattlerAttacker, move); + gBattleStruct->zmove.baseMoves[gBattlerAttacker] = move; + gCurrentMove = GetTypeBasedZMove(move); } else { @@ -12104,7 +12165,7 @@ static void Cmd_tryconversiontypechange(void) u8 moveChecked = 0; u8 moveType = 0; - if (IsTerastallized(gBattlerAttacker)) + if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_TERA) { gBattlescriptCurrInstr = cmd->failInstr; return; @@ -12248,7 +12309,7 @@ static void Cmd_tryKO(void) u16 targetAbility = GetBattlerAbility(gBattlerTarget); // Dynamaxed Pokemon cannot be hit by OHKO moves. - if (IsDynamaxed(gBattlerTarget)) + if ((GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX)) { gMoveResultFlags |= MOVE_RESULT_MISSED; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_KO_UNAFFECTED; @@ -12796,11 +12857,11 @@ static void Cmd_trysetencore(void) s32 i; - if (IsMaxMove(gLastMoves[gBattlerTarget]) && !IsDynamaxed(gBattlerTarget)) + if (IsMaxMove(gLastMoves[gBattlerTarget]) && !(GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX)) { for (i = 0; i < MAX_MON_MOVES; i++) { - if (gBattleMons[gBattlerTarget].moves[i] == gBattleStruct->dynamax.baseMove[gBattlerTarget]) + if (gBattleMons[gBattlerTarget].moves[i] == gBattleStruct->dynamax.baseMoves[gBattlerTarget]) break; } } @@ -12825,7 +12886,11 @@ static void Cmd_trysetencore(void) { gDisableStructs[gBattlerTarget].encoredMove = gBattleMons[gBattlerTarget].moves[i]; gDisableStructs[gBattlerTarget].encoredMovePos = i; - gDisableStructs[gBattlerTarget].encoreTimer = 3; + // Encore always lasts 3 turns, but we need to account for a scenario where Encore changes the move during the same turn. + if (GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget)) + gDisableStructs[gBattlerTarget].encoreTimer = 4; + else + gDisableStructs[gBattlerTarget].encoreTimer = 3; gBattlescriptCurrInstr = cmd->nextInstr; } else @@ -12875,7 +12940,7 @@ static void Cmd_settypetorandomresistance(void) { gBattlescriptCurrInstr = cmd->failInstr; } - else if (IsTerastallized(gBattlerAttacker)) + else if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_TERA) { gBattlescriptCurrInstr = cmd->failInstr; } @@ -13014,7 +13079,15 @@ static void Cmd_trychoosesleeptalkmove(void) movePosition = MOD(Random(), MAX_MON_MOVES); } while ((gBitTable[movePosition] & unusableMovesBits)); - gCalledMove = gBattleMons[gBattlerAttacker].moves[movePosition]; + if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE && !IS_MOVE_STATUS(gBattleMons[gBattlerAttacker].moves[movePosition])) + { + gBattleStruct->zmove.baseMoves[gBattlerAttacker] = gBattleMons[gBattlerAttacker].moves[movePosition]; + gCalledMove = GetTypeBasedZMove(gBattleMons[gBattlerAttacker].moves[movePosition]); + } + else + { + gCalledMove = gBattleMons[gBattlerAttacker].moves[movePosition]; + } gCurrMovePos = movePosition; gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED; gBattlerTarget = GetMoveTarget(gCalledMove, NO_TARGET_OVERRIDE); @@ -13083,7 +13156,7 @@ static void Cmd_tryspiteppreduce(void) { for (i = 0; i < MAX_MON_MOVES; i++) { - if (gBattleStruct->dynamax.baseMove[gBattlerTarget] == gBattleMons[gBattlerTarget].moves[i]) + if (gBattleStruct->dynamax.baseMoves[gBattlerTarget] == gBattleMons[gBattlerTarget].moves[i]) break; } } @@ -13359,7 +13432,16 @@ static void Cmd_handlefurycutter(void) } else { - if (gDisableStructs[gBattlerAttacker].furyCutterCounter != 5 + u32 max; + + if (B_UPDATED_MOVE_DATA >= GEN_6) + max = 3; + else if (B_UPDATED_MOVE_DATA == GEN_5) + max = 4; + else + max = 5; + + if (gDisableStructs[gBattlerAttacker].furyCutterCounter < max && gSpecialStatuses[gBattlerAttacker].parentalBondState != PARENTAL_BOND_1ST_HIT) // Don't increment counter on first hit gDisableStructs[gBattlerAttacker].furyCutterCounter++; @@ -13543,7 +13625,7 @@ static void Cmd_jumpifnopursuitswitchdmg(void) gActionsByTurnOrder[i] = B_ACTION_TRY_FINISH; } - gCurrentMove = gChosenMoveByBattler[gBattlerTarget]; + gCurrentMove = gChosenMove = gChosenMoveByBattler[gBattlerTarget]; gCurrMovePos = gChosenMovePos = *(gBattleStruct->chosenMovePositions + gBattlerTarget); gBattlescriptCurrInstr = cmd->nextInstr; gBattleScripting.animTurn = 1; @@ -13936,25 +14018,33 @@ static void Cmd_callterrainattack(void) CMD_ARGS(); gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED; - gCurrentMove = GetNaturePowerMove(); + gCurrentMove = GetNaturePowerMove(gBattlerAttacker); gBattlerTarget = GetMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE); BattleScriptPush(GET_MOVE_BATTLESCRIPT(gCurrentMove)); gBattlescriptCurrInstr = cmd->nextInstr; } -u16 GetNaturePowerMove(void) +u32 GetNaturePowerMove(u32 battler) { + u32 move = sNaturePowerMoves[gBattleTerrain]; if (gFieldStatuses & STATUS_FIELD_MISTY_TERRAIN) - return MOVE_MOONBLAST; + move = MOVE_MOONBLAST; else if (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN) - return MOVE_THUNDERBOLT; + move = MOVE_THUNDERBOLT; else if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN) - return MOVE_ENERGY_BALL; + move = MOVE_ENERGY_BALL; else if (gFieldStatuses & STATUS_FIELD_PSYCHIC_TERRAIN) - return MOVE_PSYCHIC; + move = MOVE_PSYCHIC; else if (sNaturePowerMoves[gBattleTerrain] == MOVE_NONE) - return MOVE_TRI_ATTACK; - return sNaturePowerMoves[gBattleTerrain]; + move = MOVE_TRI_ATTACK; + + if (GetActiveGimmick(battler) == GIMMICK_Z_MOVE) + { + gBattleStruct->zmove.baseMoves[gBattlerAttacker] = move; + move = GetTypeBasedZMove(move); + } + + return move; } // Refresh @@ -13980,7 +14070,7 @@ static void Cmd_settorment(void) CMD_ARGS(const u8 *failInstr); if (gBattleMons[gBattlerTarget].status2 & STATUS2_TORMENT - || IsDynamaxed(gBattlerTarget)) + || (GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX)) { gBattlescriptCurrInstr = cmd->failInstr; } @@ -14369,7 +14459,7 @@ static void Cmd_tryswapabilities(void) } else { - if (gMoveResultFlags & MOVE_RESULT_NO_EFFECT || IsDynamaxed(gBattlerTarget)) + if (gMoveResultFlags & MOVE_RESULT_NO_EFFECT || (GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX)) { gBattlescriptCurrInstr = cmd->failInstr; } @@ -14847,7 +14937,7 @@ static void Cmd_settypetoterrain(void) break; } - if (!IS_BATTLER_OF_TYPE(gBattlerAttacker, terrainType) && !IsTerastallized(gBattlerAttacker)) + if (!IS_BATTLER_OF_TYPE(gBattlerAttacker, terrainType) && GetActiveGimmick(gBattlerAttacker) != GIMMICK_TERA) { SET_BATTLER_TYPE(gBattlerAttacker, terrainType); PREPARE_TYPE_BUFFER(gBattleTextBuff1, terrainType); @@ -15265,6 +15355,13 @@ static void Cmd_givecaughtmon(void) { CMD_ARGS(); + if (B_RESTORE_HELD_BATTLE_ITEMS >= GEN_9) + { + u16 lostItem = gBattleStruct->itemLost[B_SIDE_OPPONENT][gBattlerPartyIndexes[GetCatchingBattler()]].originalItem; + if (lostItem != ITEM_NONE && ItemId_GetPocket(lostItem) != POCKET_BERRIES) + SetMonData(&gEnemyParty[gBattlerPartyIndexes[GetCatchingBattler()]], MON_DATA_HELD_ITEM, &lostItem); // Restore non-berry items + } + if (GiveMonToPlayer(&gEnemyParty[gBattlerPartyIndexes[GetCatchingBattler()]]) != MON_GIVEN_TO_PARTY) { if (!ShouldShowBoxWasFullMessage()) @@ -16437,9 +16534,9 @@ void BS_SetRemoveTerrain(void) } else { - u16 atkHoldEffect = GetBattlerHoldEffect(gBattlerAttacker, TRUE); + u32 atkHoldEffect = GetBattlerHoldEffect(gBattlerAttacker, TRUE); - gFieldStatuses &= ~STATUS_FIELD_TERRAIN_ANY; + gFieldStatuses &= ~(STATUS_FIELD_TERRAIN_ANY | STATUS_FIELD_TERRAIN_PERMANENT); gFieldStatuses |= statusFlag; gFieldTimers.terrainTimer = (atkHoldEffect == HOLD_EFFECT_TERRAIN_EXTENDER) ? 8 : 5; gBattlescriptCurrInstr = cmd->nextInstr; @@ -16469,7 +16566,7 @@ void BS_TryReflectType(void) { gBattlescriptCurrInstr = cmd->failInstr; } - else if (IsTerastallized(gBattlerAttacker)) + else if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_TERA) { gBattlescriptCurrInstr = cmd->failInstr; } @@ -16537,7 +16634,8 @@ void BS_TryRelicSong(void) { NATIVE_ARGS(); - if (GetBattlerAbility(gBattlerAttacker) != ABILITY_SHEER_FORCE && !(gBattleMons[gBattlerAttacker].status2 & STATUS2_TRANSFORMED)) + if (GetBattlerAbility(gBattlerAttacker) != ABILITY_SHEER_FORCE && !(gBattleMons[gBattlerAttacker].status2 & STATUS2_TRANSFORMED) + && (gBattleMons[gBattlerAttacker].species == SPECIES_MELOETTA_ARIA || gBattleMons[gBattlerAttacker].species == SPECIES_MELOETTA_PIROUETTE)) { if (gBattleMons[gBattlerAttacker].species == SPECIES_MELOETTA_ARIA) gBattleMons[gBattlerAttacker].species = SPECIES_MELOETTA_PIROUETTE; @@ -16548,7 +16646,9 @@ void BS_TryRelicSong(void) gBattlescriptCurrInstr = BattleScript_AttackerFormChangeMoveEffect; } else + { gBattlescriptCurrInstr = cmd->nextInstr; + } } void BS_SetPledge(void) @@ -16697,9 +16797,8 @@ void BS_TryTrainerSlideDynamaxMsg(void) NATIVE_ARGS(); s32 shouldSlide; - if ((shouldSlide = ShouldDoTrainerSlide(gBattlerAttacker, TRAINER_SLIDE_DYNAMAX))) + if ((shouldSlide = ShouldDoTrainerSlide(gBattleScripting.battler, TRAINER_SLIDE_DYNAMAX))) { - gBattleScripting.battler = gBattlerAttacker; BattleScriptPush(cmd->nextInstr); gBattlescriptCurrInstr = (shouldSlide == 1 ? BattleScript_TrainerASlideMsgRet : BattleScript_TrainerBSlideMsgRet); } @@ -16740,10 +16839,19 @@ void BS_TryCopycat(void) } else { - if (IsMaxMove(gLastUsedMove)) + if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE && !IS_MOVE_STATUS(gLastUsedMove)) + { + gBattleStruct->zmove.baseMoves[gBattlerAttacker] = gLastUsedMove; + gCalledMove = GetTypeBasedZMove(gLastUsedMove); + } + else if (IsMaxMove(gLastUsedMove)) + { gCalledMove = gBattleStruct->dynamax.lastUsedBaseMove; + } else + { gCalledMove = gLastUsedMove; + } gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED; gBattlerTarget = GetMoveTarget(gCalledMove, NO_TARGET_OVERRIDE); @@ -16831,8 +16939,8 @@ void BS_AllySwitchFailChance(void) void BS_SetPhotonGeyserCategory(void) { NATIVE_ARGS(); - if (!((gMovesInfo[gCurrentMove].effect == EFFECT_TERA_BLAST && !IsTerastallized(gBattlerAttacker)) - || (gMovesInfo[gCurrentMove].effect == EFFECT_TERA_STARSTORM && !(IsTerastallized(gBattlerAttacker) && gBattleMons[gBattlerAttacker].species == SPECIES_TERAPAGOS_STELLAR)))) + if (!((gMovesInfo[gCurrentMove].effect == EFFECT_TERA_BLAST && GetActiveGimmick(gBattlerAttacker) != GIMMICK_TERA) + || (gMovesInfo[gCurrentMove].effect == EFFECT_TERA_STARSTORM && GetActiveGimmick(gBattlerAttacker) != GIMMICK_TERA && gBattleMons[gBattlerAttacker].species == SPECIES_TERAPAGOS_STELLAR))) gBattleStruct->swapDamageCategory = (GetCategoryBasedOnStats(gBattlerAttacker) == DAMAGE_CATEGORY_PHYSICAL); gBattlescriptCurrInstr = cmd->nextInstr; } @@ -17033,3 +17141,12 @@ void BS_ApplyTerastallization(void) gBattlescriptCurrInstr = cmd->nextInstr; } +void BS_DamageToQuarterTargetHP(void) +{ + NATIVE_ARGS(); + gBattleMoveDamage = (3 * GetNonDynamaxHP(gBattlerTarget)) / 4; + if (gBattleMoveDamage == 0) + gBattleMoveDamage = 1; + + gBattlescriptCurrInstr = cmd->nextInstr; +} diff --git a/src/battle_setup.c b/src/battle_setup.c index e92b32d4c9..41f2b9b502 100644 --- a/src/battle_setup.c +++ b/src/battle_setup.c @@ -1938,15 +1938,16 @@ static bool32 HasAtLeastFiveBadges(void) void IncrementRematchStepCounter(void) { #if FREE_MATCH_CALL == FALSE - if (HasAtLeastFiveBadges() - && (I_VS_SEEKER_CHARGING != 0) - && (!CheckBagHasItem(ITEM_VS_SEEKER, 1))) - { - if (gSaveBlock1Ptr->trainerRematchStepCounter >= STEP_COUNTER_MAX) - gSaveBlock1Ptr->trainerRematchStepCounter = STEP_COUNTER_MAX; - else - gSaveBlock1Ptr->trainerRematchStepCounter++; - } + if (!HasAtLeastFiveBadges()) + return; + + if (IsVsSeekerEnabled()) + return; + + if (gSaveBlock1Ptr->trainerRematchStepCounter >= STEP_COUNTER_MAX) + gSaveBlock1Ptr->trainerRematchStepCounter = STEP_COUNTER_MAX; + else + gSaveBlock1Ptr->trainerRematchStepCounter++; #endif //FREE_MATCH_CALL } diff --git a/src/battle_terastal.c b/src/battle_terastal.c index 62ab3afacb..ff01ee2597 100644 --- a/src/battle_terastal.c +++ b/src/battle_terastal.c @@ -4,6 +4,8 @@ #include "battle_controllers.h" #include "battle_interface.h" #include "battle_terastal.h" +#include "battle_gimmick.h" +#include "battle_scripts.h" #include "event_data.h" #include "item.h" #include "palette.h" @@ -16,14 +18,13 @@ #include "constants/rgb.h" // Sets flags and variables upon a battler's Terastallization. -void PrepareBattlerForTera(u32 battler) +void ActivateTera(u32 battler) { u32 side = GetBattlerSide(battler); - u32 index = gBattlerPartyIndexes[battler]; - // Update TeraData fields. - gBattleStruct->tera.isTerastallized[side] |= gBitTable[index]; - gBattleStruct->tera.alreadyTerastallized[battler] = TRUE; + // Set appropriate flags. + SetActiveGimmick(battler, GIMMICK_TERA); + SetGimmickAsActivated(battler, GIMMICK_TERA); // Remove Tera Orb charge. if (B_FLAG_TERA_ORB_CHARGED != 0 @@ -33,6 +34,13 @@ void PrepareBattlerForTera(u32 battler) { FlagClear(B_FLAG_TERA_ORB_CHARGED); } + + // Execute battle script. + PREPARE_TYPE_BUFFER(gBattleTextBuff1, GetBattlerTeraType(battler)); + if (TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_TERASTALLIZATION)) + BattleScriptExecute(BattleScript_TeraFormChange); + else + BattleScriptExecute(BattleScript_Terastallization); } // Applies palette blend and enables UI indicator after animation has played @@ -56,33 +64,32 @@ bool32 CanTerastallize(u32 battler) u32 holdEffect = GetBattlerHoldEffect(battler, FALSE); // Check if Player has Tera Orb and has charge. - if (!CheckBagHasItem(ITEM_TERA_ORB, 1) - || !((B_FLAG_TERA_ORB_NO_COST != 0 && FlagGet(B_FLAG_TERA_ORB_NO_COST)) - || (B_FLAG_TERA_ORB_CHARGED != 0 && FlagGet(B_FLAG_TERA_ORB_CHARGED) - && ((battler == B_POSITION_PLAYER_LEFT || (!(gBattleTypeFlags & BATTLE_TYPE_MULTI) && battler == B_POSITION_PLAYER_RIGHT)))))) - { + if (!TESTING && !CheckBagHasItem(ITEM_TERA_ORB, 1)) return FALSE; + + if (!TESTING + && !(B_FLAG_TERA_ORB_NO_COST != 0 && FlagGet(B_FLAG_TERA_ORB_NO_COST)) + && (battler == B_POSITION_PLAYER_LEFT || (!(gBattleTypeFlags & BATTLE_TYPE_MULTI) && battler == B_POSITION_PLAYER_RIGHT))) + { + if (B_FLAG_TERA_ORB_CHARGED != 0 && !FlagGet(B_FLAG_TERA_ORB_CHARGED)) + return FALSE; } // Check if Trainer has already Terastallized. - if (gBattleStruct->tera.alreadyTerastallized[battler]) - { + if (HasTrainerUsedGimmick(battler, GIMMICK_TERA)) return FALSE; - } - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE - && IsPartnerMonFromSameTrainer(battler) - && (gBattleStruct->tera.alreadyTerastallized[BATTLE_PARTNER(battler)] - || (gBattleStruct->tera.toTera & gBitTable[BATTLE_PARTNER(battler)]))) - { + // Check if AI battler is intended to Terastallize. + if (!ShouldTrainerBattlerUseGimmick(battler, GIMMICK_TERA)) + return FALSE; + + // Check if battler has another gimmick active. + if (GetActiveGimmick(battler) != GIMMICK_NONE) return FALSE; - } // Check if battler is holding a Z-Crystal or Mega Stone. - if (holdEffect == HOLD_EFFECT_Z_CRYSTAL || holdEffect == HOLD_EFFECT_MEGA_STONE) - { + if (!TESTING && (holdEffect == HOLD_EFFECT_Z_CRYSTAL || holdEffect == HOLD_EFFECT_MEGA_STONE)) // tests make this check already return FALSE; - } // Every check passed! return TRUE; @@ -94,25 +101,18 @@ u32 GetBattlerTeraType(u32 battler) return GetMonData(&GetBattlerParty(battler)[gBattlerPartyIndexes[battler]], MON_DATA_TERA_TYPE); } -// Returns whether a battler is terastallized. -bool32 IsTerastallized(u32 battler) -{ - return gBattleStruct->tera.isTerastallized[GetBattlerSide(battler)] & gBitTable[gBattlerPartyIndexes[battler]]; -} - - // Uses up a type's Stellar boost. void ExpendTypeStellarBoost(u32 battler, u32 type) { if (type < 32) // avoid OOB access - gBattleStruct->tera.stellarBoostFlags[GetBattlerSide(battler)] |= gBitTable[type]; + gBattleStruct->stellarBoostFlags[GetBattlerSide(battler)] |= gBitTable[type]; } // Checks whether a type's Stellar boost has been expended. bool32 IsTypeStellarBoosted(u32 battler, u32 type) { if (type < 32) // avoid OOB access - return !(gBattleStruct->tera.stellarBoostFlags[GetBattlerSide(battler)] & gBitTable[type]); + return !(gBattleStruct->stellarBoostFlags[GetBattlerSide(battler)] & gBitTable[type]); else return FALSE; } @@ -125,7 +125,7 @@ uq4_12_t GetTeraMultiplier(u32 battler, u32 type) bool32 hasAdaptability = (GetBattlerAbility(battler) == ABILITY_ADAPTABILITY); // Safety check. - if (!IsTerastallized(battler)) + if (GetActiveGimmick(battler) != GIMMICK_TERA) return UQ_4_12(1.0); // Stellar-type checks. @@ -172,604 +172,3 @@ u16 GetTeraTypeRGB(u32 type) { return gTypesInfo[type].teraTypeRGBValue; } - -// TERASTAL TRIGGER: -static const u8 ALIGNED(4) sTeraTriggerGfx[] = INCBIN_U8("graphics/battle_interface/tera_trigger.4bpp"); -static const u16 sTeraTriggerPal[] = INCBIN_U16("graphics/battle_interface/tera_trigger.gbapal"); - -static const struct SpriteSheet sSpriteSheet_TeraTrigger = -{ - sTeraTriggerGfx, sizeof(sTeraTriggerGfx), TAG_TERA_TRIGGER_TILE -}; -static const struct SpritePalette sSpritePalette_TeraTrigger = -{ - sTeraTriggerPal, TAG_TERA_TRIGGER_PAL -}; - -static const struct OamData sOamData_TeraTrigger = -{ - .y = 0, - .affineMode = 0, - .objMode = 0, - .mosaic = 0, - .bpp = 0, - .shape = ST_OAM_SQUARE, - .x = 0, - .matrixNum = 0, - .size = 2, - .tileNum = 0, - .priority = 1, - .paletteNum = 0, - .affineParam = 0, -}; - -static const union AnimCmd sSpriteAnim_TeraTriggerOff[] = -{ - ANIMCMD_FRAME(0, 0), - ANIMCMD_END -}; - -static const union AnimCmd sSpriteAnim_TeraTriggerOn[] = -{ - ANIMCMD_FRAME(16, 0), - ANIMCMD_END -}; - -static const union AnimCmd *const sSpriteAnimTable_TeraTrigger[] = -{ - sSpriteAnim_TeraTriggerOff, - sSpriteAnim_TeraTriggerOn, -}; - -static void SpriteCb_TeraTrigger(struct Sprite *sprite); -static const struct SpriteTemplate sSpriteTemplate_TeraTrigger = -{ - .tileTag = TAG_TERA_TRIGGER_TILE, - .paletteTag = TAG_TERA_TRIGGER_PAL, - .oam = &sOamData_TeraTrigger, - .anims = sSpriteAnimTable_TeraTrigger, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_TeraTrigger -}; - -// Tera Evolution Trigger icon functions. -void ChangeTeraTriggerSprite(u8 spriteId, u8 animId) -{ - StartSpriteAnim(&gSprites[spriteId], animId); -} - -#define SINGLES_TERA_TRIGGER_POS_X_OPTIMAL (30) -#define SINGLES_TERA_TRIGGER_POS_X_PRIORITY (31) -#define SINGLES_TERA_TRIGGER_POS_X_SLIDE (15) -#define SINGLES_TERA_TRIGGER_POS_Y_DIFF (-11) - -#define DOUBLES_TERA_TRIGGER_POS_X_OPTIMAL (30) -#define DOUBLES_TERA_TRIGGER_POS_X_PRIORITY (31) -#define DOUBLES_TERA_TRIGGER_POS_X_SLIDE (15) -#define DOUBLES_TERA_TRIGGER_POS_Y_DIFF (-4) - -#define tBattler data[0] -#define tHide data[1] - -void CreateTeraTriggerSprite(u8 battler, u8 palId) -{ - LoadSpritePalette(&sSpritePalette_TeraTrigger); - if (GetSpriteTileStartByTag(TAG_TERA_TRIGGER_TILE) == 0xFFFF) - { - LoadSpriteSheet(&sSpriteSheet_TeraTrigger); - } - if (gBattleStruct->tera.triggerSpriteId == 0xFF) - { - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) - gBattleStruct->tera.triggerSpriteId = CreateSprite(&sSpriteTemplate_TeraTrigger, - gSprites[gHealthboxSpriteIds[battler]].x - DOUBLES_TERA_TRIGGER_POS_X_SLIDE, - gSprites[gHealthboxSpriteIds[battler]].y - DOUBLES_TERA_TRIGGER_POS_Y_DIFF, 0); - else - gBattleStruct->tera.triggerSpriteId = CreateSprite(&sSpriteTemplate_TeraTrigger, - gSprites[gHealthboxSpriteIds[battler]].x - SINGLES_TERA_TRIGGER_POS_X_SLIDE, - gSprites[gHealthboxSpriteIds[battler]].y - SINGLES_TERA_TRIGGER_POS_Y_DIFF, 0); - } - gSprites[gBattleStruct->tera.triggerSpriteId].tBattler = battler; - gSprites[gBattleStruct->tera.triggerSpriteId].tHide = FALSE; - - ChangeTeraTriggerSprite(gBattleStruct->tera.triggerSpriteId, palId); -} - -static void SpriteCb_TeraTrigger(struct Sprite *sprite) -{ - s32 xSlide, xPriority, xOptimal; - s32 yDiff; - - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) - { - xSlide = DOUBLES_TERA_TRIGGER_POS_X_SLIDE; - xPriority = DOUBLES_TERA_TRIGGER_POS_X_PRIORITY; - xOptimal = DOUBLES_TERA_TRIGGER_POS_X_OPTIMAL; - yDiff = DOUBLES_TERA_TRIGGER_POS_Y_DIFF; - } - else - { - xSlide = SINGLES_TERA_TRIGGER_POS_X_SLIDE; - xPriority = SINGLES_TERA_TRIGGER_POS_X_PRIORITY; - xOptimal = SINGLES_TERA_TRIGGER_POS_X_OPTIMAL; - yDiff = SINGLES_TERA_TRIGGER_POS_Y_DIFF; - } - - if (sprite->tHide) - { - if (sprite->x != gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xSlide) - sprite->x++; - - if (sprite->x >= gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xPriority) - sprite->oam.priority = 2; - else - sprite->oam.priority = 1; - - sprite->y = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y - yDiff; - sprite->y2 = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y2 - yDiff; - if (sprite->x == gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xSlide) - DestroyTeraTriggerSprite(); - } - else - { - if (sprite->x != gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xOptimal) - sprite->x--; - - if (sprite->x >= gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xPriority) - sprite->oam.priority = 2; - else - sprite->oam.priority = 1; - - sprite->y = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y - yDiff; - sprite->y2 = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y2 - yDiff; - } -} - -bool32 IsTeraTriggerSpriteActive(void) -{ - if (GetSpriteTileStartByTag(TAG_TERA_TRIGGER_TILE) == 0xFFFF) - return FALSE; - else if (IndexOfSpritePaletteTag(TAG_TERA_TRIGGER_PAL) != 0xFF) - return TRUE; - else - return FALSE; -} - -void HideTeraTriggerSprite(void) -{ - if (gBattleStruct->tera.triggerSpriteId != 0xFF) - { - ChangeTeraTriggerSprite(gBattleStruct->tera.triggerSpriteId, 0); - gSprites[gBattleStruct->tera.triggerSpriteId].tHide = TRUE; - } -} - -void DestroyTeraTriggerSprite(void) -{ - FreeSpritePaletteByTag(TAG_TERA_TRIGGER_PAL); - FreeSpriteTilesByTag(TAG_TERA_TRIGGER_TILE); - if (gBattleStruct->tera.triggerSpriteId != 0xFF) - DestroySprite(&gSprites[gBattleStruct->tera.triggerSpriteId]); - gBattleStruct->tera.triggerSpriteId = 0xFF; -} - -#undef tBattler -#undef tHide - -// TERA INDICATOR: -static const u16 sTeraIndicatorPal[] = INCBIN_U16("graphics/battle_interface/tera_indicator.gbapal"); -static const u8 ALIGNED(4) sNormalIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/normal_indicator.4bpp"); -static const u8 ALIGNED(4) sFightingIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/fighting_indicator.4bpp"); -static const u8 ALIGNED(4) sFlyingIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/flying_indicator.4bpp"); -static const u8 ALIGNED(4) sPoisonIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/poison_indicator.4bpp"); -static const u8 ALIGNED(4) sGroundIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/ground_indicator.4bpp"); -static const u8 ALIGNED(4) sRockIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/rock_indicator.4bpp"); -static const u8 ALIGNED(4) sBugIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/bug_indicator.4bpp"); -static const u8 ALIGNED(4) sGhostIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/ghost_indicator.4bpp"); -static const u8 ALIGNED(4) sSteelIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/steel_indicator.4bpp"); -static const u8 ALIGNED(4) sFireIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/fire_indicator.4bpp"); -static const u8 ALIGNED(4) sWaterIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/water_indicator.4bpp"); -static const u8 ALIGNED(4) sGrassIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/grass_indicator.4bpp"); -static const u8 ALIGNED(4) sElectricIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/electric_indicator.4bpp"); -static const u8 ALIGNED(4) sPsychicIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/psychic_indicator.4bpp"); -static const u8 ALIGNED(4) sIceIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/ice_indicator.4bpp"); -static const u8 ALIGNED(4) sDragonIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/dragon_indicator.4bpp"); -static const u8 ALIGNED(4) sDarkIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/dark_indicator.4bpp"); -static const u8 ALIGNED(4) sFairyIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/fairy_indicator.4bpp"); -static const u8 ALIGNED(4) sStellarIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/stellar_indicator.4bpp"); - -static void SpriteCb_TeraIndicator(struct Sprite *sprite); -static const s8 sIndicatorPositions[][2] = -{ - [B_POSITION_PLAYER_LEFT] = {53, -9}, - [B_POSITION_OPPONENT_LEFT] = {44, -9}, - [B_POSITION_PLAYER_RIGHT] = {52, -9}, - [B_POSITION_OPPONENT_RIGHT] = {44, -9}, -}; - -static const struct SpritePalette sSpritePalette_TeraIndicator = -{ - sTeraIndicatorPal, TAG_TERA_INDICATOR_PAL -}; - -static const struct OamData sOamData_TeraIndicator = -{ - .shape = SPRITE_SHAPE(16x16), - .size = SPRITE_SIZE(16x16), - .priority = 1, -}; - -static const struct SpriteTemplate sSpriteTemplate_NormalIndicator = -{ - .tileTag = TAG_NORMAL_INDICATOR_TILE, - .paletteTag = TAG_TERA_INDICATOR_PAL, - .oam = &sOamData_TeraIndicator, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_TeraIndicator, -}; - -static const struct SpriteTemplate sSpriteTemplate_FightingIndicator = -{ - .tileTag = TAG_FIGHTING_INDICATOR_TILE, - .paletteTag = TAG_TERA_INDICATOR_PAL, - .oam = &sOamData_TeraIndicator, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_TeraIndicator, -}; - -static const struct SpriteTemplate sSpriteTemplate_FlyingIndicator = -{ - .tileTag = TAG_FLYING_INDICATOR_TILE, - .paletteTag = TAG_TERA_INDICATOR_PAL, - .oam = &sOamData_TeraIndicator, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_TeraIndicator, -}; - -static const struct SpriteTemplate sSpriteTemplate_PoisonIndicator = -{ - .tileTag = TAG_POISON_INDICATOR_TILE, - .paletteTag = TAG_TERA_INDICATOR_PAL, - .oam = &sOamData_TeraIndicator, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_TeraIndicator, -}; - -static const struct SpriteTemplate sSpriteTemplate_GroundIndicator = -{ - .tileTag = TAG_GROUND_INDICATOR_TILE, - .paletteTag = TAG_TERA_INDICATOR_PAL, - .oam = &sOamData_TeraIndicator, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_TeraIndicator, -}; - -static const struct SpriteTemplate sSpriteTemplate_RockIndicator = -{ - .tileTag = TAG_ROCK_INDICATOR_TILE, - .paletteTag = TAG_TERA_INDICATOR_PAL, - .oam = &sOamData_TeraIndicator, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_TeraIndicator, -}; - -static const struct SpriteTemplate sSpriteTemplate_BugIndicator = -{ - .tileTag = TAG_BUG_INDICATOR_TILE, - .paletteTag = TAG_TERA_INDICATOR_PAL, - .oam = &sOamData_TeraIndicator, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_TeraIndicator, -}; - -static const struct SpriteTemplate sSpriteTemplate_GhostIndicator = -{ - .tileTag = TAG_GHOST_INDICATOR_TILE, - .paletteTag = TAG_TERA_INDICATOR_PAL, - .oam = &sOamData_TeraIndicator, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_TeraIndicator, -}; - -static const struct SpriteTemplate sSpriteTemplate_SteelIndicator = -{ - .tileTag = TAG_STEEL_INDICATOR_TILE, - .paletteTag = TAG_TERA_INDICATOR_PAL, - .oam = &sOamData_TeraIndicator, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_TeraIndicator, -}; - -static const struct SpriteTemplate sSpriteTemplate_FireIndicator = -{ - .tileTag = TAG_FIRE_INDICATOR_TILE, - .paletteTag = TAG_TERA_INDICATOR_PAL, - .oam = &sOamData_TeraIndicator, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_TeraIndicator, -}; - -static const struct SpriteTemplate sSpriteTemplate_WaterIndicator = -{ - .tileTag = TAG_WATER_INDICATOR_TILE, - .paletteTag = TAG_TERA_INDICATOR_PAL, - .oam = &sOamData_TeraIndicator, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_TeraIndicator, -}; - -static const struct SpriteTemplate sSpriteTemplate_GrassIndicator = -{ - .tileTag = TAG_GRASS_INDICATOR_TILE, - .paletteTag = TAG_TERA_INDICATOR_PAL, - .oam = &sOamData_TeraIndicator, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_TeraIndicator, -}; - -static const struct SpriteTemplate sSpriteTemplate_ElectricIndicator = -{ - .tileTag = TAG_ELECTRIC_INDICATOR_TILE, - .paletteTag = TAG_TERA_INDICATOR_PAL, - .oam = &sOamData_TeraIndicator, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_TeraIndicator, -}; - -static const struct SpriteTemplate sSpriteTemplate_PsychicIndicator = -{ - .tileTag = TAG_PSYCHIC_INDICATOR_TILE, - .paletteTag = TAG_TERA_INDICATOR_PAL, - .oam = &sOamData_TeraIndicator, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_TeraIndicator, -}; - -static const struct SpriteTemplate sSpriteTemplate_IceIndicator = -{ - .tileTag = TAG_ICE_INDICATOR_TILE, - .paletteTag = TAG_TERA_INDICATOR_PAL, - .oam = &sOamData_TeraIndicator, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_TeraIndicator, -}; - -static const struct SpriteTemplate sSpriteTemplate_DragonIndicator = -{ - .tileTag = TAG_DRAGON_INDICATOR_TILE, - .paletteTag = TAG_TERA_INDICATOR_PAL, - .oam = &sOamData_TeraIndicator, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_TeraIndicator, -}; - -static const struct SpriteTemplate sSpriteTemplate_DarkIndicator = -{ - .tileTag = TAG_DARK_INDICATOR_TILE, - .paletteTag = TAG_TERA_INDICATOR_PAL, - .oam = &sOamData_TeraIndicator, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_TeraIndicator, -}; - -static const struct SpriteTemplate sSpriteTemplate_FairyIndicator = -{ - .tileTag = TAG_FAIRY_INDICATOR_TILE, - .paletteTag = TAG_TERA_INDICATOR_PAL, - .oam = &sOamData_TeraIndicator, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_TeraIndicator, -}; - -static const struct SpriteTemplate sSpriteTemplate_StellarIndicator = -{ - .tileTag = TAG_STELLAR_INDICATOR_TILE, - .paletteTag = TAG_TERA_INDICATOR_PAL, - .oam = &sOamData_TeraIndicator, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCb_TeraIndicator, -}; - -static const struct SpriteSheet sTeraIndicatorSpriteSheets[NUMBER_OF_MON_TYPES + 1] = -{ - {sNormalIndicatorGfx, sizeof(sNormalIndicatorGfx), TAG_NORMAL_INDICATOR_TILE}, // TYPE_NONE - {sNormalIndicatorGfx, sizeof(sNormalIndicatorGfx), TAG_NORMAL_INDICATOR_TILE}, - {sFightingIndicatorGfx, sizeof(sFightingIndicatorGfx), TAG_FIGHTING_INDICATOR_TILE}, - {sFlyingIndicatorGfx, sizeof(sFlyingIndicatorGfx), TAG_FLYING_INDICATOR_TILE}, - {sPoisonIndicatorGfx, sizeof(sPoisonIndicatorGfx), TAG_POISON_INDICATOR_TILE}, - {sGroundIndicatorGfx, sizeof(sGroundIndicatorGfx), TAG_GROUND_INDICATOR_TILE}, - {sRockIndicatorGfx, sizeof(sRockIndicatorGfx), TAG_ROCK_INDICATOR_TILE}, - {sBugIndicatorGfx, sizeof(sBugIndicatorGfx), TAG_BUG_INDICATOR_TILE}, - {sGhostIndicatorGfx, sizeof(sGhostIndicatorGfx), TAG_GHOST_INDICATOR_TILE}, - {sSteelIndicatorGfx, sizeof(sSteelIndicatorGfx), TAG_STEEL_INDICATOR_TILE}, - {sNormalIndicatorGfx, sizeof(sNormalIndicatorGfx), TAG_NORMAL_INDICATOR_TILE}, // TYPE_MYSTERY - {sFireIndicatorGfx, sizeof(sFireIndicatorGfx), TAG_FIRE_INDICATOR_TILE}, - {sWaterIndicatorGfx, sizeof(sWaterIndicatorGfx), TAG_WATER_INDICATOR_TILE}, - {sGrassIndicatorGfx, sizeof(sGrassIndicatorGfx), TAG_GRASS_INDICATOR_TILE}, - {sElectricIndicatorGfx, sizeof(sElectricIndicatorGfx), TAG_ELECTRIC_INDICATOR_TILE}, - {sPsychicIndicatorGfx, sizeof(sPsychicIndicatorGfx), TAG_PSYCHIC_INDICATOR_TILE}, - {sIceIndicatorGfx, sizeof(sIceIndicatorGfx), TAG_ICE_INDICATOR_TILE}, - {sDragonIndicatorGfx, sizeof(sDragonIndicatorGfx), TAG_DRAGON_INDICATOR_TILE}, - {sDarkIndicatorGfx, sizeof(sDarkIndicatorGfx), TAG_DARK_INDICATOR_TILE}, - {sFairyIndicatorGfx, sizeof(sFairyIndicatorGfx), TAG_FAIRY_INDICATOR_TILE}, - {sStellarIndicatorGfx, sizeof(sStellarIndicatorGfx), TAG_STELLAR_INDICATOR_TILE}, - {0} -}; - -static const struct SpriteTemplate * const sTeraIndicatorSpriteTemplates[NUMBER_OF_MON_TYPES] = -{ - [TYPE_NONE] = &sSpriteTemplate_NormalIndicator, // just in case - [TYPE_NORMAL] = &sSpriteTemplate_NormalIndicator, - [TYPE_FIGHTING] = &sSpriteTemplate_FightingIndicator, - [TYPE_FLYING] = &sSpriteTemplate_FlyingIndicator, - [TYPE_POISON] = &sSpriteTemplate_PoisonIndicator, - [TYPE_GROUND] = &sSpriteTemplate_GroundIndicator, - [TYPE_ROCK] = &sSpriteTemplate_RockIndicator, - [TYPE_BUG] = &sSpriteTemplate_BugIndicator, - [TYPE_GHOST] = &sSpriteTemplate_GhostIndicator, - [TYPE_STEEL] = &sSpriteTemplate_SteelIndicator, - [TYPE_MYSTERY] = &sSpriteTemplate_NormalIndicator, // just in case - [TYPE_FIRE] = &sSpriteTemplate_FireIndicator, - [TYPE_WATER] = &sSpriteTemplate_WaterIndicator, - [TYPE_GRASS] = &sSpriteTemplate_GrassIndicator, - [TYPE_ELECTRIC] = &sSpriteTemplate_ElectricIndicator, - [TYPE_PSYCHIC] = &sSpriteTemplate_PsychicIndicator, - [TYPE_ICE] = &sSpriteTemplate_IceIndicator, - [TYPE_DRAGON] = &sSpriteTemplate_DragonIndicator, - [TYPE_DARK] = &sSpriteTemplate_DarkIndicator, - [TYPE_FAIRY] = &sSpriteTemplate_FairyIndicator, - [TYPE_STELLAR] = &sSpriteTemplate_StellarIndicator, -}; - -// for sprite data fields -#define tBattler data[0] -#define tType data[1] // Indicator type: tera -#define tPosX data[2] -#define tLevelXDelta data[3] // X position depends whether level has 3, 2 or 1 digit - -// data fields for healthboxMain -// oam.affineParam holds healthboxRight spriteId -#define hMain_TeraIndicatorId data[3] -#define hMain_HealthBarSpriteId data[5] -#define hMain_Battler data[6] -#define hMain_Data7 data[7] - -// data fields for healthboxRight -#define hOther_HealthBoxSpriteId data[5] - -// data fields for healthbar -#define hBar_HealthBoxSpriteId data[5] - -void TeraIndicator_LoadSpriteGfx(void) -{ - LoadSpriteSheets(sTeraIndicatorSpriteSheets); - LoadSpritePalette(&sSpritePalette_TeraIndicator); -} - -bool32 TeraIndicator_ShouldBeInvisible(u32 battler) -{ - return !IsTerastallized(battler); -} - -u8 TeraIndicator_GetSpriteId(u32 healthboxSpriteId) -{ - return gBattleStruct->tera.indicatorSpriteId[gSprites[healthboxSpriteId].hMain_Battler]; -} - -void TeraIndicator_SetVisibilities(u32 healthboxId, bool32 invisible) -{ - u8 spriteId = TeraIndicator_GetSpriteId(healthboxId); - u32 battler = gSprites[healthboxId].hMain_Battler; - - if (GetSafariZoneFlag()) - return; - - if (invisible == TRUE) - gSprites[spriteId].invisible = TRUE; - else // Try visible. - gSprites[spriteId].invisible = TeraIndicator_ShouldBeInvisible(battler); -} - -void TeraIndicator_UpdateOamPriorities(u32 healthboxId, u32 oamPriority) -{ - u8 spriteId = TeraIndicator_GetSpriteId(healthboxId); - gSprites[spriteId].oam.priority = oamPriority; -} - -void TeraIndicator_UpdateLevel(u32 healthboxId, u32 level) -{ - s16 xDelta = 0; - u8 spriteId = TeraIndicator_GetSpriteId(healthboxId); - - if (level >= 100) - xDelta -= 4; - else if (level < 10) - xDelta += 5; - - gSprites[spriteId].tLevelXDelta = xDelta; -} - -void TeraIndicator_CreateSprite(u32 battler, u32 healthboxSpriteId) -{ - u32 position; - u8 spriteId; - s16 xHealthbox = 0, y = 0; - s32 x = 0; - u32 type = GetBattlerTeraType(battler); - - position = GetBattlerPosition(battler); - GetBattlerHealthboxCoords(battler, &xHealthbox, &y); - - x = sIndicatorPositions[position][0]; - y += sIndicatorPositions[position][1]; - - spriteId = gBattleStruct->tera.indicatorSpriteId[battler] = CreateSpriteAtEnd(sTeraIndicatorSpriteTemplates[type], 0, y, 0); - gSprites[spriteId].tBattler = battler; - gSprites[spriteId].tPosX = x; - gSprites[spriteId].invisible = TRUE; -} - -void TeraIndicator_DestroySprite(u32 healthboxSpriteId) -{ - u8 spriteId = TeraIndicator_GetSpriteId(healthboxSpriteId); - DestroySprite(&gSprites[spriteId]); -} - -void TeraIndicator_UpdateType(u32 battler, u32 healthboxSpriteId) -{ - TeraIndicator_DestroySprite(healthboxSpriteId); - TeraIndicator_CreateSprite(battler, healthboxSpriteId); -} - -static void SpriteCb_TeraIndicator(struct Sprite *sprite) -{ - u32 battler = sprite->tBattler; - - sprite->x = gSprites[gHealthboxSpriteIds[battler]].x + sprite->tPosX + sprite->tLevelXDelta; - sprite->x2 = gSprites[gHealthboxSpriteIds[battler]].x2; - sprite->y2 = gSprites[gHealthboxSpriteIds[battler]].y2; -} - -#undef tBattler -#undef tType -#undef tPosX -#undef tLevelXDelta diff --git a/src/battle_transition.c b/src/battle_transition.c index 3e11ccb43e..b2b295e16e 100644 --- a/src/battle_transition.c +++ b/src/battle_transition.c @@ -212,7 +212,6 @@ static bool8 AngledWipes_TryEnd(struct Task *); static bool8 AngledWipes_StartNext(struct Task *); static bool8 ShredSplit_Init(struct Task *); static bool8 ShredSplit_Main(struct Task *); -static bool8 ShredSplit_BrokenCheck(struct Task *); static bool8 ShredSplit_End(struct Task *); static bool8 Blackhole_Init(struct Task *); static bool8 Blackhole_Vibrate(struct Task *); @@ -561,7 +560,6 @@ static const TransitionStateFunc sShredSplit_Funcs[] = { ShredSplit_Init, ShredSplit_Main, - ShredSplit_BrokenCheck, ShredSplit_End }; @@ -2928,29 +2926,6 @@ static bool8 ShredSplit_Main(struct Task *task) return FALSE; } -// This function never increments the state counter, because the loop condition -// is always false, resulting in the game being stuck in an infinite loop. -// It's possible this transition is only partially -// done and the second part was left out. -// In any case removing or bypassing this state allows the transition to finish. -static bool8 ShredSplit_BrokenCheck(struct Task *task) -{ - u16 i; - bool32 done = TRUE; - u16 checkVar2 = 0xFF10; - - for (i = 0; i < DISPLAY_HEIGHT; i++) - { - if (gScanlineEffectRegBuffers[1][i] != DISPLAY_WIDTH && gScanlineEffectRegBuffers[1][i] != checkVar2) - done = FALSE; - } - - if (done == TRUE) - task->tState++; - - return FALSE; -} - static bool8 ShredSplit_End(struct Task *task) { DmaStop(0); diff --git a/src/battle_util.c b/src/battle_util.c index 4b66ea6ba5..99a609a513 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -8,6 +8,7 @@ #include "battle_interface.h" #include "battle_setup.h" #include "battle_z_move.h" +#include "battle_gimmick.h" #include "party_menu.h" #include "pokemon.h" #include "international_string_util.h" @@ -157,7 +158,7 @@ void HandleAction_UseMove(void) gCurrentMove = gChosenMove = gLockedMoves[gBattlerAttacker]; } // encore forces you to use the same move - else if (!gBattleStruct->zmove.active && gDisableStructs[gBattlerAttacker].encoredMove != MOVE_NONE + else if (GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && gDisableStructs[gBattlerAttacker].encoredMove != MOVE_NONE && gDisableStructs[gBattlerAttacker].encoredMove == gBattleMons[gBattlerAttacker].moves[gDisableStructs[gBattlerAttacker].encoredMovePos]) { gCurrentMove = gChosenMove = gDisableStructs[gBattlerAttacker].encoredMove; @@ -165,7 +166,7 @@ void HandleAction_UseMove(void) *(gBattleStruct->moveTarget + gBattlerAttacker) = GetMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE); } // check if the encored move wasn't overwritten - else if (!gBattleStruct->zmove.active && gDisableStructs[gBattlerAttacker].encoredMove != MOVE_NONE + else if (GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && gDisableStructs[gBattlerAttacker].encoredMove != MOVE_NONE && gDisableStructs[gBattlerAttacker].encoredMove != gBattleMons[gBattlerAttacker].moves[gDisableStructs[gBattlerAttacker].encoredMovePos]) { gCurrMovePos = gChosenMovePos = gDisableStructs[gBattlerAttacker].encoredMovePos; @@ -185,12 +186,6 @@ void HandleAction_UseMove(void) gCurrentMove = gChosenMove = gBattleMons[gBattlerAttacker].moves[gCurrMovePos]; } - // check z move used - if (gBattleStruct->zmove.toBeUsed[gBattlerAttacker] != MOVE_NONE && !IS_MOVE_STATUS(gCurrentMove)) - { - gCurrentMove = gBattleStruct->zmove.toBeUsed[gBattlerAttacker]; - } - if (IsBattlerAlive(gBattlerAttacker)) { if (GetBattlerSide(gBattlerAttacker) == B_SIDE_PLAYER) @@ -203,11 +198,17 @@ void HandleAction_UseMove(void) SetTypeBeforeUsingMove(gChosenMove, gBattlerAttacker); GET_MOVE_TYPE(gChosenMove, moveType); - // check max move used - if (gBattleStruct->dynamax.usingMaxMove[gBattlerAttacker]) + // check Z-Move used + if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE && !IS_MOVE_STATUS(gCurrentMove) && !IsZMove(gCurrentMove)) { + gBattleStruct->categoryOverride = gMovesInfo[gCurrentMove].category; + gCurrentMove = gChosenMove = GetUsableZMove(gBattlerAttacker, gCurrentMove); + } + // check Max Move used + else if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX) + { + gBattleStruct->categoryOverride = gMovesInfo[gCurrentMove].category; gCurrentMove = gChosenMove = GetMaxMove(gBattlerAttacker, gCurrentMove); - gBattleStruct->dynamax.activeCategory = gBattleStruct->dynamax.categories[gBattlerAttacker]; } moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove); @@ -423,6 +424,8 @@ void HandleAction_Switch(void) if (gBattleResults.playerSwitchesCounter < 255) gBattleResults.playerSwitchesCounter++; + if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX) + UndoDynamax(gBattlerAttacker); // this is better performed here instead of SwitchInClearSetData TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_SWITCH); } @@ -720,7 +723,7 @@ void HandleAction_ActionFinished(void) // check if Stellar type boost should be used up GET_MOVE_TYPE(gCurrentMove, moveType); - if (IsTerastallized(gBattlerAttacker) + if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_TERA && GetBattlerTeraType(gBattlerAttacker) == TYPE_STELLAR && gMovesInfo[gCurrentMove].category != DAMAGE_CATEGORY_STATUS && IsTypeStellarBoosted(gBattlerAttacker, moveType)) @@ -741,7 +744,6 @@ void HandleAction_ActionFinished(void) gBattleCommunication[4] = 0; gBattleScripting.multihitMoveEffect = 0; gBattleResources->battleScriptsStack->size = 0; - gBattleStruct->dynamax.usingMaxMove[gBattlerAttacker] = 0; if (B_RECALC_TURN_AFTER_ACTIONS >= GEN_8 && !afterYouActive && !gBattleStruct->pledgeMove) { @@ -930,11 +932,11 @@ u8 GetBattlerForBattleScript(u8 caseId) case BS_FAINTED: ret = gBattlerFainted; break; - case BS_FAINTED_LINK_MULTIPLE_1: + case BS_FAINTED_MULTIPLE_1: ret = gBattlerFainted; break; case BS_ATTACKER_WITH_PARTNER: - case BS_FAINTED_LINK_MULTIPLE_2: + case BS_FAINTED_MULTIPLE_2: case BS_ATTACKER_SIDE: case BS_TARGET_SIDE: case BS_PLAYER1: @@ -1252,17 +1254,17 @@ bool32 IsBelchPreventingMove(u32 battler, u32 move) } // Dynamax bypasses all selection prevention except Taunt and Assault Vest. -#define DYNAMAX_BYPASS_CHECK !gBattleStruct->dynamax.playerSelect && !IsDynamaxed(gBattlerAttacker) +#define DYNAMAX_BYPASS_CHECK (!IsGimmickSelected(gBattlerAttacker, GIMMICK_DYNAMAX) && GetActiveGimmick(gBattlerAttacker) != GIMMICK_DYNAMAX) u32 TrySetCantSelectMoveBattleScript(u32 battler) { u32 limitations = 0; - u8 moveId = gBattleResources->bufferB[battler][2] & ~(RET_MEGA_EVOLUTION | RET_ULTRA_BURST | RET_DYNAMAX | RET_TERASTAL); + u8 moveId = gBattleResources->bufferB[battler][2] & ~RET_GIMMICK; u32 move = gBattleMons[battler].moves[moveId]; u32 holdEffect = GetBattlerHoldEffect(battler, TRUE); u16 *choicedMove = &gBattleStruct->choicedMove[battler]; - if (DYNAMAX_BYPASS_CHECK && gBattleStruct->zmove.toBeUsed[gBattlerAttacker] == MOVE_NONE && gDisableStructs[battler].disabledMove == move && move != MOVE_NONE) + if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && gDisableStructs[battler].disabledMove == move && move != MOVE_NONE) { gBattleScripting.battler = battler; gCurrentMove = move; @@ -1278,7 +1280,7 @@ u32 TrySetCantSelectMoveBattleScript(u32 battler) } } - if (DYNAMAX_BYPASS_CHECK && gBattleStruct->zmove.toBeUsed[gBattlerAttacker] == MOVE_NONE && move == gLastMoves[battler] && move != MOVE_STRUGGLE && (gBattleMons[battler].status2 & STATUS2_TORMENT)) + if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && move == gLastMoves[battler] && move != MOVE_STRUGGLE && (gBattleMons[battler].status2 & STATUS2_TORMENT)) { CancelMultiTurnMoves(battler); if (gBattleTypeFlags & BATTLE_TYPE_PALACE) @@ -1293,9 +1295,9 @@ u32 TrySetCantSelectMoveBattleScript(u32 battler) } } - if (gBattleStruct->zmove.toBeUsed[gBattlerAttacker] == MOVE_NONE && gDisableStructs[battler].tauntTimer != 0 && IS_MOVE_STATUS(move)) + if (GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && gDisableStructs[battler].tauntTimer != 0 && IS_MOVE_STATUS(move)) { - if (IsDynamaxed(gBattlerAttacker)) + if ((GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX)) gCurrentMove = MOVE_MAX_GUARD; else gCurrentMove = move; @@ -1311,7 +1313,7 @@ u32 TrySetCantSelectMoveBattleScript(u32 battler) } } - if (DYNAMAX_BYPASS_CHECK && gBattleStruct->zmove.toBeUsed[gBattlerAttacker] == MOVE_NONE && gDisableStructs[battler].throatChopTimer != 0 && gMovesInfo[move].soundMove) + if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && gDisableStructs[battler].throatChopTimer != 0 && gMovesInfo[move].soundMove) { gCurrentMove = move; if (gBattleTypeFlags & BATTLE_TYPE_PALACE) @@ -1326,7 +1328,7 @@ u32 TrySetCantSelectMoveBattleScript(u32 battler) } } - if (DYNAMAX_BYPASS_CHECK && gBattleStruct->zmove.toBeUsed[gBattlerAttacker] == MOVE_NONE && GetImprisonedMovesCount(battler, move)) + if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && GetImprisonedMovesCount(battler, move)) { gCurrentMove = move; if (gBattleTypeFlags & BATTLE_TYPE_PALACE) @@ -1341,7 +1343,7 @@ u32 TrySetCantSelectMoveBattleScript(u32 battler) } } - if (DYNAMAX_BYPASS_CHECK && gBattleStruct->zmove.toBeUsed[gBattlerAttacker] == MOVE_NONE && IsGravityPreventingMove(move)) + if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && IsGravityPreventingMove(move)) { gCurrentMove = move; if (gBattleTypeFlags & BATTLE_TYPE_PALACE) @@ -1356,7 +1358,7 @@ u32 TrySetCantSelectMoveBattleScript(u32 battler) } } - if (DYNAMAX_BYPASS_CHECK && gBattleStruct->zmove.toBeUsed[gBattlerAttacker] == MOVE_NONE && IsHealBlockPreventingMove(battler, move)) + if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && IsHealBlockPreventingMove(battler, move)) { gCurrentMove = move; if (gBattleTypeFlags & BATTLE_TYPE_PALACE) @@ -1371,7 +1373,7 @@ u32 TrySetCantSelectMoveBattleScript(u32 battler) } } - if (DYNAMAX_BYPASS_CHECK && gBattleStruct->zmove.toBeUsed[gBattlerAttacker] == MOVE_NONE && IsBelchPreventingMove(battler, move)) + if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && IsBelchPreventingMove(battler, move)) { gCurrentMove = move; if (gBattleTypeFlags & BATTLE_TYPE_PALACE) @@ -1435,7 +1437,7 @@ u32 TrySetCantSelectMoveBattleScript(u32 battler) } else if (holdEffect == HOLD_EFFECT_ASSAULT_VEST && IS_MOVE_STATUS(move) && gMovesInfo[move].effect != EFFECT_ME_FIRST) { - if (IsDynamaxed(gBattlerAttacker)) + if ((GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX)) gCurrentMove = MOVE_MAX_GUARD; else gCurrentMove = move; @@ -1689,7 +1691,10 @@ static bool32 EndTurnTerrain(u32 terrainFlag, u32 stringTableId) gFieldStatuses &= ~terrainFlag; TryToRevertMimicryAndFlags(); gBattleCommunication[MULTISTRING_CHOOSER] = stringTableId; - BattleScriptExecute(BattleScript_TerrainEnds); + if (terrainFlag & STATUS_FIELD_GRASSY_TERRAIN) + BattleScriptExecute(BattleScript_GrassyTerrainEnds); + else + BattleScriptExecute(BattleScript_TerrainEnds); return TRUE; } } @@ -2918,7 +2923,7 @@ u8 DoBattlerEndTurnEffects(void) gBattleStruct->turnEffectsTracker++; break; case ENDTURN_DYNAMAX: - if (IsDynamaxed(battler) + if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX && --gBattleStruct->dynamax.dynamaxTurns[battler] == 0) { gBattleScripting.battler = battler; @@ -3297,7 +3302,7 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType) gBattleStruct->atkCancellerTracker++; break; case CANCELLER_DISABLED: // disabled move - if (gBattleStruct->zmove.toBeUsed[gBattlerAttacker] == MOVE_NONE && gDisableStructs[gBattlerAttacker].disabledMove == gCurrentMove && gDisableStructs[gBattlerAttacker].disabledMove != MOVE_NONE) + if (GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && gDisableStructs[gBattlerAttacker].disabledMove == gCurrentMove && gDisableStructs[gBattlerAttacker].disabledMove != MOVE_NONE) { gProtectStructs[gBattlerAttacker].usedDisabledMove = TRUE; gBattleScripting.battler = gBattlerAttacker; @@ -3309,7 +3314,7 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType) gBattleStruct->atkCancellerTracker++; break; case CANCELLER_HEAL_BLOCKED: - if (gBattleStruct->zmove.toBeUsed[gBattlerAttacker] == MOVE_NONE && gStatuses3[gBattlerAttacker] & STATUS3_HEAL_BLOCK && IsHealBlockPreventingMove(gBattlerAttacker, gCurrentMove)) + if (GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && gStatuses3[gBattlerAttacker] & STATUS3_HEAL_BLOCK && IsHealBlockPreventingMove(gBattlerAttacker, gCurrentMove)) { gProtectStructs[gBattlerAttacker].usedHealBlockedMove = TRUE; gBattleScripting.battler = gBattlerAttacker; @@ -3333,7 +3338,7 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType) gBattleStruct->atkCancellerTracker++; break; case CANCELLER_TAUNTED: // taunt - if (gBattleStruct->zmove.toBeUsed[gBattlerAttacker] == MOVE_NONE && gDisableStructs[gBattlerAttacker].tauntTimer && IS_MOVE_STATUS(gCurrentMove)) + if (GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && gDisableStructs[gBattlerAttacker].tauntTimer && IS_MOVE_STATUS(gCurrentMove)) { gProtectStructs[gBattlerAttacker].usedTauntedMove = TRUE; CancelMultiTurnMoves(gBattlerAttacker); @@ -3344,7 +3349,7 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType) gBattleStruct->atkCancellerTracker++; break; case CANCELLER_IMPRISONED: // imprisoned - if (gBattleStruct->zmove.toBeUsed[gBattlerAttacker] == MOVE_NONE && GetImprisonedMovesCount(gBattlerAttacker, gCurrentMove)) + if (GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && GetImprisonedMovesCount(gBattlerAttacker, gCurrentMove)) { gProtectStructs[gBattlerAttacker].usedImprisonedMove = TRUE; CancelMultiTurnMoves(gBattlerAttacker); @@ -3362,7 +3367,7 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType) if (gBattleMons[gBattlerAttacker].status2 & STATUS2_CONFUSION) { // confusion dmg - if (RandomWeighted(RNG_CONFUSION, (B_CONFUSION_SELF_DMG_CHANCE >= GEN_7 ? 2 : 1), 1)) + if (RandomPercentage(RNG_CONFUSION, (B_CONFUSION_SELF_DMG_CHANCE >= GEN_7 ? 33 : 50))) { gBattleCommunication[MULTISTRING_CHOOSER] = TRUE; gBattlerTarget = gBattlerAttacker; @@ -3508,7 +3513,7 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType) gBattleStruct->atkCancellerTracker++; break; case CANCELLER_THROAT_CHOP: - if (gBattleStruct->zmove.toBeUsed[gBattlerAttacker] == MOVE_NONE && gDisableStructs[gBattlerAttacker].throatChopTimer && gMovesInfo[gCurrentMove].soundMove) + if (GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && gDisableStructs[gBattlerAttacker].throatChopTimer && gMovesInfo[gCurrentMove].soundMove) { gProtectStructs[gBattlerAttacker].usedThroatChopPreventedMove = TRUE; CancelMultiTurnMoves(gBattlerAttacker); @@ -3519,23 +3524,18 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType) gBattleStruct->atkCancellerTracker++; break; case CANCELLER_Z_MOVES: - if (gBattleStruct->zmove.toBeUsed[gBattlerAttacker] != MOVE_NONE) + if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE) { // For Z-Mirror Move, so it doesn't play the animation twice. - bool32 alreadyUsed = (gBattleStruct->zmove.used[gBattlerAttacker] == TRUE); + bool32 alreadyUsed = HasTrainerUsedGimmick(gBattlerAttacker, GIMMICK_Z_MOVE); - //attacker has a queued z move - gBattleStruct->zmove.active = TRUE; - gBattleStruct->zmove.activeCategory = gBattleStruct->zmove.categories[gBattlerAttacker]; + // attacker has a queued z move RecordItemEffectBattle(gBattlerAttacker, HOLD_EFFECT_Z_CRYSTAL); - gBattleStruct->zmove.used[gBattlerAttacker] = TRUE; - if ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE) && IsPartnerMonFromSameTrainer(gBattlerAttacker)) - gBattleStruct->zmove.used[BATTLE_PARTNER(gBattlerAttacker)] = TRUE; //if 1v1 double, set partner used flag as well + SetGimmickAsActivated(gBattlerAttacker, GIMMICK_Z_MOVE); gBattleScripting.battler = gBattlerAttacker; - if (gBattleStruct->zmove.activeCategory == DAMAGE_CATEGORY_STATUS) + if (gMovesInfo[gCurrentMove].category == DAMAGE_CATEGORY_STATUS) { - gBattleStruct->zmove.effect = gMovesInfo[gBattleStruct->zmove.baseMoves[gBattlerAttacker]].zMove.effect; if (!alreadyUsed) { BattleScriptPushCursor(); @@ -3883,7 +3883,7 @@ static bool32 TryChangeBattleTerrain(u32 battler, u32 statusFlag, u8 *timer) { if ((!(gFieldStatuses & statusFlag) && (!gBattleStruct->isSkyBattle))) { - gFieldStatuses &= ~(STATUS_FIELD_MISTY_TERRAIN | STATUS_FIELD_GRASSY_TERRAIN | STATUS_FIELD_ELECTRIC_TERRAIN | STATUS_FIELD_PSYCHIC_TERRAIN); + gFieldStatuses &= ~(STATUS_FIELD_TERRAIN_ANY | STATUS_FIELD_TERRAIN_PERMANENT); gFieldStatuses |= statusFlag; gDisableStructs[battler].terrainAbilityDone = FALSE; @@ -4861,7 +4861,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITY_SHED_SKIN: - if ((gBattleMons[battler].status1 & STATUS1_ANY) && (Random() % 3) == 0) + if ((gBattleMons[battler].status1 & STATUS1_ANY) + && (B_ABILITY_TRIGGER_CHANCE >= GEN_4 ? RandomPercentage(RNG_SHED_SKIN, 30) : RandomChance(RNG_SHED_SKIN, 1, 3))) { ABILITY_HEAL_MON_STATUS: if (gBattleMons[battler].status1 & (STATUS1_POISON | STATUS1_TOXIC_POISON)) @@ -5340,8 +5341,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && IsBattlerAlive(gBattlerAttacker) && !IsAbilityOnSide(gBattlerAttacker, ABILITY_AROMA_VEIL) && gBattleMons[gBattlerAttacker].pp[gChosenMovePos] != 0 - && !IsDynamaxed(gBattlerAttacker) // TODO: Max Moves don't make contact, useless? - && (Random() % 3) == 0) + && !(GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX) // TODO: Max Moves don't make contact, useless? + && RandomPercentage(RNG_CURSED_BODY, 30)) { gDisableStructs[gBattlerAttacker].disabledMove = gChosenMove; gDisableStructs[gBattlerAttacker].disableTimer = 4; @@ -5382,7 +5383,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && TARGET_TURN_DAMAGED && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS && IsMoveMakingContact(move, gBattlerAttacker) - && !IsDynamaxed(gBattlerTarget) + && !(GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX) && !gAbilitiesInfo[gBattleMons[gBattlerAttacker].ability].cantBeSwapped) { if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_ABILITY_SHIELD) @@ -5502,24 +5503,40 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITY_EFFECT_SPORE: - if (!IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_GRASS) - && GetBattlerAbility(gBattlerAttacker) != ABILITY_OVERCOAT + { + u32 ability = GetBattlerAbility(gBattlerAttacker); + if ((!IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_GRASS) || B_POWDER_GRASS < GEN_6) + && ability != ABILITY_OVERCOAT && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_SAFETY_GOGGLES) { - i = Random() % 3; - if (i == 0) + u32 poison, paralysis, sleep; + + if (B_ABILITY_TRIGGER_CHANCE >= GEN_5) + { + poison = 9; + paralysis = 19; + } + else + { + poison = 10; + paralysis = 20; + } + sleep = 30; + + i = RandomUniform(RNG_EFFECT_SPORE, 0, B_ABILITY_TRIGGER_CHANCE >= GEN_4 ? 99 : 299); + if (i < poison) goto POISON_POINT; - if (i == 1) + if (i < paralysis) goto STATIC; // Sleep - if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) + if (i < sleep + && !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) && IsBattlerAlive(gBattlerAttacker) && !gProtectStructs[gBattlerAttacker].confusionSelfDmg && TARGET_TURN_DAMAGED - && CanSleep(gBattlerAttacker) + && CanBeSlept(gBattlerAttacker, ability) && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS - && IsMoveMakingContact(move, gBattlerAttacker) - && (Random() % 3) == 0) + && IsMoveMakingContact(move, gBattlerAttacker)) { gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_SLEEP; PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); @@ -5529,43 +5546,48 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 effect++; } } + } break; - POISON_POINT: case ABILITY_POISON_POINT: - if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) - && IsBattlerAlive(gBattlerAttacker) - && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && TARGET_TURN_DAMAGED - && CanBePoisoned(gBattlerTarget, gBattlerAttacker) - && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS - && IsMoveMakingContact(move, gBattlerAttacker) - && RandomWeighted(RNG_POISON_POINT, 2, 1)) + if (B_ABILITY_TRIGGER_CHANCE >= GEN_4 ? RandomPercentage(RNG_POISON_POINT, 30) : RandomChance(RNG_POISON_POINT, 1, 3)) { - gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_POISON; - PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_AbilityStatusEffect; - gHitMarker |= HITMARKER_STATUS_ABILITY_EFFECT; - effect++; + POISON_POINT: + if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) + && IsBattlerAlive(gBattlerAttacker) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && TARGET_TURN_DAMAGED + && CanBePoisoned(gBattlerTarget, gBattlerAttacker, GetBattlerAbility(gBattlerAttacker)) + && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS + && IsMoveMakingContact(move, gBattlerAttacker)) + { + gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_POISON; + PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_AbilityStatusEffect; + gHitMarker |= HITMARKER_STATUS_ABILITY_EFFECT; + effect++; + } } break; - STATIC: case ABILITY_STATIC: - if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) - && IsBattlerAlive(gBattlerAttacker) - && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && TARGET_TURN_DAMAGED - && CanBeParalyzed(gBattlerAttacker) - && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS - && IsMoveMakingContact(move, gBattlerAttacker) - && RandomWeighted(RNG_STATIC, 2, 1)) + if (B_ABILITY_TRIGGER_CHANCE >= GEN_4 ? RandomPercentage(RNG_STATIC, 30) : RandomChance(RNG_STATIC, 1, 3)) { - gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_PARALYSIS; - PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_AbilityStatusEffect; - gHitMarker |= HITMARKER_STATUS_ABILITY_EFFECT; - effect++; + STATIC: + if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) + && IsBattlerAlive(gBattlerAttacker) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && TARGET_TURN_DAMAGED + && CanBeParalyzed(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker)) + && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS + && IsMoveMakingContact(move, gBattlerAttacker)) + { + gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_PARALYSIS; + PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_AbilityStatusEffect; + gHitMarker |= HITMARKER_STATUS_ABILITY_EFFECT; + effect++; + } } break; case ABILITY_FLAME_BODY: @@ -5575,8 +5597,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS && (IsMoveMakingContact(move, gBattlerAttacker)) && TARGET_TURN_DAMAGED - && CanBeBurned(gBattlerAttacker) - && RandomWeighted(RNG_FLAME_BODY, 2, 1)) + && CanBeBurned(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker)) + && (B_ABILITY_TRIGGER_CHANCE >= GEN_4 ? RandomPercentage(RNG_FLAME_BODY, 30) : RandomChance(RNG_FLAME_BODY, 1, 3))) { gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_BURN; PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); @@ -5592,7 +5614,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && !gProtectStructs[gBattlerAttacker].confusionSelfDmg && TARGET_TURN_DAMAGED && IsBattlerAlive(gBattlerTarget) - && RandomWeighted(RNG_CUTE_CHARM, 2, 1) + && (B_ABILITY_TRIGGER_CHANCE >= GEN_4 ? RandomPercentage(RNG_CUTE_CHARM, 30) : RandomChance(RNG_CUTE_CHARM, 1, 3)) && !(gBattleMons[gBattlerAttacker].status2 & STATUS2_INFATUATION) && AreBattlersOfOppositeGender(gBattlerAttacker, gBattlerTarget) && GetBattlerAbility(gBattlerAttacker) != ABILITY_OBLIVIOUS @@ -5792,11 +5814,11 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) && IsBattlerAlive(gBattlerTarget) && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && CanBePoisoned(gBattlerAttacker, gBattlerTarget) + && CanBePoisoned(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerTarget)) && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS && IsMoveMakingContact(move, gBattlerAttacker) && TARGET_TURN_DAMAGED // Need to actually hit the target - && (Random() % 3) == 0) + && RandomPercentage(RNG_POISON_TOUCH, 30)) { gBattleScripting.moveEffect = MOVE_EFFECT_POISON; PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); @@ -5806,11 +5828,27 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 effect++; } break; + case ABILITY_TOXIC_CHAIN: + if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) + && IsBattlerAlive(gBattlerTarget) + && !gProtectStructs[gBattlerAttacker].confusionSelfDmg + && CanBePoisoned(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerTarget)) + && TARGET_TURN_DAMAGED // Need to actually hit the target + && RandomWeighted(RNG_TOXIC_CHAIN, 7, 3)) + { + gBattleScripting.moveEffect = MOVE_EFFECT_TOXIC; + PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_AbilityStatusEffect; + gHitMarker |= HITMARKER_STATUS_ABILITY_EFFECT; + effect++; + } + break; case ABILITY_STENCH: if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) && IsBattlerAlive(gBattlerTarget) && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && RandomWeighted(RNG_STENCH, 9, 1) + && RandomChance(RNG_STENCH, 1, 10) && TARGET_TURN_DAMAGED && !MoveHasAdditionalEffect(gCurrentMove, MOVE_EFFECT_FLINCH)) { @@ -6244,7 +6282,14 @@ u32 GetBattlerAbility(u32 battler) bool32 noAbilityShield = GetBattlerHoldEffectIgnoreAbility(battler, TRUE) != HOLD_EFFECT_ABILITY_SHIELD; if (gAbilitiesInfo[gBattleMons[battler].ability].cantBeSuppressed) + { + // Edge case: pokemon under the effect of gastro acid transforms into a pokemon with Comatose (Todo: verify how other unsuppressable abilities behave) + if (gBattleMons[battler].status2 & STATUS2_TRANSFORMED + && gStatuses3[battler] & STATUS3_GASTRO_ACID + && gBattleMons[battler].ability == ABILITY_COMATOSE) + return ABILITY_NONE; return gBattleMons[battler].ability; + } if (gStatuses3[battler] & STATUS3_GASTRO_ACID) return ABILITY_NONE; @@ -6257,6 +6302,7 @@ u32 GetBattlerAbility(u32 battler) if (((IsMoldBreakerTypeAbility(gBattleMons[gBattlerAttacker].ability) && !(gStatuses3[gBattlerAttacker] & STATUS3_GASTRO_ACID)) || gMovesInfo[gCurrentMove].ignoresTargetAbility) + && battler != gBattlerAttacker && gAbilitiesInfo[gBattleMons[battler].ability].breakable && noAbilityShield && gBattlerByTurnOrder[gCurrentTurnActionNumber] == gBattlerAttacker @@ -6378,83 +6424,78 @@ bool32 IsBattlerTerrainAffected(u32 battler, u32 terrainFlag) return IsBattlerGrounded(battler); } -bool32 CanSleep(u32 battler) +bool32 CanBeSlept(u32 battler, u32 ability) { - u16 ability = GetBattlerAbility(battler); if (ability == ABILITY_INSOMNIA - || ability == ABILITY_VITAL_SPIRIT - || ability == ABILITY_COMATOSE - || ability == ABILITY_PURIFYING_SALT - || gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SAFEGUARD - || gBattleMons[battler].status1 & STATUS1_ANY - || IsAbilityOnSide(battler, ABILITY_SWEET_VEIL) - || IsAbilityStatusProtected(battler) - || IsBattlerTerrainAffected(battler, STATUS_FIELD_ELECTRIC_TERRAIN | STATUS_FIELD_MISTY_TERRAIN)) - return FALSE; - return TRUE; -} - -bool32 CanBePoisoned(u32 battlerAttacker, u32 battlerTarget) -{ - u16 ability = GetBattlerAbility(battlerTarget); - - if (!(CanPoisonType(battlerAttacker, battlerTarget)) - || gSideStatuses[GetBattlerSide(battlerTarget)] & SIDE_STATUS_SAFEGUARD - || gBattleMons[battlerTarget].status1 & STATUS1_ANY - || ability == ABILITY_IMMUNITY + || ability == ABILITY_VITAL_SPIRIT || ability == ABILITY_COMATOSE || ability == ABILITY_PURIFYING_SALT - || IsAbilityOnSide(battlerTarget, ABILITY_PASTEL_VEIL) - || IsAbilityStatusProtected(battlerTarget) - || IsBattlerTerrainAffected(battlerTarget, STATUS_FIELD_MISTY_TERRAIN)) + || gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SAFEGUARD + || gBattleMons[battler].status1 & STATUS1_ANY + || IsAbilityOnSide(battler, ABILITY_SWEET_VEIL) + || IsAbilityStatusProtected(battler) + || IsBattlerTerrainAffected(battler, STATUS_FIELD_ELECTRIC_TERRAIN | STATUS_FIELD_MISTY_TERRAIN)) return FALSE; return TRUE; } -bool32 CanBeBurned(u32 battler) +bool32 CanBePoisoned(u32 battlerAtk, u32 battlerDef, u32 defAbility) +{ + if (!(CanPoisonType(battlerAtk, battlerDef)) + || gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_SAFEGUARD + || gBattleMons[battlerDef].status1 & STATUS1_ANY + || defAbility == ABILITY_IMMUNITY + || defAbility == ABILITY_COMATOSE + || defAbility == ABILITY_PURIFYING_SALT + || IsAbilityOnSide(battlerDef, ABILITY_PASTEL_VEIL) + || IsAbilityStatusProtected(battlerDef) + || IsBattlerTerrainAffected(battlerDef, STATUS_FIELD_MISTY_TERRAIN)) + return FALSE; + return TRUE; +} + +bool32 CanBeBurned(u32 battler, u32 ability) { - u16 ability = GetBattlerAbility(battler); if (IS_BATTLER_OF_TYPE(battler, TYPE_FIRE) + || gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SAFEGUARD + || gBattleMons[battler].status1 & STATUS1_ANY + || ability == ABILITY_WATER_VEIL + || ability == ABILITY_WATER_BUBBLE + || ability == ABILITY_COMATOSE + || ability == ABILITY_THERMAL_EXCHANGE + || ability == ABILITY_PURIFYING_SALT + || IsAbilityStatusProtected(battler) + || IsBattlerTerrainAffected(battler, STATUS_FIELD_MISTY_TERRAIN)) + return FALSE; + return TRUE; +} + +bool32 CanBeParalyzed(u32 battler, u32 ability) +{ + if ((B_PARALYZE_ELECTRIC >= GEN_6 && IS_BATTLER_OF_TYPE(battler, TYPE_ELECTRIC)) || gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SAFEGUARD - || gBattleMons[battler].status1 & STATUS1_ANY - || ability == ABILITY_WATER_VEIL - || ability == ABILITY_WATER_BUBBLE + || ability == ABILITY_LIMBER || ability == ABILITY_COMATOSE - || ability == ABILITY_THERMAL_EXCHANGE || ability == ABILITY_PURIFYING_SALT + || gBattleMons[battler].status1 & STATUS1_ANY || IsAbilityStatusProtected(battler) || IsBattlerTerrainAffected(battler, STATUS_FIELD_MISTY_TERRAIN)) return FALSE; return TRUE; } -bool32 CanBeParalyzed(u32 battler) -{ - u16 ability = GetBattlerAbility(battler); - if ((B_PARALYZE_ELECTRIC >= GEN_6 && IS_BATTLER_OF_TYPE(battler, TYPE_ELECTRIC)) - || gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SAFEGUARD - || ability == ABILITY_LIMBER - || ability == ABILITY_COMATOSE - || ability == ABILITY_PURIFYING_SALT - || gBattleMons[battler].status1 & STATUS1_ANY - || IsAbilityStatusProtected(battler) - || IsBattlerTerrainAffected(battler, STATUS_FIELD_MISTY_TERRAIN)) - return FALSE; - return TRUE; -} - bool32 CanBeFrozen(u32 battler) { u16 ability = GetBattlerAbility(battler); if (IS_BATTLER_OF_TYPE(battler, TYPE_ICE) - || IsBattlerWeatherAffected(battler, B_WEATHER_SUN) - || gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SAFEGUARD - || ability == ABILITY_MAGMA_ARMOR - || ability == ABILITY_COMATOSE - || ability == ABILITY_PURIFYING_SALT - || gBattleMons[battler].status1 & STATUS1_ANY - || IsAbilityStatusProtected(battler) - || IsBattlerTerrainAffected(battler, STATUS_FIELD_MISTY_TERRAIN)) + || IsBattlerWeatherAffected(battler, B_WEATHER_SUN) + || gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SAFEGUARD + || ability == ABILITY_MAGMA_ARMOR + || ability == ABILITY_COMATOSE + || ability == ABILITY_PURIFYING_SALT + || gBattleMons[battler].status1 & STATUS1_ANY + || IsAbilityStatusProtected(battler) + || IsBattlerTerrainAffected(battler, STATUS_FIELD_MISTY_TERRAIN)) return FALSE; return TRUE; } @@ -6477,8 +6518,8 @@ bool32 CanGetFrostbite(u32 battler) bool32 CanBeConfused(u32 battler) { if (GetBattlerAbility(battler) == ABILITY_OWN_TEMPO - || gBattleMons[battler].status2 & STATUS2_CONFUSION - || IsBattlerTerrainAffected(battler, STATUS_FIELD_MISTY_TERRAIN)) + || gBattleMons[battler].status2 & STATUS2_CONFUSION + || IsBattlerTerrainAffected(battler, STATUS_FIELD_MISTY_TERRAIN)) return FALSE; return TRUE; } @@ -6558,7 +6599,7 @@ static u8 StatRaiseBerry(u32 battler, u32 itemId, u32 statId, bool32 end2) else SET_STATCHANGER(statId, 1, FALSE); - gBattleScripting.animArg1 = 14 + statId; + gBattleScripting.animArg1 = STAT_ANIM_PLUS1 + statId; gBattleScripting.animArg2 = 0; if (end2) @@ -6609,7 +6650,7 @@ static u8 RandomStatRaiseBerry(u32 battler, u32 itemId, bool32 end2) else SET_STATCHANGER(i + 1, 2, FALSE); - gBattleScripting.animArg1 = 0x21 + i + 6; + gBattleScripting.animArg1 = STAT_ANIM_PLUS2 + i + 1; gBattleScripting.animArg2 = 0; if (end2) { @@ -6686,7 +6727,7 @@ static u8 DamagedStatBoostBerryEffect(u32 battler, u8 statId, u8 category) SET_STATCHANGER(statId, 1, FALSE); gBattleScripting.battler = battler; - gBattleScripting.animArg1 = 14 + statId; + gBattleScripting.animArg1 = STAT_ANIM_PLUS1 + statId; gBattleScripting.animArg2 = 0; BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_BerryStatRaiseRet; @@ -6703,7 +6744,7 @@ u8 TryHandleSeed(u32 battler, u32 terrainFlag, u8 statId, u16 itemId, bool32 exe gLastUsedItem = itemId; // For surge abilities gEffectBattler = gBattleScripting.battler = battler; SET_STATCHANGER(statId, 1, FALSE); - gBattleScripting.animArg1 = 14 + statId; + gBattleScripting.animArg1 = STAT_ANIM_PLUS1 + statId; gBattleScripting.animArg2 = 0; if (execute) { @@ -7111,7 +7152,7 @@ static u8 ItemEffectMoveEnd(u32 battler, u16 holdEffect) } SET_STATCHANGER(STAT_ATK, 2, FALSE); - gBattleScripting.animArg1 = 14 + STAT_ATK; + gBattleScripting.animArg1 = STAT_ANIM_PLUS1 + STAT_ATK; gBattleScripting.animArg2 = 0; BattleScriptPushCursorAndCallback(BattleScript_BerserkGeneRet); @@ -7393,7 +7434,7 @@ u8 ItemBattleEffects(u8 caseID, u32 battler, bool32 moveTurn) } SET_STATCHANGER(STAT_ATK, 2, FALSE); - gBattleScripting.animArg1 = 14 + STAT_ATK; + gBattleScripting.animArg1 = STAT_ANIM_PLUS1 + STAT_ATK; gBattleScripting.animArg2 = 0; BattleScriptPushCursorAndCallback(BattleScript_BerserkGeneRet); @@ -7657,7 +7698,7 @@ u8 ItemBattleEffects(u8 caseID, u32 battler, bool32 moveTurn) } SET_STATCHANGER(STAT_ATK, 2, FALSE); - gBattleScripting.animArg1 = 14 + STAT_ATK; + gBattleScripting.animArg1 = STAT_ANIM_PLUS1 + STAT_ATK; gBattleScripting.animArg2 = 0; BattleScriptPushCursorAndCallback(BattleScript_BerserkGeneRet); @@ -7955,6 +7996,18 @@ u8 ItemBattleEffects(u8 caseID, u32 battler, bool32 moveTurn) case HOLD_EFFECT_MARANGA_BERRY: // consume and boost sp. defense if used special move effect = DamagedStatBoostBerryEffect(battler, STAT_SPDEF, DAMAGE_CATEGORY_SPECIAL); break; + case HOLD_EFFECT_CURE_STATUS: // only Toxic Chain's interaction with Knock Off + case HOLD_EFFECT_CURE_PSN: + if (gBattleMons[battler].status1 & STATUS1_PSN_ANY && !UnnerveOn(battler, gLastUsedItem) && GetBattlerAbility(gBattlerAttacker) == ABILITY_TOXIC_CHAIN && gMovesInfo[gCurrentMove].effect == EFFECT_KNOCK_OFF) + { + gBattleScripting.battler = battler; + gBattleMons[battler].status1 &= ~(STATUS1_PSN_ANY | STATUS1_TOXIC_COUNTER); + BattleScriptExecute(BattleScript_BerryCurePsnEnd2); + BtlController_EmitSetMonData(battler, 0, REQUEST_STATUS_BATTLE, 0, 4, &gBattleMons[battler].status1); + MarkBattlerForControllerExec(battler); + effect = ITEM_STATUS_CHANGE; + } + break; case HOLD_EFFECT_STICKY_BARB: if (TARGET_TURN_DAMAGED && (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)) @@ -7982,7 +8035,7 @@ u8 ItemBattleEffects(u8 caseID, u32 battler, bool32 moveTurn) switch (battlerHoldEffect) { case HOLD_EFFECT_TOXIC_ORB: - if (CanBePoisoned(battler, battler)) + if (CanBePoisoned(battler, battler, GetBattlerAbility(battler))) { effect = ITEM_STATUS_CHANGE; gBattleMons[battler].status1 = STATUS1_TOXIC_POISON; @@ -7991,7 +8044,7 @@ u8 ItemBattleEffects(u8 caseID, u32 battler, bool32 moveTurn) } break; case HOLD_EFFECT_FLAME_ORB: - if (CanBeBurned(battler)) + if (CanBeBurned(battler, battlerAbility)) { effect = ITEM_STATUS_CHANGE; gBattleMons[battler].status1 = STATUS1_BURN; @@ -8256,7 +8309,7 @@ u8 IsMonDisobedient(void) obedienceLevel = levelReferenced - obedienceLevel; calc = (Random() & 255); - if (calc < obedienceLevel && CanSleep(gBattlerAttacker)) + if (calc < obedienceLevel && CanBeSlept(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker))) { // try putting asleep int i; @@ -8343,7 +8396,7 @@ bool32 IsMoveMakingContact(u32 move, u32 battlerAtk) if (!gMovesInfo[move].makesContact) { - if (move == MOVE_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][gBattlerTarget] == DAMAGE_CATEGORY_SPECIAL) + if (move == MOVE_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][gBattlerTarget] == DAMAGE_CATEGORY_PHYSICAL) return TRUE; else return FALSE; @@ -8371,7 +8424,7 @@ bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move) } // Z-Moves and Max Moves bypass protection (except Max Guard). - if ((IsMaxMove(move) || gBattleStruct->zmove.active) + if ((IsZMove(move) || IsMaxMove(move)) && (!gProtectStructs[battlerDef].maxGuarded || gMovesInfo[move].argument == MAX_EFFECT_BYPASS_PROTECT)) return FALSE; @@ -8383,7 +8436,7 @@ 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 || (move == MOVE_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] == DAMAGE_CATEGORY_SPECIAL)) + && (gMovesInfo[move].makesContact || (move == MOVE_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 (gMovesInfo[move].ignoresProtect) @@ -8668,7 +8721,7 @@ static inline u32 CalcMoveBasePower(u32 move, u32 battlerAtk, u32 battlerDef, u3 u32 basePower = gMovesInfo[move].power; u32 weight, hpFraction, speed; - if (gBattleStruct->zmove.active) + if (GetActiveGimmick(battlerAtk) == GIMMICK_Z_MOVE) return GetZMovePower(gBattleStruct->zmove.baseMoves[battlerAtk]); switch (gMovesInfo[move].effect) @@ -8848,7 +8901,7 @@ static inline u32 CalcMoveBasePower(u32 move, u32 battlerAtk, u32 battlerDef, u3 basePower = uq4_12_multiply(basePower, UQ_4_12(1.5)); break; case EFFECT_DYNAMAX_DOUBLE_DMG: - if (IsDynamaxed(battlerDef)) + if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) basePower *= 2; break; case EFFECT_HIDDEN_POWER: @@ -8903,7 +8956,7 @@ static inline u32 CalcMoveBasePower(u32 move, u32 battlerAtk, u32 battlerDef, u3 basePower *= 2; break; case EFFECT_TERA_BLAST: - if (IsTerastallized(battlerAtk) && GetBattlerTeraType(battlerAtk) == TYPE_STELLAR) + if (GetActiveGimmick(battlerAtk) == GIMMICK_TERA && GetBattlerTeraType(battlerAtk) == TYPE_STELLAR) basePower = 100; break; case EFFECT_LAST_RESPECTS: @@ -8987,12 +9040,13 @@ static inline u32 CalcMoveBasePowerAfterModifiers(u32 move, u32 battlerAtk, u32 modifier = uq4_12_multiply(modifier, (B_TERRAIN_TYPE_BOOST >= GEN_8 ? UQ_4_12(1.3) : UQ_4_12(1.5))); if (IsBattlerTerrainAffected(battlerAtk, STATUS_FIELD_PSYCHIC_TERRAIN) && moveType == TYPE_PSYCHIC) modifier = uq4_12_multiply(modifier, (B_TERRAIN_TYPE_BOOST >= GEN_8 ? UQ_4_12(1.3) : UQ_4_12(1.5))); + if (moveType == TYPE_ELECTRIC && ((gFieldStatuses & STATUS_FIELD_MUDSPORT) || AbilityBattleEffects(ABILITYEFFECT_FIELD_SPORT, 0, 0, ABILITYEFFECT_MUD_SPORT, 0))) - modifier = uq4_12_multiply(modifier, UQ_4_12(B_SPORT_DMG_REDUCTION >= GEN_5 ? 0.23 : 0.5)); + modifier = uq4_12_multiply(modifier, UQ_4_12(B_SPORT_DMG_REDUCTION >= GEN_5 ? 0.33 : 0.5)); if (moveType == TYPE_FIRE && ((gFieldStatuses & STATUS_FIELD_WATERSPORT) || AbilityBattleEffects(ABILITYEFFECT_FIELD_SPORT, 0, 0, ABILITYEFFECT_WATER_SPORT, 0))) - modifier = uq4_12_multiply(modifier, UQ_4_12(B_SPORT_DMG_REDUCTION >= GEN_5 ? 0.23 : 0.5)); + modifier = uq4_12_multiply(modifier, UQ_4_12(B_SPORT_DMG_REDUCTION >= GEN_5 ? 0.33 : 0.5)); // attacker's abilities switch (atkAbility) @@ -9275,7 +9329,7 @@ static inline u32 CalcMoveBasePowerAfterModifiers(u32 move, u32 battlerAtk, u32 } // Terastallization boosts weak, non-priority, non-multi hit moves after modifiers to 60 BP. - if (IsTerastallized(battlerAtk) + if (GetActiveGimmick(battlerAtk) == GIMMICK_TERA && (moveType == GetBattlerTeraType(battlerAtk) || (GetBattlerTeraType(battlerAtk) == TYPE_STELLAR && IsTypeStellarBoosted(battlerAtk, moveType))) && uq4_12_multiply_by_int_half_down(modifier, basePower) < 60 @@ -9316,7 +9370,11 @@ static inline u32 CalcAttackStat(u32 move, u32 battlerAtk, u32 battlerDef, u32 m if (IS_MOVE_PHYSICAL(move)) { atkStat = gBattleMons[battlerAtk].defense; - atkStage = gBattleMons[battlerAtk].statStages[STAT_DEF]; + // Edge case: Body Press used during Wonder Room. For some reason, it still uses Defense over Sp.Def, but uses Sp.Def stat changes + if (gFieldStatuses & STATUS_FIELD_WONDER_ROOM) + atkStage = gBattleMons[battlerAtk].statStages[STAT_SPDEF]; + else + atkStage = gBattleMons[battlerAtk].statStages[STAT_DEF]; } else { @@ -9475,11 +9533,11 @@ static inline u32 CalcAttackStat(u32 move, u32 battlerAtk, u32 battlerDef, u32 m modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0)); break; case HOLD_EFFECT_CHOICE_BAND: - if (IS_MOVE_PHYSICAL(move) && !IsDynamaxed(battlerAtk)) + if (IS_MOVE_PHYSICAL(move) && GetActiveGimmick(battlerAtk) != GIMMICK_DYNAMAX) modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; case HOLD_EFFECT_CHOICE_SPECS: - if (IS_MOVE_SPECIAL(move) && !IsDynamaxed(battlerAtk)) + if (IS_MOVE_SPECIAL(move) && GetActiveGimmick(battlerAtk) != GIMMICK_DYNAMAX) modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; } @@ -9747,7 +9805,7 @@ static inline uq4_12_t GetGlaiveRushModifier(u32 battlerDef) static inline uq4_12_t GetZMaxMoveAgainstProtectionModifier(u32 battlerDef, u32 move) { - if ((gBattleStruct->zmove.active || IsMaxMove(move)) && IS_BATTLER_PROTECTED(battlerDef)) + if ((IsZMove(move) || IsMaxMove(move)) && IS_BATTLER_PROTECTED(battlerDef)) return UQ_4_12(0.25); return UQ_4_12(1.0); } @@ -9988,7 +10046,7 @@ static inline s32 DoMoveDamageCalcVars(u32 move, u32 battlerAtk, u32 battlerDef, dmg /= 100; } - if (IsTerastallized(battlerAtk)) + if (GetActiveGimmick(battlerAtk) == GIMMICK_TERA) DAMAGE_APPLY_MODIFIER(GetTeraMultiplier(battlerAtk, moveType)); else DAMAGE_APPLY_MODIFIER(GetSameTypeAttackBonusModifier(battlerAtk, moveType, move, abilityAtk)); @@ -10146,7 +10204,7 @@ static inline void MulByTypeEffectiveness(uq4_12_t *modifier, u32 move, u32 move mod = UQ_4_12(2.0); if (moveType == TYPE_GROUND && defType == TYPE_FLYING && IsBattlerGrounded(battlerDef) && mod == UQ_4_12(0.0)) mod = UQ_4_12(1.0); - if (moveType == TYPE_STELLAR && IsTerastallized(battlerDef)) + if (moveType == TYPE_STELLAR && GetActiveGimmick(battlerDef) == GIMMICK_TERA) mod = UQ_4_12(2.0); // B_WEATHER_STRONG_WINDS weakens Super Effective moves against Flying-type Pokémon @@ -10242,7 +10300,7 @@ static inline uq4_12_t CalcTypeEffectivenessMultiplierInternal(u32 move, u32 mov // Thousand Arrows ignores type modifiers for flying mons if (!IsBattlerGrounded(battlerDef) && (gMovesInfo[move].ignoreTypeIfFlyingAndUngrounded) - && (gBattleMons[battlerDef].type1 == TYPE_FLYING || gBattleMons[battlerDef].type2 == TYPE_FLYING || gBattleMons[battlerDef].type3 == TYPE_FLYING)) + && (GetBattlerType(battlerDef, 0, FALSE) == TYPE_FLYING || GetBattlerType(battlerDef, 1, FALSE) == TYPE_FLYING || GetBattlerType(battlerDef, 2, FALSE) == TYPE_FLYING)) { modifier = UQ_4_12(1.0); } @@ -10451,62 +10509,37 @@ bool32 DoesSpeciesUseHoldItemToChangeForm(u16 species, u16 heldItemId) bool32 CanMegaEvolve(u32 battler) { - u32 itemId, holdEffect; - struct Pokemon *mon; - u32 battlerPosition = GetBattlerPosition(battler); - u8 partnerPosition = GetBattlerPosition(BATTLE_PARTNER(battler)); - struct MegaEvolutionData *mega = &(((struct ChooseMoveStruct *)(&gBattleResources->bufferA[battler][4]))->mega); + u32 holdEffect = GetBattlerHoldEffect(battler, FALSE); - // Check if Player has a Mega Ring - if ((GetBattlerPosition(battler) == B_POSITION_PLAYER_LEFT || (!(gBattleTypeFlags & BATTLE_TYPE_MULTI) && GetBattlerPosition(battler) == B_POSITION_PLAYER_RIGHT)) - && !CheckBagHasItem(ITEM_MEGA_RING, 1)) + // Check if Player has a Mega Ring. + if (!TESTING + && (GetBattlerPosition(battler) == B_POSITION_PLAYER_LEFT || (!(gBattleTypeFlags & BATTLE_TYPE_MULTI) && GetBattlerPosition(battler) == B_POSITION_PLAYER_RIGHT)) + && !CheckBagHasItem(ITEM_MEGA_RING, 1)) return FALSE; - // Check if trainer already mega evolved a pokemon. - if (mega->alreadyEvolved[battlerPosition]) + // Check if Trainer has already Mega Evolved. + if (HasTrainerUsedGimmick(battler, GIMMICK_MEGA)) return FALSE; - // Cannot use z move and mega evolve on same turn - if (gBattleStruct->zmove.toBeUsed[battler]) + // Check if battler has another gimmick active. + if (GetActiveGimmick(battler) != GIMMICK_NONE) return FALSE; - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE - && IsPartnerMonFromSameTrainer(battler) - && (mega->alreadyEvolved[partnerPosition] || (mega->toEvolve & gBitTable[BATTLE_PARTNER(battler)]))) - return FALSE; - - // Check if mon is currently held by Sky Drop + // Check if battler is currently held by Sky Drop. if (gStatuses3[battler] & STATUS3_SKY_DROPPED) return FALSE; - // Gets mon data. - if (GetBattlerSide(battler) == B_SIDE_OPPONENT) - mon = &gEnemyParty[gBattlerPartyIndexes[battler]]; - else - mon = &gPlayerParty[gBattlerPartyIndexes[battler]]; + // Check if battler is holding a Z-Crystal. + if (holdEffect == HOLD_EFFECT_Z_CRYSTAL) + return FALSE; - itemId = GetMonData(mon, MON_DATA_HELD_ITEM); + // Check if there is an entry in the form change table for regular Mega Evolution and battler is holding Mega Stone. + if (GetBattleFormChangeTargetSpecies(battler, FORM_CHANGE_BATTLE_MEGA_EVOLUTION_ITEM) != SPECIES_NONE && holdEffect == HOLD_EFFECT_MEGA_STONE) + return TRUE; - if (itemId == ITEM_ENIGMA_BERRY_E_READER) - holdEffect = gEnigmaBerries[battler].holdEffect; - else - holdEffect = ItemId_GetHoldEffect(itemId); - - // Check if there is an entry in the evolution table for regular Mega Evolution. - if (GetBattleFormChangeTargetSpecies(battler, FORM_CHANGE_BATTLE_MEGA_EVOLUTION_ITEM) != SPECIES_NONE) - { - // Can Mega Evolve via Mega Stone. - if (holdEffect == HOLD_EFFECT_MEGA_STONE) - return TRUE; - } - - // Check if there is an entry in the evolution table for Wish Mega Evolution. + // Check if there is an entry in the form change table for Wish Mega Evolution. if (GetBattleFormChangeTargetSpecies(battler, FORM_CHANGE_BATTLE_MEGA_EVOLUTION_MOVE) != SPECIES_NONE) - { - // Can't Wish Mega Evolve if holding a Z Crystal. - if (holdEffect != HOLD_EFFECT_Z_CRYSTAL) - return TRUE; - } + return TRUE; // No checks passed, the mon CAN'T mega evolve. return FALSE; @@ -10514,58 +10547,51 @@ bool32 CanMegaEvolve(u32 battler) bool32 CanUltraBurst(u32 battler) { - u32 itemId, holdEffect; - struct Pokemon *mon; - u32 battlerPosition = GetBattlerPosition(battler); - u8 partnerPosition = GetBattlerPosition(BATTLE_PARTNER(battler)); + u32 holdEffect = GetBattlerHoldEffect(battler, FALSE); - // Check if Player has a Z Ring - if ((GetBattlerPosition(battler) == B_POSITION_PLAYER_LEFT || (!(gBattleTypeFlags & BATTLE_TYPE_MULTI) && GetBattlerPosition(battler) == B_POSITION_PLAYER_RIGHT)) - && !CheckBagHasItem(ITEM_Z_POWER_RING, 1)) + // Check if Player has a Z-Ring + if (!TESTING && (GetBattlerPosition(battler) == B_POSITION_PLAYER_LEFT + || (!(gBattleTypeFlags & BATTLE_TYPE_MULTI) && GetBattlerPosition(battler) == B_POSITION_PLAYER_RIGHT)) + && !CheckBagHasItem(ITEM_Z_POWER_RING, 1)) return FALSE; - // Check if trainer already ultra bursted a pokemon. - if (gBattleStruct->burst.alreadyBursted[battlerPosition]) + // Check if Trainer has already Ultra Bursted. + if (HasTrainerUsedGimmick(battler, GIMMICK_ULTRA_BURST)) return FALSE; - // Cannot use z move and ultra burst on same turn - if (gBattleStruct->zmove.toBeUsed[battler]) - return FALSE; - - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE - && IsPartnerMonFromSameTrainer(battler) - && (gBattleStruct->burst.alreadyBursted[partnerPosition] || (gBattleStruct->burst.toBurst & gBitTable[BATTLE_PARTNER(battler)]))) + // Check if battler has another gimmick active. + if (GetActiveGimmick(battler) != GIMMICK_NONE) return FALSE; // Check if mon is currently held by Sky Drop if (gStatuses3[battler] & STATUS3_SKY_DROPPED) return FALSE; - // Gets mon data. - if (GetBattlerSide(battler) == B_SIDE_OPPONENT) - mon = &gEnemyParty[gBattlerPartyIndexes[battler]]; - else - mon = &gPlayerParty[gBattlerPartyIndexes[battler]]; - - itemId = GetMonData(mon, MON_DATA_HELD_ITEM); - - // Check if there is an entry in the evolution table for Ultra Burst. - if (GetBattleFormChangeTargetSpecies(battler, FORM_CHANGE_BATTLE_ULTRA_BURST) != SPECIES_NONE) - { - if (itemId == ITEM_ENIGMA_BERRY_E_READER) - holdEffect = gEnigmaBerries[battler].holdEffect; - else - holdEffect = ItemId_GetHoldEffect(itemId); - - // Can Ultra Burst via Z Crystal. - if (holdEffect == HOLD_EFFECT_Z_CRYSTAL) - return TRUE; - } + // Check if there is an entry in the form change table for Ultra Burst and battler is holding a Z-Crystal. + if (GetBattleFormChangeTargetSpecies(battler, FORM_CHANGE_BATTLE_ULTRA_BURST) != SPECIES_NONE && holdEffect == HOLD_EFFECT_Z_CRYSTAL) + return TRUE; // No checks passed, the mon CAN'T ultra burst. return FALSE; } +void ActivateMegaEvolution(u32 battler) +{ + gLastUsedItem = gBattleMons[battler].item; + SetActiveGimmick(battler, GIMMICK_MEGA); + if (GetBattleFormChangeTargetSpecies(battler, FORM_CHANGE_BATTLE_MEGA_EVOLUTION_MOVE) != SPECIES_NONE) + BattleScriptExecute(BattleScript_WishMegaEvolution); + else + BattleScriptExecute(BattleScript_MegaEvolution); +} + +void ActivateUltraBurst(u32 battler) +{ + gLastUsedItem = gBattleMons[battler].item; + SetActiveGimmick(battler, GIMMICK_ULTRA_BURST); + BattleScriptExecute(BattleScript_UltraBurst); +} + bool32 IsBattlerMegaEvolved(u32 battler) { // While Transform does copy stats and visuals, it shouldn't be counted as true Mega Evolution. @@ -10717,12 +10743,12 @@ bool32 CanBattlerFormChange(u32 battler, u16 method) return DoesSpeciesHaveFormChangeMethod(gBattleMons[battler].species, method); } -bool32 TryBattleFormChange(u32 battler, u16 method) +bool32 TryBattleFormChange(u32 battler, u32 method) { - u8 monId = gBattlerPartyIndexes[battler]; - u8 side = GetBattlerSide(battler); + u32 monId = gBattlerPartyIndexes[battler]; + u32 side = GetBattlerSide(battler); struct Pokemon *party = GetBattlerParty(battler); - u16 targetSpecies; + u32 targetSpecies; if (!CanBattlerFormChange(battler, method)) return FALSE; @@ -10760,10 +10786,14 @@ bool32 TryBattleFormChange(u32 battler, u16 method) if (restoreSpecies) { + u32 abilityForm = gBattleMons[battler].ability; // Reverts the original species TryToSetBattleFormChangeMoves(&party[monId], method); SetMonData(&party[monId], MON_DATA_SPECIES, &gBattleStruct->changedSpecies[side][monId]); RecalcBattlerStats(battler, &party[monId]); + // Battler data is not updated with regular form's ability, not doing so could cause wrong ability activation. + if (method == FORM_CHANGE_FAINT) + gBattleMons[battler].ability = abilityForm; return TRUE; } } @@ -10902,12 +10932,10 @@ bool32 ShouldGetStatBadgeBoost(u16 badgeFlag, u32 battler) u8 GetBattleMoveCategory(u32 moveId) { - if (gBattleStruct != NULL && gBattleStruct->zmove.active && !IS_MOVE_STATUS(moveId)) - return gBattleStruct->zmove.activeCategory; - if (gBattleStruct != NULL && IsMaxMove(moveId)) // TODO: Might be buggy depending on when this is called. - return gBattleStruct->dynamax.activeCategory; - if (gBattleStruct != NULL && gBattleStruct->swapDamageCategory) // Photon Geyser, Shell Side Arm, Light That Burns the Sky + if (gBattleStruct != NULL && gBattleStruct->swapDamageCategory) // Photon Geyser, Shell Side Arm, Light That Burns the Sky, Tera Blast return DAMAGE_CATEGORY_PHYSICAL; + if (gBattleStruct != NULL && (IsZMove(moveId) || IsMaxMove(moveId))) // TODO: Might be buggy depending on when this is called. + return gBattleStruct->categoryOverride; if (B_PHYSICAL_SPECIAL_SPLIT >= GEN_4) return gMovesInfo[moveId].category; @@ -11049,9 +11077,9 @@ void TryRestoreHeldItems(void) for (i = 0; i < PARTY_SIZE; i++) { // Check if held items should be restored after battle based on generation - if (B_RESTORE_HELD_BATTLE_ITEMS >= GEN_9 || gBattleStruct->itemLost[i].stolen || returnNPCItems) + if (B_RESTORE_HELD_BATTLE_ITEMS >= GEN_9 || gBattleStruct->itemLost[B_SIDE_PLAYER][i].stolen || returnNPCItems) { - u16 lostItem = gBattleStruct->itemLost[i].originalItem; + u16 lostItem = gBattleStruct->itemLost[B_SIDE_PLAYER][i].originalItem; // Check if the lost item is a berry and the mon is not holding it if (ItemId_GetPocket(lostItem) == POCKET_BERRIES && GetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM) != lostItem) @@ -11112,8 +11140,8 @@ void TrySaveExchangedItem(u32 battler, u16 stolenItem) if (gBattleTypeFlags & BATTLE_TYPE_TRAINER && !(gBattleTypeFlags & BATTLE_TYPE_FRONTIER) && GetBattlerSide(battler) == B_SIDE_PLAYER - && stolenItem == gBattleStruct->itemLost[gBattlerPartyIndexes[battler]].originalItem) - gBattleStruct->itemLost[gBattlerPartyIndexes[battler]].stolen = TRUE; + && stolenItem == gBattleStruct->itemLost[B_SIDE_PLAYER][gBattlerPartyIndexes[battler]].originalItem) + gBattleStruct->itemLost[B_SIDE_PLAYER][gBattlerPartyIndexes[battler]].stolen = TRUE; } bool32 IsBattlerAffectedByHazards(u32 battler, bool32 toxicSpikes) @@ -11222,7 +11250,7 @@ bool32 TryRoomService(u32 battler) BufferStatChange(battler, STAT_SPEED, STRINGID_STATFELL); gEffectBattler = gBattleScripting.battler = battler; SET_STATCHANGER(STAT_SPEED, 1, TRUE); - gBattleScripting.animArg1 = 14 + STAT_SPEED; + gBattleScripting.animArg1 = STAT_ANIM_PLUS1 + STAT_SPEED; gBattleScripting.animArg2 = 0; gLastUsedItem = gBattleMons[battler].item; return TRUE; @@ -11292,7 +11320,7 @@ bool32 CanTargetBattler(u32 battlerAtk, u32 battlerDef, u16 move) && GetBattlerSide(battlerAtk) == GetBattlerSide(battlerDef) && gStatuses3[battlerAtk] & STATUS3_HEAL_BLOCK) return FALSE; // Pokémon affected by Heal Block cannot target allies with Pollen Puff - if ((IsDynamaxed(battlerAtk) || gBattleStruct->dynamax.playerSelect) + if ((GetActiveGimmick(battlerAtk) == GIMMICK_DYNAMAX || IsGimmickSelected(battlerAtk, GIMMICK_DYNAMAX)) && GetBattlerSide(battlerAtk) == GetBattlerSide(battlerDef)) return FALSE; return TRUE; @@ -11331,7 +11359,7 @@ void CopyMonAbilityAndTypesToBattleMon(u32 battler, struct Pokemon *mon) void RecalcBattlerStats(u32 battler, struct Pokemon *mon) { CalculateMonStats(mon); - if (IsDynamaxed(battler) && gChosenActionByBattler[battler] != B_ACTION_SWITCH) + if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX && gChosenActionByBattler[battler] != B_ACTION_SWITCH) ApplyDynamaxHPMultiplier(battler, mon); CopyMonLevelAndBaseStatsToBattleMon(battler, mon); CopyMonAbilityAndTypesToBattleMon(battler, mon); @@ -11535,14 +11563,14 @@ u8 GetBattlerType(u32 battler, u8 typeIndex, bool32 ignoreTera) types[2] = gBattleMons[battler].type3; // Handle Terastallization - if (IsTerastallized(battler) && teraType != TYPE_STELLAR && !ignoreTera) + if (GetActiveGimmick(battler) == GIMMICK_TERA && teraType != TYPE_STELLAR && !ignoreTera) return GetBattlerTeraType(battler); // Handle Roost's Flying-type suppression if (typeIndex == 0 || typeIndex == 1) { if (gBattleResources->flags->flags[battler] & RESOURCE_FLAG_ROOST - && !IsTerastallized(battler)) + && GetActiveGimmick(battler) != GIMMICK_TERA) { if (types[0] == TYPE_FLYING && types[1] == TYPE_FLYING) return B_ROOST_PURE_FLYING >= GEN_5 ? TYPE_NORMAL : TYPE_MYSTERY; @@ -11557,7 +11585,7 @@ u8 GetBattlerType(u32 battler, u8 typeIndex, bool32 ignoreTera) void RemoveBattlerType(u32 battler, u8 type) { u32 i; - if (IsTerastallized(battler)) // don't remove type if Terastallized + if (GetActiveGimmick(battler) == GIMMICK_TERA) // don't remove type if Terastallized return; for (i = 0; i < 3; i++) { @@ -11608,7 +11636,9 @@ void SetShellSideArmCategory(void) special = ((((2 * gBattleMons[battlerAtk].level / 5 + 2) * gMovesInfo[MOVE_SHELL_SIDE_ARM].power * attackerSpAtkStat) / targetSpDefStat) / 50); - if (((physical > special) || (physical == special && RandomPercentage(RNG_SHELL_SIDE_ARM, 50)))) + if ((physical > special) || (physical == special && RandomPercentage(RNG_SHELL_SIDE_ARM, 50))) + gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] = DAMAGE_CATEGORY_PHYSICAL; + else gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] = DAMAGE_CATEGORY_SPECIAL; } } diff --git a/src/battle_z_move.c b/src/battle_z_move.c index 203185cfdc..c000c35808 100644 --- a/src/battle_z_move.c +++ b/src/battle_z_move.c @@ -45,12 +45,8 @@ #define STAT_STAGE(battler, stat) (gBattleMons[battler].statStages[stat - 1]) // Function Declarations -static void SpriteCB_ZMoveTrigger(struct Sprite *sprite); -static u16 GetSignatureZMove(u16 move, u16 species, u16 item); static void ZMoveSelectionDisplayPpNumber(u32 battler); static void ZMoveSelectionDisplayPower(u16 move, u16 zMove); -static void ShowZMoveTriggerSprite(u8 battleId); -static bool32 AreStatsMaxed(u8 battler, u8 n); static void ZMoveSelectionDisplayMoveType(u16 zMove, u32 battler); // Const Data @@ -108,260 +104,130 @@ static const u8 sText_RecoverHP[] = _("Recover HP"); static const u8 sText_HealAllyHP[] = _("Heal Replacement HP"); static const u8 sText_PowerColon[] = _("Power: "); -static const u32 sZMoveTriggerGfx[] = INCBIN_U32("graphics/battle_interface/z_move_trigger.4bpp.lz"); -static const u16 sZMoveTriggerPal[] = INCBIN_U16("graphics/battle_interface/z_move_trigger.gbapal"); - -static const struct CompressedSpriteSheet sSpriteSheet_ZMoveTrigger = { - sZMoveTriggerGfx, (32 * 32) / 2, TAG_ZMOVE_TRIGGER_TILE -}; - -static const struct SpritePalette sSpritePalette_ZMoveTrigger = { - sZMoveTriggerPal, TAG_ZMOVE_TRIGGER_PAL -}; - -static const struct OamData sOamData_ZMoveTrigger = -{ - .affineMode = ST_OAM_AFFINE_OFF, - .objMode = ST_OAM_OBJ_NORMAL, - .shape = SPRITE_SHAPE(32x32), - .size = SPRITE_SIZE(32x32), - .priority = 1, -}; - -static const struct SpriteTemplate sSpriteTemplate_ZMoveTrigger = -{ - .tileTag = TAG_ZMOVE_TRIGGER_TILE, - .paletteTag = TAG_ZMOVE_TRIGGER_PAL, - .oam = &sOamData_ZMoveTrigger, - .anims = gDummySpriteAnimTable, - .images = NULL, - .affineAnims = gDummySpriteAffineAnimTable, - .callback = SpriteCB_ZMoveTrigger -}; - // Functions -bool8 IsZMove(u16 move) +bool32 IsZMove(u32 move) { return move >= FIRST_Z_MOVE && move <= LAST_Z_MOVE; } -void QueueZMove(u8 battler, u16 baseMove) +bool32 CanUseZMove(u32 battler) { - gBattleStruct->zmove.toBeUsed[battler] = gBattleStruct->zmove.chosenZMove; - gBattleStruct->zmove.baseMoves[battler] = baseMove; - if (gBattleStruct->zmove.chosenZMove == MOVE_LIGHT_THAT_BURNS_THE_SKY) - gBattleStruct->zmove.categories[battler] = GetCategoryBasedOnStats(battler); - else - gBattleStruct->zmove.categories[battler] = gMovesInfo[baseMove].category; + u32 holdEffect = GetBattlerHoldEffect(battler, FALSE); + + // Check if Player has Z-Power Ring. + if (!TESTING && (battler == B_POSITION_PLAYER_LEFT + || (!(gBattleTypeFlags & BATTLE_TYPE_MULTI) && battler == B_POSITION_PLAYER_RIGHT)) + && !CheckBagHasItem(ITEM_Z_POWER_RING, 1)) + return FALSE; + + // Add '| BATTLE_TYPE_FRONTIER' to below if issues occur + if (gBattleTypeFlags & (BATTLE_TYPE_SAFARI | BATTLE_TYPE_WALLY_TUTORIAL)) + return FALSE; + + // Check if Trainer has already used a Z-Move. + if (HasTrainerUsedGimmick(battler, GIMMICK_Z_MOVE)) + return FALSE; + + // Check if battler has another gimmick active. + if (GetActiveGimmick(battler) != GIMMICK_NONE && GetActiveGimmick(battler) != GIMMICK_ULTRA_BURST) + return FALSE; + + // Check if battler isn't holding a Z-Crystal. + if (holdEffect != HOLD_EFFECT_Z_CRYSTAL) + return FALSE; + + // All checks passed! + return TRUE; } -bool32 IsViableZMove(u8 battler, u16 move) +u32 GetUsableZMove(u32 battler, u32 move) +{ + u32 item = gBattleMons[battler].item; + u32 holdEffect = GetBattlerHoldEffect(battler, FALSE); + + if (holdEffect == HOLD_EFFECT_Z_CRYSTAL) + { + u16 zMove = GetSignatureZMove(move, gBattleMons[battler].species, item); + if (zMove != MOVE_NONE) + return zMove; // Signature z move exists + + if (move != MOVE_NONE && zMove != MOVE_Z_STATUS && gMovesInfo[move].type == ItemId_GetSecondaryId(item)) + return GetTypeBasedZMove(move); + } + + return MOVE_NONE; +} + +void ActivateZMove(u32 battler) +{ + gBattleStruct->zmove.baseMoves[battler] = gBattleMons[battler].moves[gBattleStruct->chosenMovePositions[battler]]; + SetActiveGimmick(battler, GIMMICK_Z_MOVE); +} + +bool32 IsViableZMove(u32 battler, u32 move) { u32 item; - u16 holdEffect; + u32 holdEffect = GetBattlerHoldEffect(battler, FALSE); int moveSlotIndex; item = gBattleMons[battler].item; + if (gBattleStruct->gimmick.usableGimmick[battler] != GIMMICK_Z_MOVE) + return FALSE; + for (moveSlotIndex = 0; moveSlotIndex < MAX_MON_MOVES; moveSlotIndex++) { if (gBattleMons[battler].moves[moveSlotIndex] == move && gBattleMons[battler].pp[moveSlotIndex] == 0) return FALSE; } - if (gBattleStruct->zmove.used[battler]) + // Check if Player has Z-Power Ring. + if ((battler == B_POSITION_PLAYER_LEFT || (!(gBattleTypeFlags & BATTLE_TYPE_MULTI) && battler == B_POSITION_PLAYER_RIGHT)) + && !CheckBagHasItem(ITEM_Z_POWER_RING, 1)) + { return FALSE; + } - // Add '| BATTLE_TYPE_FRONTIER' to below if issues occur - if (gBattleTypeFlags & (BATTLE_TYPE_SAFARI | BATTLE_TYPE_WALLY_TUTORIAL)) - return FALSE; - - if ((GetBattlerPosition(battler) == B_POSITION_PLAYER_LEFT || (!(gBattleTypeFlags & BATTLE_TYPE_MULTI) && GetBattlerPosition(battler) == B_POSITION_PLAYER_RIGHT)) && !CheckBagHasItem(ITEM_Z_POWER_RING, 1)) - return FALSE; - - if (item == ITEM_ENIGMA_BERRY_E_READER) - return FALSE; // HoldEffect = gEnigmaBerries[battler].holdEffect; - else - holdEffect = ItemId_GetHoldEffect(item); - + // Check for signature Z-Move or type-based Z-Move. if (holdEffect == HOLD_EFFECT_Z_CRYSTAL) { u16 zMove = GetSignatureZMove(move, gBattleMons[battler].species, item); if (zMove != MOVE_NONE) - { - gBattleStruct->zmove.chosenZMove = zMove; // Signature z move exists return TRUE; - } - if (move != MOVE_NONE && zMove != MOVE_Z_STATUS && gMovesInfo[move].type == ItemId_GetSecondaryId(item)) - { - gBattleStruct->zmove.chosenZMove = GetTypeBasedZMove(move, battler); + if (move != MOVE_NONE && gMovesInfo[move].type == ItemId_GetSecondaryId(item)) return TRUE; - } } return FALSE; } -void GetUsableZMoves(u8 battler, u16 *moves) +void AssignUsableZMoves(u32 battler, u16 *moves) { u32 i; gBattleStruct->zmove.possibleZMoves[battler] = 0; for (i = 0; i < MAX_MON_MOVES; i++) { if (moves[i] != MOVE_NONE && IsViableZMove(battler, moves[i])) - gBattleStruct->zmove.possibleZMoves[battler] |= (1 << i); + gBattleStruct->zmove.possibleZMoves[battler] |= gBitTable[i]; } } -bool32 IsZMoveUsable(u8 battler, u16 moveIndex) +bool32 TryChangeZTrigger(u32 battler, u32 moveIndex) { - if ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE) && IsPartnerMonFromSameTrainer(battler) && gBattleStruct->zmove.toBeUsed[BATTLE_PARTNER(battler)] != MOVE_NONE) - return FALSE; // Player's other mon has a z move queued up already - if (gBattleStruct->zmove.possibleZMoves[battler] & (1 << moveIndex)) - return TRUE; - return FALSE; -} - -bool32 TryChangeZIndicator(u8 battler, u8 moveIndex) -{ - bool32 viableZMove = IsZMoveUsable(battler, moveIndex); + bool32 viableZMove = (gBattleStruct->zmove.possibleZMoves[battler] & gBitTable[moveIndex]) != 0; if (gBattleStruct->zmove.viable && !viableZMove) - HideZMoveTriggerSprite(); // Was a viable z move, now is not -> slide out + HideGimmickTriggerSprite(); // Was a viable z move, now is not -> slide out else if (!gBattleStruct->zmove.viable && viableZMove) - ShowZMoveTriggerSprite(battler); // Was not a viable z move, now is -> slide back in + CreateGimmickTriggerSprite(battler); // Was not a viable z move, now is -> slide back in + + gBattleStruct->zmove.viable = viableZMove; return viableZMove; } -#define SINGLES_Z_TRIGGER_POS_X_OPTIMAL (29) -#define SINGLES_Z_TRIGGER_POS_X_PRIORITY (29) -#define SINGLES_Z_TRIGGER_POS_X_SLIDE (15) -#define SINGLES_Z_TRIGGER_POS_Y_DIFF (-10) - -#define DOUBLES_Z_TRIGGER_POS_X_OPTIMAL SINGLES_Z_TRIGGER_POS_X_OPTIMAL -#define DOUBLES_Z_TRIGGER_POS_X_PRIORITY SINGLES_Z_TRIGGER_POS_X_PRIORITY -#define DOUBLES_Z_TRIGGER_POS_X_SLIDE SINGLES_Z_TRIGGER_POS_X_SLIDE -#define DOUBLES_Z_TRIGGER_POS_Y_DIFF (-4) - -#define tBattler data[0] -#define tHide data[1] - -void CreateZMoveTriggerSprite(u8 battler, bool8 viable) -{ - s16 x, y; - - LoadSpritePalette(&sSpritePalette_ZMoveTrigger); - if (GetSpriteTileStartByTag(TAG_ZMOVE_TRIGGER_TILE) == 0xFFFF) - LoadCompressedSpriteSheetUsingHeap(&sSpriteSheet_ZMoveTrigger); - - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) - { - x = gSprites[gHealthboxSpriteIds[battler]].x - DOUBLES_Z_TRIGGER_POS_X_SLIDE; - y = gSprites[gHealthboxSpriteIds[battler]].y - DOUBLES_Z_TRIGGER_POS_Y_DIFF; - } - else - { - x = gSprites[gHealthboxSpriteIds[battler]].x - SINGLES_Z_TRIGGER_POS_X_SLIDE; - y = gSprites[gHealthboxSpriteIds[battler]].y - SINGLES_Z_TRIGGER_POS_Y_DIFF; - } - - if (gBattleStruct->zmove.triggerSpriteId == 0xFF) - gBattleStruct->zmove.triggerSpriteId = CreateSprite(&sSpriteTemplate_ZMoveTrigger, x, y, 0); - - gSprites[gBattleStruct->zmove.triggerSpriteId].tBattler = battler; - gSprites[gBattleStruct->zmove.triggerSpriteId].tHide = (viable == TRUE) ? FALSE : TRUE; -} - -static void SpriteCB_ZMoveTrigger(struct Sprite *sprite) -{ - s32 xSlide, xPriority, xOptimal; - s32 yDiff; - - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) - { - xSlide = DOUBLES_Z_TRIGGER_POS_X_SLIDE; - xPriority = DOUBLES_Z_TRIGGER_POS_X_PRIORITY; - xOptimal = DOUBLES_Z_TRIGGER_POS_X_OPTIMAL; - yDiff = DOUBLES_Z_TRIGGER_POS_Y_DIFF; - } - else - { - xSlide = SINGLES_Z_TRIGGER_POS_X_SLIDE; - xPriority = SINGLES_Z_TRIGGER_POS_X_PRIORITY; - xOptimal = SINGLES_Z_TRIGGER_POS_X_OPTIMAL; - yDiff = SINGLES_Z_TRIGGER_POS_Y_DIFF; - } - - if (sprite->tHide) - { - if (sprite->x != gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xSlide) - sprite->x++; - - if (sprite->x >= gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xPriority) - sprite->oam.priority = 2; - else - sprite->oam.priority = 1; - - sprite->y = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y - yDiff; - sprite->y2 = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y2 - yDiff; - if (sprite->x == gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xSlide) - DestroyZMoveTriggerSprite(); - } - else - { - if (sprite->x != gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xOptimal) - { - sprite->x--; - sprite->oam.priority = 2; - } - else - { - sprite->oam.priority = 1; - } - sprite->y = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y - yDiff; - sprite->y2 = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y2 - yDiff; - } -} - -bool32 IsZMoveTriggerSpriteActive(void) -{ - if (GetSpriteTileStartByTag(TAG_ZMOVE_TRIGGER_TILE) == 0xFFFF) - return FALSE; - else if (IndexOfSpritePaletteTag(TAG_ZMOVE_TRIGGER_PAL) != 0xFF) - return TRUE; - else - return FALSE; -} - -void HideZMoveTriggerSprite(void) -{ - struct Sprite *sprite; - gBattleStruct->zmove.viable = FALSE; - if (gBattleStruct->zmove.triggerSpriteId >= MAX_SPRITES) - return; - sprite = &gSprites[gBattleStruct->zmove.triggerSpriteId]; - sprite->tHide = TRUE; -} - -static void ShowZMoveTriggerSprite(u8 battler) -{ - gBattleStruct->zmove.viable = TRUE; - CreateZMoveTriggerSprite(battler, TRUE); -} - -void DestroyZMoveTriggerSprite(void) -{ - FreeSpritePaletteByTag(TAG_ZMOVE_TRIGGER_PAL); - FreeSpriteTilesByTag(TAG_ZMOVE_TRIGGER_TILE); - if (gBattleStruct->zmove.triggerSpriteId != 0xFF) - DestroySprite(&gSprites[gBattleStruct->zmove.triggerSpriteId]); - - gBattleStruct->zmove.triggerSpriteId = 0xFF; -} - -static u16 GetSignatureZMove(u16 move, u16 species, u16 item) +u32 GetSignatureZMove(u32 move, u32 species, u32 item) { u32 i; @@ -375,13 +241,17 @@ static u16 GetSignatureZMove(u16 move, u16 species, u16 item) return MOVE_NONE; } -u16 GetTypeBasedZMove(u16 move, u8 battler) +u32 GetTypeBasedZMove(u32 move) { - u8 moveType = gMovesInfo[move].type; + u32 moveType = gMovesInfo[move].type; if (moveType >= NUMBER_OF_MON_TYPES) moveType = TYPE_MYSTERY; + // Z-Weather Ball changes types, however Revelation Dance, -ate ability affected moves, and Hidden Power do not + if (gBattleStruct->dynamicMoveType && gMovesInfo[move].effect == EFFECT_WEATHER_BALL) + moveType = gBattleStruct->dynamicMoveType & DYNAMIC_TYPE_MASK; + // Get Z-Move from type if (gTypesInfo[moveType].zMove == MOVE_NONE) // failsafe return gTypesInfo[0].zMove; @@ -544,7 +414,7 @@ static void ZMoveSelectionDisplayPpNumber(u32 battler) static void ZMoveSelectionDisplayMoveType(u16 zMove, u32 battler) { - u8 *txtPtr; + u8 *txtPtr, *end; u8 zMoveType; GET_MOVE_TYPE(zMove, zMoveType); @@ -554,7 +424,8 @@ static void ZMoveSelectionDisplayMoveType(u16 zMove, u32 battler) *(txtPtr)++ = EXT_CTRL_CODE_FONT; *(txtPtr)++ = FONT_NORMAL; - StringCopy(txtPtr, gTypesInfo[zMoveType].name); + end = StringCopy(txtPtr, gTypesInfo[zMoveType].name); + PrependFontIdToFit(txtPtr, end, FONT_NORMAL, WindowWidthPx(B_WIN_MOVE_TYPE) - 25); BattlePutTextOnWindow(gDisplayedStringBattle, B_WIN_MOVE_TYPE); } @@ -564,20 +435,20 @@ static void ZMoveSelectionDisplayMoveType(u16 zMove, u32 battler) void SetZEffect(void) { u32 i; + u32 effect = gMovesInfo[gBattleStruct->zmove.baseMoves[gBattlerAttacker]].zMove.effect; - gBattleStruct->zmove.zStatusActive = TRUE; - if (gBattleStruct->zmove.effect == Z_EFFECT_CURSE) + if (effect == Z_EFFECT_CURSE) { if (IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_GHOST)) - gBattleStruct->zmove.effect = Z_EFFECT_RECOVER_HP; + effect = Z_EFFECT_RECOVER_HP; else - gBattleStruct->zmove.effect = Z_EFFECT_ATK_UP_1; + effect = Z_EFFECT_ATK_UP_1; } gBattleScripting.savedStatChanger = gBattleScripting.statChanger; // Save used move's stat changer (e.g. for Z-Growl) gBattleScripting.battler = gBattlerAttacker; - switch (gBattleStruct->zmove.effect) + switch (effect) { case Z_EFFECT_RESET_STATS: for (i = 0; i < NUM_BATTLE_STATS - 1; i++) @@ -590,22 +461,27 @@ void SetZEffect(void) gBattlescriptCurrInstr = BattleScript_ZEffectPrintString; break; case Z_EFFECT_ALL_STATS_UP_1: - if (!AreStatsMaxed(gBattlerAttacker, STAT_SPDEF)) + { + bool32 canBoost = FALSE; + for (i = STAT_ATK; i < NUM_STATS; i++) // Doesn't increase Acc or Evsn { - for (i = 0; i < STAT_ACC - 1; i++) // Doesn't increase Acc or Evsn + if (STAT_STAGE(gBattlerAttacker, i) < 12) { - if (gBattleMons[gBattlerAttacker].statStages[i] < 12) - ++gBattleMons[gBattlerAttacker].statStages[i]; + canBoost = TRUE; } + } + if (canBoost) + { gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_Z_ALL_STATS_UP; BattleScriptPush(gBattlescriptCurrInstr + Z_EFFECT_BS_LENGTH); - gBattlescriptCurrInstr = BattleScript_ZEffectPrintString; + gBattlescriptCurrInstr = BattleScript_AllStatsUpZMove; } else { gBattlescriptCurrInstr += Z_EFFECT_BS_LENGTH; } break; + } case Z_EFFECT_BOOST_CRITS: if (!(gBattleMons[gBattlerAttacker].status2 & STATUS2_FOCUS_ENERGY_ANY)) { @@ -646,17 +522,17 @@ void SetZEffect(void) gBattlescriptCurrInstr = BattleScript_ZEffectPrintString; break; case Z_EFFECT_ATK_UP_1 ... Z_EFFECT_EVSN_UP_1: - SET_STATCHANGER(gBattleStruct->zmove.effect - Z_EFFECT_ATK_UP_1 + 1, 1, FALSE); + SET_STATCHANGER(effect - Z_EFFECT_ATK_UP_1 + 1, 1, FALSE); BattleScriptPush(gBattlescriptCurrInstr + Z_EFFECT_BS_LENGTH); gBattlescriptCurrInstr = BattleScript_StatUpZMove; break; case Z_EFFECT_ATK_UP_2 ... Z_EFFECT_EVSN_UP_2: - SET_STATCHANGER(gBattleStruct->zmove.effect - Z_EFFECT_ATK_UP_2 + 1, 2, FALSE); + SET_STATCHANGER(effect - Z_EFFECT_ATK_UP_2 + 1, 2, FALSE); BattleScriptPush(gBattlescriptCurrInstr + Z_EFFECT_BS_LENGTH); gBattlescriptCurrInstr = BattleScript_StatUpZMove; break; case Z_EFFECT_ATK_UP_3 ... Z_EFFECT_EVSN_UP_3: - SET_STATCHANGER(gBattleStruct->zmove.effect - Z_EFFECT_ATK_UP_3 + 1, 3, FALSE); + SET_STATCHANGER(effect - Z_EFFECT_ATK_UP_3 + 1, 3, FALSE); BattleScriptPush(gBattlescriptCurrInstr + Z_EFFECT_BS_LENGTH); gBattlescriptCurrInstr = BattleScript_StatUpZMove; break; @@ -664,22 +540,9 @@ void SetZEffect(void) gBattlescriptCurrInstr += Z_EFFECT_BS_LENGTH; break; } - - gBattleStruct->zmove.zStatusActive = FALSE; } -static bool32 AreStatsMaxed(u8 battler, u8 n) -{ - u32 i; - for (i = STAT_ATK; i <= n; i++) - { - if (STAT_STAGE(battler, i) < MAX_STAT_STAGE) - return FALSE; - } - return TRUE; -} - -u16 GetZMovePower(u16 move) +u32 GetZMovePower(u32 move) { if (gMovesInfo[move].category == DAMAGE_CATEGORY_STATUS) return 0; diff --git a/src/clock.c b/src/clock.c index 825ccc4448..7061f96b7f 100644 --- a/src/clock.c +++ b/src/clock.c @@ -54,6 +54,7 @@ static void UpdatePerDay(struct Time *localTime) UpdateFrontierGambler(daysSince); SetShoalItemFlag(daysSince); SetRandomLotteryNumber(daysSince); + UpdateDaysPassedSinceFormChange(daysSince); *days = localTime->days; } } diff --git a/src/data/abilities.h b/src/data/abilities.h index 8b81057b4f..8fc7d699e1 100644 --- a/src/data/abilities.h +++ b/src/data/abilities.h @@ -2548,7 +2548,7 @@ const struct Ability gAbilitiesInfo[ABILITIES_COUNT] = #else .name = _("SuprswtSyrup"), #endif - .description = COMPOUND_STRING("Lowers the foe's Speed."), + .description = COMPOUND_STRING("Lowers the foe's Evasion."), .aiRating = 5, }, diff --git a/src/data/battle_move_effects.h b/src/data/battle_move_effects.h index 0b23c30817..01f717b47e 100644 --- a/src/data/battle_move_effects.h +++ b/src/data/battle_move_effects.h @@ -2243,4 +2243,11 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] = .battleScript = BattleScript_EffectHit, .battleTvScore = 0, // TODO: Assign points }, + + [EFFECT_GUARDIAN_OF_ALOLA] = + { + .battleScript = BattleScript_DamageToQuarterTargetHP, + .battleTvScore = 0, // TODO: Assign points + }, + }; diff --git a/src/data/gimmicks.h b/src/data/gimmicks.h new file mode 100644 index 0000000000..95a233e3fe --- /dev/null +++ b/src/data/gimmicks.h @@ -0,0 +1,54 @@ +#include "graphics/gimmicks.h" + +// Gimmick data + +const struct GimmickInfo gGimmicksInfo[GIMMICKS_COUNT] = +{ + [GIMMICK_NONE] = {0}, + [GIMMICK_MEGA] = + { + .triggerSheet = &sSpriteSheet_MegaTrigger, + .triggerPal = &sSpritePalette_MegaTrigger, + .triggerTemplate = &sSpriteTemplate_GimmickTrigger, + .indicatorSheet = &sSpriteSheet_MegaIndicator, + .indicatorPal = &sSpritePalette_MegaIndicator, + .CanActivate = CanMegaEvolve, + .ActivateGimmick = ActivateMegaEvolution, + }, + [GIMMICK_Z_MOVE] = + { + .triggerSheet = &sSpriteSheet_ZMoveTrigger, + .triggerPal = &sSpritePalette_ZMoveTrigger, + .triggerTemplate = &sSpriteTemplate_GimmickTrigger, + .CanActivate = CanUseZMove, + .ActivateGimmick = ActivateZMove, + }, + [GIMMICK_ULTRA_BURST] = + { + .triggerSheet = &sSpriteSheet_BurstTrigger, + .triggerPal = &sSpritePalette_BurstTrigger, + .triggerTemplate = &sSpriteTemplate_GimmickTrigger, + .CanActivate = CanUltraBurst, + .ActivateGimmick = ActivateUltraBurst, + }, + [GIMMICK_DYNAMAX] = + { + .triggerSheet = &sSpriteSheet_DynamaxTrigger, + .triggerPal = &sSpritePalette_DynamaxTrigger, + .triggerTemplate = &sSpriteTemplate_GimmickTrigger, + .indicatorSheet = &sSpriteSheet_DynamaxIndicator, + .indicatorPal = &sSpritePalette_MiscIndicator, + .CanActivate = CanDynamax, + .ActivateGimmick = ActivateDynamax, + }, + [GIMMICK_TERA] = + { + .triggerSheet = &sSpriteSheet_TeraTrigger, + .triggerPal = &sSpritePalette_TeraTrigger, + .triggerTemplate = &sSpriteTemplate_GimmickTrigger, + .indicatorSheet = NULL, // handled separately + .indicatorPal = &sSpritePalette_TeraIndicator, + .CanActivate = CanTerastallize, + .ActivateGimmick = ActivateTera, + } +}; diff --git a/src/data/graphics/gimmicks.h b/src/data/graphics/gimmicks.h new file mode 100644 index 0000000000..08c1ffe4c5 --- /dev/null +++ b/src/data/graphics/gimmicks.h @@ -0,0 +1,152 @@ +// trigger data +static const u8 ALIGNED(4) sMegaTriggerGfx[] = INCBIN_U8("graphics/battle_interface/mega_trigger.4bpp"); +static const u8 ALIGNED(4) sZMoveTriggerGfx[] = INCBIN_U8("graphics/battle_interface/z_move_trigger.4bpp"); +static const u8 ALIGNED(4) sBurstTriggerGfx[] = INCBIN_U8("graphics/battle_interface/burst_trigger.4bpp"); +static const u8 ALIGNED(4) sDynamaxTriggerGfx[] = INCBIN_U8("graphics/battle_interface/dynamax_trigger.4bpp"); +static const u8 ALIGNED(4) sTeraTriggerGfx[] = INCBIN_U8("graphics/battle_interface/tera_trigger.4bpp"); + +static const u16 sMegaTriggerPal[] = INCBIN_U16("graphics/battle_interface/mega_trigger.gbapal"); +static const u16 sZMoveTriggerPal[] = INCBIN_U16("graphics/battle_interface/z_move_trigger.gbapal"); +static const u16 sBurstTriggerPal[] = INCBIN_U16("graphics/battle_interface/burst_trigger.gbapal"); +static const u16 sDynamaxTriggerPal[] = INCBIN_U16("graphics/battle_interface/dynamax_trigger.gbapal"); +static const u16 sTeraTriggerPal[] = INCBIN_U16("graphics/battle_interface/tera_trigger.gbapal"); + +static const struct SpriteSheet sSpriteSheet_MegaTrigger = {sMegaTriggerGfx, sizeof(sMegaTriggerGfx), TAG_GIMMICK_TRIGGER_TILE}; +static const struct SpriteSheet sSpriteSheet_ZMoveTrigger = {sZMoveTriggerGfx, sizeof(sZMoveTriggerGfx), TAG_GIMMICK_TRIGGER_TILE}; +static const struct SpriteSheet sSpriteSheet_BurstTrigger = {sBurstTriggerGfx, sizeof(sBurstTriggerGfx), TAG_GIMMICK_TRIGGER_TILE}; +static const struct SpriteSheet sSpriteSheet_DynamaxTrigger = {sDynamaxTriggerGfx, sizeof(sDynamaxTriggerGfx), TAG_GIMMICK_TRIGGER_TILE}; +static const struct SpriteSheet sSpriteSheet_TeraTrigger = {sTeraTriggerGfx, sizeof(sTeraTriggerGfx), TAG_GIMMICK_TRIGGER_TILE}; + +static const struct SpritePalette sSpritePalette_MegaTrigger = {sMegaTriggerPal, TAG_GIMMICK_TRIGGER_TILE}; +static const struct SpritePalette sSpritePalette_ZMoveTrigger = {sZMoveTriggerPal, TAG_GIMMICK_TRIGGER_PAL}; +static const struct SpritePalette sSpritePalette_BurstTrigger = {sBurstTriggerPal, TAG_GIMMICK_TRIGGER_TILE}; +static const struct SpritePalette sSpritePalette_DynamaxTrigger = {sDynamaxTriggerPal, TAG_GIMMICK_TRIGGER_PAL}; +static const struct SpritePalette sSpritePalette_TeraTrigger = {sTeraTriggerPal, TAG_GIMMICK_TRIGGER_TILE}; + +static const struct OamData sOamData_GimmickTrigger = +{ + .y = 0, + .affineMode = 0, + .objMode = 0, + .mosaic = 0, + .bpp = 0, + .shape = ST_OAM_SQUARE, + .x = 0, + .matrixNum = 0, + .size = 2, + .tileNum = 0, + .priority = 1, + .paletteNum = 0, + .affineParam = 0, +}; + +static const union AnimCmd sSpriteAnim_GimmickTriggerOff[] = +{ + ANIMCMD_FRAME(0, 0), + ANIMCMD_END +}; + +static const union AnimCmd sSpriteAnim_GimmickTriggerOn[] = +{ + ANIMCMD_FRAME(16, 0), + ANIMCMD_END +}; + +static const union AnimCmd *const sSpriteAnimTable_GimmickTrigger[] = +{ + sSpriteAnim_GimmickTriggerOff, + sSpriteAnim_GimmickTriggerOn, +}; + +static void SpriteCb_GimmickTrigger(struct Sprite *sprite); +static const struct SpriteTemplate sSpriteTemplate_GimmickTrigger = +{ + .tileTag = TAG_GIMMICK_TRIGGER_TILE, + .paletteTag = TAG_GIMMICK_TRIGGER_PAL, + .oam = &sOamData_GimmickTrigger, + .anims = sSpriteAnimTable_GimmickTrigger, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_GimmickTrigger, +}; + +// indicator data +static const u8 ALIGNED(4) sMegaIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/mega_indicator.4bpp"); +static const u8 ALIGNED(4) sAlphaIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/alpha_indicator.4bpp"); +static const u8 ALIGNED(4) sOmegaIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/omega_indicator.4bpp"); +static const u8 ALIGNED(4) sDynamaxIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/dynamax_indicator.4bpp"); +static const u8 ALIGNED(4) sNormalIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/normal_indicator.4bpp"); +static const u8 ALIGNED(4) sFightingIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/fighting_indicator.4bpp"); +static const u8 ALIGNED(4) sFlyingIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/flying_indicator.4bpp"); +static const u8 ALIGNED(4) sPoisonIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/poison_indicator.4bpp"); +static const u8 ALIGNED(4) sGroundIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/ground_indicator.4bpp"); +static const u8 ALIGNED(4) sRockIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/rock_indicator.4bpp"); +static const u8 ALIGNED(4) sBugIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/bug_indicator.4bpp"); +static const u8 ALIGNED(4) sGhostIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/ghost_indicator.4bpp"); +static const u8 ALIGNED(4) sSteelIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/steel_indicator.4bpp"); +static const u8 ALIGNED(4) sFireIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/fire_indicator.4bpp"); +static const u8 ALIGNED(4) sWaterIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/water_indicator.4bpp"); +static const u8 ALIGNED(4) sGrassIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/grass_indicator.4bpp"); +static const u8 ALIGNED(4) sElectricIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/electric_indicator.4bpp"); +static const u8 ALIGNED(4) sPsychicIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/psychic_indicator.4bpp"); +static const u8 ALIGNED(4) sIceIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/ice_indicator.4bpp"); +static const u8 ALIGNED(4) sDragonIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/dragon_indicator.4bpp"); +static const u8 ALIGNED(4) sDarkIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/dark_indicator.4bpp"); +static const u8 ALIGNED(4) sFairyIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/fairy_indicator.4bpp"); +static const u8 ALIGNED(4) sStellarIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/stellar_indicator.4bpp"); + +static const u16 sMiscIndicatorPal[] = INCBIN_U16("graphics/battle_interface/misc_indicator.gbapal"); // has room for more colors +static const u16 sMegaIndicatorPal[] = INCBIN_U16("graphics/battle_interface/mega_indicator.gbapal"); +static const u16 sTeraIndicatorPal[] = INCBIN_U16("graphics/battle_interface/tera_indicator.gbapal"); + +static const struct SpriteSheet sSpriteSheet_MegaIndicator = {sMegaIndicatorGfx, sizeof(sMegaIndicatorGfx), TAG_MEGA_INDICATOR_TILE}; +static const struct SpriteSheet sSpriteSheet_AlphaIndicator = {sAlphaIndicatorGfx, sizeof(sAlphaIndicatorGfx), TAG_ALPHA_INDICATOR_TILE}; +static const struct SpriteSheet sSpriteSheet_OmegaIndicator = {sOmegaIndicatorGfx, sizeof(sOmegaIndicatorGfx), TAG_OMEGA_INDICATOR_TILE}; +static const struct SpriteSheet sSpriteSheet_DynamaxIndicator = {sDynamaxIndicatorGfx, sizeof(sDynamaxIndicatorGfx), TAG_DYNAMAX_INDICATOR_TILE}; +static const struct SpriteSheet sTeraIndicatorSpriteSheets[NUMBER_OF_MON_TYPES + 1] = +{ + {sNormalIndicatorGfx, sizeof(sNormalIndicatorGfx), TAG_NORMAL_INDICATOR_TILE}, + {sFightingIndicatorGfx, sizeof(sFightingIndicatorGfx), TAG_FIGHTING_INDICATOR_TILE}, + {sFlyingIndicatorGfx, sizeof(sFlyingIndicatorGfx), TAG_FLYING_INDICATOR_TILE}, + {sPoisonIndicatorGfx, sizeof(sPoisonIndicatorGfx), TAG_POISON_INDICATOR_TILE}, + {sGroundIndicatorGfx, sizeof(sGroundIndicatorGfx), TAG_GROUND_INDICATOR_TILE}, + {sRockIndicatorGfx, sizeof(sRockIndicatorGfx), TAG_ROCK_INDICATOR_TILE}, + {sBugIndicatorGfx, sizeof(sBugIndicatorGfx), TAG_BUG_INDICATOR_TILE}, + {sGhostIndicatorGfx, sizeof(sGhostIndicatorGfx), TAG_GHOST_INDICATOR_TILE}, + {sSteelIndicatorGfx, sizeof(sSteelIndicatorGfx), TAG_STEEL_INDICATOR_TILE}, + {sNormalIndicatorGfx, sizeof(sNormalIndicatorGfx), TAG_NORMAL_INDICATOR_TILE}, // TYPE_MYSTERY + {sFireIndicatorGfx, sizeof(sFireIndicatorGfx), TAG_FIRE_INDICATOR_TILE}, + {sWaterIndicatorGfx, sizeof(sWaterIndicatorGfx), TAG_WATER_INDICATOR_TILE}, + {sGrassIndicatorGfx, sizeof(sGrassIndicatorGfx), TAG_GRASS_INDICATOR_TILE}, + {sElectricIndicatorGfx, sizeof(sElectricIndicatorGfx), TAG_ELECTRIC_INDICATOR_TILE}, + {sPsychicIndicatorGfx, sizeof(sPsychicIndicatorGfx), TAG_PSYCHIC_INDICATOR_TILE}, + {sIceIndicatorGfx, sizeof(sIceIndicatorGfx), TAG_ICE_INDICATOR_TILE}, + {sDragonIndicatorGfx, sizeof(sDragonIndicatorGfx), TAG_DRAGON_INDICATOR_TILE}, + {sDarkIndicatorGfx, sizeof(sDarkIndicatorGfx), TAG_DARK_INDICATOR_TILE}, + {sFairyIndicatorGfx, sizeof(sFairyIndicatorGfx), TAG_FAIRY_INDICATOR_TILE}, + {sStellarIndicatorGfx, sizeof(sStellarIndicatorGfx), TAG_STELLAR_INDICATOR_TILE}, + {0} +}; + +static const struct SpritePalette sSpritePalette_MiscIndicator = {sMiscIndicatorPal, TAG_MISC_INDICATOR_PAL}; +static const struct SpritePalette sSpritePalette_MegaIndicator = {sMegaIndicatorPal, TAG_MEGA_INDICATOR_PAL}; +static const struct SpritePalette sSpritePalette_TeraIndicator = {sTeraIndicatorPal, TAG_TERA_INDICATOR_PAL}; + +static const struct OamData sOamData_GimmickIndicator = +{ + .shape = SPRITE_SHAPE(16x16), + .size = SPRITE_SIZE(16x16), + .priority = 1, +}; + +static void SpriteCb_GimmickIndicator(struct Sprite *sprite); +static const struct SpriteTemplate sSpriteTemplate_GimmickIndicator = +{ + .tileTag = TAG_NORMAL_INDICATOR_TILE, // updated dynamically + .paletteTag = TAG_TERA_INDICATOR_PAL, // updated dynamically + .oam = &sOamData_GimmickIndicator, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_GimmickIndicator, +}; diff --git a/src/data/moves_info.h b/src/data/moves_info.h index f627e01396..4e27d9b397 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -15646,7 +15646,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = [MOVE_FLORAL_HEALING] = { - .name = HANDLE_EXPANDED_MOVE_NAME("FloralHealng", "Floral Healng"), + .name = HANDLE_EXPANDED_MOVE_NAME("FloralHealng", "Floral Healing"), .description = COMPOUND_STRING( "Restores an ally's HP.\n" "Heals more on grass."), @@ -18422,7 +18422,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = [MOVE_JUNGLE_HEALING] = { - .name = HANDLE_EXPANDED_MOVE_NAME("JungleHealng", "Jungle Healng"), + .name = HANDLE_EXPANDED_MOVE_NAME("JungleHealng", "Jungle Healing"), .description = COMPOUND_STRING( "Heals HP and status of\n" "itself and allies in battle."), @@ -21042,7 +21042,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = }, [MOVE_OCEANIC_OPERETTA] = { - .name = COMPOUND_STRING("Oceaning Operetta"), + .name = COMPOUND_STRING("Oceanic Operetta"), .description = sNullDescription, .effect = EFFECT_HIT, .power = 195, @@ -21106,9 +21106,9 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = }, [MOVE_GUARDIAN_OF_ALOLA] = { - .name = COMPOUND_STRING("Guardian Of Alola"), + .name = COMPOUND_STRING("Guardian of Alola"), .description = sNullDescription, - .effect = EFFECT_SUPER_FANG, + .effect = EFFECT_GUARDIAN_OF_ALOLA, .power = 1, .type = TYPE_FAIRY, .accuracy = 0, @@ -21152,7 +21152,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = { .name = COMPOUND_STRING("Light That Burns The Sky"), .description = sNullDescription, - .effect = EFFECT_HIT, + .effect = EFFECT_PHOTON_GEYSER, .power = 200, .type = TYPE_PSYCHIC, .accuracy = 0, diff --git a/src/data/pokemon/form_change_tables.h b/src/data/pokemon/form_change_tables.h index 78de99f140..52f888a55b 100644 --- a/src/data/pokemon/form_change_tables.h +++ b/src/data/pokemon/form_change_tables.h @@ -779,6 +779,14 @@ static const struct FormChange sGreninjaBattleBondFormChangeTable[] = { }; #endif //P_FAMILY_FROAKIE +#if P_FAMILY_FURFROU +static const struct FormChange sFurfrouFormChangeTable[] = { + {FORM_CHANGE_WITHDRAW, SPECIES_FURFROU_NATURAL}, + {FORM_CHANGE_DAYS_PASSED, SPECIES_FURFROU_NATURAL, 5}, + {FORM_CHANGE_TERMINATOR}, +}; +#endif //P_FAMILY_FURFROU + #if P_FAMILY_HONEDGE static const struct FormChange sAegislashFormChangeTable[] = { {FORM_CHANGE_BATTLE_SWITCH, SPECIES_AEGISLASH_SHIELD}, @@ -841,8 +849,9 @@ static const struct FormChange sDiancieFormChangeTable[] = { #if P_FAMILY_HOOPA static const struct FormChange sHoopaFormChangeTable[] = { - {FORM_CHANGE_ITEM_USE, SPECIES_HOOPA_UNBOUND, ITEM_PRISON_BOTTLE, SPECIES_HOOPA_CONFINED}, - {FORM_CHANGE_WITHDRAW, SPECIES_HOOPA_CONFINED}, + {FORM_CHANGE_ITEM_USE, SPECIES_HOOPA_UNBOUND, ITEM_PRISON_BOTTLE, SPECIES_HOOPA_CONFINED}, + {FORM_CHANGE_WITHDRAW, SPECIES_HOOPA_CONFINED}, + {FORM_CHANGE_DAYS_PASSED, SPECIES_HOOPA_CONFINED, 3}, {FORM_CHANGE_TERMINATOR}, }; #endif //P_FAMILY_HOOPA diff --git a/src/data/pokemon/species_info.h b/src/data/pokemon/species_info.h index eae4929a9d..9427ad2bc6 100644 --- a/src/data/pokemon/species_info.h +++ b/src/data/pokemon/species_info.h @@ -76,11 +76,7 @@ const struct SpeciesInfo gSpeciesInfo[] = .categoryName = _("Unknown"), .height = 0, .weight = 0, - .description = COMPOUND_STRING( - "This is a newly discovered Pokémon.\n" - "It is currently under investigation.\n" - "No detailed information is available\n" - "at this time."), + .description = gFallbackPokedexText, .pokemonScale = 256, .pokemonOffset = 0, .trainerScale = 256, diff --git a/src/data/pokemon/species_info/gen_6_families.h b/src/data/pokemon/species_info/gen_6_families.h index 09ad2a01d0..46c776639c 100644 --- a/src/data/pokemon/species_info/gen_6_families.h +++ b/src/data/pokemon/species_info/gen_6_families.h @@ -2156,6 +2156,7 @@ const struct SpeciesInfo gSpeciesInfoGen6[] = .teachableLearnset = sFurfrouTeachableLearnset, \ .eggMoveLearnset = sFurfrouEggMoveLearnset, \ .formSpeciesIdTable = sFurfrouFormSpeciesIdTable, \ + .formChangeTable = sFurfrouFormChangeTable, \ } [SPECIES_FURFROU_NATURAL] = FURFROU_MISC_INFO(Natural, FALSE, 48, 3, 56, 0, 0), diff --git a/src/data/pokemon/species_info/gen_7_families.h b/src/data/pokemon/species_info/gen_7_families.h index feb4fe968b..4669051d04 100644 --- a/src/data/pokemon/species_info/gen_7_families.h +++ b/src/data/pokemon/species_info/gen_7_families.h @@ -4143,6 +4143,7 @@ const struct SpeciesInfo gSpeciesInfoGen7[] = .baseSpAttack = 60, \ .baseSpDefense = 100, \ .weight = 400, \ + .description = gMiniorMeteorPokedexText, \ .frontPic = gMonFrontPic_MiniorMeteor, \ .frontPicSize = MON_COORDS_SIZE(48, 40), \ .frontPicYOffset = 14, \ diff --git a/src/data/pokemon/species_info/shared_dex_text.h b/src/data/pokemon/species_info/shared_dex_text.h index dbfef82d74..0c591bd811 100644 --- a/src/data/pokemon/species_info/shared_dex_text.h +++ b/src/data/pokemon/species_info/shared_dex_text.h @@ -1,3 +1,10 @@ +// fallback +const u8 gFallbackPokedexText[] = _( + "This is a newly discovered Pokémon.\n" + "It is currently under investigation.\n" + "No detailed information is available\n" + "at this time."); + // Gen 1 families const u8 gRaticateAlolanPokedexText[] = _( "It forms a group of Rattata, which it \n" diff --git a/src/daycare.c b/src/daycare.c index ec2217cce9..3082e5a9e8 100644 --- a/src/daycare.c +++ b/src/daycare.c @@ -1039,12 +1039,14 @@ static u16 DetermineEggSpeciesAndParentSlots(struct DayCare *daycare, u8 *parent eggSpecies = SPECIES_ILLUMISE; else if (eggSpecies == SPECIES_MANAPHY) eggSpecies = SPECIES_PHIONE; - else if (eggSpecies == SPECIES_SINISTEA_ANTIQUE) - eggSpecies = SPECIES_SINISTEA_PHONY; else if (GET_BASE_SPECIES_ID(eggSpecies) == SPECIES_ROTOM) eggSpecies = SPECIES_ROTOM; else if (GET_BASE_SPECIES_ID(eggSpecies) == SPECIES_FURFROU) eggSpecies = SPECIES_FURFROU; + else if (eggSpecies == SPECIES_SINISTEA_ANTIQUE) + eggSpecies = SPECIES_SINISTEA_PHONY; + else if (eggSpecies == SPECIES_POLTCHAGEIST_ARTISAN) + eggSpecies = SPECIES_POLTCHAGEIST_COUNTERFEIT; // To avoid single-stage Totem Pokémon to breed more of themselves. else if (eggSpecies == SPECIES_MIMIKYU_TOTEM_DISGUISED) eggSpecies = SPECIES_MIMIKYU_DISGUISED; diff --git a/src/field_effect.c b/src/field_effect.c index 27c17187ca..e7d22aa05c 100644 --- a/src/field_effect.c +++ b/src/field_effect.c @@ -20,6 +20,7 @@ #include "palette.h" #include "party_menu.h" #include "pokemon.h" +#include "pokemon_storage_system.h" #include "script.h" #include "sound.h" #include "sprite.h" @@ -1015,10 +1016,10 @@ void MultiplyPaletteRGBComponents(u16 i, u8 r, u8 g, u8 b) bool8 FldEff_PokecenterHeal(void) { - u8 nPokemon; + u32 nPokemon; struct Task *task; - nPokemon = CalculatePlayerPartyCount(); + nPokemon = (OW_IGNORE_EGGS_ON_HEAL <= GEN_3) ? CalculatePlayerPartyCount() : CountPartyNonEggMons(); task = &gTasks[CreateTask(Task_PokecenterHeal, 0xff)]; task->tNumMons = nPokemon; task->tFirstBallX = 93; diff --git a/src/item.c b/src/item.c index 205719fb53..6c3742fd76 100644 --- a/src/item.c +++ b/src/item.c @@ -164,6 +164,18 @@ bool8 HasAtLeastOneBerry(void) return FALSE; } +bool8 HasAtLeastOnePokeBall(void) +{ + u16 i; + + for (i = FIRST_BALL; i <= LAST_BALL; i++) + { + if (CheckBagHasItem(i, 1) == TRUE) + return TRUE; + } + return FALSE; +} + bool8 CheckBagHasSpace(u16 itemId, u16 count) { if (ItemId_GetPocket(itemId) == POCKET_NONE) diff --git a/src/libisagbprn.c b/src/libisagbprn.c index fab9b5990b..6724059ff8 100644 --- a/src/libisagbprn.c +++ b/src/libisagbprn.c @@ -1,7 +1,7 @@ #include #include #include "gba/gba.h" -#include "config.h" +#include "config/general.h" #include "malloc.h" #include "mini_printf.h" diff --git a/src/map_name_popup.c b/src/map_name_popup.c index 4da57a26ee..67a0d6373f 100644 --- a/src/map_name_popup.c +++ b/src/map_name_popup.c @@ -20,8 +20,8 @@ #include "constants/layouts.h" #include "constants/region_map_sections.h" #include "constants/weather.h" +#include "config/general.h" #include "config/overworld.h" -#include "config.h" // enums enum MapPopUp_Themes diff --git a/src/mini_printf.c b/src/mini_printf.c index cab78d7611..7432395b79 100644 --- a/src/mini_printf.c +++ b/src/mini_printf.c @@ -35,7 +35,7 @@ #include "mini_printf.h" #include "gba/types.h" #include "gba/defines.h" -#include "config.h" +#include "config/general.h" #include "characters.h" #include "string_util.h" diff --git a/src/party_menu.c b/src/party_menu.c index 1d76ebe5d5..1c163104bb 100644 --- a/src/party_menu.c +++ b/src/party_menu.c @@ -6361,6 +6361,7 @@ static void Task_TryItemUseFormChange(u8 taskId) case 0: targetSpecies = gTasks[taskId].tTargetSpecies; SetMonData(mon, MON_DATA_SPECIES, &targetSpecies); + TrySetDayLimitToFormChange(mon); CalculateMonStats(mon); gTasks[taskId].tState++; break; diff --git a/src/pokedex.c b/src/pokedex.c index 8b70529a93..2cf8274db5 100644 --- a/src/pokedex.c +++ b/src/pokedex.c @@ -1387,7 +1387,7 @@ static const struct SearchOptionText sDexSearchColorOptions[] = {}, }; -static const struct SearchOptionText sDexSearchTypeOptions[NUMBER_OF_MON_TYPES] = // + 2 for "None" and terminator, - 2 for Mystery and Stellar +static const struct SearchOptionText sDexSearchTypeOptions[] = { {gText_DexEmptyString, gTypesInfo[TYPE_NONE].name}, {gText_DexEmptyString, gTypesInfo[TYPE_NORMAL].name}, diff --git a/src/pokedex_plus_hgss.c b/src/pokedex_plus_hgss.c index e3728132db..3788ea4c44 100644 --- a/src/pokedex_plus_hgss.c +++ b/src/pokedex_plus_hgss.c @@ -1901,7 +1901,7 @@ static const struct SearchOptionText sDexSearchColorOptions[] = {}, }; -static const struct SearchOptionText sDexSearchTypeOptions[NUMBER_OF_MON_TYPES] = // + 2 for "None" and terminator, - 2 for Mystery and Stellar +static const struct SearchOptionText sDexSearchTypeOptions[] = { {gText_DexEmptyString, gTypesInfo[TYPE_NONE].name}, {gText_DexEmptyString, gTypesInfo[TYPE_NORMAL].name}, diff --git a/src/pokemon.c b/src/pokemon.c index 0f17d1d53c..7a9efbafd6 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -1148,6 +1148,14 @@ void CreateBoxMon(struct BoxPokemon *boxMon, u16 species, u8 level, u8 fixedIV, { isShiny = DexNavTryMakeShinyMon(); } + else if (P_ONLY_OBTAINABLE_SHINIES && InBattlePyramid()) + { + isShiny = FALSE; + } + else if (P_NO_SHINIES_WITHOUT_POKEBALLS && !HasAtLeastOnePokeBall()) + { + isShiny = FALSE; + } else { u32 totalRerolls = 0; @@ -2861,6 +2869,9 @@ u32 GetBoxMonData3(struct BoxPokemon *boxMon, s32 field, u8 *data) retVal = nature ^ boxMon->hiddenNatureModifier; break; } + case MON_DATA_DAYS_SINCE_FORM_CHANGE: + retVal = boxMon->daysSinceFormChange; + break; default: break; } @@ -3295,6 +3306,9 @@ void SetBoxMonData(struct BoxPokemon *boxMon, s32 field, const void *dataArg) boxMon->hiddenNatureModifier = nature ^ hiddenNature; break; } + case MON_DATA_DAYS_SINCE_FORM_CHANGE: + SET8(boxMon->daysSinceFormChange); + break; } } @@ -3397,6 +3411,9 @@ u8 GetMonsStateToDoubles(void) s32 i; CalculatePlayerPartyCount(); + if (OW_DOUBLE_APPROACH_WITH_ONE_MON) + return PLAYER_HAS_TWO_USABLE_MONS; + if (gPlayerPartyCount == 1) return gPlayerPartyCount; // PLAYER_HAS_ONE_MON @@ -6528,6 +6545,7 @@ u16 GetFormChangeTargetSpeciesBoxMon(struct BoxPokemon *boxMon, u16 method, u32 break; case FORM_CHANGE_WITHDRAW: case FORM_CHANGE_FAINT: + case FORM_CHANGE_DAYS_PASSED: targetSpecies = formChanges[i].targetSpecies; break; case FORM_CHANGE_STATUS: @@ -6555,6 +6573,22 @@ u16 GetFormChangeTargetSpeciesBoxMon(struct BoxPokemon *boxMon, u16 method, u32 return targetSpecies; } +void TrySetDayLimitToFormChange(struct Pokemon *mon) +{ + u32 i; + u16 species = GetMonData(mon, MON_DATA_SPECIES, NULL); + const struct FormChange *formChanges = GetSpeciesFormChanges(species); + + for (i = 0; formChanges[i].method != FORM_CHANGE_TERMINATOR; i++) + { + if (formChanges[i].method == FORM_CHANGE_DAYS_PASSED && species != formChanges[i].targetSpecies) + { + SetMonData(mon, MON_DATA_DAYS_SINCE_FORM_CHANGE, &formChanges[i].param1); + break; + } + } +} + bool32 DoesSpeciesHaveFormChangeMethod(u16 species, u16 method) { u32 i; @@ -6871,3 +6905,38 @@ const u8 *GetMoveAnimationScript(u16 moveId) } return gMovesInfo[moveId].battleAnimScript; } + +void UpdateDaysPassedSinceFormChange(u16 days) +{ + u32 i; + for (i = 0; i < PARTY_SIZE; i++) + { + struct Pokemon *mon = &gPlayerParty[i]; + u8 daysSinceFormChange; + + if (!GetMonData(mon, MON_DATA_SPECIES, 0)) + continue; + + daysSinceFormChange = GetMonData(mon, MON_DATA_DAYS_SINCE_FORM_CHANGE, 0); + if (daysSinceFormChange == 0) + continue; + + if (daysSinceFormChange > days) + daysSinceFormChange -= days; + else + daysSinceFormChange = 0; + + SetMonData(mon, MON_DATA_DAYS_SINCE_FORM_CHANGE, &daysSinceFormChange); + + if (daysSinceFormChange == 0) + { + u16 targetSpecies = GetFormChangeTargetSpecies(mon, FORM_CHANGE_DAYS_PASSED, 0); + + if (targetSpecies != SPECIES_NONE) + { + SetMonData(mon, MON_DATA_SPECIES, &targetSpecies); + CalculateMonStats(mon); + } + } + } +} diff --git a/src/pokemon_storage_system.c b/src/pokemon_storage_system.c index a3c7324e0e..c846e8facf 100644 --- a/src/pokemon_storage_system.c +++ b/src/pokemon_storage_system.c @@ -1436,9 +1436,9 @@ s16 GetFirstFreeBoxSpot(u8 boxId) return -1; // all spots are taken } -u8 CountPartyNonEggMons(void) +u32 CountPartyNonEggMons(void) { - u16 i, count; + u32 i, count; for (i = 0, count = 0; i < PARTY_SIZE; i++) { @@ -6434,23 +6434,10 @@ static void SetPlacedMonData(u8 boxId, u8 position) static void PurgeMonOrBoxMon(u8 boxId, u8 position) { - u16 item = ITEM_NONE; - if (boxId == TOTAL_BOXES_COUNT) - { - if (OW_PC_RELEASE_ITEM >= GEN_8) - item = GetMonData(&gPlayerParty[position], MON_DATA_HELD_ITEM); ZeroMonData(&gPlayerParty[position]); - } else - { - if (OW_PC_RELEASE_ITEM >= GEN_8) - item = GetBoxMonDataAt(boxId, position, MON_DATA_HELD_ITEM); ZeroBoxMonAt(boxId, position); - } - - if (item != ITEM_NONE) - AddBagItem(item, 1); } static void SetShiftedMonData(u8 boxId, u8 position) @@ -6530,6 +6517,7 @@ static bool8 TryHideReleaseMon(void) static void ReleaseMon(void) { u8 boxId; + u16 item = ITEM_NONE; DestroyReleaseMonIcon(); if (sIsMonBeingMoved) @@ -6539,11 +6527,21 @@ static void ReleaseMon(void) else { if (sCursorArea == CURSOR_AREA_IN_PARTY) + { boxId = TOTAL_BOXES_COUNT; + if (OW_PC_RELEASE_ITEM >= GEN_8) + item = GetMonData(&gPlayerParty[sCursorPosition], MON_DATA_HELD_ITEM); + } else + { boxId = StorageGetCurrentBox(); + if (OW_PC_RELEASE_ITEM >= GEN_8) + item = GetBoxMonDataAt(boxId, sCursorPosition, MON_DATA_HELD_ITEM); + } PurgeMonOrBoxMon(boxId, sCursorPosition); + if (item != ITEM_NONE) + AddBagItem(item, 1); } TryRefreshDisplayMon(); } diff --git a/src/roamer.c b/src/roamer.c index 7940c4b49b..dc5977cc31 100644 --- a/src/roamer.c +++ b/src/roamer.c @@ -105,7 +105,8 @@ static void CreateInitialRoamerMon(u8 index, u16 species, u8 level) ROAMER(index)->personality = GetMonData(&gEnemyParty[0], MON_DATA_PERSONALITY); ROAMER(index)->species = species; ROAMER(index)->level = level; - ROAMER(index)->status = 0; + ROAMER(index)->statusA = 0; + ROAMER(index)->statusB = 0; ROAMER(index)->hp = GetMonData(&gEnemyParty[0], MON_DATA_MAX_HP); ROAMER(index)->cool = GetMonData(&gEnemyParty[0], MON_DATA_COOL); ROAMER(index)->beauty = GetMonData(&gEnemyParty[0], MON_DATA_BEAUTY); @@ -238,18 +239,13 @@ bool8 IsRoamerAt(u32 roamerIndex, u8 mapGroup, u8 mapNum) void CreateRoamerMonInstance(u32 roamerIndex) { - u32 status; + u32 status = ROAMER(roamerIndex)->statusA + (ROAMER(roamerIndex)->statusB << 8); struct Pokemon *mon = &gEnemyParty[0]; ZeroEnemyPartyMons(); CreateMonWithIVsPersonality(mon, ROAMER(roamerIndex)->species, ROAMER(roamerIndex)->level, ROAMER(roamerIndex)->ivs, ROAMER(roamerIndex)->personality); // The roamer's status field is u16, but SetMonData expects status to be u32, so will set the roamer's status // using the status field and the following 3 bytes (cool, beauty, and cute). -#ifdef BUGFIX - status = ROAMER(roamerIndex)->status; SetMonData(mon, MON_DATA_STATUS, &status); -#else - SetMonData(mon, MON_DATA_STATUS, &ROAMER->status); -#endif SetMonData(mon, MON_DATA_HP, &ROAMER(roamerIndex)->hp); SetMonData(mon, MON_DATA_COOL, &ROAMER(roamerIndex)->cool); SetMonData(mon, MON_DATA_BEAUTY, &ROAMER(roamerIndex)->beauty); @@ -276,8 +272,11 @@ bool8 TryStartRoamerEncounter(void) void UpdateRoamerHPStatus(struct Pokemon *mon) { + u32 status = GetMonData(mon, MON_DATA_STATUS); + ROAMER(gEncounteredRoamerIndex)->hp = GetMonData(mon, MON_DATA_HP); - ROAMER(gEncounteredRoamerIndex)->status = GetMonData(mon, MON_DATA_STATUS); + ROAMER(gEncounteredRoamerIndex)->statusA = status; + ROAMER(gEncounteredRoamerIndex)->statusB = status >> 8; RoamerMoveToOtherLocationSet(gEncounteredRoamerIndex); } diff --git a/src/siirtc.c b/src/siirtc.c index 1536aecb42..a29a62da26 100644 --- a/src/siirtc.c +++ b/src/siirtc.c @@ -4,7 +4,7 @@ #include "gba/gba.h" #include "siirtc.h" -#include "config.h" +#include "config/general.h" #define STATUS_INTFE 0x02 // frequency interrupt enable #define STATUS_INTME 0x08 // per-minute interrupt enable diff --git a/src/vs_seeker.c b/src/vs_seeker.c index 63b6c6f9d5..30dcb74ce0 100644 --- a/src/vs_seeker.c +++ b/src/vs_seeker.c @@ -577,6 +577,14 @@ u16 GetRematchTrainerIdVSSeeker(u16 trainerId) return gRematchTable[tableId].trainerIds[rematchTrainerIdx]; } +bool32 IsVsSeekerEnabled(void) +{ + if (I_VS_SEEKER_CHARGING == 0) + return FALSE; + + return (CheckBagHasItem(ITEM_VS_SEEKER, 1)); +} + static bool8 ObjectEventIdIsSane(u8 objectEventId) { struct ObjectEvent *objectEvent = &gObjectEvents[objectEventId]; diff --git a/test/battle/ability/anger_point.c b/test/battle/ability/anger_point.c new file mode 100644 index 0000000000..0b13b9df4b --- /dev/null +++ b/test/battle/ability/anger_point.c @@ -0,0 +1,73 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Anger Point raises Attack stage to maximum after receiving a critical hit") +{ + ASSUME(gMovesInfo[MOVE_FROST_BREATH].alwaysCriticalHit); + + GIVEN { + PLAYER(SPECIES_PRIMEAPE) { Ability(ABILITY_ANGER_POINT); } + OPPONENT(SPECIES_SNORUNT); + } WHEN { + TURN { MOVE(opponent, MOVE_FROST_BREATH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FROST_BREATH, opponent); + MESSAGE("A critical hit!"); + ABILITY_POPUP(player, ABILITY_ANGER_POINT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Primeape's Anger Point maxed its Attack!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], MAX_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Anger Point does not trigger when already at maximum Attack stage") +{ + ASSUME(gMovesInfo[MOVE_FROST_BREATH].alwaysCriticalHit); + ASSUME(gMovesInfo[MOVE_BELLY_DRUM].effect == EFFECT_BELLY_DRUM); + + GIVEN { + PLAYER(SPECIES_PRIMEAPE) { Ability(ABILITY_ANGER_POINT); Speed(2); } + OPPONENT(SPECIES_SNORUNT) { Speed(1); } + } WHEN { + TURN { MOVE(player, MOVE_BELLY_DRUM); MOVE(opponent, MOVE_FROST_BREATH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BELLY_DRUM, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Primeape cut its own HP and maximized ATTACK!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FROST_BREATH, opponent); + MESSAGE("A critical hit!"); + NONE_OF { + ABILITY_POPUP(player, ABILITY_ANGER_POINT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Primeape's Anger Point maxed its Attack!"); + } + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], MAX_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Anger Point does not trigger when a substitute takes the hit") +{ + ASSUME(gMovesInfo[MOVE_FROST_BREATH].alwaysCriticalHit); + ASSUME(gMovesInfo[MOVE_SUBSTITUTE].effect == EFFECT_SUBSTITUTE); + + GIVEN { + PLAYER(SPECIES_PRIMEAPE) { Ability(ABILITY_ANGER_POINT); Speed(2); } + OPPONENT(SPECIES_SNORUNT) { Speed(1); } + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_FROST_BREATH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, player); + MESSAGE("Primeape made a SUBSTITUTE!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FROST_BREATH, opponent); + MESSAGE("A critical hit!"); + NONE_OF { + ABILITY_POPUP(player, ABILITY_ANGER_POINT); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Primeape's Anger Point maxed its Attack!"); + } + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} diff --git a/test/battle/ability/ate_abilities.c b/test/battle/ability/ate_abilities.c index 12ee75722a..203452cc01 100644 --- a/test/battle/ability/ate_abilities.c +++ b/test/battle/ability/ate_abilities.c @@ -48,7 +48,7 @@ SINGLE_BATTLE_TEST("Pixilate turns a normal type move into Fairy") PLAYER(SPECIES_DRAGONITE); OPPONENT(SPECIES_ALTARIA) { Item(ITEM_ALTARIANITE); } } WHEN { - TURN { MOVE(opponent, MOVE_TACKLE, megaEvolve: TRUE); } + 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); @@ -62,9 +62,8 @@ SINGLE_BATTLE_TEST("Refrigerate turns a normal type move into Ice") PLAYER(SPECIES_MEGANIUM); OPPONENT(SPECIES_AMAURA) { Ability(ABILITY_REFRIGERATE); } } WHEN { - TURN { MOVE(opponent, MOVE_TACKLE, megaEvolve: TRUE); } + TURN { MOVE(opponent, MOVE_TACKLE); } } SCENE { - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponent); ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); MESSAGE("It's super effective!"); } @@ -76,7 +75,7 @@ SINGLE_BATTLE_TEST("Aerilate turns a normal type move into Flying") PLAYER(SPECIES_MEGANIUM); OPPONENT(SPECIES_SALAMENCE) { Item(ITEM_SALAMENCITE); } } WHEN { - TURN { MOVE(opponent, MOVE_TACKLE, megaEvolve: TRUE); } + 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); diff --git a/test/battle/ability/beads_of_ruin.c b/test/battle/ability/beads_of_ruin.c index a58297a58b..bb05031fe3 100644 --- a/test/battle/ability/beads_of_ruin.c +++ b/test/battle/ability/beads_of_ruin.c @@ -46,9 +46,9 @@ SINGLE_BATTLE_TEST("Beads of Ruin's message displays correctly after all battler ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, opponent); // Everyone faints. MESSAGE("Go! Chi-Yu!"); + MESSAGE("2 sent out Wobbuffet!"); ABILITY_POPUP(player, ABILITY_BEADS_OF_RUIN); MESSAGE("Chi-Yu's Beads of Ruin weakened the Sp. Def of all surrounding Pokémon!"); - MESSAGE("2 sent out Wobbuffet!"); } } diff --git a/test/battle/ability/clear_body.c b/test/battle/ability/clear_body.c index 5f0efd265e..1379506a72 100644 --- a/test/battle/ability/clear_body.c +++ b/test/battle/ability/clear_body.c @@ -1,15 +1,19 @@ #include "global.h" #include "test/battle.h" -SINGLE_BATTLE_TEST("Clear Body prevents intimidate") +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke prevent intimidate") { s16 turnOneHit; s16 turnTwoHit; + u32 species, ability; + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } GIVEN { PLAYER(SPECIES_EKANS) { Ability(ABILITY_SHED_SKIN); }; PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); }; - OPPONENT(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); }; + OPPONENT(species) { Ability(ability); }; } WHEN { TURN { MOVE(opponent, MOVE_TACKLE); } TURN { SWITCH(player, 1); MOVE(opponent, MOVE_TACKLE); } @@ -20,24 +24,38 @@ SINGLE_BATTLE_TEST("Clear Body prevents intimidate") NONE_OF { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); } - ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); - MESSAGE("Foe Beldum's Clear Body prevents stat loss!"); + ABILITY_POPUP(opponent, ability); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("Foe Solgaleo's Full Metal Body prevents stat loss!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("Foe Torkoal's White Smoke prevents stat loss!"); + else + MESSAGE("Foe Metang's Clear Body prevents stat loss!"); HP_BAR(player, captureDamage: &turnTwoHit); } THEN { EXPECT_EQ(turnOneHit, turnTwoHit); } } -SINGLE_BATTLE_TEST("Clear Body prevents stat stage reduction from moves") +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke prevent stat stage reduction from moves") { - u16 move; - PARAMETRIZE{ move = MOVE_GROWL; } - PARAMETRIZE{ move = MOVE_LEER; } - PARAMETRIZE{ move = MOVE_CONFIDE; } - PARAMETRIZE{ move = MOVE_FAKE_TEARS; } - PARAMETRIZE{ move = MOVE_SCARY_FACE; } - PARAMETRIZE{ move = MOVE_SWEET_SCENT; } - PARAMETRIZE{ move = MOVE_SAND_ATTACK; } + u16 move = MOVE_NONE; + u32 j, species = SPECIES_NONE, ability = ABILITY_NONE; + static const u16 statReductionMoves[] = { + MOVE_GROWL, + MOVE_LEER, + MOVE_CONFIDE, + MOVE_FAKE_TEARS, + MOVE_SCARY_FACE, + MOVE_SWEET_SCENT, + MOVE_SAND_ATTACK, + }; + for (j = 0; j < ARRAY_COUNT(statReductionMoves); j++) + { + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; move = statReductionMoves[j]; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; move = statReductionMoves[j]; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; move = statReductionMoves[j]; } + } GIVEN { ASSUME(gMovesInfo[MOVE_GROWL].effect == EFFECT_ATTACK_DOWN); @@ -48,7 +66,7 @@ SINGLE_BATTLE_TEST("Clear Body prevents stat stage reduction from moves") ASSUME(gMovesInfo[MOVE_SWEET_SCENT].effect == (B_UPDATED_MOVE_DATA >= GEN_6 ? EFFECT_EVASION_DOWN_2 : EFFECT_EVASION_DOWN)); ASSUME(gMovesInfo[MOVE_SAND_ATTACK].effect == EFFECT_ACCURACY_DOWN); PLAYER(SPECIES_WOBBUFFET) - OPPONENT(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); } + OPPONENT(species) { Ability(ability); } } WHEN { TURN { MOVE(player, move); } } SCENE { @@ -56,18 +74,27 @@ SINGLE_BATTLE_TEST("Clear Body prevents stat stage reduction from moves") ANIMATION(ANIM_TYPE_MOVE, move, player); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); } - ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); - MESSAGE("Foe Beldum's Clear Body prevents stat loss!"); + ABILITY_POPUP(opponent, ability); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("Foe Solgaleo's Full Metal Body prevents stat loss!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("Foe Torkoal's White Smoke prevents stat loss!"); + else + MESSAGE("Foe Metang's Clear Body prevents stat loss!"); } } -SINGLE_BATTLE_TEST("Clear Body prevents Sticky Web") +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke prevent Sticky Web effect on switchin") { + u32 species, ability; + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } GIVEN { ASSUME(gMovesInfo[MOVE_STICKY_WEB].effect == EFFECT_STICKY_WEB); PLAYER(SPECIES_WOBBUFFET) OPPONENT(SPECIES_WOBBUFFET) - OPPONENT(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); } + OPPONENT(species) { Ability(ability); } } WHEN { TURN { MOVE(player, MOVE_STICKY_WEB); } TURN { SWITCH(opponent, 1); } @@ -75,32 +102,43 @@ SINGLE_BATTLE_TEST("Clear Body prevents Sticky Web") NONE_OF { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); } - ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); - MESSAGE("Foe Beldum's Clear Body prevents stat loss!"); + ABILITY_POPUP(opponent, ability); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("Foe Solgaleo's Full Metal Body prevents stat loss!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("Foe Torkoal's White Smoke prevents stat loss!"); + else + MESSAGE("Foe Metang's Clear Body prevents stat loss!"); } } -SINGLE_BATTLE_TEST("Clear Body doesn't prevent stat stage reduction from moves used by the user") +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent stat stage reduction from moves used by the user") { + u32 species, ability; + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } GIVEN { ASSUME(MoveHasAdditionalEffectSelf(MOVE_SUPERPOWER, MOVE_EFFECT_ATK_DEF_DOWN) == TRUE); PLAYER(SPECIES_WOBBUFFET) - OPPONENT(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); } + OPPONENT(species) { Ability(ability); } } WHEN { TURN { MOVE(opponent, MOVE_SUPERPOWER); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPERPOWER, opponent); NONE_OF { - ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); - MESSAGE("Foe Beldum's Clear Body prevents stat loss!"); + 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!"); } } } -SINGLE_BATTLE_TEST("Mold Breaker, Teravolt, and Turboblaze ignore Clear Body") +SINGLE_BATTLE_TEST("Mold Breaker, Teravolt, and Turboblaze ignore Clear Body and White Smoke, but not Full Metal Body") { - u32 j, k; - u16 ability = ABILITY_NONE; + u32 j, k, species = SPECIES_NONE, ability = ABILITY_NONE; + u16 breakerAbility = ABILITY_NONE; u16 move = ABILITY_NONE; static const u16 breakerAbilities[] = { ABILITY_MOLD_BREAKER, @@ -121,7 +159,9 @@ SINGLE_BATTLE_TEST("Mold Breaker, Teravolt, and Turboblaze ignore Clear Body") { for (k = 0; k < ARRAY_COUNT(breakerAbilities); k++) { - PARAMETRIZE{ move = statReductionMoves[j]; ability = breakerAbilities[k]; } + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; move = statReductionMoves[j]; breakerAbility = breakerAbilities[k]; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; move = statReductionMoves[j]; breakerAbility = breakerAbilities[k]; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; move = statReductionMoves[j]; breakerAbility = breakerAbilities[k]; } } } @@ -133,146 +173,242 @@ SINGLE_BATTLE_TEST("Mold Breaker, Teravolt, and Turboblaze ignore Clear Body") ASSUME(gMovesInfo[MOVE_SCARY_FACE].effect == EFFECT_SPEED_DOWN_2); ASSUME(gMovesInfo[MOVE_SWEET_SCENT].effect == (B_UPDATED_MOVE_DATA >= GEN_6 ? EFFECT_EVASION_DOWN_2 : EFFECT_EVASION_DOWN)); ASSUME(gMovesInfo[MOVE_SAND_ATTACK].effect == EFFECT_ACCURACY_DOWN); - PLAYER(SPECIES_WOBBUFFET) { Ability(ability); } - OPPONENT(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); } + PLAYER(SPECIES_WOBBUFFET) { Ability(breakerAbility); } + OPPONENT(species) { Ability(ability); } } WHEN { TURN { MOVE(player, move); } } SCENE { - ANIMATION(ANIM_TYPE_MOVE, move, player); - NONE_OF { - ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); - MESSAGE("Foe Beldum's Clear Body prevents stat loss!"); + if (ability == ABILITY_FULL_METAL_BODY){ // Full Metal Body can't be ignored by breaker abilities + NOT ANIMATION(ANIM_TYPE_MOVE, move, player); + ABILITY_POPUP(opponent, ability); + MESSAGE("Foe Solgaleo's Full Metal Body prevents stat loss!"); + } + else{ + ANIMATION(ANIM_TYPE_MOVE, move, player); + NONE_OF { + 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!"); + } } } } -SINGLE_BATTLE_TEST("Clear Body doesn't prevent Speed reduction from Iron Ball") +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent Speed reduction from Iron Ball") { - u16 heldItem; - PARAMETRIZE{ heldItem = ITEM_NONE; } - PARAMETRIZE{ heldItem = ITEM_IRON_BALL; } + u32 j, species = SPECIES_NONE, ability = ABILITY_NONE; + u16 heldItem = ITEM_NONE; + static const u16 heldItems[] = { + ITEM_NONE, + ITEM_IRON_BALL, + }; + for (j = 0; j < ARRAY_COUNT(heldItems); j++) + { + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; heldItem = heldItems[j]; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; heldItem = heldItems[j]; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; heldItem = heldItems[j]; } + } GIVEN { ASSUME(gItemsInfo[ITEM_IRON_BALL].holdEffect == HOLD_EFFECT_IRON_BALL); PLAYER(SPECIES_WOBBUFFET) { Speed(4); } - OPPONENT(SPECIES_BELDUM) { Speed(6); Ability(ABILITY_CLEAR_BODY); Item(heldItem); } + OPPONENT(species) { Speed(6); Ability(ability); Item(heldItem); } } WHEN { TURN { } } SCENE { - NOT ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + NOT ABILITY_POPUP(opponent, ability); if (heldItem == ITEM_IRON_BALL) { MESSAGE("Wobbuffet used Celebrate!"); - MESSAGE("Foe Beldum used Celebrate!"); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("Foe Solgaleo used Celebrate!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("Foe Torkoal used Celebrate!"); + else + MESSAGE("Foe Metang used Celebrate!"); } else { - MESSAGE("Foe Beldum used Celebrate!"); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("Foe Solgaleo used Celebrate!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("Foe Torkoal used Celebrate!"); + else + MESSAGE("Foe Metang used Celebrate!"); MESSAGE("Wobbuffet used Celebrate!"); } } } -SINGLE_BATTLE_TEST("Clear Body doesn't prevent Speed reduction from paralysis") +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent Speed reduction from paralysis") { + u32 species, ability; + + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } + GIVEN { PLAYER(SPECIES_WOBBUFFET) { Speed(4); } - OPPONENT(SPECIES_BELDUM) { Speed(6); Ability(ABILITY_CLEAR_BODY); } + OPPONENT(species) { Speed(6); Ability(ability); } } WHEN { TURN { MOVE(player, MOVE_THUNDER_WAVE); } TURN { MOVE(player, MOVE_THUNDER_WAVE); } } SCENE { - MESSAGE("Foe Beldum used Celebrate!"); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("Foe Solgaleo used Celebrate!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("Foe Torkoal used Celebrate!"); + else + MESSAGE("Foe Metang used Celebrate!"); MESSAGE("Wobbuffet used Thunder Wave!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_WAVE, player); - NOT ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + NOT ABILITY_POPUP(opponent, ability); MESSAGE("Wobbuffet used Thunder Wave!"); ONE_OF { - MESSAGE("Foe Beldum used Celebrate!"); - MESSAGE("Foe Beldum is paralyzed! It can't move!"); + MESSAGE("Foe Metang used Celebrate!"); + MESSAGE("Foe Metang is paralyzed! It can't move!"); + MESSAGE("Foe Solgaleo used Celebrate!"); + MESSAGE("Foe Solgaleo is paralyzed! It can't move!"); + MESSAGE("Foe Torkoal used Celebrate!"); + MESSAGE("Foe Torkoal is paralyzed! It can't move!"); } } } -SINGLE_BATTLE_TEST("Clear Body doesn't prevent Attack reduction from burn", s16 damage) +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent Attack reduction from burn", s16 damage) { - bool32 burned; - PARAMETRIZE{ burned = FALSE; } - PARAMETRIZE{ burned = TRUE; } + bool32 burned = FALSE; + u32 species, ability; + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; burned = FALSE; } + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; burned = TRUE; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; burned = FALSE; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; burned = TRUE; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; burned = FALSE; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; burned = TRUE; } GIVEN { ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); PLAYER(SPECIES_WOBBUFFET) - OPPONENT(SPECIES_BELDUM) { Ability(ABILITY_CLEAR_BODY); if (burned) Status1(STATUS1_BURN); } + OPPONENT(species) { Ability(ability); if (burned) Status1(STATUS1_BURN); } } WHEN { TURN { MOVE(opponent, MOVE_TACKLE); } } SCENE { - NOT ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + NOT ABILITY_POPUP(opponent, ability); HP_BAR(player, captureDamage: &results[i].damage); } FINALLY { EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.5), results[1].damage); } } -SINGLE_BATTLE_TEST("Clear Body doesn't prevent receiving negative stat changes from Baton Pass") +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent receiving negative stat changes from Baton Pass") { + u32 species, ability; + + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } + GIVEN { ASSUME(gMovesInfo[MOVE_SCARY_FACE].effect == EFFECT_SPEED_DOWN_2); ASSUME(gMovesInfo[MOVE_BATON_PASS].effect == EFFECT_BATON_PASS); PLAYER(SPECIES_WOBBUFFET) { Speed(4); } OPPONENT(SPECIES_WOBBUFFET) { Speed(3); } - OPPONENT(SPECIES_BELDUM) { Speed(6); Ability(ABILITY_CLEAR_BODY); } + OPPONENT(species) { Speed(6); Ability(ability); } } WHEN { TURN { MOVE(player, MOVE_SCARY_FACE); MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); } TURN { MOVE(player, MOVE_SCARY_FACE); } } SCENE { MESSAGE("Wobbuffet used Scary Face!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_SCARY_FACE, player); - ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); - MESSAGE("Foe Beldum used Celebrate!"); + ABILITY_POPUP(opponent, ability); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("Foe Solgaleo used Celebrate!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("Foe Torkoal used Celebrate!"); + else + MESSAGE("Foe Metang used Celebrate!"); } } -SINGLE_BATTLE_TEST("Clear Body doesn't prevent Topsy-Turvy") +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent Topsy-Turvy") { + u32 species, ability; + + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } + GIVEN { ASSUME(gMovesInfo[MOVE_TOPSY_TURVY].effect == EFFECT_TOPSY_TURVY); ASSUME(gMovesInfo[MOVE_SCARY_FACE].effect == EFFECT_SPEED_DOWN_2); ASSUME(gMovesInfo[MOVE_BATON_PASS].effect == EFFECT_BATON_PASS); PLAYER(SPECIES_WOBBUFFET) { Speed(4); } OPPONENT(SPECIES_WOBBUFFET) { Speed(3); } - OPPONENT(SPECIES_BELDUM) { Speed(6); Ability(ABILITY_CLEAR_BODY); } + OPPONENT(species) { Speed(6); Ability(ability); } } WHEN { TURN { MOVE(player, MOVE_SCARY_FACE); MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); } TURN { MOVE(player, MOVE_TOPSY_TURVY); } TURN { MOVE(player, MOVE_SCARY_FACE); } } SCENE { MESSAGE("Wobbuffet used Topsy-Turvy!"); - NOT ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + NOT ABILITY_POPUP(opponent, ability); ANIMATION(ANIM_TYPE_MOVE, MOVE_TOPSY_TURVY, player); - MESSAGE("Foe Beldum used Celebrate!"); - MESSAGE("Foe Beldum used Celebrate!"); + if (ability == ABILITY_FULL_METAL_BODY) { + MESSAGE("Foe Solgaleo used Celebrate!"); + MESSAGE("Foe Solgaleo used Celebrate!"); + } + else if (ability == ABILITY_WHITE_SMOKE) { + MESSAGE("Foe Torkoal used Celebrate!"); + MESSAGE("Foe Torkoal used Celebrate!"); + } + else { + MESSAGE("Foe Metang used Celebrate!"); + MESSAGE("Foe Metang used Celebrate!"); + } MESSAGE("Wobbuffet used Scary Face!"); NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SCARY_FACE, player); - ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + ABILITY_POPUP(opponent, ability); } } -SINGLE_BATTLE_TEST("Clear Body doesn't prevent Spectral Thief from resetting positive stat changes") +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent Spectral Thief from resetting positive stat changes") { + u32 species, ability; + + PARAMETRIZE{ species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } + GIVEN { ASSUME(MoveHasAdditionalEffect(MOVE_SPECTRAL_THIEF, MOVE_EFFECT_SPECTRAL_THIEF) == TRUE); ASSUME(gMovesInfo[MOVE_AGILITY].effect == EFFECT_SPEED_UP_2); PLAYER(SPECIES_WOBBUFFET) { Speed(4); } - OPPONENT(SPECIES_METANG) { Speed(5); Ability(ABILITY_CLEAR_BODY); } + OPPONENT(species) { Speed(5); Ability(ability); } } WHEN { TURN{ MOVE(opponent, MOVE_AGILITY); } TURN{ MOVE(player, MOVE_SPECTRAL_THIEF); } TURN{ } } SCENE { - MESSAGE("Foe Metang used Agility!"); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("Foe Solgaleo used Agility!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("Foe Torkoal used Agility!"); + else + MESSAGE("Foe Metang used Agility!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_AGILITY, opponent); MESSAGE("Wobbuffet used Celebrate!"); - MESSAGE("Foe Metang used Celebrate!"); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("Foe Solgaleo used Celebrate!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("Foe Torkoal used Celebrate!"); + else + MESSAGE("Foe Metang used Celebrate!"); MESSAGE("Wobbuffet used Spectral Thief!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_SPECTRAL_THIEF, player); - NOT ABILITY_POPUP(opponent, ABILITY_CLEAR_BODY); + NOT ABILITY_POPUP(opponent, ability); MESSAGE("Wobbuffet used Celebrate!"); - MESSAGE("Foe Metang used Celebrate!"); + if (ability == ABILITY_FULL_METAL_BODY) + MESSAGE("Foe Solgaleo used Celebrate!"); + else if (ability == ABILITY_WHITE_SMOKE) + MESSAGE("Foe Torkoal used Celebrate!"); + else + MESSAGE("Foe Metang used Celebrate!"); } } diff --git a/test/battle/ability/comatose.c b/test/battle/ability/comatose.c new file mode 100644 index 0000000000..bd991c258e --- /dev/null +++ b/test/battle/ability/comatose.c @@ -0,0 +1,54 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Comatose prevents status-inducing moves") +{ + u32 move; + + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISONPOWDER; } + PARAMETRIZE { move = MOVE_SLEEP_POWDER; } + PARAMETRIZE { move = MOVE_THUNDER_WAVE; } + + GIVEN { + PLAYER(SPECIES_KOMALA) { Ability(ABILITY_COMATOSE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + MESSAGE("Komala is drowsing!"); + + NOT ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ABILITY_POPUP(player, ABILITY_COMATOSE); + MESSAGE("It doesn't affect Komala…"); + } +} + +SINGLE_BATTLE_TEST("Comatose may be suppressed if pokemon transformed into a pokemon with Comatose ability and was under the effects of Gastro Acid") +{ + u32 move; + + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISONPOWDER; } + PARAMETRIZE { move = MOVE_SLEEP_POWDER; } + PARAMETRIZE { move = MOVE_THUNDER_WAVE; } + + GIVEN { + PLAYER(SPECIES_KOMALA) { Ability(ABILITY_COMATOSE); Speed(30); } + OPPONENT(SPECIES_DITTO) { Speed(20); } + } WHEN { + TURN { MOVE(player, MOVE_GASTRO_ACID); MOVE(opponent, MOVE_TRANSFORM); } + TURN { MOVE(player, move); } + } SCENE { + MESSAGE("Komala is drowsing!"); + MESSAGE("Komala used Gastro Acid!"); + MESSAGE("Foe Ditto used Transform!"); + MESSAGE("Foe Ditto transformed into Komala!"); + + ANIMATION(ANIM_TYPE_MOVE, move, player); + if (move == MOVE_POISONPOWDER) { STATUS_ICON(opponent, poison: TRUE); } + else if (move == MOVE_TOXIC) { STATUS_ICON(opponent, badPoison: TRUE); } + else if (move == MOVE_THUNDER_WAVE) { STATUS_ICON(opponent, paralysis: TRUE); } + else if (move == MOVE_SLEEP_POWDER) { STATUS_ICON(opponent, sleep: TRUE); } + } +} diff --git a/test/battle/ability/corrosion.c b/test/battle/ability/corrosion.c index 87477ddbcc..8addbd90fa 100644 --- a/test/battle/ability/corrosion.c +++ b/test/battle/ability/corrosion.c @@ -106,23 +106,122 @@ SINGLE_BATTLE_TEST("If a Poison- or Steel-type Pokémon with Corrosion holds a T SINGLE_BATTLE_TEST("If a Poison- or Steel-type Pokémon with Corrosion poisons a target with Synchronize, Synchronize will not poison Poison- or Steel-type Pokémon") { + u16 move; + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISON_POWDER; } GIVEN { ASSUME(gMovesInfo[MOVE_TOXIC].effect == EFFECT_TOXIC); + ASSUME(gMovesInfo[MOVE_POISON_POWDER].effect == EFFECT_POISON); PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); } OPPONENT(SPECIES_ABRA) { Ability(ABILITY_SYNCHRONIZE); } } WHEN { - TURN { MOVE(player, MOVE_TOXIC); } + TURN { MOVE(player, move); } } SCENE { - ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + ANIMATION(ANIM_TYPE_MOVE, move, player); ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); - STATUS_ICON(opponent, badPoison: TRUE); + if (move == MOVE_TOXIC) + STATUS_ICON(opponent, badPoison: TRUE); + else + STATUS_ICON(opponent, poison: TRUE); NONE_OF { ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); STATUS_ICON(player, badPoison: TRUE); + STATUS_ICON(player, poison: TRUE); } } } -TO_DO_BATTLE_TEST("Corrosion cannot bypass moves or Abilities that prevent poisoning, such as Safeguard or Immunity"); -TO_DO_BATTLE_TEST("If the Pokémon with this Ability uses Magic Coat to reflect a status move that inflicts poison, the reflected move will be able to poison Poison- or Steel-type Pokémon."); -TO_DO_BATTLE_TEST("Moves used by a Pokémon with Corrosion that are reflected by Magic Coat or Magic Bounce do not retain the ability to poison Poison- or Steel-type Pokémon.") +SINGLE_BATTLE_TEST("Corrosion cannot bypass moves that prevent poisoning such as Safeguard") +{ + u16 move; + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISON_POWDER; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TOXIC].effect == EFFECT_TOXIC); + ASSUME(gMovesInfo[MOVE_POISON_POWDER].effect == EFFECT_POISON); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SAFEGUARD); MOVE(player, move); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, badPoison: TRUE); + STATUS_ICON(opponent, poison: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Corrosion cannot bypass abilities that prevent poisoning such as Immunity") +{ + u16 move; + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISON_POWDER; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TOXIC].effect == EFFECT_TOXIC); + ASSUME(gMovesInfo[MOVE_POISON_POWDER].effect == EFFECT_POISON); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); } + OPPONENT(SPECIES_SNORLAX) { Ability(ABILITY_IMMUNITY); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, badPoison: TRUE); + STATUS_ICON(opponent, poison: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Corrosion allows the Pokémon with the ability to poison a Steel or Poison-type opponent by using Magic Coat") +{ + u16 move; + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISON_POWDER; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TOXIC].effect == EFFECT_TOXIC); + ASSUME(gMovesInfo[MOVE_POISON_POWDER].effect == EFFECT_POISON); + ASSUME(gMovesInfo[MOVE_MAGIC_COAT].effect == EFFECT_MAGIC_COAT); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); } + OPPONENT(SPECIES_BELDUM); + } WHEN { + TURN { MOVE(player, MOVE_MAGIC_COAT); MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MAGIC_COAT, player); + ANIMATION(ANIM_TYPE_MOVE, move, player); // Bounced by Magic Coat + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + if (move == MOVE_TOXIC) + STATUS_ICON(opponent, badPoison: TRUE); + else + STATUS_ICON(opponent, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Corrosion's effect is lost if the move used by the Pokémon with the ability is reflected by Magic Coat") +{ + u16 move; + PARAMETRIZE { move = MOVE_TOXIC; } + PARAMETRIZE { move = MOVE_POISON_POWDER; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TOXIC].effect == EFFECT_TOXIC); + ASSUME(gMovesInfo[MOVE_POISON_POWDER].effect == EFFECT_POISON); + ASSUME(gMovesInfo[MOVE_MAGIC_COAT].effect == EFFECT_MAGIC_COAT); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_MAGIC_COAT); MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MAGIC_COAT, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, move, player); + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + if (move == MOVE_TOXIC) + STATUS_ICON(opponent, badPoison: TRUE); + else + STATUS_ICON(opponent, poison: TRUE); + } + } +} diff --git a/test/battle/ability/cursed_body.c b/test/battle/ability/cursed_body.c new file mode 100644 index 0000000000..20fe659d21 --- /dev/null +++ b/test/battle/ability/cursed_body.c @@ -0,0 +1,17 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Cursed Body triggers 30% of the time") +{ + PASSES_RANDOMLY(3, 10, RNG_CURSED_BODY); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_FRILLISH) { Ability(ABILITY_CURSED_BODY); } + } WHEN { + TURN { MOVE(player, MOVE_AQUA_JET); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AQUA_JET, player); + ABILITY_POPUP(opponent, ABILITY_CURSED_BODY); + MESSAGE("Wobbuffet's Aqua Jet was disabled by Foe Frillish's Cursed Body!"); + } +} diff --git a/test/battle/ability/cute_charm.c b/test/battle/ability/cute_charm.c index af9815e750..06eb4cf842 100644 --- a/test/battle/ability/cute_charm.c +++ b/test/battle/ability/cute_charm.c @@ -45,3 +45,22 @@ SINGLE_BATTLE_TEST("Cute Charm cannot infatuate same gender") ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); } } + +SINGLE_BATTLE_TEST("Cute Charm triggers 30% of the time") +{ + PASSES_RANDOMLY(3, 10, RNG_CUTE_CHARM); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_4); + ASSUME(gMovesInfo[MOVE_TACKLE].makesContact); + PLAYER(SPECIES_WOBBUFFET) { Gender(MON_MALE); } + OPPONENT(SPECIES_CLEFAIRY) { Gender(MON_FEMALE); Ability(ABILITY_CUTE_CHARM); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_CUTE_CHARM); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_INFATUATION, player); + MESSAGE("Foe Clefairy's Cute Charm infatuated Wobbuffet!"); + MESSAGE("Wobbuffet is in love with Foe Clefairy!"); + } +} diff --git a/test/battle/ability/download.c b/test/battle/ability/download.c index 2d9f8f466e..9fecda400d 100644 --- a/test/battle/ability/download.c +++ b/test/battle/ability/download.c @@ -56,6 +56,7 @@ SINGLE_BATTLE_TEST("Download raises Sp.Attack if enemy has lower Sp. Def than De SINGLE_BATTLE_TEST("Download doesn't activate if target hasn't been sent out yet", s16 damagePhysical, s16 damageSpecial) { u32 ability; + PARAMETRIZE { ability = ABILITY_TRACE; } PARAMETRIZE { ability = ABILITY_DOWNLOAD; } GIVEN { @@ -73,13 +74,13 @@ SINGLE_BATTLE_TEST("Download doesn't activate if target hasn't been sent out yet // Everyone faints. SEND_IN_MESSAGE("Porygon"); - MESSAGE("2 sent out Porygon2!"); - NONE_OF { ABILITY_POPUP(player, ABILITY_DOWNLOAD); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); MESSAGE("Porygon's Download raised its Attack!"); } + MESSAGE("2 sent out Porygon2!"); + if (ability == ABILITY_DOWNLOAD) { ABILITY_POPUP(opponent, ABILITY_DOWNLOAD); diff --git a/test/battle/ability/effect_spore.c b/test/battle/ability/effect_spore.c new file mode 100644 index 0000000000..107cd08675 --- /dev/null +++ b/test/battle/ability/effect_spore.c @@ -0,0 +1,90 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Effect Spore only inflicts status on contact") +{ + u32 move; + + PARAMETRIZE { move = MOVE_TACKLE; } + PARAMETRIZE { move = MOVE_SWIFT; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].makesContact); + ASSUME(!gMovesInfo[MOVE_SWIFT].makesContact); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_EFFECT_SPORE); } + } WHEN { + TURN { MOVE(player, move, WITH_RNG(RNG_EFFECT_SPORE, 1)); } + TURN {} + } SCENE { + if (gMovesInfo[move].makesContact) { + ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + MESSAGE("Wobbuffet was poisoned by Foe Breloom's Effect Spore!"); + STATUS_ICON(player, poison: TRUE); + } else { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + MESSAGE("Wobbuffet was poisoned by Foe Breloom's Effect Spore!"); + STATUS_ICON(player, poison: TRUE); + } + } + } +} + +SINGLE_BATTLE_TEST("Effect Spore causes poison 9% of the time") +{ + PASSES_RANDOMLY(9, 100, RNG_EFFECT_SPORE); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5); + ASSUME(gMovesInfo[MOVE_TACKLE].makesContact); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_EFFECT_SPORE); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + MESSAGE("Wobbuffet was poisoned by Foe Breloom's Effect Spore!"); + STATUS_ICON(player, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Effect Spore causes paralysis 10% of the time") +{ + PASSES_RANDOMLY(10, 100, RNG_EFFECT_SPORE); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5); + ASSUME(gMovesInfo[MOVE_TACKLE].makesContact); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_EFFECT_SPORE); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, player); + MESSAGE("Foe Breloom's Effect Spore paralyzed Wobbuffet! It may be unable to move!"); + STATUS_ICON(player, paralysis: TRUE); + } +} + +SINGLE_BATTLE_TEST("Effect Spore causes sleep 11% of the time") +{ + PASSES_RANDOMLY(11, 100, RNG_EFFECT_SPORE); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_5); + ASSUME(gMovesInfo[MOVE_TACKLE].makesContact); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRELOOM) { Ability(ABILITY_EFFECT_SPORE); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_EFFECT_SPORE); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, player); + MESSAGE("Foe Breloom's Effect Spore made Wobbuffet sleep!"); + STATUS_ICON(player, sleep: TRUE); + } +} diff --git a/test/battle/ability/flame_body.c b/test/battle/ability/flame_body.c index 84d2cd48f2..f44fb91724 100644 --- a/test/battle/ability/flame_body.c +++ b/test/battle/ability/flame_body.c @@ -29,3 +29,21 @@ SINGLE_BATTLE_TEST("Flame Body inflicts burn on contact") } } } + +SINGLE_BATTLE_TEST("Flame Body triggers 30% of the time") +{ + PASSES_RANDOMLY(3, 10, RNG_FLAME_BODY); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_4); + ASSUME(gMovesInfo[MOVE_TACKLE].makesContact); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MAGMAR) { Ability(ABILITY_FLAME_BODY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_FLAME_BODY); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, player); + MESSAGE("Foe Magmar's Flame Body burned Wobbuffet!"); + STATUS_ICON(player, burn: TRUE); + } +} diff --git a/test/battle/ability/full_metal_body.c b/test/battle/ability/full_metal_body.c index d00714d524..3b36f2d1f2 100644 --- a/test/battle/ability/full_metal_body.c +++ b/test/battle/ability/full_metal_body.c @@ -1,38 +1,4 @@ #include "global.h" #include "test/battle.h" -SINGLE_BATTLE_TEST("Full Metal Body prevents intimidate") -{ - s16 turnOneHit; - s16 turnTwoHit; - - GIVEN { - PLAYER(SPECIES_EKANS) { Ability(ABILITY_SHED_SKIN); }; - PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); }; - OPPONENT(SPECIES_SOLGALEO) { Ability(ABILITY_FULL_METAL_BODY); }; - } WHEN { - TURN { MOVE(opponent, MOVE_TACKLE); } - TURN { SWITCH(player, 1); MOVE(opponent, MOVE_TACKLE); } - - } SCENE { - HP_BAR(player, captureDamage: &turnOneHit); - ABILITY_POPUP(player, ABILITY_INTIMIDATE); - NONE_OF { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); } - ABILITY_POPUP(opponent, ABILITY_FULL_METAL_BODY); - MESSAGE("Foe Solgaleo's Full Metal Body prevents stat loss!"); - HP_BAR(player, captureDamage: &turnTwoHit); - } THEN { - EXPECT_EQ(turnOneHit, turnTwoHit); - } -} - -TO_DO_BATTLE_TEST("Full Metal Body prevents stat stage reduction from moves"); // Growl, Leer, Confide, Fake Tears, Scary Face, Sweet Scent, Sand Attack (Attack, Defense, Sp. Attack, Sp. Defense, Speed, Evasion, Accuracy -TO_DO_BATTLE_TEST("Full Metal Body prevents Sticky Web"); -TO_DO_BATTLE_TEST("Full Metal Body doesn't prevent stat stage reduction from moves used by the user"); // e.g. Superpower -TO_DO_BATTLE_TEST("Full Metal Body doesn't prevent Speed reduction from Iron Ball"); -TO_DO_BATTLE_TEST("Full Metal Body doesn't prevent Speed reduction from paralysis"); -TO_DO_BATTLE_TEST("Full Metal Body doesn't prevent Attack reduction from burn"); -TO_DO_BATTLE_TEST("Full Metal Body doesn't prevent receiving negative stat changes from Baton Pass"); -TO_DO_BATTLE_TEST("Full Metal Body doesn't prevent Topsy-Turvy"); -TO_DO_BATTLE_TEST("Full Metal Body doesn't prevent Spectral Thief from resetting positive stat changes"); -TO_DO_BATTLE_TEST("Full Metal Body is ignored by Mold Breaker"); +// Tests for Full Metal Body are handled in test/battle/ability/clear_body.c diff --git a/test/battle/ability/intimidate.c b/test/battle/ability/intimidate.c index f4c5395723..d2d7bc4af7 100644 --- a/test/battle/ability/intimidate.c +++ b/test/battle/ability/intimidate.c @@ -78,23 +78,29 @@ DOUBLE_BATTLE_TEST("Intimidate doesn't activate on an empty field in a double ba // Everyone faints. SEND_IN_MESSAGE("Ekans"); - MESSAGE("2 sent out Arbok!"); - SEND_IN_MESSAGE("Abra"); - MESSAGE("2 sent out Wynaut!"); - NONE_OF { ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); - MESSAGE("Ekans's Intimidate cuts Foe Arbok's attack!"); - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); - MESSAGE("Ekans's Intimidate cuts Foe Wynaut's attack!"); - + } + MESSAGE("2 sent out Arbok!"); + NONE_OF { ABILITY_POPUP(opponentLeft, ABILITY_INTIMIDATE); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); - MESSAGE("Foe Arbok's Intimidate cuts Ekans's attack!"); - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); - MESSAGE("Foe Arbok's Intimidate cuts Abra's attack!"); } + SEND_IN_MESSAGE("Abra"); + MESSAGE("2 sent out Wynaut!"); + // Intimidate activates after all battlers have been brought out + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("Ekans's Intimidate cuts Foe Arbok's attack!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("Ekans's Intimidate cuts Foe Wynaut's attack!"); + + ABILITY_POPUP(opponentLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Foe Arbok's Intimidate cuts Ekans's attack!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Foe Arbok's Intimidate cuts Abra's attack!"); } } @@ -211,6 +217,35 @@ SINGLE_BATTLE_TEST("Intimidate can not further lower opponents Atk stat if it is } } +DOUBLE_BATTLE_TEST("Intimidate is not going to trigger if a mon switches out through u-turn and the opposing field is empty") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TREECKO); + OPPONENT(SPECIES_TORCHIC); + } WHEN { + TURN { + MOVE(opponentRight, MOVE_HEALING_WISH); + MOVE(playerLeft, MOVE_U_TURN, target: opponentLeft); + SEND_OUT(playerLeft, 2); + SEND_OUT(opponentLeft, 2); + SEND_OUT(opponentRight, 3); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEALING_WISH, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, playerLeft); + HP_BAR(opponentLeft); + MESSAGE("2 sent out Treecko!"); + MESSAGE("2 sent out Torchic!"); + NOT ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + } +} + SINGLE_BATTLE_TEST("Intimidate activates when it's no longer effected by Neutralizing Gas") { GIVEN { diff --git a/test/battle/ability/minds_eye.c b/test/battle/ability/minds_eye.c index 6498f9f098..7799735ee2 100644 --- a/test/battle/ability/minds_eye.c +++ b/test/battle/ability/minds_eye.c @@ -49,8 +49,8 @@ AI_SINGLE_BATTLE_TEST("AI doesn't use accuracy-lowering moves if it knows that t for (j = MOVE_NONE + 1; j < MOVES_COUNT; j++) { if (gMovesInfo[j].effect == EFFECT_ACCURACY_DOWN || gMovesInfo[j].effect == EFFECT_ACCURACY_DOWN_2) { - PARAMETRIZE{ moveAI = j; abilityAI = ABILITY_SWIFT_SWIM; } - PARAMETRIZE{ moveAI = j; abilityAI = ABILITY_MOLD_BREAKER; } + PARAMETRIZE { moveAI = j; abilityAI = ABILITY_SWIFT_SWIM; } + PARAMETRIZE { moveAI = j; abilityAI = ABILITY_MOLD_BREAKER; } } } diff --git a/test/battle/ability/moxie.c b/test/battle/ability/moxie.c new file mode 100644 index 0000000000..b60a11507a --- /dev/null +++ b/test/battle/ability/moxie.c @@ -0,0 +1,121 @@ +#include "global.h" +#include "test/battle.h" + +DOUBLE_BATTLE_TEST("Moxie raises Attack by one stage after directly causing a Pokemon to faint") +{ + ASSUME(gMovesInfo[MOVE_EARTHQUAKE].target == MOVE_TARGET_FOES_AND_ALLY); + + GIVEN { + PLAYER(SPECIES_SALAMENCE) { Ability(ABILITY_MOXIE); } + PLAYER(SPECIES_SNORUNT) { HP(1); } + OPPONENT(SPECIES_GLALIE) { HP(1); } + OPPONENT(SPECIES_ABRA) { HP(1); } + OPPONENT(SPECIES_ABRA); + } WHEN { + TURN { MOVE(playerLeft, MOVE_EARTHQUAKE); SEND_OUT(opponentLeft, 2); } + } SCENE { + int i; + + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, playerLeft); + for (i = 0; i < 3; i++) { + ONE_OF { + MESSAGE("Snorunt fainted!"); + MESSAGE("Foe Glalie fainted!"); + MESSAGE("Foe Abra fainted!"); + } + ABILITY_POPUP(playerLeft, ABILITY_MOXIE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Salamence's Moxie 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") +{ + GIVEN { + PLAYER(SPECIES_SALAMENCE) { Ability(ABILITY_MOXIE); } + 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, ABILITY_MOXIE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Salamence's Moxie raised its Attack!"); + } + } + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Moxie does not trigger when already at maximum Attack stage") +{ + ASSUME(gMovesInfo[MOVE_BELLY_DRUM].effect == EFFECT_BELLY_DRUM); + + GIVEN { + PLAYER(SPECIES_SALAMENCE) { Ability(ABILITY_MOXIE); } + OPPONENT(SPECIES_SNORUNT) { HP(1); } + OPPONENT(SPECIES_SNORUNT); + } WHEN { + TURN { MOVE(player, MOVE_BELLY_DRUM); } + TURN { MOVE(player, MOVE_QUICK_ATTACK); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BELLY_DRUM, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Salamence 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); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Salamence's Moxie 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") +{ + s16 damage[2]; + + ASSUME(gMovesInfo[MOVE_EARTHQUAKE].target == MOVE_TARGET_FOES_AND_ALLY); + + KNOWN_FAILING; // Requires simultaneous damage implementation + GIVEN { + PLAYER(SPECIES_SALAMENCE) { Ability(ABILITY_MOXIE); } + PLAYER(SPECIES_ABRA) { HP(1); } + OPPONENT(SPECIES_GLALIE); + OPPONENT(SPECIES_GLALIE); + OPPONENT(SPECIES_ABRA); + } WHEN { + TURN { MOVE(playerLeft, MOVE_EARTHQUAKE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, playerLeft); + HP_BAR(opponentLeft, captureDamage: &damage[0]); + HP_BAR(playerRight); + MESSAGE("Abra fainted!"); + ABILITY_POPUP(playerLeft, ABILITY_MOXIE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Salamence's Moxie raised its Attack!"); + HP_BAR(opponentRight, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(damage[0], damage[1]); + } +} diff --git a/test/battle/ability/own_tempo.c b/test/battle/ability/own_tempo.c index d5a6f58a72..a37bc0024a 100644 --- a/test/battle/ability/own_tempo.c +++ b/test/battle/ability/own_tempo.c @@ -56,6 +56,23 @@ SINGLE_BATTLE_TEST("Own Tempo prevents confusion from moves by the user") } } +SINGLE_BATTLE_TEST("Own Tempo is ignored by Mold Breaker") +{ + KNOWN_FAILING; // Ideally the func CanBeConfused should be split into AttackerCanBeConfused and TargetCanBeConfused or we do it in the same func but have a check for when battlerAtk == battlerDef + GIVEN { + ASSUME(gMovesInfo[MOVE_CONFUSE_RAY].effect == EFFECT_CONFUSE); + PLAYER(SPECIES_PINSIR) { Ability(ABILITY_MOLD_BREAKER); } + OPPONENT(SPECIES_SLOWPOKE) { Ability(ABILITY_OWN_TEMPO); }; + } WHEN { + TURN { MOVE(player, MOVE_CONFUSE_RAY); } + } SCENE { + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_OWN_TEMPO); + MESSAGE("Foe Slowpoke's Own Tempo prevents confusion!"); + } + } +} + SINGLE_BATTLE_TEST("Own Tempo cures confusion obtained from an opponent with Mold Breaker") { KNOWN_FAILING; diff --git a/test/battle/ability/parental_bond.c b/test/battle/ability/parental_bond.c index 46e71096de..0b8a807e84 100644 --- a/test/battle/ability/parental_bond.c +++ b/test/battle/ability/parental_bond.c @@ -10,7 +10,7 @@ SINGLE_BATTLE_TEST("Parental Bond converts Tackle into a two-strike move") PLAYER(SPECIES_KANGASKHAN) { Item(ITEM_KANGASKHANITE); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TACKLE, megaEvolve: TRUE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_MEGA); MOVE(opponent, MOVE_CELEBRATE); } } SCENE { MESSAGE("Kangaskhan's Kangaskhanite is reacting to 1's Mega Ring!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, player); @@ -32,7 +32,7 @@ SINGLE_BATTLE_TEST("Parental Bond does not convert a move with three or more str PLAYER(SPECIES_KANGASKHAN) { Item(ITEM_KANGASKHANITE); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TRIPLE_KICK, megaEvolve: TRUE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_TRIPLE_KICK, gimmick: GIMMICK_MEGA); MOVE(opponent, MOVE_CELEBRATE); } } SCENE { MESSAGE("Kangaskhan's Kangaskhanite is reacting to 1's Mega Ring!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, player); @@ -61,7 +61,7 @@ SINGLE_BATTLE_TEST("Parental Bond converts multi-target moves into a two-strike PLAYER(SPECIES_KANGASKHAN) { Item(ITEM_KANGASKHANITE); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, move, megaEvolve: TRUE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, move, gimmick: GIMMICK_MEGA); MOVE(opponent, MOVE_CELEBRATE); } } SCENE { MESSAGE("Kangaskhan's Kangaskhanite is reacting to 1's Mega Ring!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, player); @@ -86,7 +86,7 @@ DOUBLE_BATTLE_TEST("Parental Bond does not convert multi-target moves into a two OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_PIDGEY); } WHEN { - TURN { MOVE(playerLeft, MOVE_EARTHQUAKE, megaEvolve: TRUE); MOVE(playerRight, MOVE_CELEBRATE); MOVE(opponentLeft, MOVE_CELEBRATE); MOVE(opponentRight, MOVE_CELEBRATE); } + TURN { MOVE(playerLeft, MOVE_EARTHQUAKE, gimmick: GIMMICK_MEGA); MOVE(playerRight, MOVE_CELEBRATE); MOVE(opponentLeft, MOVE_CELEBRATE); MOVE(opponentRight, MOVE_CELEBRATE); } } SCENE { MESSAGE("Kangaskhan's Kangaskhanite is reacting to 1's Mega Ring!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, playerLeft); @@ -114,7 +114,7 @@ SINGLE_BATTLE_TEST("Parental Bond-converted moves only hit once on Lightning Rod PLAYER(SPECIES_KANGASKHAN) { Item(ITEM_KANGASKHANITE); } OPPONENT(species) { Ability(ability); } } WHEN { - TURN { MOVE(player, move, megaEvolve: TRUE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, move, gimmick: GIMMICK_MEGA); MOVE(opponent, MOVE_CELEBRATE); } } SCENE { MESSAGE("Kangaskhan's Kangaskhanite is reacting to 1's Mega Ring!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, player); @@ -142,7 +142,7 @@ SINGLE_BATTLE_TEST("Parental Bond has no affect on multi hit moves and they stil PLAYER(SPECIES_KANGASKHAN) { Item(ITEM_KANGASKHANITE); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_COMET_PUNCH, megaEvolve: TRUE); } + TURN { MOVE(player, MOVE_COMET_PUNCH, gimmick: GIMMICK_MEGA); } } SCENE { MESSAGE("Kangaskhan's Kangaskhanite is reacting to 1's Mega Ring!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, player); @@ -168,7 +168,7 @@ SINGLE_BATTLE_TEST("Parental Bond has no affect on multi hit moves and they stil PLAYER(SPECIES_KANGASKHAN) { Item(ITEM_KANGASKHANITE); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_COMET_PUNCH, megaEvolve: TRUE); } + TURN { MOVE(player, MOVE_COMET_PUNCH, gimmick: GIMMICK_MEGA); } } SCENE { MESSAGE("Kangaskhan's Kangaskhanite is reacting to 1's Mega Ring!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, player); @@ -195,7 +195,7 @@ SINGLE_BATTLE_TEST("Parental Bond has no affect on multi hit moves and they stil PLAYER(SPECIES_KANGASKHAN) { Item(ITEM_KANGASKHANITE); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_COMET_PUNCH, megaEvolve: TRUE); } + TURN { MOVE(player, MOVE_COMET_PUNCH, gimmick: GIMMICK_MEGA); } } SCENE { MESSAGE("Kangaskhan's Kangaskhanite is reacting to 1's Mega Ring!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, player); @@ -223,7 +223,7 @@ SINGLE_BATTLE_TEST("Parental Bond has no affect on multi hit moves and they stil PLAYER(SPECIES_KANGASKHAN) { Item(ITEM_KANGASKHANITE); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_COMET_PUNCH, megaEvolve: TRUE); } + TURN { MOVE(player, MOVE_COMET_PUNCH, gimmick: GIMMICK_MEGA); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_COMET_PUNCH, player); ANIMATION(ANIM_TYPE_MOVE, MOVE_COMET_PUNCH, player); diff --git a/test/battle/ability/poison_point.c b/test/battle/ability/poison_point.c index bd713fa2fc..26c9c72968 100644 --- a/test/battle/ability/poison_point.c +++ b/test/battle/ability/poison_point.c @@ -30,3 +30,22 @@ SINGLE_BATTLE_TEST("Poison Point inflicts poison on contact") } } } + +SINGLE_BATTLE_TEST("Poison Point triggers 30% of the time") +{ + PASSES_RANDOMLY(3, 10, RNG_POISON_POINT); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_4); + ASSUME(gMovesInfo[MOVE_TACKLE].makesContact); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_NIDORAN_M) { Ability(ABILITY_POISON_POINT); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ABILITY_POISON_POINT); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + MESSAGE("Wobbuffet was poisoned by Foe Nidoran♂'s Poison Point!"); + STATUS_ICON(player, poison: TRUE); + } +} diff --git a/test/battle/ability/poison_touch.c b/test/battle/ability/poison_touch.c new file mode 100644 index 0000000000..b69fa20444 --- /dev/null +++ b/test/battle/ability/poison_touch.c @@ -0,0 +1,77 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Poison Touch has a 30% chance to poison when attacking with contact moves") +{ + PASSES_RANDOMLY(3, 10, RNG_POISON_TOUCH); + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].power > 0); + ASSUME(gMovesInfo[MOVE_TACKLE].makesContact); + PLAYER(SPECIES_GRIMER) { Ability(ABILITY_POISON_TOUCH); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("Foe Wobbuffet was poisoned by Grimer's Poison Touch!"); + STATUS_ICON(opponent, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Poison Touch only applies when using contact moves") +{ + u32 move; + + PARAMETRIZE { move = MOVE_TACKLE; } + PARAMETRIZE { move = MOVE_SWIFT; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].makesContact); + ASSUME(!gMovesInfo[MOVE_SWIFT].makesContact); + PLAYER(SPECIES_GRIMER) { Ability(ABILITY_POISON_TOUCH); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + if (gMovesInfo[move].makesContact) { + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("Foe Wobbuffet was poisoned by Grimer's Poison Touch!"); + STATUS_ICON(opponent, poison: TRUE); + } else { + NONE_OF { + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("Foe Wobbuffet was poisoned by Grimer's Poison Touch!"); + STATUS_ICON(opponent, poison: TRUE); + } + } + } +} + +SINGLE_BATTLE_TEST("Poison Touch applies between multi-hit move hits") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_ARM_THRUST].effect == EFFECT_MULTI_HIT); + ASSUME(gMovesInfo[MOVE_ARM_THRUST].makesContact); + ASSUME(gItemsInfo[ITEM_PECHA_BERRY].holdEffect == HOLD_EFFECT_CURE_PSN); + PLAYER(SPECIES_GRIMER) { Ability(ABILITY_POISON_TOUCH); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_PECHA_BERRY); }; + } WHEN { + TURN { MOVE(player, MOVE_ARM_THRUST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ARM_THRUST, player); + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("Foe Wobbuffet was poisoned by Grimer's Poison Touch!"); + STATUS_ICON(opponent, poison: TRUE); + MESSAGE("Foe Wobbuffet's Pecha Berry cured poison!"); + STATUS_ICON(opponent, poison: FALSE); + ABILITY_POPUP(player, ABILITY_POISON_TOUCH); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("Foe Wobbuffet was poisoned by Grimer's Poison Touch!"); + STATUS_ICON(opponent, poison: TRUE); + } +} diff --git a/test/battle/ability/prankster.c b/test/battle/ability/prankster.c index 8c5feaee43..d5fae3edb7 100644 --- a/test/battle/ability/prankster.c +++ b/test/battle/ability/prankster.c @@ -196,7 +196,7 @@ SINGLE_BATTLE_TEST("Prankster-affected moves can still be bounced back by a Dark PLAYER(SPECIES_ABSOL) { Item(ITEM_ABSOLITE); } OPPONENT(SPECIES_VOLBEAT) { Ability(ABILITY_PRANKSTER); } } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, megaEvolve: TRUE); MOVE(opponent, MOVE_CONFUSE_RAY); } + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); MOVE(opponent, MOVE_CONFUSE_RAY); } } SCENE { MESSAGE("Foe Volbeat's Confuse Ray was bounced back by Absol's Magic Bounce!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, player); @@ -209,7 +209,7 @@ SINGLE_BATTLE_TEST("Prankster-affected moves that are bounced back by Magic Boun PLAYER(SPECIES_ABSOL) { Item(ITEM_ABSOLITE); } OPPONENT(SPECIES_MURKROW) { Ability(ABILITY_PRANKSTER); } } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, megaEvolve: TRUE); MOVE(opponent, MOVE_CONFUSE_RAY); } + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); MOVE(opponent, MOVE_CONFUSE_RAY); } } SCENE { MESSAGE("Foe Murkrow's Confuse Ray was bounced back by Absol's Magic Bounce!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, player); diff --git a/test/battle/ability/shed_skin.c b/test/battle/ability/shed_skin.c new file mode 100644 index 0000000000..d3030b9baf --- /dev/null +++ b/test/battle/ability/shed_skin.c @@ -0,0 +1,19 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Shed Skin triggers 30% of the time") +{ + PASSES_RANDOMLY(3, 10, RNG_SHED_SKIN); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_4); + ASSUME(gMovesInfo[MOVE_TACKLE].makesContact); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ARBOK) { Status1(STATUS1_POISON); Ability(ABILITY_SHED_SKIN); } + } WHEN { + TURN; + } SCENE { + ABILITY_POPUP(opponent, ABILITY_SHED_SKIN); + MESSAGE("Foe Arbok's Shed Skin cured its poison problem!"); + STATUS_ICON(opponent, poison: FALSE); + } +} diff --git a/test/battle/ability/static.c b/test/battle/ability/static.c index 0f1b4f816f..2e74db3e14 100644 --- a/test/battle/ability/static.c +++ b/test/battle/ability/static.c @@ -29,3 +29,21 @@ SINGLE_BATTLE_TEST("Static inflicts paralysis on contact") } } } + +SINGLE_BATTLE_TEST("Static triggers 30% of the time") +{ + PASSES_RANDOMLY(3, 10, RNG_STATIC); + GIVEN { + ASSUME(B_ABILITY_TRIGGER_CHANCE >= GEN_4); + ASSUME(gMovesInfo[MOVE_TACKLE].makesContact); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_PIKACHU) { Ability(ABILITY_STATIC); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_STATIC); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, player); + MESSAGE("Foe Pikachu's Static paralyzed Wobbuffet! It may be unable to move!"); + STATUS_ICON(player, paralysis: TRUE); + } +} diff --git a/test/battle/ability/sticky_hold.c b/test/battle/ability/sticky_hold.c new file mode 100644 index 0000000000..2c7cc09535 --- /dev/null +++ b/test/battle/ability/sticky_hold.c @@ -0,0 +1,18 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Sticky Hold prevents item theft") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_THIEF, MOVE_EFFECT_STEAL_ITEM)); + PLAYER(SPECIES_URSALUNA) { Item(ITEM_NONE); } + OPPONENT(SPECIES_GASTRODON) { Ability(ABILITY_STICKY_HOLD); Item(ITEM_LIFE_ORB); } + } WHEN { + TURN { MOVE(player, MOVE_THIEF); } + } SCENE { + MESSAGE("Ursaluna used Thief!"); + ABILITY_POPUP(opponent, ABILITY_STICKY_HOLD); + MESSAGE("Foe Gastrodon's Sticky Hold made Thief ineffective!"); + } +} + diff --git a/test/battle/ability/supreme_overlord.c b/test/battle/ability/supreme_overlord.c index fcaca96685..dcaaa494a0 100644 --- a/test/battle/ability/supreme_overlord.c +++ b/test/battle/ability/supreme_overlord.c @@ -107,9 +107,9 @@ SINGLE_BATTLE_TEST("Supreme Overlord's message displays correctly after all batt ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, opponent); // Everyone faints. SEND_IN_MESSAGE("Kingambit"); + MESSAGE("2 sent out Wobbuffet!"); ABILITY_POPUP(player, ABILITY_SUPREME_OVERLORD); MESSAGE("Kingambit gained strength from the fallen!"); - MESSAGE("2 sent out Wobbuffet!"); } } diff --git a/test/battle/ability/switch_in_abilities.c b/test/battle/ability/switch_in_abilities.c new file mode 100644 index 0000000000..79cf2b2dc9 --- /dev/null +++ b/test/battle/ability/switch_in_abilities.c @@ -0,0 +1,128 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Switch-in abilities trigger in Speed Order at the battle's start - Single Battle") +{ + u32 spdPlayer, spdOpponent; + + PARAMETRIZE { spdPlayer = 5; spdOpponent = 1; } + PARAMETRIZE { spdOpponent = 5; spdPlayer = 1; } + + GIVEN { + PLAYER(SPECIES_EKANS) { Speed(spdPlayer); Ability(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_NINETALES) { Speed(spdOpponent); Ability(ABILITY_DROUGHT); } + } WHEN { + TURN { ; } + } SCENE { + if (spdPlayer > spdOpponent) { + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + } else { + ABILITY_POPUP(opponent, ABILITY_DROUGHT); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + } + } +} + +DOUBLE_BATTLE_TEST("Switch-in abilities trigger in Speed Order at the battle's start - Double Battle") +{ + u32 spdPlayer1, spdPlayer2, spdOpponent1, spdOpponent2; + + PARAMETRIZE { spdPlayer1 = 5; spdPlayer2 = 4; spdOpponent1 = 3; spdOpponent2 = 2; } + PARAMETRIZE { spdPlayer1 = 2; spdPlayer2 = 3; spdOpponent1 = 4; spdOpponent2 = 5; } + PARAMETRIZE { spdPlayer1 = 4; spdPlayer2 = 3; spdOpponent1 = 5; spdOpponent2 = 2; } + + GIVEN { + PLAYER(SPECIES_KYOGRE) { Speed(spdPlayer1); Ability(ABILITY_DRIZZLE); } + PLAYER(SPECIES_GYARADOS) { Speed(spdPlayer2); Ability(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_PORYGON2) { Speed(spdOpponent1); Ability(ABILITY_DOWNLOAD); } + OPPONENT(SPECIES_PINSIR) { Speed(spdOpponent2); Ability(ABILITY_MOLD_BREAKER); } + } WHEN { + TURN { ; } + } SCENE { + if (spdPlayer1 == 5) { + ABILITY_POPUP(playerLeft, ABILITY_DRIZZLE); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponentLeft, ABILITY_DOWNLOAD); + ABILITY_POPUP(opponentRight, ABILITY_MOLD_BREAKER); + } else if (spdOpponent2 == 5) { + ABILITY_POPUP(opponentRight, ABILITY_MOLD_BREAKER); + ABILITY_POPUP(opponentLeft, ABILITY_DOWNLOAD); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(playerLeft, ABILITY_DRIZZLE); + } else { + ABILITY_POPUP(opponentLeft, ABILITY_DOWNLOAD); + ABILITY_POPUP(playerLeft, ABILITY_DRIZZLE); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponentRight, ABILITY_MOLD_BREAKER); + } + } +} + +SINGLE_BATTLE_TEST("Switch-in abilities trigger in Speed Order after post-KO switch - Single Battle") +{ + u32 spdPlayer, spdOpponent; + + PARAMETRIZE { spdPlayer = 5; spdOpponent = 1; } + PARAMETRIZE { spdOpponent = 5; spdPlayer = 1; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + PLAYER(SPECIES_EKANS) { Speed(spdPlayer); Ability(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + OPPONENT(SPECIES_PORYGON2) { Speed(spdOpponent); Ability(ABILITY_DOWNLOAD); } + } WHEN { + TURN { MOVE(player, MOVE_EXPLOSION); SEND_OUT(player, 1); SEND_OUT(opponent, 1); } + TURN { ; } + } SCENE { + MESSAGE("Wobbuffet used Explosion!"); + if (spdPlayer > spdOpponent) { + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponent, ABILITY_DOWNLOAD); + } else { + ABILITY_POPUP(opponent, ABILITY_DOWNLOAD); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + } + } +} + +DOUBLE_BATTLE_TEST("Switch-in abilities trigger in Speed Order after post-KO switch - Double Battle") +{ + u32 spdPlayer1, spdPlayer2, spdOpponent1, spdOpponent2; + + PARAMETRIZE { spdPlayer1 = 5; spdPlayer2 = 4; spdOpponent1 = 3; spdOpponent2 = 2; } + PARAMETRIZE { spdPlayer1 = 2; spdPlayer2 = 3; spdOpponent1 = 4; spdOpponent2 = 5; } + PARAMETRIZE { spdPlayer1 = 4; spdPlayer2 = 3; spdOpponent1 = 5; spdOpponent2 = 2; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + PLAYER(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + PLAYER(SPECIES_TYRANITAR) { Speed(spdPlayer1); Ability(ABILITY_SAND_STREAM); } + PLAYER(SPECIES_GYARADOS) { Speed(spdPlayer2); Ability(ABILITY_INTIMIDATE); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(1); } + OPPONENT(SPECIES_WEEZING_GALARIAN) { Speed(spdOpponent1); Ability(ABILITY_MISTY_SURGE); } + OPPONENT(SPECIES_VULPIX_ALOLAN) { Speed(spdOpponent2); Ability(ABILITY_SNOW_WARNING); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_EXPLOSION); SEND_OUT(playerLeft, 2); SEND_OUT(opponentLeft, 2); SEND_OUT(playerRight, 3); SEND_OUT(opponentRight, 3); } + TURN { ; } + } SCENE { + MESSAGE("Wobbuffet used Explosion!"); + if (spdPlayer1 == 5) { + ABILITY_POPUP(playerLeft, ABILITY_SAND_STREAM); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponentLeft, ABILITY_MISTY_SURGE); + ABILITY_POPUP(opponentRight, ABILITY_SNOW_WARNING); + } else if (spdOpponent2 == 5) { + ABILITY_POPUP(opponentRight, ABILITY_SNOW_WARNING); + ABILITY_POPUP(opponentLeft, ABILITY_MISTY_SURGE); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(playerLeft, ABILITY_SAND_STREAM); + } else { + ABILITY_POPUP(opponentLeft, ABILITY_MISTY_SURGE); + ABILITY_POPUP(playerLeft, ABILITY_SAND_STREAM); + ABILITY_POPUP(playerRight, ABILITY_INTIMIDATE); + ABILITY_POPUP(opponentRight, ABILITY_SNOW_WARNING); + } + } +} diff --git a/test/battle/ability/sword_of_ruin.c b/test/battle/ability/sword_of_ruin.c index ae3e4c73a9..02eec69ed8 100644 --- a/test/battle/ability/sword_of_ruin.c +++ b/test/battle/ability/sword_of_ruin.c @@ -46,9 +46,9 @@ SINGLE_BATTLE_TEST("Sword of Ruin's message displays correctly after all battler ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, opponent); // Everyone faints. MESSAGE("Go! Chien-Pao!"); + MESSAGE("2 sent out Wobbuffet!"); ABILITY_POPUP(player, ABILITY_SWORD_OF_RUIN); MESSAGE("Chien-Pao's Sword of Ruin weakened the Defense of all surrounding Pokémon!"); - MESSAGE("2 sent out Wobbuffet!"); } } diff --git a/test/battle/ability/tablets_of_ruin.c b/test/battle/ability/tablets_of_ruin.c index c8d24f5d92..7862a0bb8c 100644 --- a/test/battle/ability/tablets_of_ruin.c +++ b/test/battle/ability/tablets_of_ruin.c @@ -46,9 +46,9 @@ SINGLE_BATTLE_TEST("Tablets of Ruin's message displays correctly after all battl ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, opponent); // Everyone faints. MESSAGE("Go! Wo-Chien!"); + MESSAGE("2 sent out Wobbuffet!"); ABILITY_POPUP(player, ABILITY_TABLETS_OF_RUIN); MESSAGE("Wo-Chien's Tablets of Ruin weakened the Attack of all surrounding Pokémon!"); - MESSAGE("2 sent out Wobbuffet!"); } } diff --git a/test/battle/ability/toxic_chain.c b/test/battle/ability/toxic_chain.c new file mode 100644 index 0000000000..2c29770f52 --- /dev/null +++ b/test/battle/ability/toxic_chain.c @@ -0,0 +1,112 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Toxic Chain inflicts bad poison when attacking") +{ + PASSES_RANDOMLY(3, 10, RNG_TOXIC_CHAIN); + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].category != DAMAGE_CATEGORY_STATUS); + ASSUME(gMovesInfo[MOVE_TACKLE].power > 0); + PLAYER(SPECIES_OKIDOGI) { Ability(ABILITY_TOXIC_CHAIN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_TOXIC_CHAIN); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("Foe Wobbuffet is badly poisoned!"); + STATUS_ICON(opponent, badPoison: TRUE); + } THEN { + EXPECT(opponent->status1 & STATUS1_TOXIC_POISON); + } +} + +SINGLE_BATTLE_TEST("Toxic Chain inflicts bad poison on any hit of a multi-hit move") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_DOUBLE_SLAP].category != DAMAGE_CATEGORY_STATUS); + ASSUME(gMovesInfo[MOVE_DOUBLE_SLAP].effect == EFFECT_MULTI_HIT); + ASSUME(gMovesInfo[MOVE_DOUBLE_SLAP].power > 0); + ASSUME(gItemsInfo[ITEM_PECHA_BERRY].holdEffect == HOLD_EFFECT_CURE_PSN); + PLAYER(SPECIES_OKIDOGI) { Ability(ABILITY_TOXIC_CHAIN); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_PECHA_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_DOUBLE_SLAP); } + } SCENE { + ABILITY_POPUP(player, ABILITY_TOXIC_CHAIN); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("Foe Wobbuffet is badly poisoned!"); + STATUS_ICON(opponent, badPoison: TRUE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + STATUS_ICON(opponent, badPoison: FALSE); + ABILITY_POPUP(player, ABILITY_TOXIC_CHAIN); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("Foe Wobbuffet is badly poisoned!"); + STATUS_ICON(opponent, badPoison: TRUE); + } THEN { + EXPECT(opponent->status1 & STATUS1_TOXIC_POISON); + } +} + +DOUBLE_BATTLE_TEST("Toxic Chain can inflict bad poison on both foes") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_RAZOR_LEAF].category != DAMAGE_CATEGORY_STATUS); + ASSUME(gMovesInfo[MOVE_RAZOR_LEAF].target == MOVE_TARGET_BOTH); + ASSUME(gMovesInfo[MOVE_RAZOR_LEAF].power > 0); + PLAYER(SPECIES_OKIDOGI) { Ability(ABILITY_TOXIC_CHAIN); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_RAZOR_LEAF, WITH_RNG(RNG_TOXIC_CHAIN, TRUE)); } + } SCENE { + HP_BAR(opponentLeft); + ABILITY_POPUP(playerLeft, ABILITY_TOXIC_CHAIN); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponentLeft); + MESSAGE("Foe Wobbuffet is badly poisoned!"); + STATUS_ICON(opponentLeft, badPoison: TRUE); + HP_BAR(opponentRight); + ABILITY_POPUP(playerLeft, ABILITY_TOXIC_CHAIN); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponentRight); + MESSAGE("Foe Wynaut is badly poisoned!"); + STATUS_ICON(opponentRight, badPoison: TRUE); + } THEN { + EXPECT(opponentLeft->status1 & STATUS1_TOXIC_POISON); + EXPECT(opponentRight->status1 & STATUS1_TOXIC_POISON); + } +} + +SINGLE_BATTLE_TEST("Toxic Chain makes Lum/Pecha Berry trigger before being knocked off") +{ + u16 item = 0; + + PARAMETRIZE { item = ITEM_PECHA_BERRY; } + PARAMETRIZE { item = ITEM_LUM_BERRY; } + + GIVEN { + ASSUME(gMovesInfo[MOVE_KNOCK_OFF].category != DAMAGE_CATEGORY_STATUS); + ASSUME(gMovesInfo[MOVE_KNOCK_OFF].effect == EFFECT_KNOCK_OFF); + ASSUME(gMovesInfo[MOVE_KNOCK_OFF].power > 0); + ASSUME(gItemsInfo[ITEM_PECHA_BERRY].holdEffect == HOLD_EFFECT_CURE_PSN); + ASSUME(gItemsInfo[ITEM_LUM_BERRY].holdEffect == HOLD_EFFECT_CURE_STATUS); + PLAYER(SPECIES_OKIDOGI) { Ability(ABILITY_TOXIC_CHAIN); } + OPPONENT(SPECIES_WOBBUFFET) { Item(item); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF, WITH_RNG(RNG_TOXIC_CHAIN, TRUE)); } + } SCENE { + ABILITY_POPUP(player, ABILITY_TOXIC_CHAIN); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + MESSAGE("Foe Wobbuffet is badly poisoned!"); + STATUS_ICON(opponent, badPoison: TRUE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + STATUS_ICON(opponent, badPoison: FALSE); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); + MESSAGE("Okidogi knocked off Foe Wobbuffet's Pecha Berry!"); + MESSAGE("Okidogi knocked off Foe Wobbuffet's Lum Berry!"); + } + } THEN { + EXPECT(opponent->status1 == 0); + } +} diff --git a/test/battle/ability/vessel_of_ruin.c b/test/battle/ability/vessel_of_ruin.c index 723c83bdf9..362b44c003 100644 --- a/test/battle/ability/vessel_of_ruin.c +++ b/test/battle/ability/vessel_of_ruin.c @@ -46,9 +46,9 @@ SINGLE_BATTLE_TEST("Vessel of Ruin's message displays correctly after all battle ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, opponent); // Everyone faints. MESSAGE("Go! Ting-Lu!"); + MESSAGE("2 sent out Wobbuffet!"); ABILITY_POPUP(player, ABILITY_VESSEL_OF_RUIN); MESSAGE("Ting-Lu's Vessel of Ruin weakened the Sp. Atk of all surrounding Pokémon!"); - MESSAGE("2 sent out Wobbuffet!"); } } diff --git a/test/battle/ability/white_smoke.c b/test/battle/ability/white_smoke.c index 7bedbca242..4cb1687141 100644 --- a/test/battle/ability/white_smoke.c +++ b/test/battle/ability/white_smoke.c @@ -1,39 +1,4 @@ #include "global.h" #include "test/battle.h" -SINGLE_BATTLE_TEST("White Smoke prevents intimidate") -{ - s16 turnOneHit; - s16 turnTwoHit; - - GIVEN { - PLAYER(SPECIES_EKANS) { Ability(ABILITY_SHED_SKIN); }; - PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); }; - OPPONENT(SPECIES_TORKOAL) { Ability(ABILITY_WHITE_SMOKE); }; - } WHEN { - TURN { MOVE(opponent, MOVE_TACKLE); } - TURN { SWITCH(player, 1); MOVE(opponent, MOVE_TACKLE); } - - } SCENE { - HP_BAR(player, captureDamage: &turnOneHit); - ABILITY_POPUP(player, ABILITY_INTIMIDATE); - NONE_OF { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); } - ABILITY_POPUP(opponent, ABILITY_WHITE_SMOKE); - MESSAGE("Foe Torkoal's White Smoke prevents stat loss!"); - HP_BAR(player, captureDamage: &turnTwoHit); - } THEN { - EXPECT_EQ(turnOneHit, turnTwoHit); - } -} - - -TO_DO_BATTLE_TEST("White Smoke prevents stat stage reduction from moves"); // Growl, Leer, Confide, Fake Tears, Scary Face, Sweet Scent, Sand Attack (Attack, Defense, Sp. Attack, Sp. Defense, Speed, Evasion, Accuracy -TO_DO_BATTLE_TEST("White Smoke prevents Sticky Web"); -TO_DO_BATTLE_TEST("White Smoke doesn't prevent stat stage reduction from moves used by the user"); // e.g. Superpower -TO_DO_BATTLE_TEST("White Smoke doesn't prevent Speed reduction from Iron Ball"); -TO_DO_BATTLE_TEST("White Smoke doesn't prevent Speed reduction from paralysis"); -TO_DO_BATTLE_TEST("White Smoke doesn't prevent Attack reduction from burn"); -TO_DO_BATTLE_TEST("White Smoke doesn't prevent receiving negative stat changes from Baton Pass"); -TO_DO_BATTLE_TEST("White Smoke doesn't prevent Topsy-Turvy"); -TO_DO_BATTLE_TEST("White Smoke doesn't prevent Spectral Thief from resetting positive stat changes"); -TO_DO_BATTLE_TEST("White Smoke is ignored by Mold Breaker"); +// Tests for White Smoke are handled in test/battle/ability/clear_body.c diff --git a/test/battle/ability/zero_to_hero.c b/test/battle/ability/zero_to_hero.c index 1fd8e9c7e1..be72a55a7e 100644 --- a/test/battle/ability/zero_to_hero.c +++ b/test/battle/ability/zero_to_hero.c @@ -153,9 +153,9 @@ SINGLE_BATTLE_TEST("Zero to Hero's message displays correctly after all battlers ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, opponent); // Everyone faints. SEND_IN_MESSAGE("Palafin"); + MESSAGE("2 sent out Wobbuffet!"); ABILITY_POPUP(player, ABILITY_ZERO_TO_HERO); MESSAGE("Palafin underwent a heroic transformation!"); - MESSAGE("2 sent out Wobbuffet!"); } } diff --git a/test/battle/ai.c b/test/battle/ai/ai.c similarity index 99% rename from test/battle/ai.c rename to test/battle/ai/ai.c index 48eee5c051..a19481c7ed 100644 --- a/test/battle/ai.c +++ b/test/battle/ai/ai.c @@ -402,9 +402,9 @@ AI_DOUBLE_BATTLE_TEST("AI will not use Helping Hand if partner does not have any { u16 move1 = MOVE_NONE, move2 = MOVE_NONE, move3 = MOVE_NONE, move4 = MOVE_NONE; - PARAMETRIZE{ move1 = MOVE_LEER; move2 = MOVE_TOXIC; } - PARAMETRIZE{ move1 = MOVE_HELPING_HAND; move2 = MOVE_PROTECT; } - PARAMETRIZE{ move1 = MOVE_ACUPRESSURE; move2 = MOVE_DOUBLE_TEAM; move3 = MOVE_TOXIC; move4 = MOVE_PROTECT; } + PARAMETRIZE { move1 = MOVE_LEER; move2 = MOVE_TOXIC; } + PARAMETRIZE { move1 = MOVE_HELPING_HAND; move2 = MOVE_PROTECT; } + PARAMETRIZE { move1 = MOVE_ACUPRESSURE; move2 = MOVE_DOUBLE_TEAM; move3 = MOVE_TOXIC; move4 = MOVE_PROTECT; } GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); @@ -431,7 +431,7 @@ AI_DOUBLE_BATTLE_TEST("AI will not use a status move if partner already chose He for (j = MOVE_NONE + 1; j < MOVES_COUNT; j++) { if (gMovesInfo[j].category == DAMAGE_CATEGORY_STATUS) { - PARAMETRIZE{ statusMove = j; } + PARAMETRIZE { statusMove = j; } } } diff --git a/test/battle/ai_calc_best_move_score.c b/test/battle/ai/ai_calc_best_move_score.c similarity index 100% rename from test/battle/ai_calc_best_move_score.c rename to test/battle/ai/ai_calc_best_move_score.c diff --git a/test/battle/ai_check_viability.c b/test/battle/ai/ai_check_viability.c similarity index 100% rename from test/battle/ai_check_viability.c rename to test/battle/ai/ai_check_viability.c diff --git a/test/battle/ai/ai_choice.c b/test/battle/ai/ai_choice.c new file mode 100644 index 0000000000..c9992d2cab --- /dev/null +++ b/test/battle/ai/ai_choice.c @@ -0,0 +1,181 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gItemsInfo[ITEM_CHOICE_SPECS].holdEffect == HOLD_EFFECT_CHOICE_SPECS); + ASSUME(gItemsInfo[ITEM_CHOICE_BAND].holdEffect == HOLD_EFFECT_CHOICE_BAND); + ASSUME(gItemsInfo[ITEM_CHOICE_SCARF].holdEffect == HOLD_EFFECT_CHOICE_SCARF); +} + +AI_SINGLE_BATTLE_TEST("Choiced Pokémon switch out after using a status move once") +{ + u32 j, ability = ABILITY_NONE, heldItem = ITEM_NONE; + + static const u32 choiceItems[] = { + ITEM_CHOICE_SPECS, + ITEM_CHOICE_BAND, + ITEM_CHOICE_SCARF, + }; + + for (j = 0; j < ARRAY_COUNT(choiceItems); j++) + { + PARAMETRIZE { ability = ABILITY_NONE; heldItem = choiceItems[j]; } + PARAMETRIZE { ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; } + } + + GIVEN { + ASSUME(gMovesInfo[MOVE_YAWN].category == DAMAGE_CATEGORY_STATUS); + ASSUME(gMovesInfo[MOVE_YAWN].effect == EFFECT_YAWN); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_RHYDON) + OPPONENT(SPECIES_LOPUNNY) { Moves(MOVE_YAWN, MOVE_TACKLE); Item(heldItem); Ability(ability); } + OPPONENT(SPECIES_SWAMPERT) { Moves(MOVE_WATERFALL); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_YAWN); } + if (ability == ABILITY_KLUTZ) { // Klutz ignores item + TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); } + } + else { + TURN { EXPECT_SWITCH(opponent, 1); } + } + } +} + +AI_SINGLE_BATTLE_TEST("Choiced Pokémon won't use stat boosting moves") +{ + // Moves defined by MOVE_TARGET_USER (with exceptions?) + u32 j, ability = ABILITY_NONE, heldItem = ITEM_NONE; + + static const u32 choiceItems[] = { + ITEM_CHOICE_SPECS, + ITEM_CHOICE_BAND, + ITEM_CHOICE_SCARF, + }; + + for (j = 0; j < ARRAY_COUNT(choiceItems); j++) + { + PARAMETRIZE { ability = ABILITY_NONE; heldItem = choiceItems[j]; } + PARAMETRIZE { ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; } + } + + GIVEN { + ASSUME(gMovesInfo[MOVE_SWORDS_DANCE].target == MOVE_TARGET_USER); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_RHYDON) + OPPONENT(SPECIES_LOPUNNY) { Moves(MOVE_SWORDS_DANCE, MOVE_TACKLE); Item(heldItem); Ability(ability); } + OPPONENT(SPECIES_SWAMPERT) { Moves(MOVE_WATERFALL); } + } WHEN { + if (ability == ABILITY_KLUTZ) { // Klutz ignores item + TURN { EXPECT_MOVE(opponent, MOVE_SWORDS_DANCE); } + } + else { + TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); } + } + } +} + +AI_SINGLE_BATTLE_TEST("Choiced Pokémon won't use status move if they are the only party member") +{ + u32 j, ability = ABILITY_NONE, isAlive = 0, heldItem = ITEM_NONE; + + static const u32 choiceItems[] = { + ITEM_CHOICE_SPECS, + ITEM_CHOICE_BAND, + ITEM_CHOICE_SCARF, + }; + + for (j = 0; j < ARRAY_COUNT(choiceItems); j++) + { + PARAMETRIZE { ability = ABILITY_NONE; heldItem = choiceItems[j]; isAlive = 0; } + PARAMETRIZE { ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; isAlive = 0; } + PARAMETRIZE { ability = ABILITY_NONE; heldItem = choiceItems[j]; isAlive = 1; } + PARAMETRIZE { ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; isAlive = 1; } + } + + GIVEN { + ASSUME(gMovesInfo[MOVE_YAWN].category == DAMAGE_CATEGORY_STATUS); + ASSUME(gMovesInfo[MOVE_YAWN].effect == EFFECT_YAWN); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_RHYDON) + OPPONENT(SPECIES_LOPUNNY) { Moves(MOVE_YAWN, MOVE_TACKLE); Item(heldItem); Ability(ability); } + OPPONENT(SPECIES_SWAMPERT) { HP(isAlive); Moves(MOVE_WATERFALL); } + } WHEN { + if (isAlive == 1 || ability == ABILITY_KLUTZ) { + TURN { EXPECT_MOVE(opponent, MOVE_YAWN); } + } + else { + TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); } + } + } +} + +AI_SINGLE_BATTLE_TEST("Choiced Pokémon won't use status move if they don't have a good switchin") +{ + u32 j, ability = ABILITY_NONE, move = MOVE_NONE, species = SPECIES_NONE, heldItem = ITEM_NONE; + + static const u32 choiceItems[] = { + ITEM_CHOICE_SPECS, + ITEM_CHOICE_BAND, + ITEM_CHOICE_SCARF, + }; + + for (j = 0; j < ARRAY_COUNT(choiceItems); j++) + { + PARAMETRIZE { ability = ABILITY_NONE; heldItem = choiceItems[j]; species = SPECIES_SWAMPERT; move = MOVE_WATERFALL; } + PARAMETRIZE { ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; species = SPECIES_SWAMPERT; move = MOVE_WATERFALL; } + PARAMETRIZE { ability = ABILITY_NONE; heldItem = choiceItems[j]; species = SPECIES_ELEKID; move = MOVE_THUNDER_WAVE; } + PARAMETRIZE { ability = ABILITY_KLUTZ; heldItem = choiceItems[j]; species = SPECIES_ELEKID; move = MOVE_THUNDER_WAVE; } + } + + GIVEN { + ASSUME(gMovesInfo[MOVE_YAWN].category == DAMAGE_CATEGORY_STATUS); + ASSUME(gMovesInfo[MOVE_YAWN].effect == EFFECT_YAWN); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_RHYDON) + OPPONENT(SPECIES_LOPUNNY) { Moves(MOVE_YAWN, MOVE_TACKLE); Item(heldItem); Ability(ability); } + OPPONENT(species) { Moves(move); } + } WHEN { + if (species == SPECIES_SWAMPERT || ability == ABILITY_KLUTZ) { + TURN { EXPECT_MOVE(opponent, MOVE_YAWN); } + } + else { + TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); } + } + } +} + +AI_SINGLE_BATTLE_TEST("Choiced Pokémon won't use status move if they are trapped") +{ + u32 j, aiAbility = ABILITY_NONE, playerAbility = MOVE_NONE, species = SPECIES_NONE, heldItem = ITEM_NONE; + + static const u32 choiceItems[] = { + ITEM_CHOICE_SPECS, + ITEM_CHOICE_BAND, + ITEM_CHOICE_SCARF, + }; + + for (j = 0; j < ARRAY_COUNT(choiceItems); j++) + { + PARAMETRIZE { aiAbility = ABILITY_NONE; heldItem = choiceItems[j]; species = SPECIES_RHYDON; playerAbility = ABILITY_LIGHTNING_ROD; } + PARAMETRIZE { aiAbility = ABILITY_KLUTZ; heldItem = choiceItems[j]; species = SPECIES_RHYDON; playerAbility = ABILITY_LIGHTNING_ROD; } + PARAMETRIZE { aiAbility = ABILITY_NONE; heldItem = choiceItems[j]; species = SPECIES_DUGTRIO; playerAbility = ABILITY_ARENA_TRAP; } + PARAMETRIZE { aiAbility = ABILITY_KLUTZ; heldItem = choiceItems[j]; species = SPECIES_DUGTRIO; playerAbility = ABILITY_ARENA_TRAP; } + } + + GIVEN { + ASSUME(gMovesInfo[MOVE_YAWN].category == DAMAGE_CATEGORY_STATUS); + ASSUME(gMovesInfo[MOVE_YAWN].effect == EFFECT_YAWN); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(species) { Ability(playerAbility); } + OPPONENT(SPECIES_LOPUNNY) { Moves(MOVE_YAWN, MOVE_TACKLE); Item(heldItem); Ability(aiAbility); } + OPPONENT(SPECIES_SWAMPERT) { Moves(MOVE_WATERFALL); } + } WHEN { + if (playerAbility != ABILITY_ARENA_TRAP || aiAbility == ABILITY_KLUTZ) { + TURN { EXPECT_MOVE(opponent, MOVE_YAWN); } + } + else { + TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); } + } + } +} diff --git a/test/battle/ai_flag_risky.c b/test/battle/ai/ai_flag_risky.c similarity index 89% rename from test/battle/ai_flag_risky.c rename to test/battle/ai/ai_flag_risky.c index 4c5d89205a..e6156de5a0 100644 --- a/test/battle/ai_flag_risky.c +++ b/test/battle/ai/ai_flag_risky.c @@ -5,8 +5,8 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: AI will blindly Mirror Coat against specia { u32 aiRiskyFlag = 0; - PARAMETRIZE{ aiRiskyFlag = 0; } - PARAMETRIZE{ aiRiskyFlag = AI_FLAG_RISKY; } + PARAMETRIZE { aiRiskyFlag = 0; } + PARAMETRIZE { aiRiskyFlag = AI_FLAG_RISKY; } GIVEN { ASSUME(gMovesInfo[MOVE_MIRROR_COAT].effect == EFFECT_MIRROR_COAT); @@ -24,8 +24,8 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: AI will blindly Counter against physical a { u32 aiRiskyFlag = 0; - PARAMETRIZE{ aiRiskyFlag = 0; } - PARAMETRIZE{ aiRiskyFlag = AI_FLAG_RISKY; } + PARAMETRIZE { aiRiskyFlag = 0; } + PARAMETRIZE { aiRiskyFlag = AI_FLAG_RISKY; } GIVEN { ASSUME(gMovesInfo[MOVE_COUNTER].effect == EFFECT_COUNTER); @@ -43,8 +43,8 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: AI will prioritize Revenge if slower") { u32 aiRiskyFlag = 0; - PARAMETRIZE{ aiRiskyFlag = 0; } - PARAMETRIZE{ aiRiskyFlag = AI_FLAG_RISKY; } + PARAMETRIZE { aiRiskyFlag = 0; } + PARAMETRIZE { aiRiskyFlag = AI_FLAG_RISKY; } GIVEN { ASSUME(gMovesInfo[MOVE_REVENGE].effect == EFFECT_REVENGE); @@ -60,8 +60,8 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: Mid-battle switches prioritize offensive o { u32 aiRiskyFlag = 0; - PARAMETRIZE{ aiRiskyFlag = 0; } - PARAMETRIZE{ aiRiskyFlag = AI_FLAG_RISKY; } + PARAMETRIZE { aiRiskyFlag = 0; } + PARAMETRIZE { aiRiskyFlag = AI_FLAG_RISKY; } GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | aiRiskyFlag); @@ -78,8 +78,8 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: AI prefers high damage moves at the expens { u32 aiRiskyFlag = 0; - PARAMETRIZE{ aiRiskyFlag = 0; } - PARAMETRIZE{ aiRiskyFlag = AI_FLAG_RISKY; } + PARAMETRIZE { aiRiskyFlag = 0; } + PARAMETRIZE { aiRiskyFlag = AI_FLAG_RISKY; } GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiRiskyFlag); diff --git a/test/battle/ai/ai_flag_sequence_switching.c b/test/battle/ai/ai_flag_sequence_switching.c new file mode 100644 index 0000000000..1b4a264a24 --- /dev/null +++ b/test/battle/ai/ai_flag_sequence_switching.c @@ -0,0 +1,129 @@ +#include "global.h" +#include "test/battle.h" + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SEQUENCE_SWITCHING: AI will always switch after a KO in exactly party order") +{ + u32 aiSequenceSwitchingFlag = 0; + + PARAMETRIZE { aiSequenceSwitchingFlag = 0; } + PARAMETRIZE { aiSequenceSwitchingFlag = AI_FLAG_SEQUENCE_SWITCHING; } + + GIVEN { + 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(5); } + OPPONENT(SPECIES_MACHOKE) { Level(5); } + OPPONENT(SPECIES_MACHAMP) { Level(5); } + OPPONENT(SPECIES_MANKEY) { Level(5); } + OPPONENT(SPECIES_PRIMEAPE) { Level(5); } + OPPONENT(SPECIES_MAGNEZONE) { Level(100); } + } WHEN { + TURN { MOVE(player, MOVE_WING_ATTACK); } + if (aiSequenceSwitchingFlag) { + TURN { MOVE(player, MOVE_WING_ATTACK); } + TURN { MOVE(player, MOVE_WING_ATTACK); } + TURN { MOVE(player, MOVE_WING_ATTACK); } + TURN { MOVE(player, MOVE_WING_ATTACK); } + } + } SCENE { + if (aiSequenceSwitchingFlag) { + MESSAGE("{PKMN} TRAINER LEAF sent out Machoke!"); + MESSAGE("{PKMN} TRAINER LEAF sent out Machamp!"); + MESSAGE("{PKMN} TRAINER LEAF sent out Mankey!"); + MESSAGE("{PKMN} TRAINER LEAF sent out Primeape!"); + MESSAGE("{PKMN} TRAINER LEAF sent out Magnezone!"); + } + else { + MESSAGE("{PKMN} TRAINER LEAF sent out Magnezone!"); + } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SEQUENCE_SWITCHING: Roar and Dragon Tail still force switch to random party member") +{ + u32 move; + + PARAMETRIZE { move = MOVE_ROAR; } + PARAMETRIZE {move = MOVE_DRAGON_TAIL; } + + PASSES_RANDOMLY(1, 2, RNG_FORCE_RANDOM_SWITCH); + GIVEN { + ASSUME(gMovesInfo[MOVE_ROAR].effect == EFFECT_ROAR); + ASSUME(gMovesInfo[MOVE_DRAGON_TAIL].effect == EFFECT_HIT_SWITCH_TARGET); + AI_FLAGS(AI_FLAG_SEQUENCE_SWITCHING); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_CHARMANDER); + OPPONENT(SPECIES_SQUIRTLE) { HP(0); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + MESSAGE("Foe Bulbasaur was dragged out!"); + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SEQUENCE_SWITCHING: AI will always switch into lowest party index after U-Turn, Parting Shot, and Baton Pass") +{ + u32 j, aiSequenceSwitchingFlag = 0, move = MOVE_NONE; + + static const u32 switchMoves[] = { + MOVE_U_TURN, + MOVE_PARTING_SHOT, + MOVE_BATON_PASS, + }; + + for (j = 0; j < ARRAY_COUNT(switchMoves); j++) + { + PARAMETRIZE { aiSequenceSwitchingFlag = 0; move = switchMoves[j]; } + PARAMETRIZE { aiSequenceSwitchingFlag = AI_FLAG_SEQUENCE_SWITCHING; move = switchMoves[j]; } + } + + GIVEN { + 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); + 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); } + OPPONENT(SPECIES_MACHOP) { Level(2); Moves(move); } + OPPONENT(SPECIES_MACHOP) { Level(3); Moves(move); } + OPPONENT(SPECIES_MACHOP) { Level(4); Moves(move); } + OPPONENT(SPECIES_MACHOP) { Level(5); Moves(move); } + OPPONENT(SPECIES_MAGNEZONE) { Level(100); Moves(move); } + } WHEN { + if (aiSequenceSwitchingFlag) { + TURN { EXPECT_MOVE(opponent, move) ; EXPECT_SEND_OUT(opponent, 1); } + TURN { EXPECT_MOVE(opponent, move) ; EXPECT_SEND_OUT(opponent, 0); } + TURN { EXPECT_MOVE(opponent, move) ; EXPECT_SEND_OUT(opponent, 1); } + TURN { EXPECT_MOVE(opponent, move) ; EXPECT_SEND_OUT(opponent, 0); } + TURN { EXPECT_MOVE(opponent, move) ; EXPECT_SEND_OUT(opponent, 1); } + } + else { + TURN { EXPECT_MOVE(opponent, move) ; EXPECT_SEND_OUT(opponent, 5); } + } + } +} + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SEQUENCE_SWITCHING: AI will not switch mid-battle") +{ + u32 aiSequenceSwitchingFlag = 0; + + PARAMETRIZE { aiSequenceSwitchingFlag = 0; } + PARAMETRIZE { aiSequenceSwitchingFlag = AI_FLAG_SEQUENCE_SWITCHING; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSequenceSwitchingFlag); + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_LICK); } + OPPONENT(SPECIES_GASTLY) { Moves(MOVE_SHADOW_BALL); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_KARATE_CHOP); } + } WHEN { + if (aiSequenceSwitchingFlag == AI_FLAG_SEQUENCE_SWITCHING) { + TURN { MOVE(player, MOVE_LICK) ; EXPECT_MOVE(opponent, MOVE_SHADOW_BALL); } + } + else { + TURN { MOVE(player, MOVE_LICK) ; EXPECT_SWITCH(opponent, 1); } + } + } +} diff --git a/test/battle/ai_powerful_status.c b/test/battle/ai/ai_powerful_status.c similarity index 100% rename from test/battle/ai_powerful_status.c rename to test/battle/ai/ai_powerful_status.c diff --git a/test/battle/ai_switching.c b/test/battle/ai/ai_switching.c similarity index 91% rename from test/battle/ai_switching.c rename to test/battle/ai/ai_switching.c index 50b59f66d1..63a9c92248 100644 --- a/test/battle/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -127,9 +127,9 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will not switch in a Pokemo u32 speedAlakazm; u32 aiSmartSwitchFlags = 0; - PARAMETRIZE{ speedAlakazm = 200; alakazamFirst = TRUE; } // AI will always send out Alakazan as it sees a KO with Focus Blast, even if Alakazam dies before it can get it off - PARAMETRIZE{ speedAlakazm = 200; alakazamFirst = FALSE; aiSmartSwitchFlags = AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES; } // AI_FLAG_SMART_MON_CHOICES lets AI see that Alakazam would be KO'd before it can KO, and won't switch it in - PARAMETRIZE{ speedAlakazm = 400; alakazamFirst = TRUE; aiSmartSwitchFlags = AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES; } // AI_FLAG_SMART_MON_CHOICES recognizes that Alakazam is faster and can KO, and will switch it in + PARAMETRIZE { speedAlakazm = 200; alakazamFirst = TRUE; } // AI will always send out Alakazan as it sees a KO with Focus Blast, even if Alakazam dies before it can get it off + PARAMETRIZE { speedAlakazm = 200; alakazamFirst = FALSE; aiSmartSwitchFlags = AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES; } // AI_FLAG_SMART_MON_CHOICES lets AI see that Alakazam would be KO'd before it can KO, and won't switch it in + PARAMETRIZE { speedAlakazm = 400; alakazamFirst = TRUE; aiSmartSwitchFlags = AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES; } // AI_FLAG_SMART_MON_CHOICES recognizes that Alakazam is faster and can KO, and will switch it in GIVEN { ASSUME(gMovesInfo[MOVE_PSYCHIC].category == DAMAGE_CATEGORY_SPECIAL); @@ -159,8 +159,8 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI considers hazard damage whe u32 aiIsSmart = 0; u32 aiSmartSwitchFlags = 0; - PARAMETRIZE{ aiIsSmart = 0; aiSmartSwitchFlags = 0; } // AI doesn't care about hazard damage resulting in Pokemon being KO'd - PARAMETRIZE{ aiIsSmart = 1; aiSmartSwitchFlags = AI_FLAG_SMART_MON_CHOICES; } // AI_FLAG_SMART_MON_CHOICES avoids being KO'd as a result of hazards damage + PARAMETRIZE { aiIsSmart = 0; aiSmartSwitchFlags = 0; } // AI doesn't care about hazard damage resulting in Pokemon being KO'd + PARAMETRIZE { aiIsSmart = 1; aiSmartSwitchFlags = AI_FLAG_SMART_MON_CHOICES; } // AI_FLAG_SMART_MON_CHOICES avoids being KO'd as a result of hazards damage GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSmartSwitchFlags); @@ -181,10 +181,10 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize u32 move2; u32 expectedIndex; - PARAMETRIZE{ expectedIndex = 3; move1 = MOVE_TACKLE; move2 = MOVE_TACKLE; aiSmartSwitchFlags = 0; } // When not smart, AI will only switch in a defensive mon if it has a SE move, otherwise will just default to damage - PARAMETRIZE{ expectedIndex = 1; move1 = MOVE_GIGA_DRAIN; move2 = MOVE_TACKLE; aiSmartSwitchFlags = 0; } - PARAMETRIZE{ expectedIndex = 2; move1 = MOVE_TACKLE; move2 = MOVE_TACKLE; aiSmartSwitchFlags = AI_FLAG_SMART_MON_CHOICES; } // When smart, AI will prioritize SE move, but still switch in good type matchup without SE move - PARAMETRIZE{ expectedIndex = 1; move1 = MOVE_GIGA_DRAIN; move2 = MOVE_TACKLE; aiSmartSwitchFlags = AI_FLAG_SMART_MON_CHOICES; } + PARAMETRIZE { expectedIndex = 3; move1 = MOVE_TACKLE; move2 = MOVE_TACKLE; aiSmartSwitchFlags = 0; } // When not smart, AI will only switch in a defensive mon if it has a SE move, otherwise will just default to damage + PARAMETRIZE { expectedIndex = 1; move1 = MOVE_GIGA_DRAIN; move2 = MOVE_TACKLE; aiSmartSwitchFlags = 0; } + PARAMETRIZE { expectedIndex = 2; move1 = MOVE_TACKLE; move2 = MOVE_TACKLE; aiSmartSwitchFlags = AI_FLAG_SMART_MON_CHOICES; } // When smart, AI will prioritize SE move, but still switch in good type matchup without SE move + PARAMETRIZE { expectedIndex = 1; move1 = MOVE_GIGA_DRAIN; move2 = MOVE_TACKLE; aiSmartSwitchFlags = AI_FLAG_SMART_MON_CHOICES; } GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiSmartSwitchFlags); @@ -257,8 +257,8 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will not switch out if Pokemo { u32 move1; - PARAMETRIZE{ move1 = MOVE_TACKLE; } - PARAMETRIZE{ move1 = MOVE_RAPID_SPIN; } + PARAMETRIZE { move1 = MOVE_TACKLE; } + PARAMETRIZE { move1 = MOVE_RAPID_SPIN; } GIVEN { ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); diff --git a/test/battle/ai_trytofaint.c b/test/battle/ai/ai_trytofaint.c similarity index 100% rename from test/battle/ai_trytofaint.c rename to test/battle/ai/ai_trytofaint.c diff --git a/test/battle/crit_chance.c b/test/battle/crit_chance.c index 4964767e8e..d4c8e2b1a0 100644 --- a/test/battle/crit_chance.c +++ b/test/battle/crit_chance.c @@ -6,7 +6,7 @@ ASSUMPTIONS ASSUME(B_CRIT_CHANCE >= GEN_7); } -SINGLE_BATTLE_TEST("Side effected by Lucky Chant blocks critical hits") +SINGLE_BATTLE_TEST("Crit Chance: Side effected by Lucky Chant blocks critical hits") { GIVEN { ASSUME(gMovesInfo[MOVE_LUCKY_CHANT].effect == EFFECT_LUCKY_CHANT); @@ -20,7 +20,7 @@ SINGLE_BATTLE_TEST("Side effected by Lucky Chant blocks critical hits") } } -SINGLE_BATTLE_TEST("Battle Armor and Shell Armor block critical hits") +SINGLE_BATTLE_TEST("Crit Chance: Battle Armor and Shell Armor block critical hits") { u32 species; u32 ability; @@ -39,7 +39,7 @@ SINGLE_BATTLE_TEST("Battle Armor and Shell Armor block critical hits") } } -SINGLE_BATTLE_TEST("Flag ignoresTargetAbility ignores Battle Armor and Shell Armor") +SINGLE_BATTLE_TEST("Crit Chance: Flag ignoresTargetAbility ignores Battle Armor and Shell Armor") { u32 species; u32 ability; @@ -59,7 +59,7 @@ SINGLE_BATTLE_TEST("Flag ignoresTargetAbility ignores Battle Armor and Shell Arm } } -SINGLE_BATTLE_TEST("Mold Breaker, Teravolt and Turboblaze ignore Battle Armor and Shell Armor") +SINGLE_BATTLE_TEST("Crit Chance: Mold Breaker, Teravolt and Turboblaze ignore Battle Armor and Shell Armor") { u32 j; static const u32 pokemonPlayer[][2] = @@ -102,7 +102,7 @@ SINGLE_BATTLE_TEST("Mold Breaker, Teravolt and Turboblaze ignore Battle Armor an } } -SINGLE_BATTLE_TEST("User effected by Laser Focus causes moves to result in a critical hit") +SINGLE_BATTLE_TEST("Crit Chance: User effected by Laser Focus causes moves to result in a critical hit") { GIVEN { ASSUME(gMovesInfo[MOVE_LASER_FOCUS].effect == EFFECT_LASER_FOCUS); @@ -118,7 +118,7 @@ SINGLE_BATTLE_TEST("User effected by Laser Focus causes moves to result in a cri } } -SINGLE_BATTLE_TEST("If the target is poisoned the ability Merciless causes a move to result in a critical hit") +SINGLE_BATTLE_TEST("Crit Chance: If the target is poisoned the ability Merciless causes a move to result in a critical hit") { GIVEN { PLAYER(SPECIES_MAREANIE) { Ability(ABILITY_MERCILESS); } @@ -131,7 +131,7 @@ SINGLE_BATTLE_TEST("If the target is poisoned the ability Merciless causes a mov } } -SINGLE_BATTLE_TEST("Focus Energy increases the user's critical hit ratio by two stage") +SINGLE_BATTLE_TEST("Crit Chance: Focus Energy increases the user's critical hit ratio by two stage") { PASSES_RANDOMLY(1, 2, RNG_CRITICAL_HIT); GIVEN { @@ -148,7 +148,7 @@ SINGLE_BATTLE_TEST("Focus Energy increases the user's critical hit ratio by two } } -SINGLE_BATTLE_TEST("High crit rate increases the critical hit ratio by one stage") +SINGLE_BATTLE_TEST("Crit Chance: High crit rate increases the critical hit ratio by one stage") { PASSES_RANDOMLY(1, 8, RNG_CRITICAL_HIT); GIVEN { @@ -163,7 +163,7 @@ SINGLE_BATTLE_TEST("High crit rate increases the critical hit ratio by one stage } } -SINGLE_BATTLE_TEST("Super Luck increases the critical hit ratio by one stage") +SINGLE_BATTLE_TEST("Crit Chance: Super Luck increases the critical hit ratio by one stage") { PASSES_RANDOMLY(1, 8, RNG_CRITICAL_HIT); GIVEN { @@ -177,7 +177,7 @@ SINGLE_BATTLE_TEST("Super Luck increases the critical hit ratio by one stage") } } -SINGLE_BATTLE_TEST("Scope Lens increases the critical hit ratio by one stage") +SINGLE_BATTLE_TEST("Crit Chance: Scope Lens increases the critical hit ratio by one stage") { PASSES_RANDOMLY(1, 8, RNG_CRITICAL_HIT); GIVEN { @@ -192,7 +192,7 @@ SINGLE_BATTLE_TEST("Scope Lens increases the critical hit ratio by one stage") } } -SINGLE_BATTLE_TEST("High crit rate, Super Luck and Scope Lens cause the move to result in a critical hit") +SINGLE_BATTLE_TEST("Crit Chance: High crit rate, Super Luck and Scope Lens cause the move to result in a critical hit") { GIVEN { ASSUME(gMovesInfo[MOVE_SLASH].criticalHitStage == 1); @@ -207,7 +207,7 @@ SINGLE_BATTLE_TEST("High crit rate, Super Luck and Scope Lens cause the move to } } -SINGLE_BATTLE_TEST("Signature items Leek and Lucky Punch increase the critical hit ratio by 2 stages") +SINGLE_BATTLE_TEST("Crit Chance: Signature items Leek and Lucky Punch increase the critical hit ratio by 2 stages") { u32 species; u32 item; @@ -232,7 +232,7 @@ SINGLE_BATTLE_TEST("Signature items Leek and Lucky Punch increase the critical h } } -SINGLE_BATTLE_TEST("Dire Hit increases a battler's critical hit chance by 2 stages") +SINGLE_BATTLE_TEST("Crit Chance: Dire Hit increases a battler's critical hit chance by 2 stages") { PASSES_RANDOMLY(1, 2, RNG_CRITICAL_HIT); GIVEN { @@ -250,7 +250,7 @@ SINGLE_BATTLE_TEST("Dire Hit increases a battler's critical hit chance by 2 stag } } -SINGLE_BATTLE_TEST("Focus Energy increases critical hit ratio by two") +SINGLE_BATTLE_TEST("Crit Chance: Focus Energy increases critical hit ratio by two") { PASSES_RANDOMLY(8, 8, RNG_CRITICAL_HIT); GIVEN { @@ -269,7 +269,7 @@ SINGLE_BATTLE_TEST("Focus Energy increases critical hit ratio by two") } } -SINGLE_BATTLE_TEST("Dragon Cheer fails in a single battle") +SINGLE_BATTLE_TEST("Crit Chance: Dragon Cheer fails in a single battle") { GIVEN { ASSUME(gMovesInfo[MOVE_DRAGON_CHEER].effect == EFFECT_DRAGON_CHEER); @@ -282,7 +282,7 @@ SINGLE_BATTLE_TEST("Dragon Cheer fails in a single battle") } } -DOUBLE_BATTLE_TEST("Dragon Cheer increases critical hit ratio by one on non Dragon types") +DOUBLE_BATTLE_TEST("Crit Chance: Dragon Cheer increases critical hit ratio by one on non Dragon types") { PASSES_RANDOMLY(1, 8, RNG_CRITICAL_HIT); GIVEN { @@ -302,7 +302,7 @@ DOUBLE_BATTLE_TEST("Dragon Cheer increases critical hit ratio by one on non Drag } } -DOUBLE_BATTLE_TEST("Dragon Cheer increases critical hit ratio by two on Dragon types") +DOUBLE_BATTLE_TEST("Crit Chance: Dragon Cheer increases critical hit ratio by two on Dragon types") { PASSES_RANDOMLY(1, 2, RNG_CRITICAL_HIT); GIVEN { @@ -322,7 +322,7 @@ DOUBLE_BATTLE_TEST("Dragon Cheer increases critical hit ratio by two on Dragon t } } -DOUBLE_BATTLE_TEST("Dragon Cheer fails if critical hit stage was already increased by Focus Energy") +DOUBLE_BATTLE_TEST("Crit Chance: Dragon Cheer fails if critical hit stage was already increased by Focus Energy") { GIVEN { ASSUME(gMovesInfo[MOVE_SLASH].criticalHitStage == 1); diff --git a/test/battle/form_change/mega_evolution.c b/test/battle/form_change/mega_evolution.c index b2426933f7..ffab6af18b 100644 --- a/test/battle/form_change/mega_evolution.c +++ b/test/battle/form_change/mega_evolution.c @@ -7,7 +7,7 @@ SINGLE_BATTLE_TEST("Venusaur can Mega Evolve holding Venusaurite") PLAYER(SPECIES_VENUSAUR) { Item(ITEM_VENUSAURITE); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, megaEvolve: TRUE); } + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } } SCENE { MESSAGE("Venusaur's Venusaurite is reacting to 1's Mega Ring!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, player); @@ -25,7 +25,7 @@ DOUBLE_BATTLE_TEST("Mega Evolution's order is determined by Speed - opponent fas OPPONENT(SPECIES_GARDEVOIR) { Item(ITEM_GARDEVOIRITE); Speed(3); } OPPONENT(SPECIES_WOBBUFFET) { Speed(4); } } WHEN { - TURN { MOVE(opponentLeft, MOVE_CELEBRATE, megaEvolve: TRUE); MOVE(playerLeft, MOVE_CELEBRATE, megaEvolve: TRUE); } + TURN { MOVE(opponentLeft, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } } SCENE { MESSAGE("Foe Gardevoir's Gardevoirite is reacting to 2's Mega Ring!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponentLeft); @@ -44,7 +44,7 @@ DOUBLE_BATTLE_TEST("Mega Evolution's order is determined by Speed - player faste OPPONENT(SPECIES_GARDEVOIR) { Item(ITEM_GARDEVOIRITE); Speed(2); } OPPONENT(SPECIES_WOBBUFFET) { Speed(4); } } WHEN { - TURN { MOVE(opponentLeft, MOVE_CELEBRATE, megaEvolve: TRUE); MOVE(playerLeft, MOVE_CELEBRATE, megaEvolve: TRUE); } + TURN { MOVE(opponentLeft, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } } SCENE { MESSAGE("Venusaur's Venusaurite is reacting to 1's Mega Ring!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, playerLeft); @@ -61,7 +61,7 @@ SINGLE_BATTLE_TEST("Rayquaza can Mega Evolve knowing Dragon Ascent") PLAYER(SPECIES_RAYQUAZA) { Moves(MOVE_DRAGON_ASCENT, MOVE_CELEBRATE); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, megaEvolve: TRUE); } + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } } SCENE { MESSAGE("1's fervent wish has reached Rayquaza!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, player); @@ -78,7 +78,7 @@ SINGLE_BATTLE_TEST("Mega Evolution affects turn order") PLAYER(SPECIES_GARDEVOIR) { Item(ITEM_GARDEVOIRITE); Speed(105); } OPPONENT(SPECIES_WOBBUFFET) { Speed(106); } } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, megaEvolve: TRUE); } + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } } SCENE { MESSAGE("Gardevoir used Celebrate!"); MESSAGE("Foe Wobbuffet used Celebrate!"); @@ -96,7 +96,7 @@ SINGLE_BATTLE_TEST("Abilities replaced by Mega Evolution do not affect turn orde PLAYER(SPECIES_SABLEYE) { Item(ITEM_SABLENITE); Ability(ABILITY_STALL); Speed(105); } OPPONENT(SPECIES_WOBBUFFET) { Speed(44); } } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, megaEvolve: TRUE); } + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } } SCENE { MESSAGE("Sableye used Celebrate!"); MESSAGE("Foe Wobbuffet used Celebrate!"); @@ -115,7 +115,7 @@ DOUBLE_BATTLE_TEST("Mega Evolution happens after switching, but before Focus Pun OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { SWITCH(opponentRight, 2); MOVE(playerRight, MOVE_FOCUS_PUNCH, megaEvolve: TRUE, target: opponentLeft); MOVE(playerLeft, MOVE_FOCUS_PUNCH, target: opponentLeft); } + TURN { SWITCH(opponentRight, 2); MOVE(playerRight, MOVE_FOCUS_PUNCH, gimmick: GIMMICK_MEGA, target: opponentLeft); MOVE(playerLeft, MOVE_FOCUS_PUNCH, target: opponentLeft); } TURN {} } SCENE { MESSAGE("2 withdrew Wobbuffet!"); @@ -139,7 +139,7 @@ SINGLE_BATTLE_TEST("Regular Mega Evolution and Fervent Wish Mega Evolution can h PLAYER(SPECIES_RAYQUAZA) { Moves(MOVE_DRAGON_ASCENT, MOVE_CELEBRATE); Speed(3); } OPPONENT(SPECIES_GARDEVOIR) { Item(ITEM_GARDEVOIRITE); Speed(2); } } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, megaEvolve: TRUE); MOVE(opponent, MOVE_CELEBRATE, megaEvolve: TRUE); } + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); MOVE(opponent, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } } SCENE { MESSAGE("1's fervent wish has reached Rayquaza!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, player); @@ -153,3 +153,26 @@ SINGLE_BATTLE_TEST("Regular Mega Evolution and Fervent Wish Mega Evolution can h EXPECT_EQ(opponent->species, SPECIES_GARDEVOIR_MEGA); } } + +SINGLE_BATTLE_TEST("Mega Evolved Pokemon do not change abilities after fainting") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_CRUNCH].makesContact == TRUE); + ASSUME(gSpeciesInfo[SPECIES_GARCHOMP_MEGA].abilities[0] != ABILITY_ROUGH_SKIN); + ASSUME(gSpeciesInfo[SPECIES_GARCHOMP_MEGA].abilities[1] != ABILITY_ROUGH_SKIN); + ASSUME(gSpeciesInfo[SPECIES_GARCHOMP_MEGA].abilities[2] != ABILITY_ROUGH_SKIN); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GARCHOMP) { Ability(ABILITY_ROUGH_SKIN); Item(ITEM_GARCHOMPITE); HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_CRUNCH); MOVE(opponent, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CRUNCH, player); + MESSAGE("Foe Garchomp fainted!"); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_ROUGH_SKIN); + MESSAGE("Wobbuffet was hurt by Foe Garchomp's Rough Skin!"); + HP_BAR(player); + } + } +} diff --git a/test/battle/form_change/ultra_burst.c b/test/battle/form_change/ultra_burst.c index f2c7b1da2b..5c6f4a2ce4 100644 --- a/test/battle/form_change/ultra_burst.c +++ b/test/battle/form_change/ultra_burst.c @@ -7,7 +7,7 @@ SINGLE_BATTLE_TEST("Dusk Mane Necrozma can Ultra Burst holding Ultranecrozium Z" PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Item(ITEM_ULTRANECROZIUM_Z); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, ultraBurst: TRUE); } + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } } SCENE { MESSAGE("Bright light is about to burst out of Necrozma!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ULTRA_BURST, player); @@ -25,7 +25,7 @@ DOUBLE_BATTLE_TEST("Ultra Burst's order is determined by Speed - opponent faster OPPONENT(SPECIES_NECROZMA_DAWN_WINGS) { Item(ITEM_ULTRANECROZIUM_Z); Speed(3); } OPPONENT(SPECIES_WOBBUFFET) { Speed(4); } } WHEN { - TURN { MOVE(opponentLeft, MOVE_CELEBRATE, ultraBurst: TRUE); MOVE(playerLeft, MOVE_CELEBRATE, ultraBurst: TRUE); } + TURN { MOVE(opponentLeft, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } } SCENE { MESSAGE("Bright light is about to burst out of Foe Necrozma!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ULTRA_BURST, opponentLeft); @@ -44,7 +44,7 @@ DOUBLE_BATTLE_TEST("Ultra Burst's order is determined by Speed - player faster") OPPONENT(SPECIES_NECROZMA_DAWN_WINGS) { Item(ITEM_ULTRANECROZIUM_Z); Speed(2); } OPPONENT(SPECIES_WOBBUFFET) { Speed(4); } } WHEN { - TURN { MOVE(opponentLeft, MOVE_CELEBRATE, ultraBurst: TRUE); MOVE(playerLeft, MOVE_CELEBRATE, ultraBurst: TRUE); } + TURN { MOVE(opponentLeft, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } } SCENE { MESSAGE("Bright light is about to burst out of Necrozma!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ULTRA_BURST, playerLeft); @@ -62,7 +62,7 @@ SINGLE_BATTLE_TEST("Ultra Burst affects turn order") PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Item(ITEM_ULTRANECROZIUM_Z); Speed(105); } OPPONENT(SPECIES_WOBBUFFET) { Speed(106); } } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, ultraBurst: TRUE); } + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } } SCENE { MESSAGE("Necrozma used Celebrate!"); MESSAGE("Foe Wobbuffet used Celebrate!"); @@ -81,7 +81,7 @@ DOUBLE_BATTLE_TEST("Ultra Burst happens after switching, but before Focus Punch- OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { SWITCH(opponentRight, 2); MOVE(playerRight, MOVE_FOCUS_PUNCH, ultraBurst: TRUE, target: opponentLeft); MOVE(playerLeft, MOVE_FOCUS_PUNCH, target: opponentLeft); } + TURN { SWITCH(opponentRight, 2); MOVE(playerRight, MOVE_FOCUS_PUNCH, gimmick: GIMMICK_ULTRA_BURST, target: opponentLeft); MOVE(playerLeft, MOVE_FOCUS_PUNCH, target: opponentLeft); } TURN {} } SCENE { MESSAGE("2 withdrew Wobbuffet!"); @@ -105,7 +105,7 @@ SINGLE_BATTLE_TEST("Ultra Burst and Mega Evolution can happen on the same turn") PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Item(ITEM_ULTRANECROZIUM_Z); Speed(3); } OPPONENT(SPECIES_GARDEVOIR) { Item(ITEM_GARDEVOIRITE); Speed(2); } } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, ultraBurst: TRUE); MOVE(opponent, MOVE_CELEBRATE, megaEvolve: TRUE); } + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); MOVE(opponent, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } } SCENE { MESSAGE("Bright light is about to burst out of Necrozma!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ULTRA_BURST, player); diff --git a/test/battle/gimmick/dynamax.c b/test/battle/gimmick/dynamax.c index 4892e3ce47..56fd2e25ea 100644 --- a/test/battle/gimmick/dynamax.c +++ b/test/battle/gimmick/dynamax.c @@ -4,14 +4,14 @@ // ============= DYNAMAX AND MAX MOVE INTERACTIONS =================== SINGLE_BATTLE_TEST("(DYNAMAX) Dynamax increases HP and max HP by 1.5x", u16 hp) { - bool32 dynamax; - PARAMETRIZE { dynamax = FALSE; } - PARAMETRIZE { dynamax = TRUE; } + u32 dynamax; + PARAMETRIZE { dynamax = GIMMICK_NONE; } + PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; } GIVEN { // TODO: Dynamax level PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TACKLE, dynamax: dynamax); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: dynamax); MOVE(opponent, MOVE_CELEBRATE); } } SCENE { if (dynamax) { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_DYNAMAX_GROWTH, player); @@ -27,14 +27,14 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamax increases HP and max HP by 1.5x", u16 hp) SINGLE_BATTLE_TEST("(DYNAMAX) Dynamax expires after three turns", u16 hp) { - bool32 dynamax; - PARAMETRIZE { dynamax = FALSE; } - PARAMETRIZE { dynamax = TRUE; } + u32 dynamax; + PARAMETRIZE { dynamax = GIMMICK_NONE; } + PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TACKLE, dynamax: dynamax); } // 1st max move + TURN { MOVE(player, MOVE_TACKLE, gimmick: dynamax); } // 1st max move TURN { MOVE(player, MOVE_TACKLE); } // 2nd max move TURN { MOVE(player, MOVE_TACKLE); } // 3rd max move } SCENE { @@ -62,7 +62,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon cannot be flinched") PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(opponent, MOVE_FAKE_OUT); MOVE(player, MOVE_TACKLE, dynamax: TRUE); } + TURN { MOVE(opponent, MOVE_FAKE_OUT); MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); } } SCENE { MESSAGE("Foe Wobbuffet used Fake Out!"); NONE_OF { MESSAGE("Wobbuffet flinched!"); } @@ -77,7 +77,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon cannot be hit by weight-based mo PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TACKLE, dynamax: TRUE); MOVE(opponent, MOVE_HEAVY_SLAM); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_HEAVY_SLAM); } } SCENE { MESSAGE("Wobbuffet used Max Strike!"); MESSAGE("Foe Wobbuffet used Heavy Slam!"); @@ -93,7 +93,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon cannot be hit by OHKO moves") PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_MACHAMP) { Ability(ABILITY_NO_GUARD); } } WHEN { - TURN { MOVE(player, MOVE_TACKLE, dynamax: TRUE); MOVE(opponent, MOVE_FISSURE); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_FISSURE); } } SCENE { MESSAGE("Wobbuffet used Max Strike!"); MESSAGE("Foe Machamp used Fissure!"); @@ -109,7 +109,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon are not affected by Destiny Bond PLAYER(SPECIES_WOBBUFFET) { Speed(50); }; OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(100); } } WHEN { - TURN { MOVE(opponent, MOVE_DESTINY_BOND); MOVE(player, MOVE_TACKLE, dynamax: TRUE); } + TURN { MOVE(opponent, MOVE_DESTINY_BOND); MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); } } SCENE { MESSAGE("Foe Wobbuffet used Destiny Bond!"); MESSAGE("Wobbuffet used Max Strike!"); @@ -124,7 +124,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon are affected by Grudge") PLAYER(SPECIES_WOBBUFFET) { Speed(50); }; OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(100); } } WHEN { - TURN { MOVE(opponent, MOVE_GRUDGE); MOVE(player, MOVE_TACKLE, dynamax: TRUE); } + TURN { MOVE(opponent, MOVE_GRUDGE); MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); } } SCENE { MESSAGE("Foe Wobbuffet used Grudge!"); MESSAGE("Wobbuffet used Max Strike!"); @@ -142,7 +142,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon are not affected by phazing move PLAYER(SPECIES_WYNAUT); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(opponent, MOVE_DRAGON_TAIL); MOVE(player, MOVE_TACKLE, dynamax: TRUE); } + TURN { MOVE(opponent, MOVE_DRAGON_TAIL); MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); } TURN { MOVE(opponent, MOVE_WHIRLWIND); MOVE(player, MOVE_TACKLE); } } SCENE { MESSAGE("Wobbuffet used Max Strike!"); @@ -163,7 +163,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon are not affected by phazing move PLAYER(SPECIES_WYNAUT); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(opponent, MOVE_DRAGON_TAIL); MOVE(player, MOVE_TACKLE, dynamax: TRUE); SEND_OUT(player, 1); } + TURN { MOVE(opponent, MOVE_DRAGON_TAIL); MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); SEND_OUT(player, 1); } } SCENE { MESSAGE("Wobbuffet used Max Strike!"); MESSAGE("Foe Wobbuffet used Dragon Tail!"); @@ -181,7 +181,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon are not affected by Red Card") PLAYER(SPECIES_WYNAUT); OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } } WHEN { - TURN { MOVE(player, MOVE_TACKLE, dynamax: TRUE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_CELEBRATE); } } SCENE { MESSAGE("Wobbuffet used Max Strike!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); @@ -195,11 +195,12 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon are not affected by Red Card") SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon can be switched out by Eject Button") { GIVEN { + ASSUME(gItemsInfo[ITEM_EJECT_BUTTON].holdEffect == HOLD_EFFECT_EJECT_BUTTON); PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_BUTTON); } PLAYER(SPECIES_WYNAUT); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TACKLE, dynamax: TRUE); MOVE(opponent, MOVE_TACKLE); SEND_OUT(player, 1); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_TACKLE); SEND_OUT(player, 1); } } SCENE { MESSAGE("Wobbuffet used Max Strike!"); MESSAGE("Foe Wobbuffet used Tackle!"); @@ -216,7 +217,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon cannot have their ability swappe PLAYER(SPECIES_MILTANK) { Ability(ABILITY_SCRAPPY); } OPPONENT(SPECIES_RUNERIGUS) { Ability(ABILITY_WANDERING_SPIRIT); } } WHEN { - TURN { MOVE(player, MOVE_TACKLE, dynamax: TRUE); MOVE(opponent, MOVE_SKILL_SWAP); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_SKILL_SWAP); } } SCENE { MESSAGE("Miltank used Max Strike!"); MESSAGE("Foe Runerigus used Skill Swap!"); @@ -232,7 +233,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon can have their ability changed o PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TACKLE, dynamax: TRUE); MOVE(opponent, MOVE_SIMPLE_BEAM); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_SIMPLE_BEAM); } } SCENE { MESSAGE("Wobbuffet used Max Strike!"); MESSAGE("Foe Wobbuffet used Simple Beam!"); @@ -248,7 +249,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon are immune to Encore") PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TACKLE, dynamax: TRUE); MOVE(opponent, MOVE_ENCORE); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_ENCORE); } TURN { MOVE(player, MOVE_EMBER); } } SCENE { MESSAGE("Wobbuffet used Max Strike!"); @@ -264,7 +265,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon can be encored immediately after PLAYER(SPECIES_WOBBUFFET) { Speed(50); }; // yes, this speed is necessary OPPONENT(SPECIES_WOBBUFFET) { Speed(100); }; } WHEN { - TURN { MOVE(player, MOVE_ARM_THRUST, dynamax: TRUE); } + TURN { MOVE(player, MOVE_ARM_THRUST, gimmick: GIMMICK_DYNAMAX); } TURN { MOVE(player, MOVE_ARM_THRUST); } TURN { MOVE(player, MOVE_ARM_THRUST); } TURN { MOVE(opponent, MOVE_ENCORE); MOVE(player, MOVE_TACKLE); } @@ -284,7 +285,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon's Max Moves cannot be disabled") PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TACKLE, dynamax: TRUE); MOVE(opponent, MOVE_DISABLE); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_DISABLE); } } SCENE { MESSAGE("Wobbuffet used Max Strike!"); MESSAGE("Foe Wobbuffet used Disable!"); @@ -300,7 +301,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon can have base moves disabled on OPPONENT(SPECIES_WOBBUFFET) { Speed(100); }; } WHEN { TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_TACKLE); } - TURN { MOVE(opponent, MOVE_DISABLE); MOVE(player, MOVE_TACKLE, dynamax: TRUE); } + TURN { MOVE(opponent, MOVE_DISABLE); MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); } TURN {} TURN {} TURN { MOVE(player, MOVE_TACKLE, allowed: FALSE); MOVE(player, MOVE_CELEBRATE); } @@ -319,7 +320,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon are immune to Torment") PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TACKLE, dynamax: TRUE); MOVE(opponent, MOVE_TORMENT); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_TORMENT); } } SCENE { MESSAGE("Wobbuffet used Max Strike!"); MESSAGE("Foe Wobbuffet used Torment!"); @@ -334,7 +335,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon are not immune to Knock Off") PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_POTION); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TACKLE, dynamax: TRUE); MOVE(opponent, MOVE_KNOCK_OFF); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_KNOCK_OFF); } } SCENE { MESSAGE("Wobbuffet used Max Strike!"); MESSAGE("Foe Wobbuffet used Knock Off!"); @@ -351,7 +352,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon lose their substitutes") OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_CELEBRATE); } - TURN { MOVE(player, MOVE_TACKLE, dynamax: TRUE); MOVE(opponent, MOVE_TACKLE); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_TACKLE); } } SCENE { MESSAGE("Wobbuffet used Substitute!"); MESSAGE("Wobbuffet made a SUBSTITUTE!"); @@ -369,7 +370,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon can have their base moves copied OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WYNAUT); } WHEN { - TURN { MOVE(playerLeft, MOVE_TRICK_ROOM, dynamax: TRUE, target: opponentLeft); MOVE(playerRight, MOVE_COPYCAT, target: opponentLeft); } + TURN { MOVE(playerLeft, MOVE_TRICK_ROOM, gimmick: GIMMICK_DYNAMAX, target: opponentLeft); MOVE(playerRight, MOVE_COPYCAT, target: opponentLeft); } } SCENE { MESSAGE("Wobbuffet used Max Guard!"); MESSAGE("Wynaut used Trick Room!"); @@ -378,15 +379,15 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon can have their base moves copied SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon take double damage from Dynamax Cannon", s16 damage) { - bool32 dynamaxed; - PARAMETRIZE { dynamaxed = FALSE; } - PARAMETRIZE { dynamaxed = TRUE; } + u32 dynamax; + PARAMETRIZE { dynamax = GIMMICK_NONE; } + PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; } GIVEN { ASSUME(gMovesInfo[MOVE_DYNAMAX_CANNON].effect == EFFECT_DYNAMAX_DOUBLE_DMG); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TACKLE, dynamax: dynamaxed); MOVE(opponent, MOVE_DYNAMAX_CANNON); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: dynamax); MOVE(opponent, MOVE_DYNAMAX_CANNON); } } SCENE { HP_BAR(player, captureDamage: &results[i].damage); } FINALLY { @@ -404,9 +405,9 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Moves deal 1/4 damage through protect", s16 da OPPONENT(SPECIES_WOBBUFFET); } WHEN { if (protected) - TURN { MOVE(player, MOVE_TACKLE, dynamax: TRUE); MOVE(opponent, MOVE_PROTECT); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_PROTECT); } else - TURN { MOVE(player, MOVE_TACKLE, dynamax: TRUE); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); } } SCENE { HP_BAR(opponent, captureDamage: &results[i].damage); } FINALLY { @@ -420,7 +421,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Moves don't bypass Max Guard") PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TACKLE, dynamax: TRUE); MOVE(opponent, MOVE_PROTECT, dynamax: TRUE); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_PROTECT, gimmick: GIMMICK_DYNAMAX); } } SCENE { NONE_OF { HP_BAR(opponent); } } @@ -434,7 +435,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) Feint bypasses Max Guard but doesn't break it") OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WYNAUT); } WHEN { - TURN { MOVE(playerLeft, MOVE_PROTECT, dynamax: TRUE); + TURN { MOVE(playerLeft, MOVE_PROTECT, gimmick: GIMMICK_DYNAMAX); MOVE(opponentLeft, MOVE_FEINT, target: playerLeft); MOVE(opponentRight, MOVE_TACKLE, target: playerLeft); } @@ -455,7 +456,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon are immune to Instruct") OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WYNAUT); } WHEN { - TURN { MOVE(playerLeft, MOVE_TACKLE, dynamax: TRUE, target: opponentLeft); + TURN { MOVE(playerLeft, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX, target: opponentLeft); MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft); } } SCENE { @@ -475,7 +476,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Pokemon with Gigantamax forms change upon Dynamaxi PLAYER(SPECIES_VENUSAUR) { GigantamaxFactor(gigantamaxFactor); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TACKLE, dynamax: TRUE); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); } } THEN { EXPECT_EQ(player->species, species); } @@ -488,7 +489,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Pokemon with Gigantamax forms revert upon switchin PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TACKLE, dynamax: TRUE); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); } TURN { SWITCH(player, 1); } TURN { SWITCH(player, 0); } } THEN { @@ -506,8 +507,8 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon are not affected by Choice items PLAYER(SPECIES_WOBBUFFET) { Item(item); }; OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TACKLE, dynamax: TRUE); } - TURN { MOVE(player, MOVE_ARM_THRUST, dynamax: TRUE); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); } + TURN { MOVE(player, MOVE_ARM_THRUST); } } SCENE { MESSAGE("Wobbuffet used Max Strike!"); HP_BAR(opponent, captureDamage: &results[i].damage); @@ -524,7 +525,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon cannot use Max Guard while holdi PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_ASSAULT_VEST); }; OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TACKLE, dynamax: TRUE); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); } TURN { MOVE(player, MOVE_PROTECT, allowed: FALSE); MOVE(player, MOVE_TACKLE); } } SCENE { MESSAGE("Wobbuffet used Max Strike!"); @@ -540,15 +541,15 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon cannot use Max Guard while holdi SINGLE_BATTLE_TEST("(DYNAMAX) Endeavor uses a Pokemon's non-Dynamax HP", s16 damage) { - bool32 dynamax; - PARAMETRIZE { dynamax = TRUE; } - PARAMETRIZE { dynamax = FALSE; } + u32 dynamax; + PARAMETRIZE { dynamax = GIMMICK_NONE; } + PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; } GIVEN { ASSUME(gMovesInfo[MOVE_ENDEAVOR].effect == EFFECT_ENDEAVOR); PLAYER(SPECIES_WOBBUFFET) { Speed(50); } OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(100); } } WHEN { - TURN { MOVE(opponent, MOVE_ENDEAVOR); MOVE(player, MOVE_TACKLE, dynamax: dynamax); } + TURN { MOVE(opponent, MOVE_ENDEAVOR); MOVE(player, MOVE_TACKLE, gimmick: dynamax); } } SCENE { MESSAGE("Foe Wobbuffet used Endeavor!"); HP_BAR(player, captureDamage: &results[i].damage); @@ -559,15 +560,15 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Endeavor uses a Pokemon's non-Dynamax HP", s16 dam SINGLE_BATTLE_TEST("(DYNAMAX) Super Fang uses a Pokemon's non-Dynamax HP", s16 damage) { - bool32 dynamax; - PARAMETRIZE { dynamax = TRUE; } - PARAMETRIZE { dynamax = FALSE; } + u32 dynamax; + PARAMETRIZE { dynamax = GIMMICK_NONE; } + PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; } GIVEN { ASSUME(gMovesInfo[MOVE_SUPER_FANG].effect == EFFECT_SUPER_FANG); PLAYER(SPECIES_WOBBUFFET) { Speed(50); } OPPONENT(SPECIES_WOBBUFFET) { Speed(100); } } WHEN { - TURN { MOVE(opponent, MOVE_SUPER_FANG); MOVE(player, MOVE_TACKLE, dynamax: dynamax); } + TURN { MOVE(opponent, MOVE_SUPER_FANG); MOVE(player, MOVE_TACKLE, gimmick: dynamax); } } SCENE { MESSAGE("Foe Wobbuffet used Super Fang!"); HP_BAR(player, captureDamage: &results[i].damage); @@ -578,15 +579,15 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Super Fang uses a Pokemon's non-Dynamax HP", s16 d SINGLE_BATTLE_TEST("(DYNAMAX) Pain Split uses a Pokemon's non-Dynamax HP", s16 damage) { - bool32 dynamax; - PARAMETRIZE { dynamax = TRUE; } - PARAMETRIZE { dynamax = FALSE; } + u32 dynamax; + PARAMETRIZE { dynamax = GIMMICK_NONE; } + PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; } GIVEN { ASSUME(gMovesInfo[MOVE_PAIN_SPLIT].effect == EFFECT_PAIN_SPLIT); PLAYER(SPECIES_WOBBUFFET) { Speed(50); } OPPONENT(SPECIES_WOBBUFFET) { HP(1); Speed(100); } } WHEN { - TURN { MOVE(opponent, MOVE_PAIN_SPLIT); MOVE(player, MOVE_TACKLE, dynamax: dynamax); } + TURN { MOVE(opponent, MOVE_PAIN_SPLIT); MOVE(player, MOVE_TACKLE, gimmick: dynamax); } } SCENE { MESSAGE("Foe Wobbuffet used Pain Split!"); HP_BAR(player, captureDamage: &results[i].damage); @@ -597,16 +598,16 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Pain Split uses a Pokemon's non-Dynamax HP", s16 d SINGLE_BATTLE_TEST("(DYNAMAX) Sitrus Berries heal based on a Pokemon's non-Dynamax HP", s16 damage) { - bool32 dynamax; - PARAMETRIZE { dynamax = TRUE; } - PARAMETRIZE { dynamax = FALSE; } + u32 dynamax; + PARAMETRIZE { dynamax = GIMMICK_NONE; } + PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; } GIVEN { ASSUME(I_SITRUS_BERRY_HEAL >= GEN_4); ASSUME(gItemsInfo[ITEM_SITRUS_BERRY].holdEffect == HOLD_EFFECT_RESTORE_PCT_HP); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); } } WHEN { - TURN { MOVE(opponent, MOVE_FLING); MOVE(player, MOVE_TACKLE, dynamax: dynamax); } + TURN { MOVE(opponent, MOVE_FLING); MOVE(player, MOVE_TACKLE, gimmick: dynamax); } } SCENE { MESSAGE("Wobbuffet's Sitrus Berry restored health!"); HP_BAR(player, captureDamage: &results[i].damage); @@ -617,15 +618,15 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Sitrus Berries heal based on a Pokemon's non-Dynam SINGLE_BATTLE_TEST("(DYNAMAX) Heal Pulse heals based on a Pokemon's non-Dynamax HP", s16 damage) { - bool32 dynamax; - PARAMETRIZE { dynamax = TRUE; } - PARAMETRIZE { dynamax = FALSE; } + u32 dynamax; + PARAMETRIZE { dynamax = GIMMICK_NONE; } + PARAMETRIZE { dynamax = GIMMICK_DYNAMAX; } GIVEN { ASSUME(gMovesInfo[MOVE_HEAL_PULSE].effect == EFFECT_HEAL_PULSE); PLAYER(SPECIES_WOBBUFFET) { HP(1); Speed(50); } OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); Speed(100); } } WHEN { - TURN { MOVE(opponent, MOVE_HEAL_PULSE); MOVE(player, MOVE_TACKLE, dynamax: dynamax); } + TURN { MOVE(opponent, MOVE_HEAL_PULSE); MOVE(player, MOVE_TACKLE, gimmick: dynamax); } } SCENE { MESSAGE("Foe Wobbuffet used Heal Pulse!"); HP_BAR(player, captureDamage: &results[i].damage); @@ -643,7 +644,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Strike lowers single opponent's speed") OPPONENT(SPECIES_WOBBUFFET) { Speed(100); } PLAYER(SPECIES_WOBBUFFET) { Speed(80); } } WHEN { - TURN { MOVE(opponent, MOVE_TACKLE); MOVE(player, MOVE_TACKLE, dynamax: TRUE); } + TURN { MOVE(opponent, MOVE_TACKLE); MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); } TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_TACKLE); } } SCENE { // turn 1 @@ -669,7 +670,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) Max Strike lowers both opponents' speed") OPPONENT(SPECIES_WOBBUFFET) {Speed(100); } OPPONENT(SPECIES_WOBBUFFET) { Speed(99); } } WHEN { - TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft, dynamax: TRUE); \ + TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft, gimmick: GIMMICK_DYNAMAX); \ MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft); \ MOVE(opponentRight, MOVE_TACKLE, target: playerLeft); } TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft); \ @@ -708,7 +709,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) Max Knuckle raises both allies' attack") OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WYNAUT); } WHEN { - TURN { MOVE(playerLeft, MOVE_CLOSE_COMBAT, target: opponentLeft, dynamax: TRUE); \ + TURN { MOVE(playerLeft, MOVE_CLOSE_COMBAT, target: opponentLeft, gimmick: GIMMICK_DYNAMAX); \ MOVE(playerRight, MOVE_TACKLE, target: opponentRight); } TURN { MOVE(playerLeft, MOVE_CLOSE_COMBAT, target: opponentLeft); \ MOVE(playerRight, MOVE_TACKLE, target: opponentRight); } @@ -746,7 +747,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Flare sets up sunlight") OPPONENT(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_EMBER, dynamax: TRUE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_EMBER, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_CELEBRATE); } } SCENE { MESSAGE("Wobbuffet used Max Flare!"); MESSAGE("The sunlight got bright!"); @@ -762,7 +763,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Geyser sets up heavy rain") OPPONENT(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_WATER_GUN, dynamax: TRUE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_WATER_GUN, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_CELEBRATE); } } SCENE { MESSAGE("Wobbuffet used Max Geyser!"); MESSAGE("It started to rain!"); @@ -778,7 +779,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Hailstorm sets up hail") OPPONENT(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_POWDER_SNOW, dynamax: TRUE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_POWDER_SNOW, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_CELEBRATE); } } SCENE { MESSAGE("Wobbuffet used Max Hailstorm!"); MESSAGE("It started to hail!"); @@ -794,7 +795,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Rockfall sets up a sandstorm") OPPONENT(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_ROCK_THROW, dynamax: TRUE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_ROCK_THROW, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_CELEBRATE); } } SCENE { MESSAGE("Wobbuffet used Max Rockfall!"); MESSAGE("A sandstorm brewed!"); @@ -812,7 +813,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Overgrowth sets up Grassy Terrain") OPPONENT(SPECIES_WOBBUFFET) { MaxHP(maxHP); HP(maxHP / 2); }; PLAYER(SPECIES_WOBBUFFET) { MaxHP(maxHP); HP(maxHP / 2); }; } WHEN { - TURN { MOVE(player, MOVE_VINE_WHIP, dynamax: TRUE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_VINE_WHIP, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_CELEBRATE); } TURN { MOVE(player, MOVE_VINE_WHIP); MOVE(opponent, MOVE_CELEBRATE); } } SCENE { MESSAGE("Wobbuffet used Max Overgrowth!"); @@ -831,7 +832,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Mindstorm sets up Psychic Terrain") OPPONENT(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(opponent, MOVE_EXTREME_SPEED); MOVE(player, MOVE_PSYCHIC, dynamax: TRUE); } + TURN { MOVE(opponent, MOVE_EXTREME_SPEED); MOVE(player, MOVE_PSYCHIC, gimmick: GIMMICK_DYNAMAX); } TURN { MOVE(opponent, MOVE_EXTREME_SPEED); MOVE(player, MOVE_PSYCHIC); } } SCENE { MESSAGE("Foe Wobbuffet used Extreme Speed!"); @@ -848,7 +849,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Lightning sets up Electric Terrain") OPPONENT(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_THUNDERBOLT, dynamax: TRUE); MOVE(opponent, MOVE_SPORE); } + TURN { MOVE(player, MOVE_THUNDERBOLT, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_SPORE); } } SCENE { MESSAGE("Wobbuffet used Max Lightning!"); MESSAGE("Foe Wobbuffet used Spore!"); @@ -863,7 +864,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Starfall sets up Misty Terrain") OPPONENT(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_MOONBLAST, dynamax: TRUE); MOVE(opponent, MOVE_TOXIC); } + TURN { MOVE(player, MOVE_MOONBLAST, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_TOXIC); } } SCENE { MESSAGE("Wobbuffet used Max Starfall!"); MESSAGE("Foe Wobbuffet used Toxic!"); @@ -879,7 +880,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) G-Max Stonesurge sets up Stealth Rocks") OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_LIQUIDATION, dynamax: TRUE); } + TURN { MOVE(player, MOVE_LIQUIDATION, gimmick: GIMMICK_DYNAMAX); } TURN { SWITCH(opponent, 1); } } SCENE { // turn 1 @@ -899,7 +900,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) G-Max Steelsurge sets up sharp steel") OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_HATTERENE); } WHEN { - TURN { MOVE(player, MOVE_IRON_HEAD, dynamax: TRUE); } + TURN { MOVE(player, MOVE_IRON_HEAD, gimmick: GIMMICK_DYNAMAX); } TURN { SWITCH(opponent, 1); } TURN { } // wait out Dynamax TURN { MOVE(opponent, MOVE_DEFOG); } @@ -929,7 +930,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) G-Max Hydrosnipe has fixed power and ignores abili PLAYER(SPECIES_INTELEON) { GigantamaxFactor(TRUE); } OPPONENT(SPECIES_ARCTOVISH) { Ability(ABILITY_WATER_ABSORB); } } WHEN { - TURN { MOVE(player, move, dynamax: TRUE); } + TURN { MOVE(player, move, gimmick: GIMMICK_DYNAMAX); } } SCENE { MESSAGE("Inteleon used G-Max Hydrosnipe!"); HP_BAR(opponent, captureDamage: &results[i].damage); @@ -947,7 +948,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Volt Crash paralyzes both opponents") OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WYNAUT); } WHEN { - TURN { MOVE(playerLeft, MOVE_THUNDERBOLT, target: opponentLeft, dynamax: TRUE); } + TURN { MOVE(playerLeft, MOVE_THUNDERBOLT, target: opponentLeft, gimmick: GIMMICK_DYNAMAX); } } SCENE { MESSAGE("Pikachu used G-Max Volt Crash!"); ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PRZ, opponentLeft); @@ -974,7 +975,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Stun Shock paralyzes or poisons both opponen OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WYNAUT); } WHEN { - TURN { MOVE(playerLeft, MOVE_THUNDERBOLT, target: opponentLeft, dynamax: TRUE, \ + TURN { MOVE(playerLeft, MOVE_THUNDERBOLT, target: opponentLeft, gimmick: GIMMICK_DYNAMAX, \ WITH_RNG(RNG_G_MAX_STUN_SHOCK, rng)); } } SCENE { MESSAGE("Toxtricity used G-Max Stun Shock!"); @@ -1011,7 +1012,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Stun Shock chooses statuses before consideri OPPONENT(SPECIES_GARBODOR); OPPONENT(SPECIES_TRUBBISH); } WHEN { - TURN { MOVE(playerLeft, MOVE_NUZZLE, target: opponentLeft, dynamax: TRUE, \ + TURN { MOVE(playerLeft, MOVE_NUZZLE, target: opponentLeft, gimmick: GIMMICK_DYNAMAX, \ WITH_RNG(RNG_G_MAX_STUN_SHOCK, STATUS1_POISON)); } } SCENE { MESSAGE("Toxtricity used G-Max Stun Shock!"); @@ -1044,7 +1045,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Befuddle paralyzes, poisons, or sleeps both OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(playerLeft, MOVE_BUG_BITE, target: opponentLeft, dynamax: TRUE, + TURN { MOVE(playerLeft, MOVE_BUG_BITE, target: opponentLeft, gimmick: GIMMICK_DYNAMAX, WITH_RNG(RNG_G_MAX_BEFUDDLE, rng)); } } SCENE { MESSAGE("Butterfree used G-Max Befuddle!"); @@ -1088,7 +1089,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Gold Rush confuses both opponents and genera OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft, dynamax: TRUE); } + TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft, gimmick: GIMMICK_DYNAMAX); } } SCENE { MESSAGE("Meowth used G-Max Gold Rush!"); ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, opponentLeft); @@ -1108,7 +1109,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Smite confuses both opponents") OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(playerLeft, MOVE_MOONBLAST, target: opponentLeft, dynamax: TRUE); } + TURN { MOVE(playerLeft, MOVE_MOONBLAST, target: opponentLeft, gimmick: GIMMICK_DYNAMAX); } } SCENE { MESSAGE("Hatterene used G-Max Smite!"); ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, opponentLeft); @@ -1127,7 +1128,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Cuddle infatuates both opponents, if possibl OPPONENT(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); } OPPONENT(SPECIES_WOBBUFFET) { Gender(MON_MALE); } } WHEN { - TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft, dynamax: TRUE); } + TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft, gimmick: GIMMICK_DYNAMAX); } } SCENE { MESSAGE("Eevee used G-Max Cuddle!"); ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_INFATUATION, opponentLeft); @@ -1148,7 +1149,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Terror traps both opponents") OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(playerLeft, MOVE_LICK, target: opponentLeft, dynamax: TRUE); } + TURN { MOVE(playerLeft, MOVE_LICK, target: opponentLeft, gimmick: GIMMICK_DYNAMAX); } } SCENE { MESSAGE("Gengar used G-Max Terror!"); MESSAGE("Foe Wobbuffet can't escape now!"); @@ -1169,7 +1170,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Meltdown torments both opponents for 3 turns OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SPLASH, MOVE_CELEBRATE); } OPPONENT(SPECIES_WYNAUT) { Moves(MOVE_SPLASH, MOVE_CELEBRATE); } } WHEN { - TURN { MOVE(playerLeft, MOVE_IRON_HEAD, target: opponentLeft, dynamax: TRUE); \ + TURN { MOVE(playerLeft, MOVE_IRON_HEAD, target: opponentLeft, gimmick: GIMMICK_DYNAMAX); \ MOVE(opponentLeft, MOVE_SPLASH); MOVE(opponentRight, MOVE_SPLASH); } TURN { MOVE(playerLeft, MOVE_CELEBRATE, target: opponentLeft); \ MOVE(opponentLeft, MOVE_SPLASH, allowed: FALSE); \ @@ -1207,7 +1208,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Wildfire sets a field effect that damages no OPPONENT(SPECIES_WYNAUT); OPPONENT(SPECIES_ARCANINE); } WHEN { - TURN { MOVE(playerLeft, MOVE_EMBER, target: opponentLeft, dynamax: TRUE); } + TURN { MOVE(playerLeft, MOVE_EMBER, target: opponentLeft, gimmick: GIMMICK_DYNAMAX); } TURN { } TURN { SWITCH(opponentLeft, 2); } TURN { } @@ -1256,7 +1257,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Replenish recycles allies' berries 50\% of t MOVE(playerRight, MOVE_STUFF_CHEEKS); \ MOVE(opponentLeft, MOVE_STUFF_CHEEKS); \ MOVE(opponentRight, MOVE_STUFF_CHEEKS); } - TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft, dynamax: TRUE); } + TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft, gimmick: GIMMICK_DYNAMAX); } } SCENE { // turn 1 MESSAGE("Using Apicot Berry, the Sp. Def of Snorlax rose!"); @@ -1281,7 +1282,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Snooze makes only the target drowsy") OPPONENT(SPECIES_BLISSEY); OPPONENT(SPECIES_CHANSEY); } WHEN { - TURN { MOVE(playerLeft, MOVE_DARK_PULSE, target: opponentLeft, dynamax: TRUE); } + TURN { MOVE(playerLeft, MOVE_DARK_PULSE, target: opponentLeft, gimmick: GIMMICK_DYNAMAX); } TURN { } } SCENE { // turn 1 @@ -1304,7 +1305,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Finale heals allies by 1/6 of their health") OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(playerLeft, MOVE_MOONBLAST, target: opponentLeft, dynamax: TRUE); } + TURN { MOVE(playerLeft, MOVE_MOONBLAST, target: opponentLeft, gimmick: GIMMICK_DYNAMAX); } } SCENE { MESSAGE("Alcremie used G-Max Finale!"); HP_BAR(playerLeft, captureDamage: &damage1); @@ -1324,7 +1325,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Sweetness cures allies' status conditions") OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(playerLeft, MOVE_VINE_WHIP, target: opponentLeft, dynamax: TRUE); } + TURN { MOVE(playerLeft, MOVE_VINE_WHIP, target: opponentLeft, gimmick: GIMMICK_DYNAMAX); } } SCENE { MESSAGE("Appletun used G-Max Sweetness!"); STATUS_ICON(playerLeft, none: TRUE); @@ -1345,7 +1346,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Centiferno traps both opponents in Fire Spin OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WYNAUT); } WHEN { - TURN { MOVE(playerLeft, MOVE_FLAME_CHARGE, target: opponentLeft, dynamax: TRUE); } + TURN { MOVE(playerLeft, MOVE_FLAME_CHARGE, target: opponentLeft, gimmick: GIMMICK_DYNAMAX); } TURN { SWITCH(playerLeft, 2); } } SCENE { // turn 1 @@ -1373,7 +1374,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Chi Strike boosts allies' crit chance") OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(playerLeft, MOVE_FORCE_PALM, target: opponentLeft, dynamax: TRUE); } + TURN { MOVE(playerLeft, MOVE_FORCE_PALM, target: opponentLeft, gimmick: GIMMICK_DYNAMAX); } TURN { MOVE(playerLeft, MOVE_FORCE_PALM, target: opponentLeft); } TURN { MOVE(playerLeft, MOVE_FORCE_PALM, target: opponentLeft); \ MOVE(playerRight, MOVE_FOCUS_ENERGY); } @@ -1406,7 +1407,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Depletion takes away 2 PP from the target's OPPONENT(SPECIES_SABLEYE) { Ability(ABILITY_PRANKSTER); } OPPONENT(SPECIES_WYNAUT); } WHEN { - TURN { MOVE(playerLeft, MOVE_DRAGON_CLAW, target: opponentLeft, dynamax: TRUE); } + TURN { MOVE(playerLeft, MOVE_DRAGON_CLAW, target: opponentLeft, gimmick: GIMMICK_DYNAMAX); } } SCENE { MESSAGE("Foe Sableye used Celebrate!"); MESSAGE("Duraludon used G-Max Depletion!"); @@ -1428,11 +1429,11 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max One Blow bypasses Max Guard for full damage" OPPONENT(SPECIES_WYNAUT); } WHEN { if (protect) - TURN { MOVE(playerLeft, MOVE_WICKED_BLOW, target: opponentLeft, dynamax: TRUE); \ - MOVE(opponentLeft, MOVE_PROTECT, dynamax: TRUE); } + TURN { MOVE(playerLeft, MOVE_WICKED_BLOW, target: opponentLeft, gimmick: GIMMICK_DYNAMAX); \ + MOVE(opponentLeft, MOVE_PROTECT, gimmick: GIMMICK_DYNAMAX); } else - TURN { MOVE(playerLeft, MOVE_WICKED_BLOW, target: opponentLeft, dynamax: TRUE); \ - MOVE(opponentLeft, MOVE_PSYCHIC, target: playerLeft, dynamax: TRUE); } + TURN { MOVE(playerLeft, MOVE_WICKED_BLOW, target: opponentLeft, gimmick: GIMMICK_DYNAMAX); \ + MOVE(opponentLeft, MOVE_PSYCHIC, target: playerLeft, gimmick: GIMMICK_DYNAMAX); } } SCENE { if (protect) MESSAGE("Foe Wobbuffet used Max Guard!"); @@ -1453,8 +1454,8 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) Max Flare doesn't softlock the game when fainting OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(playerLeft, MOVE_PROTECT, dynamax: TRUE); - MOVE(opponentLeft, MOVE_V_CREATE, target: playerRight, dynamax: TRUE); + TURN { MOVE(playerLeft, MOVE_PROTECT, gimmick: GIMMICK_DYNAMAX); + MOVE(opponentLeft, MOVE_V_CREATE, target: playerRight, gimmick: GIMMICK_DYNAMAX); SEND_OUT(playerRight, 2); } TURN { } } @@ -1466,7 +1467,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Moves don't execute effects on fainted battler PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET) { HP(1); }; } WHEN { - TURN { MOVE(player, MOVE_TACKLE, dynamax: TRUE); } + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); } } SCENE { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_DYNAMAX_GROWTH, player); MESSAGE("Wobbuffet used Max Strike!"); @@ -1483,7 +1484,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Moxie clones can be triggered by Max Moves faintin OPPONENT(SPECIES_WOBBUFFET) { HP(1); } OPPONENT(SPECIES_WYNAUT); } WHEN { - TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_WATERFALL, dynamax: TRUE); SEND_OUT(opponent, 1); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_WATERFALL, gimmick: GIMMICK_DYNAMAX); SEND_OUT(opponent, 1); } } SCENE { MESSAGE("Foe Wobbuffet fainted!"); ABILITY_POPUP(player, ABILITY_MOXIE); diff --git a/test/battle/gimmick/terastal.c b/test/battle/gimmick/terastal.c index 5b53eb6373..b3925fcbae 100644 --- a/test/battle/gimmick/terastal.c +++ b/test/battle/gimmick/terastal.c @@ -6,13 +6,13 @@ SINGLE_BATTLE_TEST("(TERA) Terastallizing into a different type preserves other STAB boosts", s16 damage1, s16 damage2) { bool32 tera; - PARAMETRIZE { tera = FALSE; } - PARAMETRIZE { tera = TRUE; } + PARAMETRIZE { tera = GIMMICK_NONE; } + PARAMETRIZE { tera = GIMMICK_TERA; } GIVEN { PLAYER(SPECIES_BULBASAUR) { TeraType(TYPE_NORMAL); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_VINE_WHIP, tera: tera); } + TURN { MOVE(player, MOVE_VINE_WHIP, gimmick: tera); } TURN { MOVE(player, MOVE_SLUDGE_BOMB); } } SCENE { MESSAGE("Bulbasaur used Vine Whip!"); @@ -30,13 +30,13 @@ SINGLE_BATTLE_TEST("(TERA) Terastallizing into a different type preserves other SINGLE_BATTLE_TEST("(TERA) Terastallizing does not affect the power of non-STAB moves", s16 damage) { bool32 tera; - PARAMETRIZE { tera = FALSE; } - PARAMETRIZE { tera = TRUE; } + PARAMETRIZE { tera = GIMMICK_NONE; } + PARAMETRIZE { tera = GIMMICK_TERA; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_PSYCHIC); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_HEADBUTT, tera: tera); } + TURN { MOVE(player, MOVE_HEADBUTT, gimmick: tera); } } SCENE { MESSAGE("Wobbuffet used Headbutt!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_HEADBUTT, player); @@ -49,13 +49,13 @@ SINGLE_BATTLE_TEST("(TERA) Terastallizing does not affect the power of non-STAB SINGLE_BATTLE_TEST("(TERA) Terastallizing into a different type gives that type 1.5x STAB", s16 damage) { bool32 tera; - PARAMETRIZE { tera = FALSE; } - PARAMETRIZE { tera = TRUE; } + PARAMETRIZE { tera = GIMMICK_NONE; } + PARAMETRIZE { tera = GIMMICK_TERA; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_NORMAL); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_HEADBUTT, tera: tera); } + TURN { MOVE(player, MOVE_HEADBUTT, gimmick: tera); } } SCENE { MESSAGE("Wobbuffet used Headbutt!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_HEADBUTT, player); @@ -69,13 +69,13 @@ SINGLE_BATTLE_TEST("(TERA) Terastallizing into a different type gives that type SINGLE_BATTLE_TEST("(TERA) Terastallizing into the same type gives that type 2x STAB", s16 damage) { bool32 tera; - PARAMETRIZE { tera = FALSE; } - PARAMETRIZE { tera = TRUE; } + PARAMETRIZE { tera = GIMMICK_NONE; } + PARAMETRIZE { tera = GIMMICK_TERA; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_PSYCHIC); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_PSYCHIC, tera: tera); } + TURN { MOVE(player, MOVE_PSYCHIC, gimmick: tera); } } SCENE { MESSAGE("Wobbuffet used Psychic!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCHIC, player); @@ -89,13 +89,13 @@ 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 = FALSE; } - PARAMETRIZE { tera = TRUE; } + 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, tera: tera); } + TURN { MOVE(player, MOVE_HEADBUTT, gimmick: tera); } } SCENE { MESSAGE("Crawdaunt used Headbutt!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_HEADBUTT, player); @@ -109,13 +109,13 @@ SINGLE_BATTLE_TEST("(TERA) Terastallizing into a different type with Adaptabilit SINGLE_BATTLE_TEST("(TERA) Terastallizing into the same type with Adaptability gives 2.25x STAB", s16 damage) { bool32 tera; - PARAMETRIZE { tera = FALSE; } - PARAMETRIZE { tera = TRUE; } + 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, tera: tera); } + TURN { MOVE(player, MOVE_WATER_PULSE, gimmick: tera); } } SCENE { MESSAGE("Crawdaunt used Water Pulse!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PULSE, player); @@ -129,14 +129,14 @@ SINGLE_BATTLE_TEST("(TERA) Terastallizing into the same type with Adaptability g SINGLE_BATTLE_TEST("(TERA) Terastallizing boosts moves of the same type to 60 BP", s16 damage) { bool32 tera; - PARAMETRIZE { tera = FALSE; } - PARAMETRIZE { tera = TRUE; } + PARAMETRIZE { tera = GIMMICK_NONE; } + PARAMETRIZE { tera = GIMMICK_TERA; } GIVEN { ASSUME(gMovesInfo[MOVE_ABSORB].power == 20); PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_GRASS); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_ABSORB, tera: tera); } + TURN { MOVE(player, MOVE_ABSORB, gimmick: tera); } } SCENE { MESSAGE("Wobbuffet used Absorb!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_ABSORB, player); @@ -150,14 +150,14 @@ SINGLE_BATTLE_TEST("(TERA) Terastallizing boosts moves of the same type to 60 BP SINGLE_BATTLE_TEST("(TERA) Terastallization's 60 BP floor occurs after Technician", s16 damage) { bool32 tera; - PARAMETRIZE { tera = FALSE; } - PARAMETRIZE { tera = TRUE; } + PARAMETRIZE { tera = GIMMICK_NONE; } + PARAMETRIZE { tera = GIMMICK_TERA; } GIVEN { ASSUME(gMovesInfo[MOVE_MEGA_DRAIN].power == 40); PLAYER(SPECIES_MR_MIME) { Ability(ABILITY_TECHNICIAN); TeraType(TYPE_GRASS); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_MEGA_DRAIN, tera: tera); } + TURN { MOVE(player, MOVE_MEGA_DRAIN, gimmick: tera); } } SCENE { MESSAGE("Mr. Mime used Mega Drain!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_MEGA_DRAIN, player); @@ -171,13 +171,13 @@ SINGLE_BATTLE_TEST("(TERA) Terastallization's 60 BP floor occurs after Technicia SINGLE_BATTLE_TEST("(TERA) Terastallization's 60 BP floor occurs after Technician", s16 damage) { bool32 tera; - PARAMETRIZE { tera = FALSE; } - PARAMETRIZE { tera = TRUE; } + PARAMETRIZE { tera = GIMMICK_NONE; } + PARAMETRIZE { tera = GIMMICK_TERA; } GIVEN { PLAYER(SPECIES_MR_MIME) { Ability(ABILITY_TECHNICIAN); TeraType(TYPE_PSYCHIC); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_STORED_POWER, tera: tera); } + TURN { MOVE(player, MOVE_STORED_POWER, gimmick: tera); } } SCENE { MESSAGE("Mr. Mime used Stored Power!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_STORED_POWER, player); @@ -191,13 +191,13 @@ SINGLE_BATTLE_TEST("(TERA) Terastallization's 60 BP floor occurs after Technicia SINGLE_BATTLE_TEST("(TERA) Terastallization's 60 BP floor does not apply to multi-hit moves", s16 damage) { bool32 tera; - PARAMETRIZE { tera = FALSE; } - PARAMETRIZE { tera = TRUE; } + PARAMETRIZE { tera = GIMMICK_NONE; } + PARAMETRIZE { tera = GIMMICK_TERA; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_NORMAL); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_FURY_SWIPES, tera: tera); } + TURN { MOVE(player, MOVE_FURY_SWIPES, gimmick: tera); } } SCENE { MESSAGE("Wobbuffet used Fury Swipes!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, player); @@ -210,13 +210,13 @@ SINGLE_BATTLE_TEST("(TERA) Terastallization's 60 BP floor does not apply to mult SINGLE_BATTLE_TEST("(TERA) Terastallization's 60 BP floor does not apply to priority moves", s16 damage) { bool32 tera; - PARAMETRIZE { tera = FALSE; } - PARAMETRIZE { tera = TRUE; } + PARAMETRIZE { tera = GIMMICK_NONE; } + PARAMETRIZE { tera = GIMMICK_TERA; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_NORMAL); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_QUICK_ATTACK, tera: tera); } + TURN { MOVE(player, MOVE_QUICK_ATTACK, gimmick: tera); } } SCENE { MESSAGE("Wobbuffet used Quick Attack!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player); @@ -231,13 +231,13 @@ SINGLE_BATTLE_TEST("(TERA) Terastallization's 60 BP floor does not apply to prio SINGLE_BATTLE_TEST("(TERA) Terastallization changes type effectiveness", s16 damage) { bool32 tera; - PARAMETRIZE { tera = FALSE; } - PARAMETRIZE { tera = TRUE; } + PARAMETRIZE { tera = GIMMICK_NONE; } + PARAMETRIZE { tera = GIMMICK_TERA; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_GRASS); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, tera: tera); MOVE(opponent, MOVE_WATER_GUN); } + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: tera); MOVE(opponent, MOVE_WATER_GUN); } } SCENE { MESSAGE("Foe Wobbuffet used Water Gun!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); @@ -253,7 +253,7 @@ SINGLE_BATTLE_TEST("(TERA) Terastallization changes type effectiveness") PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_FLYING); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, tera: TRUE); MOVE(opponent, MOVE_EARTHQUAKE); } + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_EARTHQUAKE); } } SCENE { MESSAGE("Foe Wobbuffet used Earthquake!"); MESSAGE("It doesn't affect Wobbuffet…"); @@ -268,7 +268,7 @@ SINGLE_BATTLE_TEST("(TERA) Terastallization persists across switches") PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, tera: TRUE); MOVE(opponent, MOVE_EARTHQUAKE); } + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_EARTHQUAKE); } TURN { SWITCH(player, 1); } TURN { SWITCH(player, 0); } TURN { MOVE(opponent, MOVE_EARTHQUAKE); } @@ -292,7 +292,7 @@ SINGLE_BATTLE_TEST("(TERA) Terastallization changes the effect of Curse") PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_GHOST); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_CURSE, tera: TRUE); } + TURN { MOVE(player, MOVE_CURSE, gimmick: GIMMICK_TERA); } } SCENE { MESSAGE("Wobbuffet used Curse!"); HP_BAR(player); @@ -307,7 +307,7 @@ SINGLE_BATTLE_TEST("(TERA) Roost does not remove the user's Flying type while Te PLAYER(SPECIES_ZAPDOS) { HP(1); TeraType(TYPE_FLYING); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_ROOST, tera: TRUE); MOVE(opponent, MOVE_ICE_BEAM); } + TURN { MOVE(player, MOVE_ROOST, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_ICE_BEAM); } } SCENE { MESSAGE("Zapdos used Roost!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); @@ -327,7 +327,7 @@ SINGLE_BATTLE_TEST("(TERA) Type-changing moves fail against a Terastallized Poke PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_PSYCHIC); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, tera: TRUE); MOVE(opponent, move); } + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); MOVE(opponent, move); } } SCENE { if (move != MOVE_SOAK) NOT { ANIMATION(ANIM_TYPE_MOVE, move, opponent); } @@ -341,7 +341,7 @@ SINGLE_BATTLE_TEST("(TERA) Reflect Type fails if used by a Terastallized Pokemon PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_PSYCHIC); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_REFLECT_TYPE, tera: TRUE); } + TURN { MOVE(player, MOVE_REFLECT_TYPE, gimmick: GIMMICK_TERA); } } SCENE { MESSAGE("Wobbuffet used Reflect Type!"); MESSAGE("But it failed!"); @@ -354,7 +354,7 @@ SINGLE_BATTLE_TEST("(TERA) Conversion fails if used by a Terastallized Pokemon") PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_PSYCHIC); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_CONVERSION, tera: TRUE); } + TURN { MOVE(player, MOVE_CONVERSION, gimmick: GIMMICK_TERA); } } SCENE { MESSAGE("Wobbuffet used Conversion!"); MESSAGE("But it failed!"); @@ -368,7 +368,7 @@ SINGLE_BATTLE_TEST("(TERA) Conversion2 fails if used by a Terastallized Pokemon" OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(opponent, MOVE_TACKLE); } - TURN { MOVE(player, MOVE_CONVERSION_2, tera: TRUE); } + TURN { MOVE(player, MOVE_CONVERSION_2, gimmick: GIMMICK_TERA); } } SCENE { MESSAGE("Wobbuffet used Conversion 2!"); MESSAGE("But it failed!"); @@ -381,7 +381,7 @@ SINGLE_BATTLE_TEST("(TERA) Reflect Type copies a Terastallized Pokemon's Tera Ty PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_GHOST); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE, tera: TRUE); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } TURN { MOVE(opponent, MOVE_REFLECT_TYPE); } TURN { MOVE(player, MOVE_TACKLE); } } SCENE { @@ -401,8 +401,8 @@ SINGLE_BATTLE_TEST("(TERA) Synchronoise uses a Terastallized Pokemon's Tera Type PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_GHOST); } OPPONENT(SPECIES_WOBBUFFET) { TeraType(TYPE_GHOST); } } WHEN { - TURN { MOVE(opponent, MOVE_SYNCHRONOISE); MOVE(player, MOVE_CELEBRATE, tera: TRUE); } - TURN { MOVE(opponent, MOVE_SYNCHRONOISE, tera: TRUE); } + TURN { MOVE(opponent, MOVE_SYNCHRONOISE); MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } + TURN { MOVE(opponent, MOVE_SYNCHRONOISE, gimmick: GIMMICK_TERA); } } SCENE { // turn 1 MESSAGE("Foe Wobbuffet used Synchronoise!"); @@ -420,7 +420,7 @@ SINGLE_BATTLE_TEST("(TERA) Revelation Dance uses a Terastallized Pokemon's Tera PLAYER(SPECIES_ORICORIO) { TeraType(TYPE_NORMAL); } OPPONENT(SPECIES_GENGAR); } WHEN { - TURN { MOVE(player, MOVE_REVELATION_DANCE, tera: TRUE); } + TURN { MOVE(player, MOVE_REVELATION_DANCE, gimmick: GIMMICK_TERA); } } SCENE { MESSAGE("Oricorio used Revelation Dance!"); MESSAGE("It doesn't affect Foe Gengar…"); @@ -439,7 +439,7 @@ SINGLE_BATTLE_TEST("(TERA) Double Shock does not remove the user's Electric type OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(player, MOVE_DOUBLE_SHOCK); MOVE(opponent, MOVE_RECOVER); } - TURN { MOVE(player, MOVE_DOUBLE_SHOCK, tera: TRUE); MOVE(opponent, MOVE_RECOVER); } + TURN { MOVE(player, MOVE_DOUBLE_SHOCK, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_RECOVER); } TURN { MOVE(player, MOVE_DOUBLE_SHOCK); MOVE(opponent, MOVE_RECOVER); } TURN { SWITCH(player, 1); MOVE(opponent, MOVE_RECOVER); } TURN { SWITCH(player, 0); MOVE(opponent, MOVE_RECOVER); } @@ -479,9 +479,9 @@ SINGLE_BATTLE_TEST("(TERA) Transform does not copy the target's Tera Type, and i PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_TACKLE, MOVE_EARTHQUAKE); TeraType(TYPE_GHOST); } OPPONENT(SPECIES_DITTO) { TeraType(TYPE_FLYING); } } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, tera: TRUE); MOVE(opponent, MOVE_TRANSFORM); } + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_TRANSFORM); } TURN { MOVE(player, MOVE_EARTHQUAKE); } - // TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_TACKLE, target: player, tera: TRUE); } + // TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_TACKLE, target: player, gimmick: GIMMICK_TERA); } } SCENE { // turn 2 MESSAGE("Wobbuffet used Earthquake!"); @@ -498,13 +498,13 @@ SINGLE_BATTLE_TEST("(TERA) Transform does not copy the target's Tera Type, and i SINGLE_BATTLE_TEST("(TERA) Stellar type does not change the user's defensive profile", s16 damage) { bool32 tera; - PARAMETRIZE { tera = FALSE; } - PARAMETRIZE { tera = TRUE; } + PARAMETRIZE { tera = GIMMICK_NONE; } + PARAMETRIZE { tera = GIMMICK_TERA; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, tera: tera); MOVE(opponent, MOVE_PSYCHIC); } + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: tera); MOVE(opponent, MOVE_PSYCHIC); } } SCENE { MESSAGE("Foe Wobbuffet used Psychic!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCHIC, opponent); @@ -520,7 +520,7 @@ SINGLE_BATTLE_TEST("(TERA) Reflect Type copies a Stellar-type Pokemon's base typ PLAYER(SPECIES_BANETTE) { TeraType(TYPE_STELLAR); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE, tera: TRUE); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } TURN { MOVE(opponent, MOVE_REFLECT_TYPE); } TURN { MOVE(player, MOVE_TACKLE); } } SCENE { @@ -541,7 +541,7 @@ SINGLE_BATTLE_TEST("(TERA) Revelation Dance uses a Stellar-type Pokemon's base t PLAYER(SPECIES_ORICORIO_SENSU) { TeraType(TYPE_STELLAR); } OPPONENT(SPECIES_GUMSHOOS); } WHEN { - TURN { MOVE(player, MOVE_REVELATION_DANCE, tera: TRUE); } + TURN { MOVE(player, MOVE_REVELATION_DANCE, gimmick: GIMMICK_TERA); } } SCENE { MESSAGE("Oricorio used Revelation Dance!"); MESSAGE("It doesn't affect Foe Gumshoos…"); @@ -555,7 +555,7 @@ SINGLE_BATTLE_TEST("(TERA) Conversion2 fails if last hit by a Stellar-type move" PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TERA_BLAST, tera: TRUE); } + TURN { MOVE(player, MOVE_TERA_BLAST, gimmick: GIMMICK_TERA); } TURN { MOVE(opponent, MOVE_CONVERSION_2); } } SCENE { // turn 1 @@ -573,7 +573,7 @@ SINGLE_BATTLE_TEST("(TERA) Roost does not remove Flying-type ground immunity whe PLAYER(SPECIES_ZAPDOS) { HP(1); TeraType(TYPE_STELLAR); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_ROOST, tera: TRUE); MOVE(opponent, MOVE_ICE_BEAM); } + TURN { MOVE(player, MOVE_ROOST, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_ICE_BEAM); } } SCENE { MESSAGE("Zapdos used Roost!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); @@ -591,7 +591,7 @@ SINGLE_BATTLE_TEST("(TERA) Terastallizing into the Stellar-type provides a one-t OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(player, MOVE_EXTRASENSORY); } - TURN { MOVE(player, MOVE_EXTRASENSORY, tera: TRUE); } + TURN { MOVE(player, MOVE_EXTRASENSORY, gimmick: GIMMICK_TERA); } TURN { MOVE(player, MOVE_EXTRASENSORY); } } SCENE { // turn 1 @@ -621,7 +621,7 @@ SINGLE_BATTLE_TEST("(TERA) Terastallizing into the Stellar-type provides a one-t OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(player, MOVE_TAKE_DOWN); } - TURN { MOVE(player, MOVE_TAKE_DOWN, tera: TRUE); } + TURN { MOVE(player, MOVE_TAKE_DOWN, gimmick: GIMMICK_TERA); } TURN { MOVE(player, MOVE_TAKE_DOWN); } } SCENE { // turn 1 @@ -652,7 +652,7 @@ SINGLE_BATTLE_TEST("(TERA) Terastallizing into the Stellar type boosts all moves OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(player, MOVE_MEGA_DRAIN); } - TURN { MOVE(player, MOVE_MEGA_DRAIN, tera: TRUE); } + TURN { MOVE(player, MOVE_MEGA_DRAIN, gimmick: GIMMICK_TERA); } TURN { MOVE(player, MOVE_MEGA_DRAIN); } TURN { MOVE(player, MOVE_BUBBLE); } } SCENE { @@ -686,7 +686,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, tera: TRUE); + TURN { MOVE(player, MOVE_BUBBLE, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_EMBER); } } SCENE { MESSAGE("Greninja used Bubble!"); @@ -702,7 +702,7 @@ SINGLE_BATTLE_TEST("(TERA) Status moves don't expend Stellar's one-time type boo PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_GROWL, tera: TRUE); } + TURN { MOVE(player, MOVE_GROWL, gimmick: GIMMICK_TERA); } TURN { MOVE(player, MOVE_TAKE_DOWN); } TURN { MOVE(player, MOVE_TAKE_DOWN); } } SCENE { @@ -730,7 +730,7 @@ SINGLE_BATTLE_TEST("(TERA) Stellar type's one-time boost factors in dynamically- PLAYER(SPECIES_PELIPPER) { Ability(ABILITY_DRIZZLE); TeraType(TYPE_STELLAR); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_WEATHER_BALL, tera: TRUE); MOVE(opponent, MOVE_RECOVER); } + TURN { MOVE(player, MOVE_WEATHER_BALL, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_RECOVER); } TURN { MOVE(player, MOVE_TAKE_DOWN); MOVE(opponent, MOVE_RECOVER); } TURN { MOVE(player, MOVE_TAKE_DOWN); MOVE(opponent, MOVE_RECOVER); } TURN { MOVE(player, MOVE_WATER_PULSE); MOVE(opponent, MOVE_RECOVER); } @@ -790,7 +790,7 @@ SINGLE_BATTLE_TEST("(TERA) All type indicators function correctly") PLAYER(SPECIES_WOBBUFFET) { TeraType(type); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, tera: TRUE); } + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } } } @@ -806,7 +806,7 @@ SINGLE_BATTLE_TEST("(TERA) Pokemon with Tera forms change upon Terastallizing") PLAYER(species); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, tera: TRUE); } + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } } THEN { EXPECT_EQ(player->species, targetSpecies); } diff --git a/test/battle/gimmick/zmove.c b/test/battle/gimmick/zmove.c new file mode 100644 index 0000000000..bde241bfd7 --- /dev/null +++ b/test/battle/gimmick/zmove.c @@ -0,0 +1,601 @@ +#include "global.h" +#include "test/battle.h" +#include "constants/battle_z_move_effects.h" + +// Basic Functionality +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Moves do not retain priority") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_QUICK_ATTACK].type == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); + MOVE(player, MOVE_QUICK_ATTACK, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Moves are not affected by -ate abilities") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].type == TYPE_NORMAL); + ASSUME(gSpeciesInfo[SPECIES_SWELLOW].types[1] == TYPE_FLYING); + PLAYER(SPECIES_AURORUS) { Ability(ABILITY_REFRIGERATE); Item(ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_SWELLOW); + } WHEN { + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + NOT { MESSAGE("It's super effective!"); } + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Moves are affected by Ion Deluge") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].type == TYPE_NORMAL); + ASSUME(gMovesInfo[MOVE_ION_DELUGE].effect == EFFECT_ION_DELUGE); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_SWELLOW); + } WHEN { + TURN { MOVE(opponent, MOVE_ION_DELUGE); MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Moves deal 1/4 damage through protect", s16 damage) +{ + bool32 protected; + PARAMETRIZE { protected = TRUE; } + PARAMETRIZE { protected = FALSE; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].type == TYPE_NORMAL); + ASSUME(gMovesInfo[MOVE_PROTECT].effect == EFFECT_PROTECT); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (protected) + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_Z_MOVE); MOVE(opponent, MOVE_PROTECT); } + else + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(4), results[1].damage); + } +} + +// Status Z-Effects +SINGLE_BATTLE_TEST("(Z-MOVE) Z_EFFECT_RESET_STATS clears a battler's negative stat stages") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_LEECH_SEED].zMove.effect == Z_EFFECT_RESET_STATS); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_GRASSIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SWORDS_DANCE); MOVE(opponent, MOVE_SCREECH); } + TURN { MOVE(player, MOVE_LEECH_SEED, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_LEECH_SEED, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z_EFFECT_ALL_STATS_UP raises all of a battler's stat stages by one") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_CELEBRATE].type == TYPE_NORMAL); + ASSUME(gMovesInfo[MOVE_CELEBRATE].zMove.effect == Z_EFFECT_ALL_STATS_UP_1); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z_EFFECT_BOOST_CRITS raises a battler's critical hit ratio") +{ + PASSES_RANDOMLY(1, 2, RNG_CRITICAL_HIT); + GIVEN { + ASSUME(gMovesInfo[MOVE_FORESIGHT].type == TYPE_NORMAL); + ASSUME(gMovesInfo[MOVE_FORESIGHT].zMove.effect == Z_EFFECT_BOOST_CRITS); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FORESIGHT, gimmick: GIMMICK_Z_MOVE); } + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FORESIGHT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + MESSAGE("A critical hit!"); + } +} + +DOUBLE_BATTLE_TEST("(Z-MOVE) Z_EFFECT_FOLLOW_ME redirects attacks to the user") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_DESTINY_BOND].type == TYPE_GHOST); + ASSUME(gMovesInfo[MOVE_DESTINY_BOND].zMove.effect == Z_EFFECT_FOLLOW_ME); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_GHOSTIUM_Z); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_DESTINY_BOND, gimmick: GIMMICK_Z_MOVE); + MOVE(opponentLeft, MOVE_TACKLE, target: playerRight); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DESTINY_BOND, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft); + NOT { HP_BAR(playerRight); } + HP_BAR(playerLeft); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z_EFFECT_RESTORE_REPLACEMENT_HP fully heals the replacement battler's HP") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_PARTING_SHOT].type == TYPE_DARK); + ASSUME(gMovesInfo[MOVE_PARTING_SHOT].zMove.effect == Z_EFFECT_RESTORE_REPLACEMENT_HP); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_DARKINIUM_Z); } + PLAYER(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PARTING_SHOT, gimmick: GIMMICK_Z_MOVE); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, player); + HP_BAR(player); + } THEN { + EXPECT_EQ(player->species, SPECIES_WYNAUT); + EXPECT_EQ(player->hp, player->maxHP); + } +} + +// This tests the functionality of Z_EFFECT_RECOVER_HP and Z_EFFECT_ATK_UP_1 (and thus by extension all stat-up Z-effects) +SINGLE_BATTLE_TEST("(Z-MOVE) Z_EFFECT_CURSE activates Z_EFFECT_RECOVER_HP or Z_EFFECT_ATK_UP_1 depending on the type of the battler") +{ + u32 species; + PARAMETRIZE { species = SPECIES_WOBBUFFET; } + PARAMETRIZE { species = SPECIES_DUSCLOPS; } + GIVEN { + ASSUME(gMovesInfo[MOVE_CURSE].type == TYPE_GHOST); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[0] != TYPE_GHOST); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[1] != TYPE_GHOST); + ASSUME(gSpeciesInfo[SPECIES_DUSCLOPS].types[0] == TYPE_GHOST); + ASSUME(gMovesInfo[MOVE_CURSE].zMove.effect == Z_EFFECT_CURSE); + PLAYER(species) { Item(ITEM_GHOSTIUM_Z); HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CURSE, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + if (species == SPECIES_DUSCLOPS) { + HP_BAR(player); + NOT { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); } + ANIMATION(ANIM_TYPE_MOVE, MOVE_CURSE, player); + HP_BAR(player); + } else { + NOT { HP_BAR(player); } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CURSE, player); + NOT { HP_BAR(player); } + } + } THEN { + if (species == SPECIES_DUSCLOPS) { + EXPECT_MUL_EQ(player->maxHP, UQ_4_12(0.50), player->hp); // heal to full HP then cut by half + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } else { + EXPECT_EQ(player->hp, 1); + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); // +1 from Curse, +1 from Z-Effect + } + } +} + +// Specific Z-Move Interactions +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Mirror Move raises the user's attack by two stages and copies the last used non-status move as a Z-Move") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_MIRROR_MOVE].type == TYPE_FLYING); + ASSUME(gMovesInfo[MOVE_MIRROR_MOVE].zMove.effect == Z_EFFECT_ATK_UP_2); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_FLYINIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); MOVE(player, MOVE_MIRROR_MOVE, gimmick: GIMMICK_Z_MOVE); } + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + // extra turn to make sure that everything resets properly + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Mirror Move raises the user's attack by two stages and copies the last used status move regularly") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_MIRROR_MOVE].type == TYPE_FLYING); + ASSUME(gMovesInfo[MOVE_MIRROR_MOVE].zMove.effect == Z_EFFECT_ATK_UP_2); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_FLYINIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCREECH); MOVE(player, MOVE_MIRROR_MOVE, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCREECH, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCREECH, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); // Z-Screech would cause an additional attack stat stage (reaching +3) + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Copycat raises the user's accuracy by one stage and copies the last used non-status move as a Z-Move") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_COPYCAT].type == TYPE_NORMAL); + ASSUME(gMovesInfo[MOVE_COPYCAT].zMove.effect == Z_EFFECT_ACC_UP_1); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); MOVE(player, MOVE_COPYCAT, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ACC], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Me First raises the user's speed by two stages and copies the last used non-status move as a Z-Move with boosted damage", s16 damage) +{ + u32 meFirst; + PARAMETRIZE { meFirst = TRUE; } + PARAMETRIZE { meFirst = FALSE; } + GIVEN { + ASSUME(gMovesInfo[MOVE_ME_FIRST].type == TYPE_NORMAL); + ASSUME(gMovesInfo[MOVE_ME_FIRST].zMove.effect == Z_EFFECT_SPD_UP_2); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (meFirst) + TURN { MOVE(player, MOVE_ME_FIRST, gimmick: GIMMICK_Z_MOVE); MOVE(opponent, MOVE_TACKLE); } + else + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + if (meFirst) + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } THEN { + if (meFirst) + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 2); + } FINALLY { + EXPECT_MUL_EQ(results[1].damage, UQ_4_12(1.50), results[0].damage); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Nature Power transforms into different Z-Moves based on the current terrain") +{ + u32 terrainMove = MOVE_NONE; + u32 zMove = MOVE_NONE; + PARAMETRIZE { terrainMove = MOVE_ELECTRIC_TERRAIN; zMove = gTypesInfo[TYPE_ELECTRIC].zMove; } + PARAMETRIZE { terrainMove = MOVE_PSYCHIC_TERRAIN; zMove = gTypesInfo[TYPE_PSYCHIC].zMove; } + PARAMETRIZE { terrainMove = MOVE_GRASSY_TERRAIN; zMove = gTypesInfo[TYPE_GRASS].zMove; } + PARAMETRIZE { terrainMove = MOVE_MISTY_TERRAIN; zMove = gTypesInfo[TYPE_FAIRY].zMove; } + GIVEN { + ASSUME(gMovesInfo[MOVE_NATURE_POWER].type == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, terrainMove); } + TURN { MOVE(player, MOVE_NATURE_POWER, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, terrainMove, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, zMove, player); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Hidden Power always transforms into Breakneck Blitz") +{ + u8 iv; + PARAMETRIZE { iv = 0; } // test different Hidden Power types + PARAMETRIZE { iv = 10; } + PARAMETRIZE { iv = 21; } + PARAMETRIZE { iv = 31; } + GIVEN { + ASSUME(gMovesInfo[MOVE_HIDDEN_POWER].type == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); AttackIV(iv); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HIDDEN_POWER, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Weather Ball transforms into different Z-Moves based on current weather") +{ + u32 weatherMove = MOVE_NONE; + u32 zMove = MOVE_NONE; + PARAMETRIZE { weatherMove = MOVE_RAIN_DANCE; zMove = gTypesInfo[TYPE_WATER].zMove; } + PARAMETRIZE { weatherMove = MOVE_SUNNY_DAY; zMove = gTypesInfo[TYPE_FIRE].zMove; } + PARAMETRIZE { weatherMove = MOVE_SANDSTORM; zMove = gTypesInfo[TYPE_ROCK].zMove; } + PARAMETRIZE { weatherMove = MOVE_HAIL; zMove = gTypesInfo[TYPE_ICE].zMove; } + GIVEN { + ASSUME(gMovesInfo[MOVE_WEATHER_BALL].type == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, weatherMove); } + TURN { MOVE(player, MOVE_WEATHER_BALL, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, weatherMove, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, zMove, player); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Sleep Talk transforms a used non-status move into a Z-Move") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_SLEEP_TALK].type == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); Status1(STATUS1_SLEEP); Moves(MOVE_SLEEP_TALK, MOVE_ABSORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SLEEP_TALK, gimmick: GIMMICK_Z_MOVE); } + TURN { MOVE(player, MOVE_SLEEP_TALK); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BLOOM_DOOM, player); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Z-Sleep Talk turns Weather Ball into Breakneck Blitz even under rain") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_SLEEP_TALK].type == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); Status1(STATUS1_SLEEP); Moves(MOVE_SLEEP_TALK, MOVE_WEATHER_BALL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_RAIN_DANCE); MOVE(player, MOVE_SLEEP_TALK, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAIN_DANCE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + } +} + +// Miscellaneous Interactions +DOUBLE_BATTLE_TEST("(Z-MOVE) Instruct fails if the target last used a Z-Move") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].type == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft, gimmick: GIMMICK_Z_MOVE); + MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, playerLeft); + MESSAGE("Wynaut used Instruct!"); + MESSAGE("But it failed!"); + } +} + +DOUBLE_BATTLE_TEST("(Z-MOVE) Dancer does not use a Z-Move if the battler has used a Z-Move the same turn") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].type == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_DANCER); Item(ITEM_NORMALIUM_Z); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft, gimmick: GIMMICK_Z_MOVE); + MOVE(playerRight, MOVE_FIERY_DANCE, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIERY_DANCE, playerRight); + ABILITY_POPUP(playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIERY_DANCE, playerLeft); + } +} + +// Signature Z-Moves +SINGLE_BATTLE_TEST("(Z-MOVE) Light That Burns the Sky uses the battler's highest attacking stat", s16 damage) +{ + bool32 useSwordsDance; + PARAMETRIZE { useSwordsDance = FALSE; } + PARAMETRIZE { useSwordsDance = TRUE; } + GIVEN { + ASSUME(gMovesInfo[MOVE_SWORDS_DANCE].effect == EFFECT_ATTACK_UP_2); + PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Item(ITEM_ULTRANECROZIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1000); MaxHP(1000); }; // hits hard lol + } WHEN { + TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_ULTRA_BURST); } + if (useSwordsDance) + TURN { MOVE(player, MOVE_SWORDS_DANCE); } + TURN { MOVE(player, MOVE_PHOTON_GEYSER, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ULTRA_BURST, player); // implicitly testing double gimmicks :^) + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_LIGHT_THAT_BURNS_THE_SKY, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) 10,000,000 Volt Thunderbolt has an increased critical hit ratio") +{ + PASSES_RANDOMLY(1, 2, RNG_CRITICAL_HIT); + GIVEN { + ASSUME(B_CRIT_CHANCE >= GEN_6); + ASSUME(gMovesInfo[MOVE_10_000_000_VOLT_THUNDERBOLT].criticalHitStage == 2); + PLAYER(SPECIES_PIKACHU_PARTNER_CAP) { Item(ITEM_PIKASHUNIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_THUNDERBOLT, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_10_000_000_VOLT_THUNDERBOLT, player); + MESSAGE("A critical hit!"); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Stoked Sparksurfer paralyzes the target") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_STOKED_SPARKSURFER].additionalEffects[0].moveEffect == MOVE_EFFECT_PARALYSIS); + PLAYER(SPECIES_RAICHU_ALOLA) { Item(ITEM_ALORAICHIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_THUNDERBOLT, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STOKED_SPARKSURFER, player); + STATUS_ICON(opponent, STATUS1_PARALYSIS); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Extreme Evoboost boosts all the user's stats by two stages") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_EXTREME_EVOBOOST].effect == EFFECT_EXTREME_EVOBOOST); + PLAYER(SPECIES_EEVEE) { Item(ITEM_EEVIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_LAST_RESORT, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXTREME_EVOBOOST, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 2); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 2); + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 2); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 2); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Genesis Supernova sets up psychic terrain") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_GENESIS_SUPERNOVA].effect == EFFECT_HIT_SET_REMOVE_TERRAIN); + PLAYER(SPECIES_MEW) { Item(ITEM_MEWNIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PSYCHIC, gimmick: GIMMICK_Z_MOVE); } + TURN { MOVE(player, MOVE_QUICK_ATTACK); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GENESIS_SUPERNOVA, player); + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player); } + MESSAGE("Mew cannot use Quick Attack!"); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Splintered Stormshards removes terrain") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_SPLINTERED_STORMSHARDS].effect == EFFECT_HIT_SET_REMOVE_TERRAIN); + PLAYER(SPECIES_LYCANROC_DUSK) { Item(ITEM_LYCANIUM_Z); } + OPPONENT(SPECIES_TAPU_LELE) { Ability(ABILITY_PSYCHIC_SURGE); HP(1000); MaxHP(1000); } + } WHEN { + TURN { MOVE(player, MOVE_STONE_EDGE, gimmick: GIMMICK_Z_MOVE); } + TURN { MOVE(player, MOVE_QUICK_ATTACK); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLINTERED_STORMSHARDS, player); + MESSAGE("The weirdness disappeared from the battlefield."); + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Clangorous Soulblaze boosts all the user's stats by one stage") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_CLANGOROUS_SOULBLAZE].additionalEffects[0].moveEffect == MOVE_EFFECT_ALL_STATS_UP); + PLAYER(SPECIES_KOMMO_O) { Item(ITEM_KOMMONIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CLANGING_SCALES, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CLANGOROUS_SOULBLAZE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Guardian of Alola deals 75\% of the target's current HP") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_GUARDIAN_OF_ALOLA].effect == EFFECT_GUARDIAN_OF_ALOLA); + PLAYER(SPECIES_TAPU_FINI) { Item(ITEM_TAPUNIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_NATURES_MADNESS, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GUARDIAN_OF_ALOLA, player); + } THEN { + EXPECT_MUL_EQ(opponent->maxHP, UQ_4_12(0.25), opponent->hp); + } +} + +SINGLE_BATTLE_TEST("(Z-MOVE) Searing Sunraze Smash ignores the target's abilities") +{ + GIVEN { + PLAYER(SPECIES_SOLGALEO) { Item(ITEM_SOLGANIUM_Z); } + OPPONENT(SPECIES_LAPRAS) { Ability(ABILITY_BATTLE_ARMOR); } + } WHEN { + TURN { MOVE(player, MOVE_SUNSTEEL_STRIKE, gimmick: GIMMICK_Z_MOVE, criticalHit: TRUE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SEARING_SUNRAZE_SMASH, player); + HP_BAR(opponent); + MESSAGE("A critical hit!"); + } +} diff --git a/test/battle/hold_effect/cure_status.c b/test/battle/hold_effect/cure_status.c index 93921ce9c1..182069aaf5 100644 --- a/test/battle/hold_effect/cure_status.c +++ b/test/battle/hold_effect/cure_status.c @@ -163,12 +163,12 @@ SINGLE_BATTLE_TEST("Berry hold effect cures status if a pokemon enters a battle" u16 status; u16 item; - PARAMETRIZE{ status = STATUS1_BURN; item = ITEM_RAWST_BERRY; } - PARAMETRIZE{ status = STATUS1_FREEZE; item = ITEM_ASPEAR_BERRY; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; item = ITEM_CHERI_BERRY; } - PARAMETRIZE{ status = STATUS1_POISON; item = ITEM_PECHA_BERRY; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; item = ITEM_PECHA_BERRY; } - PARAMETRIZE{ status = STATUS1_SLEEP; item = ITEM_CHESTO_BERRY; } + PARAMETRIZE { status = STATUS1_BURN; item = ITEM_RAWST_BERRY; } + PARAMETRIZE { status = STATUS1_FREEZE; item = ITEM_ASPEAR_BERRY; } + PARAMETRIZE { status = STATUS1_PARALYSIS; item = ITEM_CHERI_BERRY; } + PARAMETRIZE { status = STATUS1_POISON; item = ITEM_PECHA_BERRY; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; item = ITEM_PECHA_BERRY; } + PARAMETRIZE { status = STATUS1_SLEEP; item = ITEM_CHESTO_BERRY; } GIVEN { ASSUME(gItemsInfo[ITEM_RAWST_BERRY].holdEffect == HOLD_EFFECT_CURE_BRN); diff --git a/test/battle/item_effect/cure_status.c b/test/battle/item_effect/cure_status.c index f73bbae327..6c7cc2e64d 100644 --- a/test/battle/item_effect/cure_status.c +++ b/test/battle/item_effect/cure_status.c @@ -112,12 +112,12 @@ SINGLE_BATTLE_TEST("Ice Heal heals a battler from being frozen") SINGLE_BATTLE_TEST("Full Heal heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { ASSUME(gItemsInfo[ITEM_FULL_HEAL].battleUsage == EFFECT_ITEM_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { Status1(status); } @@ -134,12 +134,12 @@ SINGLE_BATTLE_TEST("Full Heal heals a battler from any primary status") SINGLE_BATTLE_TEST("Heal Powder heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { ASSUME(gItemsInfo[ITEM_HEAL_POWDER].battleUsage == EFFECT_ITEM_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { Status1(status); } @@ -156,12 +156,12 @@ SINGLE_BATTLE_TEST("Heal Powder heals a battler from any primary status") SINGLE_BATTLE_TEST("Pewter Crunchies heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { ASSUME(gItemsInfo[ITEM_PEWTER_CRUNCHIES].battleUsage == EFFECT_ITEM_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { Status1(status); } @@ -178,12 +178,12 @@ SINGLE_BATTLE_TEST("Pewter Crunchies heals a battler from any primary status") SINGLE_BATTLE_TEST("Lava Cookies heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { ASSUME(gItemsInfo[ITEM_LAVA_COOKIE].battleUsage == EFFECT_ITEM_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { Status1(status); } @@ -200,12 +200,12 @@ SINGLE_BATTLE_TEST("Lava Cookies heals a battler from any primary status") SINGLE_BATTLE_TEST("Rage Candy Bar heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { ASSUME(gItemsInfo[ITEM_RAGE_CANDY_BAR].battleUsage == EFFECT_ITEM_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { Status1(status); } @@ -222,12 +222,12 @@ SINGLE_BATTLE_TEST("Rage Candy Bar heals a battler from any primary status") SINGLE_BATTLE_TEST("Old Gateu heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { ASSUME(gItemsInfo[ITEM_OLD_GATEAU].battleUsage == EFFECT_ITEM_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { Status1(status); } @@ -244,12 +244,12 @@ SINGLE_BATTLE_TEST("Old Gateu heals a battler from any primary status") SINGLE_BATTLE_TEST("Casteliacone heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { ASSUME(gItemsInfo[ITEM_CASTELIACONE].battleUsage == EFFECT_ITEM_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { Status1(status); } @@ -266,12 +266,12 @@ SINGLE_BATTLE_TEST("Casteliacone heals a battler from any primary status") SINGLE_BATTLE_TEST("Lumiose Galette heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { ASSUME(gItemsInfo[ITEM_LUMIOSE_GALETTE].battleUsage == EFFECT_ITEM_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { Status1(status); } @@ -288,12 +288,12 @@ SINGLE_BATTLE_TEST("Lumiose Galette heals a battler from any primary status") SINGLE_BATTLE_TEST("Shalour Sable heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { ASSUME(gItemsInfo[ITEM_SHALOUR_SABLE].battleUsage == EFFECT_ITEM_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { Status1(status); } @@ -310,12 +310,12 @@ SINGLE_BATTLE_TEST("Shalour Sable heals a battler from any primary status") SINGLE_BATTLE_TEST("Big Malasada heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { ASSUME(gItemsInfo[ITEM_BIG_MALASADA].battleUsage == EFFECT_ITEM_CURE_STATUS); PLAYER(SPECIES_WOBBUFFET) { Status1(status); } diff --git a/test/battle/item_effect/heal_and_cure_status.c b/test/battle/item_effect/heal_and_cure_status.c index 80719cf231..70dc8322db 100644 --- a/test/battle/item_effect/heal_and_cure_status.c +++ b/test/battle/item_effect/heal_and_cure_status.c @@ -9,13 +9,13 @@ ASSUMPTIONS SINGLE_BATTLE_TEST("Full Restore restores a battler's HP and cures any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } - PARAMETRIZE{ status = STATUS1_NONE; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_NONE; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { HP(1); MaxHP(300); Status1(status); } OPPONENT(SPECIES_WOBBUFFET); @@ -35,13 +35,13 @@ SINGLE_BATTLE_TEST("Full Restore restores a battler's HP and cures any primary s SINGLE_BATTLE_TEST("Full Restore restores a party members HP and cures any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } - PARAMETRIZE{ status = STATUS1_NONE; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_NONE; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { HP(1); MaxHP(300); Status1(status); } PLAYER(SPECIES_WYNAUT) { HP(1); MaxHP(300); Status1(status); } @@ -64,12 +64,12 @@ SINGLE_BATTLE_TEST("Full Restore restores a party members HP and cures any prima SINGLE_BATTLE_TEST("Full Restore heals a battler from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { Status1(status); } OPPONENT(SPECIES_WYNAUT); @@ -86,12 +86,12 @@ SINGLE_BATTLE_TEST("Full Restore heals a battler from any primary status") SINGLE_BATTLE_TEST("Full Restore heals a party member from any primary status") { u16 status; - PARAMETRIZE{ status = STATUS1_BURN; } - PARAMETRIZE{ status = STATUS1_FREEZE; } - PARAMETRIZE{ status = STATUS1_PARALYSIS; } - PARAMETRIZE{ status = STATUS1_POISON; } - PARAMETRIZE{ status = STATUS1_TOXIC_POISON; } - PARAMETRIZE{ status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_BURN; } + PARAMETRIZE { status = STATUS1_FREEZE; } + PARAMETRIZE { status = STATUS1_PARALYSIS; } + PARAMETRIZE { status = STATUS1_POISON; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; } + PARAMETRIZE { status = STATUS1_SLEEP; } GIVEN { PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WYNAUT) { Status1(status); } diff --git a/test/battle/move_animations/smack_down.c b/test/battle/move_animations/smack_down.c new file mode 100644 index 0000000000..acd97e9505 --- /dev/null +++ b/test/battle/move_animations/smack_down.c @@ -0,0 +1,25 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Move Animation Test: Smack Down works when used 15 times in a row") +{ + u16 j, nTurns = 15; + FORCE_MOVE_ANIM(TRUE); + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + for (j = 0; j < nTurns; j++) + { + TURN { MOVE(player, MOVE_SMACK_DOWN); MOVE(opponent, MOVE_HELPING_HAND); } // Helping Hand, so there's no anim on the opponent's side. + } + } SCENE { + for (j = 0; j < nTurns; j++) + { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SMACK_DOWN, player); + } + } THEN { + FORCE_MOVE_ANIM(FALSE); + } +} diff --git a/test/battle/move_effect/acrobatics.c b/test/battle/move_effect/acrobatics.c index bc34719a42..70953d0958 100644 --- a/test/battle/move_effect/acrobatics.c +++ b/test/battle/move_effect/acrobatics.c @@ -1,5 +1,48 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Acrobatics doubles in power if the user has no held item"); -TO_DO_BATTLE_TEST("Acrobatics still doubles in power when Flying Gem is consumed"); +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_ACROBATICS].effect == EFFECT_ACROBATICS); + ASSUME(gMovesInfo[MOVE_ACROBATICS].type == TYPE_FLYING); +} + +SINGLE_BATTLE_TEST("Acrobatics doubles in power if the user has no held item", s16 damage) +{ + u16 heldItem; + PARAMETRIZE { heldItem = ITEM_POTION; } + PARAMETRIZE { heldItem = ITEM_NONE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(heldItem); } + } WHEN { + TURN { MOVE(opponent, MOVE_ACROBATICS); } + } SCENE { + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Acrobatics still doubles in power when Flying Gem is consumed", s16 damage) +{ + u16 heldItem; + PARAMETRIZE { heldItem = ITEM_NONE; } + PARAMETRIZE { heldItem = ITEM_FLYING_GEM; } + GIVEN { + ASSUME(I_GEM_BOOST_POWER >= GEN_6); + ASSUME(gItemsInfo[ITEM_FLYING_GEM].holdEffect == HOLD_EFFECT_GEMS); + ASSUME(gItemsInfo[ITEM_FLYING_GEM].secondaryId == TYPE_FLYING); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(heldItem); } + } WHEN { + TURN { MOVE(opponent, MOVE_ACROBATICS); } + } SCENE { + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + if (I_GEM_BOOST_POWER >= GEN_6) + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.3), (results[1].damage)); + else + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), (results[1].damage)); + } +} diff --git a/test/battle/move_effect/attack_accuracy_up.c b/test/battle/move_effect/attack_accuracy_up.c index 025a0cb86f..102f4d4d21 100644 --- a/test/battle/move_effect/attack_accuracy_up.c +++ b/test/battle/move_effect/attack_accuracy_up.c @@ -1,4 +1,18 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Hone Claws increases Attack and Accuracy by one stage each"); +SINGLE_BATTLE_TEST("Hone Claws increases Attack and Accuracy by one stage each") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_HONE_CLAWS].effect == EFFECT_ATTACK_ACCURACY_UP); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HONE_CLAWS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HONE_CLAWS, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Wobbuffet's Attack rose!"); + MESSAGE("Wobbuffet's accuracy rose!"); + } +} diff --git a/test/battle/move_effect/aura_wheel.c b/test/battle/move_effect/aura_wheel.c index fa4190dbdf..3d601f3583 100644 --- a/test/battle/move_effect/aura_wheel.c +++ b/test/battle/move_effect/aura_wheel.c @@ -10,8 +10,8 @@ ASSUMPTIONS SINGLE_BATTLE_TEST("Aura Wheel raises Speed; fails if the user is not Morpeko") { u16 species; - PARAMETRIZE{ species = SPECIES_WOBBUFFET; } - PARAMETRIZE{ species = SPECIES_MORPEKO; } + PARAMETRIZE { species = SPECIES_WOBBUFFET; } + PARAMETRIZE { species = SPECIES_MORPEKO; } GIVEN { PLAYER(species); OPPONENT(SPECIES_WOBBUFFET); diff --git a/test/battle/move_effect/body_press.c b/test/battle/move_effect/body_press.c index ffbd477ea7..3a61c4d55d 100644 --- a/test/battle/move_effect/body_press.c +++ b/test/battle/move_effect/body_press.c @@ -17,7 +17,6 @@ SINGLE_BATTLE_TEST("Body Press uses physical defense stat of target", s16 damage GIVEN { ASSUME(gMovesInfo[MOVE_DRILL_PECK].power == gMovesInfo[MOVE_BODY_PRESS].power); ASSUME(gMovesInfo[MOVE_CHARM].effect == EFFECT_ATTACK_DOWN_2); - ASSUME(gMovesInfo[MOVE_CHARM].effect == EFFECT_ATTACK_DOWN_2); PLAYER(SPECIES_MEW); OPPONENT(SPECIES_SHELLDER); } WHEN { @@ -30,8 +29,95 @@ SINGLE_BATTLE_TEST("Body Press uses physical defense stat of target", s16 damage } } -TO_DO_BATTLE_TEST("Body Press's damage depends on the user's base Defense instead of its base Attack"); -TO_DO_BATTLE_TEST("Body Press's damage depends on the user's Defense stat stages"); +SINGLE_BATTLE_TEST("Body Press's damage depends on the user's base Defense instead of its base Attack", s16 damage) +{ + u32 def, atk; + PARAMETRIZE { def = 150; atk = 179; } // Atk is higher + PARAMETRIZE { atk = 150; def = 179; } // Atk is lower + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Attack(atk); Defense(def); } + } WHEN { + TURN { MOVE(opponent, MOVE_BODY_PRESS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BODY_PRESS, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_GT(results[1].damage, results[0].damage); + } +} + +SINGLE_BATTLE_TEST("Body Press's damage depends on the user's Defense and not Attack stat stages", s16 damage) +{ + u32 move; + + PARAMETRIZE { move = MOVE_IRON_DEFENSE; } + PARAMETRIZE { move = MOVE_SWORDS_DANCE; } + PARAMETRIZE { move = MOVE_CELEBRATE; } // Nothing, stats are default + GIVEN { + ASSUME(gMovesInfo[MOVE_IRON_DEFENSE].effect == EFFECT_DEFENSE_UP_2); + ASSUME(gMovesInfo[MOVE_SWORDS_DANCE].effect == EFFECT_ATTACK_UP_2); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Attack(150); Defense(150); } + } WHEN { + TURN { MOVE(opponent, move); } + TURN { MOVE(opponent, MOVE_BODY_PRESS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BODY_PRESS, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_GT(results[0].damage, results[1].damage); + EXPECT_EQ(results[1].damage, results[2].damage); + } +} + +SINGLE_BATTLE_TEST("Body Press uses Defense Stat even in Wonder Room", s16 damage) +{ + u32 move; + + PARAMETRIZE { move = MOVE_WONDER_ROOM; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + GIVEN { + ASSUME(gMovesInfo[MOVE_WONDER_ROOM].effect == EFFECT_WONDER_ROOM); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { SpDefense(50); Defense(150); } + } WHEN { + TURN { MOVE(opponent, move); } + TURN { MOVE(opponent, MOVE_BODY_PRESS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BODY_PRESS, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Body Press uses Special Defense stat Stages in Wonder Room", s16 damage) +{ + u32 move; + + PARAMETRIZE { move = MOVE_IRON_DEFENSE; } + PARAMETRIZE { move = MOVE_AMNESIA; } + PARAMETRIZE { move = MOVE_CELEBRATE; } // Nothing, stats are default + GIVEN { + ASSUME(gMovesInfo[MOVE_IRON_DEFENSE].effect == EFFECT_DEFENSE_UP_2); + ASSUME(gMovesInfo[MOVE_AMNESIA].effect == EFFECT_SPECIAL_DEFENSE_UP_2); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { SpDefense(150); Defense(150); } + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_WONDER_ROOM); } + TURN { MOVE(opponent, MOVE_BODY_PRESS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BODY_PRESS, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_GT(results[1].damage, results[0].damage); + EXPECT_EQ(results[0].damage, results[2].damage); + } +} // Could be split into multiple tests or maybe to separate files based on the modifier? TO_DO_BATTLE_TEST("Body Press's damage is influenced by all other Attack modifiers that are not stat stages"); diff --git a/test/battle/move_effect/embargo.c b/test/battle/move_effect/embargo.c index f25c88b827..11e4069d80 100644 --- a/test/battle/move_effect/embargo.c +++ b/test/battle/move_effect/embargo.c @@ -253,8 +253,8 @@ SINGLE_BATTLE_TEST("Embargo disables the effect of the Plate items on the move J { u32 heldItem; - PARAMETRIZE{ heldItem = ITEM_NONE; } - PARAMETRIZE{ heldItem = ITEM_PIXIE_PLATE; } + PARAMETRIZE { heldItem = ITEM_NONE; } + PARAMETRIZE { heldItem = ITEM_PIXIE_PLATE; } GIVEN { PLAYER(SPECIES_ARCEUS) { Item(heldItem); }; OPPONENT(SPECIES_DRAGONITE); @@ -274,8 +274,8 @@ SINGLE_BATTLE_TEST("Embargo disables the effect of the Drive items on the move T { u32 heldItem; - PARAMETRIZE{ heldItem = ITEM_NONE; } - PARAMETRIZE{ heldItem = ITEM_SHOCK_DRIVE; } + PARAMETRIZE { heldItem = ITEM_NONE; } + PARAMETRIZE { heldItem = ITEM_SHOCK_DRIVE; } GIVEN { PLAYER(SPECIES_GENESECT) { Item(heldItem); }; OPPONENT(SPECIES_GYARADOS); @@ -295,8 +295,8 @@ SINGLE_BATTLE_TEST("Embargo disables the effect of the Memory items on the move { u32 heldItem; - PARAMETRIZE{ heldItem = ITEM_NONE; } - PARAMETRIZE{ heldItem = ITEM_FIRE_MEMORY; } + PARAMETRIZE { heldItem = ITEM_NONE; } + PARAMETRIZE { heldItem = ITEM_FIRE_MEMORY; } GIVEN { PLAYER(SPECIES_SILVALLY) { Item(heldItem); }; OPPONENT(SPECIES_VENUSAUR); @@ -342,7 +342,7 @@ SINGLE_BATTLE_TEST("Embargo doesn't prevent Mega Evolution") } WHEN { TURN { MOVE(player, MOVE_EMBARGO); } TURN { MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); } - TURN { MOVE(opponent, MOVE_CELEBRATE, megaEvolve: TRUE); } + TURN { MOVE(opponent, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } } SCENE { // Turn 1 MESSAGE("Wobbuffet used Embargo!"); diff --git a/test/battle/move_effect/encore.c b/test/battle/move_effect/encore.c index 670122dc37..db7f5eb042 100644 --- a/test/battle/move_effect/encore.c +++ b/test/battle/move_effect/encore.c @@ -6,40 +6,90 @@ ASSUMPTIONS ASSUME(gMovesInfo[MOVE_ENCORE].effect == EFFECT_ENCORE); } -SINGLE_BATTLE_TEST("Encore forces consecutive move uses for 2 turns for player") +SINGLE_BATTLE_TEST("Encore forces consecutive move uses for 3 turns for player: Encore used before move") { GIVEN { - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET) { Speed(10); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(20); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE); } + TURN { MOVE(opponent, MOVE_ENCORE); MOVE(player, MOVE_CELEBRATE); } + // TURN { FORCED_MOVE(player); } + TURN { FORCED_MOVE(player); } + TURN { FORCED_MOVE(player); } + TURN { MOVE(player, MOVE_SPLASH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ENCORE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, player); + } +} + +SINGLE_BATTLE_TEST("Encore forces consecutive move uses for 3 turns for player: Encore used after move") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(20); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } } WHEN { TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_ENCORE); } TURN { FORCED_MOVE(player); } TURN { FORCED_MOVE(player); } + TURN { FORCED_MOVE(player); } TURN { MOVE(player, MOVE_SPLASH); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); ANIMATION(ANIM_TYPE_MOVE, MOVE_ENCORE, opponent); ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, player); } } -SINGLE_BATTLE_TEST("Encore forces consecutive move uses for 2 turns for opponent") +SINGLE_BATTLE_TEST("Encore forces consecutive move uses for 3 turns for opponent: Encore used before move") { GIVEN { - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET) { Speed(20); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_ENCORE); MOVE(opponent, MOVE_CELEBRATE); } + // TURN { FORCED_MOVE(opponent); } + TURN { FORCED_MOVE(opponent); } + TURN { FORCED_MOVE(opponent); } + TURN { MOVE(opponent, MOVE_SPLASH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ENCORE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, opponent); + } +} + +SINGLE_BATTLE_TEST("Encore forces consecutive move uses for 3 turns for opponent: Encore used after move") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(10); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(20); } } WHEN { TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_ENCORE); } TURN { FORCED_MOVE(opponent); } TURN { FORCED_MOVE(opponent); } + TURN { FORCED_MOVE(opponent); } TURN { MOVE(opponent, MOVE_SPLASH); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); ANIMATION(ANIM_TYPE_MOVE, MOVE_ENCORE, player); ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, opponent); } } diff --git a/test/battle/move_effect/fury_cutter.c b/test/battle/move_effect/fury_cutter.c new file mode 100644 index 0000000000..cf3871964e --- /dev/null +++ b/test/battle/move_effect/fury_cutter.c @@ -0,0 +1,38 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_FURY_CUTTER].effect == EFFECT_FURY_CUTTER); +} + +SINGLE_BATTLE_TEST("Fury Cutter power doubles with each use, up to 160 power") +{ + s16 damage[6]; + int turn; + int maxTurns; + + if (B_UPDATED_MOVE_DATA >= GEN_6) + maxTurns = 4; + else if (B_UPDATED_MOVE_DATA == GEN_5) + maxTurns = 5; + else + maxTurns = 6; + + GIVEN { + PLAYER(SPECIES_CROBAT); + OPPONENT(SPECIES_LINOONE) { HP(900); } + } WHEN { + for (turn = 0; turn < maxTurns; turn++) + TURN { MOVE(player, MOVE_FURY_CUTTER); } + } SCENE { + for (turn = 0; turn < maxTurns; turn++) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_CUTTER, player); + HP_BAR(opponent, captureDamage: &damage[turn]); + } + } THEN { + for (turn = 1; turn < maxTurns - 1; turn++) + EXPECT_MUL_EQ(damage[turn - 1], UQ_4_12(2.0), damage[turn]); + EXPECT_EQ(damage[maxTurns - 2], damage[maxTurns - 1]); + } +} diff --git a/test/battle/move_effect/hit_escape.c b/test/battle/move_effect/hit_escape.c index dd1372a017..0c96bd4e75 100644 --- a/test/battle/move_effect/hit_escape.c +++ b/test/battle/move_effect/hit_escape.c @@ -126,6 +126,7 @@ SINGLE_BATTLE_TEST("Held items are consumed immediately after a mon switched in ABILITY_POPUP(player, ABILITY_ELECTRIC_SURGE); ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); HP_BAR(opponent); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); MESSAGE("2 sent out Wynaut!"); @@ -137,7 +138,6 @@ SINGLE_BATTLE_TEST("Held items are consumed immediately after a mon switched in SINGLE_BATTLE_TEST("Held items are consumed immediately after a mon switched in by U-turn and Intimidate activates after it: opposing side") { - KNOWN_FAILING; GIVEN { PLAYER(SPECIES_TAPU_KOKO) { Ability(ABILITY_ELECTRIC_SURGE); }; PLAYER(SPECIES_EKANS) { Ability(ABILITY_INTIMIDATE); } @@ -149,7 +149,7 @@ SINGLE_BATTLE_TEST("Held items are consumed immediately after a mon switched in ABILITY_POPUP(player, ABILITY_ELECTRIC_SURGE); ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); HP_BAR(opponent); - NOT ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); MESSAGE("2 sent out Wynaut!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); diff --git a/test/battle/move_effect/plasma_fists.c b/test/battle/move_effect/plasma_fists.c index a7b1ac5f6a..bdd3ed4f41 100644 --- a/test/battle/move_effect/plasma_fists.c +++ b/test/battle/move_effect/plasma_fists.c @@ -47,20 +47,19 @@ SINGLE_BATTLE_TEST("Plasma Fists turns normal moves into electric for the remain } } -SINGLE_BATTLE_TEST("Plasma Fists type-changing effect is applied after Pixilate") +SINGLE_BATTLE_TEST("Plasma Fists type-changing effect does not override Pixilate") { GIVEN { PLAYER(SPECIES_KRABBY) { Speed(300); }; - OPPONENT(SPECIES_ALTARIA) { Speed(1); Item(ITEM_ALTARIANITE); } + OPPONENT(SPECIES_SYLVEON) { Speed(1); Ability(ABILITY_PIXILATE); } } WHEN { - TURN { MOVE(player, MOVE_PLASMA_FISTS); MOVE(opponent, MOVE_EMBER, megaEvolve: TRUE); } + TURN { MOVE(player, MOVE_PLASMA_FISTS); MOVE(opponent, MOVE_TACKLE); } } SCENE { - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponent); MESSAGE("Krabby used Plasma Fists!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_PLASMA_FISTS, player); MESSAGE("A deluge of ions showers the battlefield!"); - MESSAGE("Foe Altaria used Ember!"); - ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); + MESSAGE("Foe Sylveon used Tackle!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); NOT MESSAGE("It's super effective!"); } } @@ -88,7 +87,7 @@ SINGLE_BATTLE_TEST("Plasma Fists turns normal type dynamax-moves into electric t PLAYER(SPECIES_KRABBY) { Speed(100); } OPPONENT(SPECIES_WOBBUFFET) { Speed(1); } } WHEN { - TURN { MOVE(player, MOVE_PLASMA_FISTS); MOVE(opponent, MOVE_TACKLE, dynamax: TRUE); } + TURN { MOVE(player, MOVE_PLASMA_FISTS); MOVE(opponent, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); } } SCENE { MESSAGE("Krabby used Plasma Fists!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_PLASMA_FISTS, player); diff --git a/test/battle/move_effect/pursuit.c b/test/battle/move_effect/pursuit.c index cb288015cc..7c34e914d1 100644 --- a/test/battle/move_effect/pursuit.c +++ b/test/battle/move_effect/pursuit.c @@ -25,4 +25,25 @@ SINGLE_BATTLE_TEST("Pursuited mon correctly switches out after it got hit and ac } } +// Checked so that Pursuit has only 1 PP and it forces the player to use Struggle. +SINGLE_BATTLE_TEST("Pursuit becomes a locked move after being used on switch-out while holding a Choice Item") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_CHOICE_BAND].holdEffect == HOLD_EFFECT_CHOICE_BAND); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_CHOICE_BAND); MovesWithPP({MOVE_PURSUIT, 1}, {MOVE_CELEBRATE, 10}, {MOVE_WATER_GUN, 10}, {MOVE_TACKLE, 10}); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(opponent, 1); MOVE(player, MOVE_PURSUIT); } + TURN { FORCED_MOVE(player); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PURSUIT, player); + HP_BAR(opponent); + MESSAGE("2 sent out Wobbuffet!"); + + MESSAGE("Wobbuffet used Struggle!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STRUGGLE, player); + } +} + TO_DO_BATTLE_TEST("Baton Pass doesn't cause Pursuit to increase its power or priority"); diff --git a/test/battle/move_effect/relic_song.c b/test/battle/move_effect/relic_song.c index 52db4a3601..3ea405cc50 100644 --- a/test/battle/move_effect/relic_song.c +++ b/test/battle/move_effect/relic_song.c @@ -85,6 +85,25 @@ SINGLE_BATTLE_TEST("Relic Song transforms Meloetta if used successfully") } } +SINGLE_BATTLE_TEST("Relic Song does not transform Pokemon other than Meloetta") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_RELIC_SONG); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_RELIC_SONG, player); + HP_BAR(opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, player); + MESSAGE("Wobbuffet transformed!"); + } + } THEN { + EXPECT_EQ(player->species, SPECIES_WOBBUFFET); + } +} + SINGLE_BATTLE_TEST("Relic Song transforms Meloetta twice if used successfully") { GIVEN { diff --git a/test/battle/move_effect/roost.c b/test/battle/move_effect/roost.c index 1ae84dd088..b4c0186bf0 100644 --- a/test/battle/move_effect/roost.c +++ b/test/battle/move_effect/roost.c @@ -111,24 +111,24 @@ SINGLE_BATTLE_TEST("Roost suppresses the user's Flying-typing this turn, then re SINGLE_BATTLE_TEST("Roost, if used by a Flying/Flying type, treats the user as a Normal-type (or Typeless in Gen. 4) until the end of the turn") { u32 damagingMove; - PARAMETRIZE{ damagingMove = MOVE_POUND; } - PARAMETRIZE{ damagingMove = MOVE_KARATE_CHOP; } - PARAMETRIZE{ damagingMove = MOVE_GUST; } - PARAMETRIZE{ damagingMove = MOVE_POISON_STING; } - PARAMETRIZE{ damagingMove = MOVE_EARTHQUAKE; } - PARAMETRIZE{ damagingMove = MOVE_ROCK_THROW; } - PARAMETRIZE{ damagingMove = MOVE_LEECH_LIFE; } - PARAMETRIZE{ damagingMove = MOVE_LICK; } - PARAMETRIZE{ damagingMove = MOVE_STEEL_WING; } - PARAMETRIZE{ damagingMove = MOVE_EMBER; } - PARAMETRIZE{ damagingMove = MOVE_WATER_GUN; } - PARAMETRIZE{ damagingMove = MOVE_VINE_WHIP; } - PARAMETRIZE{ damagingMove = MOVE_THUNDER_SHOCK; } - PARAMETRIZE{ damagingMove = MOVE_CONFUSION; } - PARAMETRIZE{ damagingMove = MOVE_ICE_BEAM; } - PARAMETRIZE{ damagingMove = MOVE_DRAGON_BREATH; } - PARAMETRIZE{ damagingMove = MOVE_BITE; } - PARAMETRIZE{ damagingMove = MOVE_DISARMING_VOICE; } + PARAMETRIZE { damagingMove = MOVE_POUND; } + PARAMETRIZE { damagingMove = MOVE_KARATE_CHOP; } + PARAMETRIZE { damagingMove = MOVE_GUST; } + PARAMETRIZE { damagingMove = MOVE_POISON_STING; } + PARAMETRIZE { damagingMove = MOVE_EARTHQUAKE; } + PARAMETRIZE { damagingMove = MOVE_ROCK_THROW; } + PARAMETRIZE { damagingMove = MOVE_LEECH_LIFE; } + PARAMETRIZE { damagingMove = MOVE_LICK; } + PARAMETRIZE { damagingMove = MOVE_STEEL_WING; } + PARAMETRIZE { damagingMove = MOVE_EMBER; } + PARAMETRIZE { damagingMove = MOVE_WATER_GUN; } + PARAMETRIZE { damagingMove = MOVE_VINE_WHIP; } + PARAMETRIZE { damagingMove = MOVE_THUNDER_SHOCK; } + PARAMETRIZE { damagingMove = MOVE_CONFUSION; } + PARAMETRIZE { damagingMove = MOVE_ICE_BEAM; } + PARAMETRIZE { damagingMove = MOVE_DRAGON_BREATH; } + PARAMETRIZE { damagingMove = MOVE_BITE; } + PARAMETRIZE { damagingMove = MOVE_DISARMING_VOICE; } GIVEN { ASSUME(gSpeciesInfo[SPECIES_TORNADUS].types[0] == TYPE_FLYING); @@ -179,24 +179,24 @@ SINGLE_BATTLE_TEST("Roost, if used by a Flying/Flying type, treats the user as a SINGLE_BATTLE_TEST("Roost, if used by a Mystery/Flying type, treats the user as a Mystery/Mystery type until the end of the turn") { u32 damagingMove; - PARAMETRIZE{ damagingMove = MOVE_POUND; } - PARAMETRIZE{ damagingMove = MOVE_KARATE_CHOP; } - PARAMETRIZE{ damagingMove = MOVE_GUST; } - PARAMETRIZE{ damagingMove = MOVE_POISON_STING; } - PARAMETRIZE{ damagingMove = MOVE_EARTHQUAKE; } - PARAMETRIZE{ damagingMove = MOVE_ROCK_THROW; } - PARAMETRIZE{ damagingMove = MOVE_LEECH_LIFE; } - PARAMETRIZE{ damagingMove = MOVE_LICK; } - PARAMETRIZE{ damagingMove = MOVE_STEEL_WING; } - PARAMETRIZE{ damagingMove = MOVE_EMBER; } - PARAMETRIZE{ damagingMove = MOVE_WATER_GUN; } - PARAMETRIZE{ damagingMove = MOVE_VINE_WHIP; } - PARAMETRIZE{ damagingMove = MOVE_THUNDER_SHOCK; } - PARAMETRIZE{ damagingMove = MOVE_CONFUSION; } - PARAMETRIZE{ damagingMove = MOVE_ICE_BEAM; } - PARAMETRIZE{ damagingMove = MOVE_DRAGON_BREATH; } - PARAMETRIZE{ damagingMove = MOVE_BITE; } - PARAMETRIZE{ damagingMove = MOVE_DISARMING_VOICE; } + PARAMETRIZE { damagingMove = MOVE_POUND; } + PARAMETRIZE { damagingMove = MOVE_KARATE_CHOP; } + PARAMETRIZE { damagingMove = MOVE_GUST; } + PARAMETRIZE { damagingMove = MOVE_POISON_STING; } + PARAMETRIZE { damagingMove = MOVE_EARTHQUAKE; } + PARAMETRIZE { damagingMove = MOVE_ROCK_THROW; } + PARAMETRIZE { damagingMove = MOVE_LEECH_LIFE; } + PARAMETRIZE { damagingMove = MOVE_LICK; } + PARAMETRIZE { damagingMove = MOVE_STEEL_WING; } + PARAMETRIZE { damagingMove = MOVE_EMBER; } + PARAMETRIZE { damagingMove = MOVE_WATER_GUN; } + PARAMETRIZE { damagingMove = MOVE_VINE_WHIP; } + PARAMETRIZE { damagingMove = MOVE_THUNDER_SHOCK; } + PARAMETRIZE { damagingMove = MOVE_CONFUSION; } + PARAMETRIZE { damagingMove = MOVE_ICE_BEAM; } + PARAMETRIZE { damagingMove = MOVE_DRAGON_BREATH; } + PARAMETRIZE { damagingMove = MOVE_BITE; } + PARAMETRIZE { damagingMove = MOVE_DISARMING_VOICE; } GIVEN { ASSUME(gSpeciesInfo[SPECIES_MOLTRES].types[0] == TYPE_FIRE); diff --git a/test/battle/move_effect/spicy_extract.c b/test/battle/move_effect/spicy_extract.c index 3642bf936c..8a66003ec7 100644 --- a/test/battle/move_effect/spicy_extract.c +++ b/test/battle/move_effect/spicy_extract.c @@ -170,7 +170,6 @@ AI_DOUBLE_BATTLE_TEST("Spicy Extract user will use it if partner holds Clear Amu PARAMETRIZE { move = MOVE_TACKLE; } PARAMETRIZE { move = MOVE_SWIFT;} - AI_LOG; GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_WOBBUFFET) { Speed(10); } diff --git a/test/battle/move_effect/sticky_web.c b/test/battle/move_effect/sticky_web.c index f0017c3ca7..035568dd75 100644 --- a/test/battle/move_effect/sticky_web.c +++ b/test/battle/move_effect/sticky_web.c @@ -56,7 +56,7 @@ DOUBLE_BATTLE_TEST("Sticky Web lowers Speed by 1 in a double battle after Explos OPPONENT(SPECIES_WOBBUFFET) {HP(1); Speed(1);} OPPONENT(SPECIES_WOBBUFFET) {HP(1); Speed(1);} OPPONENT(SPECIES_WYNAUT) {Speed(10);} - OPPONENT(SPECIES_WYNAUT) {Speed(10);} + OPPONENT(SPECIES_ALAKAZAM) {Speed(100);} } WHEN { TURN { MOVE(playerRight, MOVE_STICKY_WEB); MOVE(playerLeft, MOVE_EXPLOSION); SEND_OUT(playerLeft, 2); SEND_OUT(opponentLeft, 2); SEND_OUT(opponentRight, 3); } TURN {} @@ -65,13 +65,13 @@ DOUBLE_BATTLE_TEST("Sticky Web lowers Speed by 1 in a double battle after Explos MESSAGE("A sticky web spreads out on the ground around the opposing team!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, playerLeft); MESSAGE("2 sent out Wynaut!"); + MESSAGE("2 sent out Alakazam!"); + MESSAGE("Foe Alakazam was caught in a Sticky Web!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("Foe Alakazam's Speed fell!"); MESSAGE("Foe Wynaut was caught in a Sticky Web!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); MESSAGE("Foe Wynaut's Speed fell!"); - MESSAGE("2 sent out Wynaut!"); - MESSAGE("Foe Wynaut was caught in a Sticky Web!"); - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); - MESSAGE("Foe Wynaut's Speed fell!"); } } diff --git a/test/battle/move_effect/strength_sap.c b/test/battle/move_effect/strength_sap.c index ef8ef3259f..4747074270 100644 --- a/test/battle/move_effect/strength_sap.c +++ b/test/battle/move_effect/strength_sap.c @@ -10,8 +10,8 @@ SINGLE_BATTLE_TEST("Strength Sap lowers Attack by 1 and restores HP based on tar { u32 atkStat = 0; - PARAMETRIZE{ atkStat = 100; } - PARAMETRIZE{ atkStat = 50; } + PARAMETRIZE { atkStat = 100; } + PARAMETRIZE { atkStat = 50; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { HP(200); } @@ -35,8 +35,8 @@ SINGLE_BATTLE_TEST("Strength Sap works exactly the same when attacker is behind { u32 atkStat = 0; - PARAMETRIZE{ atkStat = 100; } - PARAMETRIZE{ atkStat = 50; } + PARAMETRIZE { atkStat = 100; } + PARAMETRIZE { atkStat = 50; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { HP(200); } @@ -65,7 +65,7 @@ SINGLE_BATTLE_TEST("Strength Sap lowers Attack by 1 and restores HP based on tar for (j = 0; j <= MAX_STAT_STAGE; j++) { if (j == DEFAULT_STAT_STAGE - 1) { continue; } // Ignore -6, because Strength Sap won't work otherwise - PARAMETRIZE{ statStage = j; } + PARAMETRIZE { statStage = j; } } GIVEN { diff --git a/test/battle/move_effect/tera_blast.c b/test/battle/move_effect/tera_blast.c index ca1413d2e8..4fcb52be3a 100644 --- a/test/battle/move_effect/tera_blast.c +++ b/test/battle/move_effect/tera_blast.c @@ -13,7 +13,7 @@ SINGLE_BATTLE_TEST("Tera Blast changes from Normal-type to the user's Tera Type" PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_DARK); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TERA_BLAST, tera: TRUE); } + TURN { MOVE(player, MOVE_TERA_BLAST, gimmick: GIMMICK_TERA); } } SCENE { MESSAGE("Wobbuffet used Tera Blast!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); @@ -63,7 +63,7 @@ SINGLE_BATTLE_TEST("Tera Blast has correct effectiveness for every Tera Type") PLAYER(SPECIES_WOBBUFFET) { TeraType(type); } OPPONENT(species); } WHEN { - TURN { MOVE(player, MOVE_TERA_BLAST, tera: TRUE); } + TURN { MOVE(player, MOVE_TERA_BLAST, gimmick: GIMMICK_TERA); } } SCENE { if (species == SPECIES_GASTLY && type == TYPE_NORMAL) MESSAGE("It doesn't affect Foe Gastly…"); @@ -75,13 +75,13 @@ SINGLE_BATTLE_TEST("Tera Blast has correct effectiveness for every Tera Type") SINGLE_BATTLE_TEST("Tera Blast becomes a physical move if the user is Terastallized and has a higher Attack stat", s16 damage) { bool32 tera; - PARAMETRIZE { tera = FALSE; } - PARAMETRIZE { tera = TRUE; } + PARAMETRIZE { tera = GIMMICK_NONE; } + PARAMETRIZE { tera = GIMMICK_TERA; } GIVEN { PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_NORMAL); Attack(100); SpAttack(50); } OPPONENT(SPECIES_WOBBUFFET) { Defense(200); SpDefense(200); } } WHEN { - TURN { MOVE(player, MOVE_TERA_BLAST, tera: tera); } + TURN { MOVE(player, MOVE_TERA_BLAST, gimmick: tera); } } SCENE { MESSAGE("Wobbuffet used Tera Blast!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); @@ -100,7 +100,7 @@ SINGLE_BATTLE_TEST("Stellar-type Tera Blast lowers both offensive stats") PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TERA_BLAST, tera: TRUE); } + TURN { MOVE(player, MOVE_TERA_BLAST, gimmick: GIMMICK_TERA); } } SCENE { MESSAGE("Wobbuffet used Tera Blast!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); @@ -119,7 +119,7 @@ SINGLE_BATTLE_TEST("Stellar-type Tera Blast has 100 BP and a one-time 1.2x boost OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(player, MOVE_TERA_BLAST); MOVE(opponent, MOVE_RECOVER); } - TURN { MOVE(player, MOVE_TERA_BLAST, tera: TRUE); } + TURN { MOVE(player, MOVE_TERA_BLAST, gimmick: GIMMICK_TERA); } TURN { MOVE(player, MOVE_WORK_UP); } TURN { MOVE(player, MOVE_TERA_BLAST); } } SCENE { @@ -149,7 +149,7 @@ SINGLE_BATTLE_TEST("Stellar-type Tera Blast is super-effective on Stellar-type P PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } OPPONENT(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } } WHEN { - TURN { MOVE(player, MOVE_TERA_BLAST, tera: TRUE); MOVE(opponent, MOVE_CELEBRATE, tera: TRUE); } + TURN { MOVE(player, MOVE_TERA_BLAST, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } } SCENE { MESSAGE("Wobbuffet used Tera Blast!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); @@ -163,7 +163,7 @@ SINGLE_BATTLE_TEST("Stellar-type Tera Blast activates a Stellar-type Pokemon's W PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_WEAKNESS_POLICY); TeraType(TYPE_NORMAL); } } WHEN { - TURN { MOVE(player, MOVE_TERA_BLAST, tera: TRUE); MOVE(opponent, MOVE_CELEBRATE, tera: TRUE); } + TURN { MOVE(player, MOVE_TERA_BLAST, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } } SCENE { MESSAGE("Wobbuffet used Tera Blast!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); @@ -178,7 +178,7 @@ SINGLE_BATTLE_TEST("Flying-type Tera Blast does not have its priority boosted by PLAYER(SPECIES_TALONFLAME) { Ability(ABILITY_GALE_WINGS); TeraType(TYPE_FLYING); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_TERA_BLAST, tera: TRUE); MOVE(opponent, MOVE_QUICK_ATTACK); } + TURN { MOVE(player, MOVE_TERA_BLAST, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_QUICK_ATTACK); } } SCENE { MESSAGE("Foe Wobbuffet used Quick Attack!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, opponent); diff --git a/test/battle/move_effect/tera_starstorm.c b/test/battle/move_effect/tera_starstorm.c index cad4f9a015..44e42f474d 100644 --- a/test/battle/move_effect/tera_starstorm.c +++ b/test/battle/move_effect/tera_starstorm.c @@ -43,14 +43,14 @@ DOUBLE_BATTLE_TEST("Tera Starstorm targets both opponents in a double battle if SINGLE_BATTLE_TEST("Tera Starstorm becomes a physical move if the user is Terapagos-Stellar, is Terastallized, and has a higher Attack stat", s16 damage) { bool32 tera; - PARAMETRIZE { tera = FALSE; } - PARAMETRIZE { tera = TRUE; } + PARAMETRIZE { tera = GIMMICK_NONE; } + PARAMETRIZE { tera = GIMMICK_TERA; } GIVEN { ASSUME(gMovesInfo[MOVE_TERA_STARSTORM].category == DAMAGE_CATEGORY_SPECIAL); PLAYER(SPECIES_TERAPAGOS_STELLAR) { Attack(100); SpAttack(50); } OPPONENT(SPECIES_WOBBUFFET) { Defense(200); SpDefense(200); } } WHEN { - TURN { MOVE(player, MOVE_TERA_STARSTORM, tera: tera); } + TURN { MOVE(player, MOVE_TERA_STARSTORM, gimmick: tera); } } SCENE { MESSAGE("Terapagos used Tera Starstorm!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_STARSTORM, player); diff --git a/test/battle/move_effect/toxic.c b/test/battle/move_effect/toxic.c index 35bfaa0644..804ed56b8f 100644 --- a/test/battle/move_effect/toxic.c +++ b/test/battle/move_effect/toxic.c @@ -48,3 +48,21 @@ SINGLE_BATTLE_TEST("Toxic cannot miss if used by a Poison-type") } } } + +AI_SINGLE_BATTLE_TEST("AI avoids toxic when it can not poison target") +{ + u32 species, ability; + + PARAMETRIZE { species = SPECIES_SNORLAX; ability = ABILITY_IMMUNITY; } + PARAMETRIZE { species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } + PARAMETRIZE { species = SPECIES_NACLI; ability = ABILITY_PURIFYING_SALT; } + PARAMETRIZE { species = SPECIES_BULBASAUR; ability = ABILITY_OVERGROW; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(species) { Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_TOXIC); } + } WHEN { + TURN { SCORE_EQ(opponent, MOVE_CELEBRATE, MOVE_TOXIC); } // Both get -10 + } +} diff --git a/test/battle/move_effect/two_turns_attack.c b/test/battle/move_effect/two_turns_attack.c index 9225afb48e..4b7adc6f81 100644 --- a/test/battle/move_effect/two_turns_attack.c +++ b/test/battle/move_effect/two_turns_attack.c @@ -243,8 +243,8 @@ SINGLE_BATTLE_TEST("Solar Beam and Solar Blade can be used instantly in Sunlight SINGLE_BATTLE_TEST("Solar Beam's power is halved in Rain", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_RAIN_DANCE; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_RAIN_DANCE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); @@ -261,8 +261,8 @@ SINGLE_BATTLE_TEST("Solar Beam's power is halved in Rain", s16 damage) SINGLE_BATTLE_TEST("Solar Blade's power is halved in Rain", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_RAIN_DANCE; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_RAIN_DANCE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WYNAUT); @@ -279,8 +279,8 @@ SINGLE_BATTLE_TEST("Solar Blade's power is halved in Rain", s16 damage) SINGLE_BATTLE_TEST("Solar Beam's power is halved in a Sandstorm", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_SANDSTORM; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SANDSTORM; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SAFETY_GOGGLES); }; @@ -297,8 +297,8 @@ SINGLE_BATTLE_TEST("Solar Beam's power is halved in a Sandstorm", s16 damage) SINGLE_BATTLE_TEST("Solar Blade's power is halved in a Sandstorm", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_SANDSTORM; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SANDSTORM; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SAFETY_GOGGLES); }; @@ -315,8 +315,8 @@ SINGLE_BATTLE_TEST("Solar Blade's power is halved in a Sandstorm", s16 damage) SINGLE_BATTLE_TEST("Solar Beam's power is halved in Hail", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_HAIL; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SAFETY_GOGGLES); }; @@ -333,8 +333,8 @@ SINGLE_BATTLE_TEST("Solar Beam's power is halved in Hail", s16 damage) SINGLE_BATTLE_TEST("Solar Blade's power is halved in Hail", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_HAIL; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SAFETY_GOGGLES); }; @@ -351,8 +351,8 @@ SINGLE_BATTLE_TEST("Solar Blade's power is halved in Hail", s16 damage) SINGLE_BATTLE_TEST("Solar Beam's power is halved in Snow", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_SNOWSCAPE; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); @@ -369,8 +369,8 @@ SINGLE_BATTLE_TEST("Solar Beam's power is halved in Snow", s16 damage) SINGLE_BATTLE_TEST("Solar Blade's power is halved in Snow", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_SNOWSCAPE; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WYNAUT); diff --git a/test/battle/move_effect/weather_ball.c b/test/battle/move_effect/weather_ball.c index 38c902f507..1656b60d87 100644 --- a/test/battle/move_effect/weather_ball.c +++ b/test/battle/move_effect/weather_ball.c @@ -9,8 +9,8 @@ ASSUMPTIONS SINGLE_BATTLE_TEST("Weather Ball doubles its power and turns to a Fire-type move in Sunlight", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_SUNNY_DAY; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SUNNY_DAY; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_MEGANIUM); @@ -27,8 +27,8 @@ SINGLE_BATTLE_TEST("Weather Ball doubles its power and turns to a Fire-type move SINGLE_BATTLE_TEST("Weather Ball doubles its power and turns to a Water-type move in Rain", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_RAIN_DANCE; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_RAIN_DANCE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_ARCANINE); @@ -45,8 +45,8 @@ SINGLE_BATTLE_TEST("Weather Ball doubles its power and turns to a Water-type mov SINGLE_BATTLE_TEST("Weather Ball doubles its power and turns to a Rock-type move in a Sandstorm", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_SANDSTORM; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SANDSTORM; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_MAGMAR) { Item(ITEM_SAFETY_GOGGLES); }; @@ -63,9 +63,9 @@ SINGLE_BATTLE_TEST("Weather Ball doubles its power and turns to a Rock-type move SINGLE_BATTLE_TEST("Weather Ball doubles its power and turns to an Ice-type move in Hail and Snow", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_CELEBRATE; } - PARAMETRIZE{ move = MOVE_HAIL; } - PARAMETRIZE{ move = MOVE_SNOWSCAPE; } + PARAMETRIZE { move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_DRAGONAIR) { Item(ITEM_SAFETY_GOGGLES); }; diff --git a/test/battle/move_effects_combined/hurricane.c b/test/battle/move_effects_combined/hurricane.c index eab843e720..61acac6649 100644 --- a/test/battle/move_effects_combined/hurricane.c +++ b/test/battle/move_effects_combined/hurricane.c @@ -32,4 +32,42 @@ SINGLE_BATTLE_TEST("Hurricane bypasses accuracy checks in Rain") NONE_OF { MESSAGE("Wobbuffet's attack missed!"); } } } -TO_DO_BATTLE_TEST("Hurricane Veil can hit airborne targets") // Fly, Bounce, Sky Drop + +SINGLE_BATTLE_TEST("Hurricane can hit airborne targets (Fly, Bounce)") +{ + u16 move; + PARAMETRIZE { move = MOVE_FLY; } + PARAMETRIZE { move = MOVE_BOUNCE; } + GIVEN { + ASSUME(gMovesInfo[MOVE_FLY].effect == EFFECT_SEMI_INVULNERABLE); + ASSUME(UNCOMPRESS_BITS(HIHALF(gMovesInfo[MOVE_FLY].argument)) == STATUS3_ON_AIR); + ASSUME(gMovesInfo[MOVE_BOUNCE].effect == EFFECT_SEMI_INVULNERABLE); + ASSUME(UNCOMPRESS_BITS(HIHALF(gMovesInfo[MOVE_BOUNCE].argument)) == STATUS3_ON_AIR); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(move); } + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_HURRICANE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HURRICANE, player); + NONE_OF { MESSAGE("Wobbuffet's attack missed!"); } + } +} + +DOUBLE_BATTLE_TEST("Hurricane can hit airborne targets (Sky Drop)") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_SKY_DROP].effect == EFFECT_SKY_DROP); + ASSUME(UNCOMPRESS_BITS(HIHALF(gMovesInfo[MOVE_SKY_DROP].argument)) == STATUS3_ON_AIR); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SKY_DROP, target: opponentLeft); MOVE(playerRight, MOVE_HURRICANE, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_DROP, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HURRICANE, playerRight); + NONE_OF { MESSAGE("Wobbuffet's attack missed!"); } + } +} diff --git a/test/battle/move_flags/ignores_target_ability.c b/test/battle/move_flags/ignores_target_ability.c new file mode 100644 index 0000000000..2836f4838e --- /dev/null +++ b/test/battle/move_flags/ignores_target_ability.c @@ -0,0 +1,74 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_SUNSTEEL_STRIKE].ignoresTargetAbility); + ASSUME(gMovesInfo[MOVE_MOONGEIST_BEAM].ignoresTargetAbility); + ASSUME(gMovesInfo[MOVE_PHOTON_GEYSER].ignoresTargetAbility); +} + +SINGLE_BATTLE_TEST("ignoresTargetAbility moves do not ignore the attacker's own ability", s16 damage) +{ + u32 ability, move; + + PARAMETRIZE { move = MOVE_SUNSTEEL_STRIKE; ability = ABILITY_MAGIC_GUARD; } + PARAMETRIZE { move = MOVE_SUNSTEEL_STRIKE; ability = ABILITY_UNAWARE; } + PARAMETRIZE { move = MOVE_MOONGEIST_BEAM; ability = ABILITY_MAGIC_GUARD; } + PARAMETRIZE { move = MOVE_MOONGEIST_BEAM; ability = ABILITY_UNAWARE; } + PARAMETRIZE { move = MOVE_PHOTON_GEYSER; ability = ABILITY_MAGIC_GUARD; } + PARAMETRIZE { move = MOVE_PHOTON_GEYSER; ability = ABILITY_UNAWARE; } + + ASSUME(gAbilitiesInfo[ABILITY_UNAWARE].breakable); + ASSUME(gMovesInfo[MOVE_IRON_DEFENSE].effect == EFFECT_DEFENSE_UP_2); + ASSUME(gMovesInfo[MOVE_AMNESIA].effect == EFFECT_SPECIAL_DEFENSE_UP_2); + + GIVEN { + PLAYER(SPECIES_CLEFABLE) { Speed(1); Ability(ability); } + OPPONENT(SPECIES_ARON) { Speed(2); } + } WHEN { + if (gMovesInfo[move].category == DAMAGE_CATEGORY_PHYSICAL) + TURN { MOVE(opponent, MOVE_IRON_DEFENSE); MOVE(player, move); } + else + TURN { MOVE(opponent, MOVE_AMNESIA); MOVE(player, move); } + } SCENE { + if (gMovesInfo[move].category == DAMAGE_CATEGORY_PHYSICAL) + ANIMATION(ANIM_TYPE_MOVE, MOVE_IRON_DEFENSE, opponent); + else + ANIMATION(ANIM_TYPE_MOVE, MOVE_AMNESIA, opponent); + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage); + EXPECT_MUL_EQ(results[2].damage, UQ_4_12(2.0), results[3].damage); + EXPECT_MUL_EQ(results[4].damage, UQ_4_12(2.0), results[5].damage); + } +} + +SINGLE_BATTLE_TEST("ignoresTargetAbility moves do ignore target's abilities", s16 damage) +{ + u32 ability, move; + + PARAMETRIZE { move = MOVE_SUNSTEEL_STRIKE; ability = ABILITY_INNER_FOCUS; } + PARAMETRIZE { move = MOVE_SUNSTEEL_STRIKE; ability = ABILITY_MULTISCALE; } + PARAMETRIZE { move = MOVE_MOONGEIST_BEAM; ability = ABILITY_INNER_FOCUS; } + PARAMETRIZE { move = MOVE_MOONGEIST_BEAM; ability = ABILITY_MULTISCALE; } + PARAMETRIZE { move = MOVE_PHOTON_GEYSER; ability = ABILITY_INNER_FOCUS; } + PARAMETRIZE { move = MOVE_PHOTON_GEYSER; ability = ABILITY_MULTISCALE; } + + ASSUME(gAbilitiesInfo[ABILITY_MULTISCALE].breakable); + + GIVEN { + PLAYER(SPECIES_AZUMARILL); + OPPONENT(SPECIES_DRAGONITE) { Ability(ability); } + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + EXPECT_EQ(results[2].damage, results[3].damage); + EXPECT_EQ(results[4].damage, results[5].damage); + } +} diff --git a/test/battle/status1/burn.c b/test/battle/status1/burn.c index c49bd7a3f2..4da40589fb 100644 --- a/test/battle/status1/burn.c +++ b/test/battle/status1/burn.c @@ -35,3 +35,23 @@ SINGLE_BATTLE_TEST("Burn reduces Attack by 50%", s16 damage) EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.5), results[1].damage); } } + +AI_SINGLE_BATTLE_TEST("AI avoids Will-o-Wisp when it can not burn target") +{ + u32 species, ability; + + PARAMETRIZE { species = SPECIES_BUIZEL; ability = ABILITY_WATER_VEIL; } + PARAMETRIZE { species = SPECIES_DEWPIDER; ability = ABILITY_WATER_BUBBLE; } + PARAMETRIZE { species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } + PARAMETRIZE { species = SPECIES_ARCTIBAX; ability = ABILITY_THERMAL_EXCHANGE; } + PARAMETRIZE { species = SPECIES_NACLI; ability = ABILITY_PURIFYING_SALT; } + PARAMETRIZE { species = SPECIES_CHARMANDER; ability = ABILITY_BLAZE; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(species) { Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_WILL_O_WISP); } + } WHEN { + TURN { SCORE_EQ(opponent, MOVE_CELEBRATE, MOVE_WILL_O_WISP); } // Both get -10 + } +} diff --git a/test/battle/status1/paralysis.c b/test/battle/status1/paralysis.c index b6edaf2635..558ce4fc37 100644 --- a/test/battle/status1/paralysis.c +++ b/test/battle/status1/paralysis.c @@ -42,3 +42,21 @@ SINGLE_BATTLE_TEST("Paralysis has a 25% chance of skipping the turn") MESSAGE("Wobbuffet is paralyzed! It can't move!"); } } + +AI_SINGLE_BATTLE_TEST("AI avoids Thunder Wave when it can not paralyse target") +{ + u32 species, ability; + + PARAMETRIZE { species = SPECIES_HITMONLEE; ability = ABILITY_LIMBER; } + PARAMETRIZE { species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } + PARAMETRIZE { species = SPECIES_NACLI; ability = ABILITY_PURIFYING_SALT; } + PARAMETRIZE { species = SPECIES_PIKACHU; ability = ABILITY_STATIC; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(species) { Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_THUNDER_WAVE); } + } WHEN { + TURN { SCORE_EQ(opponent, MOVE_CELEBRATE, MOVE_THUNDER_WAVE); } // Both get -10 + } +} diff --git a/test/battle/status1/sleep.c b/test/battle/status1/sleep.c index b3dd403eb1..f42f4bd714 100644 --- a/test/battle/status1/sleep.c +++ b/test/battle/status1/sleep.c @@ -21,3 +21,21 @@ SINGLE_BATTLE_TEST("Sleep prevents the battler from using a move") MESSAGE("Wobbuffet used Celebrate!"); } } + +AI_SINGLE_BATTLE_TEST("AI avoids hypnosis when it can not put target to sleep") +{ + u32 species, ability; + + PARAMETRIZE { species = SPECIES_HOOTHOOT; ability = ABILITY_INSOMNIA; } + PARAMETRIZE { species = SPECIES_MANKEY; ability = ABILITY_VITAL_SPIRIT; } + PARAMETRIZE { species = SPECIES_KOMALA; ability = ABILITY_COMATOSE; } + PARAMETRIZE { species = SPECIES_NACLI; ability = ABILITY_PURIFYING_SALT; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(species) { Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_HYPNOSIS); } + } WHEN { + TURN { SCORE_EQ(opponent, MOVE_CELEBRATE, MOVE_HYPNOSIS); } // Both get -10 + } +} diff --git a/test/battle/status2/confusion.c b/test/battle/status2/confusion.c new file mode 100644 index 0000000000..3c86e5d555 --- /dev/null +++ b/test/battle/status2/confusion.c @@ -0,0 +1,28 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Confusion adds a 50/33% chance to hit self with 40 power") +{ + s16 damage[2]; + + ASSUME(gMovesInfo[MOVE_TACKLE].power == 40); + + PASSES_RANDOMLY(B_CONFUSION_SELF_DMG_CHANCE >= GEN_7 ? 33 : 50, 100, RNG_CONFUSION); + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(1); }; + OPPONENT(SPECIES_WOBBUFFET) { Speed(2); }; + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE, WITH_RNG(RNG_DAMAGE_MODIFIER, 0)); MOVE(player, MOVE_CONFUSE_RAY); } + TURN; + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + HP_BAR(player, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, player); + MESSAGE("Foe Wobbuffet became confused!"); + MESSAGE("Foe Wobbuffet is confused!"); + MESSAGE("It hurt itself in its confusion!"); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + } +} diff --git a/test/battle/terrain/grassy.c b/test/battle/terrain/grassy.c index 678352dc5e..862c9052fd 100644 --- a/test/battle/terrain/grassy.c +++ b/test/battle/terrain/grassy.c @@ -85,3 +85,27 @@ SINGLE_BATTLE_TEST("Grassy Terrain lasts for 5 turns") MESSAGE("The grass disappeared from the battlefield."); } } + +SINGLE_BATTLE_TEST("Grassy Terrain heals the pokemon on the field for the duration of the terrain, including last turn") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); }; + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_GRASSY_TERRAIN); } + TURN {} + TURN {} + TURN {} + TURN {} + } SCENE { + MESSAGE("Foe Wobbuffet used Celebrate!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASSY_TERRAIN, player); + MESSAGE("Grass grew to cover the battlefield!"); + MESSAGE("Foe Wobbuffet is healed by the grassy terrain!"); + MESSAGE("Foe Wobbuffet is healed by the grassy terrain!"); + MESSAGE("Foe Wobbuffet is healed by the grassy terrain!"); + MESSAGE("Foe Wobbuffet is healed by the grassy terrain!"); + MESSAGE("Foe Wobbuffet is healed by the grassy terrain!"); + MESSAGE("The grass disappeared from the battlefield."); + } +} diff --git a/test/battle/terrain/starting_terrain.c b/test/battle/terrain/starting_terrain.c new file mode 100644 index 0000000000..37caa20bc7 --- /dev/null +++ b/test/battle/terrain/starting_terrain.c @@ -0,0 +1,113 @@ +#include "global.h" +#include "event_data.h" +#include "test/battle.h" + +#if B_VAR_STARTING_STATUS != 0 + +SINGLE_BATTLE_TEST("B_VAR_STARTING_STATUS starts a chosen terrain at the beginning of battle and lasts infinitely long") +{ + u16 terrain; + + PARAMETRIZE { terrain = STARTING_STATUS_GRASSY_TERRAIN; } + PARAMETRIZE { terrain = STARTING_STATUS_PSYCHIC_TERRAIN; } + PARAMETRIZE { terrain = STARTING_STATUS_MISTY_TERRAIN; } + PARAMETRIZE { terrain = STARTING_STATUS_ELECTRIC_TERRAIN; } + + VarSet(B_VAR_STARTING_STATUS, terrain); + VarSet(B_VAR_STARTING_STATUS_TIMER, 0); + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + // More than 5 turns + TURN { ; } + TURN { ; } + TURN { ; } + TURN { ; } + TURN { ; } + TURN { ; } + TURN { ; } + } SCENE { + switch (terrain) { + case STARTING_STATUS_GRASSY_TERRAIN: + MESSAGE("Grass grew to cover the battlefield!"); + break; + case STARTING_STATUS_PSYCHIC_TERRAIN: + MESSAGE("The battlefield got weird!"); + break; + case STARTING_STATUS_MISTY_TERRAIN: + MESSAGE("Mist swirled about the battlefield!"); + break; + case STARTING_STATUS_ELECTRIC_TERRAIN: + MESSAGE("An electric current runs across the battlefield!"); + break; + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_RESTORE_BG); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_RESTORE_BG); + 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."); + } + } THEN { + VarSet(B_VAR_STARTING_STATUS, 0); + } +} + +SINGLE_BATTLE_TEST("Terrain started after the one which started the battle lasts only 5 turns") +{ + bool32 viaMove; + + PARAMETRIZE { viaMove = TRUE; } + PARAMETRIZE { viaMove = FALSE; } + + VarSet(B_VAR_STARTING_STATUS, STARTING_STATUS_ELECTRIC_TERRAIN); + VarSet(B_VAR_STARTING_STATUS_TIMER, 0); + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Ability(viaMove == TRUE ? ABILITY_SHADOW_TAG : ABILITY_GRASSY_SURGE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + // More than 5 turns + TURN { MOVE(player, viaMove == TRUE ? MOVE_GRASSY_TERRAIN : MOVE_CELEBRATE); } + TURN { ; } + TURN { ; } + TURN { ; } + TURN { ; } + TURN { ; } + TURN { ; } + } SCENE { + // Electric Terrain at battle's start + MESSAGE("An electric current runs across the battlefield!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_RESTORE_BG); + // Player uses Grassy Terrain + if (viaMove) { + MESSAGE("Wobbuffet used GrssyTerrain!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASSY_TERRAIN, player); + MESSAGE("Grass grew to cover the battlefield!"); + } else { + ABILITY_POPUP(player, ABILITY_GRASSY_SURGE); + MESSAGE("Grass grew to cover the battlefield!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_RESTORE_BG); + } + + // 5 turns + MESSAGE("Wobbuffet used Celebrate!"); + MESSAGE("Foe Wobbuffet used Celebrate!"); + + MESSAGE("Wobbuffet used Celebrate!"); + MESSAGE("Foe Wobbuffet used Celebrate!"); + + MESSAGE("Wobbuffet used Celebrate!"); + MESSAGE("Foe Wobbuffet used Celebrate!"); + + MESSAGE("The grass disappeared from the battlefield."); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_RESTORE_BG); + } THEN { + VarSet(B_VAR_STARTING_STATUS, 0); + } +} + +#endif // B_VAR_STARTING_STATUS diff --git a/test/battle/trainer_control.h b/test/battle/trainer_control.h index 125a19d46f..09c4a73d64 100644 --- a/test/battle/trainer_control.h +++ b/test/battle/trainer_control.h @@ -52,7 +52,7 @@ .isShiny = TRUE, #line 18 .dynamaxLevel = 5, - .shouldDynamax = TRUE, + .useGimmick = GIMMICK_DYNAMAX, .moves = { #line 19 MOVE_AIR_SLASH, diff --git a/test/battle/weather/sandstorm.c b/test/battle/weather/sandstorm.c index 5d7a6f1713..2f3f4e7ca2 100644 --- a/test/battle/weather/sandstorm.c +++ b/test/battle/weather/sandstorm.c @@ -20,8 +20,8 @@ SINGLE_BATTLE_TEST("Sandstorm deals 1/16 damage per turn") SINGLE_BATTLE_TEST("Sandstorm multiplies the special defense of Rock-types by 1.5x", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_SANDSTORM; } - PARAMETRIZE{ move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SANDSTORM; } + PARAMETRIZE { move = MOVE_CELEBRATE; } GIVEN { ASSUME(gMovesInfo[MOVE_SWIFT].category == DAMAGE_CATEGORY_SPECIAL); PLAYER(SPECIES_WOBBUFFET) ; diff --git a/test/battle/weather/snow.c b/test/battle/weather/snow.c index 3aceaf2f85..6c084f6b7a 100644 --- a/test/battle/weather/snow.c +++ b/test/battle/weather/snow.c @@ -13,8 +13,8 @@ ASSUMPTIONS SINGLE_BATTLE_TEST("Snow multiplies the defense of Ice-types by 1.5x", s16 damage) { u16 move; - PARAMETRIZE{ move = MOVE_SNOWSCAPE; } - PARAMETRIZE{ move = MOVE_CELEBRATE; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + PARAMETRIZE { move = MOVE_CELEBRATE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_GLALIE); diff --git a/test/species.c b/test/species.c index d4f0b4027e..d09f1e3eb9 100644 --- a/test/species.c +++ b/test/species.c @@ -1,4 +1,5 @@ #include "global.h" +#include "string_util.h" #include "test/test.h" #include "constants/form_change_types.h" @@ -138,3 +139,18 @@ TEST("No species has two evolutions that use the evolution tracker") EXPECT(evolutionTrackerEvolutions < 2); } + +extern const u8 gFallbackPokedexText[]; + +TEST("Every species has a description") +{ + u32 i; + u32 species = SPECIES_NONE; + for (i = 1; i < NUM_SPECIES; i++) + { + if (IsSpeciesEnabled(i)) + PARAMETRIZE { species = i; } + } + + EXPECT_NE(StringCompare(GetSpeciesPokedexDescription(species), gFallbackPokedexText), 0); +} diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index 23433502fe..5c422ae28a 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -3,6 +3,8 @@ #include "battle_ai_util.h" #include "battle_anim.h" #include "battle_controllers.h" +#include "battle_gimmick.h" +#include "battle_z_move.h" #include "characters.h" #include "event_data.h" #include "fieldmap.h" @@ -2101,17 +2103,31 @@ void MoveGetIdAndSlot(s32 battlerId, struct MoveContext *ctx, u32 *moveId, u32 * INVALID("No move or moveSlot"); } - if (ctx->explicitMegaEvolve && ctx->megaEvolve) - *moveSlot |= RET_MEGA_EVOLUTION; + if (ctx->explicitGimmick && ctx->gimmick != GIMMICK_NONE) + { + u32 item = GetMonData(mon, MON_DATA_HELD_ITEM); + u32 holdEffect = ItemId_GetHoldEffect(item); + u32 species = GetMonData(mon, MON_DATA_SPECIES); + u32 side = GetBattlerSide(battlerId); - if (ctx->explicitUltraBurst && ctx->ultraBurst) - *moveSlot |= RET_ULTRA_BURST; + // Check invalid item usage. + INVALID_IF(ctx->gimmick == GIMMICK_MEGA && holdEffect != HOLD_EFFECT_MEGA_STONE && species != SPECIES_RAYQUAZA, "Cannot Mega Evolve without a Mega Stone"); + INVALID_IF(ctx->gimmick == GIMMICK_Z_MOVE && holdEffect != HOLD_EFFECT_Z_CRYSTAL, "Cannot use a Z-Move without a Z-Crystal"); + INVALID_IF(ctx->gimmick == GIMMICK_Z_MOVE && ItemId_GetSecondaryId(item) != gMovesInfo[*moveId].type + && GetSignatureZMove(*moveId, species, item) == MOVE_NONE + && *moveId != MOVE_PHOTON_GEYSER, // exception because test won't recognize Ultra Necrozma pre-Burst + "Cannot turn %S into a Z-Move with %S", GetMoveName(ctx->move), ItemId_GetName(item)); + INVALID_IF(ctx->gimmick != GIMMICK_MEGA && holdEffect == HOLD_EFFECT_MEGA_STONE, "Cannot use another gimmick while holding a Mega Stone"); + INVALID_IF(ctx->gimmick != GIMMICK_Z_MOVE && ctx->gimmick != GIMMICK_ULTRA_BURST && holdEffect == HOLD_EFFECT_Z_CRYSTAL, "Cannot use another gimmick while holding a Z-Crystal"); - if (ctx->explicitDynamax && ctx->dynamax) - *moveSlot |= RET_DYNAMAX; - - if (ctx->explicitTera && ctx->tera) - *moveSlot |= RET_TERASTAL; + // Check multiple gimmick use. + INVALID_IF(DATA.chosenGimmick[side][DATA.currentMonIndexes[battlerId]] != GIMMICK_NONE + && !(DATA.chosenGimmick[side][DATA.currentMonIndexes[battlerId]] == GIMMICK_ULTRA_BURST + && ctx->gimmick == GIMMICK_Z_MOVE), "Cannot use multiple gimmicks on the same battler"); + + DATA.chosenGimmick[side][DATA.currentMonIndexes[battlerId]] = ctx->gimmick; + *moveSlot |= RET_GIMMICK; + } } void Move(u32 sourceLine, struct BattlePokemon *battler, struct MoveContext ctx) @@ -2675,6 +2691,11 @@ u32 TestRunner_Battle_GetForcedAbility(u32 side, u32 partyIndex) return DATA.forcedAbilities[side][partyIndex]; } +u32 TestRunner_Battle_GetChosenGimmick(u32 side, u32 partyIndex) +{ + return DATA.chosenGimmick[side][partyIndex]; +} + // TODO: Consider storing the last successful i and searching from i+1 // to improve performance. struct AILogLine *GetLogLine(u32 battlerId, u32 moveIndex) diff --git a/tools/gbagfx/jasc_pal.c b/tools/gbagfx/jasc_pal.c index e5ba9c3c2f..8d4bb137d5 100644 --- a/tools/gbagfx/jasc_pal.c +++ b/tools/gbagfx/jasc_pal.c @@ -46,10 +46,14 @@ void ReadJascPaletteLine(FILE *fp, char *line) } if (c == '\n') - FATAL_ERROR("LF line endings aren't supported.\n"); + { + line[length] = 0; + + return; + } if (c == EOF) - FATAL_ERROR("Unexpected EOF. No CRLF at end of file.\n"); + FATAL_ERROR("Unexpected EOF. No LF or CRLF at end of file.\n"); if (c == 0) FATAL_ERROR("NUL character in file.\n"); diff --git a/tools/trainerproc/main.c b/tools/trainerproc/main.c index de8b7786c4..52df550bd7 100644 --- a/tools/trainerproc/main.c +++ b/tools/trainerproc/main.c @@ -1824,16 +1824,15 @@ static void fprint_trainers(const char *output_path, FILE *f, struct Parsed *par if (pokemon->dynamax_level_line || pokemon->gigantamax_factor_line) { - fprintf(f, " .shouldDynamax = TRUE,\n"); + fprintf(f, " .useGimmick = GIMMICK_DYNAMAX,\n"); } - - if (pokemon->tera_type_line) + else if (pokemon->tera_type_line) { fprintf(f, "#line %d\n", pokemon->tera_type_line); fprintf(f, " .teraType = "); fprint_constant(f, "TYPE", pokemon->tera_type); fprintf(f, ",\n"); - fprintf(f, " .shouldTerastal = TRUE,\n"); + fprintf(f, " .useGimmick = GIMMICK_TERA,\n"); } if (pokemon->moves_n > 0)