diff --git a/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml b/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml index 44cc8c2891..6493f4c11b 100644 --- a/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml +++ b/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml @@ -23,9 +23,10 @@ body: label: Version description: What version of pokeemerald-expansion are you using as a base? options: - - 1.7.2 (Latest release) + - 1.7.3 (Latest release) - master (default when pulling, unreleased bugfixes) - upcoming (Edge) + - 1.7.2 - 1.7.1 - 1.7.0 - 1.6.2 diff --git a/.github/ISSUE_TEMPLATE/04_other_errors.yaml b/.github/ISSUE_TEMPLATE/04_other_errors.yaml index 41d5c30eff..e39eac4f56 100644 --- a/.github/ISSUE_TEMPLATE/04_other_errors.yaml +++ b/.github/ISSUE_TEMPLATE/04_other_errors.yaml @@ -23,9 +23,10 @@ body: label: Version description: What version of pokeemerald-expansion are you using as a base? options: - - 1.7.2 (Latest release) + - 1.7.3 (Latest release) - master (default when pulling, unreleased bugfixes) - upcoming (Edge) + - 1.7.2 - 1.7.1 - 1.7.0 - 1.6.2 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8c5bcb7ed8..0322cbe11f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,6 +17,7 @@ jobs: GAME_LANGUAGE: ENGLISH MODERN: 0 COMPARE: 0 + UNUSED_ERROR: 1 steps: - name: Checkout uses: actions/checkout@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fba967c8a..d4805a5d40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ # Pokeemerald-Expansion Changelogs -## [Version 1.7.2](docs/changelogs/1.7.2.md) - Bugfix Release - -## [Version 1.7.1](docs/changelogs/1.7.1.md) - Bugfix Release - -## [Version 1.7.0](docs/changelogs/1.7.0.md) - Feature Release - -## [Version 1.6.2](docs/changelogs/1.6.2.md) - Bugfix Release +## Version 1.7.x +### [Version 1.7.3](docs/changelogs/1.7.3.md) - Bugfix Release +### [Version 1.7.2](docs/changelogs/1.7.2.md) - Bugfix Release +### [Version 1.7.1](docs/changelogs/1.7.1.md) - Bugfix Release +### [Version 1.7.0](docs/changelogs/1.7.0.md) - Feature Release + +## Version 1.6.x +### [Version 1.6.2](docs/changelogs/1.6.2.md) - Bugfix Release diff --git a/Makefile b/Makefile index a0a0415305..101b286df7 100644 --- a/Makefile +++ b/Makefile @@ -36,13 +36,14 @@ else EXE := endif -TITLE := POKEMON EMER -GAME_CODE := BPEE -MAKER_CODE := 01 -REVISION := 0 -MODERN ?= 1 -TEST ?= 0 -ANALYZE ?= 0 +TITLE := POKEMON EMER +GAME_CODE := BPEE +MAKER_CODE := 01 +REVISION := 0 +MODERN ?= 1 +TEST ?= 0 +ANALYZE ?= 0 +UNUSED_ERROR ?= 0 ifeq (agbcc,$(MAKECMDGOALS)) MODERN := 0 @@ -127,6 +128,12 @@ override CFLAGS += -mthumb -mthumb-interwork -O2 -mabi=apcs-gnu -mtune=arm7tdmi ifeq ($(ANALYZE),1) override CFLAGS += -fanalyzer endif +# Only throw an error for unused elements if its RH-Hideout's repo +ifeq ($(UNUSED_ERROR),0) +ifneq ($(GITHUB_REPOSITORY_OWNER),rh-hideout) +override CFLAGS += -Wno-error=unused-variable -Wno-error=unused-const-variable -Wno-error=unused-parameter -Wno-error=unused-function -Wno-error=unused-but-set-parameter -Wno-error=unused-but-set-variable -Wno-error=unused-value -Wno-error=unused-local-typedefs +endif +endif ROM := $(MODERN_ROM_NAME) OBJ_DIR := $(MODERN_OBJ_DIR_NAME) LIBPATH := -L "$(dir $(shell $(PATH_MODERNCC) -mthumb -print-file-name=libgcc.a))" -L "$(dir $(shell $(PATH_MODERNCC) -mthumb -print-file-name=libnosys.a))" -L "$(dir $(shell $(PATH_MODERNCC) -mthumb -print-file-name=libc.a))" diff --git a/README.md b/README.md index 53dc2ab6ed..9f2e022dd6 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ pokeemerald-expansion is a decomp hack base project based off pret's [pokeemeral If you use pokeemerald-expansion in your hack, please add RHH (Rom Hacking Hideout) to your credits list. Optionally, you can list the version used, so it can help players know what features to expect. You can phrase it as the following: ``` -Based off RHH's pokeemerald-expansion v1.7.2 https://github.com/rh-hideout/pokeemerald-expansion/ +Based off RHH's pokeemerald-expansion v1.7.3 https://github.com/rh-hideout/pokeemerald-expansion/ ``` ## What features are included? @@ -166,7 +166,7 @@ With this, you'll get the latest version of pokeemerald-expansion, plus a couple ## **How do I update my version of pokeemerald-expansion?** - If you haven't set up a remote, run the command `git remote add RHH https://github.com/rh-hideout/pokeemerald-expansion`. -- Once you have your remote set up, run the command `git pull RHH expansion/1.7.2`. +- Once you have your remote set up, run the command `git pull RHH expansion/1.7.3`. ### Please consider crediting the entire [list of contributors](https://github.com/rh-hideout/pokeemerald-expansion/wiki/Credits) in your project, as they have all worked hard to develop this project :) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index d89bab70c5..c0350d2535 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -406,7 +406,7 @@ gBattleScriptsForMoveEffects:: .4byte BattleScript_EffectHit @ EFFECT_RISING_VOLTAGE .4byte BattleScript_EffectHit @ EFFECT_BEAK_BLAST .4byte BattleScript_EffectCourtChange @ EFFECT_COURT_CHANGE - .4byte BattleScript_EffectSteelBeam @ EFFECT_STEEL_BEAM + .4byte BattleScript_EffectMaxHp50Recoil @ EFFECT_MAX_HP_50_RECOIL .4byte BattleScript_EffectExtremeEvoboost @ EFFECT_EXTREME_EVOBOOST .4byte BattleScript_EffectHitSetRemoveTerrain @ EFFECT_HIT_SET_REMOVE_TERRAIN .4byte BattleScript_EffectDarkVoid @ EFFECT_DARK_VOID @@ -927,7 +927,7 @@ BattleScript_EffectShellTrap:: waitmessage B_WAIT_TIME_LONG goto BattleScript_MoveEnd -BattleScript_EffectSteelBeam:: +BattleScript_EffectMaxHp50Recoil:: attackcanceler attackstring ppreduce @@ -3592,6 +3592,7 @@ BattleScript_MindBlownDamp: goto BattleScript_DampStopsExplosion BattleScript_EffectMindBlown_HpDown: setbyte sMULTIHIT_EFFECT, 1 @ Note to not faint the attacker + jumpifability BS_ATTACKER, ABILITY_MAGIC_GUARD, BattleScript_EffectMindBlown_AnimDmgNoFaint dmg_1_2_attackerhp healthbarupdate BS_ATTACKER datahpupdate BS_ATTACKER @@ -6715,6 +6716,7 @@ BattleScript_PrintFullBox:: BattleScript_ActionSwitch:: hpthresholds2 BS_ATTACKER + copybyte sSAVED_BATTLER, gBattlerAttacker printstring STRINGID_RETURNMON jumpifbattletype BATTLE_TYPE_DOUBLE, BattleScript_PursuitSwitchDmgSetMultihit setmultihit 1 @@ -6732,6 +6734,7 @@ BattleScript_DoSwitchOut:: switchoutabilities BS_ATTACKER updatedynamax waitstate + copybyte gBattlerAttacker, sSAVED_BATTLER returnatktoball waitstate drawpartystatussummary BS_ATTACKER diff --git a/data/maps/DesertUnderpass/scripts.inc b/data/maps/DesertUnderpass/scripts.inc index f83f144764..c102bf3ac1 100644 --- a/data/maps/DesertUnderpass/scripts.inc +++ b/data/maps/DesertUnderpass/scripts.inc @@ -18,12 +18,14 @@ DesertUnderpass_EventScript_Fossil:: DesertUnderpass_EventScript_GiveClawFossil:: giveitem ITEM_CLAW_FOSSIL + goto_if_eq VAR_RESULT, FALSE, Common_EventScript_ShowBagIsFull removeobject LOCALID_FOSSIL release end DesertUnderpass_EventScript_GiveRootFossil:: giveitem ITEM_ROOT_FOSSIL + goto_if_eq VAR_RESULT, FALSE, Common_EventScript_ShowBagIsFull removeobject LOCALID_FOSSIL release end diff --git a/data/maps/MauvilleCity/scripts.inc b/data/maps/MauvilleCity/scripts.inc index 1e4080cadc..0d3b277b15 100644 --- a/data/maps/MauvilleCity/scripts.inc +++ b/data/maps/MauvilleCity/scripts.inc @@ -399,6 +399,7 @@ MauvilleCity_EventScript_Wattson:: goto_if_set FLAG_GOT_BASEMENT_KEY_FROM_WATTSON, MauvilleCity_EventScript_BegunNewMauville msgbox MauvilleCity_Text_WattsonNeedFavorTakeKey, MSGBOX_DEFAULT giveitem ITEM_BASEMENT_KEY + goto_if_eq VAR_RESULT, FALSE, Common_EventScript_ShowBagIsFull setflag FLAG_GOT_BASEMENT_KEY_FROM_WATTSON msgbox MauvilleCity_Text_WattsonWontBeChallenge, MSGBOX_DEFAULT release diff --git a/data/maps/MauvilleCity_BikeShop/scripts.inc b/data/maps/MauvilleCity_BikeShop/scripts.inc index f10fdd5046..8e1710547c 100644 --- a/data/maps/MauvilleCity_BikeShop/scripts.inc +++ b/data/maps/MauvilleCity_BikeShop/scripts.inc @@ -40,12 +40,14 @@ MauvilleCity_BikeShop_EventScript_YesFar:: MauvilleCity_BikeShop_EventScript_GetMachBike:: msgbox MauvilleCity_BikeShop_Text_ChoseMachBike, MSGBOX_DEFAULT giveitem ITEM_MACH_BIKE + goto_if_eq VAR_RESULT, FALSE, Common_EventScript_ShowBagIsFull goto MauvilleCity_BikeShop_EventScript_ComeBackToSwitchBikes end MauvilleCity_BikeShop_EventScript_GetAcroBike:: msgbox MauvilleCity_BikeShop_Text_ChoseAcroBike, MSGBOX_DEFAULT giveitem ITEM_ACRO_BIKE + goto_if_eq VAR_RESULT, FALSE, Common_EventScript_ShowBagIsFull goto MauvilleCity_BikeShop_EventScript_ComeBackToSwitchBikes end diff --git a/data/maps/MauvilleCity_House2/scripts.inc b/data/maps/MauvilleCity_House2/scripts.inc index 32af638813..aa0aae08db 100644 --- a/data/maps/MauvilleCity_House2/scripts.inc +++ b/data/maps/MauvilleCity_House2/scripts.inc @@ -24,8 +24,9 @@ MauvilleCity_House2_EventScript_AskToTradeForHarborMail:: MauvilleCity_House2_EventScript_AcceptTrade:: msgbox MauvilleCity_House2_Text_IllTradeYouCoinCase, MSGBOX_DEFAULT - removeitem ITEM_HARBOR_MAIL giveitem ITEM_COIN_CASE + goto_if_eq VAR_RESULT, FALSE, Common_EventScript_ShowBagIsFull + removeitem ITEM_HARBOR_MAIL setflag FLAG_RECEIVED_COIN_CASE goto MauvilleCity_House2_EventScript_ReceivedCoinCase end diff --git a/data/maps/MirageTower_4F/scripts.inc b/data/maps/MirageTower_4F/scripts.inc index 76293d8871..9cdff2ec5f 100644 --- a/data/maps/MirageTower_4F/scripts.inc +++ b/data/maps/MirageTower_4F/scripts.inc @@ -10,6 +10,7 @@ MirageTower_4F_EventScript_RootFossil:: msgbox MirageTower_4F_Text_TakeRootFossil, MSGBOX_YESNO goto_if_eq VAR_RESULT, NO, MirageTower_4F_EventScript_LeaveRootFossil giveitem ITEM_ROOT_FOSSIL + goto_if_eq VAR_RESULT, FALSE, Common_EventScript_ShowBagIsFull closemessage setflag FLAG_HIDE_MIRAGE_TOWER_ROOT_FOSSIL setflag FLAG_HIDE_MIRAGE_TOWER_CLAW_FOSSIL @@ -30,6 +31,7 @@ MirageTower_4F_EventScript_ClawFossil:: msgbox MirageTower_4F_Text_TakeClawFossil, MSGBOX_YESNO goto_if_eq VAR_RESULT, NO, MirageTower_4F_EventScript_LeaveClawFossil giveitem ITEM_CLAW_FOSSIL + goto_if_eq VAR_RESULT, FALSE, Common_EventScript_ShowBagIsFull closemessage setflag FLAG_HIDE_MIRAGE_TOWER_CLAW_FOSSIL setflag FLAG_HIDE_MIRAGE_TOWER_ROOT_FOSSIL diff --git a/data/maps/MossdeepCity_House3/scripts.inc b/data/maps/MossdeepCity_House3/scripts.inc index 7472e12e9b..67aa99db03 100644 --- a/data/maps/MossdeepCity_House3/scripts.inc +++ b/data/maps/MossdeepCity_House3/scripts.inc @@ -9,6 +9,7 @@ MossdeepCity_House3_EventScript_SuperRodFisherman:: goto_if_eq VAR_RESULT, NO, MossdeepCity_House3_EventScript_DeclineSuperRod msgbox MossdeepCity_House3_Text_SuperRodIsSuper, MSGBOX_DEFAULT giveitem ITEM_SUPER_ROD + goto_if_eq VAR_RESULT, FALSE, Common_EventScript_ShowBagIsFull setflag FLAG_RECEIVED_SUPER_ROD msgbox MossdeepCity_House3_Text_TryDroppingRodInWater, MSGBOX_DEFAULT release diff --git a/data/maps/MtChimney/scripts.inc b/data/maps/MtChimney/scripts.inc index 4c15419324..6e110373b0 100644 --- a/data/maps/MtChimney/scripts.inc +++ b/data/maps/MtChimney/scripts.inc @@ -448,6 +448,7 @@ MtChimney_EventScript_MeteoriteMachine:: goto_if_eq VAR_RESULT, NO, MtChimney_EventScript_LeaveMeteoriteAlone msgbox MtChimney_Text_PlayerRemovedMeteorite, MSGBOX_DEFAULT giveitem ITEM_METEORITE + goto_if_eq VAR_RESULT, FALSE, Common_EventScript_ShowBagIsFull setflag FLAG_RECEIVED_METEORITE releaseall end diff --git a/data/maps/Route104_PrettyPetalFlowerShop/scripts.inc b/data/maps/Route104_PrettyPetalFlowerShop/scripts.inc index 5f0ec28fa5..54b2dacef1 100644 --- a/data/maps/Route104_PrettyPetalFlowerShop/scripts.inc +++ b/data/maps/Route104_PrettyPetalFlowerShop/scripts.inc @@ -74,6 +74,7 @@ Route104_PrettyPetalFlowerShop_EventScript_WailmerPailGirl:: Route104_PrettyPetalFlowerShop_EventScript_GiveWailmerPail:: msgbox Route104_PrettyPetalFlowerShop_Text_YouCanHaveThis, MSGBOX_DEFAULT giveitem ITEM_WAILMER_PAIL + goto_if_eq VAR_RESULT, FALSE, Common_EventScript_ShowBagIsFull msgbox Route104_PrettyPetalFlowerShop_Text_WailmerPailExplanation, MSGBOX_DEFAULT setflag FLAG_RECEIVED_WAILMER_PAIL release diff --git a/data/maps/Route113_GlassWorkshop/scripts.inc b/data/maps/Route113_GlassWorkshop/scripts.inc index 0ade84a40a..06a04b0a74 100644 --- a/data/maps/Route113_GlassWorkshop/scripts.inc +++ b/data/maps/Route113_GlassWorkshop/scripts.inc @@ -28,6 +28,7 @@ Route113_GlassWorkshop_EventScript_GlassWorker:: goto_if_eq VAR_GLASS_WORKSHOP_STATE, 1, Route113_GlassWorkshop_EventScript_ExplainSootSack msgbox Route113_GlassWorkshop_Text_GoCollectAshWithThis, MSGBOX_DEFAULT giveitem ITEM_SOOT_SACK + goto_if_eq VAR_RESULT, FALSE, Common_EventScript_ShowBagIsFull setvar VAR_GLASS_WORKSHOP_STATE, 1 msgbox Route113_GlassWorkshop_Text_ExplainSootSack, MSGBOX_DEFAULT release diff --git a/data/maps/Route118/scripts.inc b/data/maps/Route118/scripts.inc index 6a6a1aae58..298cc3c9f3 100644 --- a/data/maps/Route118/scripts.inc +++ b/data/maps/Route118/scripts.inc @@ -34,6 +34,7 @@ Route118_EventScript_GoodRodFisherman:: Route118_EventScript_ReceiveGoodRod:: msgbox Route118_Text_IdenticalMindsTakeThis, MSGBOX_DEFAULT giveitem ITEM_GOOD_ROD + goto_if_eq VAR_RESULT, FALSE, Common_EventScript_ShowBagIsFull setflag FLAG_RECEIVED_GOOD_ROD msgbox Route118_Text_TryYourLuckFishing, MSGBOX_DEFAULT release diff --git a/data/scripts/contest_hall.inc b/data/scripts/contest_hall.inc index c9d64c712f..fb1fd9decf 100644 --- a/data/scripts/contest_hall.inc +++ b/data/scripts/contest_hall.inc @@ -31,6 +31,7 @@ LilycoveCity_ContestLobby_EventScript_ReceptionWelcome:: LilycoveCity_ContestLobby_EventScript_GivePokeblockCase:: msgbox LilycoveCity_ContestLobby_Text_ReceptionDontHavePokeblockCase, MSGBOX_DEFAULT giveitem ITEM_POKEBLOCK_CASE + goto_if_eq VAR_RESULT, FALSE, Common_EventScript_ShowBagIsFull setflag FLAG_RECEIVED_POKEBLOCK_CASE msgbox LilycoveCity_ContestLobby_Text_NowThatWeveClearedThatUp, MSGBOX_DEFAULT return diff --git a/docs/changelogs/1.7.2.md b/docs/changelogs/1.7.2.md index 8fc0d60565..1c94bbda3d 100644 --- a/docs/changelogs/1.7.2.md +++ b/docs/changelogs/1.7.2.md @@ -1,4 +1,4 @@ -# Template +# Version 1.7.2 ```md ## How to update diff --git a/docs/changelogs/1.7.3.md b/docs/changelogs/1.7.3.md new file mode 100644 index 0000000000..4727e195bc --- /dev/null +++ b/docs/changelogs/1.7.3.md @@ -0,0 +1,77 @@ +# Version 1.7.3 + +```md +## How to update +- If you haven't set up a remote, run the command `git remote add RHH https://github.com/rh-hideout/pokeemerald-expansion`. +- Once you have your remote set up, run the command `git pull RHH expansion/1.7.3`. +``` + +## 🌋 *IMPORTANT CHANGES* 🌋 +* Unused warnings are no longer treated as errrors by default by @AsparagusEduardo in https://github.com/rh-hideout/pokeemerald-expansion/pull/4092 +* [Critical fix] Backported gHeap alignment fix from upstream pret by @AsparagusEduardo in https://github.com/rh-hideout/pokeemerald-expansion/pull/4089 + * Fixes issue that causes graphics to bug when leaving Littleroot for Birch's cutscene. + +## 🧬 General 🧬 +* Fixed HGSS Dex's dark mode search palette by @ravepossum in https://github.com/rh-hideout/pokeemerald-expansion/pull/4095 + +## 🐉 Pokémon 🐉 +### Changed +* Condensed Oinkologne teachable learnsets (they previously had different tables despite having the same learnsets) by @Bassoonian in https://github.com/rh-hideout/pokeemerald-expansion/pull/4026 +* Removed illegal teachable learnset moves that didn't match Gen 7 or 9 by @Bassoonian in https://github.com/rh-hideout/pokeemerald-expansion/pull/4039 and https://github.com/rh-hideout/pokeemerald-expansion/pull/4042 +### Fixed +* Fixed incorrect family toggle preproc blocks by @AsparagusEduardo in https://github.com/rh-hideout/pokeemerald-expansion/pull/4024 + * Cosplay and Cap Pikachu animations were still being included even if disabled. + * Fixed compile errors when: + * Feebas' family was disabled but not Castform. + * Chatot was disabled but not Spiritomb. + * Virizion was disabled but not Tornadus or Thundurus. + * Zekrom was disabled but not Landorus. + * Kyurem was disabled but not Keldeo or Meloetta. + * Wishiwashi was disabled but not Rockruff. +* Fixed compile error when disabling Gen 4 cross-evolutions while having Kingdra enabled by @Skyeward and @Bassoonian in https://github.com/rh-hideout/pokeemerald-expansion/pull/4044 and https://github.com/rh-hideout/pokeemerald-expansion/pull/4046 + +## ⚔️ Battle General ⚔️ ## +### Fixed +* Fixed Steven double battle palette error by @johannakullmann in https://github.com/rh-hideout/pokeemerald-expansion/pull/4078 + +## 🤹 Moves 🤹 +### Changed +* Renamed `EFFECT_STEEL_BEAM` to `EFFECT_MAX_HP_50_RECOIL` by @AlexOn1ine in https://github.com/rh-hideout/pokeemerald-expansion/pull/4043 +### Fixed +* Fixed Pursuit's effect not working by @ZnogyroP @AlexOn1ine in https://github.com/rh-hideout/pokeemerald-expansion/pull/4086 + +## 🎭 Abilities 🎭 +### Fixed +* Fixed Emergency Exit issues: + * Fixed rounding error that caused it to not switch out when odd-numbered HP was off by 1 by @SBird1337 in https://github.com/rh-hideout/pokeemerald-expansion/pull/4040 + * Eg: going from 101 Max HP to 50 HP would've *not* cause it to switch out. + * Fixes Emergency Exit switching out even if the Pokémon was healed above the threshold before it would've triggered by @AlexOn1ine in https://github.com/rh-hideout/pokeemerald-expansion/pull/4041 + * Eg: going from 100 Max HP to 45 HP and eating a Sitrus Berry back to 55 HP would've cause it to switch out. + * Cleanup by @AlexOn1ine in https://github.com/rh-hideout/pokeemerald-expansion/pull/4100 +* Fixed Corrosion only working for status moves by @AlexOn1ine in https://github.com/rh-hideout/pokeemerald-expansion/pull/4037 +* Fixed Magic Guard not preventing Mind Blown recoil damage by @AlexOn1ine in https://github.com/rh-hideout/pokeemerald-expansion/pull/4043 + +## 🧶 Items 🧶 +### Fixed +* Fixed oversight causing chosen fossil to be lost if bag is full by @fakuzatsu in https://github.com/rh-hideout/pokeemerald-expansion/pull/3978 + * Fixed other Key Item script oversights by @Bassoonian in https://github.com/rh-hideout/pokeemerald-expansion/pull/4066 +* Fixed Life Orb causing damage on switch-in from Eject Pack and Red Card by @AlexOn1ine in https://github.com/rh-hideout/pokeemerald-expansion/pull/4038 +* Fixed Glimmering Charm sprite by @SonikkuA-DatH and @AlexOn1ine in https://github.com/rh-hideout/pokeemerald-expansion/pull/4047 + +## 🤖 Battle AI 🤖 +### Fixed +* Fixed AI trying to switch into the same mon twice in the same turn by @DizzyEggg in https://github.com/rh-hideout/pokeemerald-expansion/pull/4098 + +## 🧪 Test Runner 🧪 +### Added +* Corrosion tests by @AlexOn1ine in https://github.com/rh-hideout/pokeemerald-expansion/pull/4037 +* Pursuit/Tangling Hair interaction by @ZnogyroP @AlexOn1ine in https://github.com/rh-hideout/pokeemerald-expansion/pull/4086 +### Changed +* Passing `KNOWN_FAILING` tests are now listed separately from `PASSED` tests by @AsparagusEduardo in https://github.com/rh-hideout/pokeemerald-expansion/pull/4063 + +## New Contributors +* @Skyeward made their first contribution in https://github.com/rh-hideout/pokeemerald-expansion/pull/4044 + +**Full Changelog**: https://github.com/rh-hideout/pokeemerald-expansion/compare/expansion/1.7.2...expansion/1.7.3 + + diff --git a/docs/changelogs/template.md b/docs/changelogs/template.md index 607d69592a..f67520c388 100644 --- a/docs/changelogs/template.md +++ b/docs/changelogs/template.md @@ -3,7 +3,7 @@ ```md ## How to update - If you haven't set up a remote, run the command `git remote add RHH https://github.com/rh-hideout/pokeemerald-expansion`. -- Once you have your remote set up, run the command `git pull RHH expansion/1.7.2`. +- Once you have your remote set up, run the command `git pull RHH expansion/1.Y.Z`. ``` ## 🌋 *IMPORTANT CHANGES* 🌋 @@ -88,6 +88,6 @@ ## New Contributors * Tony -**Full Changelog**: https://github.com/rh-hideout/pokeemerald-expansion/compare/expansion/1.7.1...expansion/1.7.2 +**Full Changelog**: https://github.com/rh-hideout/pokeemerald-expansion/compare/expansion/1.Y.Z...expansion/1.Y.Z - + diff --git a/graphics/items/icon_palettes/glimmering_charm.pal b/graphics/items/icon_palettes/glimmering_charm.pal index 813886d85a..ff3b601987 100644 --- a/graphics/items/icon_palettes/glimmering_charm.pal +++ b/graphics/items/icon_palettes/glimmering_charm.pal @@ -1,18 +1,19 @@ JASC-PAL 0100 -15 -114 109 97 +16 0 0 0 -115 62 231 +49 49 49 +60 195 171 57 95 90 -52 103 15 +255 255 255 +255 204 227 +202 150 255 +115 62 231 48 101 216 -225 59 159 -136 154 143 -60 195 171 +219 48 105 76 204 74 -202 150 255 +52 103 15 +136 154 143 142 191 235 -242 193 215 -238 219 161 -255 255 255 +255 211 201 +255 235 173 diff --git a/graphics/items/icons/glimmering_charm.png b/graphics/items/icons/glimmering_charm.png index bbed0dcb04..8909473144 100644 Binary files a/graphics/items/icons/glimmering_charm.png and b/graphics/items/icons/glimmering_charm.png differ diff --git a/include/battle.h b/include/battle.h index ea710ed797..0eba532cd9 100644 --- a/include/battle.h +++ b/include/battle.h @@ -208,7 +208,7 @@ struct SpecialStatus // End of byte u8 emergencyExited:1; u8 afterYou:1; - u8 magicianStolen:1; // So that Life Orb doesn't activate after Magician steals it. + u8 preventLifeOrbDamage:1; // So that Life Orb doesn't activate various effects. }; struct SideTimer @@ -323,7 +323,7 @@ struct AiLogicData bool8 shouldSwitchMon; // Because all available moves have no/little effect. Each bit per battler. u8 monToSwitchId[MAX_BATTLERS_COUNT]; // ID of the mon to switch. bool8 weatherHasEffect; // The same as WEATHER_HAS_EFFECT. Stored here, so it's called only once. - u8 mostSuitableMonId; // Stores result of GetMostSuitableMonToSwitchInto, which decides which generic mon the AI would switch into if they decide to switch. This can be overruled by specific mons found in ShouldSwitch; the final resulting mon is stored in AI_monToSwitchIntoId. + u8 mostSuitableMonId[MAX_BATTLERS_COUNT]; // Stores result of GetMostSuitableMonToSwitchInto, which decides which generic mon the AI would switch into if they decide to switch. This can be overruled by specific mons found in ShouldSwitch; the final resulting mon is stored in AI_monToSwitchIntoId. struct SwitchinCandidate switchinCandidate; // Struct used for deciding which mon to switch to in battle_ai_switch_items.c }; diff --git a/include/constants/battle_move_effects.h b/include/constants/battle_move_effects.h index beebe59462..056f0ce4f7 100644 --- a/include/constants/battle_move_effects.h +++ b/include/constants/battle_move_effects.h @@ -383,7 +383,7 @@ #define EFFECT_RISING_VOLTAGE 377 #define EFFECT_BEAK_BLAST 378 #define EFFECT_COURT_CHANGE 379 -#define EFFECT_STEEL_BEAM 380 +#define EFFECT_MAX_HP_50_RECOIL 380 #define EFFECT_EXTREME_EVOBOOST 381 #define EFFECT_HIT_SET_REMOVE_TERRAIN 382 // genesis supernova #define EFFECT_DARK_VOID 383 diff --git a/include/constants/expansion.h b/include/constants/expansion.h index ff027221dd..42af988a19 100644 --- a/include/constants/expansion.h +++ b/include/constants/expansion.h @@ -3,7 +3,7 @@ #define EXPANSION_VERSION_MAJOR 1 #define EXPANSION_VERSION_MINOR 7 -#define EXPANSION_VERSION_PATCH 2 +#define EXPANSION_VERSION_PATCH 3 // FALSE if this this version of Expansion is not a tagged commit, i.e. // it contains unreleased changes. diff --git a/ld_script_modern.ld b/ld_script_modern.ld index f3bf7b6798..5d9a7daf07 100644 --- a/ld_script_modern.ld +++ b/ld_script_modern.ld @@ -15,6 +15,11 @@ SECTIONS { ewram 0x2000000 (NOLOAD) : ALIGN(4) { + /* + We link malloc.o here to prevent `gHeap` from landing in the middle of EWRAM. + Otherwise this causes corruption issues on some ld versions + */ + gflib/malloc.o(ewram_data); src/*.o(ewram_data); gflib/*.o(ewram_data); diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 87ccabdd5e..639279e613 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -5,6 +5,7 @@ #include "battle_anim.h" #include "battle_ai_util.h" #include "battle_ai_main.h" +#include "battle_controllers.h" #include "battle_factory.h" #include "battle_setup.h" #include "battle_z_move.h" @@ -414,11 +415,21 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData) } } -static bool32 AI_SwitchMonIfSuitable(u32 battler) +static bool32 AI_SwitchMonIfSuitable(u32 battler, bool32 doubleBattle) { - u32 monToSwitchId = AI_DATA->mostSuitableMonId; - if (monToSwitchId != PARTY_SIZE) + u32 monToSwitchId = AI_DATA->mostSuitableMonId[battler]; + if (monToSwitchId != PARTY_SIZE && IsValidForBattle(&GetBattlerParty(battler)[monToSwitchId])) { + gBattleMoveDamage = monToSwitchId; + // Edge case: See if partner already chose to switch into the same mon + if (doubleBattle) + { + u32 partner = BATTLE_PARTNER(battler); + if (AI_DATA->shouldSwitchMon & gBitTable[partner] && AI_DATA->monToSwitchId[partner] == monToSwitchId) + { + return FALSE; + } + } AI_DATA->shouldSwitchMon |= gBitTable[battler]; AI_DATA->monToSwitchId[battler] = monToSwitchId; return TRUE; @@ -455,7 +466,7 @@ static bool32 AI_ShouldSwitchIfBadMoves(u32 battler, bool32 doubleBattle) break; } } - if (i == MAX_BATTLERS_COUNT && AI_SwitchMonIfSuitable(battler)) + if (i == MAX_BATTLERS_COUNT && AI_SwitchMonIfSuitable(battler, doubleBattle)) return TRUE; } else @@ -466,7 +477,7 @@ static bool32 AI_ShouldSwitchIfBadMoves(u32 battler, bool32 doubleBattle) break; } - if (i == MAX_MON_MOVES && AI_SwitchMonIfSuitable(battler)) + if (i == MAX_MON_MOVES && AI_SwitchMonIfSuitable(battler, doubleBattle)) return TRUE; } @@ -478,7 +489,7 @@ static bool32 AI_ShouldSwitchIfBadMoves(u32 battler, bool32 doubleBattle) && IsTruantMonVulnerable(battler, gBattlerTarget) && gDisableStructs[battler].truantCounter && gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2 - && AI_SwitchMonIfSuitable(battler)) + && AI_SwitchMonIfSuitable(battler, doubleBattle)) { return TRUE; } diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index f4ea291767..bdde562c5c 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -65,7 +65,7 @@ void GetAIPartyIndexes(u32 battler, s32 *firstId, s32 *lastId) } // Note that as many return statements as possible are INTENTIONALLY put after all of the loops; -// the function can take a max of about 0.06s to run, and this prevents the player from identifying +// the function can take a max of about 0.06s to run, and this prevents the player from identifying // whether the mon will switch or not by seeing how long the delay is before they select a move static bool8 HasBadOdds(u32 battler) @@ -82,12 +82,12 @@ static bool8 HasBadOdds(u32 battler) return FALSE; // Won't bother configuring this for double battles - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) return FALSE; - + opposingPosition = BATTLE_OPPOSITE(GetBattlerPosition(battler)); opposingBattler = GetBattlerAtPosition(opposingPosition); - + // Gets types of player (opposingBattler) and computer (battler) atkType1 = gBattleMons[opposingBattler].type1; atkType2 = gBattleMons[opposingBattler].type2; @@ -102,7 +102,7 @@ static bool8 HasBadOdds(u32 battler) if (aiMove != MOVE_NONE) { // Check if mon has an "important" status move - if (aiMoveEffect == EFFECT_REFLECT || aiMoveEffect == EFFECT_LIGHT_SCREEN + if (aiMoveEffect == EFFECT_REFLECT || aiMoveEffect == EFFECT_LIGHT_SCREEN || aiMoveEffect == EFFECT_SPIKES || aiMoveEffect == EFFECT_TOXIC_SPIKES || aiMoveEffect == EFFECT_STEALTH_ROCK || aiMoveEffect == EFFECT_STICKY_WEB || aiMoveEffect == EFFECT_LEECH_SEED || aiMoveEffect == EFFECT_EXPLOSION || aiMoveEffect == EFFECT_SLEEP || aiMoveEffect == EFFECT_YAWN || aiMoveEffect == EFFECT_TOXIC || aiMoveEffect == EFFECT_WILL_O_WISP || aiMoveEffect == EFFECT_PARALYZE @@ -168,23 +168,23 @@ static bool8 HasBadOdds(u32 battler) } // If we don't have any other viable options, don't switch out - if (AI_DATA->mostSuitableMonId == PARTY_SIZE) + if (AI_DATA->mostSuitableMonId[battler] == PARTY_SIZE) return FALSE; // Start assessing whether or not mon has bad odds // Jump straight to swtiching out in cases where mon gets OHKO'd if (((getsOneShot && gBattleMons[opposingBattler].speed > gBattleMons[battler].speed) // If the player OHKOs and outspeeds OR OHKOs, doesn't outspeed but isn't 2HKO'd - || (getsOneShot && gBattleMons[opposingBattler].speed <= gBattleMons[battler].speed && maxDamageDealt < gBattleMons[opposingBattler].hp / 2)) + || (getsOneShot && gBattleMons[opposingBattler].speed <= gBattleMons[battler].speed && maxDamageDealt < gBattleMons[opposingBattler].hp / 2)) && (gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2 // And the current mon has at least 1/2 their HP, or 1/4 HP and Regenerator - || (aiAbility == ABILITY_REGENERATOR - && gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 4))) + || (aiAbility == ABILITY_REGENERATOR + && gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 4))) { // 50% chance to stay in regardless - if (Random() % 2 == 0) + if (Random() % 2 == 0) return FALSE; // Switch mon out - *(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE; + *(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE; BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); return TRUE; } @@ -194,19 +194,19 @@ static bool8 HasBadOdds(u32 battler) { if (!hasSuperEffectiveMove // If the AI doesn't have a super effective move && (gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2 // And the current mon has at least 1/2 their HP, or 1/4 HP and Regenerator - || (aiAbility == ABILITY_REGENERATOR - && gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 4))) + || (aiAbility == ABILITY_REGENERATOR + && gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 4))) { // Then check if they have an important status move, which is worth using even in a bad matchup if(hasStatusMove) return FALSE; // 50% chance to stay in regardless - if (Random() % 2 == 0) + if (Random() % 2 == 0) return FALSE; // Switch mon out - *(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE; + *(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE; BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); return TRUE; } @@ -593,12 +593,12 @@ static bool8 ShouldSwitchIfAbilityBenefit(u32 battler) moduloChance = 4; //25% //Attempt to cure bad ailment if (gBattleMons[battler].status1 & (STATUS1_SLEEP | STATUS1_FREEZE | STATUS1_TOXIC_POISON) - && AI_DATA->mostSuitableMonId != PARTY_SIZE) + && AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE) break; //Attempt to cure lesser ailment if ((gBattleMons[battler].status1 & STATUS1_ANY) && (gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2) - && AI_DATA->mostSuitableMonId != PARTY_SIZE + && AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && Random() % (moduloChance*chanceReducer) == 0) break; @@ -610,7 +610,7 @@ static bool8 ShouldSwitchIfAbilityBenefit(u32 battler) if (gBattleMons[battler].status1 & STATUS1_ANY) return FALSE; if ((gBattleMons[battler].hp <= ((gBattleMons[battler].maxHP * 2) / 3)) - && AI_DATA->mostSuitableMonId != PARTY_SIZE + && AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && Random() % (moduloChance*chanceReducer) == 0) break; @@ -785,7 +785,7 @@ static bool32 CanMonSurviveHazardSwitchin(u32 battler) if (ability == ABILITY_REGENERATOR) battlerHp = (battlerHp * 133) / 100; // Account for Regenerator healing - + hazardDamage = GetSwitchinHazardsDamage(battler, &gBattleMons[battler]); // Battler will faint to hazards, check to see if another mon can clear them @@ -840,13 +840,13 @@ static bool32 CanMonSurviveHazardSwitchin(u32 battler) } static bool32 ShouldSwitchIfEncored(u32 battler) -{ +{ // Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer if (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING)) return FALSE; // If not Encored or if no good switchin, don't switch - if (gDisableStructs[battler].encoredMove == MOVE_NONE || AI_DATA->mostSuitableMonId == PARTY_SIZE) + if (gDisableStructs[battler].encoredMove == MOVE_NONE || AI_DATA->mostSuitableMonId[battler] == PARTY_SIZE) return FALSE; // Otherwise 50% chance to switch out @@ -879,7 +879,7 @@ static bool8 AreAttackingStatsLowered(u32 battler) // 50% chance if attack at -2 and have a good candidate mon else if (attackingStage == DEFAULT_STAT_STAGE - 2) { - if (AI_DATA->mostSuitableMonId != PARTY_SIZE && (Random() & 1)) + if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (Random() & 1)) { *(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE; BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); @@ -904,7 +904,7 @@ static bool8 AreAttackingStatsLowered(u32 battler) // 50% chance if attack at -2 and have a good candidate mon else if (spAttackingStage == DEFAULT_STAT_STAGE - 2) { - if (AI_DATA->mostSuitableMonId != PARTY_SIZE && (Random() & 1)) + if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (Random() & 1)) { *(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE; BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); @@ -1053,7 +1053,7 @@ void AI_TrySwitchOrUseItem(u32 battler) { if (*(gBattleStruct->AI_monToSwitchIntoId + battler) == PARTY_SIZE) { - s32 monToSwitchId = AI_DATA->mostSuitableMonId; + s32 monToSwitchId = AI_DATA->mostSuitableMonId[battler]; if (monToSwitchId == PARTY_SIZE) { if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) @@ -1367,7 +1367,7 @@ static s32 GetSwitchinWeatherImpact(void) // Gets one turn of recurring healing static u32 GetSwitchinRecurringHealing(void) -{ +{ u32 recurringHealing = 0, maxHP = AI_DATA->switchinCandidate.battleMon.maxHP, ability = AI_DATA->switchinCandidate.battleMon.ability; u16 item = AI_DATA->switchinCandidate.battleMon.item; @@ -1439,9 +1439,9 @@ static u32 GetSwitchinStatusDamage(u32 battler) u32 statusDamage = 0; // Status condition damage - if ((status != 0) && AI_DATA->switchinCandidate.battleMon.ability != ABILITY_MAGIC_GUARD) + if ((status != 0) && AI_DATA->switchinCandidate.battleMon.ability != ABILITY_MAGIC_GUARD) { - if (status & STATUS1_BURN) + if (status & STATUS1_BURN) { #if B_BURN_DAMAGE >= GEN_7 statusDamage = maxHP / 16; @@ -1484,7 +1484,7 @@ static u32 GetSwitchinStatusDamage(u32 battler) if (tSpikesLayers != 0 && (defType1 != TYPE_POISON && defType2 != TYPE_POISON && ability != ABILITY_IMMUNITY && ability != ABILITY_POISON_HEAL && status == 0 - && !(heldItemEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS + && !(heldItemEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS && (((gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) || ability == ABILITY_KLUTZ))) && heldItemEffect != HOLD_EFFECT_CURE_PSN && heldItemEffect != HOLD_EFFECT_CURE_STATUS && IsMonGrounded(heldItemEffect, ability, defType1, defType2))) @@ -1559,7 +1559,7 @@ static u32 GetSwitchinHitsToKO(s32 damageTaken, u32 battler) singleUseItemHeal = 1; } } - else if (currentHP < maxHP / CONFUSE_BERRY_HP_FRACTION + else if (currentHP < maxHP / CONFUSE_BERRY_HP_FRACTION && opposingAbility != ABILITY_UNNERVE && (item == ITEM_AGUAV_BERRY || item == ITEM_FIGY_BERRY || item == ITEM_IAPAPA_BERRY || item == ITEM_MAGO_BERRY || item == ITEM_WIKI_BERRY)) { @@ -1608,7 +1608,7 @@ static u16 GetSwitchinTypeMatchup(u32 opposingBattler, struct BattlePokemon batt // Check type matchup u16 typeEffectiveness = UQ_4_12(1.0); - u8 atkType1 = gSpeciesInfo[gBattleMons[opposingBattler].species].types[0], atkType2 = gSpeciesInfo[gBattleMons[opposingBattler].species].types[1], + u8 atkType1 = gSpeciesInfo[gBattleMons[opposingBattler].species].types[0], atkType2 = gSpeciesInfo[gBattleMons[opposingBattler].species].types[1], defType1 = battleMon.type1, defType2 = battleMon.type2; // Multiply type effectiveness by a factor depending on type matchup @@ -1669,7 +1669,7 @@ static s32 GetMaxDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposingBattle // the Type Matchup code will prioritize switching into a mon with the best type matchup and also a super effective move, or just best type matchup if no super effective move is found // the Most Defensive code will prioritize switching into the mon that takes the most hits to KO, with a minimum of 4 hits required to be considered a valid option // the Baton Pass code will prioritize switching into a mon with Baton Pass if it can get in, boost, and BP out without being KO'd, and randomizes between multiple valid options -// the Revenge Killer code will prioritize, in order, OHKO and outspeeds / OHKO, slower but not 2HKO'd / 2HKO, outspeeds and not OHKO'd / 2HKO, slower but not 3HKO'd +// the Revenge Killer code will prioritize, in order, OHKO and outspeeds / OHKO, slower but not 2HKO'd / 2HKO, outspeeds and not OHKO'd / 2HKO, slower but not 3HKO'd // the Most Damage code will prioritize switching into whatever mon deals the most damage, which is generally not as good as having a good Type Matchup // Everything runs in the same loop to minimize computation time. This makes it harder to read, but hopefully the comments can guide you! @@ -1712,7 +1712,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, InitializeSwitchinCandidate(&party[i]); // While not really invalid per say, not really wise to switch into this mon - if (AI_DATA->switchinCandidate.battleMon.ability == ABILITY_TRUANT && IsTruantMonVulnerable(battler, opposingBattler)) + if (AI_DATA->switchinCandidate.battleMon.ability == ABILITY_TRUANT && IsTruantMonVulnerable(battler, opposingBattler)) continue; // Get max number of hits for player to KO AI mon @@ -1748,7 +1748,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, // Only do damage calc if switching after KO, don't need it otherwise and saves ~0.02s per turn if (isSwitchAfterKO && aiMove != MOVE_NONE && gBattleMoves[aiMove].power != 0) damageDealt = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, TRUE); - + // Check for Baton Pass; hitsToKO requirements mean mon can boost and BP without dying whether it's slower or not if (aiMove == MOVE_BATON_PASS && ((hitsToKO > hitsToKOThreshold + 1 && AI_DATA->switchinCandidate.battleMon.speed < playerMonSpeed) || (hitsToKO > hitsToKOThreshold && AI_DATA->switchinCandidate.battleMon.speed > playerMonSpeed))) bits |= gBitTable[i]; @@ -1857,7 +1857,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, else if (typeMatchupId != PARTY_SIZE) return typeMatchupId; - + else if (batonPassId != PARTY_SIZE) return batonPassId; @@ -1932,7 +1932,7 @@ u8 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd) if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_MON_CHOICES) { bestMonId = GetBestMonIntegrated(party, firstId, lastId, battler, opposingBattler, battlerIn1, battlerIn2, switchAfterMonKOd); - return bestMonId; + return bestMonId; } // This all handled by the GetBestMonIntegrated function if the AI_FLAG_SMART_MON_CHOICES flag is set @@ -2138,4 +2138,4 @@ static bool32 AI_OpponentCanFaintAiWithMod(u32 battler, u32 healAmount) } } return FALSE; -} \ No newline at end of file +} diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 14016684dd..f675602fb9 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -972,7 +972,7 @@ static bool32 AI_IsMoveEffectInMinus(u32 battlerAtk, u32 battlerDef, u32 move, s case EFFECT_OVERHEAT: case EFFECT_MAKE_IT_RAIN: case EFFECT_MIND_BLOWN: - case EFFECT_STEEL_BEAM: + case EFFECT_MAX_HP_50_RECOIL: return TRUE; case EFFECT_RECOIL_25: case EFFECT_RECOIL_IF_MISS: @@ -2750,17 +2750,11 @@ bool32 AI_CanPutToSleep(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 move return TRUE; } -static bool32 AI_CanPoisonType(u32 battlerAttacker, u32 battlerTarget, u32 move) -{ - return ((AI_DATA->abilities[battlerAttacker] == ABILITY_CORROSION && gBattleMoves[move].split == SPLIT_STATUS) - || !(IS_BATTLER_OF_TYPE(battlerTarget, TYPE_POISON) || IS_BATTLER_OF_TYPE(battlerTarget, TYPE_STEEL))); -} - static bool32 AI_CanBePoisoned(u32 battlerAtk, u32 battlerDef, u32 move) { u32 ability = AI_DATA->abilities[battlerDef]; - if (!(AI_CanPoisonType(battlerAtk, battlerDef, move)) + if (!(CanPoisonType(battlerAtk, battlerDef)) || gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_SAFEGUARD || gBattleMons[battlerDef].status1 & STATUS1_ANY || ability == ABILITY_IMMUNITY diff --git a/src/battle_controller_player_partner.c b/src/battle_controller_player_partner.c index 5155db49ec..ed9f29c667 100644 --- a/src/battle_controller_player_partner.c +++ b/src/battle_controller_player_partner.c @@ -440,7 +440,7 @@ static void PlayerPartnerHandleIntroTrainerBallThrow(u32 battler) const u32 *trainerPal; if (gPartnerTrainerId == TRAINER_STEVEN_PARTNER) - trainerPal = gTrainerBackPicPaletteTable[TRAINER_STEVEN_PARTNER].data; + trainerPal = gTrainerBackPicPaletteTable[TRAINER_BACK_PIC_STEVEN].data; else if (gPartnerTrainerId >= TRAINER_CUSTOM_PARTNER) // Custom multi battle. trainerPal = gTrainerBackPicPaletteTable[gPartnerSpriteId].data; else if (IsAiVsAiBattle()) diff --git a/src/battle_main.c b/src/battle_main.c index 75bd5c3b04..42642c085f 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -4040,7 +4040,7 @@ static void HandleTurnActionSelectionState(void) if ((gBattleTypeFlags & BATTLE_TYPE_HAS_AI || IsWildMonSmart()) && (BattlerHasAi(battler) && !(gBattleTypeFlags & BATTLE_TYPE_PALACE))) { - AI_DATA->mostSuitableMonId = GetMostSuitableMonToSwitchInto(battler, FALSE); + AI_DATA->mostSuitableMonId[battler] = GetMostSuitableMonToSwitchInto(battler, FALSE); gBattleStruct->aiMoveOrAction[battler] = ComputeBattleAiScores(battler); } // fallthrough diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index e5c85ed793..7c6eefe299 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -5730,7 +5730,7 @@ static void Cmd_moveend(void) gEffectBattler = gBattlerTarget; BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_MagicianActivates; - gSpecialStatuses[gBattlerAttacker].magicianStolen = TRUE; + gSpecialStatuses[gBattlerAttacker].preventLifeOrbDamage = TRUE; effect = TRUE; } gBattleScripting.moveendState++; @@ -5917,6 +5917,7 @@ static void Cmd_moveend(void) gBattlescriptCurrInstr = BattleScript_MoveEnd; // Prevent user switch-in selection BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_RedCardActivates; + gSpecialStatuses[gBattlerAttacker].preventLifeOrbDamage = TRUE; effect = TRUE; break; // Only fastest red card activates } @@ -5943,6 +5944,7 @@ static void Cmd_moveend(void) gLastUsedItem = gBattleMons[battler].item; BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_EjectPackActivates; + gSpecialStatuses[gBattlerAttacker].preventLifeOrbDamage = TRUE; effect = TRUE; break; // Only fastest eject pack activates } @@ -6095,7 +6097,7 @@ static void Cmd_moveend(void) gStatuses3[gBattlerAttacker] &= ~STATUS3_ME_FIRST; gSpecialStatuses[gBattlerAttacker].gemBoost = FALSE; gSpecialStatuses[gBattlerAttacker].damagedMons = 0; - gSpecialStatuses[gBattlerAttacker].magicianStolen = 0; + gSpecialStatuses[gBattlerAttacker].preventLifeOrbDamage = 0; gSpecialStatuses[gBattlerTarget].berryReduced = FALSE; gBattleScripting.moveEffect = 0; // clear attacker z move data @@ -8132,8 +8134,8 @@ static bool32 HasAttackerFaintedTarget(void) bool32 CanPoisonType(u8 battlerAttacker, u8 battlerTarget) { - return ((GetBattlerAbility(battlerAttacker) == ABILITY_CORROSION && gBattleMoves[gCurrentMove].split == SPLIT_STATUS) - || !(IS_BATTLER_OF_TYPE(battlerTarget, TYPE_POISON) || IS_BATTLER_OF_TYPE(battlerTarget, TYPE_STEEL))); + return GetBattlerAbility(battlerAttacker) == ABILITY_CORROSION + || (!IS_BATTLER_OF_TYPE(battlerTarget, TYPE_STEEL) && !IS_BATTLER_OF_TYPE(battlerTarget, TYPE_POISON)); } bool32 CanParalyzeType(u8 battlerAttacker, u8 battlerTarget) diff --git a/src/battle_tv.c b/src/battle_tv.c index af485df70d..f22b9f96e5 100644 --- a/src/battle_tv.c +++ b/src/battle_tv.c @@ -467,7 +467,7 @@ static const u16 sPoints_MoveEffect[NUM_BATTLE_MOVE_EFFECTS] = [EFFECT_RISING_VOLTAGE] = 0, // TODO: Assign points [EFFECT_BEAK_BLAST] = 0, // TODO: Assign points [EFFECT_COURT_CHANGE] = 0, // TODO: Assign points - [EFFECT_STEEL_BEAM] = 0, // TODO: Assign points + [EFFECT_MAX_HP_50_RECOIL] = 0, // TODO: Assign points [EFFECT_EXTREME_EVOBOOST] = 0, // TODO: Assign points [EFFECT_HIT_SET_REMOVE_TERRAIN] = 0, // TODO: Assign points [EFFECT_DARK_VOID] = 0, // TODO: Assign points diff --git a/src/battle_util.c b/src/battle_util.c index df30826630..f424f97f53 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4211,11 +4211,14 @@ static uq4_12_t GetSupremeOverlordModifier(u32 battler) return modifier; } -static bool32 HadMoreThanHalfHpNowHasLess(u32 battler) +static inline bool32 HadMoreThanHalfHpNowHasLess(u32 battler) { + u32 cutoff = gBattleMons[battler].maxHP / 2; + if (gBattleMons[battler].maxHP % 2 == 1) + cutoff++; // Had more than half of hp before, now has less - return (gBattleStruct->hpBefore[battler] >= gBattleMons[battler].maxHP / 2 - && gBattleMons[battler].hp < gBattleMons[battler].maxHP / 2); + return (gBattleStruct->hpBefore[battler] >= cutoff + && gBattleMons[battler].hp < cutoff); } u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 moveArg) @@ -5271,8 +5274,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && TARGET_TURN_DAMAGED && IsBattlerAlive(battler) // Had more than half of hp before, now has less - && gBattleStruct->hpBefore[battler] > gBattleMons[battler].maxHP / 2 - && gBattleMons[battler].hp < gBattleMons[battler].maxHP / 2 + && HadMoreThanHalfHpNowHasLess(battler) && (gMultiHitCounter == 0 || gMultiHitCounter == 1) && !(TestSheerForceFlag(gBattlerAttacker, gCurrentMove)) && (CanBattlerSwitch(battler) || !(gBattleTypeFlags & BATTLE_TYPE_TRAINER)) @@ -6791,6 +6793,10 @@ static u8 ItemHealHp(u32 battler, u32 itemId, bool32 end2, bool32 percentHeal) BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_ItemHealHP_RemoveItemRet; } + if (gBattleResources->flags->flags[battler] & RESOURCE_FLAG_EMERGENCY_EXIT + && GetNonDynamaxHP(battler) >= GetNonDynamaxMaxHP(battler) / 2) + gBattleResources->flags->flags[battler] &= ~RESOURCE_FLAG_EMERGENCY_EXIT; + return ITEM_HP_CHANGE; } return 0; @@ -7775,7 +7781,7 @@ u8 ItemBattleEffects(u8 caseID, u32 battler, bool32 moveTurn) if (IsBattlerAlive(gBattlerAttacker) && !(TestSheerForceFlag(gBattlerAttacker, gCurrentMove)) && GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD - && !gSpecialStatuses[gBattlerAttacker].magicianStolen + && !gSpecialStatuses[gBattlerAttacker].preventLifeOrbDamage && gSpecialStatuses[gBattlerAttacker].damagedMons) { gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 10; diff --git a/src/data/battle_moves.h b/src/data/battle_moves.h index 005415a956..1a2347f557 100644 --- a/src/data/battle_moves.h +++ b/src/data/battle_moves.h @@ -12606,7 +12606,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = [MOVE_STEEL_BEAM] = { - .effect = EFFECT_STEEL_BEAM, + .effect = EFFECT_MAX_HP_50_RECOIL, .power = 140, .type = TYPE_STEEL, .accuracy = 95, @@ -13259,7 +13259,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_DYNAMAX] = #else .power = 120, #endif - .effect = EFFECT_STEEL_BEAM, + .effect = EFFECT_MAX_HP_50_RECOIL, .type = TYPE_GRASS, .accuracy = 95, .pp = 5, diff --git a/src/data/pokemon/species_info/gen_9.h b/src/data/pokemon/species_info/gen_9.h index a399b8549b..7a45fc66e4 100644 --- a/src/data/pokemon/species_info/gen_9.h +++ b/src/data/pokemon/species_info/gen_9.h @@ -537,7 +537,6 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = ICON(OinkologneMale, 1), //.footprint = gMonFootprint_Oinkologne, .levelUpLearnset = sOinkologneMaleLevelUpLearnset, - }, [SPECIES_OINKOLOGNE_FEMALE] = @@ -859,7 +858,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = ICON(Pawmo, 3), //.footprint = gMonFootprint_Pawmo, LEARNSETS(Pawmo), - .evolutions = EVOLUTION({EVO_FRIENDSHIP, 0, SPECIES_PAWMOT}), + .evolutions = EVOLUTION({EVO_NONE, 0, SPECIES_PAWMOT}), }, [SPECIES_PAWMOT] = diff --git a/src/data/pokemon_graphics/front_pic_anims.h b/src/data/pokemon_graphics/front_pic_anims.h index 75cb52732c..5529fe77ac 100644 --- a/src/data/pokemon_graphics/front_pic_anims.h +++ b/src/data/pokemon_graphics/front_pic_anims.h @@ -279,7 +279,6 @@ static const union AnimCmd sAnim_Raticate_1[] = #if P_ALOLAN_FORMS PLACEHOLDER_ANIM_SINGLE_FRAME(RattataAlolan); - PLACEHOLDER_ANIM_SINGLE_FRAME(RaticateAlolan); #endif //P_ALOLAN_FORMS #endif //P_FAMILY_RATTATA @@ -353,12 +352,15 @@ static const union AnimCmd sAnim_Pikachu_1[] = ANIMCMD_END, }; +#if P_COSPLAY_PIKACHU_FORMS PLACEHOLDER_ANIM_SINGLE_FRAME(PikachuCosplay); PLACEHOLDER_ANIM_SINGLE_FRAME(PikachuRockStar); PLACEHOLDER_ANIM_SINGLE_FRAME(PikachuBelle); PLACEHOLDER_ANIM_SINGLE_FRAME(PikachuPopStar); PLACEHOLDER_ANIM_SINGLE_FRAME(PikachuPhD); PLACEHOLDER_ANIM_SINGLE_FRAME(PikachuLibre); +#endif //P_COSPLAY_PIKACHU_FORMS +#if P_CAP_PIKACHU_FORMS PLACEHOLDER_ANIM_SINGLE_FRAME(PikachuOriginalCap); PLACEHOLDER_ANIM_SINGLE_FRAME(PikachuHoennCap); PLACEHOLDER_ANIM_SINGLE_FRAME(PikachuSinnohCap); @@ -367,6 +369,7 @@ PLACEHOLDER_ANIM_SINGLE_FRAME(PikachuKalosCap); PLACEHOLDER_ANIM_SINGLE_FRAME(PikachuAlolaCap); PLACEHOLDER_ANIM_SINGLE_FRAME(PikachuPartnerCap); PLACEHOLDER_ANIM_SINGLE_FRAME(PikachuWorldCap); +#endif //P_CAP_PIKACHU_FORMS #if P_GIGANTAMAX_FORMS PLACEHOLDER_ANIM_SINGLE_FRAME(PikachuGigantamax); @@ -4685,7 +4688,9 @@ static const union AnimCmd sAnim_Milotic_1[] = ANIMCMD_FRAME(0, 15), ANIMCMD_END, }; +#endif //P_FAMILY_FEEBAS +#if P_FAMILY_CASTFORM static const union AnimCmd sAnim_CastformNormal_1[] = { ANIMCMD_FRAME(0, 12), @@ -4727,7 +4732,7 @@ static const union AnimCmd sAnim_CastformSnowy_1[] = ANIMCMD_FRAME(0, 12), ANIMCMD_END, }; -#endif //P_FAMILY_FEEBAS +#endif //P_FAMILY_CASTFORM #if P_FAMILY_KECLEON static const union AnimCmd sAnim_Kecleon_1[] = @@ -5773,13 +5778,16 @@ static const union AnimCmd sAnim_Chatot_1[] = ANIMCMD_FRAME(0, 5), ANIMCMD_END, }; +#endif //P_FAMILY_CHATOT + +#if P_FAMILY_SPIRITOMB static const union AnimCmd sAnim_Spiritomb_1[] = { ANIMCMD_FRAME(1, 20), ANIMCMD_FRAME(0, 10), ANIMCMD_END, }; -#endif //P_FAMILY_CHATOT +#endif //P_FAMILY_SPIRITOMB #if P_FAMILY_GIBLE static const union AnimCmd sAnim_Gible_1[] = @@ -7844,7 +7852,9 @@ static const union AnimCmd sAnim_Virizion_1[] = ANIMCMD_FRAME(0, 20), ANIMCMD_END, }; +#endif //P_FAMILY_VIRIZION +#if P_FAMILY_TORNADUS static const union AnimCmd sAnim_TornadusIncarnate_1[] = { ANIMCMD_FRAME(1, 2), @@ -7881,7 +7891,9 @@ static const union AnimCmd sAnim_TornadusTherian_1[] = ANIMCMD_FRAME(0, 5), ANIMCMD_END, }; +#endif //P_FAMILY_TORNADUS +#if P_FAMILY_THUNDURUS static const union AnimCmd sAnim_ThundurusIncarnate_1[] = { ANIMCMD_FRAME(1, 2), @@ -7929,7 +7941,7 @@ static const union AnimCmd sAnim_ThundurusTherian_1[] = ANIMCMD_FRAME(0, 5), ANIMCMD_END, }; -#endif //P_FAMILY_VIRIZION +#endif //P_FAMILY_THUNDURUS #if P_FAMILY_RESHIRAM static const union AnimCmd sAnim_Reshiram_1[] = @@ -7947,7 +7959,9 @@ static const union AnimCmd sAnim_Zekrom_1[] = ANIMCMD_FRAME(0, 5), ANIMCMD_END, }; +#endif //P_FAMILY_ZEKROM +#if P_FAMILY_LANDORUS static const union AnimCmd sAnim_LandorusIncarnate_1[] = { ANIMCMD_FRAME(1, 2), @@ -7984,7 +7998,7 @@ static const union AnimCmd sAnim_LandorusTherian_1[] = ANIMCMD_FRAME(0, 15), ANIMCMD_END, }; -#endif //P_FAMILY_ZEKROM +#endif //P_FAMILY_LANDORUS #if P_FAMILY_KYUREM static const union AnimCmd sAnim_Kyurem_1[] = @@ -8013,7 +8027,9 @@ static const union AnimCmd sAnim_KyuremBlack_1[] = ANIMCMD_END, }; #endif //P_FUSION_FORMS +#endif //P_FAMILY_KYUREM +#if P_FAMILY_KELDEO static const union AnimCmd sAnim_KeldeoOrdinary_1[] = { ANIMCMD_FRAME(1, 32), @@ -8028,7 +8044,9 @@ static const union AnimCmd sAnim_KeldeoResolute_1[] = ANIMCMD_FRAME(0, 10), ANIMCMD_END, }; +#endif //P_FAMILY_KELDEO +#if P_FAMILY_MELOETTA static const union AnimCmd sAnim_MeloettaAria_1[] = { ANIMCMD_FRAME(0, 15), @@ -8051,7 +8069,7 @@ static const union AnimCmd sAnim_MeloettaPirouette_1[] = ANIMCMD_FRAME(0, 15), ANIMCMD_END, }; -#endif //P_FAMILY_KYUREM +#endif //P_FAMILY_MELOETTA #if P_FAMILY_GENESECT static const union AnimCmd sAnim_Genesect_1[] = @@ -9067,10 +9085,12 @@ static const union AnimCmd sAnim_LycanrocDusk_1[] = ANIMCMD_FRAME(0, 5), ANIMCMD_END, }; +#endif //P_FAMILY_ROCKRUFF +#if P_FAMILY_WISHIWASHI PLACEHOLDER_ANIM_SINGLE_FRAME(WishiwashiSolo); PLACEHOLDER_ANIM_SINGLE_FRAME(WishiwashiSchool); -#endif //P_FAMILY_ROCKRUFF +#endif //P_FAMILY_WISHIWASHI #if P_FAMILY_MAREANIE PLACEHOLDER_ANIM_SINGLE_FRAME(Mareanie); @@ -10189,6 +10209,8 @@ SINGLE_ANIMATION(PikachuBelle); SINGLE_ANIMATION(PikachuPopStar); SINGLE_ANIMATION(PikachuPhD); SINGLE_ANIMATION(PikachuLibre); +#endif //P_COSPLAY_PIKACHU_FORMS +#if P_CAP_PIKACHU_FORMS SINGLE_ANIMATION(PikachuOriginalCap); SINGLE_ANIMATION(PikachuHoennCap); SINGLE_ANIMATION(PikachuSinnohCap); diff --git a/test/battle/ability/corrosion.c b/test/battle/ability/corrosion.c new file mode 100644 index 0000000000..2c53a5132b --- /dev/null +++ b/test/battle/ability/corrosion.c @@ -0,0 +1,128 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Corrosion can poison or badly poison a Pokemon regardless of its typing") +{ + u16 species; + + PARAMETRIZE { species = SPECIES_ODDISH; } + PARAMETRIZE { species = SPECIES_BELDUM; } + + GIVEN { + ASSUME(gBattleMoves[MOVE_TWINEEDLE].effect == EFFECT_POISON_HIT); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); } + OPPONENT(species); + } WHEN { + TURN { MOVE(player, MOVE_TWINEEDLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TWINEEDLE, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Corrosion can poison or badly poison a Steel type with a status poison effect") +{ + u16 move; + + PARAMETRIZE { move = MOVE_POISON_POWDER; } + PARAMETRIZE { move = MOVE_TOXIC; } + + GIVEN { + ASSUME(gBattleMoves[MOVE_POISON_POWDER].effect == EFFECT_POISON); + ASSUME(gBattleMoves[MOVE_TOXIC].effect == EFFECT_TOXIC); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); } + OPPONENT(SPECIES_BELDUM); + } WHEN { + TURN { MOVE(player, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + if (move == MOVE_POISON_POWDER) + STATUS_ICON(opponent, poison: TRUE); + else + STATUS_ICON(opponent, badPoison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Corrosion does not effect poison type damaging moves if the target is immune to it") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_SLUDGE_BOMB].effect == EFFECT_POISON_HIT); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); } + OPPONENT(SPECIES_BELDUM); + } WHEN { + TURN { MOVE(player, MOVE_SLUDGE_BOMB); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SLUDGE_BOMB, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, poison: TRUE); + } + } +} + +SINGLE_BATTLE_TEST("Corrosion can poison Poison- and Steel-type targets if it uses Fling while holding a Toxic Orb or a Poison Barb") +{ + u16 heldItem; + + PARAMETRIZE { heldItem = ITEM_POISON_BARB; } + PARAMETRIZE { heldItem = ITEM_TOXIC_ORB; } + + GIVEN { + ASSUME(gBattleMoves[MOVE_FLING].effect == EFFECT_FLING); + ASSUME(gItems[ITEM_POISON_BARB].holdEffect == HOLD_EFFECT_POISON_POWER); + ASSUME(gItems[ITEM_TOXIC_ORB].holdEffect == HOLD_EFFECT_TOXIC_ORB); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); Item(heldItem); } + OPPONENT(SPECIES_ODDISH); + } WHEN { + TURN { MOVE(player, MOVE_FLING); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLING, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + if (heldItem == ITEM_POISON_BARB) + STATUS_ICON(opponent, poison: TRUE); + else + STATUS_ICON(opponent, badPoison: TRUE); + } +} + +SINGLE_BATTLE_TEST("If a Poison- or Steel-type Pokémon with Corrosion holds a Toxic Orb, it will badly poison itself") +{ + GIVEN { + ASSUME(gItems[ITEM_TOXIC_ORB].holdEffect == HOLD_EFFECT_TOXIC_ORB); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); Item(ITEM_TOXIC_ORB); } + OPPONENT(SPECIES_ODDISH); + } WHEN { + TURN { } + } SCENE { + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + STATUS_ICON(player, badPoison: TRUE); + } +} + +SINGLE_BATTLE_TEST("If a Poison- or Steel-type Pokémon with Corrosion poisons a target with Synchronize, Synchronize will not poison Poison- or Steel-type Pokémon") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_TOXIC].effect == EFFECT_TOXIC); + PLAYER(SPECIES_SALANDIT) { Ability(ABILITY_CORROSION); } + OPPONENT(SPECIES_ABRA) { Ability(ABILITY_SYNCHRONIZE); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, badPoison: TRUE); + NONE_OF { + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, player); + STATUS_ICON(player, badPoison: TRUE); + } + } +} + +TO_DO_BATTLE_TEST("Corrosion cannot bypass moves or Abilities that prevent poisoning, such as Safeguard or Immunity"); +TO_DO_BATTLE_TEST("If the Pokémon with this Ability uses Magic Coat to reflect a status move that inflicts poison, the reflected move will be able to poison Poison- or Steel-type Pokémon."); +TO_DO_BATTLE_TEST("Moves used by a Pokémon with Corrosion that are reflected by Magic Coat or Magic Bounce do not retain the ability to poison Poison- or Steel-type Pokémon.") diff --git a/test/battle/ability/emergency_exit.c b/test/battle/ability/emergency_exit.c new file mode 100644 index 0000000000..68724450e1 --- /dev/null +++ b/test/battle/ability/emergency_exit.c @@ -0,0 +1,49 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Emergency Exit switches out when taking 50% max-hp damage") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUPER_FANG); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player); + HP_BAR(opponent); + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + +SINGLE_BATTLE_TEST("Emergency Exit does not switch out when going below 50% max-HP but healed via held item back above the threshold") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); Item(ITEM_SITRUS_BERRY); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUPER_FANG); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + NOT ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} + +SINGLE_BATTLE_TEST("Emergency Exit switches out when going below 50% max-HP but healing via held item is not enough to go back above the threshold") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(133); Item(ITEM_ORAN_BERRY); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUPER_FANG); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} diff --git a/test/battle/ai.c b/test/battle/ai.c index 5fc4a5d21d..83e9226709 100644 --- a/test/battle/ai.c +++ b/test/battle/ai.c @@ -660,3 +660,31 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will not switch out if Pokemo } } } + +AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same pokemon for 2 spots in a double battle") +{ + u32 flags; + + PARAMETRIZE {flags = AI_FLAG_SMART_SWITCHING; } + PARAMETRIZE {flags = 0; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | flags); + PLAYER(SPECIES_RATTATA); + PLAYER(SPECIES_RATTATA); + // No moves to damage player. + OPPONENT(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL); } + OPPONENT(SPECIES_HAUNTER) { Moves(MOVE_SHADOW_BALL); } + OPPONENT(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL); } + OPPONENT(SPECIES_RATICATE) { Moves(MOVE_HEADBUTT); } + } WHEN { + TURN { EXPECT_SWITCH(opponentLeft, 3); }; + } SCENE { + MESSAGE("{PKMN} TRAINER LEAF withdrew Gengar!"); + MESSAGE("{PKMN} TRAINER LEAF sent out Raticate!"); + NONE_OF { + MESSAGE("{PKMN} TRAINER LEAF withdrew Haunter!"); + MESSAGE("{PKMN} TRAINER LEAF sent out Raticate!"); + } + } +} diff --git a/test/battle/hold_effect/eject_pack.c b/test/battle/hold_effect/eject_pack.c new file mode 100644 index 0000000000..4f58a1477e --- /dev/null +++ b/test/battle/hold_effect/eject_pack.c @@ -0,0 +1,26 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gItems[ITEM_EJECT_PACK].holdEffect == HOLD_EFFECT_EJECT_PACK); +} + +SINGLE_BATTLE_TEST("Eject Pack does not cause the new pokemon to lose hp due to it's held Life Orb") +{ + GIVEN { + ASSUME(gItems[ITEM_LIFE_ORB].holdEffect == HOLD_EFFECT_LIFE_ORB); + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT) { Item(ITEM_LIFE_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_OVERHEAT); SEND_OUT(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_OVERHEAT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet is switched out with the Eject Pack!"); + MESSAGE("Go! Wynaut!"); + NOT MESSAGE("Wynaut was hurt by its Life Orb!"); + } +} diff --git a/test/battle/hold_effect/red_card.c b/test/battle/hold_effect/red_card.c index 0f80dd176c..79db49fabd 100644 --- a/test/battle/hold_effect/red_card.c +++ b/test/battle/hold_effect/red_card.c @@ -430,4 +430,21 @@ SINGLE_BATTLE_TEST("Red Card is consumed after dragged out replacement has its S } } +SINGLE_BATTLE_TEST("Red Card does not cause the dragged out mon to lose hp due to it's held Life Orb") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { Item(ITEM_LIFE_ORB); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("Foe Wobbuffet held up its Red Card against Wobbuffet!"); + MESSAGE("Wynaut was dragged out!"); + NOT MESSAGE("Wynaut was hurt by its Life Orb!"); + } +} + // SINGLE_BATTLE_TEST("Red Card activates but fails if the attacker has Dynamaxed") diff --git a/test/battle/move_effect/max_hp_50_recoil.c b/test/battle/move_effect/max_hp_50_recoil.c new file mode 100644 index 0000000000..b921e5a85f --- /dev/null +++ b/test/battle/move_effect/max_hp_50_recoil.c @@ -0,0 +1,39 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_STEEL_BEAM].effect == EFFECT_MAX_HP_50_RECOIL); +} + +SINGLE_BATTLE_TEST("Steel Beam causes the user to take damage equal to half of its maximum HP") +{ + s16 recoilDamage; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STEEL_BEAM); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEEL_BEAM, player); + HP_BAR(opponent); + HP_BAR(player, captureDamage: &recoilDamage); + } THEN { + EXPECT_EQ(player->maxHP / 2, recoilDamage); + } +} + +SINGLE_BATTLE_TEST("Steel Beam hp loss is prevented by Magic Guard") +{ + GIVEN { + PLAYER(SPECIES_CLEFAIRY) { Ability(ABILITY_MAGIC_GUARD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STEEL_BEAM); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEEL_BEAM, player); + HP_BAR(opponent); + NOT HP_BAR(player); + } +} diff --git a/test/battle/move_effect/mind_blown.c b/test/battle/move_effect/mind_blown.c index 485f2abd66..671491a846 100644 --- a/test/battle/move_effect/mind_blown.c +++ b/test/battle/move_effect/mind_blown.c @@ -105,3 +105,16 @@ DOUBLE_BATTLE_TEST("Mind Blown causes everyone to faint in a double battle") MESSAGE("Wobbuffet fainted!"); } } + +SINGLE_BATTLE_TEST("Mind Blown hp loss is prevented by Magic Guard") +{ + GIVEN { + PLAYER(SPECIES_CLEFAIRY) { Ability(ABILITY_MAGIC_GUARD); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_MIND_BLOWN); } + } SCENE { + NOT HP_BAR(player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MIND_BLOWN, player); + } +} diff --git a/test/battle/move_effect/pursuit.c b/test/battle/move_effect/pursuit.c new file mode 100644 index 0000000000..9d53f30573 --- /dev/null +++ b/test/battle/move_effect/pursuit.c @@ -0,0 +1,26 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_PURSUIT].effect == EFFECT_PURSUIT); +} + +SINGLE_BATTLE_TEST("Pursuited mon correctly switches out after it got hit and activated ability Tangling Hair") +{ + GIVEN { + PLAYER(SPECIES_DUGTRIO) { Ability(ABILITY_TANGLING_HAIR); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_PURSUIT); } + } SCENE { + MESSAGE("Dugtrio, that's enough! Come back!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PURSUIT, opponent); + ABILITY_POPUP(player, ABILITY_TANGLING_HAIR); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("Foe Wynaut's Speed fell!"); + MESSAGE("Go! Wobbuffet!"); + } +} diff --git a/test/test_runner.c b/test/test_runner.c index 5ca397cd6c..901c6c86c4 100644 --- a/test/test_runner.c +++ b/test/test_runner.c @@ -30,7 +30,6 @@ void TestRunner_Battle(const struct Test *); static bool32 MgbaOpen_(void); static void MgbaExit_(u8 exitCode); -static s32 MgbaPuts_(const char *s); static s32 MgbaVPrintf_(const char *fmt, va_list va); static void Intr_Timer2(void); @@ -293,12 +292,6 @@ void CB2_TestRunner(void) color = ""; } - if (gTestRunnerState.result == TEST_RESULT_PASS - && gTestRunnerState.result != gTestRunnerState.expectedResult) - { - MgbaPuts_("\e[31mPlease remove KNOWN_FAILING if this test intentionally PASSes\e[0m"); - } - switch (gTestRunnerState.result) { case TEST_RESULT_FAIL: @@ -313,7 +306,10 @@ void CB2_TestRunner(void) } break; case TEST_RESULT_PASS: - result = "PASS"; + if (gTestRunnerState.result != gTestRunnerState.expectedResult) + result = "KNOWN_FAILING_PASS"; + else + result = "PASS"; break; case TEST_RESULT_ASSUMPTION_FAIL: result = "ASSUMPTION_FAIL"; @@ -341,7 +337,12 @@ void CB2_TestRunner(void) } if (gTestRunnerState.result == TEST_RESULT_PASS) - MgbaPrintf_(":P%s%s\e[0m", color, result); + { + if (gTestRunnerState.result != gTestRunnerState.expectedResult) + MgbaPrintf_(":U%s%s\e[0m", color, result); + else + MgbaPrintf_(":P%s%s\e[0m", color, result); + } else if (gTestRunnerState.result == TEST_RESULT_ASSUMPTION_FAIL) MgbaPrintf_(":A%s%s\e[0m", color, result); else if (gTestRunnerState.result == TEST_RESULT_TODO) @@ -503,11 +504,6 @@ static void MgbaExit_(u8 exitCode) asm("swi 0x3" :: "r" (_exitCode)); } -static s32 MgbaPuts_(const char *s) -{ - return MgbaPrintf_("%s", s); -} - s32 MgbaPrintf_(const char *fmt, ...) { va_list va; diff --git a/tools/mgba-rom-test-hydra/main.c b/tools/mgba-rom-test-hydra/main.c index c2189ac0df..d4f39feb73 100644 --- a/tools/mgba-rom-test-hydra/main.c +++ b/tools/mgba-rom-test-hydra/main.c @@ -35,7 +35,7 @@ #define min(a, b) ((a) < (b) ? (a) : (b)) #define MAX_PROCESSES 32 // See also test/test.h -#define MAX_FAILED_TESTS_TO_LIST 100 +#define MAX_SUMMARY_TESTS_TO_LIST 50 #define MAX_TEST_LIST_BUFFER_LENGTH 256 #define ARRAY_COUNT(arr) (sizeof((arr)) / sizeof((arr)[0])) @@ -54,11 +54,13 @@ struct Runner char *output_buffer; int passes; int knownFails; + int knownFailsPassing; int todos; int assumptionFails; int fails; int results; - char failedTestNames[MAX_FAILED_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH]; + char failedTestNames[MAX_SUMMARY_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH]; + char knownFailingPassedTestNames[MAX_SUMMARY_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH]; }; static unsigned nrunners = 0; @@ -107,6 +109,11 @@ static void handle_read(int i, struct Runner *runner) case 'K': runner->knownFails++; goto add_to_results; + case 'U': + if (runner->knownFailsPassing < MAX_SUMMARY_TESTS_TO_LIST) + strcpy(runner->knownFailingPassedTestNames[runner->knownFailsPassing], runner->test_name); + runner->knownFailsPassing++; + goto add_to_results; case 'T': runner->todos++; goto add_to_results; @@ -114,7 +121,7 @@ static void handle_read(int i, struct Runner *runner) runner->assumptionFails++; goto add_to_results; case 'F': - if (runner->fails < MAX_FAILED_TESTS_TO_LIST) + if (runner->fails < MAX_SUMMARY_TESTS_TO_LIST) strcpy(runner->failedTestNames[runner->fails], runner->test_name); runner->fails++; add_to_results: @@ -519,12 +526,14 @@ int main(int argc, char *argv[]) int exit_code = 0; int passes = 0; int knownFails = 0; + int knownFailsPassing = 0; int todos = 0; int assumptionFails = 0; int fails = 0; int results = 0; - char failedTestNames[MAX_FAILED_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH]; + char failedTestNames[MAX_SUMMARY_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH]; + char knownFailingPassedTestNames[MAX_SUMMARY_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH]; for (int i = 0; i < nrunners; i++) { @@ -540,18 +549,25 @@ int main(int argc, char *argv[]) exit_code = WEXITSTATUS(wstatus); passes += runners[i].passes; knownFails += runners[i].knownFails; + for (int j = 0; j < runners[i].knownFailsPassing; j++) + { + if (j < MAX_SUMMARY_TESTS_TO_LIST) + strcpy(knownFailingPassedTestNames[fails], runners[i].knownFailingPassedTestNames[j]); + knownFailsPassing++; + } todos += runners[i].todos; assumptionFails += runners[i].assumptionFails; for (int j = 0; j < runners[i].fails; j++) { - if (j < MAX_FAILED_TESTS_TO_LIST) + if (j < MAX_SUMMARY_TESTS_TO_LIST) strcpy(failedTestNames[fails], runners[i].failedTestNames[j]); fails++; } results += runners[i].results; } - qsort(failedTestNames, min(fails, MAX_FAILED_TESTS_TO_LIST), sizeof(char) * MAX_TEST_LIST_BUFFER_LENGTH, compare_strings); + qsort(failedTestNames, min(fails, MAX_SUMMARY_TESTS_TO_LIST), sizeof(char) * MAX_TEST_LIST_BUFFER_LENGTH, compare_strings); + qsort(knownFailingPassedTestNames, min(fails, MAX_SUMMARY_TESTS_TO_LIST), sizeof(char) * MAX_TEST_LIST_BUFFER_LENGTH, compare_strings); if (results == 0) { @@ -559,28 +575,42 @@ int main(int argc, char *argv[]) } else { + fprintf(stdout, "\n"); if (fails > 0) { - fprintf(stdout, "\n- Tests \e[31mFAILED\e[0m : %d Add TESTS='X' to run tests with the defined prefix.\n", fails); + fprintf(stdout, "- Tests \e[31mFAILED\e[0m : %d Add TESTS='X' to run tests with the defined prefix.\n", fails); for (int i = 0; i < fails; i++) { - if (i >= MAX_FAILED_TESTS_TO_LIST) + if (i >= MAX_SUMMARY_TESTS_TO_LIST) { - fprintf(stdout, " - \e[31mand %d more...\e[0m\n", fails - MAX_FAILED_TESTS_TO_LIST); + fprintf(stdout, " - \e[31mand %d more...\e[0m\n", fails - MAX_SUMMARY_TESTS_TO_LIST); break; } fprintf(stdout, " - \e[31m%s\e[0m.\n", failedTestNames[i]); } } - fprintf(stdout, "- Tests \e[32mPASSED\e[0m: %d\n", passes); + if (knownFailsPassing > 0) + { + fprintf(stdout, "- \e[31mKNOWN_FAILING_PASSED\e[0m: %d \e[31mPlease remove KNOWN_FAILING if these tests intentionally PASS\e[0m\n", knownFailsPassing); + for (int i = 0; i < knownFailsPassing; i++) + { + if (i >= MAX_SUMMARY_TESTS_TO_LIST) + { + fprintf(stdout, " - \e[31mand %d more...\e[0m\n", knownFailsPassing - MAX_SUMMARY_TESTS_TO_LIST); + break; + } + fprintf(stdout, " - \e[31m%s\e[0m.\n", knownFailingPassedTestNames[i]); + } + } + fprintf(stdout, "- Tests \e[32mPASSED\e[0m: %d\n", passes); if (knownFails > 0) - fprintf(stdout, "- Tests \e[33mKNOWN_FAILING\e[0m: %d\n", knownFails); + fprintf(stdout, "- Tests \e[33mKNOWN_FAILING\e[0m: %d\n", knownFails); if (todos > 0) - fprintf(stdout, "- Tests \e[33mTO_DO\e[0m: %d\n", todos); + fprintf(stdout, "- Tests \e[33mTO_DO\e[0m: %d\n", todos); if (assumptionFails > 0) - fprintf(stdout, "- \e[33mASSUMPTIONS_FAILED\e[0m: %d\n", assumptionFails); + fprintf(stdout, "- \e[33mASSUMPTIONS_FAILED\e[0m: %d\n", assumptionFails); - fprintf(stdout, "- Tests \e[34mTOTAL\e[0m: %d\n", results); + fprintf(stdout, "- Tests \e[34mTOTAL\e[0m: %d\n", results); } fprintf(stdout, "\n");