diff --git a/.all-contributorsrc b/.all-contributorsrc index f185d54866..ee9a9c49f7 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -606,6 +606,33 @@ "contributions": [ "code" ] + }, + { + "login": "Kasenn", + "name": "Kasenn", + "avatar_url": "https://avatars.githubusercontent.com/u/115586266?v=4", + "profile": "https://github.com/Kasenn", + "contributions": [ + "code" + ] + }, + { + "login": "SabataLunar", + "name": "SabataLunar", + "avatar_url": "https://avatars.githubusercontent.com/u/26584469?v=4", + "profile": "https://github.com/SabataLunar", + "contributions": [ + "design" + ] + }, + { + "login": "PacFire", + "name": "PacFire", + "avatar_url": "https://avatars.githubusercontent.com/u/108960850?v=4", + "profile": "https://github.com/PacFire", + "contributions": [ + "design" + ] } ], "contributorsPerLine": 7, diff --git a/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml b/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml index a8e9c9326a..0c918c334b 100644 --- a/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml +++ b/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml @@ -7,7 +7,7 @@ body: value: | Please fill in all fields with as many details as possible. Once your bug is posted, make sure you and your collaborators are added to `CREDITS.md` by [tagging the bot on GitHub](https://github.com/rh-hideout/pokeemerald-expansion/wiki/CREDITS.md-Frequently-Asked-Questions). EVERY contribution matters, even reporting bugs! - - type: textarea + - type: textarea id: description attributes: label: Description @@ -24,13 +24,13 @@ body: description: | What exact steps can somebody else follow in order to recreate the issue on their own? placeholder: | - Provide as much context as possible as to what was done to create the issue. + Provide as much context as possible as to what was done to create the issue. validations: required: true - type: textarea - id: media + id: media attributes: - label: Images / Video + label: Images / Video description: | Do you have images or videos to show the problem happen? placeholder: | diff --git a/.github/ISSUE_TEMPLATE/02_battle_ai_issues.yaml b/.github/ISSUE_TEMPLATE/02_battle_ai_issues.yaml index e6762925b4..b172a867aa 100644 --- a/.github/ISSUE_TEMPLATE/02_battle_ai_issues.yaml +++ b/.github/ISSUE_TEMPLATE/02_battle_ai_issues.yaml @@ -24,13 +24,13 @@ body: description: | What exact steps can somebody else follow in order to recreate the issue on their own? placeholder: | - Provide as much context as possible as to what was done to create the issue. + Provide as much context as possible as to what was done to create the issue. validations: - required: true + required: true - type: textarea - id: media + id: media attributes: - label: Images / Video + label: Images / Video description: | Do you have images or videos to show the problem happen? placeholder: | diff --git a/.github/ISSUE_TEMPLATE/03_feature_requests.yaml b/.github/ISSUE_TEMPLATE/03_feature_requests.yaml index 347e782660..e58227b112 100644 --- a/.github/ISSUE_TEMPLATE/03_feature_requests.yaml +++ b/.github/ISSUE_TEMPLATE/03_feature_requests.yaml @@ -15,13 +15,13 @@ body: description: | What is the current behavior? What behavior would you expect your feature request to provide? What other information can you provide to help your feature get implemented? placeholder: | - Provide as much context as possible. + Provide as much context as possible. validations: required: true - type: textarea id: media attributes: - label: Images / Video + label: Images / Video description: | Have other projects or games solved this problem? Do you have images or video to show this happening? placeholder: | diff --git a/.github/ISSUE_TEMPLATE/04_other_errors.yaml b/.github/ISSUE_TEMPLATE/04_other_errors.yaml index 8fa4e94564..c69e2c1052 100644 --- a/.github/ISSUE_TEMPLATE/04_other_errors.yaml +++ b/.github/ISSUE_TEMPLATE/04_other_errors.yaml @@ -24,13 +24,13 @@ body: description: | What exact steps can somebody else follow in order to recreate the issue on their own? placeholder: | - Provide as much context as possible as to what was done to create the issue. + Provide as much context as possible as to what was done to create the issue. validations: - required: false + required: false - type: textarea - id: media + id: media attributes: - label: Images / Video + label: Images / Video description: | Do you have images or videos to show the problem happen? placeholder: | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 49517cfff3..06c302c4c8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ All contributions are encouraged and valued. Please make sure to read the releva ## Bug Reports -We use [GitHub](https://github.com/rh-hideout/pokeemerald-expansion/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen+label%3Abug) issues to track bugs. +We use [GitHub](https://github.com/rh-hideout/pokeemerald-expansion/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen+label%3Abug) issues to track bugs. ### What should I do before making a bug report? @@ -15,7 +15,7 @@ We use [GitHub](https://github.com/rh-hideout/pokeemerald-expansion/issues?q=sor ### How do I submit a bug report? -If you run into an issue with the project, open an [issue](https://github.com/rh-hideout/pokeemerald-expansion/issues/new). +If you run into an issue with the project, open an [issue](https://github.com/rh-hideout/pokeemerald-expansion/issues/new). The best bug reports have enough information that we won't have to contact you for more information. We welcome all efforts to improve pokeemerald-expansion, but would be very grateful if you completed as much of the checklist as possible in your bug report. This will help other contributiors fix your issue. @@ -31,7 +31,7 @@ The best bug reports have enough information that we won't have to contact you f This section guides you through submitting a feature request for pokeemerald-expansion, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions. -- We use [GitHub](https://github.com/rh-hideout/pokeemerald-expansion/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen+label%3Afeature-request) issues to track feature requests. +- We use [GitHub](https://github.com/rh-hideout/pokeemerald-expansion/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen+label%3Afeature-request) issues to track feature requests. ### What should I do before making a feature request? @@ -44,7 +44,7 @@ This section guides you through submitting a feature request for pokeemerald-exp ### How do I submit a feature request? -To request a feature to be added to the project, open a [feature request](https://github.com/rh-hideout/pokeemerald-expansion/issues/new). +To request a feature to be added to the project, open a [feature request](https://github.com/rh-hideout/pokeemerald-expansion/issues/new). ### What happens after I submit a feature request? @@ -108,11 +108,11 @@ git push --set-upstream origin newFeature ``` #### 7. Open Pull Request -Once your work is complete and pushed to the branch on Github, you can open a [pull request from your branch](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork), targeting the branch you've chosen from `pokeemerald-expansion`. Please fill out the pull request description as completely as possible. +Once your work is complete and pushed to the branch on Github, you can open a [pull request from your branch](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork), targeting the branch you've chosen from `pokeemerald-expansion`. Please fill out the pull request description as completely as possible. ### What happens after I submit a pull request? -A maintainer will then assign themselves as a reviewer of your pull request, and may provide feedback in the form of a PR review. +A maintainer will then assign themselves as a reviewer of your pull request, and may provide feedback in the form of a PR review. Contributors are responsible for responding to and updating their branch by addressing the feedback in the review. Contributors are also responsible for making sure the branch passes the checklist at all times. diff --git a/CREDITS.md b/CREDITS.md index da5324b2b8..10786eff5d 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -86,6 +86,11 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Kildemal
Kildemal

💻 Skeli
Skeli

🎨 Josh Hufford
Josh Hufford

💻 + Kasenn
Kasenn

💻 + + + SabataLunar
SabataLunar

🎨 + PacFire
PacFire

🎨 diff --git a/README.md b/README.md index c74b22fa29..c9a538c49a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ -**`pokeemerald-expansion`** is a GBA ROM hack base that equips developers with a comprehensive toolkit for creating Pokémon ROM hacks. **`pokeemerald-expansion`** is built on top of [pret's `pokeemerald`](https://github.com/pret/pokeemerald) decompilation project. **It is not a playable Pokémon game on its own.** +**`pokeemerald-expansion`** is a GBA ROM hack base that equips developers with a comprehensive toolkit for creating Pokémon ROM hacks. **`pokeemerald-expansion`** is built on top of [pret's `pokeemerald`](https://github.com/pret/pokeemerald) decompilation project. **It is not a playable Pokémon game on its own.** # [Features](FEATURES.md) @@ -30,7 +30,7 @@ Please consider [crediting all contributors](CREDITS.md) involved in the project # [Getting Started](INSTALL.md) -❗❗ **Important**: Do not use GitHub's "Download Zip" option as it will not include commit history. This is necessary if you want to update or merge other feature branches. +❗❗ **Important**: Do not use GitHub's "Download Zip" option as it will not include commit history. This is necessary if you want to update or merge other feature branches. If you're new to git and GitHub, [Team Aqua's Asset Repo](https://github.com/Pawkkie/Team-Aquas-Asset-Repo/) has a [guide to forking and cloning the repository](https://github.com/Pawkkie/Team-Aquas-Asset-Repo/wiki/The-Basics-of-GitHub). Then you can follow one of the following guides: diff --git a/asm/macros/m4a.inc b/asm/macros/m4a.inc index 0169a3c033..f405206aca 100644 --- a/asm/macros/m4a.inc +++ b/asm/macros/m4a.inc @@ -15,7 +15,7 @@ .macro voice_group label:req, starting_note .align 2 .ifb \starting_note - .global voicegroup_\label + .global voicegroup_\label voicegroup_\label: .else .set voicegroup_\label, . - \starting_note * 0xC diff --git a/audio_rules.mk b/audio_rules.mk index 70d4466409..808f51a61c 100644 --- a/audio_rules.mk +++ b/audio_rules.mk @@ -9,7 +9,7 @@ SOUND_BIN_DIR := sound # Needs to recompile for B_NUM_LOW_HEALTH_BEEPS in battle.h EXPANSION_BATTLE_CONFIG := include/config/battle.h -SPECIAL_OUTDIRS := $(MID_ASM_DIR) $(CRY_BIN_DIR) +SPECIAL_OUTDIRS := $(MID_ASM_DIR) $(CRY_BIN_DIR) SPECIAL_OUTDIRS += $(SOUND_BIN_DIR) $(SOUND_BIN_DIR)/direct_sound_samples/phonemes $(SOUND_BIN_DIR)/direct_sound_samples/cries $(shell mkdir -p $(SPECIAL_OUTDIRS) ) @@ -25,11 +25,11 @@ $(CRY_BIN_DIR)/%.bin: $(CRY_SUBDIR)/%.wav $(WAV2AGB) -b -c -l 1 --no-pad $< $@ # Uncompressed cries -$(CRY_BIN_DIR)/uncomp_%.bin: $(CRY_SUBDIR)/uncomp_%.aif +$(CRY_BIN_DIR)/uncomp_%.bin: $(CRY_SUBDIR)/uncomp_%.aif $(AIF) $< $@ # Uncompressed sounds -$(SOUND_BIN_DIR)/%.bin: sound/%.wav +$(SOUND_BIN_DIR)/%.bin: sound/%.wav $(WAV2AGB) -b $< $@ # For each line in midi.cfg, we do some trickery to convert it into a make rule for the `.mid` file described on the line diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index e4a934a053..97ce317380 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -1024,24 +1024,59 @@ BattleScript_VCreateStatLossRet: BattleScript_EffectPartingShot:: attackcanceler jumpifstat BS_TARGET, CMP_GREATER_THAN, STAT_ATK, MIN_STAT_STAGE, BattleScript_EffectPartingShotTryAtk - jumpifstat BS_TARGET, CMP_EQUAL, STAT_SPATK, MIN_STAT_STAGE, BattleScript_CantLowerMultipleStats + jumpifstat BS_TARGET, CMP_EQUAL, STAT_SPATK, MIN_STAT_STAGE, BattleScript_EffectPartingShotCantLowerMultipleStats BattleScript_EffectPartingShotTryAtk: accuracycheck BattleScript_MoveMissedPause attackanimation waitanimation + setbyte sB_ANIM_TARGETS_HIT, 0 setstatchanger STAT_ATK, 1, TRUE statbuffchange BS_TARGET, STAT_CHANGE_ALLOW_PTR, BattleScript_EffectPartingShotTrySpAtk, BIT_SPATK - printfromtable gStatDownStringIds - waitmessage B_WAIT_TIME_LONG + call BattleScript_EffectPartingShotMaybePrintStat BattleScript_EffectPartingShotTrySpAtk: setstatchanger STAT_SPATK, 1, TRUE - statbuffchange BS_TARGET, STAT_CHANGE_ALLOW_PTR, BattleScript_EffectPartingShotSwitch - printfromtable gStatDownStringIds - waitmessage B_WAIT_TIME_LONG + statbuffchange BS_TARGET, STAT_CHANGE_ALLOW_PTR, BattleScript_EffectPartingShotMaybeSwitch + call BattleScript_EffectPartingShotMaybePrintStat +BattleScript_EffectPartingShotMaybeSwitch: + jumpifgenconfiglowerthan CONFIG_PARTING_SHOT_SWITCH, GEN_7, BattleScript_EffectPartingShotSwitch + jumpifbyte CMP_NOT_EQUAL, sB_ANIM_TARGETS_HIT, 0, BattleScript_EffectPartingShotSwitch + goto BattleScript_MoveEnd + BattleScript_EffectPartingShotSwitch: moveendall goto BattleScript_MoveSwitchPursuitEnd +BattleScript_EffectPartingShotCantLowerMultipleStats: + pause B_WAIT_TIME_SHORT + setmoveresultflags MOVE_RESULT_FAILED + call BattleScript_EffectPartingShotPrintWontDecrease + setbyte sB_ANIM_TARGETS_HIT, 0 + goto BattleScript_EffectPartingShotMaybeSwitch + +BattleScript_EffectPartingShotMaybePrintStat: + jumpifbyte CMP_EQUAL, cMULTISTRING_CHOOSER, B_MSG_ATTACKER_STAT_CHANGED, BattleScript_EffectPartingShotPrintStat + jumpifbyte CMP_EQUAL, cMULTISTRING_CHOOSER, B_MSG_DEFENDER_STAT_CHANGED, BattleScript_EffectPartingShotPrintStat + return + +BattleScript_EffectPartingShotPrintStat: + setbyte sB_ANIM_TARGETS_HIT, 1 + printfromtable gStatDownStringIds + waitmessage B_WAIT_TIME_LONG + return + +BattleScript_EffectPartingShotPrintWontDecrease: + jumpifability BS_TARGET, ABILITY_CONTRARY, BattleScript_EffectPartingShotPrintWontDecreaseContrary + printstring STRINGID_STATSWONTDECREASE2 + waitmessage B_WAIT_TIME_LONG + return + +BattleScript_EffectPartingShotPrintWontDecreaseContrary: + swapattackerwithtarget + printstring STRINGID_STATSWONTDECREASE2 + waitmessage B_WAIT_TIME_LONG + swapattackerwithtarget + return + BattleScript_EffectPowder:: attackcanceler jumpifvolatile BS_TARGET, VOLATILE_POWDER, BattleScript_ButItFailed @@ -1076,6 +1111,7 @@ BattleScript_EffectAromaticMistWontGoHigher: BattleScript_EffectMagneticFlux:: attackcanceler + savetarget setbyte gBattleCommunication, 0 BattleScript_EffectMagneticFluxStart: jumpifability BS_TARGET, ABILITY_MINUS, BattleScript_EffectMagneticFluxCheckStats @@ -1104,8 +1140,10 @@ BattleScript_EffectMagneticFluxTrySpDef: waitmessage B_WAIT_TIME_LONG BattleScript_EffectMagneticFluxLoop: jumpifbytenotequal gBattlerTarget, gBattlerAttacker, BattleScript_EffectMagneticFluxEnd + jumpifnoally BS_ATTACKER, BattleScript_EffectMagneticFluxEnd setallytonexttarget BattleScript_EffectMagneticFluxStart BattleScript_EffectMagneticFluxEnd: + restoretarget jumpifbyte CMP_NOT_EQUAL, gBattleCommunication, 0, BattleScript_MoveEnd goto BattleScript_ButItFailed @@ -1140,6 +1178,7 @@ BattleScript_EffectGearUpTrySpAtk: waitmessage B_WAIT_TIME_LONG BattleScript_EffectGearUpLoop: jumpifbytenotequal gBattlerTarget, gBattlerAttacker, BattleScript_EffectGearUpEnd + jumpifnoally BS_ATTACKER, BattleScript_EffectGearUpEnd setallytonexttarget BattleScript_EffectGearUpStart BattleScript_EffectGearUpEnd: restoretarget @@ -2148,6 +2187,9 @@ BattleScript_TryTailwindAbilitiesLoop_WindPower: BattleScript_EffectMiracleEye:: attackcanceler accuracycheck BattleScript_MoveMissedPause + jumpifgenconfiglowerthan CONFIG_MIRACLE_EYE_FAIL, GEN_5, BattleScript_MiracleEyeSet + jumpifvolatile BS_TARGET, VOLATILE_MIRACLE_EYE, BattleScript_ButItFailed +BattleScript_MiracleEyeSet: setvolatile BS_TARGET, VOLATILE_MIRACLE_EYE goto BattleScript_IdentifiedFoe @@ -3210,7 +3252,12 @@ BattleScript_EffectSpikes:: BattleScript_EffectForesight:: attackcanceler + accuracycheck BattleScript_ButItFailed + jumpifgenconfiglowerthan CONFIG_FORESIGHT_FAIL, GEN_3, BattleScript_ForesightFailCheck + jumpifgenconfiglowerthan CONFIG_FORESIGHT_FAIL, GEN_5, BattleScript_ForesightSet +BattleScript_ForesightFailCheck: jumpifvolatile BS_TARGET, VOLATILE_FORESIGHT, BattleScript_ButItFailed +BattleScript_ForesightSet: setvolatile BS_TARGET, VOLATILE_FORESIGHT BattleScript_IdentifiedFoe: attackanimation diff --git a/docs/STYLEGUIDE.md b/docs/STYLEGUIDE.md index 52bdf97de6..c320444de4 100644 --- a/docs/STYLEGUIDE.md +++ b/docs/STYLEGUIDE.md @@ -64,7 +64,7 @@ When describing a system/component in-depth, use block comment syntax. ``` When briefly describing a function or block of code, use a single-line comments -placed on its own line. +placed on its own line. There should be a single space directly to the right of `//`. ```c @@ -453,7 +453,7 @@ All other configs should be off. ### Save Philosophy -Until [save migration](https://discord.com/channels/419213663107416084/1108733346864963746) is implemented, branches will only merged in if they do not forcefully break existing game saves. +Until [save migration](https://discord.com/channels/419213663107416084/1108733346864963746) is implemented, branches will only merged in if they do not forcefully break existing game saves. When `pokemeerald-expansion` gets to a point where new functionality will require that we break saves, we will merge as many [save-breaking features](https://discord.com/channels/419213663107416084/1202774957776441427) together as possible, and increment the major version number of the project. diff --git a/docs/changelogs/1.12.x/1.12.2.md b/docs/changelogs/1.12.x/1.12.2.md index 9f1b210638..229eff250b 100644 --- a/docs/changelogs/1.12.x/1.12.2.md +++ b/docs/changelogs/1.12.x/1.12.2.md @@ -53,8 +53,8 @@ * Fixed text width for a lot of forms in HGSS Dex by @AsparagusEduardo in [#7035](https://github.com/rh-hideout/pokeemerald-expansion/pull/7035) * Fixes Roamers not saving shininess by @i0brendan0 in [#7185](https://github.com/rh-hideout/pokeemerald-expansion/pull/7185) * [FIX] Prevent caught Pokémon loss in NPC partner battles by @J2M2 in [#7177](https://github.com/rh-hideout/pokeemerald-expansion/pull/7177) - - + + ## ⚔️ Battle General ⚔️ ### Changed @@ -72,8 +72,8 @@ * Chloroblast causing recoil damage even if the move fails to connect by @LinathanZel in #7007 - - + + * [DRAFT] Fix Normalize not boosting Normal type moves if they were already Normal type by @i0brendan0 in [#7060](https://github.com/rh-hideout/pokeemerald-expansion/pull/7060) * Fixes freeze during a 1v2 double battle by @AlexOn1ine in [#7075](https://github.com/rh-hideout/pokeemerald-expansion/pull/7075) * Fixes Pursuit potentially causing both battlers to switch into the same mon by @PhallenTree in [#7084](https://github.com/rh-hideout/pokeemerald-expansion/pull/7084) @@ -93,8 +93,8 @@ ## 🤹 Moves 🤹 ### Changed * Fix ScaryFace anim for Bitter Malice by @TLM-PsIQ in [#6476](https://github.com/rh-hideout/pokeemerald-expansion/pull/6476) - - + + ### Fixed * Fix savage spin out spider web template by @ghoulslash in [#7137](https://github.com/rh-hideout/pokeemerald-expansion/pull/7137) diff --git a/docs/changelogs/1.14.x/1.14.0.md b/docs/changelogs/1.14.x/1.14.0.md index 3a75d35e6e..3e38d0a30a 100644 --- a/docs/changelogs/1.14.x/1.14.0.md +++ b/docs/changelogs/1.14.x/1.14.0.md @@ -32,7 +32,7 @@ * Add new actions to Debug Menu by @FosterProgramming in [#7837](https://github.com/rh-hideout/pokeemerald-expansion/pull/7837) - Adds an action to change a Pokemon ability in the party side of the Debug Menu - Adds an action to set the friendship of a Pokemon in the party side of the Debug Menu - + * Display TM/HM's move name in the debug menu by @estellarc in [#7994](https://github.com/rh-hideout/pokeemerald-expansion/pull/7994) ### Changed diff --git a/docs/changelogs/1.9.x/1.9.2.md b/docs/changelogs/1.9.x/1.9.2.md index 4d95fc95f9..18e9f63468 100644 --- a/docs/changelogs/1.9.x/1.9.2.md +++ b/docs/changelogs/1.9.x/1.9.2.md @@ -121,7 +121,7 @@ * Fixed message for switch out moves by @kittenchilly in [#5258](https://github.com/rh-hideout/pokeemerald-expansion/pull/5258) * Fixed Ice Fang's descriptions using the opposite of what they're supposed to do based on `B_USE_FROSTBITE` by @laserXdolphin in [#5273](https://github.com/rh-hideout/pokeemerald-expansion/pull/5273) * Fixes to Instruct by @PhallenTree in [#5262](https://github.com/rh-hideout/pokeemerald-expansion/pull/5262) - * Fixed Instruct bypassing AtkCanceler checks (Instruct allowed the target to act while asleep, flinched, etc.) and its interaction with First Turn Only moves (Fake Out, First Impression, Mat Block). + * Fixed Instruct bypassing AtkCanceler checks (Instruct allowed the target to act while asleep, flinched, etc.) and its interaction with First Turn Only moves (Fake Out, First Impression, Mat Block). * Fixed Instruct's animation using the attacker and target of the called move. * Fixed Scale Shot's effect not activating if the opponent fainted before all hits finished by @AlexOn1ine in [#5292](https://github.com/rh-hideout/pokeemerald-expansion/pull/5292) * Fixed Round not preserving turn order for non-Round users if there's a switch out at the beginning of the turn by @AlexOn1ine in [#5292](https://github.com/rh-hideout/pokeemerald-expansion/pull/5292) diff --git a/docs/team_procedures/expansion_versions.md b/docs/team_procedures/expansion_versions.md index 5c5cfabf00..4d328c796e 100644 --- a/docs/team_procedures/expansion_versions.md +++ b/docs/team_procedures/expansion_versions.md @@ -4,7 +4,7 @@ ## 1.- Autogenerating a changelog for the `master` branch. *Requires Write access to the repo.* - + If the changelog you're making is for a minor version (Eg. 1.3.0), make sure to sync the `upcoming` branch with `master` before starting. Keep in mind that if there are unreleased changes in `master`, they should be made into a patch version released alongside minor version. - Go to https://github.com/rh-hideout/pokeemerald-expansion/releases. - Press the option "Draft a new release". @@ -178,7 +178,7 @@ With this, the repo is ready again to receive new PRs. - Requires role to post in #announcements channel. - [Team Aqua's Hideout](https://discord.gg/team-aqua-s-hideout-976252009114140682) - Requires role to post in #romhacking-updates channel. - - [What a Hack!](https://discord.gg/whack-a-hack-292436944670162955) + - [What a Hack!](https://discord.gg/whack-a-hack-292436944670162955) - Announcements are done in Spanish, but not the changelogs themselves. - Requires role to ping "Decompilaciones" role. - [pret](https://discord.gg/R4c3FA95dP) diff --git a/docs/team_procedures/merge_checklist.md b/docs/team_procedures/merge_checklist.md index 53e8d7683f..fb47bd5a25 100644 --- a/docs/team_procedures/merge_checklist.md +++ b/docs/team_procedures/merge_checklist.md @@ -18,7 +18,7 @@ Contributors are asked to make sure tests pass locally, but maintainers should a ## Have you verified that the functionality works in game without any problems? If functionality cannot be verified with an automated test, proof of an in game test is required. Do not be afraid to reach out to the contributor or the community to make sure something works in game as it should. -## If the branch ports behavior from another Pokémon game, have you verified that the behavior functions as faithfully as possible? +## If the branch ports behavior from another Pokémon game, have you verified that the behavior functions as faithfully as possible? We have always tried to make sure we can mimic the original functionality as closely as possible so as to avoid confusion with users and players. Do not be afraid to ask the contributor / community for proof if you cannot personally verify. ## If the branch is a popular feature within the community with an established feature branch, is this using that established branch as a base? @@ -28,7 +28,7 @@ There are situations where this should and should not happen, and should be disc Not everything needs a migration script - if you're unsure, start a discussion. ## Should new functionality introduced by this branch be gated behind a config? -We don't have a strict definition of when configs should be used, but you can start with +We don't have a strict definition of when configs should be used, but you can start with > Why SHOULDN'T this be a config? diff --git a/docs/team_procedures/schedule.md b/docs/team_procedures/schedule.md index 520c4af000..6c948a8b3a 100644 --- a/docs/team_procedures/schedule.md +++ b/docs/team_procedures/schedule.md @@ -13,10 +13,10 @@ PRs with the Github label [`type: big feature`](https://github.com/rh-hideout/po ### Merge Freeze (14 days to the next Minor Release) Pull Requests that **DO NOT** have one of the following Github labels: -- [`bugfix`](https://github.com/rh-hideout/pokeemerald-expansion/issues?q=sort%3Aupdated-desc+label%3Abugfix) -- [`type: cleanup`](https://github.com/rh-hideout/pokeemerald-expansion/issues?q=sort%3Aupdated-desc+label%3A%22type%3A+cleanup%22+) -- [`type: credits`](https://github.com/rh-hideout/pokeemerald-expansion/issues?q=sort%3Aupdated-desc+label%3A%22type%3A+credits%22+) -- [`type: documentation`](https://github.com/rh-hideout/pokeemerald-expansion/issues?q=sort%3Aupdated-desc+label%3A%22type%3A+documentation%22+) +- [`bugfix`](https://github.com/rh-hideout/pokeemerald-expansion/issues?q=sort%3Aupdated-desc+label%3Abugfix) +- [`type: cleanup`](https://github.com/rh-hideout/pokeemerald-expansion/issues?q=sort%3Aupdated-desc+label%3A%22type%3A+cleanup%22+) +- [`type: credits`](https://github.com/rh-hideout/pokeemerald-expansion/issues?q=sort%3Aupdated-desc+label%3A%22type%3A+credits%22+) +- [`type: documentation`](https://github.com/rh-hideout/pokeemerald-expansion/issues?q=sort%3Aupdated-desc+label%3A%22type%3A+documentation%22+) - [`category: battle-tests`](https://github.com/rh-hideout/pokeemerald-expansion/issues?q=sort%3Aupdated-desc+is%3Aopen+label%3A%22category%3A+battle-tests%22) will NOT be merged until after the next Minor Release. @@ -36,7 +36,7 @@ will NOT be merged until after the next Minor Release. ## What is a "Big Feature"? * If the original owner of the PR thinks a feature should be labeled a Big Feature, it is, no questions asked * If a reviewer thinks a PR is a Big Feature, then it is -* If the two disagree, it can be discussed in a PR thread, and can ultimately be resolved with a Maintainer vote. +* If the two disagree, it can be discussed in a PR thread, and can ultimately be resolved with a Maintainer vote. ### How To Identify a Big Feature * **Big diffs**: It's easy for something to go unnoticed in review when it's a tiny part of a massive diff. diff --git a/docs/team_procedures/scope.md b/docs/team_procedures/scope.md index 70739686fc..0f90117dae 100644 --- a/docs/team_procedures/scope.md +++ b/docs/team_procedures/scope.md @@ -21,8 +21,11 @@ A pull request meets the scope criteria if: 1. **SS Species**: Adds Species that have appeared in a Showdown-supported title. Includes follower sprites for all defined species including battle-only ones (ie. Megas) 2. **SS Moves**: Adds Moves and Move Animations that have appeared in a Showdown-supported title 3. **SS Abilities**: Adds Abilities that have appeared in a Showdown-supported title -4. **SS Items**: Adds Items that have appeared in a Showdown-supported title -5. **SS Gimmicks**: Adds Gimmicks that have appeared in a Showdown-supported title (Dynamax, Mega Evolution, etc.) +4. **SS Items**: Items that meet ALL the following requirements are in scope: + - Has appeared in a Showdown supported title + - Is mechanically / functionally unique from existing items (ie. not Relic Crown, Silver Leaf), with the exception of items with in-battle functionality (ie. Lumiose Gallette) + - Do not ONLY exist for story related purpose (ie. Jade Orb) +5. **SS Gimmicks**: Adds Gimmicks that have appeared in a Showdown-supported title (Dynamax, Mega Evolution, etc.) 6. **SS Battle Types**: Adds Special Battle Types that have appeared in a Showdown-supported title (Triple battles, etc.) 7. **SS Battle Mechanics**: Adds mechanical battle changes that have appeared in a Showdown-supported title, and allow developers to choose which generation suits them where applicable 8. **Battle AI Behaviour**: Improvements towards the capability of a human competitive player, and unique or interesting behaviours otherwise diff --git a/docs/tutorials/ai_flags.md b/docs/tutorials/ai_flags.md index 502e84395f..b373a3f078 100644 --- a/docs/tutorials/ai_flags.md +++ b/docs/tutorials/ai_flags.md @@ -29,13 +29,13 @@ This section lists all of expansion’s AI Flags and briefly describes the effec ## Composite AI Flags -Expansion has a few "composite" AI flags. This means that these flags have no unique functionality themselves, and can instead be thought of as groups of other flags that are all enabled when this flag is enabled. The idea behind these flags is that if you don't care to manage the detailed behaviour of a particular trainer, you can use these as a baseline instead, and expansion will keep them updated for you. +Expansion has a few "composite" AI flags. This means that these flags have no unique functionality themselves, and can instead be thought of as groups of other flags that are all enabled when this flag is enabled. The idea behind these flags is that if you don't care to manage the detailed behaviour of a particular trainer, you can use these as a baseline instead, and expansion will keep them updated for you. `AI_FLAG_BASIC_TRAINER` is expansion's version of generic, normal AI behaviour. It includes `AI_FLAG_CHECK_BAD_MOVE` (don't use bad moves), `AI_FLAG_TRY_TO_FAINT` (faint the player where possible), and `AI_FLAG_CHECK_VIABILITY` (choose the most effective move to use in the current context). Trainers with this flag will still be smarter than they are in vanilla as there have been dramatic improvements made to move selection, but not incredibly so. Trainers with this flag should feel like normal trainers. In general we recommend these three flags be used in all cases, unless you specifically want a trainer who makes obvious mistakes in battle. `AI_FLAG_SMART_TRAINER` is expansion's version of a "smart AI". It includes everything in `AI_FLAG_BASIC_TRAINER` along with `AI_FLAG_SMART_SWITCHING` (make smart decisions about when to switch), `AI_FLAG_SMART_MON_CHOICES` (make smart decisions about what mon to send in after a switch / KO), `AI_FLAG_OMNISCIENT` (awareness of what moves, items, and abilities the player's mons have to better inform decisions), and `AI_FLAG_SMART_TERA` (make smart decisions about when to terastalize). Expansion will keep this updated to represent the most objectively intelligent behaviour our flags are capable of producing. -`AI_FLAG_PREDICTION` will enable all of the prediction flags at once, so the AI can perform as well as possible. It is best paired with the flags in `AI_FLAG_SMART_TRAINER` for optimal behaviour. This currently includes `AI_FLAG_PREDICT_SWITCH` and `AI_FLAG_PREDICT_INCOMING_MON`, but will likely be expanded in the future. +`AI_FLAG_PREDICTION` will enable all of the prediction flags at once, so the AI can perform as well as possible. It is best paired with the flags in `AI_FLAG_SMART_TRAINER` for optimal behaviour. This currently includes `AI_FLAG_PREDICT_SWITCH` and `AI_FLAG_PREDICT_INCOMING_MON`, but will likely be expanded in the future. Expansion has LOADS of flags, which will be covered in the rest of this guide. If you don't want to engage with detailed trainer AI tuning though, you can just use these two composite flags, and trust that expansion will keep their contents updated to always represent the most standard and the smartest behaviour we can. diff --git a/docs/tutorials/dns.md b/docs/tutorials/dns.md index 6fdd1c1d77..498d62caa5 100644 --- a/docs/tutorials/dns.md +++ b/docs/tutorials/dns.md @@ -12,7 +12,7 @@ If you intend to use vanilla maps and have not already edited them, revert commi If you _have_ edited vanilla maps, the merge conflicts from reverting that commit will cause problems. If you are using vanilla maps, manually copy some of the tileset changes, `.pal`, and `.pla` files in your branch, and begin rebuilding your metatiles to have windows use the palettes that have a `.pla` for light blending the correct color slots. [Triple-layer metatiles](https://github.com/pret/pokeemerald/wiki/Triple-layer-metatiles) are highly recommended. -WARNING: [As per issue #7034](https://github.com/rh-hideout/pokeemerald-expansion/issues/7034) if you follow this tutorial reverting the previously mentioned commit to use the updated palettes in the Hoenn maps and *after* that you try to follow the [Triple-layer metatiles tutorial](https://github.com/pret/pokeemerald/wiki/Triple-layer-metatiles), you'll encounter an issue when running the script to update old tilesets to support triple-layer metatiles. +WARNING: [As per issue #7034](https://github.com/rh-hideout/pokeemerald-expansion/issues/7034) if you follow this tutorial reverting the previously mentioned commit to use the updated palettes in the Hoenn maps and *after* that you try to follow the [Triple-layer metatiles tutorial](https://github.com/pret/pokeemerald/wiki/Triple-layer-metatiles), you'll encounter an issue when running the script to update old tilesets to support triple-layer metatiles. Follow the band-aid fix proposed in that issue after this tutorial but before following the triple-layer metatiles tutorial if you want everythign to work properly with light-blended palettes and triple-layer metatiles together. You will also want to add the lighting object events from that commit. diff --git a/docs/tutorials/how_to_code_entry.md b/docs/tutorials/how_to_code_entry.md index 51fe0d6f7d..8558d60a9c 100644 --- a/docs/tutorials/how_to_code_entry.md +++ b/docs/tutorials/how_to_code_entry.md @@ -293,7 +293,7 @@ MysteryGift_EventScript_Celebi:: end ``` -Walking through this, it's clear we'll need some more scripting. We first check if Celebi's corresponding Mystery Gift flag has been set, and if it has, we need to tell the player they've already redeemed it and can't again. If they haven't though, we get ourselves setup for the givemon, do the givemon, and set the mystery gift flag. Then we need soem more generic handling to prompt nicknaming and some fanfare. +Walking through this, it's clear we'll need some more scripting. We first check if Celebi's corresponding Mystery Gift flag has been set, and if it has, we need to tell the player they've already redeemed it and can't again. If they haven't though, we get ourselves setup for the givemon, do the givemon, and set the mystery gift flag. Then we need soem more generic handling to prompt nicknaming and some fanfare. Two things, then; an event script to handle the case where a mystery gift mon has already been redeemed, and an event script to handle when a mystery gift mon has successfully been received. @@ -327,13 +327,13 @@ Almost done! Just need to handle the specific nicknaming scripts, and then add a MysteryGift_EventScript_NicknamePartyMon:: msgbox gText_NicknameThisPokemon, MSGBOX_YESNO goto_if_eq VAR_RESULT, NO, MysteryGift_EventScript_Exit - call Common_EventScript_GetGiftMonPartySlot - call Common_EventScript_NameReceivedPartyMon + call Common_EventScript_GetGiftMonPartySlot + call Common_EventScript_NameReceivedPartyMon goto MysteryGift_EventScript_Exit end MysteryGift_EventScript_NicknamePCMon:: - msgbox gText_NicknameThisPokemon, MSGBOX_YESNO + msgbox gText_NicknameThisPokemon, MSGBOX_YESNO goto_if_eq VAR_RESULT, NO, MysteryGift_EventScript_TransferredToPC call Common_EventScript_NameReceivedBoxMon call Common_EventScript_TransferredToPC diff --git a/docs/tutorials/how_to_follower_npc.md b/docs/tutorials/how_to_follower_npc.md index fbd0087859..87bf31ceb9 100644 --- a/docs/tutorials/how_to_follower_npc.md +++ b/docs/tutorials/how_to_follower_npc.md @@ -1,5 +1,5 @@ # How to Use Follower NPCs -*Written by Bivurnum* +*Written by Bivurnum* *gif by ghoulslash* ![follower-npc](img/follower_npc/follower-npc.gif) @@ -11,22 +11,22 @@ The configs for follower NPCs can be found in [include/config/follower_npc.h](ht * `FNPC_FLAG_HEAL_AFTER_FOLLOWER_BATTLE`: The player's party can be automatically healed after every partner battle. Set it to a flag to toggle it on/off with scripts, or set it to `FNPC_ALWAYS` to have it happen every time. * `FNPC_FLAG_PARTNER_WILD_BATTLES`: The battle partner can join the player for wild battles. Set it to a flag to toggle it on/off with scripts, or set it to `FNPC_ALWAYS` to have it happen every time. * `FNPC_NPC_FOLLOWER_WILD_BATTLE_VS_2`: Wild battles with a battle partner default to two wild Pokémon appearing. You can set this to `FALSE` to make only one wild Pokémon appear. -* `FNPC_NPC_FOLLOWER_PARTY_PREVIEW`: By default, a preview of the player's and partner's teams will be shown at the start of every trainer battle. Set this to `FALSE` to disable this feature. +* `FNPC_NPC_FOLLOWER_PARTY_PREVIEW`: By default, a preview of the player's and partner's teams will be shown at the start of every trainer battle. Set this to `FALSE` to disable this feature. * `FNPC_FACE_NPC_FOLLOWER_ON_DOOR_EXIT`: If `TRUE` the player will turn to face the follower when they exit a doorway. * `FNPC_NPC_FOLLOWER_SHOW_AFTER_LEAVE_ROUTE`: If `TRUE` the follower will walk out of the player automatically after using Fly, Teleport, or Escape Rope. ## Set the Follower The `setfollowernpc` macro will turn the specified object into an NPC follower. It requires the object id, the [follower flags](#follower-flags), and optionally a custom script and a [battle partner](#battle-partner). If you do not include a custom script name (or you set it to `0`), the NPC follower will default to their normal interaction script. If there is a follower Pokémon present, it will be returned to its Pokeball until the NPC follower is destroyed. -Here's an example: -`setfollowernpc 3, FNPC_ALL, MyScript_Eventscript_CustomFollowerScript, PARTNER_STEVEN` +Here's an example: +`setfollowernpc 3, FNPC_ALL, MyScript_Eventscript_CustomFollowerScript, PARTNER_STEVEN` This would turn object number 3 on the current map into an NPC follower, give them access to all following behaviors, run that custom script when the player interacts with them, and adds the Steven battle partner to the follower ([more on this later](#battle-partner)). The object ***MUST*** have an event flag or the NPC follower will not be created! ## Create a Follower -The `createfollowernpc` macro will create a new follower without needing to convert an existing NPC. It works similarly to `setfollowernpc`, but instead of providing an object id, you give it a GFX id. For example, if you wanted to create a follower with the May sprite, you could do something like this: -`createfollowernpc OBJ_EVENT_GFX_RIVAL_MAY_NORMAL, FNPC_ALL, EventScript_MayFollow` +The `createfollowernpc` macro will create a new follower without needing to convert an existing NPC. It works similarly to `setfollowernpc`, but instead of providing an object id, you give it a GFX id. For example, if you wanted to create a follower with the May sprite, you could do something like this: +`createfollowernpc OBJ_EVENT_GFX_RIVAL_MAY_NORMAL, FNPC_ALL, EventScript_MayFollow` The created follower NPC will initially be invisible until the player takes a step. ## Follower Flags diff --git a/docs/tutorials/how_to_new_pokemon.md b/docs/tutorials/how_to_new_pokemon.md index 31d7bebc41..c350c04857 100644 --- a/docs/tutorials/how_to_new_pokemon.md +++ b/docs/tutorials/how_to_new_pokemon.md @@ -908,7 +908,7 @@ _NOTE: At the top of this file, you will probably see this warning:_ // DO NOT MODIFY THIS FILE! It is auto-generated from tools/learnset_helpers/teachable.py` // ``` -From version 1.9 onwards, pokeemerald-expansion includes a tool called the learnset helper, which aims to automate the generation of valid teachable moves. At the time of writing, this tool only supports generating TM and Tutor learnsets. However, in the future it may be expanded to deal with level up learnsets and egg moves. +From version 1.9 onwards, pokeemerald-expansion includes a tool called the learnset helper, which aims to automate the generation of valid teachable moves. At the time of writing, this tool only supports generating TM and Tutor learnsets. However, in the future it may be expanded to deal with level up learnsets and egg moves. Ignore the warning shown above the first time you're adding your teachable moves (as otherwise the compiler will complain about the array not existing), but in the future (if you're using the learnset helper) simply edit what teachable moves your Pokémon can learn in one of the JSON files found in `tools/learnset_helpers/porymoves_files`. It doesn't really matter which one you add your new Pokémon to, as the tool pulls from all of the files in this folder. diff --git a/docs/tutorials/how_to_new_trainer_slide.md b/docs/tutorials/how_to_new_trainer_slide.md index 49c3e80f3e..6dfab9b5ec 100644 --- a/docs/tutorials/how_to_new_trainer_slide.md +++ b/docs/tutorials/how_to_new_trainer_slide.md @@ -47,7 +47,7 @@ enum TrainerSlideType { TRAINER_SLIDE_BEFORE_FIRST_TURN, TRAINER_SLIDE_PLAYER_LANDS_FIRST_CRITICAL_HIT, -+ TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT, // Each Trainer Slide has a unqiue id. ++ TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT, // Each Trainer Slide has a unqiue id. TRAINER_SLIDE_PLAYER_LANDS_FIRST_SUPER_EFFECTIVE_HIT, TRAINER_SLIDE_PLAYER_LANDS_FIRST_STAB_MOVE, TRAINER_SLIDE_PLAYER_LANDS_FIRST_DOWN, @@ -82,7 +82,7 @@ bool32 IsTrainerSlideInitialized(enum TrainerSlideType); ```diff return IsTrainerSlideInitialized(slideId); } - + +static bool32 ShouldRunTrainerSlideEnemyLandsFirstCriticalHit(enum TrainerSlideType slideId) +{ + return IsTrainerSlideInitialized(slideId); @@ -110,7 +110,7 @@ The function that determines if a Slide should play has different function for m ```diff InitalizeTrainerSlide(slideId); } - + +void TryInitializeTrainerSlideEnemyLandsFirstCriticalHit(u32 target) +{ + enum TrainerSlideType slideId = TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT; @@ -149,10 +149,10 @@ In `BattleTurnPassed`, most Trainer Slides are checked to see if they should run ```diff { PrepareStringBattle(STRINGID_CRITICALHIT, gBattlerAttacker); - + + TryInitializeTrainerSlideEnemyLandsFirstCriticalHit(gBattlerTarget); TryInitializeTrainerSlidePlayerLandsFirstCriticalHit(gBattlerTarget); - + gBattleCommunication[MSG_DISPLAY] = 1; ``` @@ -162,7 +162,7 @@ The actual usage of `TryInitializeTrainerSlideEnemyLandsFirstCriticalHit` is add ```diff } } - + +SINGLE_BATTLE_TEST("Trainer Slide: Enemy Lands First Critical Hit") +{ + gBattleTestRunnerState->data.recordedBattle.opponentA = TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT; diff --git a/docs/tutorials/how_to_testing_system.md b/docs/tutorials/how_to_testing_system.md index 3e6f71e4d0..6d87c9c571 100644 --- a/docs/tutorials/how_to_testing_system.md +++ b/docs/tutorials/how_to_testing_system.md @@ -292,7 +292,7 @@ Adds the species to the player's, player partner's, opponent A's, or opponent B' Pokemon can be customised as per the guidance for `PLAYER(species)` and `OPPONENT(species)`. The functions assign the Pokémon to the party of the trainer at `B_POSITION_PLAYER_LEFT`, `B_POSITION_PLAYER_RIGHT`, `B_POSITION_OPPONENT_LEFT`, and `B_POSITION_OPPONENT_RIGHT`, respectively. `MULTI_PLAYER(species)` and `MULTI_OPPONENT_A(species)` set Pokémon starting at party index 0, while `MULTI_PARTNER(species)` and `MULTI_OPPONENT_B(species)` set Pokémon starting at party index 3. -For `ONE_VS_TWO` tests, `MULTI_PLAYER(species)` must be used for all player-side Pokémon, and for `TWO_VS_ONE` tests, `MULTI_OPPONENT_A(species)` must be used for all opponent-side Pokémon. +For `ONE_VS_TWO` tests, `MULTI_PLAYER(species)` must be used for all player-side Pokémon, and for `TWO_VS_ONE` tests, `MULTI_OPPONENT_A(species)` must be used for all opponent-side Pokémon. All `MULTI_PLAYER(species)` Pokémon must be set before any `MULTI_PARTNER(species)` Pokémon, and all `MULTI_OPPONENT_A(species)` must be set before any `MULTI_OPPONENT_B(species)` Pokémon, else Pokémon will be set in the incorrect parties in the test. **Note where a side in a test has two trainers, the test setup manages the assigning of correct multi-party orders, therefore when using functions such as SEND_OUT, Player and Opponent A Pokémon may be referenced using indexes 0, 1, and 2, and Player's Partner and Opponent B Pokémon may be referenced using indexes 3, 4, and 5.** diff --git a/docs/tutorials/how_to_time_of_day_encounters.md b/docs/tutorials/how_to_time_of_day_encounters.md index 85cd2ac09a..05bddc1e7d 100644 --- a/docs/tutorials/how_to_time_of_day_encounters.md +++ b/docs/tutorials/how_to_time_of_day_encounters.md @@ -10,7 +10,7 @@ - [So what are the `#define` options in `overworld.h`?](#so-what-are-the-define-options-in-overworldh) - [Examples](#examples) -## What is the Time-Based Encounters feature? +## What is the Time-Based Encounters feature? Time-Based Encounters lets you pick which Pokémon appear based on the in-game clock, per route! Gen 2 had this feature, and Gen 4 brought it back- for instance, in Sinnoh's Route 201 you have a higher chance of catching a Bidoof than a Starly at night. @@ -810,4 +810,4 @@ python3 migration_scripts/add_time_based_encounters.py --copy }, ] ``` -As you can see, the group `gRoute101` and all its encounters were copied into groups that correspond with the four vanilla times of day (Morning/Day/Evening/Night). \ No newline at end of file +As you can see, the group `gRoute101` and all its encounters were copied into groups that correspond with the four vanilla times of day (Morning/Day/Evening/Night). diff --git a/docs/tutorials/vs_seeker.md b/docs/tutorials/vs_seeker.md index 40f57eeaa6..e38dba1193 100644 --- a/docs/tutorials/vs_seeker.md +++ b/docs/tutorials/vs_seeker.md @@ -1,13 +1,13 @@ -# `pokemerald-expansion` Vs. Seeker +# `pokemerald-expansion` Vs. Seeker ## What is the Vs. Seeker? -The Vs. Seeker is a Key Item that is used to battle Trainers that the player has battled previously. +The Vs. Seeker is a Key Item that is used to battle Trainers that the player has battled previously. -When used, the Vs. Seeker sends out a signal that allows the player to find other Trainers who want a rematch. This signal affects all Trainers that are on-screen. Once used on Trainers that can be rematched, the device cannot be used again until it is charged. The player does this by walking a specific number of steps. The effect on the Trainers wears off if they are battled, the player leaves the area, or the player walks a specific number of steps. If the player attempts to use the Vs. Seeker when it is not fully charged, the player will be told how many steps remain until it is. After the player uses the Vs. Seeker, some Trainers may have their team changed from their first battle. +When used, the Vs. Seeker sends out a signal that allows the player to find other Trainers who want a rematch. This signal affects all Trainers that are on-screen. Once used on Trainers that can be rematched, the device cannot be used again until it is charged. The player does this by walking a specific number of steps. The effect on the Trainers wears off if they are battled, the player leaves the area, or the player walks a specific number of steps. If the player attempts to use the Vs. Seeker when it is not fully charged, the player will be told how many steps remain until it is. After the player uses the Vs. Seeker, some Trainers may have their team changed from their first battle. ## How is the Vs. Seeker enabled? ### Users -Vs. Seeker functionality is enabled by setting `I_VS_SEEKER_CHARGING` to `TRUE`. +Vs. Seeker functionality is enabled by setting `I_VS_SEEKER_CHARGING` to `TRUE`. ### Players `ITEM_VS_SEEKER` can only be used outside of battle. It can be used from the bag or registered to be used from the field. @@ -26,7 +26,7 @@ When `I_VS_SEEKER_CHARGING` is enabled, the Match Call does not function at all. ## How does the Vs. Seeker choose a Trainer? -When the Vs. Seeker is successfully used, every Trainer on screen is individually queried. There is a 31% chance that the Trainer will want a rematch. +When the Vs. Seeker is successfully used, every Trainer on screen is individually queried. There is a 31% chance that the Trainer will want a rematch. Objects listed in `regularTrainersOnLand` or `regularTrainersInWater` are considered Land/Water objects. | Status | Is Land/Water Object | Emote | New Movement Type | @@ -34,7 +34,7 @@ Objects listed in `regularTrainersOnLand` or `regularTrainersInWater` are consid | Wants Rematch | Yes | `MOVEMENT_ACTION_EMOTE_DOUBLE_EXCL_MARK` | `MOVEMENT_TYPE_COUNTER_CLOCKWISE` | | Wants Rematch | No | `MOVEMENT_ACTION_EMOTE_DOUBLE_EXCL_MARK` | `MOVEMENT_TYPE_FACE_DOWN` | | Does Not Want Rematch | - | `MOVEMENT_ACTION_EMOTE_X` | none | -| Has Not Been Fought | - | `MOVEMENT_ACTION_EMOTE_EXCLAMATION_MARK` | none | +| Has Not Been Fought | - | `MOVEMENT_ACTION_EMOTE_EXCLAMATION_MARK` | none | ### Rematch Table @@ -58,7 +58,7 @@ The game determines which version of the Trainer you'll fight next by following No extra work is required. With the exception of Wally, Gym Leaders and Elite Four, all of the rematchable Trainers in Emerald will work with the Vs. Seeker without any changes. ### New Trainers #### Party / `gRematchTable` -Each of the rematches for the Trainer must be defined as seperate Trainers in `src/data/trainers.party` and `include/constants/opponents`. For example, `TRAINER_CALVIN_1` also has `TRAINER_CALVIN_2`,`TRAINER_CALVIN_3`,`TRAINER_CALVIN_4`, and `TRAINER_CALVIN_5`. +Each of the rematches for the Trainer must be defined as seperate Trainers in `src/data/trainers.party` and `include/constants/opponents`. For example, `TRAINER_CALVIN_1` also has `TRAINER_CALVIN_2`,`TRAINER_CALVIN_3`,`TRAINER_CALVIN_4`, and `TRAINER_CALVIN_5`. Once all of those constants and parties are defined, a new row must be added to `gRematchTable` (located in in `src/battle_setup.c`). The row header should be a rematch ID, which can be added in `include/constants/rematches.h`. The row contents must be the five constants created for the new parties, with the lat argument being the constant of the map (`include/constants/map_groups.h`) where the Trainer is placed. diff --git a/graphics/battle_environment/building/palette2.pal b/graphics/battle_environment/building/gym.pal similarity index 100% rename from graphics/battle_environment/building/palette2.pal rename to graphics/battle_environment/building/gym.pal diff --git a/graphics/battle_environment/building/palette3.pal b/graphics/battle_environment/building/leader.pal similarity index 100% rename from graphics/battle_environment/building/palette3.pal rename to graphics/battle_environment/building/leader.pal diff --git a/graphics/battle_environment/stadium/palette1.pal b/graphics/battle_environment/stadium/aqua.pal similarity index 100% rename from graphics/battle_environment/stadium/palette1.pal rename to graphics/battle_environment/stadium/aqua.pal diff --git a/graphics/battle_environment/stadium/palette6.pal b/graphics/battle_environment/stadium/drake.pal similarity index 100% rename from graphics/battle_environment/stadium/palette6.pal rename to graphics/battle_environment/stadium/drake.pal diff --git a/graphics/battle_environment/stadium/palette5.pal b/graphics/battle_environment/stadium/glacia.pal similarity index 100% rename from graphics/battle_environment/stadium/palette5.pal rename to graphics/battle_environment/stadium/glacia.pal diff --git a/graphics/battle_environment/stadium/palette2.pal b/graphics/battle_environment/stadium/magma.pal similarity index 100% rename from graphics/battle_environment/stadium/palette2.pal rename to graphics/battle_environment/stadium/magma.pal diff --git a/graphics/battle_environment/stadium/palette4.pal b/graphics/battle_environment/stadium/phoebe.pal similarity index 100% rename from graphics/battle_environment/stadium/palette4.pal rename to graphics/battle_environment/stadium/phoebe.pal diff --git a/graphics/battle_environment/stadium/palette3.pal b/graphics/battle_environment/stadium/sidney.pal similarity index 100% rename from graphics/battle_environment/stadium/palette3.pal rename to graphics/battle_environment/stadium/sidney.pal diff --git a/graphics/battle_environment/stadium/palette7.pal b/graphics/battle_environment/stadium/wallace.pal similarity index 100% rename from graphics/battle_environment/stadium/palette7.pal rename to graphics/battle_environment/stadium/wallace.pal diff --git a/include/battle.h b/include/battle.h index f44b23aa57..ff2cac7c94 100644 --- a/include/battle.h +++ b/include/battle.h @@ -28,26 +28,6 @@ #include "random.h" // for rng_value_t #include "trainer_slide.h" -// Helper for accessing command arguments and advancing gBattlescriptCurrInstr. -// -// For example accuracycheck is defined as: -// -// .macro accuracycheck failInstr:req, move:req -// .byte 0x1 -// .4byte \failInstr -// .2byte \move -// .endm -// -// Which corresponds to: -// -// CMD_ARGS(const u8 *failInstr, u16 move); -// -// The arguments can be accessed as cmd->failInstr and cmd->move. -// gBattlescriptCurrInstr = cmd->nextInstr; advances to the next instruction. -#define CMD_ARGS(...) const struct __attribute__((packed)) { u8 opcode; RECURSIVELY(R_FOR_EACH(APPEND_SEMICOLON, __VA_ARGS__)) const u8 nextInstr[0]; } *const cmd UNUSED = (const void *)gBattlescriptCurrInstr -#define VARIOUS_ARGS(...) CMD_ARGS(u8 battler, u8 id, ##__VA_ARGS__) -#define NATIVE_ARGS(...) CMD_ARGS(void (*func)(void), ##__VA_ARGS__) - // Used to exclude moves learned temporarily by Transform or Mimic #define MOVE_IS_PERMANENT(battler, moveSlot) \ (!(gBattleMons[battler].volatiles.transformed) \ @@ -1165,7 +1145,7 @@ static inline struct PartyState *GetBattlerPartyState(u32 battler) static inline bool32 IsDoubleBattle(void) { - return (gBattleTypeFlags & BATTLE_TYPE_MORE_THAN_TWO_BATTLERS); + return !!(gBattleTypeFlags & BATTLE_TYPE_MORE_THAN_TWO_BATTLERS); } static inline bool32 IsSpreadMove(enum MoveTarget moveTarget) diff --git a/include/config/battle.h b/include/config/battle.h index a7c6ffba67..79da5a6ad3 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -99,6 +99,7 @@ #define B_CAN_SPITE_FAIL GEN_LATEST // In Gen4+, Spite can no longer fail if the foe's last move only has 1 remaining PP. #define B_CRASH_IF_TARGET_IMMUNE GEN_LATEST // In Gen4+, moves with crash damage will crash if the user attacks a target that is immune due to their typing. #define B_MEMENTO_FAIL GEN_LATEST // In Gen4+, Memento no longer fails if the target already has -6 Attack and Special Attack. Additionally, in Gen5+, it fails if there is no target, or if the target is protected or behind a Substitute. +#define B_PARTING_SHOT_SWITCH GEN_LATEST // In Gen7+, the user won't switch out if Parting Shot fails to lower the target's stats. #define B_GLARE_GHOST GEN_LATEST // In Gen4+, Glare can hit Ghost-type Pokémon normally. #define B_SKILL_SWAP GEN_LATEST // In Gen4+, Skill Swap triggers switch-in abilities after use. #define B_BRICK_BREAK GEN_LATEST // In Gen4+, you can destroy your own side's screens. In Gen 5+, screens are not removed if the target is immune. @@ -130,6 +131,8 @@ #define B_AFTER_YOU_TURN_ORDER GEN_LATEST // In Gen8+, After You doesn't fail if the turn order wouldn't change after use. #define B_QUASH_TURN_ORDER GEN_LATEST // In Gen8+, Quash-affected battlers move according to speed order. Before Gen8, Quash-affected battlers move in the order they were affected by Quash. #define B_DESTINY_BOND_FAIL GEN_LATEST // In Gen7+, Destiny Bond fails if used repeatedly. +#define B_FORESIGHT_FAIL GEN_LATEST // In Gen2 and Gen5+, Foresight fails if used against a target already under its effect. +#define B_MIRACLE_EYE_FAIL GEN_LATEST // In Gen5+, Miracle Eye fails if used against a target already under its effect. #define B_PURSUIT_TARGET GEN_LATEST // In Gen4+, Pursuit attacks a switching opponent even if they weren't targeting them. Before Gen4, Pursuit only attacks a switching opponent that it originally targeted. #define B_SKIP_RECHARGE GEN_LATEST // In Gen1, recharging moves such as Hyper Beam skip the recharge if the target gets KO'd #define B_ENCORE_TARGET GEN_LATEST // In Gen5+, encored moves are allowed to choose a target diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index c764712bae..883c161a4a 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -91,6 +91,7 @@ F(CAN_SPITE_FAIL, canSpiteFail, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(CRASH_IF_TARGET_IMMUNE, crashIfTargetImmune, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(MEMENTO_FAIL, mementoFail, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ + F(PARTING_SHOT_SWITCH, partingShotSwitch, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(GLARE_GHOST, glareGhost, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(SKILL_SWAP, skillSwap, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(BRICK_BREAK, brickBreak, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ @@ -120,6 +121,8 @@ F(AFTER_YOU_TURN_ORDER, afterYouTurnOrder, (u32, GEN_COUNT - 1)) \ F(QUASH_TURN_ORDER, quashTurnOrder, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(DESTINY_BOND_FAIL, destinyBondFail, (u32, GEN_COUNT - 1)) \ + F(FORESIGHT_FAIL, foresightFail, (u32, GEN_COUNT - 1)) \ + F(MIRACLE_EYE_FAIL, miracleEyeFail, (u32, GEN_COUNT - 1)) \ F(PURSUIT_TARGET, pursuitTarget, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(SKIP_RECHARGE, skipRecharge, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \ F(ENCORE_TARGET, encoreTarget, (u32, GEN_COUNT - 1)) \ diff --git a/include/constants/weather.h b/include/constants/weather.h index f2e41b3ab7..d79e8f1717 100644 --- a/include/constants/weather.h +++ b/include/constants/weather.h @@ -19,7 +19,7 @@ #define WEATHER_ABNORMAL 15 // The alternating weather during Groudon/Kyogre conflict #define WEATHER_ROUTE119_CYCLE 20 #define WEATHER_ROUTE123_CYCLE 21 -#define WEATHER_FOG 22 // Aggregate of WEATHER_FOG_HORIZONTAL and WEATHER_FOG_DIAGONAL +#define WEATHER_FOG 22 // Aggregate of WEATHER_FOG_HORIZONTAL and WEATHER_FOG_DIAGONAL #define WEATHER_COUNT 23 // These are used in maps' coord_weather_event entries. diff --git a/include/gametypes.h b/include/gametypes.h index 242fc9d9db..97e0533baf 100644 --- a/include/gametypes.h +++ b/include/gametypes.h @@ -4,63 +4,63 @@ #include "gba/types.h" // -// This header includes typedefs for fields that commonly appear throughout +// This header includes typedefs for fields that commonly appear throughout // the codebase, and which ROM hacks might benefit from being able to widen. // // These typedefs include the underlying type in their name for two reasons: // -// - Game Freak wasn't fully consistent about field widths throughout -// their codebase. For example, when Region Map Sections are persistently -// stored in savedata, they're stored as 8-bit values; but much of the +// - Game Freak wasn't fully consistent about field widths throughout +// their codebase. For example, when Region Map Sections are persistently +// stored in savedata, they're stored as 8-bit values; but much of the // codebase handles them as 16-bit values. // -// - Although Pokemon Emerald doesn't come close to maxing out RAM, it *does* -// use nearly all of its EEPROM. That is: the vanilla game uses 96% of the -// flash memory available for storing players' save files, leaving 2172 -// bytes to spare within each of the game's two save files (primary and -// backup). These spare bytes are not contiguous: SaveBlock1 can only grow -// by 84 bytes, and SaveBlock2 can only grow by 120 bytes, with the rest +// - Although Pokemon Emerald doesn't come close to maxing out RAM, it *does* +// use nearly all of its EEPROM. That is: the vanilla game uses 96% of the +// flash memory available for storing players' save files, leaving 2172 +// bytes to spare within each of the game's two save files (primary and +// backup). These spare bytes are not contiguous: SaveBlock1 can only grow +// by 84 bytes, and SaveBlock2 can only grow by 120 bytes, with the rest // of the free space located after the player's PC-boxed Pokemon. // -// With so little flash memory to spare, keeping track of how much space -// you're using is vital -- and so is arranging struct members to minimize -// compiler-inserted padding. It's easier to deal with this when you can +// With so little flash memory to spare, keeping track of how much space +// you're using is vital -- and so is arranging struct members to minimize +// compiler-inserted padding. It's easier to deal with this when you can // see these types' widths at a glance. // -// Accordingly, this file generally doesn't contain just single types, but -// rather families of types. For example, Region Map Sections are saved as -// u8s within the player's save file, but are sometimes handled as u16s or -// even s16s and ints; and so there are multiple typedefs for Map Sections -// corresponding to each of these underlying types, and each typedef has a +// Accordingly, this file generally doesn't contain just single types, but +// rather families of types. For example, Region Map Sections are saved as +// u8s within the player's save file, but are sometimes handled as u16s or +// even s16s and ints; and so there are multiple typedefs for Map Sections +// corresponding to each of these underlying types, and each typedef has a // name which indicates the underlying type. // -// For a given family of typedefs, the smallest one should be considered -// the "real" or "canonical" type. Continuing with Map Sections as our -// example, the smallest type is an 8-bit integer, and so any values that -// can't fit in an 8-bit integer will be truncated and lost at some point -// within the codebase. Therefore mapsec_u8_t is the "canonical" type for -// Map Sections, and the larger typedefs just exist to describe situations +// For a given family of typedefs, the smallest one should be considered +// the "real" or "canonical" type. Continuing with Map Sections as our +// example, the smallest type is an 8-bit integer, and so any values that +// can't fit in an 8-bit integer will be truncated and lost at some point +// within the codebase. Therefore mapsec_u8_t is the "canonical" type for +// Map Sections, and the larger typedefs just exist to describe situations // where the game handles Map Sections inconsistently with that "canon." // -// Map Sections are named areas that can appear in the region map. Each -// individual map can be assigned to a Map Section as appropriate. The +// Map Sections are named areas that can appear in the region map. Each +// individual map can be assigned to a Map Section as appropriate. The // possible values are in constants/region_map_sections.h. // -// If you choose to widen Map Sections, be aware that Met Locations (below) +// If you choose to widen Map Sections, be aware that Met Locations (below) // are based on Map Sections and will also be widened. typedef u8 mapsec_u8_t; typedef u16 mapsec_u16_t; typedef s16 mapsec_s16_t; typedef s32 mapsec_s32_t; -// Met Locations for caught Pokemon use the same values as Map Sections, +// Met Locations for caught Pokemon use the same values as Map Sections, // except that 0xFD, 0xFE, and 0xFF have special meanings. // -// Because this value appears inside every Pokemon's data, widening it will -// consume a lot more space within flash memory. The space usage will be -// greater than you expect due to how Pokemon substructs are laid out; you -// would have to rearrange the substructs' contents in order to minimize +// Because this value appears inside every Pokemon's data, widening it will +// consume a lot more space within flash memory. The space usage will be +// greater than you expect due to how Pokemon substructs are laid out; you +// would have to rearrange the substructs' contents in order to minimize // how much more space a wider Met Location would consume. typedef mapsec_u8_t metloc_u8_t; diff --git a/include/item_use.h b/include/item_use.h index d2ed3179f9..1ee46b1c0f 100644 --- a/include/item_use.h +++ b/include/item_use.h @@ -63,4 +63,11 @@ enum { bool32 CanThrowBall(void); bool32 CannotUseItemsInBattle(enum Item itemId, struct Pokemon *mon); +enum ItemTMHMOrEvolutionStone +{ + ITEM_IS_OTHER, + ITEM_IS_TM_HM, + ITEM_IS_EVOLUTION_STONE, +}; + #endif // GUARD_ITEM_USE_H diff --git a/include/overworld.h b/include/overworld.h index 4d913f02f8..fa84701f70 100644 --- a/include/overworld.h +++ b/include/overworld.h @@ -93,6 +93,7 @@ void SetDynamicWarpWithCoords(s32 unused, s8 mapGroup, s8 mapNum, s8 warpId, s8 void SetWarpDestinationToDynamicWarp(u8 unusedWarpId); void SetWarpDestinationToHealLocation(u8 healLocationId); void SetWarpDestinationToLastHealLocation(void); +void SetWarpDestinationForTeleport(void); void SetLastHealLocationWarp(u8 healLocationId); void UpdateEscapeWarp(s16 x, s16 y); void SetEscapeWarp(s8 mapGroup, s8 mapNum, s8 warpId, s8 x, s8 y); diff --git a/migration_scripts/add_time_based_encounters.py b/migration_scripts/add_time_based_encounters.py index b036e46e41..4c802fb44c 100644 --- a/migration_scripts/add_time_based_encounters.py +++ b/migration_scripts/add_time_based_encounters.py @@ -56,7 +56,7 @@ def GetWildEncounterFile(): if tempSuffix in map["base_label"]: editMap = False break - else: + else: editMap = True if editMap: diff --git a/src/battle_ai_switch.c b/src/battle_ai_switch.c index 5c3baff3a0..5393f84172 100644 --- a/src/battle_ai_switch.c +++ b/src/battle_ai_switch.c @@ -526,7 +526,7 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) { u32 battlerIn1, battlerIn2; u8 numAbsorbingAbilities = 0; - enum Ability absorbingTypeAbilities[3]; // Array size is maximum number of absorbing abilities for a single type + enum Ability absorbingTypeAbilities[8]; // Max needed for type + move property absorbers s32 firstId; s32 lastId; struct Pokemon *party; @@ -570,42 +570,47 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_FLASH_FIRE; } - else if (incomingType == TYPE_WATER || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_WATER)) + if (incomingType == TYPE_WATER || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_WATER)) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_WATER_ABSORB; absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_DRY_SKIN; if (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) >= GEN_5) absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_STORM_DRAIN; } - else if (incomingType == TYPE_ELECTRIC || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_ELECTRIC)) + if (incomingType == TYPE_ELECTRIC || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_ELECTRIC)) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_VOLT_ABSORB; absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_MOTOR_DRIVE; if (GetConfig(CONFIG_REDIRECT_ABILITY_IMMUNITY) >= GEN_5) absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_LIGHTNING_ROD; } - else if (incomingType == TYPE_GRASS || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_GRASS)) + if (incomingType == TYPE_GRASS || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_GRASS)) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_SAP_SIPPER; } - else if (incomingType == TYPE_GROUND || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_GROUND)) + if (incomingType == TYPE_GROUND || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_GROUND)) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_EARTH_EATER; absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_LEVITATE; } - else if (IsSoundMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsSoundMove(incomingMove))) + if (IsSoundMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsSoundMove(incomingMove))) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_SOUNDPROOF; } - else if (IsBallisticMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsBallisticMove(incomingMove))) + if (IsBallisticMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsBallisticMove(incomingMove))) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_BULLETPROOF; } - else if (IsWindMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsWindMove(incomingMove))) + if (IsWindMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsWindMove(incomingMove))) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_WIND_RIDER; } - else + if (IsPowderMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsPowderMove(incomingMove))) + { + if (GetConfig(CONFIG_POWDER_OVERCOAT) >= GEN_6) + absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_OVERCOAT; + } + if (numAbsorbingAbilities == 0) { return FALSE; } diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 2726ad7ce8..8750225f52 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -2097,40 +2097,45 @@ bool32 IsAllyProtectingFromMove(u32 battlerAtk, enum Move attackerMove, enum Mov { return FALSE; } - else + + enum ProtectMethod protectMethod = GetMoveProtectMethod(allyMove); + + switch (protectMethod) { - enum ProtectMethod protectMethod = GetMoveProtectMethod(allyMove); - - if (protectMethod == PROTECT_QUICK_GUARD) + case PROTECT_CRAFTY_SHIELD: + if (!IsBattleMoveStatus(attackerMove)) { - s32 priority = GetBattleMovePriority(battlerAtk, gAiLogicData->abilities[battlerAtk], attackerMove); - return (priority > 0); + return FALSE; } - - if (IsBattleMoveStatus(attackerMove)) + else if (GetMoveEffect(attackerMove) == EFFECT_HOLD_HANDS) { - switch (protectMethod) - { - case PROTECT_NORMAL: - case PROTECT_CRAFTY_SHIELD: - case PROTECT_MAX_GUARD: - case PROTECT_WIDE_GUARD: - return TRUE; - - default: - return FALSE; - } + return TRUE; } else { - switch (protectMethod) - { - case PROTECT_CRAFTY_SHIELD: - return FALSE; - default: - return TRUE; - } + u32 moveTarget = GetBattlerMoveTargetType(battlerAtk, attackerMove); + return (GetBattlerSide(battlerAtk) != GetBattlerSide(BATTLE_PARTNER(battlerAtk)) + && moveTarget != TARGET_OPPONENTS_FIELD + && moveTarget != TARGET_ALL_BATTLERS); } + case PROTECT_WIDE_GUARD: + return IsSpreadMove(GetBattlerMoveTargetType(battlerAtk, attackerMove)); + case PROTECT_NORMAL: + case PROTECT_SPIKY_SHIELD: + case PROTECT_MAX_GUARD: + case PROTECT_BANEFUL_BUNKER: + case PROTECT_BURNING_BULWARK: + return TRUE; + case PROTECT_OBSTRUCT: + case PROTECT_SILK_TRAP: + case PROTECT_KINGS_SHIELD: + return !IsBattleMoveStatus(attackerMove); + case PROTECT_QUICK_GUARD: + return (GetChosenMovePriority(battlerAtk, gAiLogicData->abilities[battlerAtk]) > 0); + case PROTECT_MAT_BLOCK: + return !IsBattleMoveStatus(attackerMove); + default: + return FALSE; } } diff --git a/src/battle_anim_water.c b/src/battle_anim_water.c index 829cc2bcf6..1c9f62342b 100644 --- a/src/battle_anim_water.c +++ b/src/battle_anim_water.c @@ -1399,20 +1399,20 @@ static void AnimSmallWaterOrb(struct Sprite *sprite) } } -#define tRainState data[0] -#define tWaterSpoutPower data[1] -#define tDropTaskDelay data[2] -#define tDropInitialXPos data[4] -#define tDropXRange data[5] -#define tDropEndYPos data[6] -#define tDropXPos data[7] -#define tSineTableIndex data[8] -#define tCurrentDropSprites data[9] -#define tDropHasHit data[10] -#define tCreatedDropSprites data[11] -#define tMaxDropSprites data[12] -#define tShakeTasksCreated data[13] -#define tDropInitialYPos data[14] +#define tRainState data[0] +#define tWaterSpoutPower data[1] +#define tDropTaskDelay data[2] +#define tDropInitialXPos data[4] +#define tDropXRange data[5] +#define tDropEndYPos data[6] +#define tDropXPos data[7] +#define tSineTableIndex data[8] +#define tCurrentDropSprites data[9] +#define tDropHasHit data[10] +#define tCreatedDropSprites data[11] +#define tMaxDropSprites data[12] +#define tShakeTasksCreated data[13] +#define tDropInitialYPos data[14] void AnimTask_BrineRain(u8 taskId) { @@ -1590,20 +1590,20 @@ static void AnimWaterSpoutRainHit(struct Sprite *sprite) } } -#undef tRainState -#undef tWaterSpoutPower -#undef tDropTaskDelay -#undef tDropInitialXPos -#undef tDropXRange -#undef tDropEndYPos -#undef tDropXPos -#undef tSineTableIndex -#undef tCurrentDropSprites -#undef tDropHasHit -#undef tCreatedDropSprites -#undef tMaxDropSprites -#undef tShakeTasksCreated -#undef tDropInitialYPos +#undef tRainState +#undef tWaterSpoutPower +#undef tDropTaskDelay +#undef tDropInitialXPos +#undef tDropXRange +#undef tDropEndYPos +#undef tDropXPos +#undef tSineTableIndex +#undef tCurrentDropSprites +#undef tDropHasHit +#undef tCreatedDropSprites +#undef tMaxDropSprites +#undef tShakeTasksCreated +#undef tDropInitialYPos void AnimTask_WaterSport(u8 taskId) { diff --git a/src/battle_dynamax.c b/src/battle_dynamax.c index 17c25a0f5f..a101f424af 100644 --- a/src/battle_dynamax.c +++ b/src/battle_dynamax.c @@ -458,43 +458,3 @@ void ChooseDamageNonTypesString(enum Type type) break; } } - -// Updates Dynamax HP multipliers and healthboxes. -void BS_UpdateDynamax(void) -{ - NATIVE_ARGS(); - u32 battler = gBattleScripting.battler; - struct Pokemon *mon = GetBattlerMon(battler); - - if (!IsGigantamaxed(battler)) // RecalcBattlerStats will get called on form change. - RecalcBattlerStats(battler, mon, GetActiveGimmick(battler) == GIMMICK_DYNAMAX); - - UpdateHealthboxAttribute(gHealthboxSpriteIds[battler], mon, HEALTHBOX_ALL); - gBattlescriptCurrInstr = cmd->nextInstr; -} - -// Goes to the jump instruction if the target is Dynamaxed. -void BS_JumpIfDynamaxed(void) -{ - NATIVE_ARGS(const u8 *jumpInstr); - if ((GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX)) - gBattlescriptCurrInstr = cmd->jumpInstr; - else - gBattlescriptCurrInstr = cmd->nextInstr; -} - -void BS_UndoDynamax(void) -{ - NATIVE_ARGS(u8 battler); - u32 battler = GetBattlerForBattleScript(cmd->battler); - - if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX) - { - UndoDynamax(battler); - gBattleScripting.battler = battler; - BattleScriptCall(BattleScript_DynamaxEnds_Ret); - return; - } - - gBattlescriptCurrInstr = cmd->nextInstr; -} diff --git a/src/battle_move_resolution.c b/src/battle_move_resolution.c index d0820c5b50..92f805f0e9 100644 --- a/src/battle_move_resolution.c +++ b/src/battle_move_resolution.c @@ -837,7 +837,10 @@ static enum CancelerResult CancelerMoveFailure(struct BattleContext *ctx) bool32 canUseWideGuard = (GetConfig(CONFIG_WIDE_GUARD) >= GEN_6 && protectMethod == PROTECT_WIDE_GUARD); bool32 canUseQuickGuard = (GetConfig(CONFIG_QUICK_GUARD) >= GEN_6 && protectMethod == PROTECT_QUICK_GUARD); - if (!canUseProtectSecondTime && !canUseWideGuard && !canUseQuickGuard) + if (!canUseProtectSecondTime + && !canUseWideGuard + && !canUseQuickGuard + && protectMethod != PROTECT_CRAFTY_SHIELD) battleScript = BattleScript_ButItFailed; } if (battleScript != NULL) @@ -1624,7 +1627,8 @@ static enum MoveEndResult MoveEndProtectLikeEffect(void) enum MoveEndResult result = MOVEEND_RESULT_CONTINUE; u32 temp = 0; - if (CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), gCurrentMove)) + if (gProtectStructs[gBattlerAttacker].chargingTurn + || CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), gCurrentMove)) { gBattleScripting.moveendState++; return result; @@ -1685,8 +1689,8 @@ static enum MoveEndResult MoveEndProtectLikeEffect(void) // Not strictly a protect effect, but works the same way if (IsBattlerUsingBeakBlast(gBattlerTarget) - && CanBeBurned(gBattlerAttacker, gBattlerAttacker, GetBattlerAbility(gBattlerAttacker)) - && !IsBattlerUnaffectedByMove(gBattlerTarget)) + && IsBattlerTurnDamaged(gBattlerTarget) + && CanBeBurned(gBattlerAttacker, gBattlerAttacker, GetBattlerAbility(gBattlerAttacker))) { gBattleMons[gBattlerAttacker].status1 = STATUS1_BURN; BtlController_EmitSetMonData(gBattlerAttacker, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerAttacker].status1), &gBattleMons[gBattlerAttacker].status1); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index e3420e608a..9c14e8403b 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -75,6 +75,26 @@ #include "load_save.h" #include "test/test_runner_battle.h" +// Helper for accessing command arguments and advancing gBattlescriptCurrInstr. +// +// For example accuracycheck is defined as: +// +// .macro accuracycheck failInstr:req, move:req +// .byte 0x1 +// .4byte \failInstr +// .2byte \move +// .endm +// +// Which corresponds to: +// +// CMD_ARGS(const u8 *failInstr, u16 move); +// +// The arguments can be accessed as cmd->failInstr and cmd->move. +// gBattlescriptCurrInstr = cmd->nextInstr; advances to the next instruction. +#define CMD_ARGS(...) const struct __attribute__((packed)) { u8 opcode; RECURSIVELY(R_FOR_EACH(APPEND_SEMICOLON, __VA_ARGS__)) const u8 nextInstr[0]; } *const cmd UNUSED = (const void *)gBattlescriptCurrInstr +#define VARIOUS_ARGS(...) CMD_ARGS(u8 battler, u8 id, ##__VA_ARGS__) +#define NATIVE_ARGS(...) CMD_ARGS(void (*func)(void), ##__VA_ARGS__) + // table to avoid ugly powing on gba (courtesy of doesnt) // this returns (i^2.5)/4 // the quarters cancel so no need to re-quadruple them in actual calculation @@ -2806,7 +2826,7 @@ void SetMoveEffect(u32 battlerAtk, u32 effectBattler, enum MoveEffect moveEffect if (i) { BattleScriptPush(battleScript); - if (gCurrentMove == MOVE_HYPERSPACE_FURY) + if (GetMoveEffect(gCurrentMove) == EFFECT_HYPERSPACE_FURY) gBattlescriptCurrInstr = BattleScript_HyperspaceFuryRemoveProtect; else gBattlescriptCurrInstr = BattleScript_MoveEffectFeint; @@ -7806,6 +7826,8 @@ static u32 ChangeStatBuffs(u32 battler, s8 statValue, enum Stat statId, union St } else if (battlerAbility == ABILITY_MIRROR_ARMOR && !flags.mirrorArmored && gBattlerAttacker != gBattlerTarget && battler == gBattlerTarget) { + if (GetMoveEffect(gCurrentMove) == EFFECT_PARTING_SHOT) + gBattleScripting.animTargetsHit = 1; if (flags.allowPtr) { SET_STATCHANGER(statId, GET_STAT_BUFF_VALUE(statValue) | STAT_BUFF_NEGATIVE, TRUE); @@ -9664,7 +9686,7 @@ static void Cmd_trymemento(void) { CMD_ARGS(const u8 *failInstr); - if (B_MEMENTO_FAIL >= GEN_5 && DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove)) + if (B_MEMENTO_FAIL >= GEN_4 && DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove)) { // Failed, target was protected. gBattlescriptCurrInstr = cmd->failInstr; @@ -15117,3 +15139,43 @@ void BS_TryActivateAbilityWithAbilityShield(void) BattleScriptCall(BattleScript_ActivateSwitchInAbility); } } + +// Updates Dynamax HP multipliers and healthboxes. +void BS_UpdateDynamax(void) +{ + NATIVE_ARGS(); + u32 battler = gBattleScripting.battler; + struct Pokemon *mon = GetBattlerMon(battler); + + if (!IsGigantamaxed(battler)) // RecalcBattlerStats will get called on form change. + RecalcBattlerStats(battler, mon, GetActiveGimmick(battler) == GIMMICK_DYNAMAX); + + UpdateHealthboxAttribute(gHealthboxSpriteIds[battler], mon, HEALTHBOX_ALL); + gBattlescriptCurrInstr = cmd->nextInstr; +} + +// Goes to the jump instruction if the target is Dynamaxed. +void BS_JumpIfDynamaxed(void) +{ + NATIVE_ARGS(const u8 *jumpInstr); + if ((GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX)) + gBattlescriptCurrInstr = cmd->jumpInstr; + else + gBattlescriptCurrInstr = cmd->nextInstr; +} + +void BS_UndoDynamax(void) +{ + NATIVE_ARGS(u8 battler); + u32 battler = GetBattlerForBattleScript(cmd->battler); + + if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX) + { + UndoDynamax(battler); + gBattleScripting.battler = battler; + BattleScriptCall(BattleScript_DynamaxEnds_Ret); + return; + } + + gBattlescriptCurrInstr = cmd->nextInstr; +} diff --git a/src/battle_util.c b/src/battle_util.c index e2779981b7..d9abfec976 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -5929,6 +5929,26 @@ static inline bool32 IsSideProtected(u32 battler, enum ProtectMethod method) || gProtectStructs[BATTLE_PARTNER(battler)].protected == method; } +static bool32 IsCraftyShieldProtected(u32 battlerAtk, u32 battlerDef, u32 move) +{ + if (!IsBattleMoveStatus(move)) + return FALSE; + + if (!IsSideProtected(battlerDef, PROTECT_CRAFTY_SHIELD)) + return FALSE; + + if (GetMoveEffect(move) == EFFECT_HOLD_HANDS) + return TRUE; + + u32 moveTarget = GetBattlerMoveTargetType(battlerAtk, move); + if (!IsBattlerAlly(battlerAtk, battlerDef) + && moveTarget != TARGET_OPPONENTS_FIELD + && moveTarget != TARGET_ALL_BATTLERS) + return TRUE; + + return FALSE; +} + bool32 IsBattlerProtected(struct BattleContext *ctx) { if (gProtectStructs[ctx->battlerDef].protected == PROTECT_NONE @@ -5952,9 +5972,7 @@ bool32 IsBattlerProtected(struct BattleContext *ctx) bool32 isProtected = FALSE; - if (IsSideProtected(ctx->battlerDef, PROTECT_CRAFTY_SHIELD) - && IsBattleMoveStatus(ctx->move) - && GetMoveEffect(ctx->move) != EFFECT_COACHING) + if (IsCraftyShieldProtected(ctx->battlerAtk, ctx->battlerDef, ctx->move)) isProtected = TRUE; else if (MoveIgnoresProtect(ctx->move)) isProtected = FALSE; @@ -8602,6 +8620,9 @@ bool32 DoesSpeciesUseHoldItemToChangeForm(u16 species, u16 heldItemId) u32 i; const struct FormChange *formChanges = GetSpeciesFormChanges(species); + if (heldItemId == ITEM_NONE) + return FALSE; + for (i = 0; formChanges != NULL && formChanges[i].method != FORM_CHANGE_TERMINATOR; i++) { enum FormChanges method = formChanges[i].method; diff --git a/src/data/graphics/battle_environment.h b/src/data/graphics/battle_environment.h index 4d271ea82d..c1802892e3 100644 --- a/src/data/graphics/battle_environment.h +++ b/src/data/graphics/battle_environment.h @@ -8,15 +8,15 @@ const u16 gBattleEnvironmentPalette_Building[] = INCBIN_U16("graphics/battle_env const u16 gBattleEnvironmentPalette_Kyogre[] = INCBIN_U16("graphics/battle_environment/water/kyogre.gbapal"); const u16 gBattleEnvironmentPalette_Groudon[] = INCBIN_U16("graphics/battle_environment/cave/groudon.gbapal"); -const u16 gBattleEnvironmentPalette_BuildingGym[] = INCBIN_U16("graphics/battle_environment/building/palette2.gbapal"); -const u16 gBattleEnvironmentPalette_BuildingLeader[] = INCBIN_U16("graphics/battle_environment/building/palette3.gbapal"); -const u16 gBattleEnvironmentPalette_StadiumAqua[] = INCBIN_U16("graphics/battle_environment/stadium/palette1.gbapal"); -const u16 gBattleEnvironmentPalette_StadiumMagma[] = INCBIN_U16("graphics/battle_environment/stadium/palette2.gbapal"); -const u16 gBattleEnvironmentPalette_StadiumSidney[] = INCBIN_U16("graphics/battle_environment/stadium/palette3.gbapal"); -const u16 gBattleEnvironmentPalette_StadiumPhoebe[] = INCBIN_U16("graphics/battle_environment/stadium/palette4.gbapal"); -const u16 gBattleEnvironmentPalette_StadiumGlacia[] = INCBIN_U16("graphics/battle_environment/stadium/palette5.gbapal"); -const u16 gBattleEnvironmentPalette_StadiumDrake[] = INCBIN_U16("graphics/battle_environment/stadium/palette6.gbapal"); -const u16 gBattleEnvironmentPalette_StadiumWallace[] = INCBIN_U16("graphics/battle_environment/stadium/palette7.gbapal"); +const u16 gBattleEnvironmentPalette_BuildingGym[] = INCBIN_U16("graphics/battle_environment/building/gym.gbapal"); +const u16 gBattleEnvironmentPalette_BuildingLeader[] = INCBIN_U16("graphics/battle_environment/building/leader.gbapal"); +const u16 gBattleEnvironmentPalette_StadiumAqua[] = INCBIN_U16("graphics/battle_environment/stadium/aqua.gbapal"); +const u16 gBattleEnvironmentPalette_StadiumMagma[] = INCBIN_U16("graphics/battle_environment/stadium/magma.gbapal"); +const u16 gBattleEnvironmentPalette_StadiumSidney[] = INCBIN_U16("graphics/battle_environment/stadium/sidney.gbapal"); +const u16 gBattleEnvironmentPalette_StadiumPhoebe[] = INCBIN_U16("graphics/battle_environment/stadium/phoebe.gbapal"); +const u16 gBattleEnvironmentPalette_StadiumGlacia[] = INCBIN_U16("graphics/battle_environment/stadium/glacia.gbapal"); +const u16 gBattleEnvironmentPalette_StadiumDrake[] = INCBIN_U16("graphics/battle_environment/stadium/drake.gbapal"); +const u16 gBattleEnvironmentPalette_StadiumWallace[] = INCBIN_U16("graphics/battle_environment/stadium/wallace.gbapal"); const u16 gBattleEnvironmentPalette_Rayquaza[] = INCBIN_U16("graphics/battle_environment/sky/palette.gbapal"); const u32 gBattleEnvironmentAnimTiles_TallGrass[] = INCBIN_U32("graphics/battle_environment/tall_grass/anim_tiles.4bpp.smol"); diff --git a/src/data/graphics/items.h b/src/data/graphics/items.h index e6bd7e8d67..2085c74d21 100644 --- a/src/data/graphics/items.h +++ b/src/data/graphics/items.h @@ -21,7 +21,7 @@ const u16 gItemIconPalette_UltraBall[] = INCBIN_U16("graphics/items/icon_palette const u32 gItemIcon_MasterBall[] = INCBIN_U32("graphics/items/icons/master_ball.4bpp.smol"); const u16 gItemIconPalette_MasterBall[] = INCBIN_U16("graphics/items/icon_palettes/master_ball.gbapal"); -const u32 gItemIcon_PremierBall[] = INCBIN_U16("graphics/items/icons/premier_ball.4bpp.smol"); +const u32 gItemIcon_PremierBall[] = INCBIN_U32("graphics/items/icons/premier_ball.4bpp.smol"); const u16 gItemIconPalette_PremierBall[] = INCBIN_U16("graphics/items/icon_palettes/premier_ball.gbapal"); const u32 gItemIcon_HealBall[] = INCBIN_U32("graphics/items/icons/heal_ball.4bpp.smol"); diff --git a/src/data/graphics/pokemon.h b/src/data/graphics/pokemon.h index a133f2b9d5..63ca5656b8 100644 --- a/src/data/graphics/pokemon.h +++ b/src/data/graphics/pokemon.h @@ -19391,7 +19391,7 @@ const u32 gObjectEventPic_Substitute[] = INCBIN_COMP("graphics/pokemon/question_ const u16 gMonPalette_VivillonIcySnow[] = INCBIN_U16("graphics/pokemon/vivillon/normal.gbapal"); const u32 gMonBackPic_VivillonIcySnow[] = INCBIN_U32("graphics/pokemon/vivillon/back.4bpp.smol"); const u16 gMonShinyPalette_VivillonIcySnow[] = INCBIN_U16("graphics/pokemon/vivillon/shiny.gbapal"); - const u8 gMonIcon_VivillonIcySnow[] = INCBIN_U8("graphics/pokemon/vivillon/meadow/icon.4bpp"); + const u8 gMonIcon_VivillonIcySnow[] = INCBIN_U8("graphics/pokemon/vivillon/icon.4bpp"); #if P_FOOTPRINTS const u8 gMonFootprint_Vivillon[] = INCBIN_U8("graphics/pokemon/vivillon/footprint.1bpp"); #endif //P_FOOTPRINTS diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 272b02666c..45e4d3bfb3 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -8993,7 +8993,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .priority = 0, .category = DAMAGE_CATEGORY_STATUS, .zMove = { .effect = Z_EFFECT_DEF_UP_1 }, - .ignoresProtect = (B_UPDATED_MOVE_FLAGS >= GEN_6 || B_UPDATED_MOVE_FLAGS < GEN_3), + .ignoresProtect = B_UPDATED_MOVE_FLAGS >= GEN_6, .magicCoatAffected = TRUE, .contestEffect = CONTEST_EFFECT_MAKE_FOLLOWING_MONS_NERVOUS, .contestCategory = CONTEST_CATEGORY_CUTE, @@ -16028,7 +16028,9 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .metronomeBanned = TRUE, .sketchBanned = (B_SKETCH_BANS >= GEN_9), .additionalEffects = ADDITIONAL_EFFECTS({ - // Feint move effect handled in script as it goes before animation + .moveEffect = MOVE_EFFECT_FEINT, // TODO: Is this supposed to happen before the attack animation? + }, + { .moveEffect = MOVE_EFFECT_DEF_MINUS_1, .self = TRUE, }), @@ -19945,7 +19947,6 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .target = TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .metronomeBanned = TRUE, // Only since it isn't implemented yet .battleAnimScript = gBattleAnimMove_LastRespects, }, @@ -21031,7 +21032,6 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .category = DAMAGE_CATEGORY_SPECIAL, .argument = { .absorbPercentage = 50 }, .thawsUser = TRUE, - .metronomeBanned = TRUE, .healingMove = TRUE, .additionalEffects = ADDITIONAL_EFFECTS({ .moveEffect = MOVE_EFFECT_BURN, @@ -21055,7 +21055,6 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .priority = 0, .category = DAMAGE_CATEGORY_SPECIAL, .ballisticMove = TRUE, - .metronomeBanned = TRUE, .additionalEffects = ADDITIONAL_EFFECTS({ .moveEffect = MOVE_EFFECT_SYRUP_BOMB, .chance = 100, @@ -21078,7 +21077,6 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .target = TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .metronomeBanned = TRUE, .battleAnimScript = gBattleAnimMove_IvyCudgel, }, @@ -21162,7 +21160,6 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .zMove = { .effect = Z_EFFECT_RESET_STATS }, .ignoresProtect = TRUE, .mirrorMoveBanned = TRUE, - .metronomeBanned = TRUE, .copycatBanned = TRUE, .assistBanned = TRUE, .battleAnimScript = gBattleAnimMove_BurningBulwark, diff --git a/src/data/pokemon/species_info/gen_3_families.h b/src/data/pokemon/species_info/gen_3_families.h index 954e2477f0..58d0431023 100644 --- a/src/data/pokemon/species_info/gen_3_families.h +++ b/src/data/pokemon/species_info/gen_3_families.h @@ -2906,7 +2906,7 @@ const struct SpeciesInfo gSpeciesInfoGen3[] = .baseSpeed = 100, .baseSpAttack = 165, .baseSpDefense = 135, - .types = MON_TYPES(TYPE_PSYCHIC, RALTS_FAMILY_TYPE2), + .types = MON_TYPES(TYPE_PSYCHIC, TYPE_FAIRY), .catchRate = 45, .expYield = (P_UPDATED_EXP_YIELDS >= GEN_8) ? 309 : 278, .evYield_SpAttack = 3, diff --git a/src/datetime.c b/src/datetime.c index fbbb3ea6b7..51e009b3bb 100644 --- a/src/datetime.c +++ b/src/datetime.c @@ -3,7 +3,7 @@ #include "rtc.h" -const struct DateTime gGen3Epoch = +const struct DateTime gGen3Epoch = { .year = 2000, .month = MONTH_JAN, diff --git a/src/field_effect.c b/src/field_effect.c index 5b84130ffb..4b4396067d 100644 --- a/src/field_effect.c +++ b/src/field_effect.c @@ -2653,7 +2653,7 @@ static void TeleportWarpOutFieldEffect_End(struct Task *task) if (BGMusicStopped() == TRUE) { - SetWarpDestinationToLastHealLocation(); + SetWarpDestinationForTeleport(); WarpIntoMap(); SetMainCallback2(CB2_LoadMap); gFieldCallback = FieldCallback_TeleportWarpIn; diff --git a/src/field_player_avatar.c b/src/field_player_avatar.c index 3232f91f97..64522ceecd 100644 --- a/src/field_player_avatar.c +++ b/src/field_player_avatar.c @@ -277,7 +277,11 @@ static const u8 sRSAvatarGfxIds[GENDER_COUNT] = [FEMALE] = OBJ_EVENT_GFX_LINK_RS_MAY }; -static const u8 sPlayerAvatarGfxToStateFlag[GENDER_COUNT][5][2] = +static const struct __attribute__((packed)) +{ + u8 graphicsId; + u8 playerFlag; +} sPlayerAvatarGfxToStateFlag[GENDER_COUNT][5] = { [MALE] = { @@ -843,10 +847,10 @@ static void PlayerNotOnBikeMoving(enum Direction direction, u16 heldKeys) } if (!(gPlayerAvatar.flags & PLAYER_AVATAR_FLAG_UNDERWATER) - && (heldKeys & B_BUTTON) + && (heldKeys & B_BUTTON) && FlagGet(FLAG_SYS_B_DASH) - && IsRunningDisallowed(gObjectEvents[gPlayerAvatar.objectEventId].currentMetatileBehavior) == 0 - && !FollowerNPCComingThroughDoor() + && IsRunningDisallowed(gObjectEvents[gPlayerAvatar.objectEventId].currentMetatileBehavior) == 0 + && !FollowerNPCComingThroughDoor() && (I_ORAS_DOWSING_FLAG == 0 || (I_ORAS_DOWSING_FLAG != 0 && !FlagGet(I_ORAS_DOWSING_FLAG)))) { if (ObjectMovingOnRockStairs(&gObjectEvents[gPlayerAvatar.objectEventId], direction)) @@ -1579,8 +1583,8 @@ static u8 GetPlayerAvatarStateTransitionByGraphicsId(u16 graphicsId, u8 gender) for (i = 0; i < ARRAY_COUNT(sPlayerAvatarGfxToStateFlag[0]); i++) { - if (sPlayerAvatarGfxToStateFlag[gender][i][0] == graphicsId) - return sPlayerAvatarGfxToStateFlag[gender][i][1]; + if (sPlayerAvatarGfxToStateFlag[gender][i].graphicsId == graphicsId) + return sPlayerAvatarGfxToStateFlag[gender][i].playerFlag; } return PLAYER_AVATAR_FLAG_ON_FOOT; } @@ -1592,8 +1596,8 @@ u16 GetPlayerAvatarGraphicsIdByCurrentState(void) for (i = 0; i < ARRAY_COUNT(sPlayerAvatarGfxToStateFlag[0]); i++) { - if (sPlayerAvatarGfxToStateFlag[gPlayerAvatar.gender][i][1] & flags) - return sPlayerAvatarGfxToStateFlag[gPlayerAvatar.gender][i][0]; + if (sPlayerAvatarGfxToStateFlag[gPlayerAvatar.gender][i].playerFlag & flags) + return sPlayerAvatarGfxToStateFlag[gPlayerAvatar.gender][i].graphicsId; } return 0; } diff --git a/src/field_region_map.c b/src/field_region_map.c index 966d2d1327..f12422cc01 100644 --- a/src/field_region_map.c +++ b/src/field_region_map.c @@ -187,7 +187,7 @@ static void FieldUpdateRegionMap(void) sFieldRegionMapHandler->state++; break; case MAP_INPUT_R_BUTTON: - if (sFieldRegionMapHandler->regionMap.mapSecType == MAPSECTYPE_CITY_CANFLY + if (sFieldRegionMapHandler->regionMap.mapSecType == MAPSECTYPE_CITY_CANFLY && FlagGet(OW_FLAG_POKE_RIDER) && Overworld_MapTypeAllowsTeleportAndFly(gMapHeader.mapType) == TRUE) { PlaySE(SE_SELECT); diff --git a/src/follower_npc.c b/src/follower_npc.c index da002787bc..e7790af4a6 100644 --- a/src/follower_npc.c +++ b/src/follower_npc.c @@ -285,7 +285,7 @@ static bool32 FollowerNPCHasRunningFrames(void) static bool32 IsStateMovement(u32 state) { - switch (state) + switch (state) { case MOVEMENT_ACTION_FACE_DOWN: case MOVEMENT_ACTION_FACE_UP: @@ -510,7 +510,7 @@ static void SetSurfJump(void) SetUpSurfBlobFieldEffect(follower); // Adjust surf head spawn location infront of follower. - switch (direction) + switch (direction) { case DIR_SOUTH: gFieldEffectArguments[1]++; // effect_y @@ -814,7 +814,7 @@ void CreateFollowerNPC(u32 gfx, u32 followerFlags, const u8 *scriptPtr) struct ObjectEvent *player = &gObjectEvents[gPlayerAvatar.objectEventId]; struct ObjectEvent *follower; - struct ObjectEventTemplate npc = + struct ObjectEventTemplate npc = { .localId = OBJ_EVENT_ID_NPC_FOLLOWER, .graphicsId = gfx, @@ -932,7 +932,7 @@ u32 DetermineFollowerNPCState(struct ObjectEvent *follower, u32 state, enum Dire break; } - switch (state) + switch (state) { case MOVEMENT_ACTION_WALK_SLOW_DOWN ... MOVEMENT_ACTION_WALK_SLOW_RIGHT: // Slow walk. @@ -1289,7 +1289,7 @@ void NPCFollow(struct ObjectEvent *npc, u32 state, bool32 ignoreScriptActive) ObjectEventSetHeldMovement(follower, newState); PlayerLogCoordinates(player); - switch (newState) + switch (newState) { case MOVEMENT_ACTION_JUMP_2_DOWN ... MOVEMENT_ACTION_JUMP_2_RIGHT: case MOVEMENT_ACTION_JUMP_DOWN ... MOVEMENT_ACTION_JUMP_RIGHT: @@ -1308,7 +1308,7 @@ void CreateFollowerNPCAvatar(void) return; struct ObjectEvent *player = &gObjectEvents[gPlayerAvatar.objectEventId]; - struct ObjectEventTemplate clone = + struct ObjectEventTemplate clone = { .localId = OBJ_EVENT_ID_NPC_FOLLOWER, .graphicsId = GetFollowerNPCSprite(), @@ -1837,7 +1837,7 @@ void ScriptFaceFollowerNPC(struct ScriptContext *ctx) followerDirection = playerDirection; //Flip direction. - switch (playerDirection) + switch (playerDirection) { case DIR_NORTH: playerDirection = DIR_SOUTH; @@ -1860,7 +1860,7 @@ void ScriptFaceFollowerNPC(struct ScriptContext *ctx) } } -static const u8 *const FollowerNPCHideMovementsSpeedTable[][4] = +static const u8 *const FollowerNPCHideMovementsSpeedTable[][4] = { [DIR_SOUTH] = {Common_Movement_WalkDownSlow, Common_Movement_WalkDown, Common_Movement_WalkDownFast, Common_Movement_WalkDownFaster}, [DIR_NORTH] = {Common_Movement_WalkUpSlow, Common_Movement_WalkUp, Common_Movement_WalkUpFast, Common_Movement_WalkUpFaster}, diff --git a/src/frontier_pass.c b/src/frontier_pass.c index ac87ee5abc..51e305b311 100644 --- a/src/frontier_pass.c +++ b/src/frontier_pass.c @@ -595,7 +595,7 @@ static void LeaveFrontierPass(void) static u32 AllocateFrontierPassData(MainCallback callback) { - // This variable is a MAPSEC initially, but is recycled as a + // This variable is a MAPSEC initially, but is recycled as a // bare integer near the end of the function. mapsec_u8_t i; diff --git a/src/frontier_util.c b/src/frontier_util.c index dbfc7741f2..89a253a6f4 100644 --- a/src/frontier_util.c +++ b/src/frontier_util.c @@ -2643,8 +2643,6 @@ void CreateFrontierBrainPokemon(void) friendship = 0; } SetMonData(&gEnemyParty[monPartyId], MON_DATA_FRIENDSHIP, &friendship); - j = FALSE; - SetMonData(&gPlayerParty[MULTI_PARTY_SIZE + i], MON_DATA_IS_SHINY, &j); CalculateMonStats(&gEnemyParty[monPartyId]); monPartyId++; } diff --git a/src/heal_location.c b/src/heal_location.c index fd376e71e0..d7854dbf75 100644 --- a/src/heal_location.c +++ b/src/heal_location.c @@ -79,7 +79,7 @@ bool32 IsLastHealLocationPlayerHouse() u32 GetHealNpcLocalId(u32 healLocationId) { if (healLocationId == HEAL_LOCATION_NONE || healLocationId >= NUM_HEAL_LOCATIONS) - return 0; + return LOCALID_NONE; return sWhiteoutRespawnHealerNpcIds[healLocationId - 1]; } diff --git a/src/item_use.c b/src/item_use.c index 823a109430..b2fdad5896 100644 --- a/src/item_use.c +++ b/src/item_use.c @@ -222,11 +222,11 @@ static void Task_CloseCantUseKeyItemMessage(u8 taskId) u8 CheckIfItemIsTMHMOrEvolutionStone(enum Item itemId) { if (GetItemFieldFunc(itemId) == ItemUseOutOfBattle_TMHM) - return 1; + return ITEM_IS_TM_HM; else if (GetItemFieldFunc(itemId) == ItemUseOutOfBattle_EvolutionStone) - return 2; + return ITEM_IS_EVOLUTION_STONE; else - return 0; + return ITEM_IS_OTHER; } // Mail in the bag menu can't have a message but it can be checked (view the mail background, no message) diff --git a/src/naming_screen.c b/src/naming_screen.c index 9b967044fd..9b880fb60f 100644 --- a/src/naming_screen.c +++ b/src/naming_screen.c @@ -2592,5 +2592,3 @@ static const struct SpritePalette sSpritePalettes[] = {gNamingScreenMenu_Pal[4], PALTAG_OK_BUTTON}, {} }; - - diff --git a/src/overworld.c b/src/overworld.c index 2685495d60..cf00acc256 100644 --- a/src/overworld.c +++ b/src/overworld.c @@ -706,7 +706,7 @@ static bool32 IsWhiteoutCutscene(void) { if (OW_WHITEOUT_CUTSCENE < GEN_4) return FALSE; - return GetHealNpcLocalId(GetHealLocationIndexByWarpData(&gSaveBlock1Ptr->lastHealLocation)) > 0; + return GetHealNpcLocalId(GetHealLocationIndexByWarpData(&gSaveBlock1Ptr->lastHealLocation)) != LOCALID_NONE; } void SetWarpDestinationToLastHealLocation(void) @@ -717,6 +717,11 @@ void SetWarpDestinationToLastHealLocation(void) sWarpDestination = gSaveBlock1Ptr->lastHealLocation; } +void SetWarpDestinationForTeleport(void) +{ + sWarpDestination = gSaveBlock1Ptr->lastHealLocation; +} + void SetLastHealLocationWarp(u8 healLocationId) { const struct HealLocation *healLocation = GetHealLocation(healLocationId); diff --git a/src/party_menu.c b/src/party_menu.c index 1255b5f210..e9525bfa38 100644 --- a/src/party_menu.c +++ b/src/party_menu.c @@ -1134,10 +1134,10 @@ static bool8 DisplayPartyPokemonDataForMoveTutorOrEvolutionItem(u8 slot) { default: return FALSE; - case 1: // TM/HM + case ITEM_IS_TM_HM: // TM/HM DisplayPartyPokemonDataToTeachMove(slot, ItemIdToBattleMoveId(item)); break; - case 2: // Evolution stone + case ITEM_IS_EVOLUTION_STONE: // Evolution stone if (!GetMonData(currentPokemon, MON_DATA_IS_EGG) && GetEvolutionTargetSpecies(currentPokemon, EVO_MODE_ITEM_CHECK, item, NULL, NULL, CHECK_EVO) != SPECIES_NONE) return FALSE; DisplayPartyPokemonDescriptionData(slot, PARTYBOX_DESC_NO_USE); diff --git a/src/pokenav_region_map.c b/src/pokenav_region_map.c index 5dde2aa9b9..31eb3ac2e1 100755 --- a/src/pokenav_region_map.c +++ b/src/pokenav_region_map.c @@ -220,7 +220,7 @@ static u32 HandleRegionMapInput(struct Pokenav_RegionMapMenu *state) state->callback = GetExitRegionMapMenuId; return POKENAV_MAP_FUNC_EXIT; case MAP_INPUT_R_BUTTON: - if (regionMap->mapSecType == MAPSECTYPE_CITY_CANFLY && FlagGet(OW_FLAG_POKE_RIDER) + if (regionMap->mapSecType == MAPSECTYPE_CITY_CANFLY && FlagGet(OW_FLAG_POKE_RIDER) && Overworld_MapTypeAllowsTeleportAndFly(gMapHeader.mapType) == TRUE) return POKENAV_MAP_FUNC_FLY; } @@ -772,7 +772,7 @@ void UpdateRegionMapHelpBarText(void) { struct RegionMap* regionMap = GetSubstructPtr(POKENAV_SUBSTRUCT_REGION_MAP); - if (regionMap->mapSecType == MAPSECTYPE_CITY_CANFLY && FlagGet(OW_FLAG_POKE_RIDER) + if (regionMap->mapSecType == MAPSECTYPE_CITY_CANFLY && FlagGet(OW_FLAG_POKE_RIDER) && Overworld_MapTypeAllowsTeleportAndFly(gMapHeader.mapType) == TRUE) { if (IsRegionMapZoomed()) diff --git a/src/type_icons.c b/src/type_icons.c index 21cdbe32a7..5874cc172a 100644 --- a/src/type_icons.c +++ b/src/type_icons.c @@ -239,7 +239,7 @@ void LoadTypeIcons(u32 battler) struct Pokemon* mon = GetBattlerMon(battler); u32 species = GetMonData(mon, MON_DATA_SPECIES); - if (B_SHOW_TYPES == SHOW_TYPES_NEVER + if (B_SHOW_TYPES == SHOW_TYPES_NEVER || (B_SHOW_TYPES == SHOW_TYPES_SEEN && !GetSetPokedexFlag(SpeciesToNationalPokedexNum(species), FLAG_GET_SEEN))) return; @@ -562,4 +562,3 @@ static s32 GetTypeIconBounceMovement(s32 originalY, u32 position) struct Sprite *healthbox = &gSprites[gHealthboxSpriteIds[GetBattlerAtPosition(position)]]; return originalY + healthbox->y2; } - diff --git a/test/battle/ability/aerilate.c b/test/battle/ability/aerilate.c index d9eede03c8..c34c3e3a76 100644 --- a/test/battle/ability/aerilate.c +++ b/test/battle/ability/aerilate.c @@ -162,22 +162,124 @@ SINGLE_BATTLE_TEST("Aerilate doesn't affect Hidden Power's type") ASSUME(GetMoveEffect(MOVE_HIDDEN_POWER) == EFFECT_HIDDEN_POWER); ASSUME(gTypesInfo[TYPE_ELECTRIC].isHiddenPowerType == TRUE); ASSUME(GetSpeciesType(SPECIES_DIGLETT, 0) == TYPE_GROUND); - PLAYER(SPECIES_PINSIR) { Ability(ABILITY_AERILATE); HPIV(31); AttackIV(31); DefenseIV(31); SpAttackIV(30); SpDefenseIV(31); SpeedIV(31); } // HP Electric + PLAYER(SPECIES_PINSIR) { Item(ITEM_PINSIRITE); HPIV(31); AttackIV(31); DefenseIV(31); SpAttackIV(30); SpDefenseIV(31); SpeedIV(31); } // HP Electric OPPONENT(SPECIES_DIGLETT); } WHEN { - TURN { MOVE(player, MOVE_HIDDEN_POWER); } + TURN { MOVE(player, MOVE_HIDDEN_POWER, gimmick: GIMMICK_MEGA); } } SCENE { NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_HIDDEN_POWER, player); } MESSAGE("It doesn't affect the opposing Diglett…"); } } -TO_DO_BATTLE_TEST("Aerilate doesn't override Electrify (Gen7+)"); // No mon with Aerilate exists in Gen8+, but probably behaves similar to Pixilate, which does. -TO_DO_BATTLE_TEST("Aerilate doesn't override Ion Deluge (Gen7+)"); // Ion Deluge doesn't exist in Gen 8+, but we probably could assume it behaves similar to under Electrify. TODO: Test by hacking SV. -TO_DO_BATTLE_TEST("Aerilate overrides Electrify (Gen6)") -TO_DO_BATTLE_TEST("Aerilate overrides Ion Deluge (Gen6)") -TO_DO_BATTLE_TEST("Aerilate doesn't affect Tera Starstorm's type"); +SINGLE_BATTLE_TEST("Aerilate doesn't override Electrify") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ELECTRIFY) == EFFECT_ELECTRIFY); + ASSUME(GetSpeciesType(SPECIES_SANDSHREW, 0) == TYPE_GROUND || GetSpeciesType(SPECIES_SANDSHREW, 1) == TYPE_GROUND); + PLAYER(SPECIES_PINSIR) { Item(ITEM_PINSIRITE); Speed(1); } + OPPONENT(SPECIES_SANDSHREW) { Moves(MOVE_ELECTRIFY); Speed(10); } + } WHEN { + TURN { MOVE(opponent, MOVE_ELECTRIFY); MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_MEGA); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIFY, opponent); + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); } + MESSAGE("It doesn't affect the opposing Sandshrew…"); + } +} + +SINGLE_BATTLE_TEST("Aerilate overrides Ion Deluge") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ION_DELUGE) == EFFECT_ION_DELUGE); + ASSUME(GetSpeciesType(SPECIES_MACHOP, 0) == TYPE_FIGHTING || GetSpeciesType(SPECIES_MACHOP, 1) == TYPE_FIGHTING); + PLAYER(SPECIES_PINSIR) { Item(ITEM_PINSIRITE); Speed(1); } + OPPONENT(SPECIES_MACHOP) { Moves(MOVE_ION_DELUGE); Speed(10); } + } WHEN { + TURN { MOVE(opponent, MOVE_ION_DELUGE); MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_MEGA); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ION_DELUGE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Aerilate changes Tera Blast's type when not Terastallized") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TERA_BLAST) == EFFECT_TERA_BLAST); + ASSUME(GetMoveType(MOVE_TERA_BLAST) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_MACHOP, 0) == TYPE_FIGHTING || GetSpeciesType(SPECIES_MACHOP, 1) == TYPE_FIGHTING); + ASSUME(GetMoveEffect(MOVE_SKILL_SWAP) == EFFECT_SKILL_SWAP); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SALAMENCE) { Item(ITEM_SALAMENCITE); Moves(MOVE_SKILL_SWAP); } + OPPONENT(SPECIES_MACHOP); + } WHEN { + TURN { MOVE(opponent, MOVE_SKILL_SWAP, gimmick: GIMMICK_MEGA, target: player); } + TURN { SWITCH(opponent, 1); MOVE(player, MOVE_TERA_BLAST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Aerilate doesn't change Tera Blast's type when Terastallized") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TERA_BLAST) == EFFECT_TERA_BLAST); + ASSUME(GetMoveType(MOVE_TERA_BLAST) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_MISDREAVUS, 0) == TYPE_GHOST); + ASSUME(GetMoveEffect(MOVE_SKILL_SWAP) == EFFECT_SKILL_SWAP); + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_NORMAL); } + OPPONENT(SPECIES_SALAMENCE) { Item(ITEM_SALAMENCITE); Moves(MOVE_SKILL_SWAP); } + OPPONENT(SPECIES_MISDREAVUS); + } WHEN { + TURN { MOVE(opponent, MOVE_SKILL_SWAP, gimmick: GIMMICK_MEGA, target: player); } + TURN { SWITCH(opponent, 1); MOVE(player, MOVE_TERA_BLAST, gimmick: GIMMICK_TERA); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); } + MESSAGE("It doesn't affect the opposing Misdreavus…"); + } +} + +SINGLE_BATTLE_TEST("Aerilate doesn't affect Terrain Pulse's type") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TERRAIN_PULSE) == EFFECT_TERRAIN_PULSE); + ASSUME(GetMoveType(MOVE_TERRAIN_PULSE) == TYPE_NORMAL); + ASSUME(GetMoveEffect(MOVE_SKILL_SWAP) == EFFECT_SKILL_SWAP); + ASSUME(GetSpeciesType(SPECIES_SANDSHREW, 0) == TYPE_GROUND || GetSpeciesType(SPECIES_SANDSHREW, 1) == TYPE_GROUND); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SALAMENCE) { Item(ITEM_SALAMENCITE); Moves(MOVE_SKILL_SWAP); } + OPPONENT(SPECIES_SANDSHREW); + } WHEN { + TURN { MOVE(opponent, MOVE_SKILL_SWAP, gimmick: GIMMICK_MEGA, target: player); MOVE(player, MOVE_ELECTRIC_TERRAIN); } + TURN { SWITCH(opponent, 1); MOVE(player, MOVE_TERRAIN_PULSE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, player); + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_TERRAIN_PULSE, player); } + MESSAGE("It doesn't affect the opposing Sandshrew…"); + } +} + +SINGLE_BATTLE_TEST("Aerilate doesn't affect damaging Z-Move types") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_MACHOP, 0) == TYPE_FIGHTING || GetSpeciesType(SPECIES_MACHOP, 1) == TYPE_FIGHTING); + ASSUME(GetMoveEffect(MOVE_SKILL_SWAP) == EFFECT_SKILL_SWAP); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_SALAMENCE) { Item(ITEM_SALAMENCITE); Moves(MOVE_SKILL_SWAP); } + OPPONENT(SPECIES_MACHOP); + } WHEN { + TURN { MOVE(opponent, MOVE_SKILL_SWAP, gimmick: GIMMICK_MEGA, target: player); } + TURN { SWITCH(opponent, 1); MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + NOT { MESSAGE("It's super effective!"); } + } +} + TO_DO_BATTLE_TEST("Aerilate doesn't affect Max Strike's type"); -TO_DO_BATTLE_TEST("Aerilate doesn't affect Terrain Pulse's type"); -TO_DO_BATTLE_TEST("Aerilate doesn't affect damaging Z-Move types"); TO_DO_BATTLE_TEST("(DYNAMAX) Aerilate turns Max Strike into Max Airstream"); // All other -ate abilities do this, so interpolating this as no Aerilate mon is available in a Dynamax game diff --git a/test/battle/ability/galvanize.c b/test/battle/ability/galvanize.c index e672457a37..248246d3d1 100644 --- a/test/battle/ability/galvanize.c +++ b/test/battle/ability/galvanize.c @@ -164,9 +164,72 @@ SINGLE_BATTLE_TEST("Galvanize doesn't affect Hidden Power's type") } } -TO_DO_BATTLE_TEST("Galvanize doesn't affect Tera Starstorm's type"); +SINGLE_BATTLE_TEST("Galvanize changes Tera Blast's type when not Terastallized") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TERA_BLAST) == EFFECT_TERA_BLAST); + ASSUME(GetMoveType(MOVE_TERA_BLAST) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_ROOKIDEE, 0) == TYPE_FLYING || GetSpeciesType(SPECIES_ROOKIDEE, 1) == TYPE_FLYING); + PLAYER(SPECIES_GEODUDE_ALOLA) { Ability(ABILITY_GALVANIZE); } + OPPONENT(SPECIES_ROOKIDEE); + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Galvanize doesn't change Tera Blast's type when Terastallized") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TERA_BLAST) == EFFECT_TERA_BLAST); + ASSUME(GetMoveType(MOVE_TERA_BLAST) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_SANDSHREW, 0) == TYPE_GROUND || GetSpeciesType(SPECIES_SANDSHREW, 1) == TYPE_GROUND); + PLAYER(SPECIES_GEODUDE_ALOLA) { Ability(ABILITY_GALVANIZE); TeraType(TYPE_NORMAL); } + OPPONENT(SPECIES_SANDSHREW); + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST, gimmick: GIMMICK_TERA); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + NOT { MESSAGE("It doesn't affect the opposing Sandshrew…"); } + } +} + +SINGLE_BATTLE_TEST("Galvanize doesn't affect Terrain Pulse's type") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TERRAIN_PULSE) == EFFECT_TERRAIN_PULSE); + ASSUME(GetMoveType(MOVE_TERRAIN_PULSE) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_SANDSHREW, 0) == TYPE_GROUND || GetSpeciesType(SPECIES_SANDSHREW, 1) == TYPE_GROUND); + PLAYER(SPECIES_GEODUDE_ALOLA) { Ability(ABILITY_GALVANIZE); } + OPPONENT(SPECIES_SANDSHREW); + } WHEN { + TURN { MOVE(opponent, MOVE_GRASSY_TERRAIN); MOVE(player, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_TERRAIN_PULSE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASSY_TERRAIN, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERRAIN_PULSE, player); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Galvanize doesn't affect damaging Z-Move types") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_GYARADOS, 0) == TYPE_WATER || GetSpeciesType(SPECIES_GYARADOS, 1) == TYPE_WATER); + PLAYER(SPECIES_GEODUDE_ALOLA) { Ability(ABILITY_GALVANIZE); Item(ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_GYARADOS); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + NOT { MESSAGE("It's super effective!"); } + } +} + TO_DO_BATTLE_TEST("Galvanize doesn't affect Max Strike's type"); -TO_DO_BATTLE_TEST("Galvanize doesn't affect Terrain Pulse's type"); -TO_DO_BATTLE_TEST("Galvanize doesn't affect damaging Z-Move types"); TO_DO_BATTLE_TEST("(DYNAMAX) Galvanize turns Max Strike into Max Lightning when not used by Gigantamax Pikachu/Toxtricity"); //TO_DO_BATTLE_TEST("(DYNAMAX) Galvanize doesn't turn Max Strike into Max Lightning when used by Gigantamax Pikachu/Toxtricity, instead becoming G-Max Volt Crash/Stun Shock"); // Marked in Bulbapedia as "needs research", so this assumes that it behaves like Pixilate. diff --git a/test/battle/ability/normalize.c b/test/battle/ability/normalize.c index b85db768dd..5c96cb58d0 100644 --- a/test/battle/ability/normalize.c +++ b/test/battle/ability/normalize.c @@ -274,7 +274,72 @@ SINGLE_BATTLE_TEST("Normalize doesn't affect Hidden Power's type") } } -TO_DO_BATTLE_TEST("Aerilate doesn't affect Tera Starstorm's type"); -TO_DO_BATTLE_TEST("Normalize makes Flying Press do Normal/Flying damage"); -TO_DO_BATTLE_TEST("Normalize doesn't affect Terrain Pulse's type"); -TO_DO_BATTLE_TEST("Normalize doesn't affect damaging Z-Move types"); +SINGLE_BATTLE_TEST("Normalize doesn't change Tera Blast's type when Terastallized") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TERA_BLAST) == EFFECT_TERA_BLAST); + ASSUME(GetMoveType(MOVE_TERA_BLAST) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_MISDREAVUS, 0) == TYPE_GHOST); + PLAYER(SPECIES_SKITTY) { Ability(ABILITY_NORMALIZE); TeraType(TYPE_DARK); } + OPPONENT(SPECIES_MISDREAVUS); + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST, gimmick: GIMMICK_TERA); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Normalize makes Flying Press do Normal/Flying damage") +{ + enum Ability ability; + PARAMETRIZE { ability = ABILITY_CUTE_CHARM; } + PARAMETRIZE { ability = ABILITY_NORMALIZE; } + GIVEN { + ASSUME(GetSpeciesType(SPECIES_GOLEM, 0) == TYPE_ROCK || GetSpeciesType(SPECIES_GOLEM, 1) == TYPE_ROCK); + PLAYER(SPECIES_SKITTY) { Ability(ability); Moves(MOVE_FLYING_PRESS); } + OPPONENT(SPECIES_GOLEM); + } WHEN { + TURN { MOVE(player, MOVE_FLYING_PRESS); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLYING_PRESS, player); + if (ability == ABILITY_NORMALIZE) + MESSAGE("It's not very effective…"); + else + NOT { MESSAGE("It's not very effective…"); } + } +} + +SINGLE_BATTLE_TEST("Normalize doesn't affect Terrain Pulse's type") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TERRAIN_PULSE) == EFFECT_TERRAIN_PULSE); + ASSUME(GetMoveType(MOVE_TERRAIN_PULSE) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_SANDSHREW, 0) == TYPE_GROUND || GetSpeciesType(SPECIES_SANDSHREW, 1) == TYPE_GROUND); + PLAYER(SPECIES_SKITTY) { Ability(ABILITY_NORMALIZE); } + OPPONENT(SPECIES_SANDSHREW); + } WHEN { + TURN { MOVE(opponent, MOVE_ELECTRIC_TERRAIN); MOVE(player, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_TERRAIN_PULSE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, opponent); + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_TERRAIN_PULSE, player); } + MESSAGE("It doesn't affect the opposing Sandshrew…"); + } +} + +SINGLE_BATTLE_TEST("Normalize doesn't affect damaging Z-Move types") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_WATER_GUN) == TYPE_WATER); + ASSUME(GetSpeciesType(SPECIES_GOLEM, 0) == TYPE_ROCK || GetSpeciesType(SPECIES_GOLEM, 1) == TYPE_ROCK); + PLAYER(SPECIES_SKITTY) { Ability(ABILITY_NORMALIZE); Item(ITEM_WATERIUM_Z); Moves(MOVE_WATER_GUN); } + OPPONENT(SPECIES_GOLEM); + } WHEN { + TURN { MOVE(player, MOVE_WATER_GUN, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYDRO_VORTEX, player); + MESSAGE("It's super effective!"); + } +} diff --git a/test/battle/ability/pixilate.c b/test/battle/ability/pixilate.c index ed9fa65913..58ae555306 100644 --- a/test/battle/ability/pixilate.c +++ b/test/battle/ability/pixilate.c @@ -138,13 +138,104 @@ SINGLE_BATTLE_TEST("Pixilate doesn't affect Hidden Power's type") } } -TO_DO_BATTLE_TEST("Pixilate doesn't override Electrify (Gen7+)"); -TO_DO_BATTLE_TEST("Pixilate doesn't override Ion Deluge (Gen7+)"); // Ion Deluge doesn't exist in Gen 8+, but we probably could assume it behaves similar to under Electrify. TODO: Test by hacking SV. -TO_DO_BATTLE_TEST("Pixilate overrides Electrify (Gen6)") -TO_DO_BATTLE_TEST("Pixilate overrides Ion Deluge (Gen6)") -TO_DO_BATTLE_TEST("Pixilate doesn't affect Tera Starstorm's type"); +SINGLE_BATTLE_TEST("Pixilate doesn't override Electrify") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ELECTRIFY) == EFFECT_ELECTRIFY); + ASSUME(GetSpeciesType(SPECIES_SANDSHREW, 0) == TYPE_GROUND || GetSpeciesType(SPECIES_SANDSHREW, 1) == TYPE_GROUND); + PLAYER(SPECIES_SYLVEON) { Ability(ABILITY_PIXILATE); } + OPPONENT(SPECIES_SANDSHREW); + } WHEN { + TURN { MOVE(opponent, MOVE_ELECTRIFY); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIFY, opponent); + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); } + MESSAGE("It doesn't affect the opposing Sandshrew…"); + } +} + +SINGLE_BATTLE_TEST("Pixilate overrides Ion Deluge") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ION_DELUGE) == EFFECT_ION_DELUGE); + ASSUME(GetSpeciesType(SPECIES_BAGON, 0) == TYPE_DRAGON || GetSpeciesType(SPECIES_BAGON, 1) == TYPE_DRAGON); + PLAYER(SPECIES_SYLVEON) { Ability(ABILITY_PIXILATE); } + OPPONENT(SPECIES_BAGON); + } WHEN { + TURN { MOVE(opponent, MOVE_ION_DELUGE); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ION_DELUGE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Pixilate changes Tera Blast's type when not Terastallized") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TERA_BLAST) == EFFECT_TERA_BLAST); + ASSUME(GetMoveType(MOVE_TERA_BLAST) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_MACHOP, 0) == TYPE_FIGHTING || GetSpeciesType(SPECIES_MACHOP, 1) == TYPE_FIGHTING); + PLAYER(SPECIES_SYLVEON) { Ability(ABILITY_PIXILATE); } + OPPONENT(SPECIES_MACHOP); + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Pixilate doesn't change Tera Blast's type when Terastallized") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TERA_BLAST) == EFFECT_TERA_BLAST); + ASSUME(GetMoveType(MOVE_TERA_BLAST) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_MISDREAVUS, 0) == TYPE_GHOST); + PLAYER(SPECIES_SYLVEON) { Ability(ABILITY_PIXILATE); TeraType(TYPE_NORMAL); } + OPPONENT(SPECIES_MISDREAVUS); + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST, gimmick: GIMMICK_TERA); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); } + MESSAGE("It doesn't affect the opposing Misdreavus…"); + } +} + +SINGLE_BATTLE_TEST("Pixilate doesn't affect Terrain Pulse's type") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TERRAIN_PULSE) == EFFECT_TERRAIN_PULSE); + ASSUME(GetMoveType(MOVE_TERRAIN_PULSE) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_SANDSHREW, 0) == TYPE_GROUND || GetSpeciesType(SPECIES_SANDSHREW, 1) == TYPE_GROUND); + PLAYER(SPECIES_SYLVEON) { Ability(ABILITY_PIXILATE); } + OPPONENT(SPECIES_SANDSHREW); + } WHEN { + TURN { MOVE(opponent, MOVE_ELECTRIC_TERRAIN); MOVE(player, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_TERRAIN_PULSE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, opponent); + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_TERRAIN_PULSE, player); } + MESSAGE("It doesn't affect the opposing Sandshrew…"); + } +} + +SINGLE_BATTLE_TEST("Pixilate doesn't affect damaging Z-Move types") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_BAGON, 0) == TYPE_DRAGON || GetSpeciesType(SPECIES_BAGON, 1) == TYPE_DRAGON); + PLAYER(SPECIES_SYLVEON) { Ability(ABILITY_PIXILATE); Item(ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_BAGON); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + NOT { MESSAGE("It's super effective!"); } + } +} + TO_DO_BATTLE_TEST("Pixilate doesn't affect Max Strike's type"); -TO_DO_BATTLE_TEST("Pixilate doesn't affect Terrain Pulse's type"); -TO_DO_BATTLE_TEST("Pixilate doesn't affect damaging Z-Move types"); TO_DO_BATTLE_TEST("(DYNAMAX) Pixilate turns Max Strike into Max Starfall when not used by Gigantamax Alcremie"); TO_DO_BATTLE_TEST("(DYNAMAX) Pixilate doesn't turn Max Strike into Max Starfall when used by Gigantamax Alcremie, instead becoming G-Max Finale"); diff --git a/test/battle/ability/refrigerate.c b/test/battle/ability/refrigerate.c index 834254f26b..15c009ed71 100644 --- a/test/battle/ability/refrigerate.c +++ b/test/battle/ability/refrigerate.c @@ -137,13 +137,104 @@ SINGLE_BATTLE_TEST("Refrigerate doesn't affect Hidden Power's type") } } -TO_DO_BATTLE_TEST("Refrigerate doesn't override Electrify (Gen7+)"); // No mon with Refrigerate exists in Gen8+, but probably behaves similar to Pixilate, which does. -TO_DO_BATTLE_TEST("Refrigerate doesn't override Ion Deluge (Gen7+)"); // Ion Deluge doesn't exist in Gen 8+, but we probably could assume it behaves similar to under Electrify. TODO: Test by hacking SV. -TO_DO_BATTLE_TEST("Refrigerate overrides Electrify (Gen6)") -TO_DO_BATTLE_TEST("Refrigerate overrides Ion Deluge (Gen6)") -TO_DO_BATTLE_TEST("Refrigerate doesn't affect Tera Starstorm's type"); +SINGLE_BATTLE_TEST("Refrigerate doesn't override Electrify") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ELECTRIFY) == EFFECT_ELECTRIFY); + ASSUME(GetSpeciesType(SPECIES_SANDSHREW, 0) == TYPE_GROUND || GetSpeciesType(SPECIES_SANDSHREW, 1) == TYPE_GROUND); + PLAYER(SPECIES_AMAURA) { Ability(ABILITY_REFRIGERATE); } + OPPONENT(SPECIES_SANDSHREW); + } WHEN { + TURN { MOVE(opponent, MOVE_ELECTRIFY); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIFY, opponent); + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); } + MESSAGE("It doesn't affect the opposing Sandshrew…"); + } +} + +SINGLE_BATTLE_TEST("Refrigerate overrides Ion Deluge") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_ION_DELUGE) == EFFECT_ION_DELUGE); + ASSUME(GetSpeciesType(SPECIES_SANDSHREW, 0) == TYPE_GROUND || GetSpeciesType(SPECIES_SANDSHREW, 1) == TYPE_GROUND); + PLAYER(SPECIES_AMAURA) { Ability(ABILITY_REFRIGERATE); } + OPPONENT(SPECIES_SANDSHREW); + } WHEN { + TURN { MOVE(opponent, MOVE_ION_DELUGE); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ION_DELUGE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Refrigerate changes Tera Blast's type when not Terastallized") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TERA_BLAST) == EFFECT_TERA_BLAST); + ASSUME(GetMoveType(MOVE_TERA_BLAST) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_CHARMANDER, 0) == TYPE_FIRE || GetSpeciesType(SPECIES_CHARMANDER, 1) == TYPE_FIRE); + PLAYER(SPECIES_AMAURA) { Ability(ABILITY_REFRIGERATE); } + OPPONENT(SPECIES_CHARMANDER); + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + MESSAGE("It's not very effective…"); + } +} + +SINGLE_BATTLE_TEST("Refrigerate doesn't change Tera Blast's type when Terastallized") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TERA_BLAST) == EFFECT_TERA_BLAST); + ASSUME(GetMoveType(MOVE_TERA_BLAST) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_MISDREAVUS, 0) == TYPE_GHOST); + PLAYER(SPECIES_AMAURA) { Ability(ABILITY_REFRIGERATE); TeraType(TYPE_NORMAL); } + OPPONENT(SPECIES_MISDREAVUS); + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST, gimmick: GIMMICK_TERA); } + } SCENE { + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); } + MESSAGE("It doesn't affect the opposing Misdreavus…"); + } +} + +SINGLE_BATTLE_TEST("Refrigerate doesn't affect Terrain Pulse's type") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_TERRAIN_PULSE) == EFFECT_TERRAIN_PULSE); + ASSUME(GetMoveType(MOVE_TERRAIN_PULSE) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_SANDSHREW, 0) == TYPE_GROUND || GetSpeciesType(SPECIES_SANDSHREW, 1) == TYPE_GROUND); + PLAYER(SPECIES_AMAURA) { Ability(ABILITY_REFRIGERATE); } + OPPONENT(SPECIES_SANDSHREW); + } WHEN { + TURN { MOVE(opponent, MOVE_ELECTRIC_TERRAIN); MOVE(player, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_TERRAIN_PULSE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, opponent); + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_TERRAIN_PULSE, player); } + MESSAGE("It doesn't affect the opposing Sandshrew…"); + } +} + +SINGLE_BATTLE_TEST("Refrigerate doesn't affect damaging Z-Move types") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + ASSUME(GetSpeciesType(SPECIES_BAGON, 0) == TYPE_DRAGON || GetSpeciesType(SPECIES_BAGON, 1) == TYPE_DRAGON); + PLAYER(SPECIES_AMAURA) { Ability(ABILITY_REFRIGERATE); Item(ITEM_NORMALIUM_Z); } + OPPONENT(SPECIES_BAGON); + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_Z_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BREAKNECK_BLITZ, player); + NOT { MESSAGE("It's super effective!"); } + } +} + TO_DO_BATTLE_TEST("Refrigerate doesn't affect Max Strike's type"); -TO_DO_BATTLE_TEST("Refrigerate doesn't affect Terrain Pulse's type"); -TO_DO_BATTLE_TEST("Refrigerate doesn't affect damaging Z-Move types"); TO_DO_BATTLE_TEST("(DYNAMAX) Refrigerate turns Max Strike into Max Hailstorm when not used by Gigantamax Lapras"); //TO_DO_BATTLE_TEST("(DYNAMAX) Refrigerate doesn't turn Max Strike into Max Hailstorm when used by Gigantamax Lapras, instead becoming G-Max Resonance"); // Marked in Bulbapedia as "needs research", so this assumes that it behaves like Pixilate. diff --git a/test/battle/ai/ai_doubles.c b/test/battle/ai/ai_doubles.c index b94e7cec71..b8b44eb3f0 100644 --- a/test/battle/ai/ai_doubles.c +++ b/test/battle/ai/ai_doubles.c @@ -946,27 +946,33 @@ AI_DOUBLE_BATTLE_TEST("AI does not use Helping Hand on Good as Gold ally") } } -AI_DOUBLE_BATTLE_TEST("AI uses Tailwind") +AI_DOUBLE_BATTLE_TEST("AI uses Tailwind based on speed matchups") { u32 speed1, speed2, speed3, speed4; + bool32 expectTailwind; - PARAMETRIZE { speed1 = 20; speed2 = 20; speed3 = 20; speed4 = 20; } - PARAMETRIZE { speed1 = 20; speed2 = 20; speed3 = 5; speed4 = 5; } - PARAMETRIZE { speed1 = 20; speed2 = 20; speed3 = 15; speed4 = 15; } - PARAMETRIZE { speed1 = 1; speed2 = 1; speed3 = 5; speed4 = 5; } - PARAMETRIZE { speed1 = 1; speed2 = 20; speed3 = 15; speed4 = 15; } - PARAMETRIZE { speed1 = 1; speed2 = 20; speed3 = 20; speed4 = 15; } + // All four comparisons qualify -> tailwindScore = 5 + PARAMETRIZE { speed1 = 20; speed2 = 20; speed3 = 20; speed4 = 20; expectTailwind = TRUE; } + // Only the attacker flips one foe matchup -> tailwindScore = 2 + PARAMETRIZE { speed1 = 20; speed2 = 40; speed3 = 20; speed4 = 50; expectTailwind = TRUE; } + // Only the partner flips one foe matchup -> tailwindScore = 2 + PARAMETRIZE { speed1 = 10; speed2 = 29; speed3 = 50; speed4 = 15; expectTailwind = TRUE; } + // Too slow: even after doubling, still slower than both foes -> tailwindScore = 0. + PARAMETRIZE { speed1 = 40; speed2 = 40; speed3 = 10; speed4 = 10; expectTailwind = FALSE; } + // Already faster: Tailwind doesn't improve matchups -> tailwindScore = 0. + PARAMETRIZE { speed1 = 5; speed2 = 5; speed3 = 10; speed4 = 10; expectTailwind = FALSE; } + // Boundary: speed*2 == foe speed does not count -> tailwindScore = 0. + PARAMETRIZE { speed1 = 20; speed2 = 20; speed3 = 10; speed4 = 30; expectTailwind = FALSE; } GIVEN { - ASSUME(GetMoveEffect(MOVE_AFTER_YOU) == EFFECT_AFTER_YOU); - ASSUME(GetMoveEffect(MOVE_TRICK_ROOM) == EFFECT_TRICK_ROOM); + ASSUME(GetMoveEffect(MOVE_TAILWIND) == EFFECT_TAILWIND); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_DOUBLE_BATTLE); PLAYER(SPECIES_WOBBUFFET) { Speed(speed1); } PLAYER(SPECIES_WOBBUFFET) { Speed(speed2); } OPPONENT(SPECIES_WOBBUFFET) { Speed(speed3); Moves(MOVE_TAILWIND, MOVE_HEADBUTT); } OPPONENT(SPECIES_WOBBUFFET) { Speed(speed4); Moves(MOVE_TAILWIND, MOVE_HEADBUTT); } } WHEN { - if (speed3 > 10) + if (expectTailwind) TURN { EXPECT_MOVE(opponentLeft, MOVE_TAILWIND); } else TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_TAILWIND); } @@ -1093,3 +1099,29 @@ AI_DOUBLE_BATTLE_TEST("AI prefers to Fake Out the opponent vulnerable to flinchi TURN { EXPECT_MOVE(opponentLeft, MOVE_FAKE_OUT, target:playerRight); } } } + +AI_DOUBLE_BATTLE_TEST("AI uses Gear Up") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, MOVE_CELEBRATE); } + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, MOVE_CELEBRATE); } + OPPONENT(SPECIES_KLINKLANG) { Ability(ABILITY_PLUS); Moves(MOVE_GEAR_UP, MOVE_WATER_GUN, MOVE_POUND); } + OPPONENT(SPECIES_KLINKLANG) { Ability(ABILITY_PLUS); Moves(MOVE_GEAR_UP, MOVE_WATER_GUN, MOVE_POUND); } + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, MOVE_GEAR_UP); } + } +} + +AI_DOUBLE_BATTLE_TEST("AI uses Magnetic Flux") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, MOVE_CELEBRATE); } + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, MOVE_CELEBRATE); } + OPPONENT(SPECIES_KLINK) { Ability(ABILITY_PLUS); Moves(MOVE_MAGNETIC_FLUX, MOVE_POUND); } + OPPONENT(SPECIES_KLINK) { Ability(ABILITY_PLUS); Moves(MOVE_MAGNETIC_FLUX, MOVE_POUND); } + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, MOVE_MAGNETIC_FLUX); } + } +} diff --git a/test/battle/ai/ai_multi.c b/test/battle/ai/ai_multi.c index b61aefa361..70d28e34e3 100644 --- a/test/battle/ai/ai_multi.c +++ b/test/battle/ai/ai_multi.c @@ -235,7 +235,7 @@ AI_MULTI_BATTLE_TEST("Pollen Puff: AI correctly scores moves with EFFECT_HIT_ENE MULTI_OPPONENT_A(SPECIES_WOBBUFFET) { Speed(1); HP(50); Moves(MOVE_POLLEN_PUFF); } MULTI_OPPONENT_B(SPECIES_WOBBUFFET) { Speed(1); HP(50); Moves(MOVE_POLLEN_PUFF); } } WHEN { - TURN { + TURN { // Targeting ally SCORE_EQ_VAL(opponentLeft, MOVE_POLLEN_PUFF, AI_SCORE_DEFAULT + WEAK_EFFECT, target:opponentRight); SCORE_EQ_VAL(playerRight, MOVE_POLLEN_PUFF, AI_SCORE_DEFAULT + WEAK_EFFECT, target:playerLeft); diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index f96f64c1f7..eabab5af5b 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -1157,15 +1157,18 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has an PARAMETRIZE { aiMon = SPECIES_JOLTEON; absorbingAbility = ABILITY_VOLT_ABSORB; move = MOVE_THUNDERBOLT; } PARAMETRIZE { aiMon = SPECIES_ELECTIVIRE; absorbingAbility = ABILITY_MOTOR_DRIVE; move = MOVE_THUNDERBOLT; } PARAMETRIZE { aiMon = SPECIES_MANECTRIC; absorbingAbility = ABILITY_LIGHTNING_ROD; move = MOVE_THUNDERBOLT; } - PARAMETRIZE { aiMon = SPECIES_ELECTIVIRE; absorbingAbility = ABILITY_MOTOR_DRIVE; move = MOVE_THUNDERBOLT; } PARAMETRIZE { aiMon = SPECIES_AZUMARILL; absorbingAbility = ABILITY_SAP_SIPPER; move = MOVE_GIGA_DRAIN; } PARAMETRIZE { aiMon = SPECIES_ORTHWORM; absorbingAbility = ABILITY_EARTH_EATER; move = MOVE_EARTHQUAKE; } PARAMETRIZE { aiMon = SPECIES_BRONZONG; absorbingAbility = ABILITY_LEVITATE; move = MOVE_EARTHQUAKE; } PARAMETRIZE { aiMon = SPECIES_ELECTRODE; absorbingAbility = ABILITY_SOUNDPROOF; move = MOVE_HYPER_VOICE; } PARAMETRIZE { aiMon = SPECIES_CHESNAUGHT; absorbingAbility = ABILITY_BULLETPROOF; move = MOVE_SLUDGE_BOMB; } PARAMETRIZE { aiMon = SPECIES_BRAMBLEGHAST; absorbingAbility = ABILITY_WIND_RIDER; move = MOVE_HURRICANE; } + PARAMETRIZE { aiMon = SPECIES_BRAMBLEGHAST; absorbingAbility = ABILITY_WIND_RIDER; move = MOVE_HEAT_WAVE; } + PARAMETRIZE { aiMon = SPECIES_SHELLDER; absorbingAbility = ABILITY_OVERCOAT; move = MOVE_MAGIC_POWDER; } + PARAMETRIZE { aiMon = SPECIES_SHELLDER; absorbingAbility = ABILITY_OVERCOAT; move = MOVE_STUN_SPORE; } GIVEN { WITH_CONFIG(CONFIG_REDIRECT_ABILITY_IMMUNITY, GEN_5); + WITH_CONFIG(CONFIG_POWDER_OVERCOAT, GEN_6); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING); PLAYER(SPECIES_ZIGZAGOON) { Moves(move); } OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } diff --git a/test/battle/form_change/end_battle.c b/test/battle/form_change/end_battle.c index d4f29e4315..c3ae62f77d 100644 --- a/test/battle/form_change/end_battle.c +++ b/test/battle/form_change/end_battle.c @@ -259,13 +259,13 @@ SINGLE_BATTLE_TEST("Morpeko Hangry reverts to Full Belly Form upon battle end af SINGLE_BATTLE_TEST("Ogerpon reverts to the correct form upon battle end after terastallizing") { - u32 species; - PARAMETRIZE { species = SPECIES_OGERPON_TEAL; } - PARAMETRIZE { species = SPECIES_OGERPON_WELLSPRING; } - PARAMETRIZE { species = SPECIES_OGERPON_HEARTHFLAME; } - PARAMETRIZE { species = SPECIES_OGERPON_CORNERSTONE; } + u32 species, item; + PARAMETRIZE { species = SPECIES_OGERPON_TEAL; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_OGERPON_WELLSPRING; item = ITEM_WELLSPRING_MASK; } + PARAMETRIZE { species = SPECIES_OGERPON_HEARTHFLAME; item = ITEM_HEARTHFLAME_MASK; } + PARAMETRIZE { species = SPECIES_OGERPON_CORNERSTONE; item = ITEM_CORNERSTONE_MASK; } GIVEN { - PLAYER(species); + PLAYER(species) { Item(item); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } diff --git a/test/battle/form_change/faint.c b/test/battle/form_change/faint.c index 4f66dc84c1..b0566d1e56 100644 --- a/test/battle/form_change/faint.c +++ b/test/battle/form_change/faint.c @@ -69,3 +69,46 @@ DOUBLE_BATTLE_TEST("Causing a Forecast or Flower Gift Pokémon to faint should n } } } + +SINGLE_BATTLE_TEST("Ogerpon reverts to the correct form upon fainting after terastallizing") +{ + u32 species, item; + PARAMETRIZE { species = SPECIES_OGERPON_TEAL; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_OGERPON_WELLSPRING; item = ITEM_WELLSPRING_MASK; } + PARAMETRIZE { species = SPECIES_OGERPON_HEARTHFLAME; item = ITEM_HEARTHFLAME_MASK; } + PARAMETRIZE { species = SPECIES_OGERPON_CORNERSTONE; item = ITEM_CORNERSTONE_MASK; } + GIVEN { + PLAYER(species) { HP(1); Item(item); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); + MOVE(opponent, MOVE_SCRATCH); + SEND_OUT(player, 1); + } + TURN { USE_ITEM(player, ITEM_REVIVE, 0); } + TURN { SWITCH(player, 0); } + } THEN { + EXPECT_EQ(player->species, species); + } +} + +SINGLE_BATTLE_TEST("Terapagos reverts to the correct form upon fainting after terastallizing") +{ + GIVEN { + PLAYER(SPECIES_TERAPAGOS_NORMAL) { HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_MEMENTO, gimmick: GIMMICK_TERA); + MOVE(opponent, MOVE_SCRATCH); + SEND_OUT(player, 1); + } + TURN { USE_ITEM(player, ITEM_REVIVE, 0); } + TURN { SWITCH(player, 0); } + } THEN { + EXPECT_EQ(player->species, SPECIES_TERAPAGOS_TERASTAL); // Not Normal form due to Tera Shift + } +} diff --git a/test/battle/form_change/gigantamax.c b/test/battle/form_change/gigantamax.c index 92e2b49224..ae34a5acf2 100644 --- a/test/battle/form_change/gigantamax.c +++ b/test/battle/form_change/gigantamax.c @@ -43,3 +43,22 @@ SINGLE_BATTLE_TEST("Dynamax: Venusaur returns its base Form upon battle end afte EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), SPECIES_VENUSAUR); } } + +SINGLE_BATTLE_TEST("Dynamax: Venusaur returns its base Form upon fainting end after Gigantamaxing") +{ + GIVEN { + PLAYER(SPECIES_VENUSAUR) { HP(1); GigantamaxFactor(TRUE); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_SCRATCH, gimmick: GIMMICK_DYNAMAX); + MOVE(opponent, MOVE_SCRATCH); + SEND_OUT(player, 1); + } + TURN { USE_ITEM(player, ITEM_REVIVE, 0); } + TURN { SWITCH(player, 0); } + } THEN { + EXPECT_EQ(player->species, SPECIES_VENUSAUR); + } +} diff --git a/test/battle/form_change/mega_evolution.c b/test/battle/form_change/mega_evolution.c index f246cf1cc6..8e1945c558 100644 --- a/test/battle/form_change/mega_evolution.c +++ b/test/battle/form_change/mega_evolution.c @@ -216,3 +216,41 @@ SINGLE_BATTLE_TEST("Rayquaza returns its base Form upon battle end after Mega Ev EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), SPECIES_RAYQUAZA); } } + +SINGLE_BATTLE_TEST("Venusaur returns its base Form upon fainting end after Mega Evolving") +{ + GIVEN { + PLAYER(SPECIES_VENUSAUR) { HP(1); Item(ITEM_VENUSAURITE); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); + MOVE(opponent, MOVE_SCRATCH); + SEND_OUT(player, 1); + } + TURN { USE_ITEM(player, ITEM_REVIVE, 0); } + TURN { SWITCH(player, 0); } + } THEN { + EXPECT_EQ(player->species, SPECIES_VENUSAUR); + } +} + +SINGLE_BATTLE_TEST("Rayquaza returns its base Form upon fainting end after Mega Evolving") +{ + GIVEN { + PLAYER(SPECIES_RAYQUAZA) { HP(1); Moves(MOVE_DRAGON_ASCENT, MOVE_CELEBRATE); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); + MOVE(opponent, MOVE_SCRATCH); + SEND_OUT(player, 1); + } + TURN { USE_ITEM(player, ITEM_REVIVE, 0); } + TURN { SWITCH(player, 0); } + } THEN { + EXPECT_EQ(player->species, SPECIES_RAYQUAZA); + } +} diff --git a/test/battle/form_change/primal_reversion.c b/test/battle/form_change/primal_reversion.c index cbd7435a73..db3558f0f7 100644 --- a/test/battle/form_change/primal_reversion.c +++ b/test/battle/form_change/primal_reversion.c @@ -1,7 +1,7 @@ #include "global.h" #include "test/battle.h" -SINGLE_BATTLE_TEST("Primal reversion happens for Groudon only when holding Red Orb") +SINGLE_BATTLE_TEST("Primal Reversion happens for Groudon only when holding Red Orb") { u16 heldItem; PARAMETRIZE { heldItem = ITEM_NONE; } @@ -33,7 +33,7 @@ SINGLE_BATTLE_TEST("Primal reversion happens for Groudon only when holding Red O } } -SINGLE_BATTLE_TEST("Primal reversion happens for Kyogre only when holding Blue Orb") +SINGLE_BATTLE_TEST("Primal Reversion happens for Kyogre only when holding Blue Orb") { u16 heldItem; PARAMETRIZE { heldItem = ITEM_NONE; } @@ -65,7 +65,7 @@ SINGLE_BATTLE_TEST("Primal reversion happens for Kyogre only when holding Blue O } } -DOUBLE_BATTLE_TEST("Primal reversion's order is determined by Speed - opponent faster") +DOUBLE_BATTLE_TEST("Primal Reversion's order is determined by Speed - opponent faster") { GIVEN { PLAYER(SPECIES_KYOGRE) { Item(ITEM_BLUE_ORB); Speed(5); } @@ -91,7 +91,7 @@ DOUBLE_BATTLE_TEST("Primal reversion's order is determined by Speed - opponent f } } -DOUBLE_BATTLE_TEST("Primal reversion's order is determined by Speed - player faster") +DOUBLE_BATTLE_TEST("Primal Reversion's order is determined by Speed - player faster") { GIVEN { PLAYER(SPECIES_KYOGRE) { Item(ITEM_BLUE_ORB); Speed(20); } @@ -117,7 +117,7 @@ DOUBLE_BATTLE_TEST("Primal reversion's order is determined by Speed - player fas } } -SINGLE_BATTLE_TEST("Primal reversion happens after a mon is sent out after a mon is fainted") +SINGLE_BATTLE_TEST("Primal Reversion happens after a mon is sent out after a mon is fainted") { GIVEN { ASSUME(!IsBattleMoveStatus(MOVE_SCRATCH)); @@ -136,7 +136,7 @@ SINGLE_BATTLE_TEST("Primal reversion happens after a mon is sent out after a mon } } -SINGLE_BATTLE_TEST("Primal reversion happens after a mon is switched in") +SINGLE_BATTLE_TEST("Primal Reversion happens after a mon is switched in") { GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -153,7 +153,7 @@ SINGLE_BATTLE_TEST("Primal reversion happens after a mon is switched in") } } -SINGLE_BATTLE_TEST("Primal reversion happens after a switch-in caused by Eject Button") +SINGLE_BATTLE_TEST("Primal Reversion happens after a switch-in caused by Eject Button") { GIVEN { ASSUME(!IsBattleMoveStatus(MOVE_SCRATCH)); @@ -174,7 +174,7 @@ SINGLE_BATTLE_TEST("Primal reversion happens after a switch-in caused by Eject B } } -SINGLE_BATTLE_TEST("Primal reversion happens after a switch-in caused by Red Card") +SINGLE_BATTLE_TEST("Primal Reversion happens after a switch-in caused by Red Card") { GIVEN { ASSUME(!IsBattleMoveStatus(MOVE_SCRATCH)); @@ -194,7 +194,7 @@ SINGLE_BATTLE_TEST("Primal reversion happens after a switch-in caused by Red Car } } -SINGLE_BATTLE_TEST("Primal reversion happens after the entry hazards damage") +SINGLE_BATTLE_TEST("Primal Reversion happens after the entry hazards damage") { GIVEN { ASSUME(GetMoveEffect(MOVE_SPIKES) == EFFECT_SPIKES); @@ -215,7 +215,7 @@ SINGLE_BATTLE_TEST("Primal reversion happens after the entry hazards damage") } } -SINGLE_BATTLE_TEST("Primal reversion happens immediately if it was brought in by U-turn") +SINGLE_BATTLE_TEST("Primal Reversion happens immediately if it was brought in by U-turn") { GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -236,7 +236,7 @@ SINGLE_BATTLE_TEST("Primal reversion happens immediately if it was brought in by } -DOUBLE_BATTLE_TEST("Primal reversion triggers for multiple battlers if multiple fainted the previous turn") +DOUBLE_BATTLE_TEST("Primal Reversion triggers for multiple battlers if multiple fainted the previous turn") { GIVEN { ASSUME(GetMoveTarget(MOVE_EARTHQUAKE) == TARGET_FOES_AND_ALLY); @@ -259,7 +259,7 @@ DOUBLE_BATTLE_TEST("Primal reversion triggers for multiple battlers if multiple } } -DOUBLE_BATTLE_TEST("Primal reversion triggers for all battlers if multiple fainted the previous turn") +DOUBLE_BATTLE_TEST("Primal Reversion triggers for all battlers if multiple fainted the previous turn") { GIVEN { ASSUME(IsExplosionMove(MOVE_EXPLOSION)); @@ -287,7 +287,7 @@ DOUBLE_BATTLE_TEST("Primal reversion triggers for all battlers if multiple faint } } -DOUBLE_BATTLE_TEST("Primal reversion and other switch-in effects trigger for all battlers if multiple fainted the previous turn") +DOUBLE_BATTLE_TEST("Primal Reversion and other switch-in effects trigger for all battlers if multiple fainted the previous turn") { GIVEN { ASSUME(IsExplosionMove(MOVE_EXPLOSION)); @@ -333,7 +333,7 @@ DOUBLE_BATTLE_TEST("Primal reversion and other switch-in effects trigger for all } } -SINGLE_BATTLE_TEST("Primal reversion is reverted upon battle end") +SINGLE_BATTLE_TEST("Primal Reversion is reverted upon battle end") { u32 species, item; PARAMETRIZE { species = SPECIES_GROUDON; item = ITEM_RED_ORB; } @@ -347,3 +347,25 @@ SINGLE_BATTLE_TEST("Primal reversion is reverted upon battle end") EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), species); } } + +SINGLE_BATTLE_TEST("Primal Reversion is NOT reverted upon fainting") +{ + u32 species, item, targetSpecies; + PARAMETRIZE { species = SPECIES_GROUDON; item = ITEM_RED_ORB; targetSpecies = SPECIES_GROUDON_PRIMAL; } + PARAMETRIZE { species = SPECIES_KYOGRE; item = ITEM_BLUE_ORB; targetSpecies = SPECIES_KYOGRE_PRIMAL; } + GIVEN { + PLAYER(species) { HP(1); Item(item); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_CELEBRATE); + MOVE(opponent, MOVE_SCRATCH); + SEND_OUT(player, 1); + } + TURN { USE_ITEM(player, ITEM_REVIVE, 0); } + TURN { SWITCH(player, 0); } + } THEN { + EXPECT_EQ(player->species, targetSpecies); + } +} diff --git a/test/battle/form_change/ultra_burst.c b/test/battle/form_change/ultra_burst.c index fcb0efded5..1db0bf7fd8 100644 --- a/test/battle/form_change/ultra_burst.c +++ b/test/battle/form_change/ultra_burst.c @@ -135,3 +135,25 @@ SINGLE_BATTLE_TEST("Necrozma returns its proper Form upon battle end after Ultra EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), species); } } + +SINGLE_BATTLE_TEST("Necrozma returns its proper Form upon fainting after Ultra Bursting") +{ + u32 species; + PARAMETRIZE { species = SPECIES_NECROZMA_DUSK_MANE; } + PARAMETRIZE { species = SPECIES_NECROZMA_DAWN_WINGS; } + GIVEN { + PLAYER(species) { HP(1); Item(ITEM_ULTRANECROZIUM_Z); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); + MOVE(opponent, MOVE_SCRATCH); + SEND_OUT(player, 1); + } + TURN { USE_ITEM(player, ITEM_REVIVE, 0); } + TURN { SWITCH(player, 0); } + } THEN { + EXPECT_EQ(player->species, species); + } +} diff --git a/test/battle/gimmick/terastal.c b/test/battle/gimmick/terastal.c index 0de100776e..9df8e06f2a 100644 --- a/test/battle/gimmick/terastal.c +++ b/test/battle/gimmick/terastal.c @@ -761,19 +761,19 @@ SINGLE_BATTLE_TEST("(TERA) Transformed Pokémon can't Terastalize") SINGLE_BATTLE_TEST("(TERA) Pokemon with Tera forms change upon Terastallizing") { - u32 species, targetSpecies; - PARAMETRIZE { species = SPECIES_OGERPON_TEAL; targetSpecies = SPECIES_OGERPON_TEAL_TERA; } - PARAMETRIZE { species = SPECIES_OGERPON_WELLSPRING; targetSpecies = SPECIES_OGERPON_WELLSPRING_TERA; } - PARAMETRIZE { species = SPECIES_OGERPON_HEARTHFLAME; targetSpecies = SPECIES_OGERPON_HEARTHFLAME_TERA; } - PARAMETRIZE { species = SPECIES_OGERPON_CORNERSTONE; targetSpecies = SPECIES_OGERPON_CORNERSTONE_TERA; } - PARAMETRIZE { species = SPECIES_TERAPAGOS_TERASTAL; targetSpecies = SPECIES_TERAPAGOS_STELLAR; } + u32 species, target, item; + PARAMETRIZE { species = SPECIES_OGERPON_TEAL; target = SPECIES_OGERPON_TEAL_TERA; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_OGERPON_WELLSPRING; target = SPECIES_OGERPON_WELLSPRING_TERA; item = ITEM_WELLSPRING_MASK; } + PARAMETRIZE { species = SPECIES_OGERPON_HEARTHFLAME; target = SPECIES_OGERPON_HEARTHFLAME_TERA; item = ITEM_HEARTHFLAME_MASK; } + PARAMETRIZE { species = SPECIES_OGERPON_CORNERSTONE; target = SPECIES_OGERPON_CORNERSTONE_TERA; item = ITEM_CORNERSTONE_MASK; } + PARAMETRIZE { species = SPECIES_TERAPAGOS_TERASTAL; target = SPECIES_TERAPAGOS_STELLAR; item = ITEM_NONE; } GIVEN { - PLAYER(species); + PLAYER(species) { Item(item); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } } THEN { - EXPECT_EQ(player->species, targetSpecies); + EXPECT_EQ(player->species, target); } } diff --git a/test/battle/move_effect/beak_blast.c b/test/battle/move_effect/beak_blast.c index 507d6cfdd0..e6edc59af8 100644 --- a/test/battle/move_effect/beak_blast.c +++ b/test/battle/move_effect/beak_blast.c @@ -87,7 +87,6 @@ SINGLE_BATTLE_TEST("Beak Blast burns only when contact moves are used") OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(opponent, move); MOVE(player, MOVE_BEAK_BLAST); } - TURN {} } SCENE { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_BEAK_BLAST_SETUP, player); MESSAGE("Wobbuffet started heating up its beak!"); @@ -112,6 +111,35 @@ SINGLE_BATTLE_TEST("Beak Blast burns only when contact moves are used") } } +SINGLE_BATTLE_TEST("Beak Blast doesn't burn when charging a two turn move") +{ + u32 move; + PARAMETRIZE { move = MOVE_BOUNCE; } + PARAMETRIZE { move = MOVE_DIG; } + + GIVEN { + ASSUME(MoveMakesContact(MOVE_BOUNCE)); + ASSUME(MoveMakesContact(MOVE_DIG)); + ASSUME(gBattleMoveEffects[GetMoveEffect(MOVE_BOUNCE)].twoTurnEffect); + ASSUME(gBattleMoveEffects[GetMoveEffect(MOVE_DIG)].twoTurnEffect); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_BEAK_BLAST); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_BEAK_BLAST_SETUP, player); + MESSAGE("Wobbuffet started heating up its beak!"); + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + + NONE_OF { + HP_BAR(player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, opponent); + MESSAGE("The opposing Wobbuffet was burned!"); + STATUS_ICON(opponent, burn: TRUE); + } + } +} + SINGLE_BATTLE_TEST("Beak Blast doesn't burn fire types") { GIVEN { diff --git a/test/battle/move_effect/foresight.c b/test/battle/move_effect/foresight.c index 59209030d0..9879774bab 100644 --- a/test/battle/move_effect/foresight.c +++ b/test/battle/move_effect/foresight.c @@ -1,14 +1,123 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Foresight removes Ghost's type immunity to Normal and Fighting types") +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_FORESIGHT) == EFFECT_FORESIGHT); +} + +SINGLE_BATTLE_TEST("Foresight removes Ghost's type immunity to Normal and Fighting types") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_SCRATCH) == TYPE_NORMAL); + ASSUME(GetMoveType(MOVE_LOW_KICK) == TYPE_FIGHTING); + ASSUME(GetSpeciesType(SPECIES_GENGAR, 0) == TYPE_GHOST); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_FORESIGHT, MOVE_SCRATCH, MOVE_LOW_KICK); } + OPPONENT(SPECIES_GENGAR) { Moves(MOVE_SPLASH); } + } WHEN { + TURN { MOVE(player, MOVE_FORESIGHT); MOVE(opponent, MOVE_SPLASH); } + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_SPLASH); } + TURN { MOVE(player, MOVE_LOW_KICK); MOVE(opponent, MOVE_SPLASH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FORESIGHT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_LOW_KICK, player); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Foresight always hits unless the target is semi-invulnerable") +{ + bool32 semiInvulnerable = FALSE; + PARAMETRIZE { semiInvulnerable = FALSE; } + PARAMETRIZE { semiInvulnerable = TRUE; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_DOUBLE_TEAM) == EFFECT_EVASION_UP); + ASSUME(GetMoveEffect(MOVE_FLY) == EFFECT_SEMI_INVULNERABLE); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_FORESIGHT, MOVE_SPLASH); Speed(10); } + OPPONENT(SPECIES_SQUAWKABILLY) { Moves(MOVE_DOUBLE_TEAM, MOVE_FLY); Speed(20); } + } WHEN { + if (semiInvulnerable) + TURN { MOVE(player, MOVE_FORESIGHT); MOVE(opponent, MOVE_FLY); } + else + TURN { MOVE(player, MOVE_FORESIGHT); MOVE(opponent, MOVE_DOUBLE_TEAM); } + + if (semiInvulnerable) + TURN { MOVE(player, MOVE_SPLASH); SKIP_TURN(opponent); } + } SCENE { + if (semiInvulnerable) { + MESSAGE("The opposing Squawkabilly avoided the attack!"); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_TEAM, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FORESIGHT, player); + } + } +} + +SINGLE_BATTLE_TEST("Foresight causes moves against the target to ignore positive evasion stat stages") +{ + PASSES_RANDOMLY(100, 100, RNG_ACCURACY); + GIVEN { + ASSUME(GetMoveEffect(MOVE_DOUBLE_TEAM) == EFFECT_EVASION_UP); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_FORESIGHT, MOVE_SCRATCH); Speed(10); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_DOUBLE_TEAM, MOVE_SPLASH); Speed(20); } + } WHEN { + TURN { MOVE(player, MOVE_FORESIGHT); MOVE(opponent, MOVE_DOUBLE_TEAM); } + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_SPLASH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_TEAM, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FORESIGHT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Foresight fails if the target is already under its effect (Gen 2 and Gen5+)") +{ + u32 genConfig = GEN_2; + PARAMETRIZE { genConfig = GEN_2; } + PARAMETRIZE { genConfig = GEN_5; } + GIVEN { + WITH_CONFIG(CONFIG_FORESIGHT_FAIL, genConfig); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FORESIGHT); } + TURN { MOVE(player, MOVE_FORESIGHT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FORESIGHT, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_FORESIGHT, player); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Foresight doesn't fail if the target is already under its effect (Gen 3-4)") +{ + u32 genConfig = GEN_3; + PARAMETRIZE { genConfig = GEN_3; } + PARAMETRIZE { genConfig = GEN_4; } + GIVEN { + WITH_CONFIG(CONFIG_FORESIGHT_FAIL, genConfig); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FORESIGHT); } + TURN { MOVE(player, MOVE_FORESIGHT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FORESIGHT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FORESIGHT, player); + NOT MESSAGE("But it failed!"); + } +} + TO_DO_BATTLE_TEST("Foresight causes accuracy/evasion stat changes only between the user/target when the user's accuracy stage is less than the target's evasion stage (Gen 2)") TO_DO_BATTLE_TEST("Foresight causes all moves against the target to ignore evasion stat changes (Gen 3)") TO_DO_BATTLE_TEST("Foresight causes all moves against the target to ignore only positive evasion stat changes (Gen 4+)") // Eg. Doesn't ignore Sweet Scent TO_DO_BATTLE_TEST("Foresight doesn't cause moves used against the target to always hit (Gen 2-3)") TO_DO_BATTLE_TEST("Foresight causes moves used against the target to always hit (Gen 4+)") -TO_DO_BATTLE_TEST("Foresight does not make moves hit semi-invulnerable targets") -TO_DO_BATTLE_TEST("Foresight fails if the target is already under its effect (Gen 2 and Gen5+)") -TO_DO_BATTLE_TEST("Foresight doesn't fail if the target is already under its effect (Gen 3-4)") TO_DO_BATTLE_TEST("Baton Pass passes Foresight's effect (Gen 2)"); TO_DO_BATTLE_TEST("Baton Pass doesn't pass Foresight's effect (Gen 3+)"); diff --git a/test/battle/move_effect/gear_up.c b/test/battle/move_effect/gear_up.c index 9a9bdab79c..9cac73d694 100644 --- a/test/battle/move_effect/gear_up.c +++ b/test/battle/move_effect/gear_up.c @@ -1,17 +1,47 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Gear Up increases the Attack and Sp. Attack of the user and allies if they have Plus or Minus") +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_GEAR_UP) == EFFECT_GEAR_UP); +} -AI_DOUBLE_BATTLE_TEST("AI uses Gear Up") +SINGLE_BATTLE_TEST("Gear Up raises Attack and Sp. Attack of the user with Plus/Minus in singles") { GIVEN { - AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); - PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, MOVE_CELEBRATE); } - PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, MOVE_CELEBRATE); } - OPPONENT(SPECIES_KLINKLANG) { Ability(ABILITY_PLUS); Moves(MOVE_GEAR_UP, MOVE_WATER_GUN, MOVE_POUND); } - OPPONENT(SPECIES_KLINKLANG) { Ability(ABILITY_PLUS); Moves(MOVE_GEAR_UP, MOVE_WATER_GUN, MOVE_POUND); } + PLAYER(SPECIES_PLUSLE) { Ability(ABILITY_PLUS); } + OPPONENT(SPECIES_MINUN) { Ability(ABILITY_MINUS); } } WHEN { - TURN { EXPECT_MOVE(opponentLeft, MOVE_GEAR_UP); } + TURN { MOVE(player, MOVE_GEAR_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GEAR_UP, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Gear Up raises Attack and Sp. Attack of all Plus/Minus allies in doubles") +{ + GIVEN { + PLAYER(SPECIES_PLUSLE) { Ability(ABILITY_PLUS); } + PLAYER(SPECIES_MINUN) { Ability(ABILITY_MINUS); } + OPPONENT(SPECIES_PLUSLE) { Ability(ABILITY_PLUS); } + OPPONENT(SPECIES_MINUN) { Ability(ABILITY_MINUS); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_GEAR_UP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GEAR_UP, playerLeft); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(playerLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(playerRight->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentRight->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); } } diff --git a/test/battle/move_effect/hyperspace_fury.c b/test/battle/move_effect/hyperspace_fury.c deleted file mode 100644 index 080758c94b..0000000000 --- a/test/battle/move_effect/hyperspace_fury.c +++ /dev/null @@ -1,4 +0,0 @@ -#include "global.h" -#include "test/battle.h" - -TO_DO_BATTLE_TEST("TODO: Write Hyperspace Fury (Move Effect) test titles") diff --git a/test/battle/move_effect/ivy_cudgel.c b/test/battle/move_effect/ivy_cudgel.c index 7dd7f33445..92f85d66e2 100644 --- a/test/battle/move_effect/ivy_cudgel.c +++ b/test/battle/move_effect/ivy_cudgel.c @@ -12,10 +12,10 @@ SINGLE_BATTLE_TEST("Ivy Cudgel changes the move type depending on the form of Og u16 ogerpon; enum Item item; - PARAMETRIZE { species = SPECIES_BLASTOISE; ogerpon = SPECIES_OGERPON_TEAL; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_BLASTOISE; ogerpon = SPECIES_OGERPON_TEAL; item = ITEM_NONE; } PARAMETRIZE { species = SPECIES_CHARIZARD; ogerpon = SPECIES_OGERPON_CORNERSTONE; item = ITEM_CORNERSTONE_MASK; } - PARAMETRIZE { species = SPECIES_CHARIZARD; ogerpon = SPECIES_OGERPON_WELLSPRING; item = ITEM_WELLSPRING_MASK; } - PARAMETRIZE { species = SPECIES_VENUSAUR; ogerpon = SPECIES_OGERPON_HEARTHFLAME; item = ITEM_HEARTHFLAME_MASK; } + PARAMETRIZE { species = SPECIES_CHARIZARD; ogerpon = SPECIES_OGERPON_WELLSPRING; item = ITEM_WELLSPRING_MASK; } + PARAMETRIZE { species = SPECIES_VENUSAUR; ogerpon = SPECIES_OGERPON_HEARTHFLAME; item = ITEM_HEARTHFLAME_MASK; } GIVEN { PLAYER(ogerpon) { Item(item); } diff --git a/test/battle/move_effect/magnetic_flux.c b/test/battle/move_effect/magnetic_flux.c index 7bbcd52754..ac969a9dd2 100644 --- a/test/battle/move_effect/magnetic_flux.c +++ b/test/battle/move_effect/magnetic_flux.c @@ -1,17 +1,47 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("TODO: Write Magnetic Flux (Move Effect) test titles") +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_MAGNETIC_FLUX) == EFFECT_MAGNETIC_FLUX); +} -AI_DOUBLE_BATTLE_TEST("AI uses Magnetic Flux") +SINGLE_BATTLE_TEST("Magnetic Flux raises Defense and Sp. Defense of the user with Plus/Minus in singles") { GIVEN { - AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT); - PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, MOVE_CELEBRATE); } - PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_POUND, MOVE_CELEBRATE); } - OPPONENT(SPECIES_KLINK) { Ability(ABILITY_PLUS); Moves(MOVE_MAGNETIC_FLUX, MOVE_POUND); } - OPPONENT(SPECIES_KLINK) { Ability(ABILITY_PLUS); Moves(MOVE_MAGNETIC_FLUX, MOVE_POUND); } + PLAYER(SPECIES_PLUSLE) { Ability(ABILITY_PLUS); } + OPPONENT(SPECIES_MINUN) { Ability(ABILITY_MINUS); } } WHEN { - TURN { EXPECT_MOVE(opponentLeft, MOVE_MAGNETIC_FLUX); } + TURN { MOVE(player, MOVE_MAGNETIC_FLUX); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MAGNETIC_FLUX, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Magnetic Flux raises Defense and Sp. Defense of all Plus/Minus allies in doubles") +{ + GIVEN { + PLAYER(SPECIES_PLUSLE) { Ability(ABILITY_PLUS); } + PLAYER(SPECIES_MINUN) { Ability(ABILITY_MINUS); } + OPPONENT(SPECIES_PLUSLE) { Ability(ABILITY_PLUS); } + OPPONENT(SPECIES_MINUN) { Ability(ABILITY_MINUS); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_MAGNETIC_FLUX); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MAGNETIC_FLUX, playerLeft); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(playerLeft->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(playerRight->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(playerRight->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponentLeft->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentLeft->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentRight->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentRight->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE); } } diff --git a/test/battle/move_effect/miracle_eye.c b/test/battle/move_effect/miracle_eye.c index 784323f1cc..5ba1e48f13 100644 --- a/test/battle/move_effect/miracle_eye.c +++ b/test/battle/move_effect/miracle_eye.c @@ -1,4 +1,106 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("TODO: Write Miracle Eye (Move Effect) test titles") +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_MIRACLE_EYE) == EFFECT_MIRACLE_EYE); +} + +SINGLE_BATTLE_TEST("Miracle Eye removes Dark-type immunity to Psychic-type moves") +{ + GIVEN { + ASSUME(GetMoveType(MOVE_PSYCHIC) == TYPE_PSYCHIC); + ASSUME(GetSpeciesType(SPECIES_UMBREON, 0) == TYPE_DARK); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_MIRACLE_EYE, MOVE_PSYCHIC); } + OPPONENT(SPECIES_UMBREON) { Moves(MOVE_SPLASH); } + } WHEN { + TURN { MOVE(player, MOVE_PSYCHIC); MOVE(opponent, MOVE_SPLASH); } + TURN { MOVE(player, MOVE_MIRACLE_EYE); MOVE(opponent, MOVE_SPLASH); } + TURN { MOVE(player, MOVE_PSYCHIC); MOVE(opponent, MOVE_SPLASH); } + } SCENE { + NOT HP_BAR(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRACLE_EYE, player); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Miracle Eye always hits unless the target is semi-invulnerable") +{ + bool32 semiInvulnerable = FALSE; + PARAMETRIZE { semiInvulnerable = FALSE; } + PARAMETRIZE { semiInvulnerable = TRUE; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_DOUBLE_TEAM) == EFFECT_EVASION_UP); + ASSUME(GetMoveEffect(MOVE_FLY) == EFFECT_SEMI_INVULNERABLE); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_MIRACLE_EYE, MOVE_SPLASH); Speed(10); } + OPPONENT(SPECIES_SQUAWKABILLY) { Moves(MOVE_DOUBLE_TEAM, MOVE_FLY); Speed(20); } + } WHEN { + if (semiInvulnerable) + TURN { MOVE(player, MOVE_MIRACLE_EYE); MOVE(opponent, MOVE_FLY); } + else + TURN { MOVE(player, MOVE_MIRACLE_EYE); MOVE(opponent, MOVE_DOUBLE_TEAM); } + + if (semiInvulnerable) + TURN { MOVE(player, MOVE_SPLASH); SKIP_TURN(opponent); } + } SCENE { + if (semiInvulnerable) { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRACLE_EYE, player); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_TEAM, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRACLE_EYE, player); + } + } +} + +SINGLE_BATTLE_TEST("Miracle Eye causes moves against the target to ignore positive evasion stat stages") +{ + PASSES_RANDOMLY(100, 100, RNG_ACCURACY); + GIVEN { + ASSUME(GetMoveEffect(MOVE_DOUBLE_TEAM) == EFFECT_EVASION_UP); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_MIRACLE_EYE, MOVE_SCRATCH); Speed(10); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_DOUBLE_TEAM, MOVE_SPLASH); Speed(20); } + } WHEN { + TURN { MOVE(player, MOVE_MIRACLE_EYE); MOVE(opponent, MOVE_DOUBLE_TEAM); } + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_SPLASH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_TEAM, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRACLE_EYE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Miracle Eye fails if the target is already affected by Miracle Eye (Gen5+)") +{ + GIVEN { + WITH_CONFIG(CONFIG_MIRACLE_EYE_FAIL, GEN_5); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_MIRACLE_EYE); } + TURN { MOVE(player, MOVE_MIRACLE_EYE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRACLE_EYE, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRACLE_EYE, player); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Miracle Eye does not fail if the target is already affected by Miracle Eye (Gen4)") +{ + GIVEN { + WITH_CONFIG(CONFIG_MIRACLE_EYE_FAIL, GEN_4); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_MIRACLE_EYE); } + TURN { MOVE(player, MOVE_MIRACLE_EYE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRACLE_EYE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIRACLE_EYE, player); + NOT MESSAGE("But it failed!"); + } +} diff --git a/test/battle/move_effect/parting_shot.c b/test/battle/move_effect/parting_shot.c index 4d55d09315..58c5ba5218 100644 --- a/test/battle/move_effect/parting_shot.c +++ b/test/battle/move_effect/parting_shot.c @@ -1,4 +1,385 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("TODO: Write Parting Shot (Move Effect) test titles") +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_PARTING_SHOT) == EFFECT_PARTING_SHOT); +} + +SINGLE_BATTLE_TEST("Parting Shot: Passes Substitute and switches the user out") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_PARTING_SHOT, MOVE_CELEBRATE); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SUBSTITUTE, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_SUBSTITUTE); } + TURN { MOVE(player, MOVE_PARTING_SHOT); MOVE(opponent, MOVE_CELEBRATE); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, player); + SEND_IN_MESSAGE("Wynaut"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(player->species, SPECIES_WYNAUT); + } +} + +SINGLE_BATTLE_TEST("Parting Shot: Soundproof and Good as Gold block Parting Shot") +{ + u16 species, ability; + + PARAMETRIZE { species = SPECIES_EXPLOUD; ability = ABILITY_SOUNDPROOF; } + PARAMETRIZE { species = SPECIES_GHOLDENGO; ability = ABILITY_GOOD_AS_GOLD; } + + GIVEN { + ASSUME(IsSoundMove(MOVE_PARTING_SHOT)); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_PARTING_SHOT); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(species) { Ability(ability); Moves(MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_PARTING_SHOT); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + ABILITY_POPUP(opponent, ability); + if (ability == ABILITY_SOUNDPROOF) + MESSAGE("The opposing Exploud's Soundproof blocks Parting Shot!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, player); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->species, SPECIES_WOBBUFFET); + } +} + +SINGLE_BATTLE_TEST("Parting Shot: Hyper Cutter blocks Attack drop but still switches") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_PARTING_SHOT); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_KRABBY) { Ability(ABILITY_HYPER_CUTTER); } + } WHEN { + TURN { MOVE(player, MOVE_PARTING_SHOT); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, player); + SEND_IN_MESSAGE("Wynaut"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(player->species, SPECIES_WYNAUT); + } +} + +SINGLE_BATTLE_TEST("Parting Shot: Magic Coat bounces it and switches the target out") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_PARTING_SHOT); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_MAGIC_COAT); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponent, MOVE_MAGIC_COAT); MOVE(player, MOVE_PARTING_SHOT); SEND_OUT(opponent, 1); } + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(opponent->species, SPECIES_WYNAUT); + } +} + +SINGLE_BATTLE_TEST("Parting Shot: Magic Bounce bounces it and switches the target out") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_PARTING_SHOT); } + OPPONENT(SPECIES_ESPEON) { Ability(ABILITY_MAGIC_BOUNCE); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_PARTING_SHOT); SEND_OUT(opponent, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_MAGIC_BOUNCE); + } THEN { + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(opponent->species, SPECIES_WYNAUT); + } +} + +SINGLE_BATTLE_TEST("Parting Shot: Mirror Armor switches the user even if reflected drops fail") +{ + u16 species, ability, item; + + PARAMETRIZE { species = SPECIES_METAGROSS; ability = ABILITY_CLEAR_BODY; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_LUCARIO; ability = ABILITY_INNER_FOCUS; item = ITEM_CLEAR_AMULET; } + + GIVEN { + ASSUME(gItemsInfo[ITEM_CLEAR_AMULET].holdEffect == HOLD_EFFECT_CLEAR_AMULET); + PLAYER(species) { Ability(ability); Item(item); Moves(MOVE_PARTING_SHOT); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); } + } WHEN { + TURN { MOVE(player, MOVE_PARTING_SHOT); SEND_OUT(player, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_MIRROR_ARMOR); + if (ability == ABILITY_CLEAR_BODY) { + ABILITY_POPUP(player, ABILITY_CLEAR_BODY); + MESSAGE("Metagross's Clear Body prevents stat loss!"); + } else if (ability == ABILITY_WHITE_SMOKE) { + ABILITY_POPUP(player, ABILITY_WHITE_SMOKE); + MESSAGE("Torkoal's White Smoke prevents stat loss!"); + } else if (ability == ABILITY_FULL_METAL_BODY) { + ABILITY_POPUP(player, ABILITY_FULL_METAL_BODY); + MESSAGE("Solgaleo's Full Metal Body prevents stat loss!"); + } else if (item == ITEM_CLEAR_AMULET) { + MESSAGE("The effects of the Clear Amulet held by Lucario prevents its stats from being lowered!"); + } + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->species, SPECIES_WYNAUT); + } +} + +SINGLE_BATTLE_TEST("Parting Shot: Mirror Armor switches even if reflected stats are at minimum") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_PARTING_SHOT, MOVE_SHELL_SMASH, MOVE_CELEBRATE); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_CORVIKNIGHT) { Ability(ABILITY_MIRROR_ARMOR); Moves(MOVE_TOPSY_TURVY, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_SHELL_SMASH); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_SHELL_SMASH); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_SHELL_SMASH); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_TOPSY_TURVY); } + TURN { MOVE(player, MOVE_PARTING_SHOT); SEND_OUT(player, 1); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_MIRROR_ARMOR); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->species, SPECIES_WYNAUT); + } +} + +SINGLE_BATTLE_TEST("Parting Shot: Does not switch if both stats are at minimum (Gen7+)") +{ + GIVEN { + WITH_CONFIG(CONFIG_PARTING_SHOT_SWITCH, GEN_7); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_PARTING_SHOT, MOVE_TOPSY_TURVY, MOVE_CELEBRATE); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_OMASTAR) { Moves(MOVE_SHELL_SMASH, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_SHELL_SMASH); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_SHELL_SMASH); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_SHELL_SMASH); } + TURN { MOVE(player, MOVE_TOPSY_TURVY); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_PARTING_SHOT); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + MESSAGE("The opposing Omastar's stats won't go any lower!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], MIN_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPATK], MIN_STAT_STAGE); + EXPECT_EQ(player->species, SPECIES_WOBBUFFET); + } +} + +SINGLE_BATTLE_TEST("Parting Shot: Does not switch if Contrary is at maximum stats (Gen7+)") +{ + GIVEN { + WITH_CONFIG(CONFIG_PARTING_SHOT_SWITCH, GEN_7); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_PARTING_SHOT, MOVE_TOPSY_TURVY, MOVE_CELEBRATE); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_INKAY) { Ability(ABILITY_CONTRARY); Moves(MOVE_SHELL_SMASH, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_SHELL_SMASH); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_SHELL_SMASH); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_SHELL_SMASH); } + TURN { MOVE(player, MOVE_TOPSY_TURVY); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_PARTING_SHOT); MOVE(opponent, MOVE_CELEBRATE); } + } SCENE { + MESSAGE("The opposing Inkay's stats won't go any higher!"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], MAX_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPATK], MAX_STAT_STAGE); + EXPECT_EQ(player->species, SPECIES_WOBBUFFET); + } +} + +SINGLE_BATTLE_TEST("Parting Shot: Stat drop prevention by abilities/items does not switch (Gen7+)") +{ + u16 species, ability, item; + + PARAMETRIZE { species = SPECIES_METAGROSS; ability = ABILITY_CLEAR_BODY; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_LUCARIO; ability = ABILITY_INNER_FOCUS; item = ITEM_CLEAR_AMULET; } + + GIVEN { + WITH_CONFIG(CONFIG_PARTING_SHOT_SWITCH, GEN_7); + ASSUME(gItemsInfo[ITEM_CLEAR_AMULET].holdEffect == HOLD_EFFECT_CLEAR_AMULET); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_PARTING_SHOT); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(species) { Ability(ability); Item(item); } + } WHEN { + TURN { MOVE(player, MOVE_PARTING_SHOT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, player); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->species, SPECIES_WOBBUFFET); + } +} + +SINGLE_BATTLE_TEST("Parting Shot: Mist prevents stat drops and does not switch (Gen7+)") +{ + GIVEN { + WITH_CONFIG(CONFIG_PARTING_SHOT_SWITCH, GEN_7); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_PARTING_SHOT, MOVE_CELEBRATE); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_MIST, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_MIST); MOVE(player, MOVE_CELEBRATE); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_PARTING_SHOT); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, player); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->species, SPECIES_WOBBUFFET); + } +} + +DOUBLE_BATTLE_TEST("Parting Shot: Flower Veil prevents stat drops and does not switch (Gen7+)") +{ + GIVEN { + ASSUME(GetSpeciesType(SPECIES_BULBASAUR, 0) == TYPE_GRASS); + WITH_CONFIG(CONFIG_PARTING_SHOT_SWITCH, GEN_7); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_PARTING_SHOT); } + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_COMFEY) { Ability(ABILITY_FLOWER_VEIL); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_PARTING_SHOT, target: opponentLeft); MOVE(playerRight, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, playerLeft); + } THEN { + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(playerLeft->species, SPECIES_WOBBUFFET); + } +} + +SINGLE_BATTLE_TEST("Parting Shot: Switches if both stats are at minimum (Gen6)") +{ + GIVEN { + WITH_CONFIG(CONFIG_PARTING_SHOT_SWITCH, GEN_6); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_PARTING_SHOT, MOVE_TOPSY_TURVY, MOVE_CELEBRATE); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_OMASTAR) { Moves(MOVE_SHELL_SMASH, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_SHELL_SMASH); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_SHELL_SMASH); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_SHELL_SMASH); } + TURN { MOVE(player, MOVE_TOPSY_TURVY); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_PARTING_SHOT); MOVE(opponent, MOVE_CELEBRATE); SEND_OUT(player, 1); } + } SCENE { + MESSAGE("The opposing Omastar's stats won't go any lower!"); + SEND_IN_MESSAGE("Wynaut"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], MIN_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPATK], MIN_STAT_STAGE); + EXPECT_EQ(player->species, SPECIES_WYNAUT); + } +} + +SINGLE_BATTLE_TEST("Parting Shot: Switches if Contrary is at maximum stats (Gen6)") +{ + GIVEN { + WITH_CONFIG(CONFIG_PARTING_SHOT_SWITCH, GEN_6); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_PARTING_SHOT, MOVE_TOPSY_TURVY, MOVE_CELEBRATE); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_INKAY) { Ability(ABILITY_CONTRARY); Moves(MOVE_SHELL_SMASH, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_SHELL_SMASH); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_SHELL_SMASH); } + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_SHELL_SMASH); } + TURN { MOVE(player, MOVE_TOPSY_TURVY); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_PARTING_SHOT); MOVE(opponent, MOVE_CELEBRATE); SEND_OUT(player, 1); } + } SCENE { + MESSAGE("The opposing Inkay's stats won't go any higher!"); + SEND_IN_MESSAGE("Wynaut"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], MAX_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPATK], MAX_STAT_STAGE); + EXPECT_EQ(player->species, SPECIES_WYNAUT); + } +} + +SINGLE_BATTLE_TEST("Parting Shot: Stat drop prevention by abilities/items switches (Gen6)") +{ + u16 species, ability, item; + + PARAMETRIZE { species = SPECIES_METAGROSS; ability = ABILITY_CLEAR_BODY; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; item = ITEM_NONE; } + PARAMETRIZE { species = SPECIES_LUCARIO; ability = ABILITY_INNER_FOCUS; item = ITEM_CLEAR_AMULET; } + + GIVEN { + WITH_CONFIG(CONFIG_PARTING_SHOT_SWITCH, GEN_6); + ASSUME(gItemsInfo[ITEM_CLEAR_AMULET].holdEffect == HOLD_EFFECT_CLEAR_AMULET); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_PARTING_SHOT); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(species) { Ability(ability); Item(item); } + } WHEN { + TURN { MOVE(player, MOVE_PARTING_SHOT); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, player); + SEND_IN_MESSAGE("Wynaut"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->species, SPECIES_WYNAUT); + } +} + +SINGLE_BATTLE_TEST("Parting Shot: Mist prevents stat drops and switches (Gen6)") +{ + GIVEN { + WITH_CONFIG(CONFIG_PARTING_SHOT_SWITCH, GEN_6); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_PARTING_SHOT, MOVE_CELEBRATE); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_MIST, MOVE_CELEBRATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_MIST); MOVE(player, MOVE_CELEBRATE); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_PARTING_SHOT); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, player); + SEND_IN_MESSAGE("Wynaut"); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->species, SPECIES_WYNAUT); + } +} + +DOUBLE_BATTLE_TEST("Parting Shot: Flower Veil prevents stat drops and switches (Gen6)") +{ + GIVEN { + WITH_CONFIG(CONFIG_PARTING_SHOT_SWITCH, GEN_6); + ASSUME(GetSpeciesType(SPECIES_BULBASAUR, 0) == TYPE_GRASS); + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_PARTING_SHOT); } + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_PIKACHU); + OPPONENT(SPECIES_BULBASAUR); + OPPONENT(SPECIES_COMFEY) { Ability(ABILITY_FLOWER_VEIL); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_PARTING_SHOT, target: opponentLeft); MOVE(playerRight, MOVE_CELEBRATE); SEND_OUT(playerLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PARTING_SHOT, playerLeft); + SEND_IN_MESSAGE("Pikachu"); + } THEN { + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(opponentLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(playerLeft->species, SPECIES_PIKACHU); + } +} diff --git a/test/battle/move_effect/protect.c b/test/battle/move_effect/protect.c index dea0a9eab6..66b7b419c8 100644 --- a/test/battle/move_effect/protect.c +++ b/test/battle/move_effect/protect.c @@ -125,6 +125,36 @@ SINGLE_BATTLE_TEST("Protect: King's Shield, Silk Trap and Obstruct protect from } } +SINGLE_BATTLE_TEST("Protect: King's Shield, Silk Trap and Obstruct don't lower stats when charging a two turn move") +{ + u32 move, protectMove; + PARAMETRIZE { move = MOVE_BOUNCE; protectMove = MOVE_KINGS_SHIELD; } + PARAMETRIZE { move = MOVE_DIG; protectMove = MOVE_KINGS_SHIELD; } + PARAMETRIZE { move = MOVE_BOUNCE; protectMove = MOVE_SILK_TRAP; } + PARAMETRIZE { move = MOVE_DIG; protectMove = MOVE_SILK_TRAP; } + PARAMETRIZE { move = MOVE_BOUNCE; protectMove = MOVE_OBSTRUCT; } + PARAMETRIZE { move = MOVE_DIG; protectMove = MOVE_OBSTRUCT; } + + GIVEN { + ASSUME(MoveMakesContact(MOVE_BOUNCE)); + ASSUME(MoveMakesContact(MOVE_DIG)); + ASSUME(gBattleMoveEffects[GetMoveEffect(MOVE_BOUNCE)].twoTurnEffect); + ASSUME(gBattleMoveEffects[GetMoveEffect(MOVE_DIG)].twoTurnEffect); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, protectMove); MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, protectMove, player); + + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + NONE_OF { + HP_BAR(player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } + } +} + SINGLE_BATTLE_TEST("Protect: Spiky Shield does 1/8 dmg of max hp of attackers making contact and may faint them") { enum Move usedMove = MOVE_NONE; @@ -162,6 +192,32 @@ SINGLE_BATTLE_TEST("Protect: Spiky Shield does 1/8 dmg of max hp of attackers ma } } +SINGLE_BATTLE_TEST("Protect: Spiky Shield doesn't hurt attacker when charging a two turn move") +{ + u32 move; + PARAMETRIZE { move = MOVE_BOUNCE; } + PARAMETRIZE { move = MOVE_DIG; } + + GIVEN { + ASSUME(MoveMakesContact(MOVE_BOUNCE)); + ASSUME(MoveMakesContact(MOVE_DIG)); + ASSUME(gBattleMoveEffects[GetMoveEffect(MOVE_BOUNCE)].twoTurnEffect); + ASSUME(gBattleMoveEffects[GetMoveEffect(MOVE_DIG)].twoTurnEffect); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SPIKY_SHIELD); MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKY_SHIELD, player); + + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + NONE_OF { + HP_BAR(player); + HP_BAR(opponent); + } + } +} + SINGLE_BATTLE_TEST("Protect: Baneful Bunker poisons Pokémon for moves making contact") { enum Move usedMove = MOVE_NONE; @@ -214,6 +270,32 @@ SINGLE_BATTLE_TEST("Protect: Baneful Bunker can't poison Pokémon if they are al } } +SINGLE_BATTLE_TEST("Protect: Baneful Bunker doesn't poison attacker when charging a two turn move") +{ + u32 move; + PARAMETRIZE { move = MOVE_BOUNCE; } + PARAMETRIZE { move = MOVE_DIG; } + + GIVEN { + ASSUME(MoveMakesContact(MOVE_BOUNCE)); + ASSUME(MoveMakesContact(MOVE_DIG)); + ASSUME(gBattleMoveEffects[GetMoveEffect(MOVE_BOUNCE)].twoTurnEffect); + ASSUME(gBattleMoveEffects[GetMoveEffect(MOVE_DIG)].twoTurnEffect); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BANEFUL_BUNKER); MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BANEFUL_BUNKER, player); + + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + NONE_OF { + HP_BAR(player); + STATUS_ICON(opponent, STATUS1_POISON); + } + } +} + SINGLE_BATTLE_TEST("Protect: Burning Bulwark burns Pokémon for moves making contact") { enum Move usedMove = MOVE_NONE; @@ -266,6 +348,32 @@ SINGLE_BATTLE_TEST("Protect: Burning Bulwark can't burn Pokémon if they are alr } } +SINGLE_BATTLE_TEST("Protect: Burning Bulwark doesn't burn attacker when charging a two turn move") +{ + u32 move; + PARAMETRIZE { move = MOVE_BOUNCE; } + PARAMETRIZE { move = MOVE_DIG; } + + GIVEN { + ASSUME(MoveMakesContact(MOVE_BOUNCE)); + ASSUME(MoveMakesContact(MOVE_DIG)); + ASSUME(gBattleMoveEffects[GetMoveEffect(MOVE_BOUNCE)].twoTurnEffect); + ASSUME(gBattleMoveEffects[GetMoveEffect(MOVE_DIG)].twoTurnEffect); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BURNING_BULWARK); MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BURNING_BULWARK, player); + + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + NONE_OF { + HP_BAR(player); + STATUS_ICON(opponent, STATUS1_BURN); + } + } +} + SINGLE_BATTLE_TEST("Protect: Recoil damage is not applied if target was protected") { u32 j, k; @@ -530,7 +638,7 @@ DOUBLE_BATTLE_TEST("Protect: Quick Guard can not fail on consecutive turns (Gen6 } } -DOUBLE_BATTLE_TEST("Protect: Crafty Shield protects self and ally from status moves") +DOUBLE_BATTLE_TEST("Crafty Shield protects self and ally from opposing status moves") { enum Move move = MOVE_NONE; struct BattlePokemon *targetOpponent = NULL; @@ -571,6 +679,72 @@ DOUBLE_BATTLE_TEST("Protect: Crafty Shield protects self and ally from status mo } } +DOUBLE_BATTLE_TEST("Crafty Shield does not protect against status moves used on the user's side") +{ + u32 move; + + PARAMETRIZE { move = MOVE_AROMATHERAPY; } + PARAMETRIZE { move = MOVE_ACUPRESSURE; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_AROMATHERAPY) == EFFECT_HEAL_BELL); + ASSUME(GetMoveEffect(MOVE_ACUPRESSURE) == EFFECT_ACUPRESSURE); + PLAYER(SPECIES_WOBBUFFET) { Speed(5); } + PLAYER(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } + OPPONENT(SPECIES_WYNAUT) { Speed(5); Status1(STATUS1_BURN); } + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_CRAFTY_SHIELD); + if (move == MOVE_ACUPRESSURE) + MOVE(opponentRight, move, target: opponentLeft); + else + MOVE(opponentRight, move); + } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CRAFTY_SHIELD, opponentLeft); + if (move == MOVE_ACUPRESSURE) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ACUPRESSURE, opponentRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_AROMATHERAPY, opponentRight); + STATUS_ICON(opponentRight, none: TRUE); + } + } +} + +DOUBLE_BATTLE_TEST("Crafty Shield does not protect against entry hazard moves") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SPIKES; } + PARAMETRIZE { move = MOVE_STEALTH_ROCK; } + PARAMETRIZE { move = MOVE_TOXIC_SPIKES; } + PARAMETRIZE { move = MOVE_STICKY_WEB; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_CRAFTY_SHIELD); MOVE(playerLeft, move, target: opponentLeft); } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CRAFTY_SHIELD, opponentLeft); + if (move == MOVE_SPIKES) { + MESSAGE("Spikes were scattered on the ground all around the opposing team!"); + } else if (move == MOVE_TOXIC_SPIKES) { + MESSAGE("Poison spikes were scattered on the ground all around the opposing team!"); + } else if (move == MOVE_STEALTH_ROCK) { + MESSAGE("Pointed stones float in the air around the opposing team!"); + } else { + MESSAGE("A sticky web has been laid out on the ground around the opposing team!"); + } + } +} + SINGLE_BATTLE_TEST("Protect: Protect does not block Confide or Decorate") { enum Move move; @@ -620,6 +794,11 @@ DOUBLE_BATTLE_TEST("Crafty Shield protects self and ally from Confide and Decora DOUBLE_BATTLE_TEST("Crafty Shield does not protect against moves that target all battlers") { + u32 move; + + PARAMETRIZE { move = MOVE_FLOWER_SHIELD; } + PARAMETRIZE { move = MOVE_PERISH_SONG; } + GIVEN { ASSUME(GetSpeciesType(SPECIES_TANGELA, 0) == TYPE_GRASS); ASSUME(GetSpeciesType(SPECIES_TANGROWTH, 0) == TYPE_GRASS); @@ -630,18 +809,26 @@ DOUBLE_BATTLE_TEST("Crafty Shield does not protect against moves that target all OPPONENT(SPECIES_SUNKERN); OPPONENT(SPECIES_SUNFLORA); } WHEN { - TURN { MOVE(opponentLeft, MOVE_CRAFTY_SHIELD); MOVE(opponentRight, MOVE_CELEBRATE); MOVE(playerLeft, MOVE_FLOWER_SHIELD); MOVE(playerRight, MOVE_CELEBRATE); } + TURN { MOVE(opponentLeft, MOVE_CRAFTY_SHIELD); MOVE(opponentRight, MOVE_CELEBRATE); MOVE(playerLeft, move); MOVE(playerRight, MOVE_CELEBRATE); } } SCENE { - MESSAGE("Tangela used Flower Shield!"); - ANIMATION(ANIM_TYPE_MOVE, MOVE_FLOWER_SHIELD, playerLeft); - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); - MESSAGE("Tangela's Defense rose!"); - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); - MESSAGE("The opposing Sunkern's Defense rose!"); - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); - MESSAGE("Tangrowth's Defense rose!"); - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); - MESSAGE("The opposing Sunflora's Defense rose!"); + if (move == MOVE_FLOWER_SHIELD) { + MESSAGE("Tangela used Flower Shield!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLOWER_SHIELD, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + MESSAGE("Tangela's Defense rose!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + MESSAGE("The opposing Sunkern's Defense rose!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + MESSAGE("Tangrowth's Defense rose!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("The opposing Sunflora's Defense rose!"); + } else { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PERISH_SONG, playerLeft); + NONE_OF { + MESSAGE("The opposing Sunkern protected itself!"); + MESSAGE("The opposing Sunflora protected itself!"); + } + } } } diff --git a/test/battle/move_effect/trick.c b/test/battle/move_effect/trick.c index 2110c8504d..d9a924769c 100644 --- a/test/battle/move_effect/trick.c +++ b/test/battle/move_effect/trick.c @@ -184,6 +184,18 @@ SINGLE_BATTLE_TEST("Trick fails if the target is behind a Substitute") } } +SINGLE_BATTLE_TEST("Trick can be used against targets with an active form change that doesn't require items") +{ + GIVEN { + PLAYER(SPECIES_XERNEAS); + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_ORAN_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_TRICK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, opponent); + } +} + SINGLE_BATTLE_TEST("Trick does not remove the user's choice lock if both the target and use are holding choice items before Gen 5") { GIVEN { diff --git a/test/battle/move_effects_combined/hyperspace_fury.c b/test/battle/move_effects_combined/hyperspace_fury.c new file mode 100644 index 0000000000..5c0a865c2b --- /dev/null +++ b/test/battle/move_effects_combined/hyperspace_fury.c @@ -0,0 +1,97 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_HYPERSPACE_FURY) == EFFECT_HYPERSPACE_FURY); + ASSUME(MoveHasAdditionalEffect(MOVE_HYPERSPACE_FURY, MOVE_EFFECT_FEINT)); + ASSUME(MoveHasAdditionalEffectSelf(MOVE_HYPERSPACE_FURY, MOVE_EFFECT_DEF_MINUS_1)); + ASSUME(GetMoveEffect(MOVE_PROTECT) == EFFECT_PROTECT); +} + +SINGLE_BATTLE_TEST("Hyperspace Fury fails if used by a Pokémon other than Hoopa Unbound") +{ + u32 species; + PARAMETRIZE { species = SPECIES_WOBBUFFET; } + PARAMETRIZE { species = SPECIES_HOOPA_CONFINED; } + PARAMETRIZE { species = SPECIES_HOOPA_UNBOUND; } + + GIVEN { + PLAYER(species); + OPPONENT(SPECIES_REGIROCK); + } WHEN { + TURN { MOVE(player, MOVE_HYPERSPACE_FURY); } + } SCENE { + switch (species) + { + case SPECIES_HOOPA_UNBOUND: + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPERSPACE_FURY, player); + break; + case SPECIES_HOOPA_CONFINED: + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPERSPACE_FURY, player); + MESSAGE("But Hoopa can't use it the way it is now!"); + break; + case SPECIES_WOBBUFFET: + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPERSPACE_FURY, player); + MESSAGE("But Wobbuffet can't use the move!"); + break; + default: + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPERSPACE_FURY, player); + break; + } + } +} + +DOUBLE_BATTLE_TEST("Hyperspace Fury hits the target through Protect and breaks it") +{ + GIVEN { + PLAYER(SPECIES_HOOPA_UNBOUND); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_REGIROCK); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_PROTECT); MOVE(playerLeft, MOVE_HYPERSPACE_FURY, target: opponentLeft); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPERSPACE_FURY, playerLeft); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerRight); + HP_BAR(opponentLeft); + } +} + +SINGLE_BATTLE_TEST("Hyperspace Fury lowers the user's Defense by 1 stage after hitting the target") +{ + GIVEN { + PLAYER(SPECIES_HOOPA_UNBOUND); + OPPONENT(SPECIES_REGIROCK); + } WHEN { + TURN { MOVE(player, MOVE_HYPERSPACE_FURY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPERSPACE_FURY, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); + } +} + +DOUBLE_BATTLE_TEST("Hyperspace Fury breaks protection and lowers the user's Defense by 1 stage") +{ + GIVEN { + PLAYER(SPECIES_HOOPA_UNBOUND); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_REGIROCK); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_PROTECT); MOVE(playerLeft, MOVE_HYPERSPACE_FURY, target: opponentLeft); MOVE(playerRight, MOVE_SCRATCH, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPERSPACE_FURY, playerLeft); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerRight); + HP_BAR(opponentLeft); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_DEF], DEFAULT_STAT_STAGE - 1); + } +} diff --git a/tools/aif2pcm/aif2pcm b/tools/aif2pcm/aif2pcm new file mode 100755 index 0000000000..295ae9cfb7 Binary files /dev/null and b/tools/aif2pcm/aif2pcm differ