diff --git a/INSTALL.md b/INSTALL.md
index 53d284107..e74706d6a 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -125,19 +125,53 @@ Otherwise, ask for help on Discord or IRC (see [README.md](README.md)), or conti
Note that in msys2, Copy is Ctrl+Insert and Paste is Shift+Insert.
-1. Open msys2 at C:\devkitPro\msys2\mingw64.exe or run `C:\devkitPro\msys2\msys2_shell.bat -mingw64`.
+1. Open msys2 at C:\devkitPro\msys2\msys2_shell.bat.
-2. Certain packages are required to build pokeemerald. Install these by running the following command:
+2. Certain packages are required to build pokeemerald. Install these by running the following two commands:
```bash
- pacman -S make zlib-devel git mingw-w64-x86_64-gcc mingw-w64-x86_64-libpng
+ pacman -Sy msys2-keyring
+ pacman -S make gcc zlib-devel git
```
Note...
- > This command will ask for confirmation, just enter the yes action when prompted.
+ > The commands will ask for confirmation, just enter the yes action when prompted.
+3. Download [libpng](https://sourceforge.net/projects/libpng/files/libpng16/1.6.37/libpng-1.6.37.tar.xz/download).
+
+4. Change directory to where libpng was downloaded. By default, msys2 will start in the current user's profile folder, located at **C:\Users\\_\_**, where *\* is your Windows username. In most cases, libpng should be saved within a subfolder of the profile folder. For example, if libpng was saved to **C:\Users\\_\_\Downloads** (the Downloads location for most users), enter this command:
+
+ ```bash
+ cd Downloads
+ ```
+
+
+ Notes...
+
+ > Note 1: While not shown, msys uses forward slashes `/` instead of backwards slashes `\` as the directory separator.
+ > Note 2: If the path has spaces, then the path must be wrapped with quotations, e.g. `cd "Downloads/My Downloads"`.
+ > Note 3: Windows path names are case-insensitive so adhering to capitalization isn’t needed.
+ > Note 4: If libpng was saved elsewhere, you will need to specify the full path to where libpng was downloaded, e.g. `cd c:/devkitpro/msys2` if it was saved there.
+
+
+5. Run the following commands to uncompress and install libpng.
+
+ ```bash
+ tar xf libpng-1.6.37.tar.xz
+ cd libpng-1.6.37
+ ./configure --prefix=/usr
+ make check
+ make install
+ ```
+
+6. Then finally, run the following command to change back to the user profile folder.
+
+ ```bash
+ cd
+ ```
+
### Choosing where to store pokeemerald (msys2)
At this point, you can choose a folder to store pokeemerald into. If you're okay with storing pokeemerald in the user profile folder, then proceed to [Installation](#installation). Otherwise, you'll need to account for where pokeemerald is stored when changing directory to the pokeemerald folder.
diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc
index 17163db37..275cc40a8 100644
--- a/asm/macros/battle_script.inc
+++ b/asm/macros/battle_script.inc
@@ -1053,8 +1053,9 @@
.byte 0xca
.endm
- .macro setcharge
+ .macro setcharge battler:req
.byte 0xcb
+ .byte \battler
.endm
.macro callterrainattack
diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s
index 85ed81551..8a574a70f 100644
--- a/data/battle_scripts_1.s
+++ b/data/battle_scripts_1.s
@@ -3019,12 +3019,9 @@ BattleScript_TryTailwindAbilitiesLoop_WindRider:
BattleScript_TryTailwindAbilitiesLoop_WindPower:
call BattleScript_AbilityPopUp
- copybyte sSAVED_BATTLER, gBattlerAttacker
- copybyte gBattlerAttacker, gBattlerTarget
- setcharge
+ setcharge BS_TARGET
printstring STRINGID_BEINGHITCHARGEDPKMNWITHPOWER
waitmessage B_WAIT_TIME_LONG
- copybyte gBattlerAttacker, sSAVED_BATTLER
goto BattleScript_TryTailwindAbilitiesLoop_Increment
BattleScript_EffectMircleEye:
@@ -5760,7 +5757,7 @@ BattleScript_EffectCharge::
attackcanceler
attackstring
ppreduce
- setcharge
+ setcharge BS_ATTACKER
attackanimation
waitanimation
.if B_CHARGE_SPDEF_RAISE >= GEN_5
@@ -6827,27 +6824,6 @@ BattleScript_TailwindEnds::
waitmessage B_WAIT_TIME_LONG
end2
-BattleScript_WindPowerActivatesEnd2::
- setbyte gBattlerAttacker, 0
-BattleScript_WindPowerLoop:
- printstring STRINGID_EMPTYSTRING3
- jumpifability BS_ATTACKER, ABILITY_WIND_POWER, BattleScript_WindPowerLoop_Cont
- goto BattleScript_WindPowerIncrement
-BattleScript_WindPowerLoop_Cont:
- jumpifstatus3 BS_ATTACKER, STATUS3_CHARGED_UP, BattleScript_WindPowerIncrement
- goto BattleScript_WindPower_Activate
-BattleScript_WindPower_Activate:
- call BattleScript_AbilityPopUp
- setcharge
- printstring STRINGID_BEINGHITCHARGEDPKMNWITHPOWER
- waitmessage B_WAIT_TIME_LONG
-BattleScript_WindPowerIncrement:
- addbyte gBattlerAttacker, 1
- jumpifbytenotequal gBattlerAttacker, gBattlersCount, BattleScript_WindPowerLoop
-BattleScript_WindPowerEnd:
- destroyabilitypopup
- end2
-
BattleScript_TrickRoomEnds::
printstring STRINGID_TRICKROOMENDS
waitmessage B_WAIT_TIME_LONG
@@ -7309,11 +7285,8 @@ BattleScript_AngerShellRet:
return
BattleScript_WindPowerActivates::
-.if B_CHECK_IF_CHARGED_UP == TRUE
- jumpifstatus3 BS_ATTACKER, STATUS3_CHARGED_UP, BattleScript_WindPowerActivates_Ret
-.endif
call BattleScript_AbilityPopUp
- setcharge
+ setcharge BS_TARGET
printstring STRINGID_BEINGHITCHARGEDPKMNWITHPOWER
waitmessage B_WAIT_TIME_LONG
BattleScript_WindPowerActivates_Ret:
@@ -8672,15 +8645,14 @@ BattleScript_DesolateLandActivates::
call BattleScript_ActivateWeatherAbilities
end3
-BattleScript_DesolateLandEvaporatesWaterTypeMoves::
- accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE
+BattleScript_PrimalWeatherBlocksMove::
+ jumpifword CMP_COMMON_BITS, gHitMarker, HITMARKER_ATTACKSTRING_PRINTED, BattleScript_MoveEnd @in case of multi-target moves, if move fails once, no point in printing the message twice
+ accuracycheck BattleScript_PrintMoveMissed, NO_ACC_CALC_CHECK_LOCK_ON
attackstring
pause B_WAIT_TIME_SHORT
ppreduce
- jumpifword CMP_COMMON_BITS, gHitMarker, HITMARKER_STRING_PRINTED, BattleScript_MoveEnd
- printstring STRINGID_MOVEEVAPORATEDINTHEHARSHSUNLIGHT
+ printfromtable gPrimalWeatherBlocksStringIds
waitmessage B_WAIT_TIME_LONG
- orword gHitMarker, HITMARKER_STRING_PRINTED
goto BattleScript_MoveEnd
BattleScript_PrimordialSeaActivates::
@@ -8692,17 +8664,6 @@ BattleScript_PrimordialSeaActivates::
call BattleScript_ActivateWeatherAbilities
end3
-BattleScript_PrimordialSeaFizzlesOutFireTypeMoves::
- accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE
- attackstring
- pause B_WAIT_TIME_SHORT
- ppreduce
- jumpifword CMP_COMMON_BITS, gHitMarker, HITMARKER_STRING_PRINTED, BattleScript_MoveEnd
- printstring STRINGID_MOVEFIZZLEDOUTINTHEHEAVYRAIN
- waitmessage B_WAIT_TIME_LONG
- orword gHitMarker, HITMARKER_STRING_PRINTED
- goto BattleScript_MoveEnd
-
BattleScript_DeltaStreamActivates::
pause B_WAIT_TIME_SHORT
call BattleScript_AbilityPopUp
@@ -10334,6 +10295,7 @@ BattleScript_SymbiosisActivates::
return
BattleScript_TargetAbilityStatRaiseRet::
+ copybyte sSAVED_BATTLER, gBattlerAttacker
copybyte gBattlerAbility, gEffectBattler
copybyte gBattlerAttacker, gBattlerTarget
call BattleScript_AbilityPopUp
@@ -10341,6 +10303,7 @@ BattleScript_TargetAbilityStatRaiseRet::
setgraphicalstatchangevalues
call BattleScript_StatUp
BattleScript_TargetAbilityStatRaiseRet_End:
+ copybyte gBattlerAttacker, sSAVED_BATTLER
return
BattleScript_PokemonCantUseTheMove::
diff --git a/include/battle_scripts.h b/include/battle_scripts.h
index f34f80139..bcc8632a4 100644
--- a/include/battle_scripts.h
+++ b/include/battle_scripts.h
@@ -409,9 +409,8 @@ extern const u8 BattleScript_GulpMissileGorging[];
extern const u8 BattleScript_GulpMissileGulping[];
extern const u8 BattleScript_BattleBondActivatesOnMoveEndAttacker[];
extern const u8 BattleScript_DesolateLandActivates[];
-extern const u8 BattleScript_DesolateLandEvaporatesWaterTypeMoves[];
extern const u8 BattleScript_PrimordialSeaActivates[];
-extern const u8 BattleScript_PrimordialSeaFizzlesOutFireTypeMoves[];
+extern const u8 BattleScript_PrimalWeatherBlocksMove[];
extern const u8 BattleScript_DeltaStreamActivates[];
extern const u8 BattleScript_MysteriousAirCurrentBlowsOn[];
extern const u8 BattleScript_AttackWeakenedByStrongWinds[];
diff --git a/include/battle_util.h b/include/battle_util.h
index 7ae9577b7..9de5d6ded 100644
--- a/include/battle_util.h
+++ b/include/battle_util.h
@@ -136,8 +136,8 @@ u8 DoBattlerEndTurnEffects(void);
bool8 HandleWishPerishSongOnTurnEnd(void);
bool8 HandleFaintedMonActions(void);
void TryClearRageAndFuryCutter(void);
+u8 AtkCanceller_UnableToUseMove(u32 moveType);
void SetAtkCancellerForCalledMove(void);
-u8 AtkCanceller_UnableToUseMove(void);
u8 AtkCanceller_UnableToUseMove2(void);
bool8 HasNoMonsToSwitch(u8 battlerId, u8 r1, u8 r2);
bool32 TryChangeBattleWeather(u8 battler, u32 weatherEnumId, bool32 viaAbility);
diff --git a/include/config/battle.h b/include/config/battle.h
index d92c1e2a7..68cab28b6 100644
--- a/include/config/battle.h
+++ b/include/config/battle.h
@@ -122,7 +122,6 @@
#define B_PLUS_MINUS_INTERACTION GEN_LATEST // In Gen5+, Plus and Minus can be activated with themselves and the opposite ability. Before, only the opposing ability could activate it.
#define B_WEATHER_FORMS GEN_LATEST // In Gen5+, Castform and Cherrim revert to their base form upon losing their respective ability. Cherrim needs Flower Gift to swap forms.
#define B_SYMBIOSIS_GEMS GEN_LATEST // In Gen7+, Symbiosis passes an item after a gem-boosted attack. Previously, items are passed before the gem-boosted attack hits, making the item effect apply.
-#define B_CHECK_IF_CHARGED_UP TRUE // If set to TRUE, certain abilities such as Electromorphosis WILL check if the STATUS3_CHARGED_UP status flag is applied.
#define B_ABSORBING_ABILITY_STRING GEN_LATEST // In Gen5+, the abilities that absorb moves of a certain type use a generic string for stat increases and decreases.
#define B_LEAF_GUARD_PREVENTS_REST GEN_LATEST // In Gen5+, Leaf Guard prevents the use of Rest in harsh sunlight.
#define B_SNOW_WARNING GEN_LATEST // In Gen9+, Snow Warning will summon snow instead of hail.
diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h
index 5c8dc865d..97284d6fe 100644
--- a/include/constants/battle_string_ids.h
+++ b/include/constants/battle_string_ids.h
@@ -828,6 +828,10 @@
#define B_MSG_SOMEONES_BOX_FULL 2
#define B_MSG_LANETTES_BOX_FULL 3
+// gPrimalWeatherBlocksStringIds
+#define B_MSG_PRIMAL_WEATHER_FIZZLED_BY_RAIN 0
+#define B_MSG_PRIMAL_WEATHER_EVAPORATED_IN_SUN 1
+
// gInobedientStringIds
#define B_MSG_LOAFING 0
#define B_MSG_WONT_OBEY 1
diff --git a/include/decompress.h b/include/decompress.h
index ef2ec43b0..1ded221eb 100644
--- a/include/decompress.h
+++ b/include/decompress.h
@@ -3,7 +3,7 @@
#include "sprite.h"
-extern u8 gDecompressionBuffer[0x4000];
+extern u8 ALIGNED(4) gDecompressionBuffer[0x4000];
void LZDecompressWram(const u32 *src, void *dest);
void LZDecompressVram(const u32 *src, void *dest);
diff --git a/include/gba/m4a_internal.h b/include/gba/m4a_internal.h
index 40a25ba05..fc8205efd 100644
--- a/include/gba/m4a_internal.h
+++ b/include/gba/m4a_internal.h
@@ -213,7 +213,7 @@ struct SoundInfo
ExtVolPitFunc ExtVolPit;
u8 gap2[16];
struct SoundChannel chans[MAX_DIRECTSOUND_CHANNELS];
- s8 pcmBuffer[PCM_DMA_BUF_SIZE * 2];
+ s8 ALIGNED(4) pcmBuffer[PCM_DMA_BUF_SIZE * 2];
};
struct SongHeader
diff --git a/include/gba/macro.h b/include/gba/macro.h
index 3b35a1946..5239cd4c8 100644
--- a/include/gba/macro.h
+++ b/include/gba/macro.h
@@ -1,7 +1,7 @@
#ifndef GUARD_GBA_MACRO_H
#define GUARD_GBA_MACRO_H
-#define CPU_FILL(value, dest, size, bit) \
+#define CPU_FILL_UNCHECKED(value, dest, size, bit) \
{ \
vu##bit tmp = (vu##bit)(value); \
CpuSet((void *)&tmp, \
@@ -9,10 +9,33 @@
CPU_SET_##bit##BIT | CPU_SET_SRC_FIXED | ((size)/(bit/8) & 0x1FFFFF)); \
}
+#if MODERN
+#define CPU_FILL(value, dest, size, bit) \
+ do \
+ { \
+ _Static_assert(_Alignof(dest) >= (bit / 8), "destination potentially unaligned"); \
+ CPU_FILL_UNCHECKED(value, dest, size, bit); \
+ } while (0)
+#else
+#define CPU_FILL(value, dest, size, bit) CPU_FILL_UNCHECKED(value, dest, size, bit)
+#endif
+
#define CpuFill16(value, dest, size) CPU_FILL(value, dest, size, 16)
#define CpuFill32(value, dest, size) CPU_FILL(value, dest, size, 32)
-#define CPU_COPY(src, dest, size, bit) CpuSet(src, dest, CPU_SET_##bit##BIT | ((size)/(bit/8) & 0x1FFFFF))
+#define CPU_COPY_UNCHECKED(src, dest, size, bit) CpuSet(src, dest, CPU_SET_##bit##BIT | ((size)/(bit/8) & 0x1FFFFF))
+
+#if MODERN
+#define CPU_COPY(src, dest, size, bit) \
+ do \
+ { \
+ _Static_assert(_Alignof(src) >= (bit / 8), "source potentially unaligned"); \
+ _Static_assert(_Alignof(dest) >= (bit / 8), "destination potentially unaligned"); \
+ CPU_COPY_UNCHECKED(src, dest, size, bit); \
+ } while (0)
+#else
+#define CPU_COPY(src, dest, size, bit) CPU_COPY_UNCHECKED(src, dest, size, bit)
+#endif
#define CpuCopy16(src, dest, size) CPU_COPY(src, dest, size, 16)
#define CpuCopy32(src, dest, size) CPU_COPY(src, dest, size, 32)
@@ -31,7 +54,7 @@
#define CpuFastCopy(src, dest, size) CpuFastSet(src, dest, ((size)/(32/8) & 0x1FFFFF))
-#define DmaSet(dmaNum, src, dest, control) \
+#define DmaSetUnchecked(dmaNum, src, dest, control) \
{ \
vu32 *dmaRegs = (vu32 *)REG_ADDR_DMA##dmaNum; \
dmaRegs[0] = (vu32)(src); \
@@ -40,7 +63,21 @@
dmaRegs[2]; \
}
-#define DMA_FILL(dmaNum, value, dest, size, bit) \
+#if MODERN
+// NOTE: Assumes 16-bit DMAs.
+#define DmaSet(dmaNum, src, dest, control) \
+ do \
+ { \
+ _Static_assert(_Alignof(src) >= __builtin_choose_expr(__builtin_constant_p(control), ((control) & (DMA_32BIT << 16)) ? 4 : 2, 2), "source potentially unaligned"); \
+ _Static_assert(_Alignof(dest) >= __builtin_choose_expr(__builtin_constant_p(control), ((control) & (DMA_32BIT << 16)) ? 4 : 2, 2), "destination potentially unaligned"); \
+ DmaSetUnchecked(dmaNum, src, dest, control); \
+ } while (0)
+#else
+#define DmaSet(dmaNum, src, dest, control) \
+ DmaSetUnchecked(dmaNum, src, dest, control)
+#endif
+
+#define DMA_FILL_UNCHECKED(dmaNum, value, dest, size, bit) \
{ \
vu##bit tmp = (vu##bit)(value); \
DmaSet(dmaNum, \
@@ -50,6 +87,17 @@
| ((size)/(bit/8))); \
}
+#if MODERN
+#define DMA_FILL(dmaNum, value, dest, size, bit) \
+ do \
+ { \
+ _Static_assert(_Alignof(dest) >= (bit / 8), "destination potentially unaligned"); \
+ DMA_FILL_UNCHECKED(dmaNum, value, dest, size, bit); \
+ } while (0)
+#else
+#define DMA_FILL(dmaNum, value, dest, size, bit) DMA_FILL_UNCHECKED(dmaNum, value, dest, size, bit)
+#endif
+
#define DmaFill16(dmaNum, value, dest, size) DMA_FILL(dmaNum, value, dest, size, 16)
#define DmaFill32(dmaNum, value, dest, size) DMA_FILL(dmaNum, value, dest, size, 32)
@@ -58,23 +106,46 @@
// unit size (2 or 4 bytes) and then combined with the DMA control flags using a
// bitwise OR operation.
-#define DMA_CLEAR(dmaNum, dest, size, bit) \
+#define DMA_CLEAR_UNCHECKED(dmaNum, dest, size, bit) \
{ \
vu##bit *_dest = (vu##bit *)(dest); \
u32 _size = size; \
DmaFill##bit(dmaNum, 0, _dest, _size); \
}
+#if MODERN
+#define DMA_CLEAR(dmaNum, dest, size, bit) \
+ do \
+ { \
+ _Static_assert(_Alignof(dest) >= (bit / 8), "destination potentially unaligned"); \
+ DMA_CLEAR_UNCHECKED(dmaNum, dest, size, bit); \
+ } while (0)
+#else
+#define DMA_CLEAR(dmaNum, dest, size, bit) DMA_CLEAR_UNCHECKED(dmaNum, dest, size, bit)
+#endif
+
#define DmaClear16(dmaNum, dest, size) DMA_CLEAR(dmaNum, dest, size, 16)
#define DmaClear32(dmaNum, dest, size) DMA_CLEAR(dmaNum, dest, size, 32)
-#define DMA_COPY(dmaNum, src, dest, size, bit) \
+#define DMA_COPY_UNCHECKED(dmaNum, src, dest, size, bit) \
DmaSet(dmaNum, \
src, \
dest, \
(DMA_ENABLE | DMA_START_NOW | DMA_##bit##BIT | DMA_SRC_INC | DMA_DEST_INC) << 16 \
| ((size)/(bit/8)))
+#if MODERN
+#define DMA_COPY(dmaNum, src, dest, size, bit) \
+ do \
+ { \
+ _Static_assert(_Alignof(src) >= (bit / 8), "source potentially unaligned"); \
+ _Static_assert(_Alignof(dest) >= (bit / 8), "destination potentially unaligned"); \
+ DMA_COPY_UNCHECKED(dmaNum, src, dest, size, bit); \
+ } while (0)
+#else
+#define DMA_COPY(dmaNum, src, dest, size, bit) DMA_COPY_UNCHECKED(dmaNum, src, dest, size, bit)
+#endif
+
#define DmaCopy16(dmaNum, src, dest, size) DMA_COPY(dmaNum, src, dest, size, 16)
#define DmaCopy32(dmaNum, src, dest, size) DMA_COPY(dmaNum, src, dest, size, 32)
diff --git a/include/gba/syscall.h b/include/gba/syscall.h
index 56cd4ba58..c922084d5 100644
--- a/include/gba/syscall.h
+++ b/include/gba/syscall.h
@@ -27,10 +27,32 @@ u16 ArcTan2(s16 x, s16 y);
void CpuSet(const void *src, void *dest, u32 control);
+#if MODERN
+// NOTE: Assumes 16-bit CpuSets unless control is a constant and has
+// CPU_SET_32BIT set.
+#define CpuSet(src, dest, control) \
+ do \
+ { \
+ _Static_assert(_Alignof(src) >= __builtin_choose_expr(__builtin_constant_p(control), ((control) & CPU_SET_32BIT) ? 4 : 2, 2), "source potentially unaligned"); \
+ _Static_assert(_Alignof(dest) >= __builtin_choose_expr(__builtin_constant_p(control), ((control) & CPU_SET_32BIT) ? 4 : 2, 2), "destination potentially unaligned"); \
+ CpuSet(src, dest, control); \
+ } while (0)
+#endif
+
#define CPU_FAST_SET_SRC_FIXED 0x01000000
void CpuFastSet(const void *src, void *dest, u32 control);
+#if MODERN
+#define CpuFastSet(src, dest, control) \
+ do \
+ { \
+ _Static_assert(_Alignof(src) >= 4, "source potentially unaligned"); \
+ _Static_assert(_Alignof(dest) >= 4, "destination potentially unaligned"); \
+ CpuFastSet(src, dest, control); \
+ } while (0)
+#endif
+
void BgAffineSet(struct BgAffineSrcData *src, struct BgAffineDstData *dest, s32 count);
void ObjAffineSet(struct ObjAffineSrcData *src, void *dest, s32 count, s32 offset);
diff --git a/include/global.h b/include/global.h
index d30ca045e..41c1e5924 100644
--- a/include/global.h
+++ b/include/global.h
@@ -21,7 +21,6 @@
#define BLOCK_CROSS_JUMP asm("");
// to help in decompiling
-#define asm_comment(x) asm volatile("@ -- " x " -- ")
#define asm_unified(x) asm(".syntax unified\n" x "\n.syntax divided")
#define NAKED __attribute__((naked))
@@ -116,6 +115,8 @@
// Calls m0/m1/.../m8 depending on how many arguments are passed.
#define VARARG_8(m, ...) CAT(m, NARG_8(__VA_ARGS__))(__VA_ARGS__)
+
+// This returns the number of arguments passed to it (up to 8).
#define NARG_8(...) NARG_8_(_, ##__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define NARG_8_(_, a, b, c, d, e, f, g, h, N, ...) N
@@ -831,7 +832,7 @@ struct WaldaPhrase
struct TrainerNameRecord
{
u32 trainerId;
- u8 trainerName[PLAYER_NAME_LENGTH + 1];
+ u8 ALIGNED(2) trainerName[PLAYER_NAME_LENGTH + 1];
};
struct TrainerHillSave
diff --git a/include/graphics.h b/include/graphics.h
index 25b3fd501..ff1a600f0 100644
--- a/include/graphics.h
+++ b/include/graphics.h
@@ -9079,7 +9079,7 @@ extern const u32 gIntroGroudon_Gfx[];
extern const u32 gIntroGroudon_Tilemap[];
extern const u32 gIntroLegendBg_Gfx[];
extern const u32 gIntroGroudonBg_Tilemap[];
-extern const u8 gIntro3Bg_Pal[0x200];
+extern const u8 ALIGNED(2) gIntro3Bg_Pal[0x200];
extern const u32 gIntroKyogre_Gfx[];
extern const u32 gIntroKyogre_Tilemap[];
extern const u32 gIntroKyogreBg_Tilemap[];
@@ -10591,8 +10591,8 @@ extern const u32 gPokenavOptions_Gfx[];
extern const u16 gPokenavOptions_Pal[];
// Battle Factory Screen
-extern const u8 gFrontierFactorySelectMenu_Gfx[];
-extern const u8 gFrontierFactorySelectMenu_Tilemap[];
+extern const u16 gFrontierFactorySelectMenu_Gfx[];
+extern const u16 gFrontierFactorySelectMenu_Tilemap[];
extern const u16 gFrontierFactorySelectMenu_Pal[];
// Object event pals
diff --git a/include/item_menu.h b/include/item_menu.h
index ce03cdacb..09ddd729c 100644
--- a/include/item_menu.h
+++ b/include/item_menu.h
@@ -78,7 +78,7 @@ struct BagMenu
u8 numShownItems[POCKETS_COUNT];
s16 graphicsLoadState;
u8 unused2[14];
- u8 pocketNameBuffer[32][32];
+ u8 ALIGNED(4) pocketNameBuffer[32][32];
u8 unused3[4];
};
diff --git a/include/librfu.h b/include/librfu.h
index 0026adece..6b0bd97c7 100644
--- a/include/librfu.h
+++ b/include/librfu.h
@@ -329,7 +329,7 @@ struct RfuIntrStruct
{
union RfuPacket rxPacketAlloc;
union RfuPacket txPacketAlloc;
- u8 block1[0x960]; // size of librfu_intr.s binary
+ u8 ALIGNED(2) block1[0x960]; // size of librfu_intr.s binary
struct STWIStatus block2;
};
diff --git a/include/link.h b/include/link.h
index f27cddc62..66dd5fecd 100644
--- a/include/link.h
+++ b/include/link.h
@@ -238,7 +238,7 @@ struct BlockRequest
};
extern struct Link gLink;
-extern u16 gRecvCmds[MAX_RFU_PLAYERS][CMD_LENGTH];
+extern u16 ALIGNED(4) gRecvCmds[MAX_RFU_PLAYERS][CMD_LENGTH];
extern u8 gBlockSendBuffer[BLOCK_BUFFER_SIZE];
extern u16 gLinkType;
extern u32 gLinkStatus;
diff --git a/include/mon_markings.h b/include/mon_markings.h
index fda7ad563..dbb53f8e8 100644
--- a/include/mon_markings.h
+++ b/include/mon_markings.h
@@ -18,7 +18,7 @@ struct MonMarkingsMenu
struct Sprite *textSprite;
const u8 *frameTiles;
const u16 *framePalette;
- u8 windowSpriteTiles[0x1000];
+ u8 ALIGNED(2) windowSpriteTiles[0x1000];
u8 unused[0x80];
u8 tileLoadState;
};
diff --git a/include/palette.h b/include/palette.h
index d23a658b4..15c92cc2a 100644
--- a/include/palette.h
+++ b/include/palette.h
@@ -54,9 +54,9 @@ struct PaletteFadeControl
extern struct PaletteFadeControl gPaletteFade;
extern u32 gPlttBufferTransferPending;
-extern u8 gPaletteDecompressionBuffer[];
-extern u16 gPlttBufferUnfaded[PLTT_BUFFER_SIZE];
-extern u16 gPlttBufferFaded[PLTT_BUFFER_SIZE];
+extern u8 ALIGNED(4) gPaletteDecompressionBuffer[];
+extern u16 ALIGNED(4) gPlttBufferUnfaded[PLTT_BUFFER_SIZE];
+extern u16 ALIGNED(4) gPlttBufferFaded[PLTT_BUFFER_SIZE];
void LoadCompressedPalette(const u32 *src, u16 offset, u16 size);
void LoadPalette(const void *src, u16 offset, u16 size);
diff --git a/include/pokeball.h b/include/pokeball.h
index 815221c89..602e9f973 100644
--- a/include/pokeball.h
+++ b/include/pokeball.h
@@ -49,7 +49,7 @@ extern const struct SpriteTemplate gBallSpriteTemplates[];
#define POKEBALL_OPPONENT_SENDOUT 0xFE
u8 DoPokeballSendOutAnimation(s16 pan, u8 kindOfThrow);
-void CreatePokeballSpriteToReleaseMon(u8 monSpriteId, u8 monPalNum, u8 x, u8 y, u8 oamPriority, u8 subpriortiy, u8 delay, u32 fadePalettes, u16 species);
+void CreatePokeballSpriteToReleaseMon(u8 monSpriteId, u8 monPalNum, u8 x, u8 y, u8 oamPriority, u8 subpriority, u8 delay, u32 fadePalettes, u16 species);
u8 CreateTradePokeballSprite(u8 monSpriteId, u8 monPalNum, u8 x, u8 y, u8 oamPriority, u8 subPriority, u8 delay, u32 fadePalettes);
void StartHealthboxSlideIn(u8 battler);
void DoHitAnimHealthboxEffect(u8 battler);
diff --git a/include/pokemon.h b/include/pokemon.h
index 90a6999b6..3341ad792 100644
--- a/include/pokemon.h
+++ b/include/pokemon.h
@@ -501,12 +501,17 @@ void SetMultiuseSpriteTemplateToPokemon(u16 speciesTag, u8 battlerPosition);
void SetMultiuseSpriteTemplateToTrainerBack(u16 trainerSpriteId, u8 battlerPosition);
void SetMultiuseSpriteTemplateToTrainerFront(u16 trainerPicId, u8 battlerPosition);
-// These are full type signatures for GetMonData() and GetBoxMonData(),
-// but they are not used since some code erroneously omits the third arg.
-// u32 GetMonData(struct Pokemon *mon, s32 field, u8 *data);
-// u32 GetBoxMonData(struct BoxPokemon *boxMon, s32 field, u8 *data);
-u32 GetMonData();
-u32 GetBoxMonData();
+/* GameFreak called Get(Box)MonData with either 2 or 3 arguments, for
+ * type safety we have a Get(Box)MonData macro which dispatches to
+ * either Get(Box)MonData2 or Get(Box)MonData3 based on the number of
+ * arguments. The two functions are aliases of each other, but they
+ * differ for matching purposes in the caller's codegen. */
+#define GetMonData(...) CAT(GetMonData, NARG_8(__VA_ARGS__))(__VA_ARGS__)
+#define GetBoxMonData(...) CAT(GetBoxMonData, NARG_8(__VA_ARGS__))(__VA_ARGS__)
+u32 GetMonData3(struct Pokemon *mon, s32 field, u8 *data);
+u32 GetMonData2(struct Pokemon *mon, s32 field);
+u32 GetBoxMonData3(struct BoxPokemon *boxMon, s32 field, u8 *data);
+u32 GetBoxMonData2(struct BoxPokemon *boxMon, s32 field);
void SetMonData(struct Pokemon *mon, s32 field, const void *dataArg);
void SetBoxMonData(struct BoxPokemon *boxMon, s32 field, const void *dataArg);
diff --git a/include/scanline_effect.h b/include/scanline_effect.h
index ae534d969..80d9df764 100644
--- a/include/scanline_effect.h
+++ b/include/scanline_effect.h
@@ -37,7 +37,7 @@ struct ScanlineEffect
extern struct ScanlineEffect gScanlineEffect;
-extern u16 gScanlineEffectRegBuffers[2][0x3C0];
+extern u16 ALIGNED(4) gScanlineEffectRegBuffers[2][0x3C0];
void ScanlineEffect_Stop(void);
void ScanlineEffect_Clear(void);
diff --git a/src/battle_factory_screen.c b/src/battle_factory_screen.c
index 88c655b8d..c31601944 100644
--- a/src/battle_factory_screen.c
+++ b/src/battle_factory_screen.c
@@ -268,7 +268,7 @@ static const u8 sActionHighlightMiddle_Gfx[] = INCBIN_U8( "graphics/battle_front
static const u8 sActionHighlightRight_Gfx[] = INCBIN_U8( "graphics/battle_frontier/factory_screen/action_highlight_right.4bpp");
static const u8 sMonPicBgAnim_Gfx[] = INCBIN_U8( "graphics/battle_frontier/factory_screen/mon_pic_bg_anim.4bpp");
static const u8 sMonPicBg_Tilemap[] = INCBIN_U8( "graphics/battle_frontier/factory_screen/mon_pic_bg.bin");
-static const u8 sMonPicBg_Gfx[] = INCBIN_U8( "graphics/battle_frontier/factory_screen/mon_pic_bg.4bpp");
+static const u16 sMonPicBg_Gfx[] = INCBIN_U16("graphics/battle_frontier/factory_screen/mon_pic_bg.4bpp");
static const u16 sMonPicBg_Pal[] = INCBIN_U16("graphics/battle_frontier/factory_screen/mon_pic_bg.gbapal");
static const struct SpriteSheet sSelect_SpriteSheets[] =
diff --git a/src/battle_message.c b/src/battle_message.c
index c4de3c06e..172f5a11f 100644
--- a/src/battle_message.c
+++ b/src/battle_message.c
@@ -783,7 +783,7 @@ static const u8 sText_AttackerMeltedTheIce[] = _("{B_ATK_NAME_WITH_PREFIX} melte
static const u8 sText_TargetToughedItOut[] = _("{B_DEF_NAME_WITH_PREFIX} toughed it out\nto show you its best side!");
static const u8 sText_AttackerLostElectricType[] = _("{B_ATK_NAME_WITH_PREFIX} used up all\nof its electricity!");
static const u8 sText_AttackerSwitchedStatWithTarget[] = _("{B_ATK_NAME_WITH_PREFIX} switched {B_BUFF1}\nwith its target!");
-static const u8 sText_BeingHitChargedPkmnWithPower[] = _("Being hit by {B_CURRENT_MOVE}\ncharged {B_ATK_NAME_WITH_PREFIX} with power!");
+static const u8 sText_BeingHitChargedPkmnWithPower[] = _("Being hit by {B_CURRENT_MOVE}\ncharged {B_DEF_NAME_WITH_PREFIX} with power!");
static const u8 sText_SunlightActivatedAbility[] = _("The harsh sunlight activated\n{B_SCR_ACTIVE_NAME_WITH_PREFIX}'s {B_LAST_ABILITY}!");
static const u8 sText_StatWasHeightened[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX}'s {B_BUFF1} was heightened!");
static const u8 sText_ElectricTerrainActivatedAbility[] = _("The Electric Terrain activated\n{B_SCR_ACTIVE_NAME_WITH_PREFIX}'s {B_LAST_ABILITY}!");
@@ -1852,6 +1852,12 @@ const u16 gWeatherStartsStringIds[] =
[WEATHER_ABNORMAL] = STRINGID_ITISRAINING
};
+const u16 gPrimalWeatherBlocksStringIds[] =
+{
+ [B_MSG_PRIMAL_WEATHER_FIZZLED_BY_RAIN] = STRINGID_MOVEFIZZLEDOUTINTHEHEAVYRAIN,
+ [B_MSG_PRIMAL_WEATHER_EVAPORATED_IN_SUN] = STRINGID_MOVEEVAPORATEDINTHEHARSHSUNLIGHT,
+};
+
const u16 gInobedientStringIds[] =
{
[B_MSG_LOAFING] = STRINGID_PKMNLOAFING,
diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c
index 006530e5f..63bc9cd35 100644
--- a/src/battle_script_commands.c
+++ b/src/battle_script_commands.c
@@ -1273,25 +1273,8 @@ static void Cmd_attackcanceler(void)
s32 i, moveType;
u16 attackerAbility = GetBattlerAbility(gBattlerAttacker);
-
GET_MOVE_TYPE(gCurrentMove, moveType);
- if (WEATHER_HAS_EFFECT && gBattleMoves[gCurrentMove].power)
- {
- if (moveType == TYPE_FIRE && (gBattleWeather & B_WEATHER_RAIN_PRIMAL))
- {
- BattleScriptPushCursor();
- gBattlescriptCurrInstr = BattleScript_PrimordialSeaFizzlesOutFireTypeMoves;
- return;
- }
- else if (moveType == TYPE_WATER && (gBattleWeather & B_WEATHER_SUN_PRIMAL))
- {
- BattleScriptPushCursor();
- gBattlescriptCurrInstr = BattleScript_DesolateLandEvaporatesWaterTypeMoves;
- return;
- }
- }
-
if (gBattleOutcome != 0)
{
gCurrentActionFuncId = B_ACTION_FINISHED;
@@ -1307,9 +1290,27 @@ static void Cmd_attackcanceler(void)
if (TryAegiFormChange())
return;
#endif
- if (AtkCanceller_UnableToUseMove())
+ if (AtkCanceller_UnableToUseMove(moveType))
return;
+ if (WEATHER_HAS_EFFECT && gBattleMoves[gCurrentMove].power)
+ {
+ if (moveType == TYPE_FIRE && (gBattleWeather & B_WEATHER_RAIN_PRIMAL))
+ {
+ gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_PRIMAL_WEATHER_FIZZLED_BY_RAIN;
+ BattleScriptPushCursor();
+ gBattlescriptCurrInstr = BattleScript_PrimalWeatherBlocksMove;
+ return;
+ }
+ else if (moveType == TYPE_WATER && (gBattleWeather & B_WEATHER_SUN_PRIMAL))
+ {
+ gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_PRIMAL_WEATHER_EVAPORATED_IN_SUN;
+ BattleScriptPushCursor();
+ gBattlescriptCurrInstr = BattleScript_PrimalWeatherBlocksMove;
+ return;
+ }
+ }
+
if (gSpecialStatuses[gBattlerAttacker].parentalBondState == PARENTAL_BOND_OFF
&& GetBattlerAbility(gBattlerAttacker) == ABILITY_PARENTAL_BOND
&& IsMoveAffectedByParentalBond(gCurrentMove, gBattlerAttacker)
@@ -14024,10 +14025,11 @@ static void Cmd_setforcedtarget(void)
static void Cmd_setcharge(void)
{
- CMD_ARGS();
+ CMD_ARGS(u8 battler);
- gStatuses3[gBattlerAttacker] |= STATUS3_CHARGED_UP;
- gDisableStructs[gBattlerAttacker].chargeTimer = 2;
+ u8 battler = GetBattlerForBattleScript(cmd->battler);
+ gStatuses3[battler] |= STATUS3_CHARGED_UP;
+ gDisableStructs[battler].chargeTimer = 2;
gBattlescriptCurrInstr++;
gBattlescriptCurrInstr = cmd->nextInstr;
}
diff --git a/src/battle_util.c b/src/battle_util.c
index 1170ddea7..2ec783a87 100644
--- a/src/battle_util.c
+++ b/src/battle_util.c
@@ -3337,10 +3337,9 @@ void SetAtkCancellerForCalledMove(void)
gBattleStruct->isAtkCancelerForCalledMove = TRUE;
}
-u8 AtkCanceller_UnableToUseMove(void)
+u8 AtkCanceller_UnableToUseMove(u32 moveType)
{
u8 effect = 0;
- s32 *bideDmg = &gBattleScripting.bideDmg;
do
{
switch (gBattleStruct->atkCancellerTracker)
@@ -3601,7 +3600,7 @@ u8 AtkCanceller_UnableToUseMove(void)
if (gTakenDmg[gBattlerAttacker])
{
gCurrentMove = MOVE_BIDE;
- *bideDmg = gTakenDmg[gBattlerAttacker] * 2;
+ gBattleScripting.bideDmg = gTakenDmg[gBattlerAttacker] * 2;
gBattlerTarget = gTakenDmgByBattler[gBattlerAttacker];
if (gAbsentBattlerFlags & gBitTable[gBattlerTarget])
gBattlerTarget = GetMoveTarget(MOVE_BIDE, MOVE_TARGET_SELECTED + 1);
@@ -3671,8 +3670,6 @@ u8 AtkCanceller_UnableToUseMove(void)
case CANCELLER_POWDER_STATUS:
if (gBattleMons[gBattlerAttacker].status2 & STATUS2_POWDER)
{
- u32 moveType;
- GET_MOVE_TYPE(gCurrentMove, moveType);
if (moveType == TYPE_FIRE)
{
gProtectStructs[gBattlerAttacker].powderSelfDmg = TRUE;
@@ -5637,7 +5634,6 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move
&& TARGET_TURN_DAMAGED
&& IsBattlerAlive(gBattlerTarget))
{
- gBattlerAttacker = gBattlerTarget;
BattleScriptPushCursor();
gBattlescriptCurrInstr = BattleScript_WindPowerActivates;
effect++;
diff --git a/src/berry_fix_program.c b/src/berry_fix_program.c
index af21bb929..26af445a0 100644
--- a/src/berry_fix_program.c
+++ b/src/berry_fix_program.c
@@ -117,7 +117,7 @@ static const struct WindowTemplate sBerryFixWindowTemplates[] = {
DUMMY_WIN_TEMPLATE
};
-static const u16 sBerryFixPalColors[] = {
+static const u16 ALIGNED(4) sBerryFixPalColors[] = {
RGB_WHITE, RGB_WHITE, RGB(12, 12, 12), RGB(26, 26, 25),
RGB(28, 1, 1), RGB(31, 23, 14), RGB(4, 19, 1), RGB(18, 30, 18),
RGB(6, 10, 25), RGB(20, 24, 30), RGB_WHITE, RGB(12, 12, 12),
diff --git a/src/contest.c b/src/contest.c
index 97797dbf7..51537dbb4 100644
--- a/src/contest.c
+++ b/src/contest.c
@@ -1300,8 +1300,8 @@ static void Task_ReadyStartLinkContest(u8 taskId)
static bool8 SetupContestGraphics(u8 *stateVar)
{
- u16 tempPalette1[16];
- u16 tempPalette2[16];
+ u16 ALIGNED(4) tempPalette1[16];
+ u16 ALIGNED(4) tempPalette2[16];
switch (*stateVar)
{
diff --git a/src/daycare.c b/src/daycare.c
index 9a1e98d98..2b4311734 100644
--- a/src/daycare.c
+++ b/src/daycare.c
@@ -412,7 +412,7 @@ static void Debug_AddDaycareSteps(u16 numSteps)
u8 GetNumLevelsGainedFromDaycare(void)
{
- if (GetBoxMonData(&gSaveBlock1Ptr->daycare.mons[gSpecialVar_0x8004], MON_DATA_SPECIES) != 0)
+ if (GetBoxMonData(&gSaveBlock1Ptr->daycare.mons[gSpecialVar_0x8004].mon, MON_DATA_SPECIES) != 0)
return GetNumLevelsGainedForDaycareMon(&gSaveBlock1Ptr->daycare.mons[gSpecialVar_0x8004]);
return 0;
diff --git a/src/decoration.c b/src/decoration.c
index 27ef85de9..b33580d2d 100644
--- a/src/decoration.c
+++ b/src/decoration.c
@@ -1916,7 +1916,7 @@ static void CopyPalette(u16 *dest, u16 pal)
static void CopyTile(u8 *dest, u16 tile)
{
- u8 buffer[TILE_SIZE_4BPP];
+ u8 ALIGNED(4) buffer[TILE_SIZE_4BPP];
u16 mode;
u16 i;
diff --git a/src/field_weather.c b/src/field_weather.c
index 73aef3746..bcc6a1c44 100644
--- a/src/field_weather.c
+++ b/src/field_weather.c
@@ -61,7 +61,7 @@ static void None_Main(void);
static u8 None_Finish(void);
EWRAM_DATA struct Weather gWeather = {0};
-EWRAM_DATA static u8 sFieldEffectPaletteColorMapTypes[32] = {0};
+EWRAM_DATA static u8 ALIGNED(2) sFieldEffectPaletteColorMapTypes[32] = {0};
static const u8 *sPaletteColorMapTypes;
@@ -111,7 +111,7 @@ void (*const gWeatherPalStateFuncs[])(void) =
// This table specifies which of the color maps should be
// applied to each of the background and sprite palettes.
-static const u8 sBasePaletteColorMapTypes[32] =
+static const u8 ALIGNED(2) sBasePaletteColorMapTypes[32] =
{
// background palettes
COLOR_MAP_DARK_CONTRAST,
@@ -149,7 +149,7 @@ static const u8 sBasePaletteColorMapTypes[32] =
COLOR_MAP_DARK_CONTRAST,
};
-const u16 gFogPalette[] = INCBIN_U16("graphics/weather/fog.gbapal");
+const u16 ALIGNED(4) gFogPalette[] = INCBIN_U16("graphics/weather/fog.gbapal");
void StartWeather(void)
{
diff --git a/src/fieldmap.c b/src/fieldmap.c
index 2ca274fcc..0ece73c5c 100644
--- a/src/fieldmap.c
+++ b/src/fieldmap.c
@@ -25,7 +25,7 @@ struct ConnectionFlags
u8 east:1;
};
-EWRAM_DATA static u16 sBackupMapData[MAX_MAP_DATA_SIZE] = {0};
+EWRAM_DATA static u16 ALIGNED(4) sBackupMapData[MAX_MAP_DATA_SIZE] = {0};
EWRAM_DATA struct MapHeader gMapHeader = {0};
EWRAM_DATA struct Camera gCamera = {0};
EWRAM_DATA static struct ConnectionFlags sMapConnectionFlags = {0};
diff --git a/src/graphics.c b/src/graphics.c
index 74feb5246..a595c7acf 100644
--- a/src/graphics.c
+++ b/src/graphics.c
@@ -1713,8 +1713,8 @@ const u32 gRouletteMultiplier_Gfx[] = INCBIN_U32("graphics/roulette/multiplier.4
const u16 gFrontierFactorySelectMenu_Pal[] = INCBIN_U16("graphics/battle_frontier/factory_menu1.gbapal");
const u16 gFrontierFactorySelectMenu_Pal2[] = INCBIN_U16("graphics/battle_frontier/factory_menu2.gbapal");
-const u8 gFrontierFactorySelectMenu_Gfx[] = INCBIN_U8("graphics/battle_frontier/factory_menu1.4bpp");
-const u8 gFrontierFactorySelectMenu_Gfx2[] = INCBIN_U8("graphics/battle_frontier/factory_menu2.4bpp");
+const u16 gFrontierFactorySelectMenu_Gfx[] = INCBIN_U16("graphics/battle_frontier/factory_menu1.4bpp");
+const u16 gFrontierFactorySelectMenu_Gfx2[] = INCBIN_U16("graphics/battle_frontier/factory_menu2.4bpp");
const u16 gFrontierFactorySelectMenu_Tilemap[] = INCBIN_U16("graphics/battle_frontier/factory_menu.bin");
diff --git a/src/intro.c b/src/intro.c
index bfc23a74b..28b4120b1 100644
--- a/src/intro.c
+++ b/src/intro.c
@@ -1096,6 +1096,10 @@ static u8 SetUpCopyrightScreen(void)
REG_DISPCNT = DISPCNT_MODE_0 | DISPCNT_OBJ_1D_MAP | DISPCNT_BG0_ON;
SetSerialCallback(SerialCB_CopyrightScreen);
GameCubeMultiBoot_Init(&gMultibootProgramStruct);
+ // REG_DISPCNT needs to be overwritten the second time, because otherwise the intro won't show up on VBA 1.7.2 and John GBA Lite emulators.
+ // The REG_DISPCNT overwrite is NOT needed in m-GBA, No$GBA, VBA 1.8.0, My Boy and Pizza Boy GBA emulators.
+ case 1:
+ REG_DISPCNT = DISPCNT_MODE_0 | DISPCNT_OBJ_1D_MAP | DISPCNT_BG0_ON;
default:
UpdatePaletteFade();
gMain.state++;
diff --git a/src/item_menu.c b/src/item_menu.c
index defa4f68a..92c1773e7 100755
--- a/src/item_menu.c
+++ b/src/item_menu.c
@@ -2436,16 +2436,16 @@ static void PrintPocketNames(const u8 *pocketName1, const u8 *pocketName2)
static void CopyPocketNameToWindow(u32 a)
{
- u8 (* tileDataBuffer)[32][32];
+ u8 (*tileDataBuffer)[32][32];
u8 *windowTileData;
int b;
if (a > 8)
a = 8;
tileDataBuffer = &gBagMenu->pocketNameBuffer;
windowTileData = (u8 *)GetWindowAttribute(2, WINDOW_TILE_DATA);
- CpuCopy32(tileDataBuffer[0][a], windowTileData, 0x100); // Top half of pocket name
+ CpuCopy32(&tileDataBuffer[0][a], windowTileData, 0x100); // Top half of pocket name
b = a + 16;
- CpuCopy32(tileDataBuffer[0][b], windowTileData + 0x100, 0x100); // Bottom half of pocket name
+ CpuCopy32(&tileDataBuffer[0][b], windowTileData + 0x100, 0x100); // Bottom half of pocket name
CopyWindowToVram(WIN_POCKET_NAME, COPYWIN_GFX);
}
diff --git a/src/link.c b/src/link.c
index 0894021b5..c51abc952 100644
--- a/src/link.c
+++ b/src/link.c
@@ -78,7 +78,7 @@ bool8 gRemoteLinkPlayersNotReceived[MAX_LINK_PLAYERS];
u8 gBlockReceivedStatus[MAX_LINK_PLAYERS];
u32 gLinkFiller2;
u16 gLinkHeldKeys;
-u16 gRecvCmds[MAX_RFU_PLAYERS][CMD_LENGTH];
+u16 ALIGNED(4) gRecvCmds[MAX_RFU_PLAYERS][CMD_LENGTH];
u32 gLinkStatus;
bool8 gLinkDummy1; // Never read
bool8 gLinkDummy2; // Never read
diff --git a/src/mirage_tower.c b/src/mirage_tower.c
index f9806e776..9b48ee24b 100644
--- a/src/mirage_tower.c
+++ b/src/mirage_tower.c
@@ -75,7 +75,7 @@ static void Task_FossilFallAndSink(u8);
static void SpriteCB_FallingFossil(struct Sprite *);
static void UpdateDisintegrationEffect(u8 *, u16, u8, u8, u8);
-static const u8 sBlankTile_Gfx[32] = {0};
+static const u8 ALIGNED(2) sBlankTile_Gfx[32] = {0};
static const u8 sMirageTower_Gfx[] = INCBIN_U8("graphics/misc/mirage_tower.4bpp");
static const u16 sMirageTowerTilemap[] = INCBIN_U16("graphics/misc/mirage_tower.bin");
static const u16 sFossil_Pal[] = INCBIN_U16("graphics/object_events/pics/misc/fossil.gbapal"); // Unused
diff --git a/src/palette.c b/src/palette.c
index 6ce47a493..43a4c213c 100644
--- a/src/palette.c
+++ b/src/palette.c
@@ -64,7 +64,7 @@ static EWRAM_DATA struct PaletteStruct sPaletteStructs[NUM_PALETTE_STRUCTS] = {0
EWRAM_DATA struct PaletteFadeControl gPaletteFade = {0};
static EWRAM_DATA u32 sFiller = 0;
static EWRAM_DATA u32 sPlttBufferTransferPending = 0;
-EWRAM_DATA u8 gPaletteDecompressionBuffer[PLTT_SIZE] = {0};
+EWRAM_DATA u8 ALIGNED(2) gPaletteDecompressionBuffer[PLTT_SIZE] = {0};
static const struct PaletteStructTemplate sDummyPaletteStructTemplate = {
.id = 0xFFFF,
diff --git a/src/pokeball.c b/src/pokeball.c
index 9147ce16b..2e556ec22 100644
--- a/src/pokeball.c
+++ b/src/pokeball.c
@@ -1218,13 +1218,13 @@ static u8 LaunchBallFadeMonTaskForPokeball(bool8 unFadeLater, u8 spritePalNum, u
#define sTrigIdx data[7]
// Pokeball in Birch intro, and when receiving via trade
-void CreatePokeballSpriteToReleaseMon(u8 monSpriteId, u8 monPalNum, u8 x, u8 y, u8 oamPriority, u8 subpriortiy, u8 delay, u32 fadePalettes, u16 species)
+void CreatePokeballSpriteToReleaseMon(u8 monSpriteId, u8 monPalNum, u8 x, u8 y, u8 oamPriority, u8 subpriority, u8 delay, u32 fadePalettes, u16 species)
{
u8 spriteId;
LoadCompressedSpriteSheetUsingHeap(&gBallSpriteSheets[BALL_POKE]);
LoadCompressedSpritePaletteUsingHeap(&gBallSpritePalettes[BALL_POKE]);
- spriteId = CreateSprite(&gBallSpriteTemplates[BALL_POKE], x, y, subpriortiy);
+ spriteId = CreateSprite(&gBallSpriteTemplates[BALL_POKE], x, y, subpriority);
gSprites[spriteId].sMonSpriteId = monSpriteId;
gSprites[spriteId].sFinalMonX = gSprites[monSpriteId].x;
diff --git a/src/pokedex.c b/src/pokedex.c
index b9dc894d3..49cfa9411 100644
--- a/src/pokedex.c
+++ b/src/pokedex.c
@@ -4537,7 +4537,7 @@ static void PrintDecimalNum(u8 windowId, u16 num, u8 left, u8 top)
static void DrawFootprint(u8 windowId, u16 dexNum)
{
- u8 footprint4bpp[TILE_SIZE_4BPP * NUM_FOOTPRINT_TILES];
+ u8 ALIGNED(4) footprint4bpp[TILE_SIZE_4BPP * NUM_FOOTPRINT_TILES];
const u8 *footprintGfx = gMonFootprintTable[NationalPokedexNumToSpecies(dexNum)];
u32 i, j, tileIdx = 0;
diff --git a/src/pokedex_area_region_map.c b/src/pokedex_area_region_map.c
index da50f8c68..cd2975473 100644
--- a/src/pokedex_area_region_map.c
+++ b/src/pokedex_area_region_map.c
@@ -8,7 +8,7 @@
static EWRAM_DATA u8 *sPokedexAreaMapBgNum = NULL;
-static const u16 sPokedexAreaMap_Pal[] = INCBIN_U16("graphics/pokedex/region_map.gbapal");
+static const u16 ALIGNED(4) sPokedexAreaMap_Pal[] = INCBIN_U16("graphics/pokedex/region_map.gbapal");
static const u32 sPokedexAreaMap_Gfx[] = INCBIN_U32("graphics/pokedex/region_map.8bpp.lz");
static const u32 sPokedexAreaMap_Tilemap[] = INCBIN_U32("graphics/pokedex/region_map.bin.lz");
static const u32 sPokedexAreaMapAffine_Gfx[] = INCBIN_U32("graphics/pokedex/region_map_affine.8bpp.lz");
diff --git a/src/pokemon.c b/src/pokemon.c
index 7fac2958c..3ded30679 100644
--- a/src/pokemon.c
+++ b/src/pokemon.c
@@ -4688,7 +4688,11 @@ static union PokemonSubstruct *GetSubstruct(struct BoxPokemon *boxMon, u32 perso
return substruct;
}
-u32 GetMonData(struct Pokemon *mon, s32 field, u8 *data)
+/* GameFreak called GetMonData with either 2 or 3 arguments, for type
+ * safety we have a GetMonData macro (in include/pokemon.h) which
+ * dispatches to either GetMonData2 or GetMonData3 based on the number
+ * of arguments. */
+u32 GetMonData3(struct Pokemon *mon, s32 field, u8 *data)
{
u32 ret;
@@ -4746,7 +4750,13 @@ u32 GetMonData(struct Pokemon *mon, s32 field, u8 *data)
return ret;
}
-u32 GetBoxMonData(struct BoxPokemon *boxMon, s32 field, u8 *data)
+u32 GetMonData2(struct Pokemon *mon, s32 field) __attribute__((alias("GetMonData3")));
+
+/* GameFreak called GetBoxMonData with either 2 or 3 arguments, for type
+ * safety we have a GetBoxMonData macro (in include/pokemon.h) which
+ * dispatches to either GetBoxMonData2 or GetBoxMonData3 based on the
+ * number of arguments. */
+u32 GetBoxMonData3(struct BoxPokemon *boxMon, s32 field, u8 *data)
{
s32 i;
u32 retVal = 0;
@@ -5110,6 +5120,8 @@ u32 GetBoxMonData(struct BoxPokemon *boxMon, s32 field, u8 *data)
return retVal;
}
+u32 GetBoxMonData2(struct BoxPokemon *boxMon, s32 field) __attribute__((alias("GetBoxMonData3")));
+
#define SET8(lhs) (lhs) = *data
#define SET16(lhs) (lhs) = data[0] + (data[1] << 8)
#define SET32(lhs) (lhs) = data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24)
diff --git a/src/pokemon_storage_system.c b/src/pokemon_storage_system.c
index 82fdca6f9..9cb894f13 100644
--- a/src/pokemon_storage_system.c
+++ b/src/pokemon_storage_system.c
@@ -552,8 +552,8 @@ struct PokemonStorageSystemData
u16 *displayMonTilePtr;
struct Sprite *displayMonSprite;
u16 displayMonPalBuffer[0x40];
- u8 tileBuffer[MON_PIC_SIZE * MAX_MON_PIC_FRAMES];
- u8 itemIconBuffer[0x800];
+ u8 ALIGNED(4) tileBuffer[MON_PIC_SIZE * MAX_MON_PIC_FRAMES];
+ u8 ALIGNED(4) itemIconBuffer[0x800];
u8 wallpaperBgTilemapBuffer[0x1000];
u8 displayMenuTilemapBuffer[0x800];
};
@@ -10150,7 +10150,7 @@ void UpdateSpeciesSpritePSS(struct BoxPokemon *boxMon)
{
DestroyBoxMonIcon(sStorage->boxMonsSprites[sCursorPosition]);
CreateBoxMonIconAtPos(sCursorPosition);
- SetBoxMonIconObjMode(sCursorPosition, GetMonData(boxMon, MON_DATA_HELD_ITEM) == ITEM_NONE);
+ SetBoxMonIconObjMode(sCursorPosition, GetBoxMonData(boxMon, MON_DATA_HELD_ITEM) == ITEM_NONE);
}
}
sJustOpenedBag = FALSE;
diff --git a/src/pokenav_conditions_gfx.c b/src/pokenav_conditions_gfx.c
index e382cbf69..9f4b2db61 100644
--- a/src/pokenav_conditions_gfx.c
+++ b/src/pokenav_conditions_gfx.c
@@ -116,10 +116,12 @@ static const LoopedTask sLoopedTaskFuncs[] =
[CONDITION_FUNC_CLOSE_MARKINGS] = LoopedTask_CloseMonMarkingsWindow
};
+typedef u8 ALIGNED(4) TilemapBuffer[BG_SCREEN_SIZE];
+
struct Pokenav_ConditionMenuGfx
{
u32 loopedTaskId;
- u8 tilemapBuffers[3][BG_SCREEN_SIZE];
+ TilemapBuffer tilemapBuffers[3];
u8 filler[2];
u8 partyPokeballSpriteIds[PARTY_SIZE + 1];
u32 (*callback)(void);
diff --git a/src/pokenav_region_map.c b/src/pokenav_region_map.c
index e589e2818..f81ff4296 100755
--- a/src/pokenav_region_map.c
+++ b/src/pokenav_region_map.c
@@ -35,7 +35,7 @@ struct Pokenav_RegionMapGfx
u32 loopTaskId;
u16 infoWindowId;
struct Sprite *cityZoomTextSprites[3];
- u8 tilemapBuffer[BG_SCREEN_SIZE];
+ u8 ALIGNED(2) tilemapBuffer[BG_SCREEN_SIZE];
u8 cityZoomPics[NUM_CITY_MAPS][200];
};
diff --git a/src/rayquaza_scene.c b/src/rayquaza_scene.c
index c984dfec8..cacadf528 100644
--- a/src/rayquaza_scene.c
+++ b/src/rayquaza_scene.c
@@ -60,10 +60,12 @@ enum
#define MAX_SMOKE 10
+typedef u8 ALIGNED(4) TilemapBuffer[BG_SCREEN_SIZE];
+
struct RayquazaScene
{
MainCallback exitCallback;
- u8 tilemapBuffers[4][BG_SCREEN_SIZE];
+ TilemapBuffer tilemapBuffers[4];
u16 unk; // never read
u8 animId;
bool8 endEarly;
diff --git a/src/scanline_effect.c b/src/scanline_effect.c
index dc3ca03f4..684c89546 100644
--- a/src/scanline_effect.c
+++ b/src/scanline_effect.c
@@ -13,7 +13,7 @@ static void CopyValue32Bit(void);
// Per-scanline register values.
// This is double buffered so that it can be safely written to at any time
// without overwriting the buffer that the DMA is currently reading
-EWRAM_DATA u16 gScanlineEffectRegBuffers[2][0x3C0] = {0};
+EWRAM_DATA u16 ALIGNED(4) gScanlineEffectRegBuffers[2][0x3C0] = {0};
EWRAM_DATA struct ScanlineEffect gScanlineEffect = {0};
EWRAM_DATA static bool8 sShouldStopWaveTask = FALSE;
diff --git a/src/util.c b/src/util.c
index 32f31a26d..ab5603b86 100644
--- a/src/util.c
+++ b/src/util.c
@@ -158,7 +158,7 @@ void CopySpriteTiles(u8 shape, u8 size, u8 *tiles, u16 *tilemap, u8 *output)
{
u8 x, y;
s8 i, j;
- u8 xflip[32];
+ u8 ALIGNED(4) xflip[32];
u8 h = sSpriteDimensions[shape][size][1];
u8 w = sSpriteDimensions[shape][size][0];
diff --git a/test/ability_electromorphosis.c b/test/ability_electromorphosis.c
new file mode 100644
index 000000000..25d33aac0
--- /dev/null
+++ b/test/ability_electromorphosis.c
@@ -0,0 +1,56 @@
+#include "global.h"
+#include "test_battle.h"
+
+SINGLE_BATTLE_TEST("Electromorphosis sets up Charge when hit by any move")
+{
+ s16 dmgBefore, dmgAfter;
+ u16 move;
+
+ PARAMETRIZE {move = MOVE_TACKLE; }
+ PARAMETRIZE {move = MOVE_GUST; }
+
+ GIVEN {
+ ASSUME(gBattleMoves[MOVE_TACKLE].power != 0);
+ ASSUME(gBattleMoves[MOVE_GUST].power != 0);
+ ASSUME(gBattleMoves[MOVE_GUST].split == SPLIT_SPECIAL);
+ ASSUME(gBattleMoves[MOVE_TACKLE].split == SPLIT_PHYSICAL);
+ ASSUME(gBattleMoves[MOVE_THUNDERBOLT].power != 0);
+ ASSUME(gBattleMoves[MOVE_THUNDERBOLT].type == TYPE_ELECTRIC);
+
+ PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_ELECTROMORPHOSIS); Speed(10); }
+ OPPONENT(SPECIES_WOBBUFFET) {Ability(ABILITY_LIMBER); Speed(5) ;} // Limber, so it doesn't get paralyzed.
+ }
+ WHEN {
+ TURN { MOVE(player, MOVE_THUNDERBOLT), MOVE(opponent, move); }
+ TURN { MOVE(player, MOVE_THUNDERBOLT), MOVE(opponent, move); }
+ } SCENE {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, player);
+ HP_BAR(opponent, captureDamage: &dmgBefore);
+
+ ANIMATION(ANIM_TYPE_MOVE, move, opponent);
+ HP_BAR(player);
+ ABILITY_POPUP(player, ABILITY_ELECTROMORPHOSIS);
+ if (move == MOVE_TACKLE) {
+ MESSAGE("Being hit by Tackle charged Wobbuffet with power!");
+ }
+ else {
+ MESSAGE("Being hit by Gust charged Wobbuffet with power!");
+ }
+
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, player);
+ HP_BAR(opponent, captureDamage: &dmgAfter);
+
+ ANIMATION(ANIM_TYPE_MOVE, move, opponent);
+ HP_BAR(player);
+ ABILITY_POPUP(player, ABILITY_ELECTROMORPHOSIS);
+ if (move == MOVE_TACKLE) {
+ MESSAGE("Being hit by Tackle charged Wobbuffet with power!");
+ }
+ else {
+ MESSAGE("Being hit by Gust charged Wobbuffet with power!");
+ }
+ }
+ THEN {
+ EXPECT_MUL_EQ(dmgBefore, Q_4_12(2.0), dmgAfter);
+ }
+}
diff --git a/test/ability_rattled.c b/test/ability_rattled.c
new file mode 100644
index 000000000..493e3cfca
--- /dev/null
+++ b/test/ability_rattled.c
@@ -0,0 +1,93 @@
+#include "global.h"
+#include "test_battle.h"
+
+ASSUMPTIONS
+{
+ ASSUME(gBattleMoves[MOVE_FURY_CUTTER].type == TYPE_BUG);
+ ASSUME(gBattleMoves[MOVE_FURY_CUTTER].power != 0);
+ ASSUME(gBattleMoves[MOVE_FEINT_ATTACK].type == TYPE_DARK);
+ ASSUME(gBattleMoves[MOVE_FEINT_ATTACK].power != 0);
+ ASSUME(gBattleMoves[MOVE_SHADOW_PUNCH].type == TYPE_GHOST);
+ ASSUME(gBattleMoves[MOVE_SHADOW_PUNCH].power != 0);
+ ASSUME(gBattleMoves[MOVE_TACKLE].type == TYPE_NORMAL);
+ ASSUME(gBattleMoves[MOVE_TACKLE].power != 0);
+}
+
+SINGLE_BATTLE_TEST("Rattled boosts speed by 1 when hit by Bug, Dark or Ghost type move")
+{
+ u16 move;
+ PARAMETRIZE { move = MOVE_FURY_CUTTER; }
+ PARAMETRIZE { move = MOVE_FEINT_ATTACK; }
+ PARAMETRIZE { move = MOVE_SHADOW_PUNCH; }
+ PARAMETRIZE { move = MOVE_TACKLE; }
+ GIVEN {
+ PLAYER(SPECIES_WOBBUFFET) {Speed(42) ;}
+ OPPONENT(SPECIES_SUDOWOODO) {Speed(40); Ability(ABILITY_RATTLED);}
+ } WHEN {
+ TURN { MOVE(player, move); }
+ TURN { MOVE(player, move); }
+ } SCENE {
+ ANIMATION(ANIM_TYPE_MOVE, move, player);
+ HP_BAR(opponent);
+ if (move != MOVE_TACKLE) {
+ ABILITY_POPUP(opponent, ABILITY_RATTLED);
+ ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
+ MESSAGE("Foe Sudowoodo's Speed rose!");
+ }
+ MESSAGE("Foe Sudowoodo used Celebrate!");
+ // Sudowoodo is now faster
+ if (move != MOVE_TACKLE){
+ MESSAGE("Foe Sudowoodo used Celebrate!");
+ ANIMATION(ANIM_TYPE_MOVE, move, player);
+ HP_BAR(opponent);
+ ABILITY_POPUP(opponent, ABILITY_RATTLED);
+ ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
+ MESSAGE("Foe Sudowoodo's Speed rose!");
+ }
+ else {
+ ANIMATION(ANIM_TYPE_MOVE, move, player);
+ HP_BAR(opponent);
+ MESSAGE("Foe Sudowoodo used Celebrate!");
+ }
+ }
+}
+
+SINGLE_BATTLE_TEST("Rattled boosts speed by 1 when affected by Intimidate")
+{
+ GIVEN {
+ ASSUME(B_UPDATED_INTIMIDATE >= GEN_8);
+ PLAYER(SPECIES_GYARADOS) {Ability(ABILITY_INTIMIDATE); }
+ OPPONENT(SPECIES_SUDOWOODO) {Ability(ABILITY_RATTLED); }
+ } WHEN {
+ TURN {}
+ } SCENE {
+ ABILITY_POPUP(player, ABILITY_INTIMIDATE);
+ ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
+ MESSAGE("Gyarados's Intimidate cuts Foe Sudowoodo's attack!");
+ ABILITY_POPUP(opponent, ABILITY_RATTLED);
+ ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
+ MESSAGE("Foe Sudowoodo's Speed rose!");
+ }
+}
+
+SINGLE_BATTLE_TEST("Rattled triggers correctly when hit by U-Turn") // Specific test here, because of #3124
+{
+ GIVEN {
+ ASSUME(gBattleMoves[MOVE_U_TURN].effect == EFFECT_HIT_ESCAPE);
+ ASSUME(gBattleMoves[MOVE_U_TURN].type == TYPE_BUG);
+ PLAYER(SPECIES_WOBBUFFET);
+ PLAYER(SPECIES_WYNAUT);
+ OPPONENT(SPECIES_SUDOWOODO) {Ability(ABILITY_RATTLED); }
+ OPPONENT(SPECIES_SUDOWOODO);
+ } WHEN {
+ TURN { MOVE(player, MOVE_U_TURN); SEND_OUT(player, 1); }
+ } SCENE {
+ MESSAGE("Wobbuffet used U-turn!");
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, player);
+ HP_BAR(opponent);
+ ABILITY_POPUP(opponent, ABILITY_RATTLED);
+ ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
+ MESSAGE("Foe Sudowoodo's Speed rose!");
+ MESSAGE("Go! Wynaut!");
+ }
+}
diff --git a/test/ability_stamina.c b/test/ability_stamina.c
new file mode 100644
index 000000000..70cf3cdc0
--- /dev/null
+++ b/test/ability_stamina.c
@@ -0,0 +1,89 @@
+#include "global.h"
+#include "test_battle.h"
+
+#define STAMINA_STAT_RAISE(target, msg) \
+{ \
+ ABILITY_POPUP(target, ABILITY_STAMINA); \
+ ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, target); \
+ MESSAGE(msg); \
+}
+
+#define STAMINA_HIT(attacker, target, move, msg, dmgVar) \
+{ \
+ ANIMATION(ANIM_TYPE_MOVE, move, attacker); \
+ HP_BAR(target, captureDamage: &dmgVar); \
+ STAMINA_STAT_RAISE(target, msg); \
+}
+
+SINGLE_BATTLE_TEST("Stamina raises Defense by 1 when hit by a move")
+{
+ s16 turnOneHit, turnTwoHit;
+ u16 move;
+
+ PARAMETRIZE {move = MOVE_TACKLE; }
+ PARAMETRIZE {move = MOVE_GUST; }
+
+ GIVEN {
+ ASSUME(gBattleMoves[MOVE_TACKLE].power != 0);
+ ASSUME(gBattleMoves[MOVE_GUST].power != 0);
+ ASSUME(gBattleMoves[MOVE_GUST].split == SPLIT_SPECIAL);
+ ASSUME(gBattleMoves[MOVE_TACKLE].split == SPLIT_PHYSICAL);
+ PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_STAMINA); }
+ OPPONENT(SPECIES_WOBBUFFET);
+ } WHEN {
+ TURN { MOVE(opponent, move); }
+ TURN { MOVE(opponent, move); }
+ } SCENE {
+ STAMINA_HIT(opponent, player, move, "Wobbuffet's Defense rose!", turnOneHit);
+ STAMINA_HIT(opponent, player, move, "Wobbuffet's Defense rose!", turnTwoHit);
+ }
+ THEN {
+ if (move == MOVE_TACKLE) {
+ EXPECT_MUL_EQ(turnTwoHit, Q_4_12(1.5), turnOneHit);
+ }
+ else {
+ EXPECT_EQ(turnTwoHit, turnOneHit);
+ }
+ }
+}
+
+DOUBLE_BATTLE_TEST("Stamina activates correctly for every battler with the ability when hit by a multi target move")
+{
+ u16 abilityLeft, abilityRight;
+
+ PARAMETRIZE {abilityLeft = ABILITY_NONE, abilityRight = ABILITY_STAMINA; }
+ PARAMETRIZE {abilityLeft = ABILITY_STAMINA, abilityRight = ABILITY_NONE; }
+ PARAMETRIZE {abilityLeft = ABILITY_STAMINA, abilityRight = ABILITY_STAMINA; }
+
+ GIVEN {
+ ASSUME(gBattleMoves[MOVE_EARTHQUAKE].target == MOVE_TARGET_FOES_AND_ALLY);
+ PLAYER(SPECIES_WOBBUFFET) { Ability(abilityLeft); Speed(10); }
+ PLAYER(SPECIES_WOBBUFFET) { Ability(abilityRight); Speed(5); }
+ OPPONENT(SPECIES_WOBBUFFET) {Speed(20); }
+ OPPONENT(SPECIES_WOBBUFFET) {Speed(15); }
+ } WHEN {
+ TURN { MOVE(opponentLeft, MOVE_EARTHQUAKE);}
+ } SCENE {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, opponentLeft);
+
+ HP_BAR(playerLeft);
+ if (abilityLeft == ABILITY_STAMINA) {
+ STAMINA_STAT_RAISE(playerLeft, "Wobbuffet's Defense rose!");
+ }
+ NOT HP_BAR(opponentLeft); // We need to check the attacker itself does NOT get damaged. There was an issue when the targets would get overwritten by the Stamina's stat raise.
+
+ HP_BAR(playerRight);
+ if (abilityRight == ABILITY_STAMINA) {
+ STAMINA_STAT_RAISE(playerRight, "Wobbuffet's Defense rose!");
+ }
+ NOT HP_BAR(opponentLeft); // We need to check the attacker itself does NOT get damaged. There was an issue when the targets would get overwritten by the Stamina's stat raise.
+
+ HP_BAR(opponentRight);
+ }
+ THEN {
+ EXPECT_NE(playerLeft->hp, playerLeft->maxHP);
+ EXPECT_NE(playerRight->hp, playerRight->maxHP);
+ EXPECT_NE(opponentRight->hp, opponentRight->maxHP);
+ EXPECT_EQ(opponentLeft->hp, opponentLeft->maxHP);
+ }
+}
diff --git a/test/ability_wind_power.c b/test/ability_wind_power.c
new file mode 100644
index 000000000..e9cb082a9
--- /dev/null
+++ b/test/ability_wind_power.c
@@ -0,0 +1,221 @@
+#include "global.h"
+#include "test_battle.h"
+
+ASSUMPTIONS
+{
+ ASSUME(gBattleMoves[MOVE_THUNDERBOLT].power != 0);
+ ASSUME(gBattleMoves[MOVE_THUNDERBOLT].type == TYPE_ELECTRIC);
+ ASSUME(gBattleMoves[MOVE_TACKLE].power != 0);
+ ASSUME(gBattleMoves[MOVE_AIR_CUTTER].power != 0);
+ ASSUME(gBattleMoves[MOVE_AIR_CUTTER].target == MOVE_TARGET_BOTH);
+ ASSUME(gBattleMoves[MOVE_AIR_CUTTER].windMove == TRUE);
+ ASSUME(gBattleMoves[MOVE_PETAL_BLIZZARD].power != 0);
+ ASSUME(gBattleMoves[MOVE_PETAL_BLIZZARD].target == MOVE_TARGET_FOES_AND_ALLY);
+ ASSUME(gBattleMoves[MOVE_PETAL_BLIZZARD].windMove == TRUE);
+ ASSUME(gBattleMoves[MOVE_TACKLE].windMove == FALSE);
+}
+
+SINGLE_BATTLE_TEST("Wind Power sets up Charge for player when hit by a wind move")
+{
+ s16 dmgBefore, dmgAfter;
+ u16 move;
+
+ PARAMETRIZE {move = MOVE_TACKLE; }
+ PARAMETRIZE {move = MOVE_AIR_CUTTER; }
+
+ GIVEN {
+ PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_WIND_POWER); Speed(10); }
+ OPPONENT(SPECIES_WOBBUFFET) {Ability(ABILITY_LIMBER); Speed(5) ;} // Limber, so it doesn't get paralyzed.
+ } WHEN {
+ TURN { MOVE(player, MOVE_THUNDERBOLT), MOVE(opponent, move); }
+ TURN { MOVE(player, MOVE_THUNDERBOLT), MOVE(opponent, move); }
+ } SCENE {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, player);
+ HP_BAR(opponent, captureDamage: &dmgBefore);
+
+ ANIMATION(ANIM_TYPE_MOVE, move, opponent);
+ HP_BAR(player);
+ if (move == MOVE_AIR_CUTTER) {
+ ABILITY_POPUP(player, ABILITY_WIND_POWER);
+ MESSAGE("Being hit by Air Cutter charged Wobbuffet with power!");
+ }
+
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, player);
+ HP_BAR(opponent, captureDamage: &dmgAfter);
+
+ ANIMATION(ANIM_TYPE_MOVE, move, opponent);
+ HP_BAR(player);
+ if (move == MOVE_AIR_CUTTER) {
+ ABILITY_POPUP(player, ABILITY_WIND_POWER);
+ MESSAGE("Being hit by Air Cutter charged Wobbuffet with power!");
+ }
+ }
+ THEN {
+ if (move == MOVE_AIR_CUTTER) {
+ EXPECT_MUL_EQ(dmgBefore, Q_4_12(2.0), dmgAfter);
+ }
+ else {
+ EXPECT_EQ(dmgAfter, dmgBefore);
+ }
+ }
+}
+
+SINGLE_BATTLE_TEST("Wind Power sets up Charge for opponent when hit by a wind move")
+{
+ s16 dmgBefore, dmgAfter;
+ u16 move;
+
+ PARAMETRIZE {move = MOVE_TACKLE; }
+ PARAMETRIZE {move = MOVE_AIR_CUTTER; }
+
+ GIVEN {
+ PLAYER(SPECIES_WOBBUFFET) {Ability(ABILITY_LIMBER); Speed(5) ;} // Limber, so it doesn't get paralyzed.
+ OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_WIND_POWER); Speed(10); }
+ } WHEN {
+ TURN { MOVE(opponent, MOVE_THUNDERBOLT), MOVE(player, move); }
+ TURN { MOVE(opponent, MOVE_THUNDERBOLT), MOVE(player, move); }
+ } SCENE {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, opponent);
+ HP_BAR(player, captureDamage: &dmgBefore);
+
+ ANIMATION(ANIM_TYPE_MOVE, move, player);
+ HP_BAR(opponent);
+ if (move == MOVE_AIR_CUTTER) {
+ ABILITY_POPUP(opponent, ABILITY_WIND_POWER);
+ MESSAGE("Being hit by Air Cutter charged Foe Wobbuffet with power!");
+ }
+
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_THUNDERBOLT, opponent);
+ HP_BAR(player, captureDamage: &dmgAfter);
+
+ ANIMATION(ANIM_TYPE_MOVE, move, player);
+ HP_BAR(opponent);
+ if (move == MOVE_AIR_CUTTER) {
+ ABILITY_POPUP(opponent, ABILITY_WIND_POWER);
+ MESSAGE("Being hit by Air Cutter charged Foe Wobbuffet with power!");
+ }
+ }
+ THEN {
+ if (move == MOVE_AIR_CUTTER) {
+ EXPECT_MUL_EQ(dmgBefore, Q_4_12(2.0), dmgAfter);
+ }
+ else {
+ EXPECT_EQ(dmgAfter, dmgBefore);
+ }
+ }
+}
+
+DOUBLE_BATTLE_TEST("Wind Power activates correctly for every battler with the ability when hit by a 2/3 target move")
+{
+ u16 move, abilityLeft, abilityRight;
+
+ PARAMETRIZE {abilityLeft = ABILITY_NONE, abilityRight = ABILITY_WIND_POWER;}
+ PARAMETRIZE {abilityLeft = ABILITY_WIND_POWER, abilityRight = ABILITY_NONE; }
+ PARAMETRIZE {abilityLeft = ABILITY_WIND_POWER, abilityRight = ABILITY_WIND_POWER; }
+
+ GIVEN {
+ PLAYER(SPECIES_WOBBUFFET) { Ability(abilityLeft); Speed(10); }
+ PLAYER(SPECIES_WOBBUFFET) { Ability(abilityRight); Speed(5); }
+ OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_LIMBER); Speed(20); }
+ OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_LIMBER); Speed(15); }
+ } WHEN {
+ TURN { MOVE(opponentLeft, MOVE_AIR_CUTTER); MOVE(opponentRight, MOVE_AIR_CUTTER);}
+ } SCENE {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_AIR_CUTTER, opponentLeft);
+
+ HP_BAR(playerLeft);
+ if (abilityLeft == ABILITY_WIND_POWER) {
+ ABILITY_POPUP(playerLeft, ABILITY_WIND_POWER);
+ MESSAGE("Being hit by Air Cutter charged Wobbuffet with power!");
+ }
+ HP_BAR(playerRight);
+ if (abilityRight == ABILITY_WIND_POWER) {
+ ABILITY_POPUP(playerRight, ABILITY_WIND_POWER);
+ MESSAGE("Being hit by Air Cutter charged Wobbuffet with power!");
+ }
+ NOT HP_BAR(opponentLeft);
+ NOT HP_BAR(opponentRight);
+ }
+ THEN {
+ EXPECT_NE(playerLeft->hp, playerLeft->maxHP);
+ EXPECT_NE(playerRight->hp, playerRight->maxHP);
+ EXPECT_EQ(opponentRight->hp, opponentRight->maxHP);
+ EXPECT_EQ(opponentLeft->hp, opponentLeft->maxHP);
+ }
+}
+
+DOUBLE_BATTLE_TEST("Wind Power activates correctly for every battler with the ability when hit by a 3 target move")
+{
+ u16 abilityLeft, abilityRight;
+
+ PARAMETRIZE {abilityLeft = ABILITY_NONE, abilityRight = ABILITY_WIND_POWER; }
+ PARAMETRIZE {abilityLeft = ABILITY_WIND_POWER, abilityRight = ABILITY_NONE; }
+ PARAMETRIZE {abilityLeft = ABILITY_WIND_POWER, abilityRight = ABILITY_WIND_POWER; }
+
+ GIVEN {
+ PLAYER(SPECIES_WOBBUFFET) { Ability(abilityLeft); Speed(10); }
+ PLAYER(SPECIES_WOBBUFFET) { Ability(abilityRight); Speed(5); }
+ OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_LIMBER); Speed(20); }
+ OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_LIMBER); Speed(15); }
+ } WHEN {
+ TURN { MOVE(opponentLeft, MOVE_PETAL_BLIZZARD);}
+ } SCENE {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_PETAL_BLIZZARD, opponentLeft);
+
+ HP_BAR(playerLeft);
+ if (abilityLeft == ABILITY_WIND_POWER) {
+ ABILITY_POPUP(playerLeft, ABILITY_WIND_POWER);
+ MESSAGE("Being hit by PetalBlizzrd charged Wobbuffet with power!");
+ }
+ HP_BAR(playerRight);
+ if (abilityRight == ABILITY_WIND_POWER) {
+ ABILITY_POPUP(playerRight, ABILITY_WIND_POWER);
+ MESSAGE("Being hit by PetalBlizzrd charged Wobbuffet with power!");
+ }
+ HP_BAR(opponentRight);
+ NOT HP_BAR(opponentLeft);
+ }
+ THEN {
+ EXPECT_NE(playerLeft->hp, playerLeft->maxHP);
+ EXPECT_NE(playerRight->hp, playerRight->maxHP);
+ EXPECT_NE(opponentRight->hp, opponentRight->maxHP);
+ EXPECT_EQ(opponentLeft->hp, opponentLeft->maxHP);
+ }
+}
+
+DOUBLE_BATTLE_TEST("Wind Power activates correctly when Tailwind is used")
+{
+ bool8 opponentSide;
+
+ PARAMETRIZE {opponentSide = TRUE;}
+ PARAMETRIZE {opponentSide = FALSE;}
+
+ GIVEN {
+ ASSUME(gBattleMoves[MOVE_TAILWIND].effect == EFFECT_TAILWIND);
+ PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_WIND_POWER); Speed(10); }
+ PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_WIND_POWER); Speed(5); }
+ OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_WIND_POWER); Speed(20); }
+ OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_WIND_POWER); Speed(15); }
+ } WHEN {
+ TURN { MOVE((opponentSide == TRUE) ? opponentLeft : playerLeft, MOVE_TAILWIND);}
+ } SCENE {
+ if (opponentSide) {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_TAILWIND, opponentLeft);
+
+ ABILITY_POPUP(opponentLeft, ABILITY_WIND_POWER);
+ MESSAGE("Being hit by Tailwind charged Foe Wobbuffet with power!");
+
+ ABILITY_POPUP(opponentRight, ABILITY_WIND_POWER);
+ MESSAGE("Being hit by Tailwind charged Foe Wobbuffet with power!");
+ }
+ else {
+ ANIMATION(ANIM_TYPE_MOVE, MOVE_TAILWIND, playerLeft);
+
+ ABILITY_POPUP(playerLeft, ABILITY_WIND_POWER);
+ MESSAGE("Being hit by Tailwind charged Wobbuffet with power!");
+
+ ABILITY_POPUP(playerRight, ABILITY_WIND_POWER);
+ MESSAGE("Being hit by Tailwind charged Wobbuffet with power!");
+ }
+ }
+}
diff --git a/test/primal_weather.c b/test/primal_weather.c
new file mode 100644
index 000000000..650a79921
--- /dev/null
+++ b/test/primal_weather.c
@@ -0,0 +1,127 @@
+#include "global.h"
+#include "test_battle.h"
+
+ASSUMPTIONS
+{
+ ASSUME(gBattleMoves[MOVE_EMBER].power != 0);
+ ASSUME(gBattleMoves[MOVE_EMBER].type == TYPE_FIRE);
+ ASSUME(gBattleMoves[MOVE_WATER_GUN].power != 0);
+ ASSUME(gBattleMoves[MOVE_WATER_GUN].type == TYPE_WATER);
+}
+
+SINGLE_BATTLE_TEST("Primordial Sea blocks damaging Fire-type moves")
+{
+ GIVEN {
+ PLAYER(SPECIES_KYOGRE) {Item(ITEM_BLUE_ORB);}
+ OPPONENT(SPECIES_WOBBUFFET);
+ } WHEN {
+ TURN { MOVE(opponent, MOVE_EMBER); }
+ TURN { MOVE(opponent, MOVE_EMBER); }
+ } SCENE {
+ MESSAGE("Foe Wobbuffet used Ember!");
+ NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent);
+ MESSAGE("The Fire-type attack fizzled out\nin the heavy rain!");
+ NOT HP_BAR(player);
+ MESSAGE("Foe Wobbuffet used Ember!");
+ NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent);
+ MESSAGE("The Fire-type attack fizzled out\nin the heavy rain!");
+ NOT HP_BAR(player);
+ } THEN {
+ EXPECT_EQ(player->hp, player->maxHP);
+ }
+}
+
+DOUBLE_BATTLE_TEST("Primordial Sea blocks damaging Fire-type moves and prints the message only once with moves hitting multiple targets")
+{
+ GIVEN {
+ ASSUME(gBattleMoves[MOVE_ERUPTION].power != 0);
+ ASSUME(gBattleMoves[MOVE_ERUPTION].type == TYPE_FIRE);
+ ASSUME(gBattleMoves[MOVE_ERUPTION].target == MOVE_TARGET_BOTH);
+ PLAYER(SPECIES_KYOGRE) {Item(ITEM_BLUE_ORB); {Speed(5);}}
+ PLAYER(SPECIES_WOBBUFFET) {Speed(5);}
+ OPPONENT(SPECIES_WOBBUFFET) {Speed(10);}
+ OPPONENT(SPECIES_WOBBUFFET) {Speed(8);}
+ } WHEN {
+ TURN { MOVE(opponentLeft, MOVE_ERUPTION); }
+ } SCENE {
+ MESSAGE("Foe Wobbuffet used Eruption!");
+ NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_ERUPTION, opponentLeft);
+ MESSAGE("The Fire-type attack fizzled out\nin the heavy rain!");
+ NOT MESSAGE("The Fire-type attack fizzled out\nin the heavy rain!");
+ } THEN {
+ EXPECT_EQ(playerLeft->hp, playerLeft->maxHP);
+ EXPECT_EQ(playerRight->hp, playerRight->maxHP);
+ }
+}
+
+SINGLE_BATTLE_TEST("Primordial Sea does not block a move if pokemon is asleep and uses a Fire-type move") // Sleep/confusion/paralysis all happen before the check for primal weather
+{
+ GIVEN {
+ PLAYER(SPECIES_KYOGRE) {Item(ITEM_BLUE_ORB);}
+ OPPONENT(SPECIES_WOBBUFFET) {Status1(STATUS1_SLEEP);}
+ } WHEN {
+ TURN { MOVE(opponent, MOVE_EMBER); }
+ } SCENE {
+ NOT MESSAGE("The Fire-type attack fizzled out\nin the heavy rain!");
+ MESSAGE("Foe Wobbuffet is fast asleep.");
+ }
+}
+
+SINGLE_BATTLE_TEST("Desolate Land blocks damaging Water-type moves")
+{
+ GIVEN {
+ PLAYER(SPECIES_GROUDON) {Item(ITEM_RED_ORB);}
+ OPPONENT(SPECIES_WOBBUFFET);
+ } WHEN {
+ TURN { MOVE(opponent, MOVE_WATER_GUN); }
+ TURN { MOVE(opponent, MOVE_WATER_GUN); }
+ } SCENE {
+ MESSAGE("Foe Wobbuffet used Water Gun!");
+ NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent);
+ MESSAGE("The Water-type attack evaporated in the harsh sunlight!");
+ NOT HP_BAR(player);
+ MESSAGE("Foe Wobbuffet used Water Gun!");
+ NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent);
+ MESSAGE("The Water-type attack evaporated in the harsh sunlight!");
+ NOT HP_BAR(player);
+ } THEN {
+ EXPECT_EQ(player->hp, player->maxHP);
+ }
+}
+
+DOUBLE_BATTLE_TEST("Desolate Land blocks damaging Water-type moves and prints the message only once with moves hitting multiple targets")
+{
+ GIVEN {
+ ASSUME(gBattleMoves[MOVE_SURF].power != 0);
+ ASSUME(gBattleMoves[MOVE_SURF].type == TYPE_WATER);
+ ASSUME(gBattleMoves[MOVE_SURF].target == MOVE_TARGET_FOES_AND_ALLY);
+ PLAYER(SPECIES_GROUDON) {Item(ITEM_RED_ORB); {Speed(5);}}
+ PLAYER(SPECIES_WOBBUFFET) {Speed(5);}
+ OPPONENT(SPECIES_WOBBUFFET) {Speed(10);}
+ OPPONENT(SPECIES_WOBBUFFET) {Speed(8);}
+ } WHEN {
+ TURN { MOVE(opponentLeft, MOVE_SURF); }
+ } SCENE {
+ MESSAGE("Foe Wobbuffet used Surf!");
+ NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SURF, opponentLeft);
+ MESSAGE("The Water-type attack evaporated in the harsh sunlight!");
+ NOT MESSAGE("The Water-type attack evaporated in the harsh sunlight!");
+ } THEN {
+ EXPECT_EQ(playerLeft->hp, playerLeft->maxHP);
+ EXPECT_EQ(playerRight->hp, playerRight->maxHP);
+ EXPECT_EQ(opponentRight->hp, opponentRight->maxHP);
+ }
+}
+
+SINGLE_BATTLE_TEST("Desolate Land does not block a move if pokemon is asleep and uses a Water-type move") // Sleep/confusion/paralysis all happen before the check for primal weather
+{
+ GIVEN {
+ PLAYER(SPECIES_GROUDON) {Item(ITEM_RED_ORB);}
+ OPPONENT(SPECIES_WOBBUFFET) {Status1(STATUS1_SLEEP);}
+ } WHEN {
+ TURN { MOVE(opponent, MOVE_WATER_GUN); }
+ } SCENE {
+ NOT MESSAGE("The Water-type attack evaporated in the harsh sunlight!");
+ MESSAGE("Foe Wobbuffet is fast asleep.");
+ }
+}