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."); + } +}