From 18980b20a3128b22ab9ba23f2a466d076e95536d Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Wed, 14 Aug 2024 22:48:20 -0400 Subject: [PATCH 01/16] Remove trailing whitespace (master) (#5174) --- INSTALL.md | 4 +- data/battle_scripts_2.s | 2 +- .../followers/rename_to_graphics_pokemon.py | 2 +- docs/ai_flags.md | 16 +- docs/changelogs/1.5.x/1.5.2.md | 2 +- docs/changelogs/1.5.x/1.5.3.md | 4 +- docs/changelogs/1.7.x/1.7.2.md | 2 +- docs/how_to_new_pokemon_1_6_0.md | 10 +- docs/how_to_new_pokemon_1_7_0.md | 24 +-- docs/how_to_new_pokemon_1_8_0.md | 22 +-- docs/how_to_new_pokemon_1_9_0.md | 24 +-- docs/how_to_testing_system.md | 150 +++++++++--------- docs/how_to_trainer_class.md | 4 +- include/test/battle.h | 2 +- migration_scripts/1.8/item_ball_refactor.py | 2 +- .../1.9/battle_anim_moves_refactor.py | 2 +- spritesheet_rules.mk | 2 +- src/battle_anim.c | 2 +- src/battle_anim_dragon.c | 12 +- src/battle_anim_effects_1.c | 6 +- src/battle_anim_flying.c | 2 +- src/battle_anim_normal.c | 4 +- src/battle_controllers.c | 2 +- src/battle_dome.c | 8 +- src/battle_factory.c | 4 +- src/battle_factory_screen.c | 2 +- src/battle_setup.c | 2 +- src/clock.c | 2 +- src/data/gimmicks.h | 8 +- .../object_event_graphics_info_pointers.h | 2 +- src/event_object_movement.c | 10 +- src/field_specials.c | 4 +- src/load_save.c | 2 +- src/map_name_popup.c | 4 +- src/script_menu.c | 2 +- src/script_pokemon_util.c | 2 +- src/vs_seeker.c | 2 +- test/battle/ability/anger_point.c | 4 +- test/battle/ability/clear_body.c | 4 +- test/battle/ability/gulp_missile.c | 2 +- test/battle/ability/magic_guard.c | 2 +- test/battle/ai/ai_flag_risky.c | 8 +- test/battle/gimmick/terastal.c | 2 +- test/battle/gimmick/zmove.c | 4 +- test/battle/hold_effect/ability_shield.c | 2 +- test/battle/move_effect/heal_bell.c | 12 +- test/battle/move_effect/knock_off.c | 4 +- test/battle/move_effect/revelation_dance.c | 4 +- test/battle/move_effect/sleep_talk.c | 4 +- test/battle/move_effect/telekinesis.c | 2 +- tools/learnset_helpers/teachable.py | 4 +- tools/preproc/asm_file.cpp | 6 +- 52 files changed, 210 insertions(+), 210 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 6349d24fbbd8..5744adcb04a9 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -327,7 +327,7 @@ If this works, then proceed to [Installation](#installation). Otherwise, ask for libpng is now installed. Continue to [Installing pkg-config (macOS)](#installing-pkg-config-macos) if **pkg-config is not installed**. Otherwise, continue to [Installing devkitARM (macOS)](#installing-devkitarm-macos) if **devkitARM is not installed**. - + If both pkg-config and devkitARM are already installed, go to [Choosing where to store pokeemerald Expansion (macOS)](#choosing-where-to-store-pokeemerald-expansion-macos). ### Installing pkg-config (macOS) @@ -541,7 +541,7 @@ If this works, then proceed to [Installation](#installation). Otherwise, ask for > ``` > Where *\* is the path of the folder [where you chose to store pokeemerald Expansion](#Choosing-where-to-store-pokeemerald-expansion-WSL1). Then run the `git clone` command again. - + Now you're ready to build pokeemerald Expansion. ## Build pokeemerald Expansion diff --git a/data/battle_scripts_2.s b/data/battle_scripts_2.s index 6238afb36c27..7fe22195c864 100644 --- a/data/battle_scripts_2.s +++ b/data/battle_scripts_2.s @@ -45,7 +45,7 @@ BattleScript_UseItemMessage: printfromtable gTrainerUsedItemStringIds waitmessage B_WAIT_TIME_LONG return - + BattleScript_ItemRestoreHPRet: bichalfword gMoveResultFlags, MOVE_RESULT_NO_EFFECT orword gHitMarker, HITMARKER_IGNORE_SUBSTITUTE diff --git a/dev_scripts/followers/rename_to_graphics_pokemon.py b/dev_scripts/followers/rename_to_graphics_pokemon.py index bad1758b0c5e..a89348ddb43b 100644 --- a/dev_scripts/followers/rename_to_graphics_pokemon.py +++ b/dev_scripts/followers/rename_to_graphics_pokemon.py @@ -59,6 +59,6 @@ def rellocate_follower_graphics(): #os.popen('cp followers/' + name + '.png followers/' + name + '/follower.png') #os.remove('followers/' + name + '.png') #print(pth) - #subprocess.run(["tools/gbagfx/gbagfx " + name +".png " + name + "_normal.pal'" + str(count) + "'"]) + #subprocess.run(["tools/gbagfx/gbagfx " + name +".png " + name + "_normal.pal'" + str(count) + "'"]) rellocate_follower_graphics() diff --git a/docs/ai_flags.md b/docs/ai_flags.md index cc5c1631ff6d..bb7b1b7444e5 100644 --- a/docs/ai_flags.md +++ b/docs/ai_flags.md @@ -21,14 +21,14 @@ AI: Check Bad Move / Try to Faint / Check Viability. The name of each flag is ju * Sequence Switching ## `COMPETITIVE_PARTY_SYNTAX != TRUE` / Not Found -If you are not using competitive syntax parties, instead access the trainer data directly in [`src/data/trainers.h`](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/trainers.h), and add flags like so, typed exactly the same as the flag names themselves: +If you are not using competitive syntax parties, instead access the trainer data directly in [`src/data/trainers.h`](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/trainers.h), and add flags like so, typed exactly the same as the flag names themselves: `.aiFlags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY` # What AI Flags does pokeemerald-expansion have? This section lists all of expansion’s AI Flags and briefly describes the effect they have on the AI’s behaviour. In all cases, please check the corresponding function or surrounding code around their implementation for more details. Some of these functions are vanilla, some share a name with vanilla but have been modified to varying degrees, and some are completely new. ## `AI_FLAG_CHECK_BAD_MOVE` -The AI will avoid using moves that are likely to fail in the current situation. This flag helps prevent the AI from making ineffective choices, such as using moves into immunities, into invulnerable states, or when the moves are otherwise hindered by abilities, terrain, or status conditions. +The AI will avoid using moves that are likely to fail in the current situation. This flag helps prevent the AI from making ineffective choices, such as using moves into immunities, into invulnerable states, or when the moves are otherwise hindered by abilities, terrain, or status conditions. ## `AI_FLAG_TRY_TO_FAINT` AI will prioritize KOing the player if able rather than using status moves. Will prioritize using a move that can OHKO the player. If the player can KO the AI’s mon and the AI’s mon is slower, prioritize priority moves (this does not prevent the AI from switching out instead). @@ -58,12 +58,12 @@ AI will generally behave more recklessly. This AI enables the following behaviou * Prioritize Explosion moves ## `AI_FLAG_PREFER_STRONGEST_MOVE` -Adds score bonus to any move the AI has that either OHKOs or 2HKOs the player. +Adds score bonus to any move the AI has that either OHKOs or 2HKOs the player. Keep in mind that this is a weaker form of `AI_FLAG_TRY_TO_FAINT` at scoring OHKOs as it does not take into account who is attacking first, it does however handle 2HKOs. ## `AI_FLAG_PREFER_BATON_PASS` -AI prefers raising its own stats if it has >= 60% HP, as well as Ingrain, Aqua Ring, and Protect. Prioritizes Baton Bass if the mon is rooted (Ingrain) or has the Aqua Ring effect, and doesn’t if it has been Leech Seeded. +AI prefers raising its own stats if it has >= 60% HP, as well as Ingrain, Aqua Ring, and Protect. Prioritizes Baton Bass if the mon is rooted (Ingrain) or has the Aqua Ring effect, and doesn’t if it has been Leech Seeded. ## `AI_FLAG_DOUBLE_BATTLE` This flag is automatically set in double battles, and controls much of the doubles-specific scoring. I’ll summarize some of its scoring as follows: @@ -83,7 +83,7 @@ With respect to the AI’s mons, in doubles: In both singles and doubles: * Prioritizes not using moves that require the user fainting (Destiny Bond, Explosion etc.) and healing moves while on >= 70% HP. * Prioritize not using moves that require the user fainting or losing significant HP (Belly Drum etc) while between 30% and 70% HP -* Prioritize not using setup moves (Light Screen etc.) and Bide while on <= 30% HP +* Prioritize not using setup moves (Light Screen etc.) and Bide while on <= 30% HP With respect to the player’s mons: * Prioritize not using many status moves (stat buffs, Poison, Pain Split) if the player has between 30% and 70% HP @@ -96,7 +96,7 @@ AI prioritizes setting up field effects (Trick Room, Rain Dance, etc.) and side AI does not understand ability suppression (Mold Breaker etc., weather suppression (Air Lock etc.), redirection abilities (Lightningrod etc.) being temporarily removed due to move effects (Sky Drop etc.), or item suppression (Magic Room etc.) and will ignore them. This is a handicap flag. ## `AI_FLAG_WILL_SUICIDE` -AI prioritizes self destruction moves (Explosion, Memento). +AI prioritizes self destruction moves (Explosion, Memento). ## `AI_FLAG_PREFER_STATUS_MOVES` AI gets a score bonus for status moves. This should be combined with `AI_FLAG_CHECK_BAD_MOVE` to prevent using only status moves. @@ -127,7 +127,7 @@ Affects when the AI chooses to switch. AI will make smarter decisions about when Marks the last Pokemon in the party as the Ace Pokemon. It will not be used unless it is the last one remaining, or is forced to be switched in (Roar, U-Turn with 1 mon remaining, etc.) ## `AI_FLAG_OMNISCIENT` -AI has full knowledge of player moves, abilities, and hold items, and can use this knowledge when making decisions. +AI has full knowledge of player moves, abilities, and hold items, and can use this knowledge when making decisions. ## `AI_FLAG_SMART_MON_CHOICES` Affects what the AI chooses to send out after a switch. AI will make smarter decisions when choosing which mon to send out mid-battle and after a KO, which are handled separately. Automatically included when `AI_FLAG_SMART_SWITCHING` is enabled. @@ -148,7 +148,7 @@ And will choose mons after a mid-battle switch prioritizing the following criter * Has Baton Pass ## `AI_FLAG_CONSERVATIVE` -AI always assumes it will roll the lowest possible result when comparing damage in scoring. +AI always assumes it will roll the lowest possible result when comparing damage in scoring. ## `AI_FLAG_SEQUENCE_SWITCHING` AI will always switch out after a KO in exactly party order as defined in the trainer data (ie. slot 1, then 2, then 3, etc.). The AI will never switch out mid-battle unless forced to (Roar etc.). If the AI uses a move that requires a switch where it makes a decision about what to send in (U-Turn etc.), it will always switch out into the lowest available party index. diff --git a/docs/changelogs/1.5.x/1.5.2.md b/docs/changelogs/1.5.x/1.5.2.md index ab6735c7a36a..69e0ecaabf71 100644 --- a/docs/changelogs/1.5.x/1.5.2.md +++ b/docs/changelogs/1.5.x/1.5.2.md @@ -92,5 +92,5 @@ * Court Change by @DizzyEggg in https://github.com/rh-hideout/pokeemerald-expansion/pull/3160 * Item Effects * Utility Umbrella, by @AsparagusEduardo in https://github.com/rh-hideout/pokeemerald-expansion/pull/2835 - + **Full Changelog**: https://github.com/rh-hideout/pokeemerald-expansion/compare/expansion/1.5.1...expansion/1.5.2 \ No newline at end of file diff --git a/docs/changelogs/1.5.x/1.5.3.md b/docs/changelogs/1.5.x/1.5.3.md index 831814740a01..85748b2375da 100644 --- a/docs/changelogs/1.5.x/1.5.3.md +++ b/docs/changelogs/1.5.x/1.5.3.md @@ -9,8 +9,8 @@ ## CRITICAL FIX, please update to avoid the issues detailed down below: - Fixed memory corruption when handling trigger sprites by @SBird1337 in https://github.com/rh-hideout/pokeemerald-expansion/pull/3238 - This had the posibility of manifesting in weird ways, like camera and music changes, NPC duplication and more. If you've had this issue in the past, we ***heavily*** recommend you update to this version of the expansion. - - Thank you @Bassoonian for helping us pinpointing the issue. - + - Thank you @Bassoonian for helping us pinpointing the issue. + ![image](https://github.com/rh-hideout/pokeemerald-expansion/assets/2904965/26b9b984-c5db-4dac-85f7-5fc4e95a32ce) ![image](https://github.com/rh-hideout/pokeemerald-expansion/assets/2904965/d490eb30-ce54-4b90-bb2e-79c2e9bb50ac) diff --git a/docs/changelogs/1.7.x/1.7.2.md b/docs/changelogs/1.7.x/1.7.2.md index 34aa131e70e5..a92d7789561d 100644 --- a/docs/changelogs/1.7.x/1.7.2.md +++ b/docs/changelogs/1.7.x/1.7.2.md @@ -41,7 +41,7 @@ * Clodsire * Crocalor * Dolliv - * Dudunsparce + * Dudunsparce * Esparthra ### Fixed * Multiple Pokémon graphical fixes by @katykat5099 in https://github.com/rh-hideout/pokeemerald-expansion/pull/3805 diff --git a/docs/how_to_new_pokemon_1_6_0.md b/docs/how_to_new_pokemon_1_6_0.md index d965e48a5f00..9bf3e2e61f27 100644 --- a/docs/how_to_new_pokemon_1_6_0.md +++ b/docs/how_to_new_pokemon_1_6_0.md @@ -384,7 +384,7 @@ Edit [src/data/text/species_names.h](https://github.com/rh-hideout/pokeemerald-e const u8 gSpeciesNames[][POKEMON_NAME_LENGTH + 1] = { [SPECIES_NONE] = _("??????????"), [SPECIES_BULBASAUR] = _("Bulbasaur"), - ... + ... [SPECIES_ENAMORUS] = _("Enamorus"), + [SPECIES_MEWTHREE] = _("Mewthree"), }; @@ -482,7 +482,7 @@ Append to [src/data/pokemon/pokedex_text.h](https://github.com/rh-hideout/pokeem "winter. According to legend, this\n" "Pokémon's love gives rise to the\n" "budding of fresh life across the land."); - + +const u8 gMewthreePokedexText[] = _( + "The rumors became true.\n" + "This is Mews final form.\n" @@ -509,7 +509,7 @@ Edit [src/data/pokemon/pokedex_entries.h](https://github.com/rh-hideout/pokeemer .trainerScale = 296, .trainerOffset = 1, }, - + + [NATIONAL_DEX_MEWTHREE] = + { + .categoryName = _("NEW SPECIES"), @@ -553,7 +553,7 @@ Edit [src/data/pokemon/pokedex_orders.h](https://github.com/rh-hideout/pokeemera NATIONAL_DEX_DUGTRIO, ... }; - + const u16 gPokedexOrder_Height[] = { ... @@ -598,7 +598,7 @@ Edit [src/data/pokemon/species_info.h](https://github.com/rh-hideout/pokeemerald }, + [SPECIES_MEWTHREE] = -+ { ++ { + .baseHP = 106, + .baseAttack = 150, + .baseDefense = 70, diff --git a/docs/how_to_new_pokemon_1_7_0.md b/docs/how_to_new_pokemon_1_7_0.md index 801ae8e54996..30c1f82dbe24 100644 --- a/docs/how_to_new_pokemon_1_7_0.md +++ b/docs/how_to_new_pokemon_1_7_0.md @@ -170,7 +170,7 @@ Edit [src/data/pokemon/species_info.h](https://github.com/rh-hideout/pokeemerald }, + [SPECIES_MEWTHREE] = -+ { ++ { + .baseHP = 106, + .baseAttack = 150, + .baseDefense = 70, @@ -234,7 +234,7 @@ That's all the basic fields present in vanilla emerald, so now let's take a look { ... [SPECIES_MEWTHREE] = - { + { ... .isLegendary = TRUE, .allPerfectIVs = TRUE, @@ -309,7 +309,7 @@ Lastly, we add the cry to our species entry { ... [SPECIES_MEWTHREE] = - { + { ... .isLegendary = TRUE, .allPerfectIVs = TRUE, @@ -388,7 +388,7 @@ Now we can add the number and entry to our Mewthree: { ... [SPECIES_MEWTHREE] = - { + { ... .cryId = CRY_MEWTHREE, + .natDexNum = NATIONAL_DEX_MEWTHREE, @@ -409,7 +409,7 @@ Now we can add the number and entry to our Mewthree: ``` ![image](https://github.com/rh-hideout/pokeemerald-expansion/assets/2904965/3759dd4c-8da5-4b1c-9a50-b9e9d0815e7f) -The values `pokemonScale`, `pokemonOffset`, `trainerScale` and `trainerOffset` are used for the height comparison figure in the Pokédex. +The values `pokemonScale`, `pokemonOffset`, `trainerScale` and `trainerOffset` are used for the height comparison figure in the Pokédex. `height` and `weight` are specified in decimeters and hectograms respectively (which are meters and kilograms multiplied by 10, so 2.5 meters are 25 decimeters). @@ -443,7 +443,7 @@ Edit [src/data/pokemon/pokedex_orders.h](https://github.com/rh-hideout/pokeemera NATIONAL_DEX_DUGTRIO, ... }; - + const u16 gPokedexOrder_Height[] = { ... @@ -560,7 +560,7 @@ Now that we have all the external data ready, we just need to add it to `gSpecie { ... [SPECIES_MEWTHREE] = - { + { ... .pokemonScale = 256, .pokemonOffset = 0, @@ -612,7 +612,7 @@ Let's explain each of these: - Used to define what Y position of the back sprite. When working with the animation debug menu, we recommend aligning the back sprite to the white background, as it was designed to properyly align with the real battle layout. - `backAnimId`: - Like `frontAnimId` except for the back sprites and them being a single frame. The IDs listed [here](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/include/pokemon_animation.h) are used to represent 3 different animations that happen based on the the Pokémon's nature. -- `PALETTES` +- `PALETTES` - This macro was created to handle both regular and shiny palettes of a Pokémon. It just needs the species suffix to call the corresponding palette. ```c #define PALETTES(pal) \ @@ -650,7 +650,7 @@ We're almost there just a bit left! { ... [SPECIES_MEWTHREE] = - { + { ... .abilities = { ABILITY_INSOMNIA, ABILITY_NONE, ABILITY_NONE }, .bodyColor = BODY_COLOR_PURPLE, @@ -745,7 +745,7 @@ Again, we need to register the learnset in `gSpeciesInfo`: { ... [SPECIES_MEWTHREE] = - { + { ... PALETTES(Mewthree), ICON(Mewthree, 2), @@ -846,7 +846,7 @@ Once more, we need to register the learnset in `gSpeciesInfo`: { ... [SPECIES_MEWTHREE] = - { + { ... FOOTPRINT(Mewthree) .levelUpLearnset = sMewthreeLevelUpLearnset, @@ -869,7 +869,7 @@ Edit `gSpeciesInfo`: { ... [SPECIES_MEWTWO] = - { + { ... FOOTPRINT(Mewtwo) .isLegendary = TRUE, diff --git a/docs/how_to_new_pokemon_1_8_0.md b/docs/how_to_new_pokemon_1_8_0.md index c4c7ab621cd1..6dbb009fcfff 100644 --- a/docs/how_to_new_pokemon_1_8_0.md +++ b/docs/how_to_new_pokemon_1_8_0.md @@ -181,7 +181,7 @@ Edit [src/data/pokemon/species_info.h](https://github.com/rh-hideout/pokeemerald }, + [SPECIES_MEWTHREE] = -+ { ++ { + .baseHP = 106, + .baseAttack = 150, + .baseDefense = 70, @@ -245,7 +245,7 @@ That's all the basic fields present in vanilla emerald, so now let's take a look { ... [SPECIES_MEWTHREE] = - { + { ... .isLegendary = TRUE, .allPerfectIVs = TRUE, @@ -320,7 +320,7 @@ Lastly, we add the cry to our species entry { ... [SPECIES_MEWTHREE] = - { + { ... .isLegendary = TRUE, .allPerfectIVs = TRUE, @@ -399,7 +399,7 @@ Now we can add the number and entry to our Mewthree: { ... [SPECIES_MEWTHREE] = - { + { ... .cryId = CRY_MEWTHREE, + .natDexNum = NATIONAL_DEX_MEWTHREE, @@ -420,7 +420,7 @@ Now we can add the number and entry to our Mewthree: ``` ![image](https://github.com/rh-hideout/pokeemerald-expansion/assets/2904965/3759dd4c-8da5-4b1c-9a50-b9e9d0815e7f) -The values `pokemonScale`, `pokemonOffset`, `trainerScale` and `trainerOffset` are used for the height comparison figure in the Pokédex. +The values `pokemonScale`, `pokemonOffset`, `trainerScale` and `trainerOffset` are used for the height comparison figure in the Pokédex. `height` and `weight` are specified in decimeters and hectograms respectively (which are meters and kilograms multiplied by 10, so 2.5 meters are 25 decimeters). @@ -454,7 +454,7 @@ Edit [src/data/pokemon/pokedex_orders.h](https://github.com/rh-hideout/pokeemera NATIONAL_DEX_DUGTRIO, ... }; - + const u16 gPokedexOrder_Height[] = { ... @@ -571,7 +571,7 @@ Now that we have all the external data ready, we just need to add it to `gSpecie { ... [SPECIES_MEWTHREE] = - { + { ... .pokemonScale = 256, .pokemonOffset = 0, @@ -651,7 +651,7 @@ We're almost there just a bit left! { ... [SPECIES_MEWTHREE] = - { + { ... .abilities = { ABILITY_INSOMNIA, ABILITY_NONE, ABILITY_NONE }, .bodyColor = BODY_COLOR_PURPLE, @@ -746,7 +746,7 @@ Again, we need to register the learnset in `gSpeciesInfo`: { ... [SPECIES_MEWTHREE] = - { + { ... PALETTES(Mewthree), ICON(Mewthree, 2), @@ -847,7 +847,7 @@ Once more, we need to register the learnset in `gSpeciesInfo`: { ... [SPECIES_MEWTHREE] = - { + { ... FOOTPRINT(Mewthree) .levelUpLearnset = sMewthreeLevelUpLearnset, @@ -870,7 +870,7 @@ Edit `gSpeciesInfo`: { ... [SPECIES_MEWTWO] = - { + { ... FOOTPRINT(Mewtwo) .isLegendary = TRUE, diff --git a/docs/how_to_new_pokemon_1_9_0.md b/docs/how_to_new_pokemon_1_9_0.md index 84742c3a76a2..e64f8e6e667f 100644 --- a/docs/how_to_new_pokemon_1_9_0.md +++ b/docs/how_to_new_pokemon_1_9_0.md @@ -176,7 +176,7 @@ Edit [src/data/pokemon/species_info.h](https://github.com/rh-hideout/pokeemerald }, + [SPECIES_MEWTHREE] = -+ { ++ { + .baseHP = 106, + .baseAttack = 150, + .baseDefense = 70, @@ -240,7 +240,7 @@ That's all the basic fields present in vanilla emerald, so now let's take a look { ... [SPECIES_MEWTHREE] = - { + { ... .isLegendary = TRUE, .allPerfectIVs = TRUE, @@ -315,7 +315,7 @@ Lastly, we add the cry to our species entry { ... [SPECIES_MEWTHREE] = - { + { ... .isLegendary = TRUE, .allPerfectIVs = TRUE, @@ -394,7 +394,7 @@ Now we can add the number and entry to our Mewthree: { ... [SPECIES_MEWTHREE] = - { + { ... .cryId = CRY_MEWTHREE, + .natDexNum = NATIONAL_DEX_MEWTHREE, @@ -415,7 +415,7 @@ Now we can add the number and entry to our Mewthree: ``` ![image](https://github.com/rh-hideout/pokeemerald-expansion/assets/2904965/3759dd4c-8da5-4b1c-9a50-b9e9d0815e7f) -The values `pokemonScale`, `pokemonOffset`, `trainerScale` and `trainerOffset` are used for the height comparison figure in the Pokédex. +The values `pokemonScale`, `pokemonOffset`, `trainerScale` and `trainerOffset` are used for the height comparison figure in the Pokédex. `height` and `weight` are specified in decimeters and hectograms respectively (which are meters and kilograms multiplied by 10, so 2.5 meters are 25 decimeters). @@ -449,7 +449,7 @@ Edit [src/data/pokemon/pokedex_orders.h](https://github.com/rh-hideout/pokeemera NATIONAL_DEX_DUGTRIO, ... }; - + const u16 gPokedexOrder_Height[] = { ... @@ -566,7 +566,7 @@ Now that we have all the external data ready, we just need to add it to `gSpecie { ... [SPECIES_MEWTHREE] = - { + { ... .pokemonScale = 256, .pokemonOffset = 0, @@ -646,7 +646,7 @@ We're almost there just a bit left! { ... [SPECIES_MEWTHREE] = - { + { ... .abilities = { ABILITY_INSOMNIA, ABILITY_NONE, ABILITY_NONE }, .bodyColor = BODY_COLOR_PURPLE, @@ -745,7 +745,7 @@ Again, we need to register the learnset in `gSpeciesInfo`: { ... [SPECIES_MEWTHREE] = - { + { ... PALETTES(Mewthree), ICON(Mewthree, 2), @@ -846,7 +846,7 @@ Once more, we need to register the learnset in `gSpeciesInfo`: { ... [SPECIES_MEWTHREE] = - { + { ... FOOTPRINT(Mewthree) .levelUpLearnset = sMewthreeLevelUpLearnset, @@ -869,7 +869,7 @@ Edit `gSpeciesInfo`: { ... [SPECIES_MEWTWO] = - { + { ... FOOTPRINT(Mewtwo) .isLegendary = TRUE, @@ -1097,7 +1097,7 @@ And finally, in `gSpeciesInfo`: { ... [SPECIES_MEWTHREE] = - { + { ... FOOTPRINT(Mewthree) + OVERWORLD( diff --git a/docs/how_to_testing_system.md b/docs/how_to_testing_system.md index 793465114c1a..1f25f9847ebc 100644 --- a/docs/how_to_testing_system.md +++ b/docs/how_to_testing_system.md @@ -1,11 +1,11 @@ # How to use the Testing System ## Running Tests -To run all the tests use: -`make check -j` -To run specific tests, e.g. Spikes ones, use: -`make check TESTS='Spikes'` -To build a ROM (pokemerald-test.elf) that can be opened in mgba to view specific tests, e.g. Spikes ones, use: +To run all the tests use: +`make check -j` +To run specific tests, e.g. Spikes ones, use: +`make check TESTS='Spikes'` +To build a ROM (pokemerald-test.elf) that can be opened in mgba to view specific tests, e.g. Spikes ones, use: `make pokeemerald-test.elf TESTS='Spikes'` ## How to Write Tests @@ -48,21 +48,21 @@ SINGLE_BATTLE_TEST("Stun Spore inflicts paralysis") STATUS_ICON(opponent, paralysis: TRUE); // 4. } } -``` +``` -The `ASSUMPTIONS` block documents that Stun Spore has `EFFECT_PARALYZE`. -If Stun Spore did not have that effect it would cause the tests in the file to be skipped. We write our tests like this so that hackers can change the effects of moves without causing tests to fail. +The `ASSUMPTIONS` block documents that Stun Spore has `EFFECT_PARALYZE`. +If Stun Spore did not have that effect it would cause the tests in the file to be skipped. We write our tests like this so that hackers can change the effects of moves without causing tests to fail. -`SINGLE_BATTLE_TEST` defines the name of the test. Related tests should start with the same prefix, e.g. Stun Spore tests should start with "Stun Spore", this allows just the Stun Spore-related tests to be run with: -`make check TESTS='Stun Spore'` +`SINGLE_BATTLE_TEST` defines the name of the test. Related tests should start with the same prefix, e.g. Stun Spore tests should start with "Stun Spore", this allows just the Stun Spore-related tests to be run with: +`make check TESTS='Stun Spore'` -`GIVEN` initializes the parties, `PLAYER` and `OPPONENT` add a Pokémon to their respective parties. They can both accept a block which further customizes the Pokémon's stats, moves, item, ability, etc. +`GIVEN` initializes the parties, `PLAYER` and `OPPONENT` add a Pokémon to their respective parties. They can both accept a block which further customizes the Pokémon's stats, moves, item, ability, etc. -`WHEN` describes the turns, and `TURN` describes the choices made in a single turn. `MOVE` causes the player to use Stun Spore and adds the move to the Pokémon's moveset if an explicit Moves was not specified. -Pokémon that are not mentioned in a `TURN` use Celebrate. -The test runner rigs the RNG so that unless otherwise specified, moves always hit, never critical hit, always activate their secondary effects, and always roll the same damage modifier. +`WHEN` describes the turns, and `TURN` describes the choices made in a single turn. `MOVE` causes the player to use Stun Spore and adds the move to the Pokémon's moveset if an explicit Moves was not specified. +Pokémon that are not mentioned in a `TURN` use Celebrate. +The test runner rigs the RNG so that unless otherwise specified, moves always hit, never critical hit, always activate their secondary effects, and always roll the same damage modifier. -`SCENE` describes the player-visible output of the battle. In this case `ANIMATION` checks that the Stun Spore animation played, `MESSAGE` checks the paralysis message was shown, and `STATUS_ICON` checks that the opponent's HP bar shows a PRZ icon. +`SCENE` describes the player-visible output of the battle. In this case `ANIMATION` checks that the Stun Spore animation played, `MESSAGE` checks the paralysis message was shown, and `STATUS_ICON` checks that the opponent's HP bar shows a PRZ icon. ### Example 2 As a second example, to manually test that Stun Spore does not effect Grass-types you might: @@ -90,8 +90,8 @@ SINGLE_BATTLE_TEST("Stun Spore does not affect Grass-types") } } ``` -The `ASSUME` commands are documenting the reasons why Stun Spore does not affect Oddish, namely that Stun Spore is a powder move, and Oddish is a Grass-type. These `ASSUME` statements function similarly to the ones in `ASSUMPTIONS` but apply only to the one test. -NOT inverts the meaning of a `SCENE` check, so applying it to `ANIMATION` requires that the Stun Spore animation does not play. `MESSAGE` checks that the message was shown. +The `ASSUME` commands are documenting the reasons why Stun Spore does not affect Oddish, namely that Stun Spore is a powder move, and Oddish is a Grass-type. These `ASSUME` statements function similarly to the ones in `ASSUMPTIONS` but apply only to the one test. +NOT inverts the meaning of a `SCENE` check, so applying it to `ANIMATION` requires that the Stun Spore animation does not play. `MESSAGE` checks that the message was shown. The checks in `SCENE` are ordered, so together this says "The doesn't affect message is shown, and the Stun Spore animation does not play at any time before that". Normally you would only test one or the other, or even better, just `NOT STATUS_ICON(opponent, paralysis: TRUE);` to say that Oddish was not paralyzed without specifying the exact outputs which led to that. ### Example 3 @@ -131,9 +131,9 @@ SINGLE_BATTLE_TEST("Meditate raises Attack", s16 damage) } ``` -`PARAMETRIZE` causes a test to run multiple times, once per `PARAMETRIZE` block (e.g. once with `raiseAttack = FALSE` and once with `raiseAttack = TRUE`). -The `HP_BAR` command's `captureDamage` causes the change in HP to be stored in a variable, and the variable chosen is `results[i].damage`. -`results[i]` contains all the variables defined at the end of `SINGLE_BATTLE_TEST`, `i` is the current `PARAMETRIZE` index. +`PARAMETRIZE` causes a test to run multiple times, once per `PARAMETRIZE` block (e.g. once with `raiseAttack = FALSE` and once with `raiseAttack = TRUE`). +The `HP_BAR` command's `captureDamage` causes the change in HP to be stored in a variable, and the variable chosen is `results[i].damage`. +`results[i]` contains all the variables defined at the end of `SINGLE_BATTLE_TEST`, `i` is the current `PARAMETRIZE` index. `FINALLY` runs after the last parameter has finished, and uses `EXPECT_MUL_EQ` to check that the second battle deals 1.5× the damage of the first battle (with a small tolerance to account for rounding). You might notice that all the tests check the outputs the player could see rather than the internal battle state. e.g. the Meditate test could have used `gBattleMons[B_POSITION_OPPONENT_LEFT].hp` instead of using `HP_BAR` to capture the damage. This is a deliberate choice, by checking what the player can observe the tests are more robust to refactoring, e.g. if `gBattleMons` got moved into `gBattleStruct` then any test that used it would need to be updated. @@ -144,8 +144,8 @@ The overworld is not available, so it is only possible to test commands which do ## REFERENCE ### `ASSUME` -`ASSUME(cond)` -Causes the test to be skipped if `cond` is false. Used to document any prerequisites of the test, e.g. to test Burn reducing the Attack of a Pokémon we can observe the damage of a physical attack with and without the burn. To document that this test assumes the attack is physical we can use: +`ASSUME(cond)` +Causes the test to be skipped if `cond` is false. Used to document any prerequisites of the test, e.g. to test Burn reducing the Attack of a Pokémon we can observe the damage of a physical attack with and without the burn. To document that this test assumes the attack is physical we can use: `ASSUME(gMovesInfo[MOVE_WHATEVER].category == DAMAGE_CATEGORY_PHYSICAL);` ### `ASSUMPTIONS` @@ -164,16 +164,16 @@ ASSUMPTIONS ``` ### `SINGLE_BATTLE_TEST` -`SINGLE_BATTLE_TEST(name, results...)` and `DOUBLE_BATTLE_TEST(name, results...)` -Define single- and double- battles. The names should start with the name of the mechanic being tested so that it is easier to run all the related tests. `results` contains variable declarations to be placed into the `results` array which is available in tests using `PARAMETRIZE` commands. +`SINGLE_BATTLE_TEST(name, results...)` and `DOUBLE_BATTLE_TEST(name, results...)` +Define single- and double- battles. The names should start with the name of the mechanic being tested so that it is easier to run all the related tests. `results` contains variable declarations to be placed into the `results` array which is available in tests using `PARAMETRIZE` commands. The main differences for doubles are: - Move targets sometimes need to be explicit. - Instead of `player` and `opponent` there is `playerLeft`, `playerRight`, `opponentLeft`, and `opponentRight`. ### `AI_SINGLE_BATTLE_TEST` -`AI_SINGLE_BATTLE_TEST(name, results...)` and `AI_DOUBLE_BATTLE_TEST(name, results...)` +`AI_SINGLE_BATTLE_TEST(name, results...)` and `AI_DOUBLE_BATTLE_TEST(name, results...)` Define battles where opponent mons are controlled by AI, the same that runs -when battling regular Trainers. The flags for AI should be specified by the `AI_FLAGS` command. +when battling regular Trainers. The flags for AI should be specified by the `AI_FLAGS` command. The rules remain the same as with the `SINGLE` and `DOUBLE` battle tests with some differences: - opponent's action is specified by the `EXPECT_MOVE` / `EXPECT_SEND_OUT` / `EXPECT_SWITCH` commands - we don't control what opponent actually does, instead we make sure the opponent does what we expect it to do @@ -182,7 +182,7 @@ The rules remain the same as with the `SINGLE` and `DOUBLE` battle tests with so ### `KNOWN_FAILING` `KNOWN_FAILING;` -Marks a test as not passing due to a bug. If there is an issue number associated with the bug it should be included in a comment. If the test passes the developer will be notified to remove `KNOWN_FAILING`. +Marks a test as not passing due to a bug. If there is an issue number associated with the bug it should be included in a comment. If the test passes the developer will be notified to remove `KNOWN_FAILING`. For example: ``` SINGLE_BATTLE_TEST("Jump Kick has no recoil if no target") @@ -192,7 +192,7 @@ SINGLE_BATTLE_TEST("Jump Kick has no recoil if no target") } ``` ### `PARAMETRIZE` -`PARAMETERIZE { parameter; }` +`PARAMETERIZE { parameter; }` Runs a test multiple times. `i` will be set to which parameter is running, and `results` will contain an entry for each parameter, e.g.: ``` SINGLE_BATTLE_TEST("Blaze boosts Fire-type moves in a pinch", s16 damage) @@ -246,7 +246,7 @@ Contains the initial state of the parties before the battle. ## `RNGSeed` `RNGSeed(seed)` -Explicitly sets the RNG seed. Try to avoid using this because it is a very fragile tool. +Explicitly sets the RNG seed. Try to avoid using this because it is a very fragile tool. Example: ``` GIVEN { @@ -256,10 +256,10 @@ GIVEN { ``` ### `FLAG_SET` -`FLAG_SET(flagId)` -Sets the specified flag. Can currently only set one flag at a time. -Cleared between parameters and at the end of the test. -Example: +`FLAG_SET(flagId)` +Sets the specified flag. Can currently only set one flag at a time. +Cleared between parameters and at the end of the test. +Example: ``` GIVEN { FLAG_SET(FLAG_SYS_EXAMPLE_FLAG); @@ -268,8 +268,8 @@ GIVEN { ``` ### `PLAYER` and `OPPONENT` -`PLAYER(species)` and `OPPONENT(species` -Adds the species to the player's or opponent's party respectively. +`PLAYER(species)` and `OPPONENT(species` +Adds the species to the player's or opponent's party respectively. The Pokémon can be further customized with the following functions: - `Gender(MON_MALE | MON_FEMALE)` - `Nature(nature)` @@ -280,14 +280,14 @@ The Pokémon can be further customized with the following functions: - `Moves(moves...)` - `Friendship(friendship)` - `Status1(status1)` -For example to create a level 42 Wobbuffet that is poisoned: -`PLAYER(SPECIES_WOBBUFFET) { Level(42); Status1(STATUS1_POISON); }` -**Note if Speed is specified for any Pokémon then it must be specified for all Pokémon.** +For example to create a level 42 Wobbuffet that is poisoned: +`PLAYER(SPECIES_WOBBUFFET) { Level(42); Status1(STATUS1_POISON); }` +**Note if Speed is specified for any Pokémon then it must be specified for all Pokémon.** **Note if Moves is specified then MOVE will not automatically add moves to the moveset.** ### `AI_FLAGS` -`AI_FLAGS(flags)` -Specifies which AI flags are run during the test. Has use only for AI tests. +`AI_FLAGS(flags)` +Specifies which AI flags are run during the test. Has use only for AI tests. The most common combination is `AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT)` which is the general 'smart' AI. ### `WHEN` @@ -300,8 +300,8 @@ The most common combination is `AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_ Contains the choices that battlers make during the battle. ### `TURN` -`TURN { ... }` -Groups the choices made by the battlers on a single turn. If Speeds have not been explicitly specified then the order of the `MOVE` commands in the `TURN` will be used to infer the Speeds of the Pokémon, e.g.: +`TURN { ... }` +Groups the choices made by the battlers on a single turn. If Speeds have not been explicitly specified then the order of the `MOVE` commands in the `TURN` will be used to infer the Speeds of the Pokémon, e.g.: ``` // player's speed will be greater than opponent's speed. TURN { MOVE(player, MOVE_SPLASH); MOVE(opponent, MOVE_SPLASH); } @@ -311,7 +311,7 @@ Groups the choices made by the battlers on a single turn. If Speeds have not bee The inference process is naive, if your test contains anything that modifies the speed of a battler you should specify them explicitly. ### `MOVE` -`MOVE(battler, move | moveSlot:, [megaEvolve:], [hit:], [criticalHit:], [target:], [allowed:], [WITH_RNG(tag, value])` +`MOVE(battler, move | moveSlot:, [megaEvolve:], [hit:], [criticalHit:], [target:], [allowed:], [WITH_RNG(tag, value])` Used when the battler chooses Fight. Either the move ID (e.g. `MOVE_TACKLE` or move slot must be specified. - `megaEvolve: TRUE` causes the battler to Mega Evolve if able - `hit: FALSE` causes the move to miss @@ -325,35 +325,35 @@ Used when the battler chooses Fight. Either the move ID (e.g. `MOVE_TACKLE` or m If the battler does not have an explicit Moves specified the moveset will be populated based on the `MOVE`s it uses. ### `FORCED_MOVE` -`FORCED_MOVE(battler)` +`FORCED_MOVE(battler)` Used when the battler chooses Fight and then their move is chosen for them, e.g. when affected by Encore. ``` FORCED_MOVE(player); ``` ### `SWITCH` -`SWITCH(battler, partyIndex)` +`SWITCH(battler, partyIndex)` Used when the battler chooses Switch. ``` SWITCH(player, 1); ``` ### `SKIP_TURN` -`SKIP_TURN(battler)` +`SKIP_TURN(battler)` Used when the battler cannot choose an action, e.g. when locked into Thrash. ``` SKIP_TURN(player); ``` ### `SEND_OUT` -`SEND_OUT(battler, partyIndex)` +`SEND_OUT(battler, partyIndex)` Used when the battler chooses to switch to another Pokémon but not via Switch, e.g. after fainting or due to a U-turn. ``` SEND_OUT(player, 1); ``` ### `USE_ITEM` -`USE_ITEM(battler, itemId, [partyIndex:], [move:])` +`USE_ITEM(battler, itemId, [partyIndex:], [move:])` Used when the battler chooses to use an item from the Bag. The item ID (e.g. ITEM_POTION) must be specified, and party index and move slot if applicable, e.g: ``` USE_ITEM(player, ITEM_X_ATTACK); @@ -378,15 +378,15 @@ Contains an abridged description of the UI during the `THEN`. The order of the d ``` ### `ABILITY_POPUP` -`ABILITY_POPUP(battler, [ability])` -Causes the test to fail if the battler's ability pop-up is not shown. +`ABILITY_POPUP(battler, [ability])` +Causes the test to fail if the battler's ability pop-up is not shown. If specified, ability is the ability shown in the pop-up. ``` ABILITY_POPUP(opponent, ABILITY_MOLD_BREAKER); ``` ### `ANIMATION` -`ANIMATION(type, animId, [battler], [target:])` +`ANIMATION(type, animId, [battler], [target:])` Causes the test to fail if the animation does not play. A common use of this command is to check if a move was successful, e.g.: ``` ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); @@ -394,7 +394,7 @@ Causes the test to fail if the animation does not play. A common use of this com `target` can only be specified for `ANIM_TYPE_MOVE`. ### `EXPERIENCE_BAR` -`EXPERIENCE_BAR(battler, [exp: | captureGainedExp:])` +`EXPERIENCE_BAR(battler, [exp: | captureGainedExp:])` If `exp:` is used, causes the test to fail if that amount of experience is not gained, e.g.: ``` EXPERIENCE_BAR(player, exp: 0); @@ -404,11 +404,11 @@ If `captureGainedExp:` is used, causes the test to fail if the Experience bar do u32 exp; EXPERIENCE_BAR(player, captureGainedExp: &exp); ``` -If none of the above are used, causes the test to fail if the Exp does not change at all. +If none of the above are used, causes the test to fail if the Exp does not change at all. **Please note that due to nature of tests, this command is only usable in `WILD_BATTLE_TEST` and will fail elsewhere.** ### `HP_BAR` -`HP_BAR(battler, [damage: | hp: | captureDamage: | captureHP:])` +`HP_BAR(battler, [damage: | hp: | captureDamage: | captureHP:])` If `hp:` or `damage:` are used, causes the test to fail if that amount of damage is not dealt, e.g.: ``` HP_BAR(player, hp: 0); @@ -422,9 +422,9 @@ If `captureDamage:` or `captureHP:` are used, causes the test to fail if the HP If none of the above are used, causes the test to fail if the HP does not change at all. ### MESSAGE -`MESSAGE(pattern)` -Causes the test to fail if the message in pattern is not displayed. -Spaces in pattern match newlines (\n, \l, and \p) in the message. +`MESSAGE(pattern)` +Causes the test to fail if the message in pattern is not displayed. +Spaces in pattern match newlines (\n, \l, and \p) in the message. Often used to check that a battler took its turn but it failed, e.g.: ``` MESSAGE("Wobbuffet used Dream Eater!"); @@ -432,7 +432,7 @@ Often used to check that a battler took its turn but it failed, e.g.: ``` ### `STATUS_ICON` -`STATUS_ICON(battler, status1 | none: | sleep: | poison: | burn: | freeze: | paralysis:, badPoison:)` +`STATUS_ICON(battler, status1 | none: | sleep: | poison: | burn: | freeze: | paralysis:, badPoison:)` Causes the test to fail if the battler's status is not changed to the specified status. ``` STATUS_ICON(player, badPoison: TRUE); @@ -447,7 +447,7 @@ If the expected status icon is parametrized the corresponding `STATUS1` constant ``` ### `NOT` -`NOT sceneCommand` +`NOT sceneCommand` Causes the test to fail if the `SCENE` command succeeds before the following command succeeds. ``` // Our Wobbuffet does not Celebrate before the foe's. @@ -519,27 +519,27 @@ Contains code to run after the battle has finished. If the test is using `PARAME Contains checks to run after all `PARAMETERIZE` commands have run. Prefer to write your checks in `THEN` where possible, because a failure in `THEN` will be tagged with which parameter it corresponds to. ### `EXPECT` -`EXPECT(cond)` +`EXPECT(cond)` Causes the test to fail if `cond` is false. ### `EXPECT_XX` -`EXPECT_EQ(a, b)` -`a == b` +`EXPECT_EQ(a, b)` +`a == b` -`EXPECT_NE(a, b)` -`a != b` +`EXPECT_NE(a, b)` +`a != b` -`EXPECT_LT(a, b)` -`a < b` +`EXPECT_LT(a, b)` +`a < b` -`EXPECT_LE(a, b)` -`a <= b` +`EXPECT_LE(a, b)` +`a <= b` -`EXPECT_GT(a, b)` -`a > b` +`EXPECT_GT(a, b)` +`a > b` -`EXPECT_GE(a, b)` -`a >= b` +`EXPECT_GE(a, b)` +`a >= b` Causes the test to fail if a and b compare incorrectly, e.g. ``` @@ -547,7 +547,7 @@ Causes the test to fail if a and b compare incorrectly, e.g. ``` ### `EXPECT_MUL_EQ` -`EXPECT_MUL_EQ(a, m, b)` +`EXPECT_MUL_EQ(a, m, b)` Causes the test to fail if `a*m != b` (within a threshold), e.g. ``` // Expect results[0].damage * 1.5 == results[1].damage. @@ -557,7 +557,7 @@ Causes the test to fail if a and b compare incorrectly, e.g. ## Overworld Command Reference ### `OVERWORLD_SCRIPT` -`OVERWORLD_SCRIPT(instructions...)` +`OVERWORLD_SCRIPT(instructions...)` Returns a pointer to a compiled overworld script. Cannot be used to initialize global `const` data, although the pointer **IS** to `const` data. Note that each script command must be followed by a ;, e.g.: ``` @@ -568,7 +568,7 @@ const u8 *myScript = OVERWORLD_SCRIPT( ``` ### `RUN_OVERWORLD_SCRIPT` -`RUN_OVERWORLD_SCRIPT(instructions...)` +`RUN_OVERWORLD_SCRIPT(instructions...)` Runs an overworld script in the immediate script context, which means that commands like `waitstate` are not supported. ``` RUN_OVERWORLD_SCRIPT( diff --git a/docs/how_to_trainer_class.md b/docs/how_to_trainer_class.md index a47cbe19c561..3ce0217c2808 100644 --- a/docs/how_to_trainer_class.md +++ b/docs/how_to_trainer_class.md @@ -12,13 +12,13 @@ * [Usage](#usage) ## Quick Summary -(Page contains out of date information, [new instructions for Sprites here](https://github.com/rh-hideout/pokeemerald-expansion/pull/3597).) +(Page contains out of date information, [new instructions for Sprites here](https://github.com/rh-hideout/pokeemerald-expansion/pull/3597).) If you've done this before and just need a quick lookup, here's what files you need: 1. GFX into [graphics/trainers/front_pics](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/graphics/trainers/front_pics) 2. Palette into [graphics/trainers/palettes](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/graphics/trainers/palettes) 3. Register sprites to [include/graphics.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/include/graphics.h) 4. Point game to where graphic files are found: [src/data/graphics/trainers](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/graphics/trainers.h) -5. Add animation to: [src/data/trainer_graphics/front_pic_anims.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/trainer_graphics/front_pic_anims.h) +5. Add animation to: [src/data/trainer_graphics/front_pic_anims.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/trainer_graphics/front_pic_anims.h) 6. Add the trainer to all three structs in: [src/data/trainer_graphics/front_pic_table.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/trainer_graphics/front_pic_table.h) 7. Add trainer to [include/constants/trainers.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/include/constants/trainers.h) diff --git a/include/test/battle.h b/include/test/battle.h index a44e813f822e..865afe8e0b78 100644 --- a/include/test/battle.h +++ b/include/test/battle.h @@ -328,7 +328,7 @@ * * 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. gimmick: GIMMICK_MEGA causes the battler to Mega + * 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 diff --git a/migration_scripts/1.8/item_ball_refactor.py b/migration_scripts/1.8/item_ball_refactor.py index f121978ec85c..83d6f9f167cb 100755 --- a/migration_scripts/1.8/item_ball_refactor.py +++ b/migration_scripts/1.8/item_ball_refactor.py @@ -81,5 +81,5 @@ raw = re.sub("script %s[ \n]*\{[ \n]*finditem\((.*)\)[ \n]*\}[ \n]*" % unused, "", raw) with open(file, "w") as f2: f2.write(raw) - + print("Done!") diff --git a/migration_scripts/1.9/battle_anim_moves_refactor.py b/migration_scripts/1.9/battle_anim_moves_refactor.py index 116d3964c0e2..a7adac98fffe 100644 --- a/migration_scripts/1.9/battle_anim_moves_refactor.py +++ b/migration_scripts/1.9/battle_anim_moves_refactor.py @@ -37,7 +37,7 @@ def IsCommaMissing(line: str): comment_split = line.split('//') if move and IsCommaMissing(comment_split[0]): line = comment_split[0].removesuffix('\n') + ',' + line[len(comment_split[0]):-1] + '\n' - + moves_info_lines.append(line) diff --git a/spritesheet_rules.mk b/spritesheet_rules.mk index 03e6b8a3b104..c10c9081f4b0 100644 --- a/spritesheet_rules.mk +++ b/spritesheet_rules.mk @@ -731,7 +731,7 @@ $(FLDEFFGFXDIR)/secret_power_tree.4bpp: %.4bpp: %.png $(FLDEFFGFXDIR)/record_mix_lights.4bpp: %.4bpp: %.png $(GFX) $< $@ -mwidth 4 -mheight 1 - + $(POKEMONGFXDIR)/question_mark/overworld.4bpp: %.4bpp: %.png $(GFX) $< $@ -mwidth 4 -mheight 4 diff --git a/src/battle_anim.c b/src/battle_anim.c index 71813ddf46bf..df597560ec15 100644 --- a/src/battle_anim.c +++ b/src/battle_anim.c @@ -798,7 +798,7 @@ static void Cmd_end(void) // Debugging - ensure no hanging mon bg tasks if (FuncIsActiveTask(Task_UpdateMonBg)) DebugPrintf("Move %d animation still has Task_UpdateMonBg active at the end!", gAnimMoveIndex); - + m4aMPlayVolumeControl(&gMPlayInfo_BGM, TRACKS_ALL, 256); if (!IsContest()) { diff --git a/src/battle_anim_dragon.c b/src/battle_anim_dragon.c index 4191c425855e..862c0ad06908 100644 --- a/src/battle_anim_dragon.c +++ b/src/battle_anim_dragon.c @@ -42,35 +42,35 @@ const struct SpriteTemplate gOutrageFlameSpriteTemplate = .callback = AnimOutrageFlame, }; -static const union AnimCmd sAnim_DreepyMissileOpponent_0[] = +static const union AnimCmd sAnim_DreepyMissileOpponent_0[] = { ANIMCMD_FRAME(0, 0, .hFlip = TRUE), ANIMCMD_END, }; -const union AnimCmd *const gAnims_DreepyMissileOpponent[] = +const union AnimCmd *const gAnims_DreepyMissileOpponent[] = { sAnim_DreepyMissileOpponent_0, }; -static const union AnimCmd sAnim_DreepyMissilePlayer_0[] = +static const union AnimCmd sAnim_DreepyMissilePlayer_0[] = { ANIMCMD_FRAME(0, 0), ANIMCMD_END, }; -const union AnimCmd *const gAnims_DreepyMissilePlayer[] = +const union AnimCmd *const gAnims_DreepyMissilePlayer[] = { sAnim_DreepyMissilePlayer_0, }; -static const union AnimCmd sAnim_DreepyMissileNotDrag_0[] = +static const union AnimCmd sAnim_DreepyMissileNotDrag_0[] = { ANIMCMD_FRAME(0, 0, .hFlip = TRUE, .vFlip = TRUE), ANIMCMD_END, }; -const union AnimCmd *const gAnims_DreepyMissileOpponentNotDrag[] = +const union AnimCmd *const gAnims_DreepyMissileOpponentNotDrag[] = { sAnim_DreepyMissileNotDrag_0, }; diff --git a/src/battle_anim_effects_1.c b/src/battle_anim_effects_1.c index d8283fc7dccf..b29d9b2b94ee 100644 --- a/src/battle_anim_effects_1.c +++ b/src/battle_anim_effects_1.c @@ -6578,11 +6578,11 @@ void PrepareDoubleTeamAnim(u32 taskId, u32 animBattler, bool32 forAllySwitch) gSprites[spriteId].sBattlerFlank = (animBattler != ANIM_ATTACKER); else gSprites[spriteId].sBattlerFlank = (animBattler == ANIM_ATTACKER); - + // correct direction on opponent side if (GetBattlerSide(gBattleAnimAttacker) == B_SIDE_OPPONENT) gSprites[spriteId].sBattlerFlank ^= 1; - + gSprites[spriteId].callback = AnimDoubleTeam; task->tBlendSpritesCount++; } @@ -6614,7 +6614,7 @@ static void ReloadBattlerSprites(u32 battler, struct Pokemon *party) UpdateHealthboxAttribute(gHealthboxSpriteIds[battler], mon, HEALTHBOX_ALL); // 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_anim_flying.c b/src/battle_anim_flying.c index f58aa97ee282..6afe1e9f369a 100644 --- a/src/battle_anim_flying.c +++ b/src/battle_anim_flying.c @@ -33,7 +33,7 @@ static void AnimSkyAttackBird_Step(struct Sprite *); static void AnimTask_AnimateGustTornadoPalette_Step(u8); static void AnimTask_LoadWindstormBackground_Step(u8 taskId); -const struct SpriteTemplate gEllipticalGustCenteredSpriteTemplate = +const struct SpriteTemplate gEllipticalGustCenteredSpriteTemplate = { .tileTag = ANIM_TAG_GUST, .paletteTag = ANIM_TAG_GUST, diff --git a/src/battle_anim_normal.c b/src/battle_anim_normal.c index 6333cc8183c9..759c410946ce 100644 --- a/src/battle_anim_normal.c +++ b/src/battle_anim_normal.c @@ -414,7 +414,7 @@ u32 UnpackSelectedBattlePalettes(s16 selector) bool8 anim1 = (selector >> 5) & 1; bool8 anim2 = (selector >> 6) & 1; u32 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gAnimMoveIndex); - + switch (moveTarget) { case MOVE_TARGET_BOTH: @@ -431,7 +431,7 @@ u32 UnpackSelectedBattlePalettes(s16 selector) } break; } - + return GetBattlePalettesMask(battleBackground, attacker, target, attackerPartner, targetPartner, anim1, anim2); } diff --git a/src/battle_controllers.c b/src/battle_controllers.c index fae42939d0f8..d6b2fbdc3a95 100644 --- a/src/battle_controllers.c +++ b/src/battle_controllers.c @@ -610,7 +610,7 @@ static void InitLinkBtlControllers(void) bool32 IsValidForBattle(struct Pokemon *mon) { u32 species = GetMonData(mon, MON_DATA_SPECIES_OR_EGG); - return (species != SPECIES_NONE + return (species != SPECIES_NONE && species != SPECIES_EGG && GetMonData(mon, MON_DATA_HP) != 0 && GetMonData(mon, MON_DATA_IS_EGG) == FALSE); diff --git a/src/battle_dome.c b/src/battle_dome.c index 67b9a1e2934a..06acf6e2f2e1 100644 --- a/src/battle_dome.c +++ b/src/battle_dome.c @@ -2136,7 +2136,7 @@ static void CalcDomeMonStats(const struct TrainerMon *fmon, int level, u8 ivs, i { int evs[NUM_STATS]; int i; - + for (i = 0; i < NUM_STATS; i++) { if (fmon->ev != NULL) @@ -2144,7 +2144,7 @@ static void CalcDomeMonStats(const struct TrainerMon *fmon, int level, u8 ivs, i else evs[i] = 0; } - + if (fmon->species == SPECIES_SHEDINJA) { stats[STAT_HP] = 1; @@ -2199,7 +2199,7 @@ static void CreateDomeOpponentMon(u8 monPartyId, u16 tournamentTrainerId, u8 tou u8 fixedIv = GetDomeTrainerMonIvs(tournamentTrainerId); // BUG: Using the wrong ID. As a result, all Pokémon have ivs of 3. #endif u8 level = SetFacilityPtrsGetLevel(); - + CreateFacilityMon(&gFacilityTrainerMons[DOME_MONS[tournamentTrainerId][tournamentMonId]], level, fixedIv, otId, 0, &gEnemyParty[monPartyId]); } @@ -4446,7 +4446,7 @@ static void DisplayTrainerInfoOnCard(u8 flags, u8 trainerTourneyId) else allocatedArray[j] = 0; } - + allocatedArray[NUM_STATS] += allocatedArray[STAT_HP]; for (j = 0; j < NUM_NATURE_STATS; j++) { diff --git a/src/battle_factory.c b/src/battle_factory.c index 7d7ec7b3af5d..b5645368c2de 100644 --- a/src/battle_factory.c +++ b/src/battle_factory.c @@ -406,7 +406,7 @@ static void SetPlayerAndOpponentParties(void) u8 monLevel; u16 monId; u8 ivs; - + if (gSaveBlock2Ptr->frontier.lvlMode == FRONTIER_LVL_TENT) { gFacilityTrainerMons = gSlateportBattleTentMons; @@ -428,7 +428,7 @@ static void SetPlayerAndOpponentParties(void) { monId = gSaveBlock2Ptr->frontier.rentalMons[i].monId; ivs = gSaveBlock2Ptr->frontier.rentalMons[i].ivs; - + CreateFacilityMon(&gFacilityTrainerMons[monId], monLevel, ivs, OT_ID_PLAYER_ID, FLAG_FRONTIER_MON_FACTORY, &gPlayerParty[i]); SetMonData(&gPlayerParty[i], MON_DATA_PERSONALITY, &gSaveBlock2Ptr->frontier.rentalMons[i].personality); diff --git a/src/battle_factory_screen.c b/src/battle_factory_screen.c index 5aa019c29fd0..f76c6a2fc830 100644 --- a/src/battle_factory_screen.c +++ b/src/battle_factory_screen.c @@ -1761,7 +1761,7 @@ static void CreateFrontierFactorySelectableMons(u8 firstMonId) ivs = GetFactoryMonFixedIV(challengeNum + 1, FALSE); else ivs = GetFactoryMonFixedIV(challengeNum, FALSE); - + CreateFacilityMon(&gFacilityTrainerMons[monId], level, ivs, otId, FLAG_FRONTIER_MON_FACTORY, &sFactorySelectScreen->mons[i + firstMonId].monData); diff --git a/src/battle_setup.c b/src/battle_setup.c index 41f2b9b50232..54c4f99b9289 100644 --- a/src/battle_setup.c +++ b/src/battle_setup.c @@ -1940,7 +1940,7 @@ void IncrementRematchStepCounter(void) #if FREE_MATCH_CALL == FALSE if (!HasAtLeastFiveBadges()) return; - + if (IsVsSeekerEnabled()) return; diff --git a/src/clock.c b/src/clock.c index 7061f96b7f59..54c9422bbe47 100644 --- a/src/clock.c +++ b/src/clock.c @@ -84,7 +84,7 @@ static void FormChangeTimeUpdate() { struct Pokemon *mon = &gPlayerParty[i]; u16 targetSpecies = GetFormChangeTargetSpecies(mon, FORM_CHANGE_TIME_OF_DAY, 0); - + if (targetSpecies != SPECIES_NONE) { SetMonData(mon, MON_DATA_SPECIES, &targetSpecies); diff --git a/src/data/gimmicks.h b/src/data/gimmicks.h index 95a233e3fe57..5b06feddefb7 100644 --- a/src/data/gimmicks.h +++ b/src/data/gimmicks.h @@ -5,7 +5,7 @@ const struct GimmickInfo gGimmicksInfo[GIMMICKS_COUNT] = { [GIMMICK_NONE] = {0}, - [GIMMICK_MEGA] = + [GIMMICK_MEGA] = { .triggerSheet = &sSpriteSheet_MegaTrigger, .triggerPal = &sSpritePalette_MegaTrigger, @@ -15,7 +15,7 @@ const struct GimmickInfo gGimmicksInfo[GIMMICKS_COUNT] = .CanActivate = CanMegaEvolve, .ActivateGimmick = ActivateMegaEvolution, }, - [GIMMICK_Z_MOVE] = + [GIMMICK_Z_MOVE] = { .triggerSheet = &sSpriteSheet_ZMoveTrigger, .triggerPal = &sSpritePalette_ZMoveTrigger, @@ -23,7 +23,7 @@ const struct GimmickInfo gGimmicksInfo[GIMMICKS_COUNT] = .CanActivate = CanUseZMove, .ActivateGimmick = ActivateZMove, }, - [GIMMICK_ULTRA_BURST] = + [GIMMICK_ULTRA_BURST] = { .triggerSheet = &sSpriteSheet_BurstTrigger, .triggerPal = &sSpritePalette_BurstTrigger, @@ -31,7 +31,7 @@ const struct GimmickInfo gGimmicksInfo[GIMMICKS_COUNT] = .CanActivate = CanUltraBurst, .ActivateGimmick = ActivateUltraBurst, }, - [GIMMICK_DYNAMAX] = + [GIMMICK_DYNAMAX] = { .triggerSheet = &sSpriteSheet_DynamaxTrigger, .triggerPal = &sSpritePalette_DynamaxTrigger, diff --git a/src/data/object_events/object_event_graphics_info_pointers.h b/src/data/object_events/object_event_graphics_info_pointers.h index 7f487a5ae52b..60dc6997db68 100755 --- a/src/data/object_events/object_event_graphics_info_pointers.h +++ b/src/data/object_events/object_event_graphics_info_pointers.h @@ -239,7 +239,7 @@ extern const struct ObjectEventGraphicsInfo gObjectEventGraphicsInfo_HoOh; // Begin pokemon event objects extern const struct ObjectEventGraphicsInfo gObjectEventGraphicsInfo_PokeBall; extern const struct ObjectEventGraphicsInfo gObjectEventGraphicsInfo_Follower; - + extern const struct ObjectEventGraphicsInfo gObjectEventGraphicsInfo_Bard; extern const struct ObjectEventGraphicsInfo gObjectEventGraphicsInfo_Hipster; extern const struct ObjectEventGraphicsInfo gObjectEventGraphicsInfo_Trader; diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 5a6eb28fc3d1..1edc986c9b18 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -1493,7 +1493,7 @@ static s16 ReallocSpriteTiles(struct Sprite *sprite, u32 byteSize) { i = -1; } - + sprite->invisible = wasVisible; return i; } @@ -1510,7 +1510,7 @@ u16 LoadSheetGraphicsInfo(const struct ObjectEventGraphicsInfo *info, u16 uuid, bool32 oldInvisible; if (tag == TAG_NONE) tag = COMP_OW_TILE_TAG_BASE + uuid; - + if (sprite) { oldInvisible = sprite->invisible; @@ -1547,7 +1547,7 @@ u16 LoadSheetGraphicsInfo(const struct ObjectEventGraphicsInfo *info, u16 uuid, { FieldEffectFreeTilesIfUnused(oldTiles); } - + if (sprite) { sprite->sheetTileStart = tileStart; @@ -1566,7 +1566,7 @@ u16 LoadSheetGraphicsInfo(const struct ObjectEventGraphicsInfo *info, u16 uuid, { sprite->oam.tileNum = sprite->sheetTileStart; sprite->usingSheet = FALSE; - + } else if (sprite && !sprite->sheetTileStart && sprite->oam.size != info->oam->size) { @@ -1925,7 +1925,7 @@ static u8 LoadDynamicFollowerPalette(u16 species, u8 form, bool32 shiny) spritePalette.data = gSpeciesInfo[species].overworldShinyPalette; else spritePalette.data = gSpeciesInfo[species].overworldPalette; - + // Check if pal data must be decompressed if (IsLZ77Data(spritePalette.data, PLTT_SIZE_4BPP, PLTT_SIZE_4BPP)) { diff --git a/src/field_specials.c b/src/field_specials.c index d1b27e2df686..e00aa0380313 100644 --- a/src/field_specials.c +++ b/src/field_specials.c @@ -977,8 +977,8 @@ static bool32 IsBuildingPCTile(u32 tileId) static bool32 IsPlayerHousePCTile(u32 tileId) { - return gMapHeader.mapLayout->secondaryTileset == &gTileset_BrendansMaysHouse - && (tileId == METATILE_BrendansMaysHouse_BrendanPC_On + return gMapHeader.mapLayout->secondaryTileset == &gTileset_BrendansMaysHouse + && (tileId == METATILE_BrendansMaysHouse_BrendanPC_On || tileId == METATILE_BrendansMaysHouse_BrendanPC_Off || tileId == METATILE_BrendansMaysHouse_MayPC_On || tileId == METATILE_BrendansMaysHouse_MayPC_Off); diff --git a/src/load_save.c b/src/load_save.c index ee8a6bd04c22..a5f269353834 100644 --- a/src/load_save.c +++ b/src/load_save.c @@ -211,7 +211,7 @@ void SaveObjectEvents(void) gSaveBlock1Ptr->objectEvents[i].graphicsId = (graphicsId >> 8) | (graphicsId << 8); gSaveBlock1Ptr->objectEvents[i].spriteId = 127; // magic number // To avoid crash on vanilla, save follower as inactive - if (gObjectEvents[i].localId == OBJ_EVENT_ID_FOLLOWER) + if (gObjectEvents[i].localId == OBJ_EVENT_ID_FOLLOWER) gSaveBlock1Ptr->objectEvents[i].active = FALSE; } } diff --git a/src/map_name_popup.c b/src/map_name_popup.c index 67a0d6373f85..5cc1f3cfd5d9 100644 --- a/src/map_name_popup.c +++ b/src/map_name_popup.c @@ -550,7 +550,7 @@ static void ShowMapNamePopUpWindow(void) if (OW_POPUP_GENERATION == GEN_5) { AddTextPrinterParameterized(mapNamePopUpWindowId, FONT_SHORT, mapDisplayHeader, 8, 2, TEXT_SKIP_DRAW, NULL); - + if (OW_POPUP_BW_TIME_MODE != OW_POPUP_BW_TIME_NONE) { RtcCalcLocalTime(); @@ -622,7 +622,7 @@ static void LoadMapNamePopUpWindowBg(void) if (OW_POPUP_GENERATION == GEN_5) { popUpThemeId = sRegionMapSectionId_To_PopUpThemeIdMapping_BW[regionMapSectionId]; - switch (popUpThemeId) + switch (popUpThemeId) { // add additional gen 5-style pop-up themes as cases here case MAPPOPUP_THEME_BW_DEFAULT: diff --git a/src/script_menu.c b/src/script_menu.c index b78f1ef112c1..706c4ac71517 100644 --- a/src/script_menu.c +++ b/src/script_menu.c @@ -487,7 +487,7 @@ static void Task_HandleScrollingMultichoiceInput(u8 taskId) { bool32 done = FALSE; s32 input = ListMenu_ProcessInput(gTasks[taskId].data[0]); - + switch (input) { case LIST_HEADER: diff --git a/src/script_pokemon_util.c b/src/script_pokemon_util.c index e732dc7aa239..3d2c7d640cc3 100644 --- a/src/script_pokemon_util.c +++ b/src/script_pokemon_util.c @@ -466,7 +466,7 @@ u32 ScriptGiveMon(u16 species, u8 level, u16 item) #define PARSE_FLAG(n, default_) (flags & (1 << (n))) ? VarGet(ScriptReadHalfword(ctx)) : (default_) -/* Give or create a mon to either player or opponent +/* Give or create a mon to either player or opponent */ void ScrCmd_createmon(struct ScriptContext *ctx) { diff --git a/src/vs_seeker.c b/src/vs_seeker.c index 30dcb74ce0c4..771c01dc7235 100644 --- a/src/vs_seeker.c +++ b/src/vs_seeker.c @@ -581,7 +581,7 @@ bool32 IsVsSeekerEnabled(void) { if (I_VS_SEEKER_CHARGING == 0) return FALSE; - + return (CheckBagHasItem(ITEM_VS_SEEKER, 1)); } diff --git a/test/battle/ability/anger_point.c b/test/battle/ability/anger_point.c index 7cb283426a0f..270d66f28e82 100644 --- a/test/battle/ability/anger_point.c +++ b/test/battle/ability/anger_point.c @@ -37,7 +37,7 @@ SINGLE_BATTLE_TEST("Anger Point does not trigger when already at maximum Attack MESSAGE("Primeape cut its own HP and maximized ATTACK!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_FROST_BREATH, opponent); MESSAGE("A critical hit!"); - NONE_OF { + NONE_OF { ABILITY_POPUP(player, ABILITY_ANGER_POINT); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); MESSAGE("Primeape's Anger Point maxed its Attack!"); @@ -64,7 +64,7 @@ SINGLE_BATTLE_TEST("Anger Point does not trigger when a substitute takes the hit MESSAGE("Primeape made a SUBSTITUTE!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_FROST_BREATH, opponent); MESSAGE("A critical hit!"); - NONE_OF { + NONE_OF { ABILITY_POPUP(player, ABILITY_ANGER_POINT); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); MESSAGE("Primeape's Anger Point maxed its Attack!"); diff --git a/test/battle/ability/clear_body.c b/test/battle/ability/clear_body.c index 1379506a72d0..dc82be67b434 100644 --- a/test/battle/ability/clear_body.c +++ b/test/battle/ability/clear_body.c @@ -127,7 +127,7 @@ SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent s } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPERPOWER, opponent); NONE_OF { - ABILITY_POPUP(opponent, ability); + ABILITY_POPUP(opponent, ability); MESSAGE("Foe Solgaleo's Full Metal Body prevents stat loss!"); MESSAGE("Foe Torkoal's White Smoke prevents stat loss!"); MESSAGE("Foe Metang's Clear Body prevents stat loss!"); @@ -186,7 +186,7 @@ SINGLE_BATTLE_TEST("Mold Breaker, Teravolt, and Turboblaze ignore Clear Body and else{ ANIMATION(ANIM_TYPE_MOVE, move, player); NONE_OF { - ABILITY_POPUP(opponent, ability); + ABILITY_POPUP(opponent, ability); MESSAGE("Foe Solgaleo's Full Metal Body prevents stat loss!"); MESSAGE("Foe Torkoal's White Smoke prevents stat loss!"); MESSAGE("Foe Metang's Clear Body prevents stat loss!"); diff --git a/test/battle/ability/gulp_missile.c b/test/battle/ability/gulp_missile.c index 2ec9acc61b71..7ccf1063b569 100644 --- a/test/battle/ability/gulp_missile.c +++ b/test/battle/ability/gulp_missile.c @@ -116,7 +116,7 @@ SINGLE_BATTLE_TEST("(Gulp Missile) Cramorant in Gorging paralyzes the target if } SINGLE_BATTLE_TEST("(Gulp Missile) triggers even if the user is fainted by opposing mon") -{ +{ GIVEN { PLAYER(SPECIES_CRAMORANT) { HP(1); MaxHP(250); Ability(ABILITY_GULP_MISSILE); } PLAYER(SPECIES_WOBBUFFET); diff --git a/test/battle/ability/magic_guard.c b/test/battle/ability/magic_guard.c index 69e8bac9c7fc..5579652265eb 100644 --- a/test/battle/ability/magic_guard.c +++ b/test/battle/ability/magic_guard.c @@ -13,5 +13,5 @@ SINGLE_BATTLE_TEST("Magic Guard prevents recoil damage to the user") ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_EDGE, player); HP_BAR(opponent); NOT HP_BAR(player); - } + } } diff --git a/test/battle/ai/ai_flag_risky.c b/test/battle/ai/ai_flag_risky.c index e6156de5a08a..e1ceeb2161b3 100644 --- a/test/battle/ai/ai_flag_risky.c +++ b/test/battle/ai/ai_flag_risky.c @@ -13,7 +13,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: AI will blindly Mirror Coat against specia ASSUME(gSpeciesInfo[SPECIES_GROVYLE].baseSpAttack == 85); ASSUME(gSpeciesInfo[SPECIES_GROVYLE].baseAttack == 65); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiRiskyFlag); - PLAYER(SPECIES_GROVYLE) { Level(20); Moves(MOVE_ENERGY_BALL); } + PLAYER(SPECIES_GROVYLE) { Level(20); Moves(MOVE_ENERGY_BALL); } OPPONENT(SPECIES_CASTFORM) { Level(20); Moves(MOVE_TACKLE, MOVE_MIRROR_COAT); } } WHEN { TURN { MOVE(player, MOVE_ENERGY_BALL) ; EXPECT_MOVE(opponent, aiRiskyFlag ? MOVE_MIRROR_COAT : MOVE_TACKLE); } @@ -32,7 +32,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: AI will blindly Counter against physical a ASSUME(gSpeciesInfo[SPECIES_MARSHTOMP].baseAttack == 85); ASSUME(gSpeciesInfo[SPECIES_MARSHTOMP].baseSpAttack == 60); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiRiskyFlag); - PLAYER(SPECIES_MARSHTOMP) { Level(20); Moves(MOVE_WATERFALL); } + PLAYER(SPECIES_MARSHTOMP) { Level(20); Moves(MOVE_WATERFALL); } OPPONENT(SPECIES_CASTFORM) { Level(20); Moves(MOVE_TACKLE, MOVE_COUNTER); } } WHEN { TURN { MOVE(player, MOVE_WATERFALL) ; EXPECT_MOVE(opponent, aiRiskyFlag ? MOVE_COUNTER : MOVE_TACKLE); } @@ -49,7 +49,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: AI will prioritize Revenge if slower") GIVEN { ASSUME(gMovesInfo[MOVE_REVENGE].effect == EFFECT_REVENGE); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiRiskyFlag); - PLAYER(SPECIES_GROVYLE) { Level(20); Speed(4); Moves(MOVE_ENERGY_BALL); } + PLAYER(SPECIES_GROVYLE) { Level(20); Speed(4); Moves(MOVE_ENERGY_BALL); } OPPONENT(SPECIES_CASTFORM) { Level(19); Speed(3); Moves(MOVE_TACKLE, MOVE_REVENGE); } } WHEN { TURN { MOVE(player, MOVE_ENERGY_BALL) ; EXPECT_MOVE(opponent, aiRiskyFlag ? MOVE_REVENGE : MOVE_TACKLE); } @@ -83,7 +83,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_RISKY: AI prefers high damage moves at the expens GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | aiRiskyFlag); - PLAYER(SPECIES_PSYDUCK) { Level(5); Moves(MOVE_TACKLE); } + PLAYER(SPECIES_PSYDUCK) { Level(5); Moves(MOVE_TACKLE); } OPPONENT(SPECIES_CASTFORM) { Level(20); Moves(MOVE_THUNDER, MOVE_THUNDERBOLT); } } WHEN { TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, aiRiskyFlag ? MOVE_THUNDER : MOVE_THUNDERBOLT); } diff --git a/test/battle/gimmick/terastal.c b/test/battle/gimmick/terastal.c index 7a70ba0de014..1b50bc4bcc3c 100644 --- a/test/battle/gimmick/terastal.c +++ b/test/battle/gimmick/terastal.c @@ -646,7 +646,7 @@ SINGLE_BATTLE_TEST("(TERA) Protean cannot change the type of a Terastallized Pok PLAYER(SPECIES_GRENINJA) { Ability(ABILITY_PROTEAN); TeraType(TYPE_GRASS); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_BUBBLE, gimmick: GIMMICK_TERA); + TURN { MOVE(player, MOVE_BUBBLE, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_EMBER); } } SCENE { MESSAGE("Greninja used Bubble!"); diff --git a/test/battle/gimmick/zmove.c b/test/battle/gimmick/zmove.c index 1eb2c7ae7481..51c65161061d 100644 --- a/test/battle/gimmick/zmove.c +++ b/test/battle/gimmick/zmove.c @@ -10,7 +10,7 @@ SINGLE_BATTLE_TEST("(Z-MOVE) Z-Moves do not retain priority") PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(opponent, MOVE_TACKLE); + TURN { MOVE(opponent, MOVE_TACKLE); MOVE(player, MOVE_QUICK_ATTACK, gimmick: GIMMICK_Z_MOVE); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); @@ -597,7 +597,7 @@ SINGLE_BATTLE_TEST("(Z-MOVE) Searing Sunraze Smash ignores the target's abilitie ANIMATION(ANIM_TYPE_MOVE, MOVE_SEARING_SUNRAZE_SMASH, player); HP_BAR(opponent); MESSAGE("A critical hit!"); - } + } } SINGLE_BATTLE_TEST("(Z-MOVE) Z-Revelation Dance always transforms into Breakneck Blitz") diff --git a/test/battle/hold_effect/ability_shield.c b/test/battle/hold_effect/ability_shield.c index ee84b2c5e0d5..e10c3e08872a 100644 --- a/test/battle/hold_effect/ability_shield.c +++ b/test/battle/hold_effect/ability_shield.c @@ -69,7 +69,7 @@ SINGLE_BATTLE_TEST("Ability Shield protects against Mycelium Might") } WHEN { TURN { MOVE(opponent, MOVE_SPORE); MOVE(player, MOVE_SPORE); } } SCENE { - + if (item == ITEM_ABILITY_SHIELD) { NONE_OF { ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponent); diff --git a/test/battle/move_effect/heal_bell.c b/test/battle/move_effect/heal_bell.c index 8bd6c1f45679..743af905ce5b 100644 --- a/test/battle/move_effect/heal_bell.c +++ b/test/battle/move_effect/heal_bell.c @@ -10,7 +10,7 @@ ASSUMPTIONS DOUBLE_BATTLE_TEST("Heal Bell cures the entire party") { u32 move; - + PARAMETRIZE { move = MOVE_HEAL_BELL; } PARAMETRIZE { move = MOVE_AROMATHERAPY; } @@ -39,10 +39,10 @@ DOUBLE_BATTLE_TEST("Heal Bell cures the entire party") DOUBLE_BATTLE_TEST("Heal Bell does not cure soundproof partners") { u32 ability; - + PARAMETRIZE { ability = ABILITY_SCRAPPY; } PARAMETRIZE { ability = ABILITY_SOUNDPROOF; } - + ASSUME(B_HEAL_BELL_SOUNDPROOF != GEN_5); GIVEN { @@ -65,10 +65,10 @@ DOUBLE_BATTLE_TEST("Heal Bell does not cure soundproof partners") SINGLE_BATTLE_TEST("Heal Bell cures inactive soundproof Pokemon") { u32 ability; - + PARAMETRIZE { ability = ABILITY_SCRAPPY; } PARAMETRIZE { ability = ABILITY_SOUNDPROOF; } - + ASSUME(B_HEAL_BELL_SOUNDPROOF >= GEN_5); GIVEN { @@ -87,7 +87,7 @@ SINGLE_BATTLE_TEST("Heal Bell cures inactive soundproof Pokemon") SINGLE_BATTLE_TEST("Heal Bell cures a soundproof user") -{ +{ ASSUME(B_HEAL_BELL_SOUNDPROOF == GEN_5 || B_HEAL_BELL_SOUNDPROOF >= GEN_8); GIVEN { diff --git a/test/battle/move_effect/knock_off.c b/test/battle/move_effect/knock_off.c index ba6f9b6fe7c6..50d8aaa77304 100644 --- a/test/battle/move_effect/knock_off.c +++ b/test/battle/move_effect/knock_off.c @@ -90,7 +90,7 @@ SINGLE_BATTLE_TEST("Knock Off does not remove items through Substitute") PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LEFTOVERS); }; } WHEN { - TURN { MOVE(opponent, MOVE_SUBSTITUTE); + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_KNOCK_OFF); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); @@ -112,7 +112,7 @@ SINGLE_BATTLE_TEST("Recycle cannot recover an item removed by Knock Off") ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); MESSAGE("Wobbuffet knocked off Foe Wobbuffet's Leftovers!"); - + MESSAGE("Foe Wobbuffet used Recycle!"); MESSAGE("But it failed!"); } THEN { diff --git a/test/battle/move_effect/revelation_dance.c b/test/battle/move_effect/revelation_dance.c index 489dee1c879b..9ab5d4a8e24b 100644 --- a/test/battle/move_effect/revelation_dance.c +++ b/test/battle/move_effect/revelation_dance.c @@ -37,7 +37,7 @@ SINGLE_BATTLE_TEST("Revelation Dance changes its type depending on the user's 1s ANIMATION(ANIM_TYPE_MOVE, MOVE_REVELATION_DANCE, player); HP_BAR(opponent); MESSAGE("It's not very effective…"); - } + } } } } @@ -142,7 +142,7 @@ SINGLE_BATTLE_TEST("Revelation Dance becomes Normal type if used by a Typeless P NONE_OF { ANIMATION(ANIM_TYPE_MOVE, MOVE_REVELATION_DANCE, player); HP_BAR(opponent); - MESSAGE("It's not very effective…"); + MESSAGE("It's not very effective…"); } } } diff --git a/test/battle/move_effect/sleep_talk.c b/test/battle/move_effect/sleep_talk.c index 0a42eb90e921..8ecd600f362d 100644 --- a/test/battle/move_effect/sleep_talk.c +++ b/test/battle/move_effect/sleep_talk.c @@ -14,7 +14,7 @@ SINGLE_BATTLE_TEST("Sleep Talk fails if not asleep") u32 status; PARAMETRIZE { status = STATUS1_SLEEP; } PARAMETRIZE { status = STATUS1_NONE; } - + GIVEN { PLAYER(SPECIES_WOBBUFFET) { Status1(status); Moves(MOVE_SLEEP_TALK, MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH); } OPPONENT(SPECIES_WOBBUFFET); @@ -35,7 +35,7 @@ SINGLE_BATTLE_TEST("Sleep Talk fails if not asleep") SINGLE_BATTLE_TEST("Sleep Talk works if user has Comatose") { - + GIVEN { PLAYER(SPECIES_KOMALA) { Moves(MOVE_SLEEP_TALK, MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH); } OPPONENT(SPECIES_WOBBUFFET); diff --git a/test/battle/move_effect/telekinesis.c b/test/battle/move_effect/telekinesis.c index 4210b1865bee..3da96a08b999 100644 --- a/test/battle/move_effect/telekinesis.c +++ b/test/battle/move_effect/telekinesis.c @@ -71,4 +71,4 @@ SINGLE_BATTLE_TEST("Telekinesis makes the target immune to Ground-type attacks") TO_DO_BATTLE_TEST("Baton Pass passes Telekinesis' effect"); //Bulbapedia doesn't confirm what happens with Diglett, Dugtrio, Sandygast and Palossand, so it needs to be tested in-game. -TO_DO_BATTLE_TEST("Baton Pass removes Telekinesis' effect disappears if the switching-in mon is Mega Gengar"); +TO_DO_BATTLE_TEST("Baton Pass removes Telekinesis' effect disappears if the switching-in mon is Mega Gengar"); diff --git a/tools/learnset_helpers/teachable.py b/tools/learnset_helpers/teachable.py index 92ed8562d8c3..eb109e7a46e8 100644 --- a/tools/learnset_helpers/teachable.py +++ b/tools/learnset_helpers/teachable.py @@ -13,7 +13,7 @@ def parse_mon_name(name): return re.sub(r'(?!^)([A-Z]+)', r'_\1', name).upper() - + tm_moves = [] tutor_moves = [] @@ -196,7 +196,7 @@ def header_print(str): header += "// " + longest_move_name * "*" + " //\n" header_print(tutor_title) tutor_moves.sort() # alphabetically sort tutor moves for easier referencing -for move in tutor_moves: +for move in tutor_moves: header_print("- " + move) header += "// " + longest_move_name * "*" + " //\n" header_print(universal_title) diff --git a/tools/preproc/asm_file.cpp b/tools/preproc/asm_file.cpp index ca8b8cc4ae96..39d352bbd7ad 100644 --- a/tools/preproc/asm_file.cpp +++ b/tools/preproc/asm_file.cpp @@ -673,14 +673,14 @@ int AsmFile::FindLastLineNumber(std::string& filename) if (pos < 0) RaiseError("line indicator for header file not found before `enum`"); - + pos++; while (m_buffer[pos] == ' ' || m_buffer[pos] == '\t') pos++; if (!IsAsciiDigit(m_buffer[pos])) RaiseError("malformatted line indicator found before `enum`, expected line number"); - + unsigned n = 0; int digit = 0; while ((digit = ConvertDigit(m_buffer[pos++], 10)) != -1) @@ -715,7 +715,7 @@ int AsmFile::FindLastLineNumber(std::string& filename) filename += c; } - + return n + linebreaks - 1; } From 45f10d734fde18c96fccdf2bf152c9e3e3aebae8 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Thu, 15 Aug 2024 13:51:39 +0200 Subject: [PATCH 02/16] Fixed Ice Face, implemented tests (#5171) * Fixed Ice Face, implemented tests * Fixed agbcc and bad battle mon looping * Fixed the ShouldChangeFormInWeather function again * Cleaned up End of Turn weather form changes, simplified Ice Face --------- Co-authored-by: Hedara --- include/battle.h | 1 - src/battle_main.c | 2 - src/battle_util.c | 45 +++-------- test/battle/ability/ice_face.c | 140 +++++++++++++++++++++++++++++++-- 4 files changed, 146 insertions(+), 42 deletions(-) diff --git a/include/battle.h b/include/battle.h index 1d60aacec172..ace178c0e734 100644 --- a/include/battle.h +++ b/include/battle.h @@ -763,7 +763,6 @@ struct BattleStruct bool8 effectsBeforeUsingMoveDone:1; // Mega Evo and Focus Punch/Shell Trap effects. u8 targetsDone[MAX_BATTLERS_COUNT]; // Each battler as a bit. u16 overwrittenAbilities[MAX_BATTLERS_COUNT]; // abilities overwritten during battle (keep separate from battle history in case of switching) - bool8 allowedToChangeFormInWeather[PARTY_SIZE][NUM_BATTLE_SIDES]; // For each party member and side, used by Ice Face. u8 battleBondTransformed[NUM_BATTLE_SIDES]; // Bitfield for each party. u8 storedHealingWish:4; // Each battler as a bit. u8 storedLunarDance:4; // Each battler as a bit. diff --git a/src/battle_main.c b/src/battle_main.c index e164976e9d16..55337a045eb6 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3114,8 +3114,6 @@ static void BattleStartClearSetData(void) gBattleStruct->itemLost[B_SIDE_PLAYER][i].originalItem = GetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM); gBattleStruct->itemLost[B_SIDE_OPPONENT][i].originalItem = GetMonData(&gEnemyParty[i], MON_DATA_HELD_ITEM); gPartyCriticalHits[i] = 0; - gBattleStruct->allowedToChangeFormInWeather[i][B_SIDE_PLAYER] = FALSE; - gBattleStruct->allowedToChangeFormInWeather[i][B_SIDE_OPPONENT] = FALSE; } gBattleStruct->swapDamageCategory = FALSE; // Photon Geyser, Shell Side Arm, Light That Burns the Sky diff --git a/src/battle_util.c b/src/battle_util.c index ada7cb20a329..c18b0544a095 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -1671,7 +1671,6 @@ enum ENDTURN_ION_DELUGE, ENDTURN_FAIRY_LOCK, ENDTURN_RETALIATE, - ENDTURN_WEATHER_FORM, ENDTURN_STATUS_HEAL, ENDTURN_RAINBOW, ENDTURN_SEA_OF_FIRE, @@ -2171,18 +2170,6 @@ u8 DoFieldEndTurnEffects(void) gSideTimers[B_SIDE_OPPONENT].retaliateTimer--; gBattleStruct->turnCountersTracker++; break; - case ENDTURN_WEATHER_FORM: - for (i = 0; i < gBattlersCount; i++) - { - if (AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, i, 0, 0, 0)) - { - effect++; - break; - } - } - if (effect == 0) - gBattleStruct->turnCountersTracker++; - break; case ENDTURN_STATUS_HEAL: for (gBattlerAttacker = 0; gBattlerAttacker < gBattlersCount; gBattlerAttacker++) { @@ -3894,21 +3881,6 @@ static const u16 sWeatherFlagsInfo[][3] = [ENUM_WEATHER_FOG] = {B_WEATHER_FOG_TEMPORARY, B_WEATHER_FOG_PERMANENT, HOLD_EFFECT_NONE}, }; -static void ShouldChangeFormInWeather(u32 battler) -{ - int i; - int side = GetBattlerSide(battler); - struct Pokemon *party = GetSideParty(side); - - for (i = 0; i < PARTY_SIZE; i++) - { - if (GetMonData(&party[i], MON_DATA_SPECIES) == SPECIES_EISCUE_NOICE_FACE) - gBattleStruct->allowedToChangeFormInWeather[i][side] = TRUE; - else - gBattleStruct->allowedToChangeFormInWeather[i][side] = FALSE; - } -} - bool32 TryChangeBattleWeather(u32 battler, u32 weatherEnumId, bool32 viaAbility) { u16 battlerAbility = GetBattlerAbility(battler); @@ -3922,7 +3894,6 @@ bool32 TryChangeBattleWeather(u32 battler, u32 weatherEnumId, bool32 viaAbility) else if (B_ABILITY_WEATHER < GEN_6 && viaAbility && !(gBattleWeather & sWeatherFlagsInfo[weatherEnumId][1])) { gBattleWeather = (sWeatherFlagsInfo[weatherEnumId][0] | sWeatherFlagsInfo[weatherEnumId][1]); - ShouldChangeFormInWeather(battler); return TRUE; } else if (!(gBattleWeather & (sWeatherFlagsInfo[weatherEnumId][0] | sWeatherFlagsInfo[weatherEnumId][1]))) @@ -3932,7 +3903,6 @@ bool32 TryChangeBattleWeather(u32 battler, u32 weatherEnumId, bool32 viaAbility) gWishFutureKnock.weatherDuration = 8; else gWishFutureKnock.weatherDuration = 5; - ShouldChangeFormInWeather(battler); return TRUE; } return FALSE; @@ -4961,6 +4931,17 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 effect++; } break; + case ABILITY_ICE_FACE: + if (IsBattlerWeatherAffected(battler, B_WEATHER_HAIL | B_WEATHER_SNOW) + && gBattleMons[battler].species == SPECIES_EISCUE_NOICE_FACE + && !(gBattleMons[battler].status2 & STATUS2_TRANSFORMED)) + { + // TODO: Convert this to a proper FORM_CHANGE type. + gBattleMons[battler].species = SPECIES_EISCUE_ICE_FACE; + BattleScriptPushCursorAndCallback(BattleScript_BattlerFormChangeWithStringEnd3); + effect++; + } + break; } break; case ABILITYEFFECT_ENDTURN: @@ -6285,11 +6266,9 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITY_ICE_FACE: if (IsBattlerWeatherAffected(battler, B_WEATHER_HAIL | B_WEATHER_SNOW) && gBattleMons[battler].species == SPECIES_EISCUE_NOICE_FACE - && !(gBattleMons[battler].status2 & STATUS2_TRANSFORMED) - && gBattleStruct->allowedToChangeFormInWeather[gBattlerPartyIndexes[battler]][GetBattlerSide(battler)]) + && !(gBattleMons[battler].status2 & STATUS2_TRANSFORMED)) { // TODO: Convert this to a proper FORM_CHANGE type. - gBattleStruct->allowedToChangeFormInWeather[gBattlerPartyIndexes[battler]][GetBattlerSide(battler)] = FALSE; gBattleMons[battler].species = SPECIES_EISCUE_ICE_FACE; BattleScriptPushCursorAndCallback(BattleScript_BattlerFormChangeWithStringEnd3); effect++; diff --git a/test/battle/ability/ice_face.c b/test/battle/ability/ice_face.c index 53917b5623a2..54a307754c96 100644 --- a/test/battle/ability/ice_face.c +++ b/test/battle/ability/ice_face.c @@ -1,9 +1,137 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Ice Face blocks physical moves, changing Eiscue into its Noice Face form"); // Include Special move in test -TO_DO_BATTLE_TEST("Ice Face is restored if hail or snow begins while Noice Face Eiscue is out"); -TO_DO_BATTLE_TEST("Ice Face is restored if Noice Face Eiscue is sent in while hail or snow is active"); -TO_DO_BATTLE_TEST("Ice Face is not restored if Eiscue changes into Noice Face form while there's already hail"); -TO_DO_BATTLE_TEST("Ice Face form change persists after switching out"); -TO_DO_BATTLE_TEST("Ice Face doesn't transform Eiscue if Cloud Nine/Air Lock is on the field"); +SINGLE_BATTLE_TEST("Ice Face blocks physical moves, changing Eiscue into its Noice Face form") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_EISCUE); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + } +} + +SINGLE_BATTLE_TEST("Ice Face does not block special moves, Eiscue stays in Ice Face form") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(gMovesInfo[MOVE_EMBER].category == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_EISCUE); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_EMBER); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ICE_FACE); + } +} + +SINGLE_BATTLE_TEST("Ice Face is restored if hail or snow begins while Noice Face Eiscue is out") +{ + u32 move; + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + PARAMETRIZE { move = MOVE_HAIL; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(gMovesInfo[MOVE_SNOWSCAPE].effect == EFFECT_SNOWSCAPE); + ASSUME(gMovesInfo[MOVE_HAIL].effect == EFFECT_HAIL); + PLAYER(SPECIES_EISCUE); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + TURN { MOVE(opponent, move); } + TURN { MOVE(opponent, MOVE_TACKLE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + } +} + +SINGLE_BATTLE_TEST("Ice Face is restored if Noice Face Eiscue is sent in while hail or snow is active") +{ + u32 move; + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + PARAMETRIZE { move = MOVE_HAIL; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(gMovesInfo[MOVE_SNOWSCAPE].effect == EFFECT_SNOWSCAPE); + ASSUME(gMovesInfo[MOVE_HAIL].effect == EFFECT_HAIL); + PLAYER(SPECIES_EISCUE); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_TACKLE); } + TURN { SWITCH(player, 1); MOVE(opponent, move); } + TURN { SWITCH(player, 0); MOVE(opponent, MOVE_TACKLE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + } +} + +SINGLE_BATTLE_TEST("Ice Face is not restored if Eiscue changes into Noice Face form while there's already hail or snow") +{ + u32 move; + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + PARAMETRIZE { move = MOVE_HAIL; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(gMovesInfo[MOVE_SNOWSCAPE].effect == EFFECT_SNOWSCAPE); + ASSUME(gMovesInfo[MOVE_HAIL].effect == EFFECT_HAIL); + PLAYER(SPECIES_EISCUE) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, move); MOVE(opponent, MOVE_TACKLE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_TACKLE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + MESSAGE("Eiscue used Celebrate!"); + MESSAGE("Eiscue fainted!"); + } +} + +SINGLE_BATTLE_TEST("Ice Face form change persists after switching out") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_EISCUE) { HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_CELEBRATE); } + TURN { SWITCH(player, 0); MOVE(opponent, MOVE_TACKLE); SEND_OUT(player, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + MESSAGE("Eiscue fainted!"); + } +} + +SINGLE_BATTLE_TEST("Ice Face doesn't transform Eiscue if Cloud Nine/Air Lock is on the field") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_TACKLE].category == DAMAGE_CATEGORY_PHYSICAL); + PLAYER(SPECIES_EISCUE) { HP(1); } + OPPONENT(SPECIES_RAYQUAZA) { Ability(ABILITY_AIR_LOCK); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_TACKLE); } + TURN { MOVE(player, MOVE_SNOWSCAPE); MOVE(opponent, MOVE_TACKLE); } + } SCENE { + ABILITY_POPUP(player, ABILITY_ICE_FACE); + MESSAGE("Eiscue transformed!"); + MESSAGE("Eiscue fainted!"); + } +} From 47356d181ac954897d92c2edcc1fa72400160a53 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Thu, 15 Aug 2024 19:27:35 +0200 Subject: [PATCH 03/16] Fixed followers breaking for species IDs above 1535 (#5179) * Fixed followers breaking for species Ids above 1535 * Added reminder to increase follower species bits if needed in the future --------- Co-authored-by: Hedara --- include/constants/event_objects.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/constants/event_objects.h b/include/constants/event_objects.h index e2ac7f792068..fccc206638ad 100644 --- a/include/constants/event_objects.h +++ b/include/constants/event_objects.h @@ -271,7 +271,7 @@ #define OBJ_EVENT_GFX_VAR_F (OBJ_EVENT_GFX_VARS + 0xF) #define OBJ_EVENT_GFX_MON_BASE 0x200 // 512 -#define OBJ_EVENT_GFX_SPECIES_BITS 11 +#define OBJ_EVENT_GFX_SPECIES_BITS 12 // This will need to be updated when NUM_SPECIES is > ~3.5k #define OBJ_EVENT_GFX_SPECIES_MASK ((1 << OBJ_EVENT_GFX_SPECIES_BITS) - 1) // Used to call a specific species' follower graphics. Useful for static encounters. From ec3a86dd9a85c6dd6c85ff0826b576925ef0383e Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Fri, 16 Aug 2024 14:37:23 +0100 Subject: [PATCH 04/16] Adds in-battle effect of Pickup, adds Harvest and Pickup tests (#5170) * Adds Harvest tests * Adds Pickup in-battle effect + tests * Fix G-Max Replenish test (Munchlax activates Pickup before G-Max Replenish) * Change canPickupItem to bit field * Make RandomUniformExcept inclusive (higher end) + convert bitfield * Use CantPickupItem in PickupHasValidTargetc check * Review --- data/battle_scripts_1.s | 9 ++ include/battle.h | 1 + include/battle_scripts.h | 1 + include/battle_util.h | 2 + include/random.h | 2 + src/battle_main.c | 2 + src/battle_script_commands.c | 6 +- src/battle_util.c | 32 +++- test/battle/ability/harvest.c | 274 +++++++++++++++++++++++++++++-- test/battle/ability/pickup.c | 295 ++++++++++++++++++++++++++++++++++ test/battle/gimmick/dynamax.c | 2 +- test/test_runner_battle.c | 2 +- 12 files changed, 610 insertions(+), 18 deletions(-) create mode 100644 test/battle/ability/pickup.c diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index c47127c651eb..95fffd05137c 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -7754,6 +7754,15 @@ BattleScript_CheekPouchActivates:: copybyte gBattlerAttacker, sSAVED_BATTLER return +BattleScript_PickupActivates:: + pause 5 + tryrecycleitem BattleScript_PickupActivatesEnd + call BattleScript_AbilityPopUp + printstring STRINGID_XFOUNDONEY + waitmessage B_WAIT_TIME_LONG +BattleScript_PickupActivatesEnd: + end3 + BattleScript_HarvestActivates:: pause 5 tryrecycleitem BattleScript_HarvestActivatesEnd diff --git a/include/battle.h b/include/battle.h index ace178c0e734..0d54f2a614b7 100644 --- a/include/battle.h +++ b/include/battle.h @@ -676,6 +676,7 @@ struct BattleStruct u16 chosenItem[MAX_BATTLERS_COUNT]; u16 choicedMove[MAX_BATTLERS_COUNT]; u16 changedItems[MAX_BATTLERS_COUNT]; + u8 canPickupItem; u8 switchInBattlerCounter; u8 arenaTurnCounter; u8 turnSideTracker; diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 6bdb88b28f5b..cd35e1de8218 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -295,6 +295,7 @@ extern const u8 BattleScript_WeakArmorActivates[]; extern const u8 BattleScript_FellStingerRaisesStat[]; extern const u8 BattleScript_SnowWarningActivatesHail[]; extern const u8 BattleScript_SnowWarningActivatesSnow[]; +extern const u8 BattleScript_PickupActivates[]; extern const u8 BattleScript_HarvestActivates[]; extern const u8 BattleScript_ImposterActivates[]; extern const u8 BattleScript_SelectingNotAllowedMoveAssaultVest[]; diff --git a/include/battle_util.h b/include/battle_util.h index 8eadf47c983c..de05242902d6 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -231,6 +231,8 @@ bool32 TryRoomService(u32 battler); void BufferStatChange(u32 battler, u8 statId, u8 stringId); bool32 BlocksPrankster(u16 move, u32 battlerPrankster, u32 battlerDef, bool32 checkTarget); u16 GetUsedHeldItem(u32 battler); +bool32 PickupHasValidTarget(u32 battler); +bool32 CantPickupItem(u32 battler); bool32 IsBattlerWeatherAffected(u32 battler, u32 weatherFlags); u32 GetBattlerMoveTargetType(u32 battler, u32 move); bool32 CanTargetBattler(u32 battlerAtk, u32 battlerDef, u16 move); diff --git a/include/random.h b/include/random.h index cd7e26b58920..d254a08f0384 100644 --- a/include/random.h +++ b/include/random.h @@ -173,12 +173,14 @@ enum RandomTag RNG_G_MAX_BEFUDDLE, RNG_G_MAX_REPLENISH, RNG_G_MAX_SNOOZE, + RNG_HARVEST, RNG_HITS, RNG_HOLD_EFFECT_FLINCH, RNG_INFATUATION, RNG_LOADED_DICE, RNG_METRONOME, RNG_PARALYSIS, + RNG_PICKUP, RNG_POISON_POINT, RNG_POISON_TOUCH, RNG_RAMPAGE_TURNS, diff --git a/src/battle_main.c b/src/battle_main.c index 55337a045eb6..10f27e921f12 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3219,6 +3219,7 @@ void SwitchInClearSetData(u32 battler) gBattleStruct->lastMoveFailed &= ~(gBitTable[battler]); gBattleStruct->palaceFlags &= ~(gBitTable[battler]); gBattleStruct->boosterEnergyActivates &= ~(gBitTable[battler]); + gBattleStruct->canPickupItem &= ~(1u << battler); for (i = 0; i < ARRAY_COUNT(gSideTimers); i++) { @@ -5112,6 +5113,7 @@ static void TurnValuesCleanUp(bool8 var0) if (gDisableStructs[i].rechargeTimer == 0) gBattleMons[i].status2 &= ~STATUS2_RECHARGE; } + gBattleStruct->canPickupItem &= ~(1u << i); } if (gDisableStructs[i].substituteHP == 0) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index afc63fed9b9d..6ba28f5027bd 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -8071,6 +8071,7 @@ static void Cmd_removeitem(void) gBattleStruct->usedHeldItems[gBattlerPartyIndexes[battler]][GetBattlerSide(battler)] = itemId; // Remember if switched out gBattleMons[battler].item = ITEM_NONE; + gBattleStruct->canPickupItem |= (1u << battler); CheckSetUnburden(battler); BtlController_EmitSetMonData(battler, BUFFER_A, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[battler].item), &gBattleMons[battler].item); @@ -14853,7 +14854,10 @@ static void Cmd_tryrecycleitem(void) u16 *usedHeldItem; - usedHeldItem = &gBattleStruct->usedHeldItems[gBattlerPartyIndexes[gBattlerAttacker]][GetBattlerSide(gBattlerAttacker)]; + if (gCurrentMove == MOVE_NONE && GetBattlerAbility(gBattlerAttacker) == ABILITY_PICKUP) + usedHeldItem = &gBattleStruct->usedHeldItems[gBattlerPartyIndexes[gBattlerTarget]][GetBattlerSide(gBattlerTarget)]; + else + usedHeldItem = &gBattleStruct->usedHeldItems[gBattlerPartyIndexes[gBattlerAttacker]][GetBattlerSide(gBattlerAttacker)]; if (*usedHeldItem != ITEM_NONE && gBattleMons[gBattlerAttacker].item == ITEM_NONE) { gLastUsedItem = *usedHeldItem; diff --git a/src/battle_util.c b/src/battle_util.c index c18b0544a095..8729d0cf1a21 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4950,8 +4950,19 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 gBattlerAttacker = battler; switch (gLastUsedAbility) { + case ABILITY_PICKUP: + if (gBattleMons[battler].item == ITEM_NONE + && gBattleStruct->changedItems[battler] == ITEM_NONE // Will not inherit an item + && PickupHasValidTarget(battler)) + { + gBattlerTarget = RandomUniformExcept(RNG_PICKUP, 0, gBattlersCount - 1, CantPickupItem); + gLastUsedItem = GetUsedHeldItem(gBattlerTarget); + BattleScriptPushCursorAndCallback(BattleScript_PickupActivates); + effect++; + } + break; case ABILITY_HARVEST: - if ((IsBattlerWeatherAffected(battler, B_WEATHER_SUN) || Random() % 2 == 0) + if ((IsBattlerWeatherAffected(battler, B_WEATHER_SUN) || RandomPercentage(RNG_HARVEST, 50)) && gBattleMons[battler].item == ITEM_NONE && gBattleStruct->changedItems[battler] == ITEM_NONE // Will not inherit an item && ItemId_GetPocket(GetUsedHeldItem(battler)) == POCKET_BERRIES) @@ -11398,6 +11409,25 @@ u16 GetUsedHeldItem(u32 battler) return gBattleStruct->usedHeldItems[gBattlerPartyIndexes[battler]][GetBattlerSide(battler)]; } +bool32 CantPickupItem(u32 battler) +{ + // Used by RandomUniformExcept() for RNG_PICKUP + if (battler == gBattlerAttacker && gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_LINK)) + return TRUE; + return !(IsBattlerAlive(battler) && GetUsedHeldItem(battler) && gBattleStruct->canPickupItem & (1u << battler)); +} + +bool32 PickupHasValidTarget(u32 battler) +{ + u32 i; + for (i = 0; i < gBattlersCount; i++) + { + if (!CantPickupItem(i)) + return TRUE; + } + return FALSE; +} + bool32 IsBattlerWeatherAffected(u32 battler, u32 weatherFlags) { if (gBattleWeather & weatherFlags && WEATHER_HAS_EFFECT) diff --git a/test/battle/ability/harvest.c b/test/battle/ability/harvest.c index b4783c4542fa..f19e0715de69 100644 --- a/test/battle/ability/harvest.c +++ b/test/battle/ability/harvest.c @@ -1,18 +1,264 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Harvest has a 50% chance to restore a Berry at the end of the turn"); -TO_DO_BATTLE_TEST("Harvest always restores a Berry in Sunlight"); -TO_DO_BATTLE_TEST("Harvest doesn't always restore a Berry if Cloud Nine/Air Lock is on the field"); -TO_DO_BATTLE_TEST("Harvest restores a Berry even after being switched out and back in"); -TO_DO_BATTLE_TEST("Harvest restores a Berry consumed by Fling"); -TO_DO_BATTLE_TEST("Harvest restores a Berry consumed by Natural Gift"); +ASSUMPTIONS +{ + ASSUME(gItemsInfo[ITEM_SITRUS_BERRY].holdEffect == HOLD_EFFECT_RESTORE_PCT_HP); + ASSUME(I_SITRUS_BERRY_HEAL >= GEN_4); + ASSUME(gMovesInfo[MOVE_SUNNY_DAY].effect == EFFECT_SUNNY_DAY); +} + +SINGLE_BATTLE_TEST("Harvest has a 50% chance to restore a Berry at the end of the turn") +{ + PASSES_RANDOMLY(1, 2, RNG_HARVEST); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Harvest always restores a Berry in Sunlight") +{ + PASSES_RANDOMLY(1, 1, RNG_HARVEST); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Harvest doesn't always restore a Berry if Cloud Nine/Air Lock is on the field") +{ + PASSES_RANDOMLY(1, 2, RNG_HARVEST); + GIVEN { + PLAYER(SPECIES_GOLDUCK) { Ability(ABILITY_CLOUD_NINE); } + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Harvest restores a Berry even after being switched out and back in") +{ + ASSUME(gMovesInfo[MOVE_PARTING_SHOT].effect == EFFECT_PARTING_SHOT); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_PARTING_SHOT); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_PARTING_SHOT); SEND_OUT(opponent, 0); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Harvest restores a Berry consumed by Fling") +{ + ASSUME(gMovesInfo[MOVE_FLING].effect == EFFECT_FLING); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_FLING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, opponent); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Harvest restores a Berry consumed by Natural Gift") +{ + ASSUME(gMovesInfo[MOVE_NATURAL_GIFT].effect == EFFECT_NATURAL_GIFT); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_NATURAL_GIFT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, opponent); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_SITRUS_BERRY); + } +} + TO_DO_BATTLE_TEST("Harvest only works once per turn"); // Check for berries that are consumed immediately, like Pecha Berry -TO_DO_BATTLE_TEST("Harvest doesn't restore a Berry when destroyed by Incinerate"); -TO_DO_BATTLE_TEST("Harvest doesn't restore a Berry when knocked off by Knock Off"); -TO_DO_BATTLE_TEST("Harvest doesn't restore a Berry when eaten by Bug Bite/Pluck"); -TO_DO_BATTLE_TEST("Harvest doesn't restore a Berry that's collected via Pickup"); -TO_DO_BATTLE_TEST("Harvest order is affected by speed"); -TO_DO_BATTLE_TEST("Harvest doesn't restore a Berry when transfered to another Pokémon"); -TO_DO_BATTLE_TEST("Harvest can restore a Berry that was transferred from another Pokémon"); -TO_DO_BATTLE_TEST("Harvest can only restore the newest berry consumed that was transferred from another Pokémon instead of its original Berry"); + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry when destroyed by Incinerate") +{ + PASSES_RANDOMLY(1, 1, RNG_HARVEST); + ASSUME(MoveHasAdditionalEffect(MOVE_INCINERATE, MOVE_EFFECT_INCINERATE)); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_INCINERATE); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_INCINERATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry when knocked off by Knock Off") +{ + PASSES_RANDOMLY(1, 1, RNG_HARVEST); + ASSUME(MoveHasAdditionalEffect(MOVE_KNOCK_OFF, MOVE_EFFECT_KNOCK_OFF)); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry when eaten by Bug Bite/Pluck") +{ + PASSES_RANDOMLY(1, 1, RNG_HARVEST); + ASSUME(MoveHasAdditionalEffect(MOVE_BUG_BITE, MOVE_EFFECT_BUG_BITE)); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_BUG_BITE); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BUG_BITE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry that's collected via Pickup") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Speed(50); Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_EXEGGUTOR) { Speed(10); Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, opponent); + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Harvest order is affected by speed") +{ + GIVEN { + PLAYER(SPECIES_EXEGGUTOR) { Speed(2); Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + PLAYER(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_EXEGGUTOR) { Speed(10); Ability(ABILITY_HARVEST); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(50); } + } WHEN { + TURN { MOVE(playerRight, MOVE_BULLDOZE); MOVE(playerLeft, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLDOZE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_HARVEST); + ABILITY_POPUP(playerLeft, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponentLeft->item, ITEM_SITRUS_BERRY); + EXPECT_EQ(playerLeft->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Harvest doesn't restore a Berry when transfered to another Pokémon") +{ + ASSUME(gMovesInfo[MOVE_TRICK].effect == EFFECT_TRICK); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_TRICK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_HARVEST); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Harvest can restore a Berry that was transferred from another Pokémon") +{ + ASSUME(gMovesInfo[MOVE_TRICK].effect == EFFECT_TRICK); + GIVEN { + PLAYER(SPECIES_TORKOAL) { Ability(ABILITY_DROUGHT); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); HP(100); MaxHP(500); } + } WHEN { + TURN { MOVE(opponent, MOVE_TRICK); MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT_GT(opponent->hp, opponent->maxHP / 2); // eats 2 Sitrus + } +} + +SINGLE_BATTLE_TEST("Harvest can only restore the newest berry consumed that was transferred from another Pokémon instead of its original Berry") +{ + ASSUME(gMovesInfo[MOVE_TRICK].effect == EFFECT_TRICK); + ASSUME(gItemsInfo[ITEM_APICOT_BERRY].holdEffect == HOLD_EFFECT_SP_DEFENSE_UP); + GIVEN { + PLAYER(SPECIES_TORKOAL) { Ability(ABILITY_DROUGHT); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_EXEGGUTOR) { Ability(ABILITY_HARVEST); HP(100); MaxHP(500); Item(ITEM_APICOT_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_TRICK); MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ABILITY_POPUP(opponent, ABILITY_HARVEST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT_GT(opponent->hp, opponent->maxHP / 2); // eats 2 Sitrus + } +} diff --git a/test/battle/ability/pickup.c b/test/battle/ability/pickup.c new file mode 100644 index 000000000000..57f6429febf9 --- /dev/null +++ b/test/battle/ability/pickup.c @@ -0,0 +1,295 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gItemsInfo[ITEM_SITRUS_BERRY].holdEffect == HOLD_EFFECT_RESTORE_PCT_HP); + ASSUME(I_SITRUS_BERRY_HEAL >= GEN_4); +} + +SINGLE_BATTLE_TEST("Pickup grants an item used by another Pokémon") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } THEN { + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant the user their item") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); MaxHP(500); HP(251); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant another Pokémon's popped Air Balloon") +{ + ASSUME(gItemsInfo[ITEM_AIR_BALLOON].holdEffect == HOLD_EFFECT_AIR_BALLOON); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_AIR_BALLOON); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Air Balloon!"); + } + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an item not used that turn") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an item after its holder faints") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_MEMENTO); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an used item if holder is replaced") +{ + ASSUME(gMovesInfo[MOVE_PARTING_SHOT].effect == EFFECT_PARTING_SHOT); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(300); HP(151); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(300); HP(151); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_PARTING_SHOT); SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); MOVE(opponent, MOVE_PARTING_SHOT); SEND_OUT(opponent, 0); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, opponent); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an item if it destroyed the item with Incinerate") +{ + ASSUME(MoveHasAdditionalEffect(MOVE_INCINERATE, MOVE_EFFECT_INCINERATE)); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_INCINERATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_INCINERATE, player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an item if it knocked off that item") +{ + ASSUME(MoveHasAdditionalEffect(MOVE_KNOCK_OFF, MOVE_EFFECT_KNOCK_OFF)); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an item if the user eats it with Bug Bite/Pluck") +{ + ASSUME(MoveHasAdditionalEffect(MOVE_BUG_BITE, MOVE_EFFECT_BUG_BITE)); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_BUG_BITE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BUG_BITE, player); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup doesn't grant an used item if its user already restored it") +{ + ASSUME(gMovesInfo[MOVE_RECYCLE].effect == EFFECT_RECYCLE); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_RECYCLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RECYCLE, opponent); + NONE_OF { + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } + } THEN { + EXPECT_EQ(player->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Pickup restores an item that has been Flinged") +{ + ASSUME(gMovesInfo[MOVE_FLING].effect == EFFECT_FLING); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_FLING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, opponent); + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } THEN { + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + } +} + +SINGLE_BATTLE_TEST("Pickup restores an item that was used by Natural Gift") +{ + ASSUME(gMovesInfo[MOVE_NATURAL_GIFT].effect == EFFECT_NATURAL_GIFT); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_NATURAL_GIFT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_NATURAL_GIFT, opponent); + ABILITY_POPUP(player, ABILITY_PICKUP); + MESSAGE("Zigzagoon found one Sitrus Berry!"); + } THEN { + EXPECT_EQ(player->item, ITEM_SITRUS_BERRY); + } +} + +DOUBLE_BATTLE_TEST("Pickup triggers based on Speed order") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Speed(1); Ability(ABILITY_PICKUP); } + PLAYER(SPECIES_WOBBUFFET) { Speed(2); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(3); MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_ZIGZAGOON) { Speed(50); Ability(ABILITY_PICKUP); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft); + ABILITY_POPUP(opponentRight, ABILITY_PICKUP); + NOT ABILITY_POPUP(playerLeft, ABILITY_PICKUP); + } THEN { + EXPECT_EQ(opponentRight->item, ITEM_SITRUS_BERRY); + EXPECT_EQ(playerLeft->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Pickup grants a random item used by another Pokémon") +{ + PASSES_RANDOMLY(1, 3, RNG_PICKUP); + ASSUME(gItemsInfo[ITEM_WHITE_HERB].holdEffect == HOLD_EFFECT_RESTORE_STATS); + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); } + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_WHITE_HERB); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_WHITE_HERB); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_BULLDOZE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLDOZE, playerLeft); + ABILITY_POPUP(playerLeft, ABILITY_PICKUP); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_SITRUS_BERRY); + } +} + +DOUBLE_BATTLE_TEST("Pickup doesn't trigger more than once per turn") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { HP(1); Ability(ABILITY_PICKUP); } + PLAYER(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_BULLDOZE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLDOZE, playerLeft); + ABILITY_POPUP(playerLeft, ABILITY_PICKUP); + NOT ABILITY_POPUP(playerLeft, ABILITY_PICKUP); + } THEN { + EXPECT_EQ(playerLeft->item, ITEM_NONE); + EXPECT_GT(playerLeft->hp, 1); + EXPECT_LT(playerLeft->hp, playerLeft->maxHP/2); + } +} diff --git a/test/battle/gimmick/dynamax.c b/test/battle/gimmick/dynamax.c index dca175c63711..b865ef2f1c41 100644 --- a/test/battle/gimmick/dynamax.c +++ b/test/battle/gimmick/dynamax.c @@ -1234,7 +1234,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Replenish recycles allies' berries 50\% of t GIVEN { ASSUME(gMovesInfo[MOVE_G_MAX_REPLENISH].argument == MAX_EFFECT_RECYCLE_BERRIES); PLAYER(SPECIES_SNORLAX) { Item(ITEM_APICOT_BERRY); GigantamaxFactor(TRUE); } - PLAYER(SPECIES_MUNCHLAX) { Item(ITEM_APICOT_BERRY); } + PLAYER(SPECIES_MUNCHLAX) { Item(ITEM_APICOT_BERRY); Ability(ABILITY_THICK_FAT); } OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_APICOT_BERRY); } OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_APICOT_BERRY); } } WHEN { diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index 92789710f7f0..6f318bbeadac 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -410,7 +410,7 @@ u32 RandomUniformExcept(enum RandomTag tag, u32 lo, u32 hi, bool32 (*reject)(u32 if (STATE->trials == 1) { u32 n = 0, i; - for (i = lo; i < hi; i++) + for (i = lo; i <= hi; i++) if (!reject(i)) n++; STATE->trials = n; From bd3d99d7d9031ef0e12de2c9d1ab31ecfca503b8 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Fri, 16 Aug 2024 17:31:28 +0200 Subject: [PATCH 05/16] Fixed Retaliate not working correctly with passive damage (#5182) * Fixed Retaliate not working correctly when allies fainted from passive end of turn damage * Changed test parameters to use legal stats --------- Co-authored-by: Hedara --- src/battle_main.c | 5 + src/battle_script_commands.c | 2 +- src/battle_util.c | 8 -- test/battle/move_effect/retaliate.c | 137 ++++++++++++++++++++++++++++ 4 files changed, 143 insertions(+), 9 deletions(-) create mode 100644 test/battle/move_effect/retaliate.c diff --git a/src/battle_main.c b/src/battle_main.c index 10f27e921f12..cba22fa5ef91 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -4038,6 +4038,11 @@ void BattleTurnPassed(void) SetAiLogicDataForTurn(AI_DATA); // get assumed abilities, hold effects, etc of all battlers gBattleMainFunc = HandleTurnActionSelectionState; + if (gSideTimers[B_SIDE_PLAYER].retaliateTimer > 0) + gSideTimers[B_SIDE_PLAYER].retaliateTimer--; + if (gSideTimers[B_SIDE_OPPONENT].retaliateTimer > 0) + gSideTimers[B_SIDE_OPPONENT].retaliateTimer--; + if (gBattleTypeFlags & BATTLE_TYPE_PALACE) BattleScriptExecute(BattleScript_PalacePrintFlavorText); else if (gBattleTypeFlags & BATTLE_TYPE_ARENA && gBattleStruct->arenaTurnCounter == 0) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 6ba28f5027bd..394a455b3aa2 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -3502,7 +3502,7 @@ void SetMoveEffect(bool32 primary, bool32 certain) && GetBattlerAbility(BATTLE_PARTNER(gBattlerTarget)) != ABILITY_MAGIC_GUARD) { gBattleScripting.savedBattler = BATTLE_PARTNER(gBattlerTarget); - gBattleMoveDamage = gBattleMons[BATTLE_PARTNER(gBattlerTarget)].hp / 16; + gBattleMoveDamage = gBattleMons[BATTLE_PARTNER(gBattlerTarget)].maxHP / 16; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; gBattlescriptCurrInstr = BattleScript_MoveEffectFlameBurst; diff --git a/src/battle_util.c b/src/battle_util.c index 8729d0cf1a21..0a680cc27250 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -1670,7 +1670,6 @@ enum ENDTURN_PSYCHIC_TERRAIN, ENDTURN_ION_DELUGE, ENDTURN_FAIRY_LOCK, - ENDTURN_RETALIATE, ENDTURN_STATUS_HEAL, ENDTURN_RAINBOW, ENDTURN_SEA_OF_FIRE, @@ -2163,13 +2162,6 @@ u8 DoFieldEndTurnEffects(void) } gBattleStruct->turnCountersTracker++; break; - case ENDTURN_RETALIATE: - if (gSideTimers[B_SIDE_PLAYER].retaliateTimer > 0) - gSideTimers[B_SIDE_PLAYER].retaliateTimer--; - if (gSideTimers[B_SIDE_OPPONENT].retaliateTimer > 0) - gSideTimers[B_SIDE_OPPONENT].retaliateTimer--; - gBattleStruct->turnCountersTracker++; - break; case ENDTURN_STATUS_HEAL: for (gBattlerAttacker = 0; gBattlerAttacker < gBattlersCount; gBattlerAttacker++) { diff --git a/test/battle/move_effect/retaliate.c b/test/battle/move_effect/retaliate.c new file mode 100644 index 000000000000..581793e8547a --- /dev/null +++ b/test/battle/move_effect/retaliate.c @@ -0,0 +1,137 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_RETALIATE].effect == EFFECT_RETALIATE); +} + +SINGLE_BATTLE_TEST("Retaliate doubles in base power the turn after an ally faints") +{ + s16 damage[2]; + GIVEN { + PLAYER(SPECIES_WYNAUT) { HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); SEND_OUT(player, 1); } + TURN { MOVE(player, MOVE_RETALIATE); } + TURN { MOVE(player, MOVE_RETALIATE); } + } SCENE { + HP_BAR(opponent, captureDamage: &damage[0]); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[1], Q_4_12(2), damage[0]); + } +} + +SINGLE_BATTLE_TEST("Retaliate doubles in base power the turn after an ally faints (opponent)") +{ + s16 damage[2]; + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); SEND_OUT(opponent, 1); } + TURN { MOVE(opponent, MOVE_RETALIATE); } + TURN { MOVE(opponent, MOVE_RETALIATE); } + } SCENE { + HP_BAR(player, captureDamage: &damage[0]); + HP_BAR(player, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[1], Q_4_12(2), damage[0]); + } +} + +DOUBLE_BATTLE_TEST("Retaliate works with passive damage") +{ + s16 damage[2]; + u32 move; + u32 move2 = MOVE_CELEBRATE; + struct BattlePokemon *moveTarget = playerLeft; + PARAMETRIZE { move = MOVE_TOXIC; moveTarget = playerLeft; } + PARAMETRIZE { move = MOVE_POISON_POWDER; moveTarget = playerLeft; } + PARAMETRIZE { move = MOVE_WILL_O_WISP; moveTarget = playerLeft; } + #if B_USE_FROSTBITE == TRUE + PARAMETRIZE { move = MOVE_ICE_BEAM; moveTarget = playerLeft; } + #endif + PARAMETRIZE { move = MOVE_SANDSTORM; moveTarget = playerLeft; } + PARAMETRIZE { move = MOVE_HAIL; moveTarget = playerLeft; } + PARAMETRIZE { move = MOVE_LEECH_SEED; moveTarget = playerLeft; } + PARAMETRIZE { move = MOVE_MAGMA_STORM; moveTarget = playerLeft; } + PARAMETRIZE { move = MOVE_FLAME_BURST; moveTarget = playerRight; } + PARAMETRIZE { move = MOVE_FIRE_PLEDGE; moveTarget = playerRight; move2 = MOVE_GRASS_PLEDGE; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TOXIC].effect == EFFECT_TOXIC); + ASSUME(gMovesInfo[MOVE_POISON_POWDER].effect == EFFECT_POISON); + ASSUME(gMovesInfo[MOVE_WILL_O_WISP].effect == EFFECT_WILL_O_WISP); + #if B_USE_FROSTBITE == TRUE + ASSUME(gMovesInfo[MOVE_ICE_BEAM].additionalEffects[0].moveEffect == MOVE_EFFECT_FREEZE_OR_FROSTBITE); + #endif + ASSUME(gMovesInfo[MOVE_SANDSTORM].effect == EFFECT_SANDSTORM); + ASSUME(gMovesInfo[MOVE_HAIL].effect == EFFECT_HAIL); + ASSUME(gMovesInfo[MOVE_LEECH_SEED].effect == EFFECT_LEECH_SEED); + ASSUME(gMovesInfo[MOVE_MAGMA_STORM].additionalEffects[0].moveEffect == MOVE_EFFECT_WRAP); + ASSUME(gMovesInfo[MOVE_FLAME_BURST].additionalEffects[0].moveEffect == MOVE_EFFECT_FLAME_BURST); + PLAYER(SPECIES_WYNAUT) { Ability(ABILITY_SHADOW_TAG); HP(18); } + PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_SHADOW_TAG); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_CLEFABLE) { Ability(ABILITY_MAGIC_GUARD); Level(1); } + OPPONENT(SPECIES_CLEFABLE) { Ability(ABILITY_MAGIC_GUARD); } + } WHEN { + TURN { MOVE(opponentRight, move2, target: moveTarget); MOVE(opponentLeft, move, target: moveTarget); MOVE(playerLeft, MOVE_CELEBRATE); SEND_OUT(playerLeft, 2); } + TURN { MOVE(opponentRight, MOVE_CELEBRATE, target: moveTarget); MOVE(playerLeft, MOVE_RETALIATE, target: opponentRight); } + TURN { MOVE(opponentRight, MOVE_CELEBRATE, target: moveTarget); MOVE(playerLeft, MOVE_RETALIATE, target: opponentRight); } + } SCENE { + if (move != MOVE_FLAME_BURST) + MESSAGE("Wynaut used Celebrate!"); + HP_BAR(opponentRight, captureDamage: &damage[0]); + HP_BAR(opponentRight, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[1], Q_4_12(2), damage[0]); + } +} + +SINGLE_BATTLE_TEST("Retaliate works with Perish Song") +{ + s16 damage[2]; + GIVEN { + ASSUME(gMovesInfo[MOVE_PERISH_SONG].effect == EFFECT_PERISH_SONG); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_KOMMO_O) { Ability(ABILITY_SOUNDPROOF); } + } WHEN { + TURN { MOVE(opponent, MOVE_PERISH_SONG); } + TURN { MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(opponent, MOVE_CELEBRATE); SEND_OUT(player, 1); } + TURN { MOVE(player, MOVE_RETALIATE); } + TURN { MOVE(player, MOVE_RETALIATE); } + } SCENE { + HP_BAR(opponent, captureDamage: &damage[0]); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[1], Q_4_12(2), damage[0]); + } +} + +SINGLE_BATTLE_TEST("Retaliate works with self-inflicted fainting") +{ + s16 damage[2]; + GIVEN { + ASSUME(gMovesInfo[MOVE_HEALING_WISH].effect == EFFECT_HEALING_WISH); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HEALING_WISH); SEND_OUT(player, 1); } + TURN { MOVE(player, MOVE_RETALIATE); } + TURN { MOVE(player, MOVE_RETALIATE); } + } SCENE { + HP_BAR(opponent, captureDamage: &damage[0]); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[1], Q_4_12(2), damage[0]); + } +} From b641c6f4fa33127052b0a59f1088fc3192f5e011 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Fri, 16 Aug 2024 23:02:49 +0200 Subject: [PATCH 06/16] Fixed incorrect weather damage rounding when maxHP <16 (#5183) Co-authored-by: Hedara --- src/battle_ai_switch_items.c | 2 +- src/battle_ai_util.c | 4 ++-- src/battle_util.c | 6 +++++- test/battle/ability/ice_body.c | 30 +++++++++++++++++++++++++++--- test/battle/ability/rain_dish.c | 18 ++++++++++++++++-- test/battle/weather/hail.c | 12 ++++++++++++ test/battle/weather/sandstorm.c | 12 ++++++++++++ 7 files changed, 75 insertions(+), 9 deletions(-) diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 29642df9ff73..e0359ff00faa 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -1564,9 +1564,9 @@ static u32 GetSwitchinStatusDamage(u32 battler) if ((status & STATUS1_TOXIC_COUNTER) != STATUS1_TOXIC_TURN(15)) // not 16 turns AI_DATA->switchinCandidate.battleMon.status1 += STATUS1_TOXIC_TURN(1); statusDamage = maxHP / 16; - statusDamage *= AI_DATA->switchinCandidate.battleMon.status1 & STATUS1_TOXIC_COUNTER >> 8; if (statusDamage == 0) statusDamage = 1; + statusDamage *= AI_DATA->switchinCandidate.battleMon.status1 & STATUS1_TOXIC_COUNTER >> 8; } } diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index a094dfd694a5..30bce86c3df1 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -2514,7 +2514,7 @@ u32 GetBattlerSecondaryDamage(u32 battlerId) bool32 BattlerWillFaintFromWeather(u32 battler, u32 ability) { if ((BattlerAffectedBySandstorm(battler, ability) || BattlerAffectedByHail(battler, ability)) - && gBattleMons[battler].hp <= gBattleMons[battler].maxHP / 16) + && gBattleMons[battler].hp <= max(1, gBattleMons[battler].maxHP / 16)) return TRUE; return FALSE; @@ -2523,7 +2523,7 @@ bool32 BattlerWillFaintFromWeather(u32 battler, u32 ability) bool32 BattlerWillFaintFromSecondaryDamage(u32 battler, u32 ability) { if (GetBattlerSecondaryDamage(battler) != 0 - && gBattleMons[battler].hp <= gBattleMons[battler].maxHP / 16) + && gBattleMons[battler].hp <= max(1, gBattleMons[battler].maxHP / 16)) return TRUE; return FALSE; } diff --git a/src/battle_util.c b/src/battle_util.c index 0a680cc27250..aefce1f0a920 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -2379,6 +2379,8 @@ u8 DoBattlerEndTurnEffects(void) { gBattleScripting.battler = battler; gBattleMoveDamage = GetNonDynamaxMaxHP(battler) / 16; + if (gBattleMoveDamage == 0) + gBattleMoveDamage = 1; BattleScriptExecute(BattleScript_DamagingWeather); effect++; } @@ -2389,7 +2391,7 @@ u8 DoBattlerEndTurnEffects(void) && !(gStatuses3[battler] & STATUS3_HEAL_BLOCK)) { gBattleScripting.battler = battler; - gBattleMoveDamage = -1 * (GetNonDynamaxMaxHP(battler) / 16); + gBattleMoveDamage = -1 * max(1, GetNonDynamaxMaxHP(battler) / 16); BattleScriptExecute(BattleScript_IceBodyHeal); effect++; } @@ -2403,6 +2405,8 @@ u8 DoBattlerEndTurnEffects(void) { gBattleScripting.battler = battler; gBattleMoveDamage = GetNonDynamaxMaxHP(battler) / 16; + if (gBattleMoveDamage == 0) + gBattleMoveDamage = 1; BattleScriptExecute(BattleScript_DamagingWeather); effect++; } diff --git a/test/battle/ability/ice_body.c b/test/battle/ability/ice_body.c index 304059a00e69..3f278a50cd5e 100644 --- a/test/battle/ability/ice_body.c +++ b/test/battle/ability/ice_body.c @@ -1,13 +1,21 @@ #include "global.h" #include "test/battle.h" +ASSUMPTIONS { + ASSUME(gMovesInfo[MOVE_HAIL].effect == EFFECT_HAIL); + ASSUME(gMovesInfo[MOVE_SNOWSCAPE].effect == EFFECT_SNOWSCAPE); +} + SINGLE_BATTLE_TEST("Ice Body prevents damage from hail") { + u32 move; + PARAMETRIZE { move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_GLALIE) { Ability(ABILITY_ICE_BODY); } } WHEN { - TURN { MOVE(player, MOVE_HAIL); MOVE(opponent, MOVE_SKILL_SWAP); } + TURN { MOVE(player, move); MOVE(opponent, MOVE_SKILL_SWAP); } } SCENE { NONE_OF { HP_BAR(player); } } @@ -15,11 +23,14 @@ SINGLE_BATTLE_TEST("Ice Body prevents damage from hail") SINGLE_BATTLE_TEST("Ice Body recovers 1/16th of Max HP in hail.") { + u32 move; + PARAMETRIZE { move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } GIVEN { PLAYER(SPECIES_GLALIE) { Ability(ABILITY_ICE_BODY); HP(1); MaxHP(100); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(opponent, MOVE_HAIL); } + TURN { MOVE(opponent, move); } } SCENE { ABILITY_POPUP(player, ABILITY_ICE_BODY); HP_BAR(player, damage: -(100 / 16)); @@ -27,4 +38,17 @@ SINGLE_BATTLE_TEST("Ice Body recovers 1/16th of Max HP in hail.") } } -TO_DO_BATTLE_TEST("Sand Rush doesn't recover HP if Cloud Nine/Air Lock is on the field"); +SINGLE_BATTLE_TEST("Ice Body doesn't recover HP if Cloud Nine/Air Lock is on the field") +{ + u32 move; + PARAMETRIZE { move = MOVE_HAIL; } + PARAMETRIZE { move = MOVE_SNOWSCAPE; } + GIVEN { + PLAYER(SPECIES_GLALIE) { Ability(ABILITY_ICE_BODY); HP(1); MaxHP(100); } + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(opponent, move); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ICE_BODY); + } +} diff --git a/test/battle/ability/rain_dish.c b/test/battle/ability/rain_dish.c index ed71a67f9277..93f642c633a2 100644 --- a/test/battle/ability/rain_dish.c +++ b/test/battle/ability/rain_dish.c @@ -1,6 +1,10 @@ #include "global.h" #include "test/battle.h" +ASSUMPTIONS { + ASSUME(gMovesInfo[MOVE_RAIN_DANCE].effect == EFFECT_RAIN_DANCE); +} + SINGLE_BATTLE_TEST("Rain Dish recovers 1/16th of Max HP in Rain") { GIVEN { @@ -11,8 +15,18 @@ SINGLE_BATTLE_TEST("Rain Dish recovers 1/16th of Max HP in Rain") } SCENE { ABILITY_POPUP(player, ABILITY_RAIN_DISH); MESSAGE("Ludicolo's Rain Dish restored its HP a little!"); - HP_BAR(player, damage: -(100 / 16)); + HP_BAR(player, damage: -(100 / 16)); } } -TO_DO_BATTLE_TEST("Rain Dish doesn't recover HP if Cloud Nine/Air Lock is on the field"); +SINGLE_BATTLE_TEST("Rain Dish doesn't recover HP if Cloud Nine/Air Lock is on the field") +{ + GIVEN { + PLAYER(SPECIES_LUDICOLO) { Ability(ABILITY_RAIN_DISH); HP(1); MaxHP(100); } + OPPONENT(SPECIES_GOLDUCK) { Ability(ABILITY_CLOUD_NINE); } + } WHEN { + TURN { MOVE(opponent, MOVE_RAIN_DANCE); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_RAIN_DISH); + } +} diff --git a/test/battle/weather/hail.c b/test/battle/weather/hail.c index 5caeb8489151..4210a22d9ee0 100644 --- a/test/battle/weather/hail.c +++ b/test/battle/weather/hail.c @@ -70,3 +70,15 @@ DOUBLE_BATTLE_TEST("Hail deals damage based on turn order") HP_BAR(playerRight); } } + +SINGLE_BATTLE_TEST("Hail damage rounds properly when maxHP < 16") +{ + GIVEN { + PLAYER(SPECIES_MAGIKARP) { Level(1); MaxHP(11); HP(11); } + OPPONENT(SPECIES_GLALIE); + } WHEN { + TURN { MOVE(opponent, MOVE_HAIL); } + } SCENE { + HP_BAR(player, damage: 1); + } +} diff --git a/test/battle/weather/sandstorm.c b/test/battle/weather/sandstorm.c index 63b24afd7e41..1673c26f999b 100644 --- a/test/battle/weather/sandstorm.c +++ b/test/battle/weather/sandstorm.c @@ -82,3 +82,15 @@ DOUBLE_BATTLE_TEST("Sandstorm deals damage based on turn order") HP_BAR(playerRight); } } + +SINGLE_BATTLE_TEST("Sandstorm damage rounds properly when maxHP < 16") +{ + GIVEN { + PLAYER(SPECIES_MAGIKARP) { Level(1); MaxHP(11); HP(11); } + OPPONENT(SPECIES_SANDSLASH); + } WHEN { + TURN { MOVE(opponent, MOVE_SANDSTORM); } + } SCENE { + HP_BAR(player, damage: 1); + } +} From 3a0c5c9c6cd284a7bbf6f6f49c558f7984098038 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Sat, 17 Aug 2024 18:18:03 +0200 Subject: [PATCH 07/16] Adds missing AI checks for poltergeist (#5189) Adds missing AI checks for poltergeist --- src/battle_ai_util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 30bce86c3df1..08e05e02c1de 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -483,7 +483,7 @@ bool32 IsDamageMoveUnusable(u32 move, u32 battlerAtk, u32 battlerDef) return TRUE; break; case EFFECT_POLTERGEIST: - if (AI_DATA->items[battlerDef] == ITEM_NONE) + if (AI_DATA->items[battlerDef] == ITEM_NONE || gFieldStatuses & STATUS_FIELD_MAGIC_ROOM || battlerDefAbility == ABILITY_KLUTZ) return TRUE; break; case EFFECT_FIRST_TURN_ONLY: From 8607a7fb338d81a3e5692d1aafd0951e7fd603c5 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Sat, 17 Aug 2024 18:19:22 +0200 Subject: [PATCH 08/16] Fixes UB in Cmd_averagestats (#5191) * Fixes UB in Cmd_averagestats * fix test and align default case --- src/battle_script_commands.c | 23 ++++++++++++++++------- test/battle/move_effect/guard_split.c | 22 ++++++++++++++++++++++ test/battle/move_effect/power_split.c | 22 ++++++++++++++++++++++ 3 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 test/battle/move_effect/guard_split.c create mode 100644 test/battle/move_effect/power_split.c diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 394a455b3aa2..9165d7b7e421 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -15669,17 +15669,26 @@ static void Cmd_swapstatstages(void) gBattlescriptCurrInstr = cmd->nextInstr; } +static u16 *GetBattlerStat(struct BattlePokemon *battler, u32 stat) +{ + switch (stat) + { + case STAT_ATK: return &battler->attack; + case STAT_DEF: return &battler->defense; + case STAT_SPATK: return &battler->spAttack; + case STAT_SPDEF: return &battler->spDefense; + default: return NULL; + } +} + static void Cmd_averagestats(void) { CMD_ARGS(u8 stat); - u8 stat = cmd->stat; - u16 atkStat = *(u16 *)((&gBattleMons[gBattlerAttacker].attack) + (stat - 1)); - u16 defStat = *(u16 *)((&gBattleMons[gBattlerTarget].attack) + (stat - 1)); - u16 average = (atkStat + defStat) / 2; - - *(u16 *)((&gBattleMons[gBattlerAttacker].attack) + (stat - 1)) = average; - *(u16 *)((&gBattleMons[gBattlerTarget].attack) + (stat - 1)) = average; + u16 *stat1 = GetBattlerStat(&gBattleMons[gBattlerAttacker], cmd->stat); + u16 *stat2 = GetBattlerStat(&gBattleMons[gBattlerTarget], cmd->stat); + u16 avg = (*stat1 + *stat2) / 2; + *stat1 = *stat2 = avg; gBattlescriptCurrInstr = cmd->nextInstr; } diff --git a/test/battle/move_effect/guard_split.c b/test/battle/move_effect/guard_split.c new file mode 100644 index 000000000000..3f012ab6e2e4 --- /dev/null +++ b/test/battle/move_effect/guard_split.c @@ -0,0 +1,22 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_GUARD_SPLIT].effect == EFFECT_GUARD_SPLIT); +} + +SINGLE_BATTLE_TEST("Guard Split averages users and targets Def and Sp. Def stats") +{ + GIVEN { + PLAYER(SPECIES_BULBASAUR); + OPPONENT(SPECIES_IVYSAUR); + } WHEN { + TURN { MOVE(player, MOVE_GUARD_SPLIT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GUARD_SPLIT, player); + } THEN { + EXPECT_EQ(player->defense, opponent->defense); + EXPECT_EQ(player->spDefense, opponent->spDefense); + } +} diff --git a/test/battle/move_effect/power_split.c b/test/battle/move_effect/power_split.c new file mode 100644 index 000000000000..70d1bfd5ea06 --- /dev/null +++ b/test/battle/move_effect/power_split.c @@ -0,0 +1,22 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_POWER_SPLIT].effect == EFFECT_POWER_SPLIT); +} + +SINGLE_BATTLE_TEST("Power Split averages user and targets Atk and Sp. Atk stats") +{ + GIVEN { + PLAYER(SPECIES_BULBASAUR); + OPPONENT(SPECIES_IVYSAUR); + } WHEN { + TURN { MOVE(player, MOVE_POWER_SPLIT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POWER_SPLIT, player); + } THEN { + EXPECT_EQ(player->attack, opponent->attack); + EXPECT_EQ(player->spAttack, opponent->spAttack); + } +} From a958e6111017d7acb8632a013020fb75958cd5b9 Mon Sep 17 00:00:00 2001 From: kittenchilly Date: Sat, 17 Aug 2024 15:33:11 -0500 Subject: [PATCH 09/16] Fix using Population Dice with Loaded Dice printing garbage text (#5195) --- src/battle_util.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/battle_util.c b/src/battle_util.c index aefce1f0a920..9412737fae1b 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -3641,13 +3641,14 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType) else { gMultiHitCounter = gMovesInfo[gCurrentMove].strikeCount; - PREPARE_BYTE_NUMBER_BUFFER(gBattleScripting.multihitString, 3, 0) if (gMovesInfo[gCurrentMove].effect == EFFECT_DRAGON_DARTS && CanTargetPartner(gBattlerAttacker, gBattlerTarget) && TargetFullyImmuneToCurrMove(gBattlerAttacker, gBattlerTarget)) gBattlerTarget = BATTLE_PARTNER(gBattlerTarget); } + + PREPARE_BYTE_NUMBER_BUFFER(gBattleScripting.multihitString, 3, 0) } else if (B_BEAT_UP >= GEN_5 && gMovesInfo[gCurrentMove].effect == EFFECT_BEAT_UP) { From bc5f40e5182e78ca05c0048b089d1fa75a86597a Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Mon, 19 Aug 2024 14:25:44 +0100 Subject: [PATCH 10/16] Adds missing Wind Rider activation and tests (#5207) * Adds missing Wind Rider activation + tests * Adds test for opponent setting up Tailwind * Update src/battle_util.c Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --------- Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- src/battle_util.c | 13 ++++ test/battle/ability/wind_rider.c | 127 +++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 test/battle/ability/wind_rider.c diff --git a/src/battle_util.c b/src/battle_util.c index 9412737fae1b..f489ea62583b 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4766,6 +4766,19 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 effect++; } break; + case ABILITY_WIND_RIDER: + if (!gSpecialStatuses[battler].switchInAbilityDone + && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN) + && gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_TAILWIND) + { + gBattleScripting.savedBattler = gBattlerAttacker; + gBattlerAttacker = battler; + gSpecialStatuses[battler].switchInAbilityDone = TRUE; + SET_STATCHANGER(STAT_ATK, 1, FALSE); + BattleScriptPushCursorAndCallback(BattleScript_BattlerAbilityStatRaiseOnSwitchIn); + effect++; + } + break; case ABILITY_DESOLATE_LAND: if (TryChangeBattleWeather(battler, ENUM_WEATHER_SUN_PRIMAL, TRUE)) { diff --git a/test/battle/ability/wind_rider.c b/test/battle/ability/wind_rider.c new file mode 100644 index 000000000000..57e6f0d27582 --- /dev/null +++ b/test/battle/ability/wind_rider.c @@ -0,0 +1,127 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_TAILWIND].effect == EFFECT_TAILWIND); + ASSUME(gMovesInfo[MOVE_TAILWIND].windMove == TRUE); +} + +SINGLE_BATTLE_TEST("Wind Rider raises Attack by one stage if it sets up Tailwind") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRAMBLIN) { Ability(ABILITY_WIND_RIDER); } + } WHEN { + TURN { MOVE(opponent, MOVE_TAILWIND); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAILWIND, opponent); + ABILITY_POPUP(opponent, ABILITY_WIND_RIDER); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Bramblin's Attack rose!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} + +DOUBLE_BATTLE_TEST("Wind Rider raises Attack by one stage if Tailwind is setup by its partner") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRAMBLIN) { Ability(ABILITY_WIND_RIDER); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_TAILWIND); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAILWIND, opponentLeft); + ABILITY_POPUP(opponentRight, ABILITY_WIND_RIDER); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("Foe Bramblin's Attack rose!"); + } THEN { + EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Wind Rider doesn't raise Attack if opponent sets up Tailwind") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRAMBLIN) { Ability(ABILITY_WIND_RIDER); } + } WHEN { + TURN { MOVE(player, MOVE_TAILWIND); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAILWIND, player); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_WIND_RIDER); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Bramblin's Attack rose!"); + } + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Wind Rider raises Attack by one stage if switched into Tailwind on its side of the field") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_BRAMBLIN) { Ability(ABILITY_WIND_RIDER); } + } WHEN { + TURN { MOVE(opponent, MOVE_TAILWIND); } + TURN { SWITCH(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAILWIND, opponent); + ABILITY_POPUP(opponent, ABILITY_WIND_RIDER); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Bramblin's Wind Rider raised its Attack!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Wind Rider activates when it's no longer effected by Neutralizing Gas") +{ + GIVEN { + PLAYER(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRAMBLIN) { Ability(ABILITY_WIND_RIDER); } + } WHEN { + TURN { MOVE(opponent, MOVE_TAILWIND); } + TURN { SWITCH(player, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_NEUTRALIZING_GAS); + MESSAGE("Neutralizing Gas filled the area!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAILWIND, opponent); + SWITCH_OUT_MESSAGE("Weezing"); + MESSAGE("The effects of Neutralizing Gas wore off!"); + ABILITY_POPUP(opponent, ABILITY_WIND_RIDER); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Bramblin's Wind Rider raised its Attack!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} + +SINGLE_BATTLE_TEST("Wind Rider absorbs Wind moves and raises Attack by one stage") +{ + ASSUME(gMovesInfo[MOVE_GUST].windMove == TRUE); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRAMBLIN) { Ability(ABILITY_WIND_RIDER); } + } WHEN { + TURN { MOVE(player, MOVE_GUST); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GUST, player); + HP_BAR(opponent); + } + ABILITY_POPUP(opponent, ABILITY_WIND_RIDER); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Bramblin's Attack rose!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + } +} From 3a6c3d580d2ac68a6f92588b5ede7c56b791a13e Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Mon, 19 Aug 2024 19:03:47 +0200 Subject: [PATCH 11/16] Round Tests (#5196) * Round Tests * Apply suggestions from code review Co-authored-by: Eduardo Quezada --------- Co-authored-by: Hedara Co-authored-by: Eduardo Quezada --- test/battle/move_effect/round.c | 115 ++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 test/battle/move_effect/round.c diff --git a/test/battle/move_effect/round.c b/test/battle/move_effect/round.c new file mode 100644 index 000000000000..c911c96b6327 --- /dev/null +++ b/test/battle/move_effect/round.c @@ -0,0 +1,115 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_ROUND].effect == EFFECT_ROUND); +} + +DOUBLE_BATTLE_TEST("Round allows other battlers which also selected the moves to immediately use the move, ignoring turn order") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LAGGING_TAIL].holdEffect == HOLD_EFFECT_LAGGING_TAIL); + ASSUME(gMovesInfo[MOVE_IRON_HEAD].additionalEffects[0].moveEffect == MOVE_EFFECT_FLINCH); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LAGGING_TAIL); } + } WHEN { + TURN { + MOVE(playerRight, MOVE_CELEBRATE); + MOVE(opponentLeft, MOVE_ROUND, target: playerLeft); + MOVE(playerLeft, MOVE_IRON_HEAD, target: opponentRight); + MOVE(opponentRight, MOVE_ROUND, target: playerLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponentLeft); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_IRON_HEAD, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_IRON_HEAD, playerLeft); + } +} + +DOUBLE_BATTLE_TEST("Round usages beyond the first one has double base power") +{ + s16 damage[2]; + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_ROUND, target: playerLeft); + MOVE(opponentRight, MOVE_ROUND, target: playerLeft); + } + } SCENE { + HP_BAR(playerLeft, captureDamage: &damage[0]); + HP_BAR(playerLeft, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[0], Q_4_12(2.0), damage[1]); + } +} + +DOUBLE_BATTLE_TEST("Round still preserves the turn order outside of the other Round users moving immediately") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_ROUND, target: playerLeft); + MOVE(playerRight, MOVE_CELEBRATE); + MOVE(playerLeft, MOVE_CELEBRATE); + MOVE(opponentRight, MOVE_ROUND, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft); + } +} +DOUBLE_BATTLE_TEST("Round still preserves the turn order outside of the other Round users moving immediately with switch") +{ + KNOWN_FAILING; // #5148 + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + SWITCH(playerRight, 2); + MOVE(opponentLeft, MOVE_ROUND, target: playerLeft); + MOVE(playerLeft, MOVE_CELEBRATE); + MOVE(opponentRight, MOVE_ROUND, target: playerLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft); + } +} + +DOUBLE_BATTLE_TEST("Round causes opposing pokemon to use Round immediately") +{ + GIVEN { + ASSUME(gItemsInfo[ITEM_LAGGING_TAIL].holdEffect == HOLD_EFFECT_LAGGING_TAIL); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LAGGING_TAIL); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_CELEBRATE); MOVE(playerRight, MOVE_ROUND, target: opponentLeft); MOVE(playerLeft, MOVE_CELEBRATE, target: opponentRight); MOVE(opponentRight, MOVE_ROUND, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, playerRight); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROUND, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft); + } +} From a11f03ed9d9cdf31b291144b7c2e16f0573bc049 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Mon, 19 Aug 2024 18:25:39 -0400 Subject: [PATCH 12/16] Version 1.9.1 (#5199) * Version 1.9.1 * Added 5195 * Apply suggestions from code review Co-authored-by: hedara90 <90hedara@gmail.com> * Added 5196 and 5207 --------- Co-authored-by: hedara90 <90hedara@gmail.com> --- .../ISSUE_TEMPLATE/01_battle_engine_bugs.yaml | 3 +- .../ISSUE_TEMPLATE/02_battle_ai_issues.yaml | 3 +- .github/ISSUE_TEMPLATE/04_other_errors.yaml | 3 +- CHANGELOG.md | 1 + README.md | 2 +- docs/SUMMARY.md | 8 +- docs/changelogs/1.9.x/1.9.1.md | 140 ++++++++++++++++++ docs/changelogs/template.md | 5 +- include/constants/expansion.h | 4 +- 9 files changed, 158 insertions(+), 11 deletions(-) create mode 100644 docs/changelogs/1.9.x/1.9.1.md diff --git a/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml b/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml index 310f92c11874..edebc1bbf0a4 100644 --- a/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml +++ b/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml @@ -23,9 +23,10 @@ body: label: Version description: What version of pokeemerald-expansion are you using as a base? options: - - 1.9.0 (Latest release) + - 1.9.1 (Latest release) - master (default, unreleased bugfixes) - upcoming (Edge) + - 1.9.0 - 1.8.6 - 1.8.5 - 1.8.4 diff --git a/.github/ISSUE_TEMPLATE/02_battle_ai_issues.yaml b/.github/ISSUE_TEMPLATE/02_battle_ai_issues.yaml index 9fcb1595a27a..dd3024230be5 100644 --- a/.github/ISSUE_TEMPLATE/02_battle_ai_issues.yaml +++ b/.github/ISSUE_TEMPLATE/02_battle_ai_issues.yaml @@ -23,9 +23,10 @@ body: label: Version description: What version of pokeemerald-expansion are you using as a base? options: - - 1.9.0 (Latest release) + - 1.9.1 (Latest release) - master (default, unreleased bugfixes) - upcoming (Edge) + - 1.9.0 - 1.8.6 - 1.8.5 - 1.8.4 diff --git a/.github/ISSUE_TEMPLATE/04_other_errors.yaml b/.github/ISSUE_TEMPLATE/04_other_errors.yaml index 73d9fd11d31f..29960c267b88 100644 --- a/.github/ISSUE_TEMPLATE/04_other_errors.yaml +++ b/.github/ISSUE_TEMPLATE/04_other_errors.yaml @@ -23,9 +23,10 @@ body: label: Version description: What version of pokeemerald-expansion are you using as a base? options: - - 1.9.0 (Latest release) + - 1.9.1 (Latest release) - master (default, unreleased bugfixes) - upcoming (Edge) + - 1.9.0 - 1.8.6 - 1.8.5 - 1.8.4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0999ae5f7ed2..fb2f38af1d7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Pokeemerald-Expansion Changelogs ## 1.9.x +- **[Version 1.9.1](docs/changelogs/1.9.x/1.9.1.md) - 🧹 Bugfix Release** - **[Version 1.9.0](docs/changelogs/1.9.x/1.9.0.md) - ✨ Feature Release** ## 1.8.x diff --git a/README.md b/README.md index 3a87ac27f62c..f09f5a7d9099 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ pokeemerald-expansion is a decomp hack base project based off pret's [pokeemeral If you use pokeemerald-expansion in your hack, please add RHH (Rom Hacking Hideout) to your credits list. Optionally, you can list the version used, so it can help players know what features to expect. You can phrase it as the following: ``` -Based off RHH's pokeemerald-expansion 1.9.0 https://github.com/rh-hideout/pokeemerald-expansion/ +Based off RHH's pokeemerald-expansion 1.9.1 https://github.com/rh-hideout/pokeemerald-expansion/ ``` ## What features are included? diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 0e065d018e71..603fa225db6f 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -12,10 +12,10 @@ - [How to add a new move](./how_to_new_move.md) - [How to add a new trainer class](./how_to_trainer_class.md) - [How to add a new Pokémon]() - - [v1.9.0](./how_to_new_pokemon_1_9_0.md) - - [v1.8.0](./how_to_new_pokemon_1_8_0.md) - - [v1.7.0](./how_to_new_pokemon_1_7_0.md) - - [v1.6.0](./how_to_new_pokemon_1_6_0.md) + - [v1.9.x](./how_to_new_pokemon_1_9_0.md) + - [v1.8.x](./how_to_new_pokemon_1_8_0.md) + - [v1.7.x](./how_to_new_pokemon_1_7_0.md) + - [v1.6.x](./how_to_new_pokemon_1_6_0.md) - [How to use the Testing System](./how_to_testing_system.md) - [Changelog](./CHANGELOG.md) - [1.9.x]() diff --git a/docs/changelogs/1.9.x/1.9.1.md b/docs/changelogs/1.9.x/1.9.1.md new file mode 100644 index 000000000000..8393727c2aa3 --- /dev/null +++ b/docs/changelogs/1.9.x/1.9.1.md @@ -0,0 +1,140 @@ +# Version 1.9.1 + +```md +## How to update +- If you haven't set up a remote, run the command `git remote add RHH https://github.com/rh-hideout/pokeemerald-expansion`. +- Once you have your remote set up, run the command `git pull RHH expansion/1.9.1`. +``` + +## 🌋 *REFACTORS* 🌋 +* Removed `ENDTURN_RETALIATE` in [#5182](https://github.com/rh-hideout/pokeemerald-expansion/pull/5182) +* Removed `ENDTURN_WEATHER_FORM` and `allowedToChangeFormInWeather` in [#5171](https://github.com/rh-hideout/pokeemerald-expansion/pull/5171) + +## 🧬 General 🧬 +### Added +* Move Relearner UI now displays move category by @kittenchilly in [#5081](https://github.com/rh-hideout/pokeemerald-expansion/pull/5081) +### Fixed +* Fixes wrong padding field in `SpeciesInfo` struct by @AlexOn1ine in [#5139](https://github.com/rh-hideout/pokeemerald-expansion/pull/5139) +* Fixed specific tiles changing to PC tiles when using Box Link/Debug PC option by @cawtds in [#5141](https://github.com/rh-hideout/pokeemerald-expansion/pull/5141) + +## 🐉 Pokémon 🐉 +### Fixed +* Fixed stray transparent pixels in Urshifu sprites by @hedara90 in [#5071](https://github.com/rh-hideout/pokeemerald-expansion/pull/5071) +* Fixed `bufferspeciesname` not working for species IDs over 1023 by @SBird1337 in [#5088](https://github.com/rh-hideout/pokeemerald-expansion/pull/5088) +* Fixed overworld Pokémon breaking for species IDs above 1535 by @hedara90, @mrgriffin and +@SarnPoke in [#5179](https://github.com/rh-hideout/pokeemerald-expansion/pull/5179) +* Fixed overworld palettes for multiple species by @hedara90 in [#5107](https://github.com/rh-hideout/pokeemerald-expansion/pull/5107) + * Dialga Origin (Normal and Shiny) + * Palkia Origin (Normal and Shiny) + * Giratina Origin (shiny palette fixed by @hedara90 in [#5108](https://github.com/rh-hideout/pokeemerald-expansion/pull/5108)) + * Xerneas Neutral/Active (Normal and Shiny) + * Enamorus Incarnate/Therian (Normal and Shiny) +* Fixed/added missing Pokémon sprites and palettes by @Cafeei in [#5126](https://github.com/rh-hideout/pokeemerald-expansion/pull/5126) + * Overworld: + * Shiny Sneasler, Morelul, Bounsweet, Bruxish, Guzzlord, Regieleki, Zacian, Zamazenta + * Hisuian Zorua sprite + * Shiny Summer Sawsbuck + * Shiny Galarian Yamask, Darumaka, Zigzagoon, Zapdos, Ponyta, Rapidash, Slowpoke, Farfetch'd, Weezing, Mr. Mime, Articuno, Moltres, Slowking, Stunfisk, Darmanitan + * Shiny Hisuian Sneasel, Qwilfish, Samurott, + * Battle sprites: + * Shiny Sneasler, Cursola, Pincurchin, Runerigus + * Shiny Galarian Yamask, Darumaka +* Fixed Unown Overworld follower sprites by Sarn by @hedara90 in [#5146](https://github.com/rh-hideout/pokeemerald-expansion/pull/5146) + +## ⚔️ Battle General ⚔️ +### Changed +* Set new animation particles by default to off by @AlexOn1ine in [#5161](https://github.com/rh-hideout/pokeemerald-expansion/pull/5161) +### Fixed +* Fixed speed ties by @mrgriffin in [#4780](https://github.com/rh-hideout/pokeemerald-expansion/pull/4780) + * Cleanup by @hedara90 in [#5092](https://github.com/rh-hideout/pokeemerald-expansion/pull/5092) +* Fixed Defiant/Competitive not working after the battler enters the field with a Court Changed Sticky Web on its side of the field by @PhallenTree in [#5093](https://github.com/rh-hideout/pokeemerald-expansion/pull/5093) +* Fixed `trainerproc` not properly parsing line markers, which caused erroring lines to be offset by @mrgriffin in [#5122](https://github.com/rh-hideout/pokeemerald-expansion/pull/5122) +* Fixed initial Zigzagoon battle being able to use a Gimmick by @AlexOn1ine in [#5129](https://github.com/rh-hideout/pokeemerald-expansion/pull/5129) +* Fixed incorrect rounding when `maxHP` is lower than 16 by @hedara90 in [#5183](https://github.com/rh-hideout/pokeemerald-expansion/pull/5183) + * This caused these Pokémon to not be hurt by Sandstorm/Hail. +* Fixes UB in `Cmd_averagestats` by @mrgriffin and @AlexOn1ine in [#5191](https://github.com/rh-hideout/pokeemerald-expansion/pull/5191) + +## 🤹 Moves 🤹 +### Added +* Added move animations for multiple moves by @TheTrueSadfish in [#5159](https://github.com/rh-hideout/pokeemerald-expansion/pull/5159) + * Spin Out, Mortal Spin, Fillet Away, Flower Trick, Make It Rain, Shed Tail, Hyper Drill, Twin Beam, Comeuppance, Blood Moon, Fickle Beam, Thunder Clap, Hard Press, Dragon Cheer, Malignant Chain. + * Purple chains by ogwon on Discord, beam by @TheTrueSadfish and livra on Discord. +### Changed +* Adjusted Raging Bull's animation to include Brick Break's wall break effect by @TheTrueSadfish in [#5159](https://github.com/rh-hideout/pokeemerald-expansion/pull/5159) +### Fixed +* Fixed non-grass Ivy Cudgel breaking battle UI by @hedara90 in [#5117](https://github.com/rh-hideout/pokeemerald-expansion/pull/5117) +* Fixes Stomping Tantrum effect not doubling power in certain situations by @AlexOn1ine in [#5140](https://github.com/rh-hideout/pokeemerald-expansion/pull/5140) +* Fixed Fickle Beam's description by @PhallenTree in [#5093](https://github.com/rh-hideout/pokeemerald-expansion/pull/5093) +* Fixed Revelation Dance interactions with Z-Move, Roost and typeless mons by @PhallenTree in [#5133](https://github.com/rh-hideout/pokeemerald-expansion/pull/5133) +* Fixes Poltergeist missing its accuracy check by @AlexOn1ine in [#5168](https://github.com/rh-hideout/pokeemerald-expansion/pull/5168) +* Fixed Fickle Beam not showing its message by @TheTrueSadfish in [#5159](https://github.com/rh-hideout/pokeemerald-expansion/pull/5159) +* Fixed Retaliate not working correctly if the party member fainted via passive damage during end of turn by @hedara90 in [#5182](https://github.com/rh-hideout/pokeemerald-expansion/pull/5182) +* Fixed Flame Burst's passive damage being based off current HP rather than Max HP by @hedara90 in [#5182](https://github.com/rh-hideout/pokeemerald-expansion/pull/5182) +* Fixed using Population Bomb with Loaded Dice printing garbage text by @kittenchilly in [#5195](https://github.com/rh-hideout/pokeemerald-expansion/pull/5195) + +## 🎭 Abilities 🎭 +### Added +* Added in-battle effect of Pickup by @PhallenTree in [#5170](https://github.com/rh-hideout/pokeemerald-expansion/pull/5170) +### Fixed +* Fixes Purifying Salt not halving damage for dynamic move types by @AlexOn1ine in [#5145](https://github.com/rh-hideout/pokeemerald-expansion/pull/5145) +* Fixed Dancer-called moves not changing their type based on the new user by @PhallenTree in [#5133](https://github.com/rh-hideout/pokeemerald-expansion/pull/5133) +* Fixed Ice Face not regenerating after switching in during Hail/Snow by @hedara90 in [#5171](https://github.com/rh-hideout/pokeemerald-expansion/pull/5171) +* Fixed Wind Rider not activating when switched in while Tailwind is active on the user's side of the field activation and tests by @PhallenTree in https://github.com/rh-hideout/pokeemerald-expansion/pull/5207 + +## 🧶 Items 🧶 +### Added +* Added Dowsing Machine's expanded name by @kittenchilly in [#5134](https://github.com/rh-hideout/pokeemerald-expansion/pull/5134) +### Fixed +* Fixes Booster Energy not increasing speed by @AlexOn1ine in [#5167](https://github.com/rh-hideout/pokeemerald-expansion/pull/5167) + +## 🤖 Battle AI 🤖 +### Changed +* Adjusted AI calculation for Triple Kick Effect by @AlexOn1ine in [#5127](https://github.com/rh-hideout/pokeemerald-expansion/pull/5127) +### Fixed +* Fix Switch AI Bug: AI never switching out when it could be OHKO'd by @Pawkkie in [#5089](https://github.com/rh-hideout/pokeemerald-expansion/pull/5089) +* Adds missing AI checks for poltergeist by @AlexOn1ine in [#5189](https://github.com/rh-hideout/pokeemerald-expansion/pull/5189) + +## 🧹 Other Cleanup 🧹 +* `IsValidForBattle` function formatting by @AlexOn1ine in [#5085](https://github.com/rh-hideout/pokeemerald-expansion/pull/5085) +* Opportunist/Mirror Herb cleanup by @AlexOn1ine in [#5120](https://github.com/rh-hideout/pokeemerald-expansion/pull/5120) + * Cleanup by @AlexOn1ine in [#5158](https://github.com/rh-hideout/pokeemerald-expansion/pull/5158) +* Remove trailing whitespace (master) by @AsparagusEduardo in [#5174](https://github.com/rh-hideout/pokeemerald-expansion/pull/5174) + +## 🧪 Test Runner 🧪 +### Added +* Added missing Move Effect TODO tests - Volume C by @AsparagusEduardo in [#5094](https://github.com/rh-hideout/pokeemerald-expansion/pull/5094) +* Added multiple missing ability TODO tests by @AsparagusEduardo in [#5163](https://github.com/rh-hideout/pokeemerald-expansion/pull/5163) +* Added missing Guard/Power split tests by @mrgriffin and @AlexOn1ine in [#5191](https://github.com/rh-hideout/pokeemerald-expansion/pull/5191) +* Added missing Harvest and Pickup tests by @PhallenTree in [#5170](https://github.com/rh-hideout/pokeemerald-expansion/pull/5170) +* Added missing Round tests by @hedara90 in https://github.com/rh-hideout/pokeemerald-expansion/pull/5196 +* Added missing Wind Rider tests by @PhallenTree in https://github.com/rh-hideout/pokeemerald-expansion/pull/5207 +### Changed +* Fixed G-Max Replenish not considering Gen 5+ Pickup by @PhallenTree in [#5170](https://github.com/rh-hideout/pokeemerald-expansion/pull/5170) +### Fixed +* Fixed `RandomUniformExcept` not being exclusive on the higher boundary by @PhallenTree in [#5170](https://github.com/rh-hideout/pokeemerald-expansion/pull/5170) + + +## 📚 Documentation 📚 +* Added guide to running documentation website locally by @AsparagusEduardo in [#5059](https://github.com/rh-hideout/pokeemerald-expansion/pull/5059) +* How to docs and fixes to be added to the mdbook documentation site by @anrichtait in [#5070](https://github.com/rh-hideout/pokeemerald-expansion/pull/5070) +* Improved 1.8 ⇒ 1.9 non-Competitive syntax migration instructions by @mrgriffin in [#5079](https://github.com/rh-hideout/pokeemerald-expansion/pull/5079) + +## 📦 Branch Synchronisation 📦 +### pret +* 5th of August in [#5098](https://github.com/rh-hideout/pokeemerald-expansion/pull/5098) + * Fixed bottom half of Mt. Pyre not being labeled in PokeNav by @fdeblasio in [pret#2018](https://github.com/pret/pokeemerald/pull/2018) +* 7th of August in [#5116](https://github.com/rh-hideout/pokeemerald-expansion/pull/5116) + * Changed type1 and type2 to be consistent by @pkmnsnfrn in [pret#2021](https://github.com/pret/pokeemerald/pull/2021) +* 14th of August in [#5165](https://github.com/rh-hideout/pokeemerald-expansion/pull/5165) + * Fix type for offset in MapConnection by @GriffinRichards in [pret#2011](https://github.com/pret/pokeemerald/pull/2011) +### Followers +* 7th of August in [#5110](https://github.com/rh-hideout/pokeemerald-expansion/pull/5110) + * Fixed expanded OW IDs by @pkmnsnfrn in [aarant#38](https://github.com/aarant/pokeemerald/pull/38) + * Fix two small text errors in follower dialogue by @Bassoonian in [aarant#39](https://github.com/aarant/pokeemerald/pull/39) + +**Full Changelog**: https://github.com/rh-hideout/pokeemerald-expansion/compare/expansion/1.9.0...expansion/1.9.1 + +## New Contributors +* @TheTrueSadfish made their first contribution in https://github.com/rh-hideout/pokeemerald-expansion/pull/5159 + + diff --git a/docs/changelogs/template.md b/docs/changelogs/template.md index 6641d612f36b..a40afebad35c 100644 --- a/docs/changelogs/template.md +++ b/docs/changelogs/template.md @@ -122,7 +122,10 @@ ### Fixed * N/A -## 📦 Pret merges 📦 +## 📦 Branch Synchronisation 📦 +### pret's base pokeemerald +* N/A +### merrp/aarant's followers * N/A diff --git a/include/constants/expansion.h b/include/constants/expansion.h index ad79d584cbec..9fc3a6fe73d3 100644 --- a/include/constants/expansion.h +++ b/include/constants/expansion.h @@ -1,13 +1,13 @@ #ifndef GUARD_CONSTANTS_EXPANSION_H #define GUARD_CONSTANTS_EXPANSION_H -// 1.9.0 +// 1.9.1 #define EXPANSION_VERSION_MAJOR 1 #define EXPANSION_VERSION_MINOR 9 #define EXPANSION_VERSION_PATCH 1 // FALSE if this this version of Expansion is not a tagged commit, i.e. // it contains unreleased changes. -#define EXPANSION_TAGGED_RELEASE FALSE +#define EXPANSION_TAGGED_RELEASE TRUE #endif From becb2fcac839a36df868297a8945ec51f32123ad Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Mon, 19 Aug 2024 18:29:48 -0400 Subject: [PATCH 13/16] Start 1.9.2 cycle --- include/constants/expansion.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/constants/expansion.h b/include/constants/expansion.h index 9fc3a6fe73d3..caf7e4ddab2d 100644 --- a/include/constants/expansion.h +++ b/include/constants/expansion.h @@ -4,10 +4,10 @@ // 1.9.1 #define EXPANSION_VERSION_MAJOR 1 #define EXPANSION_VERSION_MINOR 9 -#define EXPANSION_VERSION_PATCH 1 +#define EXPANSION_VERSION_PATCH 2 // FALSE if this this version of Expansion is not a tagged commit, i.e. // it contains unreleased changes. -#define EXPANSION_TAGGED_RELEASE TRUE +#define EXPANSION_TAGGED_RELEASE FALSE #endif From a111ac496d08d580bcb83998a0e0a678acec53d6 Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Tue, 20 Aug 2024 06:44:27 -0400 Subject: [PATCH 14/16] Fix typos (#5221) Fix typos in move description of Decorate, Collision Course and Electro Drift --- src/data/moves_info.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/data/moves_info.h b/src/data/moves_info.h index bec17632ec89..08579f274f72 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -17470,7 +17470,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .name = COMPOUND_STRING("Decorate"), .description = COMPOUND_STRING( "The user sharply raises\n" - "the target's Atk and Sp.Atk"), + "the target's Atk and Sp.Atk."), .effect = EFFECT_DECORATE, .power = 0, .type = TYPE_FAIRY, @@ -19739,7 +19739,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .name = HANDLE_EXPANDED_MOVE_NAME("ColisinCours", "Collision Course"), .description = COMPOUND_STRING( "Prehistoric explosion that's\n" - "stronger if supereffective."), + "stronger if super effective."), .effect = EFFECT_COLLISION_COURSE, .power = 100, .type = TYPE_FIGHTING, @@ -19758,7 +19758,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .name = HANDLE_EXPANDED_MOVE_NAME("ElectroDrift", "Electro Drift"), .description = COMPOUND_STRING( "Futuristic electricity. It's\n" - "stronger if supereffective."), + "stronger if super effective."), .effect = EFFECT_COLLISION_COURSE, .power = 100, .type = TYPE_ELECTRIC, From af12697845f83b2a9984556af436c9668a938c5a Mon Sep 17 00:00:00 2001 From: psf <77138753+pkmnsnfrn@users.noreply.github.com> Date: Tue, 20 Aug 2024 04:23:33 -0700 Subject: [PATCH 15/16] Adds OW_BERRY_IMMORTAL (#5187) * Added OW_BERRY_IMMORTAL Added cases for OW_BERRY_IMMORTAL * removed one preproc * removed other preproc * Fixed identation * Set config to FALSE * Update include/config/overworld.h per https://github.com/rh-hideout/pokeemerald-expansion/pull/5187#discussion_r1720747388 Co-authored-by: Bassoonian * Reordered condition per https://github.com/rh-hideout/pokeemerald-expansion/pull/5187\#discussion_r1720747652 * Update include/config/overworld.h --------- Co-authored-by: Bassoonian --- include/config/overworld.h | 1 + src/berry.c | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/include/config/overworld.h b/include/config/overworld.h index b47cef5c5082..63479f18317c 100644 --- a/include/config/overworld.h +++ b/include/config/overworld.h @@ -33,6 +33,7 @@ #define OW_BERRY_GROWTH_RATE GEN_3 // Presets for how long each Berry plant takes to grow. #define OW_BERRY_YIELD_RATE GEN_3 // Presets for how many Berries each plant can yield. #define OW_BERRY_DRAIN_RATE GEN_6_ORAS // If OW_BERRY_MOISTURE is enabled, this setting changes how fast the soil dries out. GEN_4 uses a Berry-dependent drain rate, GEN_6_XY dries out in 24 hours (4 hours with the relevant Mulch) and GEN_6_ORAS dries out in 4 hours. Other values are illegal. +#define OW_BERRY_IMMORTAL FALSE // If enabled, once a Berry tree has grown a Berry, the tree will not disappear until picked by the player. // Overworld Pokémon #define OW_POKEMON_OBJECT_EVENTS TRUE // Adds Object Event fields for every species. Can be used for NPCs using the OBJ_EVENT_GFX_SPECIES macro (eg. OBJ_EVENT_GFX_SPECIES(BULBASAUR)) diff --git a/src/berry.c b/src/berry.c index 9f187bd1e239..1a1c3b7f1f6b 100644 --- a/src/berry.c +++ b/src/berry.c @@ -1817,6 +1817,8 @@ bool32 BerryTreeGrow(struct BerryTree *tree) tree->stage = BERRY_STAGE_BERRIES; break; case BERRY_STAGE_BERRIES: + if (OW_BERRY_IMMORTAL) + break; tree->watered = 0; tree->berryYield = 0; tree->stage = BERRY_STAGE_SPROUTED; @@ -1842,16 +1844,16 @@ static u16 GetMulchAffectedGrowthRate(u16 berryDuration, u8 mulch, u8 stage) void BerryTreeTimeUpdate(s32 minutes) { int i; - u8 drainVal; + u32 drainVal; struct BerryTree *tree; for (i = 0; i < BERRY_TREES_COUNT; i++) { tree = &gSaveBlock1Ptr->berryTrees[i]; - if (tree->berry && tree->stage && !tree->stopGrowth) + if (tree->berry && tree->stage && !tree->stopGrowth && (!OW_BERRY_IMMORTAL || tree->stage != BERRY_STAGE_BERRIES)) { - if (minutes >= GetStageDurationByBerryType(tree->berry) * 71) + if ((!OW_BERRY_IMMORTAL) && (minutes >= GetStageDurationByBerryType(tree->berry) * 71)) { *tree = gBlankBerryTree; } From bde69828909cfe2edbda93a0065fc836e95b1cbf Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Tue, 20 Aug 2024 13:24:48 +0200 Subject: [PATCH 16/16] =?UTF-8?q?Fixes=20weather=20abilities=20not=20activ?= =?UTF-8?q?ating=20when=20Cloud=20Nine=20user=20leaves=20th=E2=80=A6=20(#5?= =?UTF-8?q?209)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixes weather abilities not activating when Cloud Nine user leaves the field * parametrize --- data/battle_scripts_1.s | 4 ++-- src/battle_script_commands.c | 29 +++++++++++++++++++++-------- src/battle_util.c | 13 +++++++------ test/battle/ability/forecast.c | 22 ++++++++++++++++++++++ 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 95fffd05137c..083143bdf243 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -7150,7 +7150,7 @@ BattleScript_TargetFormChangeWithStringNoPopup:: BattleScript_BattlerFormChangeWithStringEnd3:: pause 5 - call BattleScript_AbilityPopUp + call BattleScript_AbilityPopUpScripting flushtextbox handleformchange BS_SCRIPTING, 0 handleformchange BS_SCRIPTING, 1 @@ -8010,7 +8010,7 @@ BattleScript_DeltaStreamActivates:: end3 BattleScript_ProtosynthesisActivates:: - call BattleScript_AbilityPopUp + call BattleScript_AbilityPopUpScripting printstring STRINGID_SUNLIGHTACTIVATEDABILITY waitmessage B_WAIT_TIME_MED printstring STRINGID_STATWASHEIGHTENED diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 9165d7b7e421..efa841a631cc 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -7292,19 +7292,32 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler) gDisableStructs[battler].truantSwitchInHack = 0; - for (i = 0; i < gBattlersCount; i++) - { - if (i != battler - && GetBattlerAbility(i) == ABILITY_TRACE - && AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, i, 0, 0, 0)) - return TRUE; - } - if (DoSwitchInAbilities(battler) || ItemBattleEffects(ITEMEFFECT_ON_SWITCH_IN, battler, FALSE)) return TRUE; else if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, 0, 0, 0)) return TRUE; + for (i = 0; i < gBattlersCount; i++) + { + if (i == battler) + continue; + + switch (GetBattlerAbility(i)) + { + case ABILITY_TRACE: + if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, i, 0, 0, 0)) + return TRUE; + break; + case ABILITY_FORECAST: + case ABILITY_FLOWER_GIFT: + case ABILITY_ICE_FACE: + case ABILITY_PROTOSYNTHESIS: + if (AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, i, 0, 0, 0)) + return TRUE; + break; + } + } + gDisableStructs[battler].stickyWebDone = FALSE; gDisableStructs[battler].spikesDone = FALSE; gDisableStructs[battler].toxicSpikesDone = FALSE; diff --git a/src/battle_util.c b/src/battle_util.c index f489ea62583b..b133b7c62953 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4767,7 +4767,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITY_WIND_RIDER: - if (!gSpecialStatuses[battler].switchInAbilityDone + if (!gSpecialStatuses[battler].switchInAbilityDone && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN) && gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_TAILWIND) { @@ -6269,17 +6269,17 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITYEFFECT_ON_WEATHER: // For ability effects that activate when the battle weather changes. - battler = gBattlerAbility = gBattleScripting.battler; gLastUsedAbility = GetBattlerAbility(battler); switch (gLastUsedAbility) { case ABILITY_FORECAST: case ABILITY_FLOWER_GIFT: if ((IsBattlerWeatherAffected(battler, gBattleWeather) - || gBattleWeather == B_WEATHER_NONE - || !WEATHER_HAS_EFFECT) // Air Lock active - && TryBattleFormChange(battler, FORM_CHANGE_BATTLE_WEATHER)) + || gBattleWeather == B_WEATHER_NONE + || !WEATHER_HAS_EFFECT) // Air Lock active + && TryBattleFormChange(battler, FORM_CHANGE_BATTLE_WEATHER)) { + gBattleScripting.battler = battler; BattleScriptPushCursorAndCallback(BattleScript_BattlerFormChangeWithStringEnd3); effect++; } @@ -6290,6 +6290,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && !(gBattleMons[battler].status2 & STATUS2_TRANSFORMED)) { // TODO: Convert this to a proper FORM_CHANGE type. + gBattleScripting.battler = battler; gBattleMons[battler].species = SPECIES_EISCUE_ICE_FACE; BattleScriptPushCursorAndCallback(BattleScript_BattlerFormChangeWithStringEnd3); effect++; @@ -6300,7 +6301,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 { gDisableStructs[battler].weatherAbilityDone = TRUE; PREPARE_STAT_BUFFER(gBattleTextBuff1, GetHighestStatId(battler)); - gBattlerAbility = gBattleScripting.battler = battler; + gBattleScripting.battler = battler; BattleScriptPushCursorAndCallback(BattleScript_ProtosynthesisActivates); effect++; } diff --git a/test/battle/ability/forecast.c b/test/battle/ability/forecast.c index 296c123dbd6e..24a0eed95779 100644 --- a/test/battle/ability/forecast.c +++ b/test/battle/ability/forecast.c @@ -397,3 +397,25 @@ SINGLE_BATTLE_TEST("Forecast transforms Castform back when it uses a move that f EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), SPECIES_CASTFORM); } } + +SINGLE_BATTLE_TEST("Forecast transforms Castform when Cloud Nine ability user leaves the field") +{ + u32 species = 0, ability = 0; + PARAMETRIZE { species = SPECIES_PSYDUCK; ability = ABILITY_CLOUD_NINE; } + PARAMETRIZE { species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } + + GIVEN { + PLAYER(SPECIES_CASTFORM) { Ability(ABILITY_FORECAST); } + OPPONENT(species) { Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); MOVE(opponent, MOVE_CELEBRATE); } + TURN { SWITCH(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + MESSAGE("2 sent out Wobbuffet!"); + ABILITY_POPUP(player, ABILITY_FORECAST); + MESSAGE("Castform transformed!"); + } +}