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 💻 |
 poetahto 💻 |
 lordraindance2 💻 |
+  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