diff --git a/.all-contributorsrc b/.all-contributorsrc index 5b6d38e32f..c8b79ba30e 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -321,7 +321,7 @@ "code" ] }, - { + { "login": "lordraindance2", "name": "lordraindance2", "avatar_url": "https://avatars.githubusercontent.com/u/47706100?v=4", @@ -329,6 +329,15 @@ "contributions": [ "code" ] + }, + { + "login": "pablopenna", + "name": "Pablo Pena", + "avatar_url": "https://avatars.githubusercontent.com/u/11214682?v=4", + "profile": "https://github.com/pablopenna", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml b/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml index da15109c8c..1ab103d6c7 100644 --- a/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml +++ b/.github/ISSUE_TEMPLATE/01_battle_engine_bugs.yaml @@ -7,6 +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 id: description attributes: label: Description @@ -42,9 +43,10 @@ body: label: Version description: What version of pokeemerald-expansion are you using? options: - - 1.12.0 (Latest release) + - 1.12.1 (Latest release) - master (default, unreleased bugfixes) - upcoming (Edge) + - 1.12.0 - 1.11.4 - 1.11.3 - 1.11.2 diff --git a/.github/ISSUE_TEMPLATE/02_battle_ai_issues.yaml b/.github/ISSUE_TEMPLATE/02_battle_ai_issues.yaml index 7b1a489cfb..49bf8a5525 100644 --- a/.github/ISSUE_TEMPLATE/02_battle_ai_issues.yaml +++ b/.github/ISSUE_TEMPLATE/02_battle_ai_issues.yaml @@ -43,9 +43,10 @@ body: label: Version description: What version of pokeemerald-expansion are you using? options: - - 1.12.0 (Latest release) + - 1.12.1 (Latest release) - master (default, unreleased bugfixes) - upcoming (Edge) + - 1.12.0 - 1.11.4 - 1.11.3 - 1.11.2 diff --git a/.github/ISSUE_TEMPLATE/04_other_errors.yaml b/.github/ISSUE_TEMPLATE/04_other_errors.yaml index 197791a776..0d25f6a96a 100644 --- a/.github/ISSUE_TEMPLATE/04_other_errors.yaml +++ b/.github/ISSUE_TEMPLATE/04_other_errors.yaml @@ -43,9 +43,10 @@ body: label: Version description: What version of pokeemerald-expansion are you using? options: - - 1.12.0 (Latest release) + - 1.12.1 (Latest release) - master (default, unreleased bugfixes) - upcoming (Edge) + - 1.12.0 - 1.11.4 - 1.11.3 - 1.11.1 diff --git a/CREDITS.md b/CREDITS.md index 128f26df6f..ab5c596248 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -58,6 +58,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Zatsu
Zatsu

💻 poetahto
poetahto

💻 lordraindance2
lordraindance2

💻 + Pablo Pena
Pablo Pena

💻 diff --git a/Makefile b/Makefile index 0a8e78c28c..b79d3bd433 100644 --- a/Makefile +++ b/Makefile @@ -381,6 +381,9 @@ $(C_BUILDDIR)/pokedex_plus_hgss.o: CFLAGS := -mthumb -mthumb-interwork -O2 -mabi # Annoyingly we can't turn this on just for src/data/trainers.h $(C_BUILDDIR)/data.o: CFLAGS += -fno-show-column -fno-diagnostics-show-caret +# Needed for parity with pret +$(C_BUILDDIR)/graphics.o: override CFLAGS += -Wno-missing-braces + $(TEST_BUILDDIR)/%.o: CFLAGS := -mthumb -mthumb-interwork -O2 -mabi=apcs-gnu -mtune=arm7tdmi -march=armv4t -Wno-pointer-to-int-cast -Werror -Wall -Wno-strict-aliasing -Wno-attribute-alias -Woverride-init # Dependency rules (for the *.c & *.s sources to .o files) @@ -394,10 +397,10 @@ ifneq ($(KEEP_TEMPS),1) @echo "$(CC1) -o $@ $<" @$(CPP) $(CPPFLAGS) $< | $(PREPROC) -i $< charmap.txt | $(CC1) $(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $(AS) $(ASFLAGS) -o $@ - else - @$(CPP) $(CPPFLAGS) $< -o $*.i - @$(PREPROC) $*.i charmap.txt | $(CC1) $(CFLAGS) -o $*.s - @echo -e ".text\n\t.align\t2, 0\n" >> $*.s - $(AS) $(ASFLAGS) -o $@ $*.s + @$(CPP) $(CPPFLAGS) $< -o $(C_BUILDDIR)/$*.i + @$(PREPROC) $(C_BUILDDIR)/$*.i charmap.txt | $(CC1) $(CFLAGS) -o $(C_BUILDDIR)/$*.s + @echo -e ".text\n\t.align\t2, 0\n" >> $(C_BUILDDIR)/$*.s + $(AS) $(ASFLAGS) -o $@ $(C_BUILDDIR)/$*.s endif $(C_BUILDDIR)/%.d: $(C_SUBDIR)/%.c diff --git a/README.md b/README.md index 626eea5d7e..6d86c27a2a 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you use **`pokeemerald-expansion`**, please credit **RHH (Rom Hacking Hideout)**. Optionally, include the version number for clarity. ``` -Based off RHH's pokeemerald-expansion 1.12.0 https://github.com/rh-hideout/pokeemerald-expansion/ +Based off RHH's pokeemerald-expansion 1.12.1 https://github.com/rh-hideout/pokeemerald-expansion/ ``` Please consider [crediting all contributors](CREDITS.md) involved in the project! diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index b524104a36..02546d2f8a 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -834,8 +834,10 @@ .4byte \failInstr .endm - .macro unused_0x94 + .macro checknonvolatiletrigger nonVolatile:req, failInstr:req .byte 0x94 + .2byte \nonVolatile + .4byte \failInstr .endm .macro copybidedmg @@ -1527,14 +1529,8 @@ .4byte \jumpInstr .endm - .macro jumpifmovepropertyargument argument:req, jumpInstr:req - callnative BS_JumpIfMovePropertyArgument - .byte \argument - .4byte \jumpInstr - .endm - - .macro setremoveterrain failInstr:req - callnative BS_SetRemoveTerrain + .macro setterrain failInstr:req + callnative BS_SetTerrain .4byte \failInstr .endm diff --git a/asm/macros/event.inc b/asm/macros/event.inc index d3c1442f56..1cf9c7fca7 100644 --- a/asm/macros/event.inc +++ b/asm/macros/event.inc @@ -736,11 +736,11 @@ @ When used with an event script, you can also pass in an optional flag to disable music .macro trainerbattle_single trainer:req, intro_text:req, lose_text:req, event_script=FALSE, music=TRUE .if \event_script == FALSE - trainerbattle TRAINER_BATTLE_SINGLE, OBJ_ID_NONE, \trainer, \intro_text, \lose_text, NULL, OBJ_ID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, NULL, FALSE, TRUE, FALSE, FALSE + trainerbattle TRAINER_BATTLE_SINGLE, LOCALID_NONE, \trainer, \intro_text, \lose_text, NULL, LOCALID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, NULL, FALSE, TRUE, FALSE, FALSE .elseif \music == TRUE - trainerbattle TRAINER_BATTLE_CONTINUE_SCRIPT, OBJ_ID_NONE, \trainer, \intro_text, \lose_text, \event_script, OBJ_ID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, NULL, FALSE, TRUE, FALSE, FALSE + trainerbattle TRAINER_BATTLE_CONTINUE_SCRIPT, LOCALID_NONE, \trainer, \intro_text, \lose_text, \event_script, LOCALID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, NULL, FALSE, TRUE, FALSE, FALSE .else - trainerbattle TRAINER_BATTLE_CONTINUE_SCRIPT_NO_MUSIC, OBJ_ID_NONE, \trainer, \intro_text, \lose_text, \event_script, OBJ_ID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, NULL, FALSE, TRUE, FALSE, FALSE + trainerbattle TRAINER_BATTLE_CONTINUE_SCRIPT_NO_MUSIC, LOCALID_NONE, \trainer, \intro_text, \lose_text, \event_script, LOCALID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, NULL, FALSE, TRUE, FALSE, FALSE .endif .endm @@ -748,27 +748,27 @@ @ and an optional event script. When used with an event script you can pass in an optional flag to disable music .macro trainerbattle_double trainer:req, intro_text:req, lose_text:req, not_enough_pkmn_text:req, event_script=FALSE, music=TRUE .if \event_script == FALSE - trainerbattle TRAINER_BATTLE_DOUBLE, OBJ_ID_NONE, \trainer, \intro_text, \lose_text, NULL, OBJ_ID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, \not_enough_pkmn_text, TRUE, TRUE, FALSE, FALSE + trainerbattle TRAINER_BATTLE_DOUBLE, LOCALID_NONE, \trainer, \intro_text, \lose_text, NULL, LOCALID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, \not_enough_pkmn_text, TRUE, TRUE, FALSE, FALSE .elseif \music == TRUE - trainerbattle TRAINER_BATTLE_CONTINUE_SCRIPT_DOUBLE, OBJ_ID_NONE, \trainer, \intro_text, \lose_text, \event_script, OBJ_ID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, \not_enough_pkmn_text, TRUE, TRUE, FALSE, FALSE + trainerbattle TRAINER_BATTLE_CONTINUE_SCRIPT_DOUBLE, LOCALID_NONE, \trainer, \intro_text, \lose_text, \event_script, LOCALID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, \not_enough_pkmn_text, TRUE, TRUE, FALSE, FALSE .else - trainerbattle TRAINER_BATTLE_CONTINUE_SCRIPT_DOUBLE_NO_MUSIC, OBJ_ID_NONE, \trainer, \intro_text, \lose_text, \event_script, OBJ_ID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, \not_enough_pkmn_text, TRUE, FALSE, FALSE, FALSE + trainerbattle TRAINER_BATTLE_CONTINUE_SCRIPT_DOUBLE_NO_MUSIC, LOCALID_NONE, \trainer, \intro_text, \lose_text, \event_script, LOCALID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, \not_enough_pkmn_text, TRUE, FALSE, FALSE, FALSE .endif .endm @ Starts a rematch battle. Takes a trainer, intro text and loss text .macro trainerbattle_rematch trainer:req, intro_text:req, lose_text:req - trainerbattle TRAINER_BATTLE_REMATCH, OBJ_ID_NONE, \trainer, \intro_text, \lose_text, NULL, OBJ_ID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, NULL, FALSE, TRUE, FALSE, TRUE + trainerbattle TRAINER_BATTLE_REMATCH, LOCALID_NONE, \trainer, \intro_text, \lose_text, NULL, LOCALID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, NULL, FALSE, TRUE, FALSE, TRUE .endm @ Starts a rematch double battle. Takes a trainer, intro text, loss text, and text for when you have too few pokemon .macro trainerbattle_rematch_double trainer:req, intro_text:req, lose_text:req, not_enough_pkmn_text:req - trainerbattle TRAINER_BATTLE_REMATCH_DOUBLE, OBJ_ID_NONE, \trainer, \intro_text, \lose_text, NULL, OBJ_ID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, \not_enough_pkmn_text, FALSE, TRUE, FALSE, TRUE + trainerbattle TRAINER_BATTLE_REMATCH_DOUBLE, LOCALID_NONE, \trainer, \intro_text, \lose_text, NULL, LOCALID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, \not_enough_pkmn_text, FALSE, TRUE, FALSE, TRUE .endm @ Starts a trainer battle, skipping intro text. Takes a trainer and loss text .macro trainerbattle_no_intro trainer:req, lose_text:req - trainerbattle TRAINER_BATTLE_SINGLE_NO_INTRO_TEXT, OBJ_ID_NONE, \trainer, NULL, \lose_text, NULL, OBJ_ID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, NULL, FALSE, TRUE, FALSE, FALSE + trainerbattle TRAINER_BATTLE_SINGLE_NO_INTRO_TEXT, LOCALID_NONE, \trainer, NULL, \lose_text, NULL, LOCALID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, NULL, FALSE, TRUE, FALSE, FALSE .endm @ Starts a double battle with the player against two trainers diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index a10dfaa40c..e32dfd2859 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -1503,11 +1503,12 @@ BattleScript_EffectFlowerShield:: attackcanceler attackstring ppreduce + savetarget selectfirstvalidtarget BattleScript_FlowerShieldIsAnyGrass: jumpiftype BS_TARGET, TYPE_GRASS, BattleScript_FlowerShieldLoopStart jumpifnexttargetvalid BattleScript_FlowerShieldIsAnyGrass - goto BattleScript_ButItFailed + goto BattleScript_RestoreTargetButItFailed BattleScript_FlowerShieldLoopStart: selectfirstvalidtarget BattleScript_FlowerShieldLoop: @@ -1532,6 +1533,7 @@ BattleScript_FlowerShieldString: BattleScript_FlowerShieldMoveTargetEnd: moveendto MOVEEND_NEXT_TARGET jumpifnexttargetvalid BattleScript_FlowerShieldLoop + restoretarget end BattleScript_EffectRototiller:: @@ -1834,20 +1836,20 @@ BattleScript_EffectToxicThread:: setstatchanger STAT_SPEED, 1, TRUE attackcanceler jumpifsubstituteblocks BattleScript_FailedFromAtkString - jumpifstat BS_TARGET, CMP_NOT_EQUAL, STAT_SPEED, MIN_STAT_STAGE, BattleScript_ToxicThreadWorks - jumpifstatus BS_TARGET, STATUS1_PSN_ANY, BattleScript_FailedFromAtkString + checknonvolatiletrigger MOVE_EFFECT_POISON, BattleScript_EffectStatDownFromAccCheck BattleScript_ToxicThreadWorks: accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE attackstring ppreduce + attackanimation + waitanimation + setstatchanger STAT_SPEED, 1, TRUE statbuffchange STAT_CHANGE_ALLOW_PTR, BattleScript_ToxicThreadTryPsn jumpifbyte CMP_LESS_THAN, cMULTISTRING_CHOOSER, B_MSG_STAT_WONT_DECREASE, BattleScript_ToxicThreadDoAnim jumpifbyte CMP_EQUAL, cMULTISTRING_CHOOSER, B_MSG_STAT_FELL_EMPTY, BattleScript_ToxicThreadTryPsn pause B_WAIT_TIME_SHORT goto BattleScript_ToxicThreadPrintString BattleScript_ToxicThreadDoAnim:: - attackanimation - waitanimation setgraphicalstatchangevalues playanimation BS_TARGET, B_ANIM_STATS_CHANGE, sB_ANIM_ARG1 BattleScript_ToxicThreadPrintString:: @@ -2246,7 +2248,7 @@ BattleScript_EffectPsychicTerrain:: attackcanceler attackstring ppreduce - setremoveterrain BattleScript_ButItFailed + setterrain BattleScript_ButItFailed attackanimation waitanimation printfromtable gTerrainStringIds @@ -3142,6 +3144,7 @@ BattleScript_EffectEvasionDown:: BattleScript_EffectStatDown: attackcanceler jumpifsubstituteblocks BattleScript_FailedFromAtkString +BattleScript_EffectStatDownFromAccCheck: accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE BattleScript_StatDownFromAttackString: attackstring @@ -4513,6 +4516,9 @@ BattleScript_ButItFailed:: BattleScript_RestoreAttackerButItFailed: restoreattacker goto BattleScript_ButItFailed +BattleScript_RestoreTargetButItFailed: + restoretarget + goto BattleScript_ButItFailed BattleScript_NotAffected:: pause B_WAIT_TIME_SHORT @@ -7436,7 +7442,7 @@ BattleScript_EmergencyExit:: switchoutabilities BS_SCRIPTING waitstate switchhandleorder BS_SCRIPTING, 2 - returntoball BS_TARGET, FALSE + returntoball BS_SCRIPTING, FALSE getswitchedmondata BS_SCRIPTING switchindataupdate BS_SCRIPTING hpthresholds BS_SCRIPTING @@ -7469,7 +7475,7 @@ BattleScript_EmergencyExitEnd2:: switchoutabilities BS_ATTACKER waitstate switchhandleorder BS_ATTACKER, 2 - returntoball BS_TARGET, FALSE + returntoball BS_ATTACKER, FALSE getswitchedmondata BS_ATTACKER switchindataupdate BS_ATTACKER hpthresholds BS_ATTACKER @@ -8571,6 +8577,7 @@ BattleScript_MoveUsedLoafingAroundMsg:: moveendto MOVEEND_NEXT_TARGET end BattleScript_TruantLoafingAround:: + flushtextbox call BattleScript_AbilityPopUp goto BattleScript_MoveUsedLoafingAroundMsg @@ -9296,12 +9303,11 @@ BattleScript_ExtremeEvoboostSpDef:: BattleScript_ExtremeEvoboostEnd:: goto BattleScript_MoveEnd -BattleScript_EffectHitSetRemoveTerrain:: +BattleScript_EffectHitSetTerrain:: attackcanceler accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE attackstring ppreduce - jumpifmovepropertyargument ARG_TRY_REMOVE_TERRAIN_FAIL, BattleScript_RemoveTerrain critcalc damagecalc adjustdamage @@ -9316,7 +9322,7 @@ BattleScript_EffectHitSetRemoveTerrain:: waitmessage B_WAIT_TIME_LONG resultmessage waitmessage B_WAIT_TIME_LONG - setremoveterrain BattleScript_TryFaint + setterrain BattleScript_TryFaint playanimation BS_ATTACKER, B_ANIM_RESTORE_BG printfromtable gTerrainStringIds waitmessage B_WAIT_TIME_LONG @@ -9324,30 +9330,17 @@ BattleScript_TryFaint: tryfaintmon BS_TARGET goto BattleScript_MoveEnd -BattleScript_RemoveTerrain: - jumpifterrainaffected BS_TARGET, STATUS_FIELD_TERRAIN_ANY, BattleScript_RemoveTerrain_Cont - goto BattleScript_ButItFailed -BattleScript_RemoveTerrain_Cont: - critcalc - damagecalc - adjustdamage - attackanimation - waitanimation - effectivenesssound - hitanimation BS_TARGET - waitstate - healthbarupdate BS_TARGET - datahpupdate BS_TARGET - critmessage - waitmessage B_WAIT_TIME_LONG - resultmessage - waitmessage B_WAIT_TIME_LONG +BattleScript_EffectSteelRoller:: + attackcanceler + jumpifhalfword CMP_NO_COMMON_BITS, gFieldStatuses, STATUS_FIELD_TERRAIN_ANY, BattleScript_FailedFromAtkString + goto BattleScript_HitFromAccCheck + +BattleScript_RemoveTerrain:: removeterrain playanimation BS_ATTACKER, B_ANIM_RESTORE_BG printfromtable gTerrainStringIds waitmessage B_WAIT_TIME_LONG - tryfaintmon BS_TARGET - goto BattleScript_MoveEnd + return BattleScript_Pickpocket:: call BattleScript_AbilityPopUp diff --git a/data/maps/BattleFrontier_BattlePyramidFloor/scripts.inc b/data/maps/BattleFrontier_BattlePyramidFloor/scripts.inc index 91fd479e4a..441d7df874 100644 --- a/data/maps/BattleFrontier_BattlePyramidFloor/scripts.inc +++ b/data/maps/BattleFrontier_BattlePyramidFloor/scripts.inc @@ -105,7 +105,7 @@ BattlePyramid_WarpToTop:: @ TRAINER_PHILLIP is used as a placeholder BattlePyramid_TrainerBattle:: - trainerbattle TRAINER_BATTLE_HILL, OBJ_ID_NONE, TRAINER_PHILLIP, BattleFacility_TrainerBattle_PlaceholderText, BattleFacility_TrainerBattle_PlaceholderText, NULL, OBJ_ID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, NULL, FALSE, FALSE, FALSE, FALSE + trainerbattle TRAINER_BATTLE_HILL, LOCALID_NONE, TRAINER_PHILLIP, BattleFacility_TrainerBattle_PlaceholderText, BattleFacility_TrainerBattle_PlaceholderText, NULL, LOCALID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, NULL, FALSE, FALSE, FALSE, FALSE pyramid_showhint waitmessage waitbuttonpress diff --git a/data/scripts/battle_pike.inc b/data/scripts/battle_pike.inc index 2108becd06..c4612609b0 100644 --- a/data/scripts/battle_pike.inc +++ b/data/scripts/battle_pike.inc @@ -85,6 +85,7 @@ BattleFrontier_BattlePikeThreePathRoom_EventScript_RightRoomWarp:: end BattleFrontier_BattlePikeThreePathRoom_EventScript_RoomWarp:: + clearflag FLAG_SAFE_FOLLOWER_MOVEMENT pike_get PIKE_DATA_WIN_STREAK addvar VAR_RESULT, 1 pike_set PIKE_DATA_WIN_STREAK, VAR_RESULT diff --git a/data/scripts/trainer_hill.inc b/data/scripts/trainer_hill.inc index 877b48c3db..ece0b01ee2 100644 --- a/data/scripts/trainer_hill.inc +++ b/data/scripts/trainer_hill.inc @@ -60,7 +60,7 @@ TrainerHill_1F_Movement_SetInvisible:: @ TRAINER_PHILLIP is an actual Trainer on the SS Tidal, but is used as a placeholder here TrainerHill_EventScript_TrainerBattle:: - trainerbattle TRAINER_BATTLE_HILL, OBJ_ID_NONE, TRAINER_PHILLIP, BattleFacility_TrainerBattle_PlaceholderText, BattleFacility_TrainerBattle_PlaceholderText, NULL, OBJ_ID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, NULL, FALSE, FALSE, FALSE, FALSE + trainerbattle TRAINER_BATTLE_HILL, LOCALID_NONE, TRAINER_PHILLIP, BattleFacility_TrainerBattle_PlaceholderText, BattleFacility_TrainerBattle_PlaceholderText, NULL, LOCALID_NONE, TRAINER_NONE, NULL, NULL, NULL, NULL, NULL, FALSE, FALSE, FALSE, FALSE trainerhill_postbattletext waitmessage waitbuttonpress diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index fa03ca20ee..258d078cdb 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -20,6 +20,7 @@ - [Day/Night System FAQ](tutorials/dns.md) - [Changelog](./CHANGELOG.md) - [1.12.x]() + - [Version 1.12.1](changelogs/1.12.x/1.12.1.md) - [Version 1.12.0](changelogs/1.12.x/1.12.0.md) - [1.11.x]() - [Version 1.11.4](changelogs/1.11.x/1.11.4.md) diff --git a/docs/changelogs/1.12.x/1.12.1.md b/docs/changelogs/1.12.x/1.12.1.md new file mode 100644 index 0000000000..ba1d24c934 --- /dev/null +++ b/docs/changelogs/1.12.x/1.12.1.md @@ -0,0 +1,145 @@ +```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.12.1 +`. +``` + + +## 🧬 General 🧬 +### Changed +* Changed trainer tutorial to take into account removed palette compression by @hedara90 in [#7044](https://github.com/rh-hideout/pokeemerald-expansion/pull/7044) +* Update CI to ignore allcontributors commits by @pkmnsnfrn in [#7046](https://github.com/rh-hideout/pokeemerald-expansion/pull/7046) +* Fixed CI issue introduced 7046 by @pkmnsnfrn in [#7072](https://github.com/rh-hideout/pokeemerald-expansion/pull/7072) +* Added loop iterator style by @hedara90 in [#7092](https://github.com/rh-hideout/pokeemerald-expansion/pull/7092) +* Fix typo in INSTALL.md by @hedara90 in [#7116](https://github.com/rh-hideout/pokeemerald-expansion/pull/7116) +* Use RGB values for DEFAULT_LIGHT_COLOR by @hedara90 in [#7133](https://github.com/rh-hideout/pokeemerald-expansion/pull/7133) +* Applied Kasen's documentation improvements by @hedara90 in [#7104](https://github.com/rh-hideout/pokeemerald-expansion/pull/7104) +* Fixed description of `FORM_CHANGE_WITHDRAW` by @AsparagusEduardo in [#7152](https://github.com/rh-hideout/pokeemerald-expansion/pull/7152) +* Pret merge (19th of June, 2025) by @Bassoonian in [#7163](https://github.com/rh-hideout/pokeemerald-expansion/pull/7163) +* Update the description of OW_OBJECT_VANILLA_SHADOWS by @pkmnsnfrn in [#7184](https://github.com/rh-hideout/pokeemerald-expansion/pull/7184) +* fix: use BackPickId to calculate player intro ball throw animation palette by @pablopenna in [#7193](https://github.com/rh-hideout/pokeemerald-expansion/pull/7193) + +### Fixed +* Added line break between Trainer 1 name and Trainer 2 name in sText_TwoTrainersWantToBattle by @grintoul1 in [#7028](https://github.com/rh-hideout/pokeemerald-expansion/pull/7028) +* Fixed Battle Pyramid mon generation by @hedara90 in [#7146](https://github.com/rh-hideout/pokeemerald-expansion/pull/7146) +* force sGFRomHeader to always be present by @DizzyEggg in [#7186](https://github.com/rh-hideout/pokeemerald-expansion/pull/7186) +* force RHH Rom Header to always be present by @DizzyEggg in [#7187](https://github.com/rh-hideout/pokeemerald-expansion/pull/7187) +* Fixed debug flag menu sound by @AsparagusEduardo in [#7190](https://github.com/rh-hideout/pokeemerald-expansion/pull/7190) +* Fix BtlController_EmitChosenMonReturnValue UB by @DizzyEggg in [#7197](https://github.com/rh-hideout/pokeemerald-expansion/pull/7197) +* Backported Safari catch and add to party fix by @hedara90 in [#7192](https://github.com/rh-hideout/pokeemerald-expansion/pull/7192) +* Fix 01_battle_engine_bugs.yaml by @hedara90 in [#7242](https://github.com/rh-hideout/pokeemerald-expansion/pull/7242) + +## 🗺️ Overworld 🗺️ +### Fixed +* Bug fix: clear saved follower NPC door warp when doing dive warp by @Bivurnum in [#7065](https://github.com/rh-hideout/pokeemerald-expansion/pull/7065) +* Fix Contest Painting load palette error by @ExMingYan in [#7077](https://github.com/rh-hideout/pokeemerald-expansion/pull/7077) +* Bug fix: Follower NPC no longer retains bike sprite after white out by @Bivurnum in [#7120](https://github.com/rh-hideout/pokeemerald-expansion/pull/7120) +* Bug fix: clear follower npc surf blob on white out by @Bivurnum in [#7153](https://github.com/rh-hideout/pokeemerald-expansion/pull/7153) + +## 🐉 Pokémon 🐉 +### Changed +* Fixes Rapid Spin description (#7178) by @grintoul1 in [#7181](https://github.com/rh-hideout/pokeemerald-expansion/pull/7181) + +### Fixed +* 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 +* Fixes large battle messages being cut off instead of being prompted to advance 2 by @PhallenTree in [#7036](https://github.com/rh-hideout/pokeemerald-expansion/pull/7036) +* Battle controller pret documentation by @AlexOn1ine in [#7029](https://github.com/rh-hideout/pokeemerald-expansion/pull/7029) +* Fix typos and some cleanup (mainly in battle files) by @PhallenTree in [#7107](https://github.com/rh-hideout/pokeemerald-expansion/pull/7107) + +### Fixed +* Fixes multi battle party re-order by @AlexOn1ine in [#7042](https://github.com/rh-hideout/pokeemerald-expansion/pull/7042) +* Fixes Aura Wheel + Normalize and Hunger Switch while Transformed/Terastallized by @PhallenTree in [#7061](https://github.com/rh-hideout/pokeemerald-expansion/pull/7061) +* Fixes speed calculation order by @AlexOn1ine in [#7064](https://github.com/rh-hideout/pokeemerald-expansion/pull/7064) +* Bug fix for Grassy Terrain incorrectly healing non-grounded Pokemon by @LinathanZel in [#7058](https://github.com/rh-hideout/pokeemerald-expansion/pull/7058) +* Fixes Wandering Spirit copied ability activation on fainted mon by @AlexOn1ine in [#7066](https://github.com/rh-hideout/pokeemerald-expansion/pull/7066) +* Chloroblast fix by @LinathanZel in [#7008](https://github.com/rh-hideout/pokeemerald-expansion/pull/7008) +* 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) +* Fixed potential mismatch between players and battlers in tests by @AsparagusEduardo in [#7101](https://github.com/rh-hideout/pokeemerald-expansion/pull/7101) +* Fixes Ally Switch in multi battles by @AlexOn1ine in [#7109](https://github.com/rh-hideout/pokeemerald-expansion/pull/7109) +* Add missing flag for Berserk Gene by @AlexOn1ine in [#7151](https://github.com/rh-hideout/pokeemerald-expansion/pull/7151) +* Fixes Neutralizing Gas leaving the field activating unsuppressable abilities again by @PhallenTree in [#7170](https://github.com/rh-hideout/pokeemerald-expansion/pull/7170) +* Fixes Enigma, Kee and Maranga Berry activation timing by @AlexOn1ine in [#7171](https://github.com/rh-hideout/pokeemerald-expansion/pull/7171) +* Fixes wrong Future Sight indexing by @AlexOn1ine in [#7198](https://github.com/rh-hideout/pokeemerald-expansion/pull/7198) +* Fixes OOB for Teatime and Flower Shield by @AlexOn1ine in [#7214](https://github.com/rh-hideout/pokeemerald-expansion/pull/7214) +* Fixes wrong assignment in TrySymbiosis by @AlexOn1ine in [#7221](https://github.com/rh-hideout/pokeemerald-expansion/pull/7221) +* Adds missing healBlockTimer for Baton Pass by @AlexOn1ine in [#7220](https://github.com/rh-hideout/pokeemerald-expansion/pull/7220) +* Jaboca berry triggers instead of being stolen by bug bite by @ghoulslash in [#7237](https://github.com/rh-hideout/pokeemerald-expansion/pull/7237) +* Fixes Scald defrosting target while asleep by @AlexOn1ine in [#7233](https://github.com/rh-hideout/pokeemerald-expansion/pull/7233) +* Fixes Emergency Exit sometimes causing an unrelated battler to become invisible by @PhallenTree in [#7241](https://github.com/rh-hideout/pokeemerald-expansion/pull/7241) + +## 🤹 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) + +## 🎭 Abilities 🎭 +### Changed +* Flush textbox for Truant Popup by @ghoulslash in [#7252](https://github.com/rh-hideout/pokeemerald-expansion/pull/7252) + +## 🧶 Items 🧶 +### Fixed +* Fix sell price display by @cawtds in [#7123](https://github.com/rh-hideout/pokeemerald-expansion/pull/7123) + +## 🤖 Battle AI 🤖 +### Fixed +* Added AI_FLAG_PP_STALL_PREVENTION to AI_FLAG_SMART_TRAINER by @AlexOn1ine in [#7112](https://github.com/rh-hideout/pokeemerald-expansion/pull/7112) +* Fix incorrect function parameters used in AI damage calc by @Pawkkie in [#7130](https://github.com/rh-hideout/pokeemerald-expansion/pull/7130) + +## 🧹 Other Cleanup 🧹 +* Fix typos and some cleanup (mainly in battle files) by @PhallenTree in [#7107](https://github.com/rh-hideout/pokeemerald-expansion/pull/7107) +* Fix typo in INSTALL.md by @hedara90 in [#7116](https://github.com/rh-hideout/pokeemerald-expansion/pull/7116) +* Fixed description of `FORM_CHANGE_WITHDRAW` by @AsparagusEduardo in [#7152](https://github.com/rh-hideout/pokeemerald-expansion/pull/7152) +* Spruce up `FEATURES.md` by @AsparagusEduardo in [#7159](https://github.com/rh-hideout/pokeemerald-expansion/pull/7159) +* Fixes Rapid Spin description (#7178) by @grintoul1 in [#7181](https://github.com/rh-hideout/pokeemerald-expansion/pull/7181) + +## 🧪 Test Runner 🧪 +### Changed +* Wrote some missing tests by @AsparagusEduardo in [#7094](https://github.com/rh-hideout/pokeemerald-expansion/pull/7094) +* Fixed KNOWN_FAILING Tera test by @AsparagusEduardo in [#6949](https://github.com/rh-hideout/pokeemerald-expansion/pull/6949) +* Add some tests by @ghoulslash in [#7234](https://github.com/rh-hideout/pokeemerald-expansion/pull/7234) +* Added tests for Toxic Thread by @hedara90 in [#7244](https://github.com/rh-hideout/pokeemerald-expansion/pull/7244) + +### Fixed +* Test runner fixes by @hedara90 in [#7100](https://github.com/rh-hideout/pokeemerald-expansion/pull/7100) +* Fixed Aura Wheel `KNOWN_FAILING` test by @AsparagusEduardo in [#7135](https://github.com/rh-hideout/pokeemerald-expansion/pull/7135) +* Fix AI party count calc being maintained between tests by @AsparagusEduardo in [#7200](https://github.com/rh-hideout/pokeemerald-expansion/pull/7200) +* Fix tests failing with `B_FRIENDSHIP_BOOST` being `TRUE` by @AsparagusEduardo in [#7194](https://github.com/rh-hideout/pokeemerald-expansion/pull/7194) +* Jaboca berry triggers instead of being stolen by bug bite by @ghoulslash in [#7237](https://github.com/rh-hideout/pokeemerald-expansion/pull/7237) + +## 📚 Documentation 📚 +* Changed trainer tutorial to take into account removed palette compression by @hedara90 in [#7044](https://github.com/rh-hideout/pokeemerald-expansion/pull/7044) +* Battle controller pret documentation by @AlexOn1ine in [#7029](https://github.com/rh-hideout/pokeemerald-expansion/pull/7029) +* Added loop iterator style by @hedara90 in [#7092](https://github.com/rh-hideout/pokeemerald-expansion/pull/7092) +* Use RGB values for DEFAULT_LIGHT_COLOR by @hedara90 in [#7133](https://github.com/rh-hideout/pokeemerald-expansion/pull/7133) +* Applied Kasen's documentation improvements by @hedara90 in [#7104](https://github.com/rh-hideout/pokeemerald-expansion/pull/7104) +* Spruce up `FEATURES.md` by @AsparagusEduardo in [#7159](https://github.com/rh-hideout/pokeemerald-expansion/pull/7159) +* Update the description of OW_OBJECT_VANILLA_SHADOWS by @pkmnsnfrn in [#7184](https://github.com/rh-hideout/pokeemerald-expansion/pull/7184) + +## 📦 Branch Synchronisation 📦 +### pret +* 24th of June, 2025 in [#7206](https://github.com/rh-hideout/pokeemerald-expansion/pull/7206) + * Move gTradePlatform_Tilemap to header and change to u32 by @DizzyEggg in [pret#2088](https://github.com/pret/pokeemerald/pull/2088) + * Fix wrong keep temps files directory in makefile by @DizzyEggg in [pret#2156](https://github.com/pret/pokeemerald/pull/2156) + * Fix collision comparison in PlayerNotOnBikeMoving by @GriffinRichards in [pret#2104](https://github.com/pret/pokeemerald/pull/2104) + * Match graphics declarations with externs in graphics.h by @DizzyEggg in [pret#2089](https://github.com/pret/pokeemerald/pull/2089) + +## New Contributors +* @TLM-PsIQ made their first contribution in [#6476](https://github.com/rh-hideout/pokeemerald-expansion/pull/6476) +* @pablopenna made their first contribution in [#7193](https://github.com/rh-hideout/pokeemerald-expansion/pull/7193) +* @J2M2 made their first contribution in [#7177](https://github.com/rh-hideout/pokeemerald-expansion/pull/7177) + +**Full Changelog**: https://github.com/rh-hideout/pokeemerald-expansion/compare/expansion/1.12.0...expansion/1.12.1 + + + + diff --git a/include/battle_scripts.h b/include/battle_scripts.h index de3e876aa2..cd88a120a5 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -292,6 +292,7 @@ extern const u8 BattleScript_CursedBodyActivates[]; extern const u8 BattleScript_MummyActivates[]; extern const u8 BattleScript_WeakArmorActivates[]; extern const u8 BattleScript_FellStingerRaisesStat[]; +extern const u8 BattleScript_RemoveTerrain[]; extern const u8 BattleScript_SnowWarningActivatesHail[]; extern const u8 BattleScript_SnowWarningActivatesSnow[]; extern const u8 BattleScript_PickupActivates[]; @@ -339,6 +340,7 @@ extern const u8 BattleScript_GrassySurgeActivates[]; extern const u8 BattleScript_MistySurgeActivates[]; extern const u8 BattleScript_ElectricSurgeActivates[]; extern const u8 BattleScript_EffectSpectralThief[]; +extern const u8 BattleScript_EffectSteelRoller[]; extern const u8 BattleScript_StatUpMsg[]; extern const u8 BattleScript_AbilityRaisesDefenderStat[]; extern const u8 BattleScript_PowderMoveNoEffect[]; @@ -842,7 +844,7 @@ extern const u8 BattleScript_EffectSkyDrop[]; extern const u8 BattleScript_EffectMeteorBeam[]; extern const u8 BattleScript_EffectCourtChange[]; extern const u8 BattleScript_EffectExtremeEvoboost[]; -extern const u8 BattleScript_EffectHitSetRemoveTerrain[]; +extern const u8 BattleScript_EffectHitSetTerrain[]; extern const u8 BattleScript_EffectDarkVoid[]; extern const u8 BattleScript_EffectVictoryDance[]; extern const u8 BattleScript_EffectTeatime[]; diff --git a/include/battle_util.h b/include/battle_util.h index 2ce91c3f25..57327176a8 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -263,7 +263,8 @@ enum ItemHoldEffect GetBattlerHoldEffect(u32 battler, bool32 checkNegating); enum ItemHoldEffect GetBattlerHoldEffectIgnoreAbility(u32 battler, bool32 checkNegating); enum ItemHoldEffect GetBattlerHoldEffectInternal(u32 battler, bool32 checkNegating, bool32 checkAbility); u32 GetBattlerHoldEffectParam(u32 battler); -bool32 IsMoveMakingContact(u32 move, u32 battlerAtk); +bool32 CanBattlerAvoidContactEffects(u32 battlerAtk, u32 battlerDef, u32 abilityAtk, enum ItemHoldEffect holdEffectAtk, u32 move); +bool32 IsMoveMakingContact(u32 battlerAtk, u32 battlerDef, u32 abilityAtk, enum ItemHoldEffect holdEffectAtk, u32 move); bool32 IsBattlerGrounded(u32 battler); u32 GetMoveSlot(u16 *moves, u32 move); u32 GetBattlerWeight(u32 battler); diff --git a/include/config/overworld.h b/include/config/overworld.h index ad95df0cf0..31d174c958 100644 --- a/include/config/overworld.h +++ b/include/config/overworld.h @@ -99,7 +99,7 @@ #define OW_ENABLE_DNS TRUE // If set to TRUE, the overworld will be tinted depending on time of day. // Object Event Shadows -#define OW_OBJECT_VANILLA_SHADOWS FALSE // In vanilla shadows in the overworld are only shown when jumping. +#define OW_OBJECT_VANILLA_SHADOWS FALSE // When FALSE, every object in the overworld has a shadow. WARNING: This means every object will take up two sprites instead of one. When TRUE, an object's shadow is only shown when jumping. // Overworld flags // To use the following features in scripting, replace the 0s with the flag ID you're assigning it to. diff --git a/include/constants/battle_move_effects.h b/include/constants/battle_move_effects.h index d081d3a163..e21387c2af 100644 --- a/include/constants/battle_move_effects.h +++ b/include/constants/battle_move_effects.h @@ -310,7 +310,7 @@ enum BattleMoveEffects EFFECT_MIND_BLOWN, // Same as EFFECT_MAX_HP_50_RECOIL but is cancelled by Damp EFFECT_CHLOROBLAST, // Same effect as EFFECT_MAX_HP_50_RECOIL but follows the same rules as EFFECT_RECOIL EFFECT_EXTREME_EVOBOOST, - EFFECT_HIT_SET_REMOVE_TERRAIN, + EFFECT_HIT_SET_TERRAIN, EFFECT_DARK_VOID, EFFECT_VICTORY_DANCE, EFFECT_TEATIME, @@ -349,6 +349,8 @@ enum BattleMoveEffects EFFECT_SPECTRAL_THIEF, EFFECT_RECOIL, EFFECT_SMACK_DOWN, + EFFECT_ICE_SPINNER, // Removes terrain unless attacker is removed from field either by fainting or ejected out + EFFECT_STEEL_ROLLER, // Will fail if there is no terrain up but removes it regardless if attacker is removed from field or not NUM_BATTLE_MOVE_EFFECTS, }; diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 2b0287be40..1da5f3b134 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -243,7 +243,7 @@ enum CmdVarious #define PARTY_SCREEN_OPTIONAL (1 << 7) // Flag for first argument to openpartyscreen -// cases for Cmd_moveend +// cases for Cmd_moveend - Order matters! enum MoveEndEffects { MOVEEND_SUM_DAMAGE, @@ -283,6 +283,7 @@ enum MoveEndEffects MOVEEND_HIT_ESCAPE, MOVEEND_OPPORTUNIST, // Occurs after other stat change items/abilities to try and copy the boosts MOVEEND_PICKPOCKET, + MOVEEND_REMOVE_TERRAIN, MOVEEND_WHITE_HERB, MOVEEND_CHANGED_ITEMS, MOVEEND_SAME_MOVE_TURNS, @@ -297,9 +298,4 @@ enum MoveEndEffects #define B_SWITCH_HIT 1 // dragon tail, circle throw #define B_SWITCH_RED_CARD 2 -// Argument labels for EFFECT_HIT_SET_REMOVE_TERRAIN -#define ARG_SET_PSYCHIC_TERRAIN 0 -#define ARG_TRY_REMOVE_TERRAIN_HIT 1 -#define ARG_TRY_REMOVE_TERRAIN_FAIL 2 - #endif // GUARD_CONSTANTS_BATTLE_SCRIPT_COMMANDS_H diff --git a/include/constants/event_objects.h b/include/constants/event_objects.h index a0ca0063dc..eacd3b3a2a 100644 --- a/include/constants/event_objects.h +++ b/include/constants/event_objects.h @@ -331,11 +331,14 @@ #define LOCALID_NONE 0 #define LOCALID_CAMERA 127 #define LOCALID_BERRY_BLENDER_PLAYER_END 240 // This will use 5 (MAX_RFU_PLAYERS) IDs ending at 240, i.e. 236-240 +#define LOCALID_FOLLOWING_POKEMON 254 #define LOCALID_PLAYER 255 -#define OBJ_EVENT_ID_FOLLOWER 0xFE -#define OBJ_EVENT_ID_NPC_FOLLOWER 0xFD +#define OBJ_EVENT_ID_FOLLOWER 0xFE +#define OBJ_EVENT_ID_NPC_FOLLOWER 0xFD // Aliases for old names. "object event id" normally refers to an index into gObjectEvents, which these are not. +// Used for link player OWs in CreateLinkPlayerSprite +#define OBJ_EVENT_ID_DYNAMIC_BASE 0xF0 #define OBJ_EVENT_ID_CAMERA LOCALID_CAMERA #define OBJ_EVENT_ID_PLAYER LOCALID_PLAYER diff --git a/include/constants/expansion.h b/include/constants/expansion.h index b0e7397b56..11822f8510 100644 --- a/include/constants/expansion.h +++ b/include/constants/expansion.h @@ -1,10 +1,10 @@ #ifndef GUARD_CONSTANTS_EXPANSION_H #define GUARD_CONSTANTS_EXPANSION_H -// Last version: 1.12.0 +// Last version: 1.12.1 #define EXPANSION_VERSION_MAJOR 1 #define EXPANSION_VERSION_MINOR 12 -#define EXPANSION_VERSION_PATCH 1 +#define EXPANSION_VERSION_PATCH 2 // FALSE if this this version of Expansion is not a tagged commit, i.e. // it contains unreleased changes. diff --git a/include/constants/flags.h b/include/constants/flags.h index 71a9116728..47a3b245e1 100644 --- a/include/constants/flags.h +++ b/include/constants/flags.h @@ -1397,6 +1397,7 @@ #define FLAG_SYS_USE_FLASH (SYSTEM_FLAGS + 0x28) #define FLAG_SYS_USE_STRENGTH (SYSTEM_FLAGS + 0x29) +// Sets abnormal weather on maps that check for it #define FLAG_SYS_WEATHER_CTRL (SYSTEM_FLAGS + 0x2A) #define FLAG_SYS_CYCLING_ROAD (SYSTEM_FLAGS + 0x2B) #define FLAG_SYS_SAFARI_MODE (SYSTEM_FLAGS + 0x2C) diff --git a/include/event_object_movement.h b/include/event_object_movement.h index 51cc2c991c..a9d1e61277 100644 --- a/include/event_object_movement.h +++ b/include/event_object_movement.h @@ -182,7 +182,7 @@ u8 GetWalkInPlaceFasterMovementAction(u32); u8 GetWalkInPlaceFastMovementAction(u32); u8 GetWalkInPlaceNormalMovementAction(u32); u8 GetWalkInPlaceSlowMovementAction(u32); -u8 GetCollisionAtCoords(struct ObjectEvent *, s16 x, s16 y, u32 dir); +u8 GetCollisionAtCoords(struct ObjectEvent *objectEvent, s16 x, s16 y, u32 dir); u32 GetObjectObjectCollidesWith(struct ObjectEvent *objectEvent, s16 x, s16 y, bool32 addCoords); void MoveCoords(u8 direction, s16 *x, s16 *y); bool8 ObjectEventIsHeldMovementActive(struct ObjectEvent *objectEvent); diff --git a/include/field_effect_helpers.h b/include/field_effect_helpers.h index 0c97da4337..65dce3dfda 100644 --- a/include/field_effect_helpers.h +++ b/include/field_effect_helpers.h @@ -22,7 +22,7 @@ void StartRevealDisguise(struct ObjectEvent *objectEvent); void StartAshFieldEffect(s16 x, s16 y, u16 metatileId, s16 delay); void SetUpReflection(struct ObjectEvent *objectEvent, struct Sprite *sprite, bool8 stillReflection); void SetUpShadow(struct ObjectEvent *objectEvent); -u32 StartFieldEffectForObjectEvent(u8, struct ObjectEvent *objectEvent); +u32 StartFieldEffectForObjectEvent(u8 fieldEffectId, struct ObjectEvent *objectEvent); u8 FindTallGrassFieldEffectSpriteId(u8 localId, u8 mapNum, u8 mapGroup, s16 x, s16 y); void UpdateRayquazaSpotlightEffect(struct Sprite *sprite); void UpdateShadowFieldEffect(struct Sprite *sprite); diff --git a/include/follower_helper.h b/include/follower_helper.h index 54a2b4c12a..2960077696 100644 --- a/include/follower_helper.h +++ b/include/follower_helper.h @@ -70,7 +70,8 @@ enum MessageCondition #define MATCH_U16(type, value1, value2) {type, {.split = {.hw = value1, .b = value2}}} #define MATCH_U8(type, v1, v2, v3) {type, {.bytes = {v1, v2, v3}}} -#define MATCH_SPECIES(species) MATCH_U24(MSG_COND_SPECIES, species) +#define MATCH_SPECIES(species) MATCH_U16(MSG_COND_SPECIES, species, 0) +#define MATCH_NOT_SPECIES(species) MATCH_U16(MSG_COND_SPECIES, species, 1) #define MATCH_TYPES(type1, type2) MATCH_U8(MSG_COND_TYPE, type1, type2, 0) // Checks that follower has *neither* of the two types #define MATCH_NOT_TYPES(type1, type2) MATCH_U8(MSG_COND_TYPE, type1, type2, TYPE_NONE | 1) @@ -118,6 +119,7 @@ enum ConditionalMessage COND_MSG_BURN, COND_MSG_DAY, COND_MSG_NIGHT, + COND_MSG_ABNORMAL_WEATHER, COND_MSG_COUNT, }; diff --git a/include/global.h b/include/global.h index 057a53b498..b58ef3518b 100644 --- a/include/global.h +++ b/include/global.h @@ -680,7 +680,8 @@ struct Roamer /*0x12*/ u8 tough; /*0x13*/ bool8 active; /*0x14*/ u8 statusB; // Stores frostbite - /*0x14*/ u8 filler[0x7]; + /*0x15*/ bool8 shiny; + /*0x16*/ u8 filler[0x6]; }; struct RamScriptData diff --git a/include/graphics.h b/include/graphics.h index 58212d67c2..262afd5337 100644 --- a/include/graphics.h +++ b/include/graphics.h @@ -2,8 +2,8 @@ #define GUARD_GRAPHICS_H // overworld -extern const u32 gSignpostWindow_Gfx[]; -extern const u32 gMessageBox_Gfx[]; +extern const u8 gSignpostWindow_Gfx[]; +extern const u8 gMessageBox_Gfx[]; extern const u16 gMessageBox_Pal[]; // pokeballs @@ -285,17 +285,17 @@ extern const u16 gBattleEnvironmentPalette_StadiumWallace[]; extern const u32 gPokedexInterface_Gfx[]; extern const u16 gPokedexBgHoenn_Pal[]; extern const u32 gPokedexMenu_Gfx[]; -extern const u8 gPokedexList_Tilemap[]; -extern const u8 gPokedexListUnderlay_Tilemap[]; -extern const u8 gPokedexStartMenuMain_Tilemap[]; -extern const u8 gPokedexStartMenuSearchResults_Tilemap[]; +extern const u32 gPokedexList_Tilemap[]; +extern const u32 gPokedexListUnderlay_Tilemap[]; +extern const u32 gPokedexStartMenuMain_Tilemap[]; +extern const u32 gPokedexStartMenuSearchResults_Tilemap[]; extern const u16 gPokedexSearchResults_Pal[]; extern const u16 gPokedexBgNational_Pal[]; -extern const u8 gPokedexInfoScreen_Tilemap[]; -extern const u8 gPokedexCryScreen_Tilemap[]; -extern const u8 gPokedexSizeScreen_Tilemap[]; -extern const u8 gPokedexScreenSelectBarMain_Tilemap[]; -extern const u8 gPokedexScreenSelectBarSubmenu_Tilemap[]; +extern const u32 gPokedexInfoScreen_Tilemap[]; +extern const u32 gPokedexCryScreen_Tilemap[]; +extern const u32 gPokedexSizeScreen_Tilemap[]; +extern const u32 gPokedexScreenSelectBarMain_Tilemap[]; +extern const u32 gPokedexScreenSelectBarSubmenu_Tilemap[]; extern const u16 gPokedexCaughtScreen_Pal[]; extern const u32 gPokedexSearchMenu_Gfx[]; extern const u32 gPokedexSearchMenuHoenn_Tilemap[]; @@ -1782,7 +1782,7 @@ extern const u32 gIntroGroudon_Gfx[]; extern const u32 gIntroGroudon_Tilemap[]; extern const u32 gIntroLegendBg_Gfx[]; extern const u32 gIntroGroudonBg_Tilemap[]; -extern const u8 ALIGNED(2) gIntro3Bg_Pal[0x200]; +extern const u16 gIntro3Bg_Pal[16][16]; extern const u32 gIntroKyogre_Gfx[]; extern const u32 gIntroKyogre_Tilemap[]; extern const u32 gIntroKyogreBg_Tilemap[]; @@ -2005,9 +2005,9 @@ extern const u8 gHealthboxElementsGfxTable[][32]; extern const u16 gNamingScreenMenu_Pal[6][16]; extern const u32 gNamingScreenMenu_Gfx[]; extern const u32 gNamingScreenBackground_Tilemap[]; -extern const u8 gNamingScreenKeyboardUpper_Tilemap[]; -extern const u8 gNamingScreenKeyboardLower_Tilemap[]; -extern const u8 gNamingScreenKeyboardSymbols_Tilemap[]; +extern const u32 gNamingScreenKeyboardUpper_Tilemap[]; +extern const u32 gNamingScreenKeyboardLower_Tilemap[]; +extern const u32 gNamingScreenKeyboardSymbols_Tilemap[]; extern const u32 gNamingScreenPageSwapFrame_Gfx[]; extern const u32 gNamingScreenBackButton_Gfx[]; extern const u32 gNamingScreenOKButton_Gfx[]; @@ -3098,7 +3098,7 @@ extern const u32 gContestAudienceTilemap[]; extern const u16 gContestInterfaceAudiencePalette[]; extern const u32 gContestInterfaceTilemap[]; extern const u32 gContestCurtainTilemap[]; -extern const u32 gContestSliderHeart_Gfx[]; +extern const u8 gContestSliderHeart_Gfx[]; extern const u32 gContestNextTurnGfx[]; extern const u16 gContestPal[]; extern const u32 gContestFaces_Gfx[]; @@ -3125,11 +3125,11 @@ extern const u16 gUsePokeblockGraph_Pal[]; extern const u16 gUsePokeblockNatureWin_Pal[]; // Berry blender -extern const u32 gBerryBlenderPlayerArrow_Gfx[]; -extern const u32 gBerryBlenderStart_Gfx[]; -extern const u32 gBerryBlenderScoreSymbols_Gfx[]; -extern const u32 gBerryBlenderParticles_Gfx[]; -extern const u32 gBerryBlenderCountdownNumbers_Gfx[]; +extern const u8 gBerryBlenderPlayerArrow_Gfx[]; +extern const u8 gBerryBlenderStart_Gfx[]; +extern const u8 gBerryBlenderScoreSymbols_Gfx[]; +extern const u8 gBerryBlenderParticles_Gfx[]; +extern const u8 gBerryBlenderCountdownNumbers_Gfx[]; extern const u16 gBerryBlenderMiscPalette[]; extern const u16 gBerryBlenderArrowPalette[]; extern const u32 gBerryBlenderCenter_Gfx[]; @@ -3360,30 +3360,30 @@ extern const u16 gMailPalette_Tropic[]; extern const u16 gMailPalette_Dream[]; extern const u16 gMailPalette_Fab[]; extern const u16 gMailPalette_Retro[]; -extern const u8 gMailTiles_Orange[]; -extern const u8 gMailTilemap_Orange[]; -extern const u8 gMailTiles_Harbor[]; -extern const u8 gMailTilemap_Harbor[]; -extern const u8 gMailTiles_Glitter[]; -extern const u8 gMailTilemap_Glitter[]; -extern const u8 gMailTiles_Mech[]; -extern const u8 gMailTilemap_Mech[]; -extern const u8 gMailTiles_Wood[]; -extern const u8 gMailTilemap_Wood[]; -extern const u8 gMailTiles_Wave[]; -extern const u8 gMailTilemap_Wave[]; -extern const u8 gMailTiles_Bead[]; -extern const u8 gMailTilemap_Bead[]; -extern const u8 gMailTiles_Shadow[]; -extern const u8 gMailTilemap_Shadow[]; -extern const u8 gMailTiles_Tropic[]; -extern const u8 gMailTilemap_Tropic[]; -extern const u8 gMailTiles_Dream[]; -extern const u8 gMailTilemap_Dream[]; -extern const u8 gMailTiles_Fab[]; -extern const u8 gMailTilemap_Fab[]; -extern const u8 gMailTiles_Retro[]; -extern const u8 gMailTilemap_Retro[]; +extern const u32 gMailTiles_Orange[]; +extern const u32 gMailTilemap_Orange[]; +extern const u32 gMailTiles_Harbor[]; +extern const u32 gMailTilemap_Harbor[]; +extern const u32 gMailTiles_Glitter[]; +extern const u32 gMailTilemap_Glitter[]; +extern const u32 gMailTiles_Mech[]; +extern const u32 gMailTilemap_Mech[]; +extern const u32 gMailTiles_Wood[]; +extern const u32 gMailTilemap_Wood[]; +extern const u32 gMailTiles_Wave[]; +extern const u32 gMailTilemap_Wave[]; +extern const u32 gMailTiles_Bead[]; +extern const u32 gMailTilemap_Bead[]; +extern const u32 gMailTiles_Shadow[]; +extern const u32 gMailTilemap_Shadow[]; +extern const u32 gMailTiles_Tropic[]; +extern const u32 gMailTilemap_Tropic[]; +extern const u32 gMailTiles_Dream[]; +extern const u32 gMailTilemap_Dream[]; +extern const u32 gMailTiles_Fab[]; +extern const u32 gMailTilemap_Fab[]; +extern const u32 gMailTiles_Retro[]; +extern const u32 gMailTilemap_Retro[]; extern const u8 gMonMarkingsMenu_Gfx[]; extern const u16 gMonMarkingsMenu_Pal[]; diff --git a/include/move.h b/include/move.h index 16ede9d809..52d4d0f072 100644 --- a/include/move.h +++ b/include/move.h @@ -498,6 +498,11 @@ static inline u32 GetMoveProtectMethod(u32 moveId) return gMovesInfo[SanitizeMoveId(moveId)].argument.protectMethod; } +static inline u32 GetMoveTerrainFlag(u32 moveId) +{ + return gMovesInfo[SanitizeMoveId(moveId)].argument.moveProperty; +} + static inline u32 GetMoveEffectArg_Status(u32 moveId) { return gMovesInfo[SanitizeMoveId(moveId)].argument.status; diff --git a/include/trade.h b/include/trade.h index 33191f0ac6..24de69b036 100644 --- a/include/trade.h +++ b/include/trade.h @@ -7,6 +7,7 @@ extern struct Mail gTradeMail[PARTY_SIZE]; extern u8 gSelectedTradeMonPositions[2]; +extern const u16 gTradePlatform_Tilemap[]; extern const struct WindowTemplate gTradeEvolutionSceneYesNoWindowTemplate; s32 GetGameProgressForLinkTrade(void); diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 18c140c2d2..1b8520fa5d 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -2047,7 +2047,11 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case EFFECT_COPYCAT: case EFFECT_MIRROR_MOVE: - return AI_CheckBadMove(battlerAtk, battlerDef, predictedMove, score); + if (predictedMove && GetMoveEffect(predictedMove) != GetMoveEffect(move)) + return AI_CheckBadMove(battlerAtk, battlerDef, predictedMove, score); + else + ADJUST_SCORE(-10); + break; case EFFECT_FLOWER_SHIELD: if (!IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GRASS) && !(isDoubleBattle && IS_BATTLER_OF_TYPE(BATTLE_PARTNER(battlerAtk), TYPE_GRASS))) @@ -2312,7 +2316,10 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); break; case EFFECT_NATURE_POWER: - return AI_CheckBadMove(battlerAtk, battlerDef, GetNaturePowerMove(battlerAtk), score); + predictedMove = GetNaturePowerMove(battlerAtk); + if (GetMoveEffect(predictedMove) != GetMoveEffect(move)) + return AI_CheckBadMove(battlerAtk, battlerDef, GetNaturePowerMove(battlerAtk), score); + break; case EFFECT_TAUNT: if (gDisableStructs[battlerDef].tauntTimer > 0 || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) @@ -2492,7 +2499,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { if (AI_IsSlower(battlerAtk, battlerDef, move)) ADJUST_SCORE(-10); // Target is predicted to go first, Me First will fail - else + else if (GetMoveEffect(predictedMove) != GetMoveEffect(move)) return AI_CheckBadMove(battlerAtk, battlerDef, predictedMove, score); } else @@ -3852,7 +3859,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(DECENT_EFFECT); break; case EFFECT_MIRROR_MOVE: - if (predictedMove != MOVE_NONE) + if (predictedMove && GetMoveEffect(predictedMove) != GetMoveEffect(move)) return AI_CheckViability(battlerAtk, battlerDef, predictedMove, score); break; case EFFECT_ATTACK_UP: @@ -4116,7 +4123,8 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) case EFFECT_MIMIC: if (AI_IsFaster(battlerAtk, battlerDef, move)) { - if (gLastMoves[battlerDef] != MOVE_NONE && gLastMoves[battlerDef] != 0xFFFF) + if (gLastMoves[battlerDef] != MOVE_NONE && gLastMoves[battlerDef] != 0xFFFF + && (GetMoveEffect(gLastMoves[battlerDef]) != GetMoveEffect(move))) return AI_CheckViability(battlerAtk, battlerDef, gLastMoves[battlerDef], score); } break; diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 93801927bd..2d7136eefc 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -548,8 +548,8 @@ bool32 IsDamageMoveUnusable(u32 battlerAtk, u32 battlerDef, u32 move, u32 moveTy if (!IS_BATTLER_OF_TYPE(battlerAtk, GetMoveArgType(move))) return TRUE; break; - case EFFECT_HIT_SET_REMOVE_TERRAIN: - if (!(gFieldStatuses & STATUS_FIELD_TERRAIN_ANY) && GetMoveEffectArg_MoveProperty(move) == ARG_TRY_REMOVE_TERRAIN_FAIL) + case EFFECT_STEEL_ROLLER: + if (!(gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)) return TRUE; break; case EFFECT_POLTERGEIST: @@ -827,7 +827,7 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u simDamage.median = ApplyModifiersAfterDmgRoll(simDamage.median, &damageCalcData, effectivenessMultiplier, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef]); - + simDamage.maximum = GetDamageByRollType(damage, DMG_ROLL_HIGHEST); simDamage.maximum = ApplyModifiersAfterDmgRoll(simDamage.maximum, &damageCalcData, effectivenessMultiplier, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index 66ef28abb1..269d1ce3fe 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -2332,7 +2332,8 @@ static void PlayerHandleOneReturnValue_Duplicate(u32 battler) static void PlayerHandleIntroTrainerBallThrow(u32 battler) { - const u16 *trainerPal = gTrainerBacksprites[gSaveBlock2Ptr->playerGender].palette.data; + const u32 paletteIndex = PlayerGetTrainerBackPicId(); + const u16 *trainerPal = gTrainerBacksprites[paletteIndex].palette.data; BtlController_HandleIntroTrainerBallThrow(battler, 0xD6F8, trainerPal, 31, Intro_TryShinyAnimShowHealthbox); } diff --git a/src/battle_controller_safari.c b/src/battle_controller_safari.c index 4a1fc3888c..9311c2f7a1 100644 --- a/src/battle_controller_safari.c +++ b/src/battle_controller_safari.c @@ -11,6 +11,7 @@ #include "main.h" #include "m4a.h" #include "palette.h" +#include "party_menu.h" #include "pokeball.h" #include "pokeblock.h" #include "pokemon.h" @@ -22,6 +23,7 @@ #include "window.h" #include "line_break.h" #include "constants/battle_anim.h" +#include "constants/party_menu.h" #include "constants/songs.h" #include "constants/trainers.h" #include "constants/rgb.h" @@ -33,6 +35,7 @@ static void SafariHandlePrintString(u32 battler); static void SafariHandlePrintSelectionString(u32 battler); static void SafariHandleChooseAction(u32 battler); static void SafariHandleChooseItem(u32 battler); +static void SafariHandleChoosePokemon(u32 battler); static void SafariHandleStatusIconUpdate(u32 battler); static void SafariHandleFaintingCry(u32 battler); static void SafariHandleIntroTrainerBallThrow(u32 battler); @@ -42,6 +45,7 @@ static void SafariHandleEndLinkBattle(u32 battler); static void SafariBufferRunCommand(u32 battler); static void SafariBufferExecCompleted(u32 battler); static void CompleteWhenChosePokeblock(u32 battler); +static void WaitForMonSelection(u32 battler); static void (*const sSafariBufferCommands[CONTROLLER_CMDS_COUNT])(u32 battler) = { @@ -67,7 +71,7 @@ static void (*const sSafariBufferCommands[CONTROLLER_CMDS_COUNT])(u32 battler) = [CONTROLLER_YESNOBOX] = BtlController_Empty, [CONTROLLER_CHOOSEMOVE] = BtlController_Empty, [CONTROLLER_OPENBAG] = SafariHandleChooseItem, - [CONTROLLER_CHOOSEPOKEMON] = BtlController_Empty, + [CONTROLLER_CHOOSEPOKEMON] = SafariHandleChoosePokemon, [CONTROLLER_23] = BtlController_Empty, [CONTROLLER_HEALTHBARUPDATE] = BtlController_Empty, [CONTROLLER_EXPUPDATE] = BtlController_Empty, @@ -225,6 +229,41 @@ static void CompleteWhenChosePokeblock(u32 battler) } } +static void OpenPartyMenuToChooseMon(u32 battler) +{ + if (!gPaletteFade.active) + { + gBattlerControllerFuncs[battler] = WaitForMonSelection; + u8 caseId = gTasks[gBattleControllerData[battler]].data[0]; + DestroyTask(gBattleControllerData[battler]); + FreeAllWindowBuffers(); + OpenPartyMenuInBattle(caseId); + } +} + +static void WaitForMonSelection(u32 battler) +{ + if (gMain.callback2 == BattleMainCB2 && !gPaletteFade.active) + { + if (gPartyMenuUseExitCallback == TRUE) + BtlController_EmitChosenMonReturnValue(battler, B_COMM_TO_ENGINE, gSelectedMonPartyId, gBattlePartyCurrentOrder); + else + BtlController_EmitChosenMonReturnValue(battler, B_COMM_TO_ENGINE, PARTY_SIZE, NULL); + + if ((gBattleResources->bufferA[battler][1] & 0xF) == 1) + { + if (gBattleTypeFlags & BATTLE_TYPE_LINK) + { + gBattle_BG0_X = 0; + gBattle_BG0_Y = 0; + BattlePutTextOnWindow(gText_LinkStandby, B_WIN_MSG); + } + } + + SafariBufferExecCompleted(battler); + } +} + static void SafariBufferExecCompleted(u32 battler) { gBattlerControllerFuncs[battler] = SafariBufferRunCommand; @@ -305,6 +344,31 @@ static void SafariHandleChooseItem(u32 battler) gBattlerInMenuId = battler; } +static void SafariHandleChoosePokemon(u32 battler) +{ + for (s32 i = 0; i < ARRAY_COUNT(gBattlePartyCurrentOrder); i++) + gBattlePartyCurrentOrder[i] = gBattleResources->bufferA[battler][4 + i]; + + if (gBattleTypeFlags & BATTLE_TYPE_ARENA && (gBattleResources->bufferA[battler][1] & 0xF) != PARTY_ACTION_CANT_SWITCH + && (gBattleResources->bufferA[battler][1] & 0xF) != PARTY_ACTION_CHOOSE_FAINTED_MON + && (gBattleResources->bufferA[battler][1] & 0xF) != PARTY_ACTION_SEND_MON_TO_BOX) + { + BtlController_EmitChosenMonReturnValue(battler, B_COMM_TO_ENGINE, gBattlerPartyIndexes[battler] + 1, gBattlePartyCurrentOrder); + SafariBufferExecCompleted(battler); + } + else + { + gBattleControllerData[battler] = CreateTask(TaskDummy, 0xFF); + gTasks[gBattleControllerData[battler]].data[0] = gBattleResources->bufferA[battler][1] & 0xF; + *(&gBattleStruct->battlerPreventingSwitchout) = gBattleResources->bufferA[battler][1] >> 4; + *(&gBattleStruct->prevSelectedPartySlot) = gBattleResources->bufferA[battler][2]; + *(&gBattleStruct->abilityPreventingSwitchout) = (gBattleResources->bufferA[battler][3] & 0xFF) | (gBattleResources->bufferA[battler][7] << 8); + BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 0x10, RGB_BLACK); + gBattlerControllerFuncs[battler] = OpenPartyMenuToChooseMon; + gBattlerInMenuId = battler; + } +} + static void SafariHandleStatusIconUpdate(u32 battler) { UpdateHealthboxAttribute(gHealthboxSpriteIds[battler], GetBattlerMon(battler), HEALTHBOX_SAFARI_BALLS_TEXT); diff --git a/src/battle_controllers.c b/src/battle_controllers.c index 4faec49613..ec3dfa7426 100644 --- a/src/battle_controllers.c +++ b/src/battle_controllers.c @@ -1415,8 +1415,11 @@ void BtlController_EmitChosenMonReturnValue(u32 battler, u32 bufferId, u8 partyI gBattleResources->transferBuffer[0] = CONTROLLER_CHOSENMONRETURNVALUE; gBattleResources->transferBuffer[1] = partyId; - for (i = 0; i < (int)ARRAY_COUNT(gBattlePartyCurrentOrder); i++) - gBattleResources->transferBuffer[2 + i] = battlePartyOrder[i]; + if (battlePartyOrder != NULL) + { + for (i = 0; i < (int)ARRAY_COUNT(gBattlePartyCurrentOrder); i++) + gBattleResources->transferBuffer[2 + i] = battlePartyOrder[i]; + } PrepareBufferDataTransfer(battler, bufferId, gBattleResources->transferBuffer, 5); } diff --git a/src/battle_main.c b/src/battle_main.c index bdd0c33a54..4614a2cf22 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -583,6 +583,12 @@ static void CB2_InitBattleInternal(void) TryFormChange(i, B_SIDE_OPPONENT, FORM_CHANGE_BEGIN_BATTLE); } + if (TESTING) + { + gPlayerPartyCount = CalculatePartyCount(gPlayerParty); + gEnemyPartyCount = CalculatePartyCount(gEnemyParty); + } + gBattleCommunication[MULTIUSE_STATE] = 0; } @@ -3182,6 +3188,7 @@ void SwitchInClearSetData(u32 battler) gDisableStructs[battler].perishSongTimer = disableStructCopy.perishSongTimer; gDisableStructs[battler].battlerPreventingEscape = disableStructCopy.battlerPreventingEscape; gDisableStructs[battler].embargoTimer = disableStructCopy.embargoTimer; + gDisableStructs[battler].healBlockTimer = disableStructCopy.healBlockTimer; } else if (effect == EFFECT_SHED_TAIL) { diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index bd2ef1509e..25edc5d759 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -66,6 +66,8 @@ #include "constants/pokemon.h" #include "config/battle.h" #include "data/battle_move_effects.h" +#include "follower_npc.h" +#include "load_save.h" // table to avoid ugly powing on gba (courtesy of doesnt) // this returns (i^2.5)/4 @@ -494,7 +496,7 @@ static void Cmd_tryconversiontypechange(void); static void Cmd_givepaydaymoney(void); static void Cmd_setlightscreen(void); static void Cmd_tryKO(void); -static void Cmd_unused_0x94(void); +static void Cmd_checknonvolatiletrigger(void); static void Cmd_copybidedmg(void); static void Cmd_unused_96(void); static void Cmd_tryinfatuating(void); @@ -753,7 +755,7 @@ void (*const gBattleScriptingCommandsTable[])(void) = Cmd_givepaydaymoney, //0x91 Cmd_setlightscreen, //0x92 Cmd_tryKO, //0x93 - Cmd_unused_0x94, //0x94 + Cmd_checknonvolatiletrigger, //0x94 Cmd_copybidedmg, //0x95 Cmd_unused_96, //0x96 Cmd_tryinfatuating, //0x97 @@ -1420,7 +1422,7 @@ static void Cmd_attackcanceler(void) && effect != EFFECT_COUNTER && effect != EFFECT_UPPER_HAND) { - if (IsMoveMakingContact(gCurrentMove, gBattlerAttacker)) + if (!CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker, TRUE), gCurrentMove)) gProtectStructs[gBattlerAttacker].touchedProtectLike = TRUE; CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_ATTACKCANCELLER_CHECK); gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED; @@ -1436,7 +1438,7 @@ static void Cmd_attackcanceler(void) gBattleCommunication[MISS_TYPE] = B_MSG_PROTECTED; gBattlescriptCurrInstr = cmd->nextInstr; } - else if (gProtectStructs[gBattlerTarget].beakBlastCharge && IsMoveMakingContact(gCurrentMove, gBattlerAttacker)) + else if (gProtectStructs[gBattlerTarget].beakBlastCharge && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker, TRUE), gCurrentMove)) { gProtectStructs[gBattlerAttacker].touchedProtectLike = TRUE; gBattlescriptCurrInstr = cmd->nextInstr; @@ -3800,7 +3802,12 @@ void SetMoveEffect(bool32 primary, bool32 certain) } break; case MOVE_EFFECT_BUG_BITE: - if (GetItemPocket(gBattleMons[gEffectBattler].item) == POCKET_BERRIES + if (GetBattlerHoldEffect(gEffectBattler, TRUE) == HOLD_EFFECT_JABOCA_BERRY) + { + // jaboca berry triggers instead of being stolen + gBattlescriptCurrInstr++; + } + else if (GetItemPocket(gBattleMons[gEffectBattler].item) == POCKET_BERRIES && battlerAbility != ABILITY_STICKY_HOLD) { // target loses their berry @@ -6561,6 +6568,24 @@ static void Cmd_moveend(void) effect = TRUE; gBattleScripting.moveendState++; break; + case MOVEEND_SYMBIOSIS: + for (i = 0; i < gBattlersCount; i++) + { + if ((gSpecialStatuses[i].berryReduced + || (B_SYMBIOSIS_GEMS >= GEN_7 && gSpecialStatuses[i].gemBoost)) + && TryTriggerSymbiosis(i, BATTLE_PARTNER(i))) + { + BestowItem(BATTLE_PARTNER(i), i); + gLastUsedAbility = gBattleMons[BATTLE_PARTNER(i)].ability; + gBattleScripting.battler = gBattlerAbility = BATTLE_PARTNER(i); + gBattlerAttacker = i; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_SymbiosisActivates; + effect = TRUE; + } + } + gBattleScripting.moveendState++; + break; case MOVEEND_FIRST_MOVE_BLOCK: if ((gSpecialStatuses[gBattlerAttacker].parentalBondState == PARENTAL_BOND_1ST_HIT && IsBattlerAlive(gBattlerTarget)) || gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT @@ -6633,12 +6658,6 @@ static void Cmd_moveend(void) else gBattleScripting.moveendState++; break; - case MOVEEND_KINGSROCK: // King's rock - // These effects will occur at each hit in a multi-strike move - if (ItemBattleEffects(ITEMEFFECT_KINGSROCK, 0, FALSE)) - effect = TRUE; - gBattleScripting.moveendState++; - break; case MOVEEND_ATTACKER_INVISIBLE: // make attacker sprite invisible if (gStatuses3[gBattlerAttacker] & (STATUS3_SEMI_INVULNERABLE) && gHitMarker & (HITMARKER_NO_ANIMATIONS | HITMARKER_DISABLE_ANIMATION)) @@ -6676,6 +6695,12 @@ static void Cmd_moveend(void) } gBattleScripting.moveendState++; break; + case MOVEEND_KINGSROCK: // King's rock + // These effects will occur at each hit in a multi-strike move + if (ItemBattleEffects(ITEMEFFECT_KINGSROCK, 0, FALSE)) + effect = TRUE; + gBattleScripting.moveendState++; + break; case MOVEEND_SUBSTITUTE: for (i = 0; i < gBattlersCount; i++) { @@ -6812,6 +6837,37 @@ static void Cmd_moveend(void) } gBattleScripting.moveendState++; break; + case MOVEEND_DEFROST: + if (gBattleMons[gBattlerTarget].status1 & STATUS1_FREEZE + && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerAlive(gBattlerTarget) + && gBattlerAttacker != gBattlerTarget + && (moveType == TYPE_FIRE || CanBurnHitThaw(gCurrentMove)) + && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) + { + gBattleMons[gBattlerTarget].status1 &= ~STATUS1_FREEZE; + BtlController_EmitSetMonData(gBattlerTarget, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].status1), &gBattleMons[gBattlerTarget].status1); + MarkBattlerForControllerExec(gBattlerTarget); + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_DefrostedViaFireMove; + effect = TRUE; + } + if (gBattleMons[gBattlerTarget].status1 & STATUS1_FROSTBITE + && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerAlive(gBattlerTarget) + && gBattlerAttacker != gBattlerTarget + && MoveThawsUser(originallyUsedMove) + && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) + { + gBattleMons[gBattlerTarget].status1 &= ~STATUS1_FROSTBITE; + BtlController_EmitSetMonData(gBattlerTarget, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].status1), &gBattleMons[gBattlerTarget].status1); + MarkBattlerForControllerExec(gBattlerTarget); + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_FrostbiteHealedViaFireMove; + effect = TRUE; + } + gBattleScripting.moveendState++; + break; case MOVEEND_NEXT_TARGET: // For moves hitting two opposing Pokemon. { u16 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove); @@ -6935,36 +6991,6 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; } - case MOVEEND_DEFROST: - if (gBattleMons[gBattlerTarget].status1 & STATUS1_FREEZE - && IsBattlerTurnDamaged(gBattlerTarget) - && IsBattlerAlive(gBattlerTarget) - && gBattlerAttacker != gBattlerTarget - && (moveType == TYPE_FIRE || CanBurnHitThaw(gCurrentMove)) - && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) - { - gBattleMons[gBattlerTarget].status1 &= ~STATUS1_FREEZE; - BtlController_EmitSetMonData(gBattlerTarget, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].status1), &gBattleMons[gBattlerTarget].status1); - MarkBattlerForControllerExec(gBattlerTarget); - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_DefrostedViaFireMove; - effect = TRUE; - } - if (gBattleMons[gBattlerTarget].status1 & STATUS1_FROSTBITE - && IsBattlerAlive(gBattlerTarget) - && gBattlerAttacker != gBattlerTarget - && MoveThawsUser(originallyUsedMove) - && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) - { - gBattleMons[gBattlerTarget].status1 &= ~STATUS1_FROSTBITE; - BtlController_EmitSetMonData(gBattlerTarget, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].status1), &gBattleMons[gBattlerTarget].status1); - MarkBattlerForControllerExec(gBattlerTarget); - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_FrostbiteHealedViaFireMove; - effect = TRUE; - } - gBattleScripting.moveendState++; - break; case MOVEEND_SECOND_MOVE_BLOCK: if (gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) { @@ -7075,6 +7101,57 @@ static void Cmd_moveend(void) else gBattleScripting.moveendState++; break; + case MOVEEND_RED_CARD: + { + u32 redCardBattlers = 0, i; + for (i = 0; i < gBattlersCount; i++) + { + if (i == gBattlerAttacker) + continue; + if (GetBattlerHoldEffect(i, TRUE) == HOLD_EFFECT_RED_CARD) + redCardBattlers |= (1u << i); + } + if (redCardBattlers && IsBattlerAlive(gBattlerAttacker)) + { + // Since we check if battler was damaged, we don't need to check move result. + // In fact, doing so actually prevents multi-target moves from activating red card properly + u8 battlers[4] = {0, 1, 2, 3}; + SortBattlersBySpeed(battlers, FALSE); + for (i = 0; i < gBattlersCount; i++) + { + u32 battler = battlers[i]; + // Search for fastest hit pokemon with a red card + // Attacker is the one to be switched out, battler is one with red card + if (redCardBattlers & (1u << battler) + && IsBattlerAlive(battler) + && !DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove) + && IsBattlerTurnDamaged(battler) + && CanBattlerSwitch(gBattlerAttacker) + && !(moveEffect == EFFECT_HIT_SWITCH_TARGET && CanBattlerSwitch(battler))) + { + effect = TRUE; + gLastUsedItem = gBattleMons[battler].item; + SaveBattlerTarget(battler); // save battler with red card + SaveBattlerAttacker(gBattlerAttacker); + gBattleScripting.battler = battler; + gEffectBattler = gBattlerAttacker; + BattleScriptPushCursor(); + if (gBattleStruct->commanderActive[gBattlerAttacker] != SPECIES_NONE + || GetBattlerAbility(gBattlerAttacker) == ABILITY_GUARD_DOG + || GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX) + gBattlescriptCurrInstr = BattleScript_RedCardActivationNoSwitch; + else + gBattlescriptCurrInstr = BattleScript_RedCardActivates; + break; // Only fastest red card activates + } + } + } + } + if (effect) + gBattleScripting.moveendState = MOVEEND_OPPORTUNIST; + else + gBattleScripting.moveendState++; + break; case MOVEEND_EJECT_BUTTON: { // Because sorting the battlers by speed takes lots of cycles, it's better to just check if any of the battlers has the Eject items. @@ -7122,6 +7199,61 @@ static void Cmd_moveend(void) else gBattleScripting.moveendState++; break; + case MOVEEND_LIFEORB_SHELLBELL: + if (ItemBattleEffects(ITEMEFFECT_LIFEORB_SHELLBELL, 0, FALSE)) + effect = TRUE; + gBattleScripting.moveendState++; + break; + case MOVEEND_EMERGENCY_EXIT: // Special case, because moves hitting multiple opponents stop after switching out + { + // Because sorting the battlers by speed takes lots of cycles, + // we check if EE can be activated and cound how many. + u32 numEmergencyExitBattlers = 0; + u32 emergencyExitBattlers = 0; + + for (i = 0; i < gBattlersCount; i++) + { + if (EmergencyExitCanBeTriggered(i)) + { + emergencyExitBattlers |= 1u << i; + numEmergencyExitBattlers++; + } + } + + if (numEmergencyExitBattlers == 0) + { + gBattleScripting.moveendState++; + break; + } + + u8 battlers[4] = {0, 1, 2, 3}; + if (numEmergencyExitBattlers > 1) + SortBattlersBySpeed(battlers, FALSE); + + for (i = 0; i < gBattlersCount; i++) + { + u32 battler = battlers[i]; + + if (!(emergencyExitBattlers & 1u << battler)) + continue; + + effect = TRUE; + gBattleScripting.battler = battler; + BattleScriptPushCursor(); + + if (gBattleTypeFlags & BATTLE_TYPE_TRAINER || IsOnPlayerSide(battler)) + gBattlescriptCurrInstr = BattleScript_EmergencyExit; + else + gBattlescriptCurrInstr = BattleScript_EmergencyExitWild; + + break; // Only the fastest Emergency Exit / Wimp Out activates + } + } + if (effect) + gBattleScripting.moveendState = MOVEEND_OPPORTUNIST; + else + gBattleScripting.moveendState++; + break; case MOVEEND_EJECT_PACK: { // Because sorting the battlers by speed takes lots of cycles, it's better to just check if any of the battlers has the Eject items. @@ -7176,85 +7308,30 @@ static void Cmd_moveend(void) else gBattleScripting.moveendState++; break; - case MOVEEND_WHITE_HERB: - for (i = 0; i < gBattlersCount; i++) + case MOVEEND_HIT_ESCAPE: + if (moveEffect == EFFECT_HIT_ESCAPE + && !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) + && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerAlive(gBattlerAttacker) + && !NoAliveMonsForBattlerSide(gBattlerTarget)) { - if (!IsBattlerAlive(i)) - continue; - - if (GetBattlerHoldEffect(i, TRUE) == HOLD_EFFECT_WHITE_HERB - && RestoreWhiteHerbStats(i)) - { - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_WhiteHerbRet; - effect = TRUE; - break; - } + effect = TRUE; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_EffectHitEscape; } - if (!effect) - gBattleScripting.moveendState++; + gBattleScripting.moveendState++; break; - case MOVEEND_RED_CARD: - { - u32 redCardBattlers = 0, i; - for (i = 0; i < gBattlersCount; i++) - { - if (i == gBattlerAttacker) - continue; - if (GetBattlerHoldEffect(i, TRUE) == HOLD_EFFECT_RED_CARD) - redCardBattlers |= (1u << i); - } - if (redCardBattlers && IsBattlerAlive(gBattlerAttacker)) - { - // Since we check if battler was damaged, we don't need to check move result. - // In fact, doing so actually prevents multi-target moves from activating red card properly - u8 battlers[4] = {0, 1, 2, 3}; - SortBattlersBySpeed(battlers, FALSE); - for (i = 0; i < gBattlersCount; i++) - { - u32 battler = battlers[i]; - // Search for fastest hit pokemon with a red card - // Attacker is the one to be switched out, battler is one with red card - if (redCardBattlers & (1u << battler) - && IsBattlerAlive(battler) - && !DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove) - && IsBattlerTurnDamaged(battler) - && CanBattlerSwitch(gBattlerAttacker) - && !(moveEffect == EFFECT_HIT_SWITCH_TARGET && CanBattlerSwitch(battler))) - { - effect = TRUE; - gLastUsedItem = gBattleMons[battler].item; - SaveBattlerTarget(battler); // save battler with red card - SaveBattlerAttacker(gBattlerAttacker); - gBattleScripting.battler = battler; - gEffectBattler = gBattlerAttacker; - BattleScriptPushCursor(); - if (gBattleStruct->commanderActive[gBattlerAttacker] != SPECIES_NONE - || GetBattlerAbility(gBattlerAttacker) == ABILITY_GUARD_DOG - || GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX) - gBattlescriptCurrInstr = BattleScript_RedCardActivationNoSwitch; - else - gBattlescriptCurrInstr = BattleScript_RedCardActivates; - break; // Only fastest red card activates - } - } - } - } - if (effect) - gBattleScripting.moveendState = MOVEEND_OPPORTUNIST; + case MOVEEND_OPPORTUNIST: + if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, 0, 0, 0, 0)) + effect = TRUE; // it loops through all battlers, so we increment after its done with all battlers else gBattleScripting.moveendState++; break; - case MOVEEND_LIFEORB_SHELLBELL: - if (ItemBattleEffects(ITEMEFFECT_LIFEORB_SHELLBELL, 0, FALSE)) - effect = TRUE; - gBattleScripting.moveendState++; - break; case MOVEEND_PICKPOCKET: if (IsBattlerAlive(gBattlerAttacker) && gBattleMons[gBattlerAttacker].item != ITEM_NONE // Attacker must be holding an item && !(gWishFutureKnock.knockedOffMons[GetBattlerSide(gBattlerAttacker)] & (1u << gBattlerPartyIndexes[gBattlerAttacker])) // But not knocked off - && IsMoveMakingContact(gCurrentMove, gBattlerAttacker) // Pickpocket requires contact + && IsMoveMakingContact(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker, TRUE), gCurrentMove) // Pickpocket requires contact && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) // Obviously attack needs to have worked { u8 battlers[4] = {0, 1, 2, 3}; @@ -7286,89 +7363,49 @@ static void Cmd_moveend(void) } gBattleScripting.moveendState++; break; - case MOVEEND_EMERGENCY_EXIT: // Special case, because moves hitting multiple opponents stop after switching out + case MOVEEND_REMOVE_TERRAIN: + if (GetMoveEffect(gChosenMove) == EFFECT_STEEL_ROLLER // Steel Roller has to check the chosen move, Otherwise it would fail in certain cases + && IsBattlerTurnDamaged(gBattlerTarget)) { - // Because sorting the battlers by speed takes lots of cycles, - // we check if EE can be activated and cound how many. - u32 numEmergencyExitBattlers = 0; - u32 emergencyExitBattlers = 0; - - for (i = 0; i < gBattlersCount; i++) - { - if (EmergencyExitCanBeTriggered(i)) - { - emergencyExitBattlers |= 1u << i; - numEmergencyExitBattlers++; - } - } - - if (numEmergencyExitBattlers == 0) - { - gBattleScripting.moveendState++; - break; - } - - u8 battlers[4] = {0, 1, 2, 3}; - if (numEmergencyExitBattlers > 1) - SortBattlersBySpeed(battlers, FALSE); - - for (i = 0; i < gBattlersCount; i++) - { - u32 battler = battlers[i]; - - if (!(emergencyExitBattlers & 1u << battler)) - continue; - - effect = TRUE; - gBattleScripting.battler = battler; - BattleScriptPushCursor(); - - if (gBattleTypeFlags & BATTLE_TYPE_TRAINER || IsOnPlayerSide(battler)) - gBattlescriptCurrInstr = BattleScript_EmergencyExit; - else - gBattlescriptCurrInstr = BattleScript_EmergencyExitWild; - - break; // Only the fastest Emergency Exit / Wimp Out activates - } - } - if (effect) - gBattleScripting.moveendState = MOVEEND_OPPORTUNIST; - else - gBattleScripting.moveendState++; - break; - case MOVEEND_HIT_ESCAPE: - if (moveEffect == EFFECT_HIT_ESCAPE - && !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) - && IsBattlerTurnDamaged(gBattlerTarget) - && IsBattlerAlive(gBattlerAttacker) - && !NoAliveMonsForBattlerSide(gBattlerTarget)) - { - effect = TRUE; BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_EffectHitEscape; + gBattlescriptCurrInstr = BattleScript_RemoveTerrain; + effect = TRUE; + } + else if (moveEffect == EFFECT_ICE_SPINNER + && IsBattlerAlive(gBattlerAttacker) + && IsBattlerTurnDamaged(gBattlerTarget)) + { + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_RemoveTerrain; + effect = TRUE; } gBattleScripting.moveendState++; break; - case MOVEEND_OPPORTUNIST: - if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, 0, 0, 0, 0)) - effect = TRUE; // it loops through all battlers, so we increment after its done with all battlers - else - gBattleScripting.moveendState++; - break; - case MOVEEND_SYMBIOSIS: + case MOVEEND_WHITE_HERB: for (i = 0; i < gBattlersCount; i++) { - if ((gSpecialStatuses[i].berryReduced - || (B_SYMBIOSIS_GEMS >= GEN_7 && gSpecialStatuses[i].gemBoost)) - && TryTriggerSymbiosis(i, BATTLE_PARTNER(i))) + if (!IsBattlerAlive(i)) + continue; + + if (GetBattlerHoldEffect(i, TRUE) == HOLD_EFFECT_WHITE_HERB + && RestoreWhiteHerbStats(i)) { - BestowItem(BATTLE_PARTNER(i), i); - gLastUsedAbility = gBattleMons[BATTLE_PARTNER(i)].ability; - gBattleScripting.battler = gBattlerAbility = BATTLE_PARTNER(i); - gBattlerAttacker = i; BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_SymbiosisActivates; + gBattlescriptCurrInstr = BattleScript_WhiteHerbRet; effect = TRUE; + break; + } + } + if (!effect) + gBattleScripting.moveendState++; + break; + case MOVEEND_CHANGED_ITEMS: + for (i = 0; i < gBattlersCount; i++) + { + if (gBattleStruct->changedItems[i] != ITEM_NONE) + { + gBattleMons[i].item = gBattleStruct->changedItems[i]; + gBattleStruct->changedItems[i] = ITEM_NONE; } } gBattleScripting.moveendState++; @@ -7382,17 +7419,6 @@ static void Cmd_moveend(void) gBattleStruct->sameMoveTurns[gBattlerAttacker]++; gBattleScripting.moveendState++; break; - case MOVEEND_CHANGED_ITEMS: - for (i = 0; i < gBattlersCount; i++) - { - if (gBattleStruct->changedItems[i] != ITEM_NONE) - { - gBattleMons[i].item = gBattleStruct->changedItems[i]; - gBattleStruct->changedItems[i] = ITEM_NONE; - } - } - gBattleScripting.moveendState++; - break; case MOVEEND_CLEAR_BITS: // Clear/Set bits for things like using a move for all targets and all hits. if (gSpecialStatuses[gBattlerAttacker].instructedChosenTarget) gBattleStruct->moveTarget[gBattlerAttacker] = gSpecialStatuses[gBattlerAttacker].instructedChosenTarget & 0x3; @@ -9181,7 +9207,6 @@ static bool32 TrySymbiosis(u32 battler, u32 itemId) BestowItem(BATTLE_PARTNER(battler), battler); gLastUsedAbility = gBattleMons[BATTLE_PARTNER(battler)].ability; gBattleScripting.battler = gBattlerAbility = BATTLE_PARTNER(battler); - gBattlerAttacker = battler; BattleScriptPush(gBattlescriptCurrInstr + 2); gBattlescriptCurrInstr = BattleScript_SymbiosisActivates; return TRUE; @@ -11751,9 +11776,14 @@ static void Cmd_jumpifnexttargetvalid(void) } if (gBattlerTarget >= gBattlersCount) + { + gBattlerTarget = gBattlersCount - 1; gBattlescriptCurrInstr = cmd->nextInstr; + } else + { gBattlescriptCurrInstr = jumpInstr; + } } static void Cmd_tryhealhalfhealth(void) @@ -12582,12 +12612,13 @@ static void Cmd_trynonvolatilestatus(void) CMD_ARGS(); bool32 canInflictStatus = TRUE; - if (!CanSetNonVolatileStatus(gBattlerAttacker, - gBattlerTarget, - GetBattlerAbility(gBattlerAttacker), - GetBattlerAbility(gBattlerTarget), - GetMoveNonVolatileStatus(gCurrentMove), - STATUS_RUN_SCRIPT)) + if (!CanSetNonVolatileStatus( + gBattlerAttacker, + gBattlerTarget, + GetBattlerAbility(gBattlerAttacker), + GetBattlerAbility(gBattlerTarget), + GetMoveNonVolatileStatus(gCurrentMove), + STATUS_RUN_SCRIPT)) canInflictStatus = FALSE; if (canInflictStatus && DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove)) @@ -13036,8 +13067,22 @@ static void Cmd_tryKO(void) #undef FOCUS_BANDED #undef AFFECTION_ENDURED -static void Cmd_unused_0x94(void) +static void Cmd_checknonvolatiletrigger(void) { + CMD_ARGS(u16 nonVolatile, const u8 *failInstr); + + if (!CanSetNonVolatileStatus( + gBattlerAttacker, + gBattlerTarget, + GetBattlerAbility(gBattlerAttacker), + GetBattlerAbility(gBattlerTarget), + cmd->nonVolatile, + STATUS_CHECK_TRIGGER)) + gBattlescriptCurrInstr = cmd->failInstr; + else if (DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove)) + gBattlescriptCurrInstr = cmd->failInstr; + else + gBattlescriptCurrInstr = cmd->nextInstr; } static void Cmd_copybidedmg(void) @@ -14485,6 +14530,8 @@ static void Cmd_selectfirstvalidtarget(void) if (IsBattlerAlive(gBattlerTarget)) break; } + if (gBattlerTarget >= gBattlersCount) + gBattlerTarget = 0; gBattlescriptCurrInstr = cmd->nextInstr; } @@ -16024,6 +16071,9 @@ static void Cmd_givecaughtmon(void) { CMD_ARGS(const u8 *passInstr); enum GiveCaughtMonStates state = gBattleCommunication[MULTIUSE_STATE]; + // Restore players party in order to handle properly the case when a wild mon is caught. + if (IsNPCFollowerWildBattle()) + LoadPlayerParty(); switch (state) { @@ -16161,6 +16211,9 @@ static void Cmd_givecaughtmon(void) gBattlescriptCurrInstr = cmd->nextInstr; break; } + // Save the player's party again to not interferes with RestorePartyAfterFollowerNPCBattle() called after battle. + if (IsNPCFollowerWildBattle()) + SavePlayerParty(); } static void Cmd_trysetcaughtmondexflags(void) @@ -17248,17 +17301,7 @@ void BS_ApplySaltCure(void) gBattlescriptCurrInstr = cmd->nextInstr; } -void BS_JumpIfMovePropertyArgument(void) -{ - NATIVE_ARGS(u8 argument, const u8 *jumpInstr); - - if (GetMoveEffectArg_MoveProperty(gCurrentMove) == cmd->argument) - gBattlescriptCurrInstr = cmd->jumpInstr; - else - gBattlescriptCurrInstr = cmd->nextInstr; -} - -void BS_SetRemoveTerrain(void) +void BS_SetTerrain(void) { NATIVE_ARGS(const u8 *jumpInstr); u32 statusFlag = 0; @@ -17281,48 +17324,19 @@ void BS_SetRemoveTerrain(void) statusFlag = STATUS_FIELD_PSYCHIC_TERRAIN; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_PSYCHIC; break; - case EFFECT_HIT_SET_REMOVE_TERRAIN: - switch (GetMoveEffectArg_MoveProperty(gCurrentMove)) - { - case ARG_SET_PSYCHIC_TERRAIN: // Genesis Supernova - statusFlag = STATUS_FIELD_PSYCHIC_TERRAIN; - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_PSYCHIC; - break; - case ARG_TRY_REMOVE_TERRAIN_HIT: // Splintered Stormshards - case ARG_TRY_REMOVE_TERRAIN_FAIL: // Steel Roller - if (!(gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)) - { - // No terrain to remove, jump to battle script pointer. - gBattlescriptCurrInstr = cmd->jumpInstr; - } - else - { - // Remove all terrains. - RemoveAllTerrains(); - gBattlescriptCurrInstr = cmd->nextInstr; - } - return; - default: - break; - } + case EFFECT_HIT_SET_TERRAIN: + statusFlag = GetMoveTerrainFlag(gCurrentMove); + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_PSYCHIC; break; default: break; } + enum ItemHoldEffect atkHoldEffect = GetBattlerHoldEffect(gBattlerAttacker, TRUE); - if (gFieldStatuses & statusFlag || statusFlag == 0) - { - gBattlescriptCurrInstr = cmd->jumpInstr; - } - else - { - enum ItemHoldEffect atkHoldEffect = GetBattlerHoldEffect(gBattlerAttacker, TRUE); - - gFieldStatuses &= ~STATUS_FIELD_TERRAIN_ANY; - gFieldStatuses |= statusFlag; - gFieldTimers.terrainTimer = gBattleTurnCounter + (atkHoldEffect == HOLD_EFFECT_TERRAIN_EXTENDER) ? 8 : 5; - gBattlescriptCurrInstr = cmd->nextInstr; - } + gFieldStatuses &= ~STATUS_FIELD_TERRAIN_ANY; + gFieldStatuses |= statusFlag; + gFieldTimers.terrainTimer = gBattleTurnCounter + (atkHoldEffect == HOLD_EFFECT_TERRAIN_EXTENDER) ? 8 : 5; + gBattlescriptCurrInstr = cmd->nextInstr; } void BS_JumpIfTerrainAffected(void) diff --git a/src/battle_transition.c b/src/battle_transition.c index fe7188203b..c43837a5a0 100644 --- a/src/battle_transition.c +++ b/src/battle_transition.c @@ -1048,6 +1048,9 @@ static void Task_BattleTransition(u8 taskId) static bool8 Transition_StartIntro(struct Task *task) { SetWeatherScreenFadeOut(); + // cause all shadow sprites to destroy themselves, + // freeing up sprite slots for the transition + gWeatherPtr->noShadows = TRUE; CpuCopy32(gPlttBufferFaded, gPlttBufferUnfaded, PLTT_SIZE); if (sTasks_Intro[task->tTransitionId] != NULL) { diff --git a/src/battle_util.c b/src/battle_util.c index ba63bf4fe9..4f0ffcf962 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4465,8 +4465,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (!(gBattleStruct->moveResultFlags[battler] & MOVE_RESULT_NO_EFFECT) && IsBattlerAlive(gBattlerAttacker) && IsBattlerTurnDamaged(gBattlerTarget) - && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS - && IsMoveMakingContact(move, gBattlerAttacker) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker, TRUE), move) && gDisableStructs[gBattlerAttacker].overwrittenAbility != GetBattlerAbility(gBattlerTarget) && gBattleMons[gBattlerAttacker].ability != ABILITY_MUMMY && gBattleMons[gBattlerAttacker].ability != ABILITY_LINGERING_AROMA @@ -4490,8 +4489,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (!(gBattleStruct->moveResultFlags[battler] & MOVE_RESULT_NO_EFFECT) && IsBattlerAlive(gBattlerAttacker) && IsBattlerTurnDamaged(gBattlerTarget) - && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS - && IsMoveMakingContact(move, gBattlerAttacker) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker, TRUE), move) && !(GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX) && !gAbilitiesInfo[gBattleMons[gBattlerAttacker].ability].cantBeSwapped) { @@ -4547,8 +4545,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && (CompareStat(gBattlerAttacker, STAT_SPEED, MIN_STAT_STAGE, CMP_GREATER_THAN) || GetBattlerAbility(gBattlerAttacker) == ABILITY_MIRROR_ARMOR) && !gProtectStructs[gBattlerAttacker].confusionSelfDmg && IsBattlerTurnDamaged(gBattlerTarget) - && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS - && IsMoveMakingContact(move, gBattlerAttacker)) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker, TRUE), move)) { SET_STATCHANGER(STAT_SPEED, 1, TRUE); PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); @@ -4564,8 +4561,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && IsBattlerAlive(gBattlerAttacker) && !gProtectStructs[gBattlerAttacker].confusionSelfDmg && IsBattlerTurnDamaged(gBattlerTarget) - && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS - && IsMoveMakingContact(move, gBattlerAttacker)) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker, TRUE), move)) { gBattleStruct->moveDamage[gBattlerAttacker] = GetNonDynamaxMaxHP(gBattlerAttacker) / (B_ROUGH_SKIN_DMG >= GEN_4 ? 8 : 16); if (gBattleStruct->moveDamage[gBattlerAttacker] == 0) @@ -4580,8 +4576,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (!(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) && !IsBattlerAlive(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker) - && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS - && IsMoveMakingContact(move, gBattlerAttacker)) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker, TRUE), move)) { if ((battler = IsAbilityOnField(ABILITY_DAMP))) { @@ -4650,8 +4645,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && !gProtectStructs[gBattlerAttacker].confusionSelfDmg && IsBattlerTurnDamaged(gBattlerTarget) && CanBeSlept(gBattlerAttacker, gBattlerTarget, ability, NOT_BLOCKED_BY_SLEEP_CLAUSE) - && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS - && IsMoveMakingContact(move, gBattlerAttacker)) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker, TRUE), move)) { if (IsSleepClauseEnabled()) gBattleStruct->battlerState[gBattlerAttacker].sleepClauseEffectExempt = TRUE; @@ -4674,8 +4668,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && !gProtectStructs[gBattlerAttacker].confusionSelfDmg && IsBattlerTurnDamaged(gBattlerTarget) && CanBePoisoned(gBattlerTarget, gBattlerAttacker, gLastUsedAbility, GetBattlerAbility(gBattlerAttacker)) - && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS - && IsMoveMakingContact(move, gBattlerAttacker)) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker, TRUE), move)) { gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_POISON; PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); @@ -4695,8 +4688,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && !gProtectStructs[gBattlerAttacker].confusionSelfDmg && IsBattlerTurnDamaged(gBattlerTarget) && CanBeParalyzed(gBattlerTarget, gBattlerAttacker, GetBattlerAbility(gBattlerAttacker)) - && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS - && IsMoveMakingContact(move, gBattlerAttacker)) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker, TRUE), move)) { gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_PARALYSIS; PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); @@ -4711,8 +4703,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (!(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) && IsBattlerAlive(gBattlerAttacker) && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS - && (IsMoveMakingContact(move, gBattlerAttacker)) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker, TRUE), move) && IsBattlerTurnDamaged(gBattlerTarget) && CanBeBurned(gBattlerTarget, gBattlerAttacker, GetBattlerAbility(gBattlerAttacker)) && (B_ABILITY_TRIGGER_CHANCE >= GEN_4 ? RandomPercentage(RNG_FLAME_BODY, 30) : RandomChance(RNG_FLAME_BODY, 1, 3))) @@ -4735,8 +4726,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && !(gBattleMons[gBattlerAttacker].status2 & STATUS2_INFATUATION) && AreBattlersOfOppositeGender(gBattlerAttacker, gBattlerTarget) && GetBattlerAbility(gBattlerAttacker) != ABILITY_OBLIVIOUS - && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS - && IsMoveMakingContact(move, gBattlerAttacker) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker, TRUE), move) && !IsAbilityOnSide(gBattlerAttacker, ABILITY_AROMA_VEIL)) { gBattleMons[gBattlerAttacker].status2 |= STATUS2_INFATUATED_WITH(gBattlerTarget); @@ -4806,8 +4796,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && !gProtectStructs[gBattlerAttacker].confusionSelfDmg && IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(battler) - && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS - && (IsMoveMakingContact(move, gBattlerAttacker)) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker, TRUE), move) && !(gStatuses3[gBattlerAttacker] & STATUS3_PERISH_SONG)) { if (!(gStatuses3[battler] & STATUS3_PERISH_SONG)) @@ -4932,8 +4921,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && IsBattlerAlive(gBattlerTarget) && !gProtectStructs[gBattlerAttacker].confusionSelfDmg && CanBePoisoned(gBattlerAttacker, gBattlerTarget, gLastUsedAbility, GetBattlerAbility(gBattlerTarget)) - && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS - && IsMoveMakingContact(move, gBattlerAttacker) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker, TRUE), move) && IsBattlerTurnDamaged(gBattlerTarget) // Need to actually hit the target && RandomPercentage(RNG_POISON_TOUCH, 30)) { @@ -7191,8 +7179,7 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler, bool32 moveTurn) break; case HOLD_EFFECT_ROCKY_HELMET: if (IsBattlerTurnDamaged(gBattlerTarget) - && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS - && IsMoveMakingContact(gCurrentMove, gBattlerAttacker) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker, TRUE), gCurrentMove) && IsBattlerAlive(gBattlerAttacker) && GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD) { @@ -7324,8 +7311,7 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler, bool32 moveTurn) case HOLD_EFFECT_STICKY_BARB: if (IsBattlerTurnDamaged(gBattlerTarget) && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) - && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_PROTECTIVE_PADS - && IsMoveMakingContact(gCurrentMove, gBattlerAttacker) + && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker, TRUE), gCurrentMove) && !DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove) && IsBattlerAlive(gBattlerAttacker) && CanStealItem(gBattlerAttacker, gBattlerTarget, gBattleMons[gBattlerTarget].item) @@ -7483,7 +7469,7 @@ u32 GetBattleMoveTarget(u16 move, u8 setTarget) else battlerAbilityOnField = IsAbilityOnOpposingSide(targetBattler, ABILITY_LIGHTNING_ROD); - if (battlerAbilityOnField > 0) + if (battlerAbilityOnField > 0 && (battlerAbilityOnField - 1) != gBattlerAttacker) { targetBattler = battlerAbilityOnField - 1; RecordAbilityBattle(targetBattler, gBattleMons[targetBattler].ability); @@ -7497,7 +7483,7 @@ u32 GetBattleMoveTarget(u16 move, u8 setTarget) else battlerAbilityOnField = IsAbilityOnOpposingSide(targetBattler, ABILITY_STORM_DRAIN); - if (battlerAbilityOnField > 0) + if (battlerAbilityOnField > 0 && (battlerAbilityOnField - 1) != gBattlerAttacker) { targetBattler = battlerAbilityOnField - 1; RecordAbilityBattle(targetBattler, gBattleMons[targetBattler].ability); @@ -7691,26 +7677,35 @@ u32 GetBattlerHoldEffectParam(u32 battler) return GetItemHoldEffectParam(gBattleMons[battler].item); } -bool32 IsMoveMakingContact(u32 move, u32 battlerAtk) +bool32 CanBattlerAvoidContactEffects(u32 battlerAtk, u32 battlerDef, u32 abilityAtk, enum ItemHoldEffect holdEffectAtk, u32 move) { - enum ItemHoldEffect atkHoldEffect = GetBattlerHoldEffect(battlerAtk, TRUE); - - if (!MoveMakesContact(move)) + if (holdEffectAtk == HOLD_EFFECT_PROTECTIVE_PADS) { - if (GetMoveEffect(move) == EFFECT_SHELL_SIDE_ARM && gBattleStruct->shellSideArmCategory[battlerAtk][gBattlerTarget] == DAMAGE_CATEGORY_PHYSICAL) - return TRUE; - else - return FALSE; + RecordItemEffectBattle(battlerAtk, HOLD_EFFECT_PROTECTIVE_PADS); + return TRUE; } - else if ((atkHoldEffect == HOLD_EFFECT_PUNCHING_GLOVE && IsPunchingMove(move)) - || GetBattlerAbility(battlerAtk) == ABILITY_LONG_REACH) + + return !IsMoveMakingContact(battlerAtk, battlerDef, abilityAtk, holdEffectAtk, move); +} + +bool32 IsMoveMakingContact(u32 battlerAtk, u32 battlerDef, u32 abilityAtk, enum ItemHoldEffect holdEffectAtk, u32 move) +{ + if (!(MoveMakesContact(move) || (GetMoveEffect(move) == EFFECT_SHELL_SIDE_ARM + && gBattleStruct->shellSideArmCategory[battlerAtk][battlerDef] == DAMAGE_CATEGORY_PHYSICAL))) { return FALSE; } - else + else if (holdEffectAtk == HOLD_EFFECT_PUNCHING_GLOVE && IsPunchingMove(move)) { - return TRUE; + RecordItemEffectBattle(battlerAtk, HOLD_EFFECT_PUNCHING_GLOVE); + return FALSE; } + else if (abilityAtk == ABILITY_LONG_REACH) + { + RecordAbilityBattle(battlerAtk, ABILITY_LONG_REACH); + return FALSE; + } + return TRUE; } static inline bool32 IsSideProtected(u32 battler, enum ProtectMethod method) @@ -7729,7 +7724,8 @@ bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move) { if (IsZMove(move) || IsMaxMove(move)) return FALSE; // Z-Moves and Max Moves bypass protection (except Max Guard). - if (IsMoveMakingContact(move, battlerAtk) && GetBattlerAbility(battlerAtk) == ABILITY_UNSEEN_FIST) + if (GetBattlerAbility(battlerAtk) == ABILITY_UNSEEN_FIST + && IsMoveMakingContact(battlerAtk, battlerDef, ABILITY_UNSEEN_FIST, GetBattlerHoldEffect(battlerAtk, TRUE), move)) return FALSE; } @@ -8187,10 +8183,20 @@ static inline u32 CalcMoveBasePower(struct DamageCalculationData *damageCalcData basePower = sSpeedDiffPowerTable[speed]; break; case EFFECT_GYRO_BALL: - basePower = ((25 * GetBattlerTotalSpeedStat(battlerDef)) / GetBattlerTotalSpeedStat(battlerAtk)) + 1; - if (basePower > 150) - basePower = 150; - break; + { + u32 attackerSpeed = GetBattlerTotalSpeedStat(battlerAtk); + if (attackerSpeed == 0) + { + basePower = 1; + } + else + { + basePower = ((25 * GetBattlerTotalSpeedStat(battlerDef)) / attackerSpeed) + 1; + if (basePower > 150) + basePower = 150; + } + break; + } case EFFECT_ECHOED_VOICE: // gBattleStruct->sameMoveTurns incremented in ppreduce if (gBattleStruct->sameMoveTurns[battlerAtk] != 0) @@ -8431,7 +8437,7 @@ static inline u32 CalcMoveBasePowerAfterModifiers(struct DamageCalculationData * modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); break; case ABILITY_TOUGH_CLAWS: - if (IsMoveMakingContact(move, battlerAtk)) + if (IsMoveMakingContact(battlerAtk, battlerDef, atkAbility, holdEffectAtk, move)) modifier = uq4_12_multiply(modifier, UQ_4_12(1.3)); break; case ABILITY_STRONG_JAW: @@ -9242,7 +9248,7 @@ static inline uq4_12_t GetAttackerAbilitiesModifier(u32 battlerAtk, uq4_12_t typ return UQ_4_12(1.0); } -static inline uq4_12_t GetDefenderAbilitiesModifier(u32 move, u32 moveType, u32 battlerAtk, u32 battlerDef, uq4_12_t typeEffectivenessModifier, u32 abilityDef) +static inline uq4_12_t GetDefenderAbilitiesModifier(u32 move, u32 moveType, u32 battlerAtk, u32 battlerDef, uq4_12_t typeEffectivenessModifier, u32 abilityDef, enum ItemHoldEffect holdEffectAtk) { switch (abilityDef) { @@ -9258,9 +9264,9 @@ static inline uq4_12_t GetDefenderAbilitiesModifier(u32 move, u32 moveType, u32 return UQ_4_12(0.75); break; case ABILITY_FLUFFY: - if (!IsMoveMakingContact(move, battlerAtk) && moveType == TYPE_FIRE) + if (moveType == TYPE_FIRE && !IsMoveMakingContact(battlerAtk, battlerDef, ABILITY_NONE, holdEffectAtk, move)) return UQ_4_12(2.0); - if (IsMoveMakingContact(move, battlerAtk) && moveType != TYPE_FIRE) + if (moveType != TYPE_FIRE && IsMoveMakingContact(battlerAtk, battlerDef, ABILITY_NONE, holdEffectAtk, move)) return UQ_4_12(0.5); break; case ABILITY_PUNK_ROCK: @@ -9375,14 +9381,14 @@ static inline uq4_12_t GetOtherModifiers(struct DamageCalculationData *damageCal if (unmodifiedAttackerSpeed >= unmodifiedDefenderSpeed) { DAMAGE_MULTIPLY_MODIFIER(GetAttackerAbilitiesModifier(battlerAtk, typeEffectivenessModifier, isCrit, abilityAtk)); - DAMAGE_MULTIPLY_MODIFIER(GetDefenderAbilitiesModifier(move, moveType, battlerAtk, battlerDef, typeEffectivenessModifier, abilityDef)); + DAMAGE_MULTIPLY_MODIFIER(GetDefenderAbilitiesModifier(move, moveType, battlerAtk, battlerDef, typeEffectivenessModifier, abilityDef, holdEffectAtk)); DAMAGE_MULTIPLY_MODIFIER(GetDefenderPartnerAbilitiesModifier(battlerDefPartner)); DAMAGE_MULTIPLY_MODIFIER(GetAttackerItemsModifier(battlerAtk, typeEffectivenessModifier, holdEffectAtk)); DAMAGE_MULTIPLY_MODIFIER(GetDefenderItemsModifier(damageCalcData, typeEffectivenessModifier, abilityDef, holdEffectDef)); } else { - DAMAGE_MULTIPLY_MODIFIER(GetDefenderAbilitiesModifier(move, moveType, battlerAtk, battlerDef, typeEffectivenessModifier, abilityDef)); + DAMAGE_MULTIPLY_MODIFIER(GetDefenderAbilitiesModifier(move, moveType, battlerAtk, battlerDef, typeEffectivenessModifier, abilityDef, holdEffectAtk)); DAMAGE_MULTIPLY_MODIFIER(GetDefenderPartnerAbilitiesModifier(battlerDefPartner)); DAMAGE_MULTIPLY_MODIFIER(GetAttackerAbilitiesModifier(battlerAtk, typeEffectivenessModifier, isCrit, abilityAtk)); DAMAGE_MULTIPLY_MODIFIER(GetDefenderItemsModifier(damageCalcData, typeEffectivenessModifier, abilityDef, holdEffectDef)); @@ -9582,8 +9588,13 @@ bool32 IsFutureSightAttackerInParty(u32 battlerAtk, u32 battlerDef, u32 move) return FALSE; struct Pokemon *party = GetBattlerParty(battlerAtk); - return &party[gWishFutureKnock.futureSightPartyIndex[battlerDef]] != &party[gBattlerPartyIndexes[battlerAtk]] - && &party[gWishFutureKnock.futureSightPartyIndex[battlerDef]] != &party[BATTLE_PARTNER(gBattlerPartyIndexes[battlerAtk])]; + if (IsDoubleBattle()) + { + return &party[gWishFutureKnock.futureSightPartyIndex[battlerDef]] != &party[gBattlerPartyIndexes[battlerAtk]] + && &party[gWishFutureKnock.futureSightPartyIndex[battlerDef]] != &party[gBattlerPartyIndexes[BATTLE_PARTNER(battlerAtk)]]; + } + + return &party[gWishFutureKnock.futureSightPartyIndex[battlerDef]] != &party[gBattlerPartyIndexes[battlerAtk]]; } s32 CalculateMoveDamage(struct DamageCalculationData *damageCalcData, u32 fixedBasePower) diff --git a/src/data/battle_move_effects.h b/src/data/battle_move_effects.h index dfca0c5232..08d8123296 100644 --- a/src/data/battle_move_effects.h +++ b/src/data/battle_move_effects.h @@ -1978,9 +1978,9 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] = .battleTvScore = 0, // TODO: Assign points }, - [EFFECT_HIT_SET_REMOVE_TERRAIN] = + [EFFECT_HIT_SET_TERRAIN] = { - .battleScript = BattleScript_EffectHitSetRemoveTerrain, + .battleScript = BattleScript_EffectHitSetTerrain, .battleTvScore = 0, // TODO: Assign points }, @@ -2218,4 +2218,16 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] = .battleScript = BattleScript_EffectHit, .battleTvScore = 0, // TODO: Assign points }, + + [EFFECT_ICE_SPINNER] = + { + .battleScript = BattleScript_EffectHit, + .battleTvScore = 0, // TODO: Assign points + }, + + [EFFECT_STEEL_ROLLER] = + { + .battleScript = BattleScript_EffectSteelRoller, + .battleTvScore = 0, // TODO: Assign points + }, }; diff --git a/src/data/graphics/intro_scene.h b/src/data/graphics/intro_scene.h index cb7706c9a3..8b17b00346 100644 --- a/src/data/graphics/intro_scene.h +++ b/src/data/graphics/intro_scene.h @@ -1,6 +1,6 @@ const u16 gIntroGameFreakTextFade_Pal[] = INCBIN_U16("graphics/intro/scene_1/text.gbapal"); // game freak text blue fade const u16 gIntroPlayer_Pal[] = INCBIN_U16("graphics/intro/scene_2/player.gbapal"); -const u16 gIntro3Bg_Pal[] = INCBIN_U16("graphics/intro/scene_3/bg.gbapal"); +const u16 gIntro3Bg_Pal[16][16] = INCBIN_U16("graphics/intro/scene_3/bg.gbapal"); const u16 gIntroVolbeat_Pal[] = INCBIN_U16("graphics/intro/scene_2/volbeat.gbapal"); const u16 gIntroTorchic_Pal[] = INCBIN_U16("graphics/intro/scene_2/torchic.gbapal"); const u16 gIntroManectric_Pal[] = INCBIN_U16("graphics/intro/scene_2/manectric.gbapal"); diff --git a/src/data/moves_info.h b/src/data/moves_info.h index f0951d2225..00bba4b35c 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -18301,7 +18301,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .description = COMPOUND_STRING( "在破坏场地的同时攻击对手。\n" "脚下没有任何场地便会失败。"), - .effect = EFFECT_HIT_SET_REMOVE_TERRAIN, + .effect = EFFECT_STEEL_ROLLER, .power = 130, .type = TYPE_STEEL, .accuracy = 100, @@ -18310,7 +18310,6 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, .makesContact = TRUE, - .argument = { .moveProperty = ARG_TRY_REMOVE_TERRAIN_FAIL }, // Remove a field terrain if there is one and hit, otherwise fail. .skyBattleBanned = TRUE, .contestEffect = CONTEST_EFFECT_WORSEN_CONDITION_OF_PREV_MONS, .contestCategory = CONTEST_CATEGORY_TOUGH, @@ -19732,7 +19731,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .description = COMPOUND_STRING( "脚上覆盖薄冰,旋转着撞击\n" "对手。用旋转动作破坏场地。"), - .effect = EFFECT_HIT_SET_REMOVE_TERRAIN, + .effect = EFFECT_ICE_SPINNER, .power = 80, .type = TYPE_ICE, .accuracy = 100, @@ -19741,7 +19740,6 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, .makesContact = TRUE, - .argument = { .moveProperty = ARG_TRY_REMOVE_TERRAIN_HIT }, // Remove the active field terrain if there is one. .skyBattleBanned = B_EXTRAPOLATED_MOVE_FLAGS, .battleAnimScript = gBattleAnimMove_IceSpinner, }, @@ -21370,7 +21368,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .description = COMPOUND_STRING( "梦幻以Z力量全力攻击对手,\n" "脚下会变成精神场地。"), - .effect = EFFECT_HIT_SET_REMOVE_TERRAIN, + .effect = EFFECT_HIT_SET_TERRAIN, .power = 185, .type = TYPE_PSYCHIC, .accuracy = 0, @@ -21378,7 +21376,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_SPECIAL, - .argument = { .moveProperty = ARG_SET_PSYCHIC_TERRAIN }, // Set Psychic Terrain. If there's a different field terrain active, overwrite it. + .argument = { .moveProperty = STATUS_FIELD_PSYCHIC_TERRAIN }, .battleAnimScript = gBattleAnimMove_GenesisSupernova, }, [MOVE_SINISTER_ARROW_RAID] = @@ -21435,7 +21433,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .description = COMPOUND_STRING( "鬃岩狼人以Z力量全力进行\n" "攻击。而且会消除场地状态。"), - .effect = EFFECT_HIT_SET_REMOVE_TERRAIN, + .effect = EFFECT_ICE_SPINNER, .power = 190, .type = TYPE_ROCK, .accuracy = 0, @@ -21443,7 +21441,6 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .moveProperty = ARG_TRY_REMOVE_TERRAIN_HIT }, // Remove the active field terrain if there is one. .battleAnimScript = gBattleAnimMove_SplinteredStormshards, }, [MOVE_LETS_SNUGGLE_FOREVER] = diff --git a/src/debug.c b/src/debug.c index c2b7c07c6e..2385e802fe 100644 --- a/src/debug.c +++ b/src/debug.c @@ -2527,6 +2527,7 @@ static void DebugAction_FlagsVars_FlagsSelect(u8 taskId) { if (JOY_NEW(A_BUTTON)) { + PlaySE(SE_SELECT); FlagToggle(gTasks[taskId].tInput); } else if (JOY_NEW(B_BUTTON)) @@ -2536,7 +2537,6 @@ static void DebugAction_FlagsVars_FlagsSelect(u8 taskId) return; } - PlaySE(SE_SELECT); Debug_HandleInput_Numeric(taskId, 1, FLAGS_COUNT - 1, DEBUG_NUMBER_DIGITS_FLAGS); if (JOY_NEW(DPAD_ANY) || JOY_NEW(A_BUTTON)) diff --git a/src/egg_hatch.c b/src/egg_hatch.c index f65ee674d8..3f72cadeaa 100644 --- a/src/egg_hatch.c +++ b/src/egg_hatch.c @@ -33,6 +33,7 @@ #include "naming_screen.h" #include "pokemon_storage_system.h" #include "field_screen_effect.h" +#include "trade.h" #include "data.h" #include "battle.h" // to get rid of later #include "constants/rgb.h" @@ -63,7 +64,6 @@ struct EggHatchData u8 textColor[3]; }; -extern const u32 gTradePlatform_Tilemap[]; extern const u8 gText_HatchedFromEgg[]; extern const u8 gText_NicknameHatchPrompt[]; diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 361111ac4e..0c0d5b4694 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -2335,7 +2335,12 @@ bool32 CheckMsgCondition(const struct MsgCondition *cond, struct Pokemon *mon, u switch (cond->type) { case MSG_COND_SPECIES: - return (cond->data.raw == species); + multi = cond->data.split.hw; + // if byte nonzero, invert; check != species! + if (cond->data.split.b) + return (cond->data.split.hw != species); + else + return (cond->data.split.hw == species); case MSG_COND_TYPE: multi = (SpeciesHasType(species, cond->data.bytes[0]) || SpeciesHasType(species, cond->data.bytes[1])); diff --git a/src/field_player_avatar.c b/src/field_player_avatar.c index 07f4b74e08..d02ecbd9fc 100644 --- a/src/field_player_avatar.c +++ b/src/field_player_avatar.c @@ -805,9 +805,27 @@ static void PlayerNotOnBikeMoving(u8 direction, u16 heldKeys) } else { - u8 adjustedCollision = collision - COLLISION_STOP_SURFING; - if (adjustedCollision > 3) + // Player collided with something. Certain collisions have special handling that precludes the normal collision effect. + // COLLISION_STOP_SURFING and COLLISION_PUSHED_BOULDER's effects are started by CheckForObjectEventCollision. + // COLLISION_LEDGE_JUMP's effect is handled further up in this function, so it will never reach this point. + // COLLISION_ROTATING_GATE is unusual however, this was probably included by mistake. When the player walks into a + // rotating gate that cannot rotate there is no additional handling, it's just a regular collision. Its exclusion here + // means that the player avatar won't update if they encounter this kind of collision. This has two noticeable effects: + // - Colliding with it head-on stops the player dead, rather than playing the walking animation and playing a bump sound effect + // - Colliding with it by changing direction won't turn the player avatar, their walking animation will just speed up. +#ifdef BUGFIX + if (collision != COLLISION_STOP_SURFING + && collision != COLLISION_LEDGE_JUMP + && collision != COLLISION_PUSHED_BOULDER) +#else + if (collision != COLLISION_STOP_SURFING + && collision != COLLISION_LEDGE_JUMP + && collision != COLLISION_PUSHED_BOULDER + && collision != COLLISION_ROTATING_GATE) +#endif + { PlayerNotOnBikeCollide(direction); + } return; } } diff --git a/src/follower_helper.c b/src/follower_helper.c index a53a79d268..c4352b09c8 100644 --- a/src/follower_helper.c +++ b/src/follower_helper.c @@ -74,6 +74,7 @@ static const u8* const sDayTexts[] = {sCondMsg43, sCondMsg44, NULL}; static const u8 sCondMsg45[] = _("你的宝可梦正呆呆地\n凝望着夜空!"); static const u8 sCondMsg46[] = _("你的宝可梦正开心地\n凝视着美丽的星空!"); static const u8* const sNightTexts[] = {sCondMsg45, sCondMsg46, NULL}; +static const u8 sCondMsg50[] = _("{STR_VAR_1} is disturbed by the\nabnormal weather!"); // See the struct definition in follower_helper.h for more info const struct FollowerMsgInfoExtended gFollowerConditionalMessages[COND_MSG_COUNT] = @@ -378,6 +379,18 @@ const struct FollowerMsgInfoExtended gFollowerConditionalMessages[COND_MSG_COUNT MATCH_TIME_OF_DAY(TIME_NIGHT), }, }, + [COND_MSG_ABNORMAL_WEATHER] = + { + .text = sCondMsg50, + .emotion = FOLLOWER_EMOTION_SURPRISE, + .conditions = + { + MATCH_MUSIC(MUS_ABNORMAL_WEATHER), + MATCH_NOT_SPECIES(SPECIES_KYOGRE), + MATCH_NOT_SPECIES(SPECIES_GROUDON), + MATCH_NOT_SPECIES(SPECIES_RAYQUAZA), + } + }, }; // Pool of "unconditional" follower messages diff --git a/src/follower_npc.c b/src/follower_npc.c index 0a8cde49c8..d9882b61bf 100644 --- a/src/follower_npc.c +++ b/src/follower_npc.c @@ -1179,10 +1179,6 @@ void FollowerNPC_HandleSprite(void) else if (gPlayerAvatar.flags & PLAYER_AVATAR_FLAG_ACRO_BIKE) SetFollowerNPCSprite(FOLLOWER_NPC_SPRITE_INDEX_ACRO_BIKE); } - else if (gMapHeader.mapType == MAP_TYPE_UNDERWATER) - { - TryUpdateFollowerNPCSpriteUnderwater(); - } else if (gPlayerAvatar.flags & PLAYER_AVATAR_FLAG_ON_FOOT) { SetFollowerNPCSprite(FOLLOWER_NPC_SPRITE_INDEX_NORMAL); diff --git a/src/graphics.c b/src/graphics.c index 97acda7732..4f245c4212 100644 --- a/src/graphics.c +++ b/src/graphics.c @@ -1,4 +1,5 @@ #include "global.h" +#include "graphics.h" const u32 gBattleTextboxTiles[] = INCBIN_U32("graphics/battle_interface/textbox.4bpp.lz"); const u16 gBattleTextboxPalette[] = INCBIN_U16("graphics/battle_interface/textbox.gbapal"); @@ -707,7 +708,7 @@ const u16 gBattleInterface_BallStatusBarPal[] = INCBIN_U16("graphics/battle_inte const u16 gBattleInterface_BallDisplayPal[] = INCBIN_U16("graphics/battle_interface/ball_display.gbapal"); -const u8 gHealthboxElementsGfxTable[] = INCBIN_U8("graphics/battle_interface/hpbar.4bpp", +const u8 gHealthboxElementsGfxTable[][32] = INCBIN_U8("graphics/battle_interface/hpbar.4bpp", "graphics/battle_interface/expbar.4bpp", "graphics/battle_interface/status.4bpp", "graphics/battle_interface/misc.4bpp", @@ -1332,7 +1333,7 @@ const u16 gTilesetAnims_BattleDomePals0_3[] = INCBIN_U16("graphics/battle_fronti static const u16 sUnused0[] = {0x13F, 0x119, 0x113, 0x10E}; -const u16 gBattlePyramidFloor_Pal[] = INCBIN_U16("graphics/battle_frontier/pyramid_floor.gbapal"); +const u16 gBattlePyramidFloor_Pal[][16] = INCBIN_U16("graphics/battle_frontier/pyramid_floor.gbapal"); const u32 gMultiBattleIntroBg_Opponent_Tilemap[] = INCBIN_U32("graphics/battle_frontier/multi_battle_intro_bg_opponent.bin.lz"); const u32 gMultiBattleIntroBg_Player_Tilemap[] = INCBIN_U32("graphics/battle_frontier/multi_battle_intro_bg_player.bin.lz"); @@ -2087,12 +2088,12 @@ const u16 gTitleScreenPressStartPal[] = INCBIN_U16("graphics/title_screen/p const u32 gTitleScreenPressStartGfx[] = INCBIN_U32("graphics/title_screen/press_start.4bpp.lz"); const u32 gTitleScreenPokemonLogoTilemap[] = INCBIN_U32("graphics/title_screen/pokemon_logo.bin.lz"); -const u16 gFrontierPassBg_Pal[] = INCBIN_U16("graphics/frontier_pass/bg.gbapal"); // 8 x 16 +const u16 gFrontierPassBg_Pal[][16] = INCBIN_U16("graphics/frontier_pass/bg.gbapal"); // 8 x 16 const u32 gFrontierPassBg_Gfx[] = INCBIN_U32("graphics/frontier_pass/bg.4bpp.lz"); const u32 gFrontierPassMapAndCard_Gfx[] = INCBIN_U32("graphics/frontier_pass/map_and_card.8bpp.lz"); const u32 gFrontierPassBg_Tilemap[] = INCBIN_U32("graphics/frontier_pass/bg.bin.lz"); -const u16 gFrontierPassCancelButton_Tilemap[] = INCBIN_U16("graphics/frontier_pass/cancel.bin"); -const u16 gFrontierPassCancelButtonHighlighted_Tilemap[] = INCBIN_U16("graphics/frontier_pass/cancel_highlighted.bin"); +const u32 gFrontierPassCancelButton_Tilemap[] = INCBIN_U32("graphics/frontier_pass/cancel.bin"); +const u32 gFrontierPassCancelButtonHighlighted_Tilemap[] = INCBIN_U32("graphics/frontier_pass/cancel_highlighted.bin"); // Berry Crush const u16 gBerryCrush_Crusher_Pal[] = INCBIN_U16("graphics/berry_crush/crusher.gbapal"); diff --git a/src/intro.c b/src/intro.c index 7a19e235ef..ae5ee190fc 100644 --- a/src/intro.c +++ b/src/intro.c @@ -1875,12 +1875,15 @@ static void Task_Scene3_StartGroudon(u8 taskId) #define tTrigIdx data[6] // Re-used #define tPalIdx data[7] +// Treats gIntro3Bg_Pal as u8* and iterates by 1 +#define INTRO3_RAW_PTR(palId)(((void *) &gIntro3Bg_Pal) + palId) + static void Task_Scene3_Groudon(u8 taskId) { s16 *data = gTasks[taskId].data; tTimer++; - if ((u16)(tState - 1) < 7 && tTimer % 2 == 0) + if ((tState >= 1 && tState <= 7) && tTimer % 2 == 0) tYShake ^= 3; PanFadeAndZoomScreen(tScreenX, tScreenY + tYShake, tZoom, 0); switch (tState) @@ -1899,7 +1902,7 @@ static void Task_Scene3_Groudon(u8 taskId) if (--tDelay == 0) { tDelay = 2; - CpuCopy16(&gIntro3Bg_Pal[tPalIdx], &gPlttBufferFaded[BG_PLTT_ID(1) + 15], PLTT_SIZEOF(1)); + CpuCopy16(INTRO3_RAW_PTR(tPalIdx), &gPlttBufferFaded[BG_PLTT_ID(1) + 15], PLTT_SIZEOF(1)); tPalIdx += 2; if (tPalIdx == 0x1EC) tState++; @@ -1916,7 +1919,7 @@ static void Task_Scene3_Groudon(u8 taskId) if (--tDelay == 0) { tDelay = 2; - CpuCopy16(&gIntro3Bg_Pal[tPalIdx], &gPlttBufferFaded[BG_PLTT_ID(1) + 15], PLTT_SIZEOF(1)); + CpuCopy16(INTRO3_RAW_PTR(tPalIdx), &gPlttBufferFaded[BG_PLTT_ID(1) + 15], PLTT_SIZEOF(1)); tPalIdx -= 2; if (tPalIdx == 0x1E0) { @@ -2158,7 +2161,7 @@ static void Task_Scene3_Kyogre(u8 taskId) if (--tDelay == 0) { tDelay = 4; - CpuCopy16(&gIntro3Bg_Pal[tPalIdx], &gPlttBufferFaded[BG_PLTT_ID(2) + 15], PLTT_SIZEOF(1)); + CpuCopy16(INTRO3_RAW_PTR(tPalIdx), &gPlttBufferFaded[BG_PLTT_ID(2) + 15], PLTT_SIZEOF(1)); tPalIdx -= 2; if (tPalIdx == 0x1E0) tState++; @@ -2176,7 +2179,7 @@ static void Task_Scene3_Kyogre(u8 taskId) if (--tDelay == 0) { tDelay = 4; - CpuCopy16(&gIntro3Bg_Pal[tPalIdx], &gPlttBufferFaded[BG_PLTT_ID(2) + 15], PLTT_SIZEOF(1)); + CpuCopy16(INTRO3_RAW_PTR(tPalIdx), &gPlttBufferFaded[BG_PLTT_ID(2) + 15], PLTT_SIZEOF(1)); tPalIdx += 2; if (tPalIdx == 0x1EE) { @@ -2507,7 +2510,7 @@ static void SpriteCB_Lightning(struct Sprite *sprite) sprite->sPalIdx = 0x1C2; sprite->sState++; case 1: - CpuCopy16(&gIntro3Bg_Pal[sprite->sPalIdx], &gPlttBufferFaded[BG_PLTT_ID(5) + 13], PLTT_SIZEOF(1)); + CpuCopy16(INTRO3_RAW_PTR(sprite->sPalIdx), &gPlttBufferFaded[BG_PLTT_ID(5) + 13], PLTT_SIZEOF(1)); sprite->sPalIdx += 2; if (sprite->sPalIdx != 0x1CE) break; @@ -2518,7 +2521,7 @@ static void SpriteCB_Lightning(struct Sprite *sprite) if (--sprite->sDelay == 0) { sprite->sDelay = 4; - CpuCopy16(&gIntro3Bg_Pal[sprite->sPalIdx], &gPlttBufferFaded[BG_PLTT_ID(5) + 13], PLTT_SIZEOF(1)); + CpuCopy16(INTRO3_RAW_PTR(sprite->sPalIdx), &gPlttBufferFaded[BG_PLTT_ID(5) + 13], PLTT_SIZEOF(1)); sprite->sPalIdx -= 2; if (sprite->sPalIdx == 0x1C0) DestroySprite(sprite); @@ -2621,7 +2624,7 @@ static void Task_RayquazaAttack(u8 taskId) case 0: if ((data[2] & 1) != 0) { - CpuCopy16(&gIntro3Bg_Pal[0x1A2 + data[1] * 2], &gPlttBufferFaded[BG_PLTT_ID(5) + 14], PLTT_SIZEOF(1)); + CpuCopy16(INTRO3_RAW_PTR(0x1A2 + data[1] * 2), &gPlttBufferFaded[BG_PLTT_ID(5) + 14], PLTT_SIZEOF(1)); data[1]++; } if (data[1] == 6) @@ -2636,7 +2639,7 @@ static void Task_RayquazaAttack(u8 taskId) { if ((data[2] & 1) != 0) { - CpuCopy16(&gIntro3Bg_Pal[0x1A2 + data[1] * 2], &gPlttBufferFaded[BG_PLTT_ID(5) + 8], PLTT_SIZEOF(1)); + CpuCopy16(INTRO3_RAW_PTR(0x1A2 + data[1] * 2), &gPlttBufferFaded[BG_PLTT_ID(5) + 8], PLTT_SIZEOF(1)); data[1]++; } if (data[1] == 6) @@ -2655,7 +2658,7 @@ static void Task_RayquazaAttack(u8 taskId) { if ((data[2] & 1) != 0) { - CpuCopy16(&gIntro3Bg_Pal[0x182 + data[1] * 2], &gPlttBufferFaded[BG_PLTT_ID(5) + 12], PLTT_SIZEOF(1)); + CpuCopy16(INTRO3_RAW_PTR(0x182 + data[1] * 2), &gPlttBufferFaded[BG_PLTT_ID(5) + 12], PLTT_SIZEOF(1)); data[1]++; } if (data[1] == 6) @@ -2679,9 +2682,9 @@ static void Task_RayquazaAttack(u8 taskId) if (--data[3] != 0) { BlendPalette(BG_PLTT_ID(5), 16, data[3], RGB(9, 10, 10)); - CpuCopy16(&gIntro3Bg_Pal[428], &gPlttBufferFaded[BG_PLTT_ID(5) + 14], PLTT_SIZEOF(1)); - CpuCopy16(&gIntro3Bg_Pal[428], &gPlttBufferFaded[BG_PLTT_ID(5) + 8], PLTT_SIZEOF(1)); - CpuCopy16(&gIntro3Bg_Pal[396], &gPlttBufferFaded[BG_PLTT_ID(5) + 12], PLTT_SIZEOF(1)); + CpuCopy16(INTRO3_RAW_PTR(428), &gPlttBufferFaded[BG_PLTT_ID(5) + 14], PLTT_SIZEOF(1)); + CpuCopy16(INTRO3_RAW_PTR(428), &gPlttBufferFaded[BG_PLTT_ID(5) + 8], PLTT_SIZEOF(1)); + CpuCopy16(INTRO3_RAW_PTR(396), &gPlttBufferFaded[BG_PLTT_ID(5) + 12], PLTT_SIZEOF(1)); } else { diff --git a/src/mail.c b/src/mail.c index 7cff48c8ed..34d1520ef4 100644 --- a/src/mail.c +++ b/src/mail.c @@ -48,8 +48,8 @@ struct MailLayout struct MailGraphics { const u16 *palette; - const u8 *tiles; - const u8 *tileMap; + const u32 *tiles; + const u32 *tileMap; u32 unused; u16 textColor; u16 textShadow; diff --git a/src/naming_screen.c b/src/naming_screen.c index 2496616294..a08c229459 100644 --- a/src/naming_screen.c +++ b/src/naming_screen.c @@ -1981,7 +1981,7 @@ static void PrintKeyboardKeys(u8 window, u8 page) PutWindowTilemap(window); } -static const u8 *const sNextKeyboardPageTilemaps[] = +static const u32 *const sNextKeyboardPageTilemaps[] = { [KBPAGE_SYMBOLS] = gNamingScreenKeyboardUpper_Tilemap, [KBPAGE_LETTERS_UPPER] = gNamingScreenKeyboardLower_Tilemap, // lower diff --git a/src/palette.c b/src/palette.c index c4d65404d1..4054e007de 100644 --- a/src/palette.c +++ b/src/palette.c @@ -2,10 +2,12 @@ #include "palette.h" #include "util.h" #include "decompress.h" +#include "field_weather.h" #include "malloc.h" #include "menu.h" #include "gpu_regs.h" #include "task.h" +#include "constants/field_weather.h" #include "constants/rgb.h" enum @@ -718,26 +720,46 @@ static void UpdateBlendRegisters(void) { SetGpuReg(REG_OFFSET_BLDCNT, (u16)gPaletteFadeBlendCnt); SetGpuReg(REG_OFFSET_BLDY, gPaletteFade.y); - // If fade-out, also adjust BLDALPHA and DISPCNT - if (!gPaletteFade.yDec) + // if TGT2 enabled, also adjust BLDALPHA and DISPCNT + if (((u16)gPaletteFadeBlendCnt) & BLDCNT_TGT2_ALL) { u16 bldAlpha = GetGpuReg(REG_OFFSET_BLDALPHA); u8 tgt1 = BLDALPHA_TGT1(bldAlpha); u8 tgt2 = BLDALPHA_TGT2(bldAlpha); - u8 bldFade; + u8 mode = (gPaletteFadeBlendCnt & BLDCNT_EFFECT_EFF_MASK) == BLDCNT_EFFECT_LIGHTEN ? FADE_FROM_WHITE : FADE_FROM_BLACK; + if (!gPaletteFade.yDec) + mode++; - switch (gPaletteFadeBlendCnt & BLDCNT_EFFECT_EFF_MASK) + ClearGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_FORCED_BLANK); + + switch (mode) { - // FADE_TO_BLACK - case BLDCNT_EFFECT_DARKEN: - bldFade = BLDALPHA_TGT1(max(0, 16 - gPaletteFade.y)); - SetGpuReg(REG_OFFSET_BLDALPHA, - BLDALPHA_BLEND(min(tgt1, bldFade), min(tgt2, bldFade))); + case FADE_FROM_BLACK: + // increment each target until reaching weather's values + SetGpuReg( + REG_OFFSET_BLDALPHA, + BLDALPHA_BLEND( + min(++tgt1, gWeatherPtr->currBlendEVA), + min(++tgt2, gWeatherPtr->currBlendEVB) + ) + ); break; - // FADE_TO_WHITE - case BLDCNT_EFFECT_LIGHTEN: - SetGpuReg(REG_OFFSET_BLDALPHA, - BLDALPHA_BLEND(min(++tgt1, 31), min(++tgt2, 31))); + case FADE_TO_BLACK: + bldAlpha = BLDALPHA_TGT1(max(0, 16 - gPaletteFade.y)); + SetGpuReg( + REG_OFFSET_BLDALPHA, + BLDALPHA_BLEND(min(tgt1, bldAlpha), min(tgt2, bldAlpha)) + ); + break; + // Not handled; blend sprites will pop in, + // but the effect coming from white looks okay + // case FADE_FROM_WHITE: + // break; + case FADE_TO_WHITE: + SetGpuReg( + REG_OFFSET_BLDALPHA, + BLDALPHA_BLEND(min(++tgt1, 31), min(++tgt2, 31)) + ); // cause display to show white when finished // (otherwise blend-mode sprites will still be visible) if (gPaletteFade.hardwareFadeFinishing && gPaletteFade.y >= 16) @@ -745,10 +767,6 @@ static void UpdateBlendRegisters(void) break; } } - else - { - ClearGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_FORCED_BLANK); - } if (gPaletteFade.hardwareFadeFinishing) { diff --git a/src/roamer.c b/src/roamer.c index 9bca6fdc45..0fc86b6711 100644 --- a/src/roamer.c +++ b/src/roamer.c @@ -113,6 +113,7 @@ static void CreateInitialRoamerMon(u8 index, u16 species, u8 level) ROAMER(index)->cute = GetMonData(&gEnemyParty[0], MON_DATA_CUTE); ROAMER(index)->smart = GetMonData(&gEnemyParty[0], MON_DATA_SMART); ROAMER(index)->tough = GetMonData(&gEnemyParty[0], MON_DATA_TOUGH); + ROAMER(index)->shiny = GetMonData(&gEnemyParty[0], MON_DATA_IS_SHINY); ROAMER(index)->active = TRUE; sRoamerLocation[index][MAP_GRP] = ROAMER_MAP_GROUP; sRoamerLocation[index][MAP_NUM] = sRoamerLocations[Random() % NUM_LOCATION_SETS][0]; @@ -243,8 +244,6 @@ void CreateRoamerMonInstance(u32 roamerIndex) struct Pokemon *mon = &gEnemyParty[0]; ZeroEnemyPartyMons(); CreateMonWithIVsPersonality(mon, ROAMER(roamerIndex)->species, ROAMER(roamerIndex)->level, ROAMER(roamerIndex)->ivs, ROAMER(roamerIndex)->personality); - // The roamer's status field is u16, but SetMonData expects status to be u32, so will set the roamer's status - // using the status field and the following 3 bytes (cool, beauty, and cute). SetMonData(mon, MON_DATA_STATUS, &status); SetMonData(mon, MON_DATA_HP, &ROAMER(roamerIndex)->hp); SetMonData(mon, MON_DATA_COOL, &ROAMER(roamerIndex)->cool); @@ -252,6 +251,7 @@ void CreateRoamerMonInstance(u32 roamerIndex) SetMonData(mon, MON_DATA_CUTE, &ROAMER(roamerIndex)->cute); SetMonData(mon, MON_DATA_SMART, &ROAMER(roamerIndex)->smart); SetMonData(mon, MON_DATA_TOUGH, &ROAMER(roamerIndex)->tough); + SetMonData(mon, MON_DATA_IS_SHINY, &ROAMER(roamerIndex)->shiny); } bool8 TryStartRoamerEncounter(void) diff --git a/src/scrcmd.c b/src/scrcmd.c index 87bc8e6298..b5077420e3 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -839,6 +839,8 @@ bool8 ScrCmd_fadescreenswapbuffers(struct ScriptContext *ctx) switch (mode) { case FADE_FROM_BLACK: + SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(0, 0)); + break; case FADE_FROM_WHITE: // Restore last weather blend before fading in, // since BLDALPHA was modified by fade-out diff --git a/test/battle/ability/fluffy.c b/test/battle/ability/fluffy.c index 4b8ef60396..6525c73212 100644 --- a/test/battle/ability/fluffy.c +++ b/test/battle/ability/fluffy.c @@ -21,7 +21,7 @@ SINGLE_BATTLE_TEST("Fluffy halves damage taken from moves that make direct conta } WHEN { TURN { MOVE(player, MOVE_SCRATCH); } } SCENE { - MESSAGE("Wobbuffet used Scratch!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); HP_BAR(opponent, captureDamage: &results[i].damage); } FINALLY { EXPECT_MUL_EQ(results[0].damage, UQ_4_12(0.5), results[1].damage); @@ -39,7 +39,7 @@ SINGLE_BATTLE_TEST("Fluffy doubles damage taken from fire type moves", s16 damag } WHEN { TURN { MOVE(player, MOVE_EMBER); } } SCENE { - MESSAGE("Wobbuffet used Ember!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, player); HP_BAR(opponent, captureDamage: &results[i].damage); } FINALLY { EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.0), results[1].damage); @@ -57,7 +57,43 @@ SINGLE_BATTLE_TEST("Fluffy does not alter damage of fire-type moves that make di } WHEN { TURN { MOVE(player, MOVE_FIRE_PUNCH); } } SCENE { - MESSAGE("Wobbuffet used Fire Punch!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PUNCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Fluffy halves damage taken from moves that make direct contact even if protected by Protective Pads", s16 damage) +{ + u32 ability; + PARAMETRIZE { ability = ABILITY_KLUTZ; } + PARAMETRIZE { ability = ABILITY_FLUFFY; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_PROTECTIVE_PADS); } + OPPONENT(SPECIES_STUFFUL) { Ability(ability); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(0.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Fluffy does not halve damage taken from moves that make direct contact but are ignored by Punching Glove", s16 damage) +{ + u32 ability; + PARAMETRIZE { ability = ABILITY_KLUTZ; } + PARAMETRIZE { ability = ABILITY_FLUFFY; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_PUNCHING_GLOVE); } + OPPONENT(SPECIES_STUFFUL) { Ability(ability); } + } WHEN { + TURN { MOVE(player, MOVE_THUNDER_PUNCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDER_PUNCH, player); HP_BAR(opponent, captureDamage: &results[i].damage); } FINALLY { EXPECT_EQ(results[0].damage, results[1].damage); diff --git a/test/battle/ability/liquid_ooze.c b/test/battle/ability/liquid_ooze.c index 0e5a0c6cf7..fa8ca8e3b2 100644 --- a/test/battle/ability/liquid_ooze.c +++ b/test/battle/ability/liquid_ooze.c @@ -138,7 +138,7 @@ SINGLE_BATTLE_TEST("Liquid Ooze causes leech seed victim to faint before seeder" } } -SINGLE_BATTLE_TEST("Liquid Ooze causes Dream Eater users to lose HP instead of heal (Gen 5+") +SINGLE_BATTLE_TEST("Liquid Ooze causes Dream Eater users to lose HP instead of heal (Gen 5+)") { s16 damage; GIVEN { diff --git a/test/battle/defrost.c b/test/battle/defrost.c index 3d456d6673..a226a49f1e 100644 --- a/test/battle/defrost.c +++ b/test/battle/defrost.c @@ -16,3 +16,17 @@ DOUBLE_BATTLE_TEST("Defrost: A fire type spread move will thaw both targets") STATUS_ICON(opponentRight, freeze: FALSE); } } + +SINGLE_BATTLE_TEST("Defrost: Scald does not thaw targets if user is asleep") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FROSTBITE); } + OPPONENT(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP_TURN(3)); } + } WHEN { + TURN { MOVE(opponent, MOVE_SCALD); MOVE(player, MOVE_CELEBRATE); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SCALD, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player); + HP_BAR(player); + } +} diff --git a/test/battle/gimmick/zmove.c b/test/battle/gimmick/zmove.c index fd5635d3fc..058d59c620 100644 --- a/test/battle/gimmick/zmove.c +++ b/test/battle/gimmick/zmove.c @@ -578,7 +578,7 @@ SINGLE_BATTLE_TEST("(Z-MOVE) Extreme Evoboost boosts all the user's stats by two SINGLE_BATTLE_TEST("(Z-MOVE) Genesis Supernova sets up psychic terrain") { GIVEN { - ASSUME(GetMoveEffect(MOVE_GENESIS_SUPERNOVA) == EFFECT_HIT_SET_REMOVE_TERRAIN); + ASSUME(GetMoveEffect(MOVE_GENESIS_SUPERNOVA) == EFFECT_HIT_SET_TERRAIN); PLAYER(SPECIES_MEW) { Item(ITEM_MEWNIUM_Z); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -595,7 +595,7 @@ SINGLE_BATTLE_TEST("(Z-MOVE) Genesis Supernova sets up psychic terrain") SINGLE_BATTLE_TEST("(Z-MOVE) Splintered Stormshards removes terrain") { GIVEN { - ASSUME(GetMoveEffect(MOVE_SPLINTERED_STORMSHARDS) == EFFECT_HIT_SET_REMOVE_TERRAIN); + ASSUME(GetMoveEffect(MOVE_SPLINTERED_STORMSHARDS) == EFFECT_ICE_SPINNER); PLAYER(SPECIES_LYCANROC_DUSK) { Item(ITEM_LYCANIUM_Z); } OPPONENT(SPECIES_TAPU_LELE) { Ability(ABILITY_PSYCHIC_SURGE); HP(1000); MaxHP(1000); } } WHEN { diff --git a/test/battle/hold_effect/jaboca_berry.c b/test/battle/hold_effect/jaboca_berry.c index 29658c7fe1..a23cd90bb9 100644 --- a/test/battle/hold_effect/jaboca_berry.c +++ b/test/battle/hold_effect/jaboca_berry.c @@ -42,7 +42,6 @@ SINGLE_BATTLE_TEST("Jaboca Berry causes the attacker to lose 1/8 of its max HP i SINGLE_BATTLE_TEST("Jaboca Berry triggers before Bug Bite can steal it") { - KNOWN_FAILING; GIVEN { ASSUME(GetMoveCategory(MOVE_BUG_BITE) == DAMAGE_CATEGORY_PHYSICAL); PLAYER(SPECIES_WYNAUT); diff --git a/test/battle/hold_effect/life_orb.c b/test/battle/hold_effect/life_orb.c index 7f4fcc2ccf..92c545dfad 100644 --- a/test/battle/hold_effect/life_orb.c +++ b/test/battle/hold_effect/life_orb.c @@ -31,6 +31,23 @@ SINGLE_BATTLE_TEST("Life Orb activates if it hits a Substitute") } } +SINGLE_BATTLE_TEST("Life Orb does not activate if using status move on a Substitute") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LIFE_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_GROWL); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, player); + NONE_OF { + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the Life Orb!"); + } + } +} + SINGLE_BATTLE_TEST("Life Orb does not activate if using a status move") { GIVEN { diff --git a/test/battle/move_animations/all_anims.c b/test/battle/move_animations/all_anims.c index 778b407937..a994bab63f 100644 --- a/test/battle/move_animations/all_anims.c +++ b/test/battle/move_animations/all_anims.c @@ -152,7 +152,7 @@ static void WhenSingles(u32 move, struct BattlePokemon *attacker, struct BattleP { // Has to be hailing TURN { MOVE(attacker, MOVE_HAIL); } } - else if (gMovesInfo[move].effect == EFFECT_HIT_SET_REMOVE_TERRAIN && gMovesInfo[move].argument.moveProperty == ARG_TRY_REMOVE_TERRAIN_FAIL) + else if (gMovesInfo[move].effect == EFFECT_STEEL_ROLLER) { // Needs a terrain TURN { MOVE(attacker, MOVE_ELECTRIC_TERRAIN); } } @@ -281,7 +281,7 @@ static void DoublesWhen(u32 move, struct BattlePokemon *attacker, struct BattleP { // Has to be hailing TURN { MOVE(attacker, MOVE_HAIL); } } - else if (gMovesInfo[move].effect == EFFECT_HIT_SET_REMOVE_TERRAIN && gMovesInfo[move].argument.moveProperty == ARG_TRY_REMOVE_TERRAIN_FAIL) + else if (gMovesInfo[move].effect == EFFECT_STEEL_ROLLER) { // Needs a terrain TURN { MOVE(attacker, MOVE_ELECTRIC_TERRAIN); } } diff --git a/test/battle/move_effect/assist.c b/test/battle/move_effect/assist.c index 6036de8e8c..3a380ef125 100644 --- a/test/battle/move_effect/assist.c +++ b/test/battle/move_effect/assist.c @@ -17,6 +17,7 @@ TO_DO_BATTLE_TEST("Assist cannot call a Mimicked move (Gen4 only)"); TO_DO_BATTLE_TEST("Assist can call a Mimicked move but not the original Mimic (Gen5+)"); TO_DO_BATTLE_TEST("Assist can call moves in unhatched Eggs (Gen5 only)"); TO_DO_BATTLE_TEST("Assist can be used by wild Pokémon in Wild Double Battles, even if the partner faints"); +TO_DO_BATTLE_TEST("Assist called move does not get boosted by Normalize"); SINGLE_BATTLE_TEST("Assist fails if there are no valid moves to choose from") { @@ -32,3 +33,27 @@ SINGLE_BATTLE_TEST("Assist fails if there are no valid moves to choose from") MESSAGE("But it failed!"); } } + +SINGLE_BATTLE_TEST("Assisted move triggers correct weakness berry") +{ + u16 item; + PARAMETRIZE { item = ITEM_CHILAN_BERRY; } + PARAMETRIZE { item = ITEM_PASSHO_BERRY; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_ASSIST, MOVE_NONE, MOVE_NONE, MOVE_NONE); } + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_SURF, MOVE_NONE, MOVE_NONE, MOVE_NONE); } + OPPONENT(SPECIES_ARON) { Item(item); } + } WHEN { + TURN { MOVE(player, MOVE_ASSIST); } + } SCENE { + MESSAGE("Wobbuffet used Assist!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ASSIST, player); + MESSAGE("Wobbuffet used Surf!"); + if (item == ITEM_PASSHO_BERRY) { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } else { + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_SURF, player); + } +} diff --git a/test/battle/move_effect/hit_set_remove_terrain.c b/test/battle/move_effect/hit_set_remove_terrain.c index 9b9180d6e4..e69de29bb2 100644 --- a/test/battle/move_effect/hit_set_remove_terrain.c +++ b/test/battle/move_effect/hit_set_remove_terrain.c @@ -1,126 +0,0 @@ -#include "global.h" -#include "test/battle.h" - -ASSUMPTIONS -{ - ASSUME(GetMoveEffect(MOVE_ELECTRIC_TERRAIN) == EFFECT_ELECTRIC_TERRAIN); - ASSUME(GetMoveEffect(MOVE_PSYCHIC_TERRAIN) == EFFECT_PSYCHIC_TERRAIN); - ASSUME(GetMoveEffect(MOVE_GRASSY_TERRAIN) == EFFECT_GRASSY_TERRAIN); - ASSUME(GetMoveEffect(MOVE_MISTY_TERRAIN) == EFFECT_MISTY_TERRAIN); - ASSUME(GetMoveEffect(MOVE_STEEL_ROLLER) == EFFECT_HIT_SET_REMOVE_TERRAIN); - ASSUME(GetMoveEffect(MOVE_ICE_SPINNER) == EFFECT_HIT_SET_REMOVE_TERRAIN); -} - -SINGLE_BATTLE_TEST("Steel Roller and Ice Spinner can remove a terrain from the field") -{ - u32 j; - static const u16 terrainMoves[] = - { - MOVE_ELECTRIC_TERRAIN, - MOVE_PSYCHIC_TERRAIN, - MOVE_GRASSY_TERRAIN, - MOVE_MISTY_TERRAIN, - }; - - u16 terrainMove = MOVE_NONE; - u16 removeTerrainMove = MOVE_NONE; - - for (j = 0; j < ARRAY_COUNT(terrainMoves); j++) - { - PARAMETRIZE { removeTerrainMove = MOVE_STEEL_ROLLER; terrainMove = terrainMoves[j]; } - PARAMETRIZE { removeTerrainMove = MOVE_ICE_SPINNER; terrainMove = terrainMoves[j]; } - } - - GIVEN { - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN { MOVE(opponent, terrainMove); MOVE(player, removeTerrainMove); } - } SCENE { - ANIMATION(ANIM_TYPE_MOVE, terrainMove, opponent); - ANIMATION(ANIM_TYPE_MOVE, removeTerrainMove, player); - switch (terrainMove) - { - case MOVE_ELECTRIC_TERRAIN: - MESSAGE("The electricity disappeared from the battlefield."); - break; - case MOVE_PSYCHIC_TERRAIN: - MESSAGE("The weirdness disappeared from the battlefield!"); - break; - case MOVE_GRASSY_TERRAIN: - MESSAGE("The grass disappeared from the battlefield."); - break; - case MOVE_MISTY_TERRAIN: - MESSAGE("The mist disappeared from the battlefield."); - break; - } - } -} - -SINGLE_BATTLE_TEST("Steel Roller fails if there is no terrain on the field") -{ - GIVEN { - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN { MOVE(player, MOVE_STEEL_ROLLER); } - } SCENE { - NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STEEL_ROLLER, player); - MESSAGE("But it failed!"); - } -} - -SINGLE_BATTLE_TEST("Ice Spinner doesn't fail if there is no terrain on the field") -{ - GIVEN { - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN { MOVE(player, MOVE_ICE_SPINNER); } - } SCENE { - ANIMATION(ANIM_TYPE_MOVE, MOVE_ICE_SPINNER, player); - NOT MESSAGE("But it failed!"); - } -} - -AI_SINGLE_BATTLE_TEST("AI will not choose Steel Roller if it might fail") -{ - u32 move; - - PARAMETRIZE { move = MOVE_ELECTRIC_TERRAIN; } - PARAMETRIZE { move = MOVE_NONE; } - - GIVEN { - AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_STEEL_ROLLER, MOVE_ICE_SHARD); } - } WHEN { - if (move == MOVE_ELECTRIC_TERRAIN) { - TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); EXPECT_MOVE(opponent, MOVE_ICE_SHARD); } - TURN { EXPECT_MOVE(opponent, MOVE_STEEL_ROLLER); } - } else { - TURN { EXPECT_MOVE(opponent, MOVE_ICE_SHARD); } - } - } -} - -AI_SINGLE_BATTLE_TEST("AI will can choose Ice Spinner regardless if there is a terrain or not") -{ - u32 move; - - PARAMETRIZE { move = MOVE_ELECTRIC_TERRAIN; } - PARAMETRIZE { move = MOVE_NONE; } - - GIVEN { - AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_ICE_SPINNER, MOVE_ICE_SHARD); } - } WHEN { - if (move == MOVE_ELECTRIC_TERRAIN) { - TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); EXPECT_MOVE(opponent, MOVE_ICE_SPINNER); } - TURN { EXPECT_MOVE(opponent, MOVE_ICE_SPINNER); } - } else { - TURN { EXPECT_MOVE(opponent, MOVE_ICE_SPINNER); } - } - } -} diff --git a/test/battle/move_effect/ice_spinner.c b/test/battle/move_effect/ice_spinner.c new file mode 100644 index 0000000000..44ce21eb4f --- /dev/null +++ b/test/battle/move_effect/ice_spinner.c @@ -0,0 +1,122 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_ICE_SPINNER) == EFFECT_ICE_SPINNER); +} + +SINGLE_BATTLE_TEST("Ice Spinner and Steel Roller remove a terrain from field") +{ + u32 j; + static const u16 terrainMoves[] = + { + MOVE_ELECTRIC_TERRAIN, + MOVE_PSYCHIC_TERRAIN, + MOVE_GRASSY_TERRAIN, + MOVE_MISTY_TERRAIN, + }; + + u16 terrainMove = MOVE_NONE; + u16 removeTerrainMove = MOVE_NONE; + + for (j = 0; j < ARRAY_COUNT(terrainMoves); j++) + { + PARAMETRIZE { removeTerrainMove = MOVE_STEEL_ROLLER; terrainMove = terrainMoves[j]; } + PARAMETRIZE { removeTerrainMove = MOVE_ICE_SPINNER; terrainMove = terrainMoves[j]; } + } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_ELECTRIC_TERRAIN) == EFFECT_ELECTRIC_TERRAIN); + ASSUME(GetMoveEffect(MOVE_PSYCHIC_TERRAIN) == EFFECT_PSYCHIC_TERRAIN); + ASSUME(GetMoveEffect(MOVE_GRASSY_TERRAIN) == EFFECT_GRASSY_TERRAIN); + ASSUME(GetMoveEffect(MOVE_MISTY_TERRAIN) == EFFECT_MISTY_TERRAIN); + ASSUME(GetMoveEffect(MOVE_STEEL_ROLLER) == EFFECT_STEEL_ROLLER); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, terrainMove); MOVE(player, removeTerrainMove); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, terrainMove, opponent); + ANIMATION(ANIM_TYPE_MOVE, removeTerrainMove, player); + switch (terrainMove) + { + case MOVE_ELECTRIC_TERRAIN: + MESSAGE("The electricity disappeared from the battlefield."); + break; + case MOVE_PSYCHIC_TERRAIN: + MESSAGE("The weirdness disappeared from the battlefield!"); + break; + case MOVE_GRASSY_TERRAIN: + MESSAGE("The grass disappeared from the battlefield."); + break; + case MOVE_MISTY_TERRAIN: + MESSAGE("The mist disappeared from the battlefield."); + break; + } + } +} + +SINGLE_BATTLE_TEST("Ice Spinner fails to remove terrain if user faints during attack execution") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LIFE_ORB); HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); MOVE(opponent, MOVE_ICE_SPINNER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ICE_SPINNER, opponent); + NOT MESSAGE("The electricity disappeared from the battlefield."); + } +} + +SINGLE_BATTLE_TEST("Ice Spinner will not be remove Terrain if user is switched out due to Red Card") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); MOVE(opponent, MOVE_ICE_SPINNER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ICE_SPINNER, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + NOT MESSAGE("The electricity disappeared from the battlefield."); + } +} + +SINGLE_BATTLE_TEST("Ice Spinner doesn't fail if there is no terrain on the field") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ICE_SPINNER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ICE_SPINNER, player); + NOT MESSAGE("But it failed!"); + } +} + +AI_SINGLE_BATTLE_TEST("Ice Spinner can be chosen by AI regardless if there is a terrain or not") +{ + u32 move; + + PARAMETRIZE { move = MOVE_ELECTRIC_TERRAIN; } + PARAMETRIZE { move = MOVE_NONE; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_ICE_SPINNER, MOVE_ICE_SHARD); } + } WHEN { + if (move == MOVE_ELECTRIC_TERRAIN) { + TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); EXPECT_MOVE(opponent, MOVE_ICE_SPINNER); } + TURN { EXPECT_MOVE(opponent, MOVE_ICE_SPINNER); } + } else { + TURN { EXPECT_MOVE(opponent, MOVE_ICE_SPINNER); } + } + } +} diff --git a/test/battle/move_effect/protect.c b/test/battle/move_effect/protect.c index e0c32e5267..09fc4c12bf 100644 --- a/test/battle/move_effect/protect.c +++ b/test/battle/move_effect/protect.c @@ -618,3 +618,20 @@ SINGLE_BATTLE_TEST("Protect: Quick Guard, Wide Guard and Crafty Shield don't red EXPECT_EQ(results[4].damage, results[5].damage); } } + +SINGLE_BATTLE_TEST("Protect: Protective Pads protects from secondary effects") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_PROTECTIVE_PADS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_BURNING_BULWARK); MOVE(player, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BURNING_BULWARK, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + STATUS_ICON(player, STATUS1_BURN); + } + } +} diff --git a/test/battle/move_effect/steel_roller.c b/test/battle/move_effect/steel_roller.c new file mode 100644 index 0000000000..4b658a6124 --- /dev/null +++ b/test/battle/move_effect/steel_roller.c @@ -0,0 +1,75 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_STEEL_ROLLER) == EFFECT_STEEL_ROLLER); +} + +// Covered in ice_spinner.c +// SINGLE_BATTLE_TEST("Ice Spinner and Steel Roller remove a terrain from field") + +SINGLE_BATTLE_TEST("Steel Roller removes Terrain even if user faints during attack execution") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LIFE_ORB); HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); MOVE(opponent, MOVE_STEEL_ROLLER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEEL_ROLLER, opponent); + MESSAGE("The electricity disappeared from the battlefield."); + } +} + +SINGLE_BATTLE_TEST("Steel Roller removes Terrain if user is switched out due to Red Card") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); MOVE(opponent, MOVE_STEEL_ROLLER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIC_TERRAIN, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEEL_ROLLER, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("The electricity disappeared from the battlefield."); + } +} + +SINGLE_BATTLE_TEST("Steel Roller will fail if there is no Terrain") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_STEEL_ROLLER); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STEEL_ROLLER, opponent); + MESSAGE("The opposing Wobbuffet used Steel Roller!"); + MESSAGE("But it failed!"); + } +} + +AI_SINGLE_BATTLE_TEST("Steel Roller wont be chosen by AI if there is no terrain on the field") +{ + u32 move; + + PARAMETRIZE { move = MOVE_ELECTRIC_TERRAIN; } + PARAMETRIZE { move = MOVE_NONE; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_STEEL_ROLLER, MOVE_ICE_SHARD); } + } WHEN { + if (move == MOVE_ELECTRIC_TERRAIN) { + TURN { MOVE(player, MOVE_ELECTRIC_TERRAIN); EXPECT_MOVE(opponent, MOVE_ICE_SHARD); } + TURN { EXPECT_MOVE(opponent, MOVE_STEEL_ROLLER); } + } else { + TURN { EXPECT_MOVE(opponent, MOVE_ICE_SHARD); } + } + } +} diff --git a/test/battle/move_effects_combined/toxic_thread.c b/test/battle/move_effects_combined/toxic_thread.c new file mode 100644 index 0000000000..ec27f46365 --- /dev/null +++ b/test/battle/move_effects_combined/toxic_thread.c @@ -0,0 +1,165 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_TOXIC_THREAD) == EFFECT_TOXIC_THREAD); +} + +SINGLE_BATTLE_TEST("Toxic Thread both reduces speed and inflicts Poison") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TOXIC_THREAD); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_THREAD, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, poison: TRUE); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Toxic Thread still inflicts Poison if speed can't go lower") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SCARY_FACE) == EFFECT_SPEED_DOWN_2); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCARY_FACE); } + TURN { MOVE(player, MOVE_SCARY_FACE); } + TURN { MOVE(player, MOVE_SCARY_FACE); } + TURN { MOVE(player, MOVE_TOXIC_THREAD); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCARY_FACE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCARY_FACE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCARY_FACE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_THREAD, player); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, poison: TRUE); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 6); + } +} + +SINGLE_BATTLE_TEST("Toxic Thread still inflicts Poison if speed can't be lowered") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_REGICE) { Ability(ABILITY_CLEAR_BODY); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC_THREAD); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_THREAD, player); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, poison: TRUE); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Toxic Thread still lowers speed if the target can't be Poisoned") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_BRONZOR].types[0] == TYPE_STEEL || gSpeciesInfo[SPECIES_BRONZOR].types[1] == TYPE_STEEL); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BRONZOR); + } WHEN { + TURN { MOVE(player, MOVE_TOXIC_THREAD); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_THREAD, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + NOT ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Toxic Thread still lowers speed if the target is already Poisoned") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_POISON_POWDER) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_POISON_POWDER) == MOVE_EFFECT_POISON); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_POISON_POWDER); } + TURN { MOVE(player, MOVE_TOXIC_THREAD); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_POWDER, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + STATUS_ICON(opponent, poison: TRUE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_THREAD, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } THEN { + EXPECT_EQ(opponent->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + } +} + +SINGLE_BATTLE_TEST("Toxic Thread fails if speed can't be lowered and status can't be inflicted") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_POISON_POWDER) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_POISON_POWDER) == MOVE_EFFECT_POISON); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SCARY_FACE); } + TURN { MOVE(player, MOVE_SCARY_FACE); } + TURN { MOVE(player, MOVE_SCARY_FACE); } + TURN { MOVE(player, MOVE_POISON_POWDER); } + TURN { MOVE(player, MOVE_TOXIC_THREAD); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCARY_FACE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCARY_FACE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCARY_FACE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_POWDER, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_THREAD, player); + } +} + +SINGLE_BATTLE_TEST("Toxic Thread fails if speed can't be lowered due to Clear Body and status can't be inflicted") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_POISON_POWDER) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_POISON_POWDER) == MOVE_EFFECT_POISON); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_REGICE) { Ability(ABILITY_CLEAR_BODY); } + } WHEN { + TURN { MOVE(player, MOVE_POISON_POWDER); } + TURN { MOVE(player, MOVE_TOXIC_THREAD); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_POWDER, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_THREAD, player); + } +} + +SINGLE_BATTLE_TEST("Toxic Thread fails if speed can't be lowered and target is a poison type") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_POISON_POWDER) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_POISON_POWDER) == MOVE_EFFECT_POISON); + ASSUME(gSpeciesInfo[SPECIES_ODDISH].types[0] == TYPE_POISON || gSpeciesInfo[SPECIES_ODDISH].types[1] == TYPE_POISON); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ODDISH); + } WHEN { + TURN { MOVE(player, MOVE_SCARY_FACE); } + TURN { MOVE(player, MOVE_SCARY_FACE); } + TURN { MOVE(player, MOVE_SCARY_FACE); } + TURN { MOVE(player, MOVE_TOXIC_THREAD); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCARY_FACE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCARY_FACE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCARY_FACE, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_THREAD, player); + } +} diff --git a/test/battle/status1/poison.c b/test/battle/status1/poison.c index 0a749ea96d..97b8782590 100644 --- a/test/battle/status1/poison.c +++ b/test/battle/status1/poison.c @@ -27,7 +27,6 @@ SINGLE_BATTLE_TEST("Poison can't bad poison a poison or steel type") GIVEN { ASSUME(GetMoveEffect(MOVE_POISON_GAS) == EFFECT_NON_VOLATILE_STATUS); ASSUME(GetMoveNonVolatileStatus(MOVE_POISON_GAS) == MOVE_EFFECT_POISON); - ASSUME(GetMoveNonVolatileStatus(MOVE_POISON_GAS) == MOVE_EFFECT_POISON); PLAYER(SPECIES_WOBBUFFET); OPPONENT(species); } WHEN { diff --git a/test/pokemon.c b/test/pokemon.c index 4e8a1da7fa..ac57314f9c 100644 --- a/test/pokemon.c +++ b/test/pokemon.c @@ -82,10 +82,14 @@ TEST("Shininess independent from PID and OTID") TEST("Hyper Training increases stats without affecting IVs") { - u32 data, hp, atk, def, speed, spatk, spdef; + u32 data, hp, atk, def, speed, spatk, spdef, friendship = 0; struct Pokemon mon; CreateMon(&mon, SPECIES_WOBBUFFET, 100, 3, TRUE, 0, OT_ID_PRESET, 0); + // Consider B_FRIENDSHIP_BOOST. + SetMonData(&mon, MON_DATA_FRIENDSHIP, &friendship); + CalculateMonStats(&mon); + hp = GetMonData(&mon, MON_DATA_HP); atk = GetMonData(&mon, MON_DATA_ATK); def = GetMonData(&mon, MON_DATA_DEF); @@ -142,8 +146,13 @@ TEST("Status1 round-trips through BoxPokemon") TEST("canhypertrain/hypertrain affect MON_DATA_HYPER_TRAINED_* and recalculate stats") { - u32 atk; + u32 atk, friendship = 0; CreateMon(&gPlayerParty[0], SPECIES_WOBBUFFET, 100, 0, FALSE, 0, OT_ID_PRESET, 0); + + // Consider B_FRIENDSHIP_BOOST. + SetMonData(&gPlayerParty[0], MON_DATA_FRIENDSHIP, &friendship); + CalculateMonStats(&gPlayerParty[0]); + atk = GetMonData(&gPlayerParty[0], MON_DATA_ATK); RUN_OVERWORLD_SCRIPT( diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index 7884993a1c..20271a588d 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -1563,6 +1563,13 @@ void OpenPokemon(u32 sourceLine, u32 side, u32 species) data = MOVE_NONE; for (i = 0; i < MAX_MON_MOVES; i++) SetMonData(DATA.currentMon, MON_DATA_MOVE1 + i, &data); + data = 0; + if (B_FRIENDSHIP_BOOST) + { + // This way, we avoid the boost affecting tests unless explicitly stated. + SetMonData(DATA.currentMon, MON_DATA_FRIENDSHIP, &data); + CalculateMonStats(DATA.currentMon); + } } // (sNaturePersonalities[i] % NUM_NATURES) == i