solve conflics

This commit is contained in:
Alex 2023-08-07 14:25:30 +02:00
commit 8ee3a3c5bb
80 changed files with 3280 additions and 1838 deletions

View File

@ -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
```
<details>
<summary><i>Note...</i></summary>
> 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.
</details>
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\\&#8288;_\<user>_**, where *\<user>* 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\\_\<user>_\Downloads** (the Downloads location for most users), enter this command:
```bash
cd Downloads
```
<details>
<summary><i>Notes...</i></summary>
> 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 isnt 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.
</details>
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.

View File

@ -118,7 +118,7 @@ LIBPATH := -L ../../tools/agbcc/lib
LIB := $(LIBPATH) -lgcc -lc -L../../libagbsyscall -lagbsyscall
else
CC1 = $(shell $(PATH_MODERNCC) --print-prog-name=cc1) -quiet
override CFLAGS += -mthumb -mthumb-interwork -O2 -mabi=apcs-gnu -mtune=arm7tdmi -march=armv4t -fno-toplevel-reorder -Wno-pointer-to-int-cast
override CFLAGS += -mthumb -mthumb-interwork -O2 -mabi=apcs-gnu -mtune=arm7tdmi -march=armv4t -fno-toplevel-reorder -Wno-pointer-to-int-cast -std=gnu17 -fanalyzer
ROM := $(MODERN_ROM_NAME)
OBJ_DIR := $(MODERN_OBJ_DIR_NAME)
LIBPATH := -L "$(dir $(shell $(PATH_MODERNCC) -mthumb -print-file-name=libgcc.a))" -L "$(dir $(shell $(PATH_MODERNCC) -mthumb -print-file-name=libnosys.a))" -L "$(dir $(shell $(PATH_MODERNCC) -mthumb -print-file-name=libc.a))"

View File

@ -1053,8 +1053,9 @@
.byte 0xca
.endm
.macro setcharge
.macro setcharge battler:req
.byte 0xcb
.byte \battler
.endm
.macro callterrainattack
@ -1381,6 +1382,12 @@
.4byte \ptr
.endm
.macro jumpifcantloseitem battler:req, ptr:req
callnative BS_JumpIfCantLoseItem
.byte \battler
.4byte \ptr
.endm
@ various command changed to more readable macros
.macro cancelmultiturnmoves battler:req
various \battler, VARIOUS_CANCEL_MULTI_TURN_MOVES

File diff suppressed because it is too large Load Diff

View File

@ -435,6 +435,7 @@ gBattleScriptsForMoveEffects::
.4byte BattleScript_EffectHit @ EFFECT_COLLISION_COURSE
.4byte BattleScript_EffectSpinOut @ EFFECT_SPIN_OUT
.4byte BattleScript_EffectMakeItRain @ EFFECT_MAKE_IT_RAIN
.4byte BattleScript_EffectCorrosiveGas @ EFFECT_CORROSIVE_GAS
.4byte BattleScript_EffectHit @ EFFECT_POPULATION_BOMB
.4byte BattleScript_EffectMortalSpin @ EFFECT_MORTAL_SPIN
@ -447,6 +448,29 @@ BattleScript_EffectMortalSpin:
moveendall
end
BattleScript_EffectCorrosiveGas:
attackcanceler
accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE
attackstring
ppreduce
jumpifsubstituteblocks BattleScript_CorrosiveGasFail
jumpifcantloseitem BS_TARGET, BattleScript_CorrosiveGasFail
attackanimation
waitanimation
jumpifability BS_TARGET, ABILITY_STICKY_HOLD, BattleScript_StickyHoldActivates
setlastuseditem BS_TARGET
removeitem BS_TARGET
printstring STRINGID_PKMNITEMMELTED
waitmessage B_WAIT_TIME_LONG
goto BattleScript_MoveEnd
BattleScript_CorrosiveGasFail:
pause B_WAIT_TIME_SHORT
orhalfword gMoveResultFlags, MOVE_RESULT_FAILED
printstring STRINGID_NOEFFECTONTARGET
waitmessage B_WAIT_TIME_LONG
goto BattleScript_MoveEnd
BattleScript_EffectMakeItRain:
setmoveeffect MOVE_EFFECT_PAYDAY
call BattleScript_EffectHit_Ret
@ -3006,12 +3030,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:
@ -5747,7 +5768,7 @@ BattleScript_EffectCharge::
attackcanceler
attackstring
ppreduce
setcharge
setcharge BS_ATTACKER
attackanimation
waitanimation
.if B_CHARGE_SPDEF_RAISE >= GEN_5
@ -6783,14 +6804,14 @@ BattleScript_SunlightFaded::
BattleScript_OverworldWeatherStarts::
printfromtable gWeatherStartsStringIds
waitmessage B_WAIT_TIME_LONG
playanimation_var BS_ATTACKER, sB_ANIM_ARG1
playanimation_var BS_BATTLER_0, sB_ANIM_ARG1
call BattleScript_ActivateWeatherAbilities
end3
BattleScript_OverworldTerrain::
printfromtable gTerrainStringIds
waitmessage B_WAIT_TIME_LONG
playanimation BS_SCRIPTING, B_ANIM_RESTORE_BG
playanimation BS_BATTLER_0, B_ANIM_RESTORE_BG
call BattleScript_ActivateTerrainEffects
end3
@ -6814,27 +6835,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
@ -7296,11 +7296,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:
@ -8659,15 +8656,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::
@ -8679,17 +8675,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
@ -10321,6 +10306,7 @@ BattleScript_SymbiosisActivates::
return
BattleScript_TargetAbilityStatRaiseRet::
copybyte sSAVED_BATTLER, gBattlerAttacker
copybyte gBattlerAbility, gEffectBattler
copybyte gBattlerAttacker, gBattlerTarget
call BattleScript_AbilityPopUp
@ -10328,6 +10314,7 @@ BattleScript_TargetAbilityStatRaiseRet::
setgraphicalstatchangevalues
call BattleScript_StatUp
BattleScript_TargetAbilityStatRaiseRet_End:
copybyte gBattlerAttacker, sSAVED_BATTLER
return
BattleScript_PokemonCantUseTheMove::

View File

@ -48,11 +48,7 @@ struct OamDimensions
s8 height;
};
static void UpdateOamCoords(void);
static void BuildSpritePriorities(void);
static void SortSprites(void);
static void CopyMatricesToOamBuffer(void);
static void AddSpritesToOamBuffer(void);
static void SortSprites(u32 *spritePriorities, s32 n);
static u8 CreateSpriteAt(u8 index, const struct SpriteTemplate *template, s16 x, s16 y, u8 subpriority);
static void ResetOamMatrices(void);
static void ResetSprite(struct Sprite *sprite);
@ -280,12 +276,12 @@ u32 gOamMatrixAllocBitmap;
u8 gReservedSpritePaletteCount;
EWRAM_DATA struct Sprite gSprites[MAX_SPRITES + 1] = {0};
EWRAM_DATA static u16 sSpritePriorities[MAX_SPRITES] = {0};
EWRAM_DATA static u8 sSpriteOrder[MAX_SPRITES] = {0};
EWRAM_DATA static bool8 sShouldProcessSpriteCopyRequests = 0;
EWRAM_DATA static u8 sSpriteCopyRequestCount = 0;
EWRAM_DATA static struct SpriteCopyRequest sSpriteCopyRequests[MAX_SPRITES] = {0};
EWRAM_DATA u8 gOamLimit = 0;
static EWRAM_DATA u8 gOamDummyIndex = 0;
EWRAM_DATA u16 gReservedSpriteTileCount = 0;
EWRAM_DATA static u8 sSpriteTileAllocBitmap[128] = {0};
EWRAM_DATA s16 gSpriteCoordOffsetX = 0;
@ -296,6 +292,7 @@ EWRAM_DATA bool8 gAffineAnimsDisabled = FALSE;
void ResetSpriteData(void)
{
ResetOamRange(0, 128);
gOamDummyIndex = 0;
ResetAllSprites();
ClearSpriteCopyRequests();
ResetAffineAnimData();
@ -326,179 +323,128 @@ void AnimateSprites(void)
void BuildOamBuffer(void)
{
u8 temp;
UpdateOamCoords();
BuildSpritePriorities();
SortSprites();
temp = gMain.oamLoadDisabled;
bool32 oamLoadDisabled;
u32 i, stride;
u8 oamIndex;
// All attributes which affect sorting packed into a single u32:
// { priority:2, subpriority:8, y:9, :5, index:8 }.
// Index has its own byte even though it only needs 6 bits so that
// we can load it with a ldrb instead of having to mask out the
// bottom 6 bits.
u32 spritePriorities[MAX_SPRITES];
s32 toSort = 0;
u8 skippedSprites[MAX_SPRITES];
u32 skippedSpritesN = 0;
u32 matrices = 0;
for (i = 0; i < MAX_SPRITES; i++)
{
// Reuse existing sSpriteOrder because we expect the order to be
// relatively stable between frames.
u32 index = sSpriteOrder[i];
struct Sprite *sprite = &gSprites[index];
s32 y;
if (!sprite->inUse || sprite->invisible)
{
skippedSprites[skippedSpritesN++] = index;
continue;
}
if (sprite->oam.affineMode & ST_OAM_AFFINE_ON_MASK)
matrices |= 1 << sprite->oam.matrixNum;
if (sprite->coordOffsetEnabled)
{
sprite->oam.x = sprite->x + sprite->x2 + sprite->centerToCornerVecX + gSpriteCoordOffsetX;
sprite->oam.y = sprite->y + sprite->y2 + sprite->centerToCornerVecY + gSpriteCoordOffsetY;
}
else
{
sprite->oam.x = sprite->x + sprite->x2 + sprite->centerToCornerVecX;
sprite->oam.y = sprite->y + sprite->y2 + sprite->centerToCornerVecY;
}
y = sprite->oam.y;
if (y >= DISPLAY_HEIGHT)
{
y -= 256;
}
else if (sprite->oam.affineMode == ST_OAM_AFFINE_DOUBLE
&& sprite->oam.size == ST_OAM_SIZE_3)
{
u32 shape = sprite->oam.shape;
if (shape == ST_OAM_SQUARE || shape == ST_OAM_V_RECTANGLE)
{
if (y > 128)
y -= 256;
}
}
// y in [-128...159], so (159 - y) in [0..287].
spritePriorities[toSort++]
= (sprite->oam.priority << 30)
| (sprite->subpriority << 22)
| (((159 - y) & 0x1FF) << 13)
| (index << 0);
}
SortSprites(spritePriorities, toSort);
for (i = 0; i < toSort; i++)
sSpriteOrder[i] = spritePriorities[i] & 0xFF;
for (i = 0; i < skippedSpritesN; i++)
sSpriteOrder[toSort + i] = skippedSprites[i];
oamLoadDisabled = gMain.oamLoadDisabled;
gMain.oamLoadDisabled = TRUE;
AddSpritesToOamBuffer();
CopyMatricesToOamBuffer();
gMain.oamLoadDisabled = temp;
for (i = 0, oamIndex = 0; i < toSort; i++)
{
if (AddSpriteToOamBuffer(&gSprites[spritePriorities[i] & 0xFF], &oamIndex))
break;
}
for (i = oamIndex; i < gOamDummyIndex; i++)
gMain.oamBuffer[i] = gDummyOamData;
gOamDummyIndex = oamIndex;
for (i = 0; matrices != 0; i++, matrices >>= 1)
{
if (matrices & 1)
{
u32 base = 4 * i;
gMain.oamBuffer[base + 0].affineParam = gOamMatrices[i].a;
gMain.oamBuffer[base + 1].affineParam = gOamMatrices[i].b;
gMain.oamBuffer[base + 2].affineParam = gOamMatrices[i].c;
gMain.oamBuffer[base + 3].affineParam = gOamMatrices[i].d;
}
}
gMain.oamLoadDisabled = oamLoadDisabled;
sShouldProcessSpriteCopyRequests = TRUE;
}
void UpdateOamCoords(void)
static inline void InsertionSort(u32 *spritePriorities, s32 n)
{
u8 i;
for (i = 0; i < MAX_SPRITES; i++)
s32 i = 1;
while (i < n)
{
struct Sprite *sprite = &gSprites[i];
if (sprite->inUse && !sprite->invisible)
u32 x = spritePriorities[i];
s32 j = i - 1;
while (j >= 0 && spritePriorities[j] > x)
{
if (sprite->coordOffsetEnabled)
{
sprite->oam.x = sprite->x + sprite->x2 + sprite->centerToCornerVecX + gSpriteCoordOffsetX;
sprite->oam.y = sprite->y + sprite->y2 + sprite->centerToCornerVecY + gSpriteCoordOffsetY;
}
else
{
sprite->oam.x = sprite->x + sprite->x2 + sprite->centerToCornerVecX;
sprite->oam.y = sprite->y + sprite->y2 + sprite->centerToCornerVecY;
}
}
}
}
void BuildSpritePriorities(void)
{
u16 i;
for (i = 0; i < MAX_SPRITES; i++)
{
struct Sprite *sprite = &gSprites[i];
u16 priority = sprite->subpriority | (sprite->oam.priority << 8);
sSpritePriorities[i] = priority;
}
}
void SortSprites(void)
{
u8 i;
for (i = 1; i < MAX_SPRITES; i++)
{
u8 j = i;
struct Sprite *sprite1 = &gSprites[sSpriteOrder[i - 1]];
struct Sprite *sprite2 = &gSprites[sSpriteOrder[i]];
u16 sprite1Priority = sSpritePriorities[sSpriteOrder[i - 1]];
u16 sprite2Priority = sSpritePriorities[sSpriteOrder[i]];
s16 sprite1Y = sprite1->oam.y;
s16 sprite2Y = sprite2->oam.y;
if (sprite1Y >= DISPLAY_HEIGHT)
sprite1Y = sprite1Y - 256;
if (sprite2Y >= DISPLAY_HEIGHT)
sprite2Y = sprite2Y - 256;
if (sprite1->oam.affineMode == ST_OAM_AFFINE_DOUBLE
&& sprite1->oam.size == ST_OAM_SIZE_3)
{
u32 shape = sprite1->oam.shape;
if (shape == ST_OAM_SQUARE || shape == ST_OAM_V_RECTANGLE)
{
if (sprite1Y > 128)
sprite1Y = sprite1Y - 256;
}
}
if (sprite2->oam.affineMode == ST_OAM_AFFINE_DOUBLE
&& sprite2->oam.size == ST_OAM_SIZE_3)
{
u32 shape = sprite2->oam.shape;
if (shape == ST_OAM_SQUARE || shape == ST_OAM_V_RECTANGLE)
{
if (sprite2Y > 128)
sprite2Y = sprite2Y - 256;
}
}
while (j > 0
&& ((sprite1Priority > sprite2Priority)
|| (sprite1Priority == sprite2Priority && sprite1Y < sprite2Y)))
{
u8 temp = sSpriteOrder[j];
sSpriteOrder[j] = sSpriteOrder[j - 1];
sSpriteOrder[j - 1] = temp;
// UB: If j equals 1, then j-- makes j equal 0.
// Then, sSpriteOrder[-1] gets accessed below.
// Although this doesn't result in a bug in the ROM,
// the behavior is undefined.
spritePriorities[j + 1] = spritePriorities[j];
j--;
#ifdef UBFIX
if (j == 0)
break;
#endif
sprite1 = &gSprites[sSpriteOrder[j - 1]];
sprite2 = &gSprites[sSpriteOrder[j]];
sprite1Priority = sSpritePriorities[sSpriteOrder[j - 1]];
sprite2Priority = sSpritePriorities[sSpriteOrder[j]];
sprite1Y = sprite1->oam.y;
sprite2Y = sprite2->oam.y;
if (sprite1Y >= DISPLAY_HEIGHT)
sprite1Y = sprite1Y - 256;
if (sprite2Y >= DISPLAY_HEIGHT)
sprite2Y = sprite2Y - 256;
if (sprite1->oam.affineMode == ST_OAM_AFFINE_DOUBLE
&& sprite1->oam.size == ST_OAM_SIZE_3)
{
u32 shape = sprite1->oam.shape;
if (shape == ST_OAM_SQUARE || shape == ST_OAM_V_RECTANGLE)
{
if (sprite1Y > 128)
sprite1Y = sprite1Y - 256;
}
}
if (sprite2->oam.affineMode == ST_OAM_AFFINE_DOUBLE
&& sprite2->oam.size == ST_OAM_SIZE_3)
{
u32 shape = sprite2->oam.shape;
if (shape == ST_OAM_SQUARE || shape == ST_OAM_V_RECTANGLE)
{
if (sprite2Y > 128)
sprite2Y = sprite2Y - 256;
}
}
}
}
}
void CopyMatricesToOamBuffer(void)
{
u8 i;
for (i = 0; i < OAM_MATRIX_COUNT; i++)
{
u32 base = 4 * i;
gMain.oamBuffer[base + 0].affineParam = gOamMatrices[i].a;
gMain.oamBuffer[base + 1].affineParam = gOamMatrices[i].b;
gMain.oamBuffer[base + 2].affineParam = gOamMatrices[i].c;
gMain.oamBuffer[base + 3].affineParam = gOamMatrices[i].d;
}
}
void AddSpritesToOamBuffer(void)
{
u8 i = 0;
u8 oamIndex = 0;
while (i < MAX_SPRITES)
{
struct Sprite *sprite = &gSprites[sSpriteOrder[i]];
if (sprite->inUse && !sprite->invisible && AddSpriteToOamBuffer(sprite, &oamIndex))
return;
spritePriorities[j + 1] = x;
i++;
}
}
while (oamIndex < gOamLimit)
{
gMain.oamBuffer[oamIndex] = gDummyOamData;
oamIndex++;
}
static void SortSprites(u32 *spritePriorities, s32 n)
{
InsertionSort(spritePriorities, n);
}
u8 CreateSprite(const struct SpriteTemplate *template, s16 x, s16 y, u8 subpriority)
@ -849,7 +795,7 @@ void CopyToSprites(u8 *src)
void ResetAllSprites(void)
{
u8 i;
u32 i;
for (i = 0; i < MAX_SPRITES; i++)
{

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 402 B

After

Width:  |  Height:  |  Size: 363 B

View File

@ -168,7 +168,9 @@ bool32 PartnerMoveIsSameNoTarget(u8 battlerAtkPartner, u16 move, u16 partnerMove
bool32 ShouldUseWishAromatherapy(u8 battlerAtk, u8 battlerDef, u16 move);
// party logic
s32 AI_CalcPartyMonDamage(u16 move, u8 battlerAtk, u8 battlerDef, struct Pokemon *mon);
struct BattlePokemon *AllocSaveBattleMons(void);
void FreeRestoreBattleMons(struct BattlePokemon *savedBattleMons);
s32 AI_CalcPartyMonBestMoveDamage(u32 battlerAtk, u32 battlerDef, struct Pokemon *attackerMon, struct Pokemon *targetMon);
s32 CountUsablePartyMons(u8 battlerId);
bool32 IsPartyFullyHealedExceptBattler(u8 battler);
bool32 PartyHasMoveSplit(u8 battlerId, u8 split);

View File

@ -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[];

View File

@ -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);

View File

@ -71,7 +71,7 @@
#define GEN_7 4
#define GEN_8 5
#define GEN_9 6
#define GEN_LATEST GEN_8
#define GEN_LATEST GEN_9
// General settings
#define EXPANSION_INTRO TRUE // If TRUE, a custom RHH intro will play after the vanilla copyright screen.

View File

@ -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.

View File

@ -397,6 +397,7 @@
#define ANIM_TAG_STEEL_BEAM (ANIM_SPRITES_START + 383)
#define ANIM_TAG_POLTERGEIST (ANIM_SPRITES_START + 384)
#define ANIM_TAG_TEAPOT (ANIM_SPRITES_START + 385)
#define ANIM_TAG_WOOD_HAMMER_HAMMER (ANIM_SPRITES_START + 386)
// battlers

View File

@ -412,9 +412,10 @@
#define EFFECT_COLLISION_COURSE 406
#define EFFECT_SPIN_OUT 407
#define EFFECT_MAKE_IT_RAIN 408
#define EFFECT_POPULATION_BOMB 409
#define EFFECT_MORTAL_SPIN 410
#define EFFECT_CORROSIVE_GAS 409
#define EFFECT_POPULATION_BOMB 410
#define EFFECT_MORTAL_SPIN 411
#define NUM_BATTLE_MOVE_EFFECTS 411
#define NUM_BATTLE_MOVE_EFFECTS 412
#endif // GUARD_CONSTANTS_BATTLE_MOVE_EFFECTS_H

View File

@ -664,8 +664,9 @@
#define STRINGID_SNOWCONTINUES 662
#define STRINGID_SNOWSTOPPED 663
#define STRINGID_SNOWWARNINGSNOW 664
#define STRINGID_PKMNITEMMELTED 665
#define BATTLESTRINGS_COUNT 665
#define BATTLESTRINGS_COUNT 666
// This is the string id that gBattleStringsTable starts with.
// String ids before this (e.g. STRINGID_INTROMSG) are not in the table,
@ -827,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

View File

@ -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);

View File

@ -664,6 +664,7 @@
#define TIMER_64CLK 0x01
#define TIMER_256CLK 0x02
#define TIMER_1024CLK 0x03
#define TIMER_COUNTUP 0x04
#define TIMER_INTR_ENABLE 0x40
#define TIMER_ENABLE 0x80

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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

View File

@ -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[];
@ -10161,6 +10161,8 @@ extern const u32 gBattleAnimSpriteGfx_ZMoveSymbol[];
extern const u32 gBattleAnimSpritePal_ZMoveSymbol[];
extern const u32 gBattleAnimSpriteGfx_Teapot[];
extern const u32 gBattleAnimSpritePal_Teapot[];
extern const u32 gBattleAnimSpriteGfx_WoodHammerHammer[];
extern const u32 gBattleAnimSpritePal_WoodHammerHammer[];
extern const u32 gBattleAnimBgImage_Dark[];
extern const u32 gBattleAnimBgImage_Ghost[];
@ -10589,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

View File

@ -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];
};

View File

@ -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;
};

View File

@ -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;

View File

@ -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;
};

View File

@ -67,7 +67,7 @@ void LoadObjEventTemplatesFromHeader(void);
void LoadSaveblockObjEventScripts(void);
void SetObjEventTemplateCoords(u8 localId, s16 x, s16 y);
void SetObjEventTemplateMovementType(u8 localId, u8 movementType);
const struct MapLayout *GetMapLayout(void);
const struct MapLayout *GetMapLayout(u16 mapLayoutId);
void ApplyCurrentWarp(void);
struct MapHeader const *const Overworld_GetMapHeaderByGroupAndId(u16 mapGroup, u16 mapNum);
struct MapHeader const *const GetDestinationWarpMapHeader(void);

View File

@ -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);

View File

@ -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);

View File

@ -337,7 +337,7 @@ struct SpeciesInfo /*0x24*/
struct BattleMove
{
u16 effect;
u16 power; //higher than 255 for z moves
u8 power;
u8 type;
u8 accuracy;
u8 pp;
@ -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);

View File

@ -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);

View File

@ -28,6 +28,7 @@ static bool8 ShouldUseItem(void);
static bool32 AiExpectsToFaintPlayer(void);
static bool32 AI_ShouldHeal(u32 healAmount);
static bool32 AI_OpponentCanFaintAiWithMod(u32 healAmount);
static bool32 IsAiPartyMonOHKOBy(u32 battlerAtk, struct Pokemon *aiMon);
static bool32 IsAceMon(u32 battlerId, u32 monPartyId)
{
@ -112,11 +113,7 @@ static bool8 ShouldSwitchIfWonderGuard(void)
// Find a Pokemon in the party that has a super effective move.
for (i = firstId; i < lastId; i++)
{
if (GetMonData(&party[i], MON_DATA_HP) == 0)
continue;
if (GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_NONE)
continue;
if (GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_EGG)
if (!IsValidForBattle(&party[i]))
continue;
if (i == gBattlerPartyIndexes[gActiveBattler])
continue;
@ -195,13 +192,9 @@ static bool8 FindMonThatAbsorbsOpponentsMove(void)
for (i = firstId; i < lastId; i++)
{
u16 species;
u16 monAbility;
if (GetMonData(&party[i], MON_DATA_HP) == 0)
continue;
species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG);
if (species == SPECIES_NONE || species == SPECIES_EGG)
if (!IsValidForBattle(&party[i]))
continue;
if (i == gBattlerPartyIndexes[battlerIn1])
continue;
@ -215,7 +208,6 @@ static bool8 FindMonThatAbsorbsOpponentsMove(void)
continue;
monAbility = GetMonAbility(&party[i]);
if (absorbingTypeAbility == monAbility && Random() & 1)
{
// we found a mon.
@ -290,9 +282,7 @@ static bool8 ShouldSwitchIfGameStatePrompt(void)
continue;
//Look for mon in party that is able to be switched into and has ability that sets terrain
if (GetMonData(&party[i], MON_DATA_HP) != 0
&& GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE
&& GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG
if (IsValidForBattle(&party[i])
&& i != gBattlerPartyIndexes[gActiveBattler]
&& i != gBattlerPartyIndexes[BATTLE_PARTNER(gActiveBattler)]
&& IsBattlerGrounded(gActiveBattler)
@ -561,13 +551,9 @@ static bool8 FindMonWithFlagsAndSuperEffective(u16 flags, u8 moduloPercent)
for (i = firstId; i < lastId; i++)
{
u16 species;
u16 monAbility;
u16 species, monAbility;
if (GetMonData(&party[i], MON_DATA_HP) == 0)
continue;
species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG);
if (species == SPECIES_NONE || species == SPECIES_EGG)
if (!IsValidForBattle(&party[i]))
continue;
if (i == gBattlerPartyIndexes[battlerIn1])
continue;
@ -580,8 +566,8 @@ static bool8 FindMonWithFlagsAndSuperEffective(u16 flags, u8 moduloPercent)
if (IsAceMon(gActiveBattler, i))
continue;
species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG);
monAbility = GetMonAbility(&party[i]);
CalcPartyMonTypeEffectivenessMultiplier(gLastLandedMoves[gActiveBattler], species, monAbility);
if (gMoveResultFlags & flags)
{
@ -650,11 +636,7 @@ bool32 ShouldSwitch(void)
for (i = firstId; i < lastId; i++)
{
if (GetMonData(&party[i], MON_DATA_HP) == 0)
continue;
if (GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_NONE)
continue;
if (GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) == SPECIES_EGG)
if (!IsValidForBattle(&party[i]))
continue;
if (i == gBattlerPartyIndexes[battlerIn1])
continue;
@ -751,7 +733,7 @@ void AI_TrySwitchOrUseItem(void)
for (monToSwitchId = (lastId-1); monToSwitchId >= firstId; monToSwitchId--)
{
if (GetMonData(&party[monToSwitchId], MON_DATA_HP) == 0)
if (!IsValidForBattle(&party[monToSwitchId]))
continue;
if (monToSwitchId == gBattlerPartyIndexes[battlerIn1])
continue;
@ -785,7 +767,7 @@ void AI_TrySwitchOrUseItem(void)
// If there are two(or more) mons to choose from, always choose one that has baton pass
// as most often it can't do much on its own.
static u32 GetBestMonBatonPass(struct Pokemon *party, int firstId, int lastId, u8 invalidMons, int aliveCount)
static u32 GetBestMonBatonPass(struct Pokemon *party, int firstId, int lastId, u8 invalidMons, int aliveCount, u32 opposingBattler)
{
int i, j, bits = 0;
@ -793,6 +775,8 @@ static u32 GetBestMonBatonPass(struct Pokemon *party, int firstId, int lastId, u
{
if (invalidMons & gBitTable[i])
continue;
if (IsAiPartyMonOHKOBy(opposingBattler, &party[i]))
continue;
for (j = 0; j < MAX_MON_MOVES; j++)
{
@ -837,6 +821,9 @@ static u32 GetBestMonTypeMatchup(struct Pokemon *party, int firstId, int lastId,
u8 defType1 = gSpeciesInfo[species].types[0];
u8 defType2 = gSpeciesInfo[species].types[1];
if (IsAiPartyMonOHKOBy(opposingBattler, &party[i]))
continue;
typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType1, defType1)));
if (atkType2 != atkType1)
typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType2, defType1)));
@ -881,7 +868,7 @@ static u32 GetBestMonTypeMatchup(struct Pokemon *party, int firstId, int lastId,
static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 invalidMons, u32 opposingBattler)
{
int i, j;
int bestDmg = 0;
int dmg, bestDmg = 0;
int bestMonId = PARTY_SIZE;
gMoveResultFlags = 0;
@ -890,19 +877,14 @@ static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 inva
{
if (gBitTable[i] & invalidMons)
continue;
if (IsAiPartyMonOHKOBy(opposingBattler, &party[i]))
continue;
for (j = 0; j < MAX_MON_MOVES; j++)
dmg = AI_CalcPartyMonBestMoveDamage(gActiveBattler, opposingBattler, &party[i], NULL);
if (bestDmg < dmg)
{
u32 move = GetMonData(&party[i], MON_DATA_MOVE1 + j);
if (move != MOVE_NONE && gBattleMoves[move].power != 0)
{
s32 dmg = AI_CalcPartyMonDamage(move, gActiveBattler, opposingBattler, &party[i]);
if (bestDmg < dmg)
{
bestDmg = dmg;
bestMonId = i;
}
}
bestDmg = dmg;
bestMonId = i;
}
}
@ -912,7 +894,7 @@ static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 inva
u8 GetMostSuitableMonToSwitchInto(void)
{
u32 opposingBattler = 0;
u32 bestMonId = 0;
u32 bestMonId = PARTY_SIZE;
u8 battlerIn1 = 0, battlerIn2 = 0;
s32 firstId = 0;
s32 lastId = 0; // + 1
@ -954,10 +936,7 @@ u8 GetMostSuitableMonToSwitchInto(void)
// Get invalid slots ids.
for (i = firstId; i < lastId; i++)
{
u16 species = GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG);
if (species == SPECIES_NONE
|| species == SPECIES_EGG
|| GetMonData(&party[i], MON_DATA_HP) == 0
if (!IsValidForBattle(&party[i])
|| gBattlerPartyIndexes[battlerIn1] == i
|| gBattlerPartyIndexes[battlerIn2] == i
|| i == *(gBattleStruct->monToSwitchIntoId + battlerIn1)
@ -977,7 +956,7 @@ u8 GetMostSuitableMonToSwitchInto(void)
}
}
bestMonId = GetBestMonBatonPass(party, firstId, lastId, invalidMons, aliveCount);
bestMonId = GetBestMonBatonPass(party, firstId, lastId, invalidMons, aliveCount, opposingBattler);
if (bestMonId != PARTY_SIZE)
return bestMonId;
@ -1040,9 +1019,7 @@ static bool8 ShouldUseItem(void)
for (i = 0; i < PARTY_SIZE; i++)
{
if (GetMonData(&party[i], MON_DATA_HP) != 0
&& GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_NONE
&& GetMonData(&party[i], MON_DATA_SPECIES_OR_EGG) != SPECIES_EGG)
if (IsValidForBattle(&party[i]))
{
validMons++;
}
@ -1155,3 +1132,29 @@ static bool32 AI_OpponentCanFaintAiWithMod(u32 healAmount)
}
return FALSE;
}
static bool32 IsAiPartyMonOHKOBy(u32 battlerAtk, struct Pokemon *aiMon)
{
bool32 ret = FALSE;
struct BattlePokemon *savedBattleMons;
s32 hp = GetMonData(aiMon, MON_DATA_HP);
s32 bestDmg = AI_CalcPartyMonBestMoveDamage(battlerAtk, gActiveBattler, NULL, aiMon);
switch (GetNoOfHitsToKO(bestDmg, hp))
{
case 1:
ret = TRUE;
break;
case 2: // if AI mon is faster allow 2 turns
savedBattleMons = AllocSaveBattleMons();
PokemonToBattleMon(aiMon, &gBattleMons[gActiveBattler]);
if (AI_WhoStrikesFirst(gActiveBattler, battlerAtk, 0) == AI_IS_SLOWER)
ret = TRUE;
else
ret = FALSE;
FreeRestoreBattleMons(savedBattleMons);
break;
}
return ret;
}

View File

@ -3369,25 +3369,49 @@ bool32 ShouldUseWishAromatherapy(u8 battlerAtk, u8 battlerDef, u16 move)
return FALSE;
}
// party logic
s32 AI_CalcPartyMonDamage(u16 move, u8 battlerAtk, u8 battlerDef, struct Pokemon *mon)
#define SIZE_G_BATTLE_MONS (sizeof(struct BattlePokemon) * MAX_BATTLERS_COUNT)
struct BattlePokemon *AllocSaveBattleMons(void)
{
s32 dmg;
u32 i;
struct BattlePokemon *savedBattleMons = Alloc(SIZE_G_BATTLE_MONS);
memcpy(savedBattleMons, gBattleMons, SIZE_G_BATTLE_MONS);
return savedBattleMons;
}
void FreeRestoreBattleMons(struct BattlePokemon *savedBattleMons)
{
memcpy(gBattleMons, savedBattleMons, SIZE_G_BATTLE_MONS);
Free(savedBattleMons);
}
// party logic
s32 AI_CalcPartyMonBestMoveDamage(u32 battlerAtk, u32 battlerDef, struct Pokemon *attackerMon, struct Pokemon *targetMon)
{
s32 i, move, bestDmg, dmg;
u8 effectiveness;
struct BattlePokemon *battleMons = Alloc(sizeof(struct BattlePokemon) * MAX_BATTLERS_COUNT);
struct BattlePokemon *savedBattleMons = AllocSaveBattleMons();
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
battleMons[i] = gBattleMons[i];
if (attackerMon != NULL)
PokemonToBattleMon(attackerMon, &gBattleMons[battlerAtk]);
if (targetMon != NULL)
PokemonToBattleMon(targetMon, &gBattleMons[battlerDef]);
PokemonToBattleMon(mon, &gBattleMons[battlerAtk]);
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, FALSE);
for (bestDmg = 0, i = 0; i < MAX_MON_MOVES; i++)
{
if (BattlerHasAi(battlerAtk))
move = GetMonData(attackerMon, MON_DATA_MOVE1 + i);
else
move = AI_PARTY->mons[GET_BATTLER_SIDE2(battlerAtk)][gBattlerPartyIndexes[battlerAtk]].moves[i];
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
gBattleMons[i] = battleMons[i];
Free(battleMons);
if (move != MOVE_NONE && gBattleMoves[move].power != 0)
{
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, FALSE);
if (dmg > bestDmg)
bestDmg = dmg;
}
}
FreeRestoreBattleMons(savedBattleMons);
return dmg;
}

View File

@ -147,6 +147,9 @@ static void AnimGrassKnotStep(struct Sprite *);
static void AnimGrassKnot(struct Sprite *);
static void AnimWoodHammerSmall(struct Sprite *);
static void AnimWoodHammerBig(struct Sprite *);
static void AnimWoodHammerHammer(struct Sprite *);
static void AnimWoodHammerHammer_WaitForPunch(struct Sprite *);
static void AnimWoodHammerHammer_WaitForDestruction(struct Sprite *);
static void AnimTask_DoubleTeam_Step(u8);
static void AnimDoubleTeam(struct Sprite *);
static void AnimNightSlash(struct Sprite *);
@ -2847,24 +2850,67 @@ const union AffineAnimCmd *const gWoodHammerBigAffineAnims[] =
gWoodHammerBigAffineAnimCmd_2,
};
const union AnimCmd gWoodHammerSmallAnimCmd_1[] =
#define WOOD_HAMMER_SCALE_STEP 5
#define WOOD_HAMMER_CC_ROTATION_STEP 2
#define WOOD_HAMMER_BACKWARDS_DURATION 40
#define WOOD_HAMMER_ROTATED_AMOUNT (WOOD_HAMMER_CC_ROTATION_STEP * WOOD_HAMMER_BACKWARDS_DURATION)
#define WOOD_HAMMER_SCALED_AMOUNT (WOOD_HAMMER_SCALE_STEP * WOOD_HAMMER_BACKWARDS_DURATION)
const union AffineAnimCmd gWoodHammerHammerAffineAnimCmd_BackwardsRotateAndScale[] =
{
ANIMCMD_FRAME(32, 1),
ANIMCMD_END,
AFFINEANIMCMD_FRAME(WOOD_HAMMER_SCALE_STEP, WOOD_HAMMER_SCALE_STEP, WOOD_HAMMER_CC_ROTATION_STEP, WOOD_HAMMER_BACKWARDS_DURATION),
AFFINEANIMCMD_END
};
const union AnimCmd gWoodHammerSmallAnimCmd_2[] =
const union AffineAnimCmd gWoodHammerHammerAffineAnimCmd_BackwardsRotateAndScaleFlipped[] =
{
AFFINEANIMCMD_FRAME(-0x100, 0x100, 0, 0),
AFFINEANIMCMD_FRAME(-WOOD_HAMMER_SCALE_STEP, WOOD_HAMMER_SCALE_STEP, -WOOD_HAMMER_CC_ROTATION_STEP, WOOD_HAMMER_BACKWARDS_DURATION),
AFFINEANIMCMD_END
};
const union AffineAnimCmd gWoodHammerHammerAffineAnimCmd_PunchClockwise[] =
{
AFFINEANIMCMD_FRAME(0x100 + WOOD_HAMMER_SCALED_AMOUNT, 0x100 + WOOD_HAMMER_SCALED_AMOUNT, WOOD_HAMMER_ROTATED_AMOUNT, 0),
AFFINEANIMCMD_FRAME(0, 0, -16, 7),
AFFINEANIMCMD_END
};
const union AffineAnimCmd gWoodHammerHammerAffineAnimCmd_PunchCounterClockwise[] =
{
AFFINEANIMCMD_FRAME(-0x100 - WOOD_HAMMER_SCALED_AMOUNT, 0x100 + WOOD_HAMMER_SCALED_AMOUNT, -WOOD_HAMMER_ROTATED_AMOUNT, 0),
AFFINEANIMCMD_FRAME(0, 0, 16, 7),
AFFINEANIMCMD_END
};
// Animations 0, 2 are for the player side attacking
// Animations 1, 3 are for the opponent side attacking (flipped)
const union AffineAnimCmd *const gWoodHammerHammerAffineAnims[] =
{
gWoodHammerHammerAffineAnimCmd_BackwardsRotateAndScale,
gWoodHammerHammerAffineAnimCmd_BackwardsRotateAndScaleFlipped,
gWoodHammerHammerAffineAnimCmd_PunchClockwise,
gWoodHammerHammerAffineAnimCmd_PunchCounterClockwise,
};
const union AnimCmd gWoodHammerSmallAnimCmd_1[] =
{
ANIMCMD_FRAME(48, 1),
ANIMCMD_END,
};
const union AnimCmd gWoodHammerSmallAnimCmd_3[] =
const union AnimCmd gWoodHammerSmallAnimCmd_2[] =
{
ANIMCMD_FRAME(64, 1),
ANIMCMD_END,
};
const union AnimCmd gWoodHammerSmallAnimCmd_3[] =
{
ANIMCMD_FRAME(80, 1),
ANIMCMD_END,
};
const union AnimCmd *const gWoodHammerSmallAnims[] =
{
gWoodHammerSmallAnimCmd_1,
@ -2905,6 +2951,17 @@ const struct SpriteTemplate gWoodHammerSmallSpriteTemplate =
.callback = AnimWoodHammerSmall,
};
const struct SpriteTemplate gWoodHammerHammerSpriteTemplate =
{
.tileTag = ANIM_TAG_WOOD_HAMMER_HAMMER,
.paletteTag = ANIM_TAG_WOOD_HAMMER_HAMMER,
.oam = &gOamData_AffineDouble_ObjNormal_64x64,
.anims = gDummySpriteAnimTable,
.images = NULL,
.affineAnims = gWoodHammerHammerAffineAnims,
.callback = AnimWoodHammerHammer,
};
const struct SpriteTemplate gJudgmentGrayOutwardSpikesTemplate =
{
.tileTag = ANIM_TAG_GREEN_SPIKE,
@ -3022,6 +3079,65 @@ static void AnimWoodHammerSmall(struct Sprite *sprite)
StoreSpriteCallbackInData6(sprite, DestroySpriteAndMatrix);
}
#define HAMMER_X_OFFSET 40
#define HAMMER_PUNCH_WAIT_FRAMES 37
static void AnimWoodHammerHammer(struct Sprite *sprite)
{
if (GetBattlerSide(gBattleAnimAttacker) != B_SIDE_PLAYER)
{
sprite->x += HAMMER_X_OFFSET;
StartSpriteAffineAnim(sprite, 1);
}
else
{
sprite->x -= HAMMER_X_OFFSET;
StartSpriteAffineAnim(sprite, 0);
}
sprite->data[6] = HAMMER_PUNCH_WAIT_FRAMES;
sprite->callback = AnimWoodHammerHammer_WaitForPunch;
}
static void AnimWoodHammerHammer_WaitForPunch(struct Sprite *sprite)
{
if (!sprite->affineAnimEnded)
return;
if (sprite->data[6] != 0)
{
sprite->data[6]--;
if (sprite->data[6] & 1)
{
if ((sprite->data[6] / 2) & 1)
sprite->x2++;
else
sprite->x2--;
}
return;
}
if (GetBattlerSide(gBattleAnimAttacker) != B_SIDE_PLAYER)
{
StartSpriteAffineAnim(sprite, 3);
}
else
{
StartSpriteAffineAnim(sprite, 2);
}
sprite->callback = AnimWoodHammerHammer_WaitForDestruction;
}
static void AnimWoodHammerHammer_WaitForDestruction(struct Sprite *sprite)
{
if (sprite->affineAnimEnded)
{
DestroySpriteAndMatrix(sprite);
}
}
#undef HAMMER_X_OFFSET
#undef HAMMER_PUNCH_WAIT_FRAMES
// Animates the falling particles that horizontally wave back and forth.
// Used by Sleep Powder, Stun Spore, and Poison Powder.
// arg 0: initial x pixel offset

View File

@ -1688,7 +1688,6 @@ static void OpponentHandleChoosePokemon(void)
else if (*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) == PARTY_SIZE)
{
chosenMonId = GetMostSuitableMonToSwitchInto();
if (chosenMonId == PARTY_SIZE)
{
s32 battler1, battler2, firstId, lastId;
@ -1702,14 +1701,13 @@ static void OpponentHandleChoosePokemon(void)
battler1 = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
battler2 = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
pokemonInBattle = 2;
}
GetAIPartyIndexes(gActiveBattler, &firstId, &lastId);
for (chosenMonId = (lastId-1); chosenMonId >= firstId; chosenMonId--)
{
if (GetMonData(&gEnemyParty[chosenMonId], MON_DATA_HP) != 0
if (IsValidForBattle(&gEnemyParty[chosenMonId])
&& chosenMonId != gBattlerPartyIndexes[battler1]
&& chosenMonId != gBattlerPartyIndexes[battler2]
&& (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_ACE_POKEMON)

View File

@ -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[] =

View File

@ -1210,8 +1210,11 @@ void AllocateMonSpritesGfx(void)
for (j = 0; j < 4; j++)
{
gMonSpritesGfxPtr->frameImages[i][j].data = gMonSpritesGfxPtr->sprites.ptr[i] + (j * MON_PIC_SIZE);
gMonSpritesGfxPtr->frameImages[i][j].size = MON_PIC_SIZE;
if (gMonSpritesGfxPtr->sprites.ptr[i])
{
gMonSpritesGfxPtr->frameImages[i][j].data = gMonSpritesGfxPtr->sprites.ptr[i] + (j * MON_PIC_SIZE);
gMonSpritesGfxPtr->frameImages[i][j].size = MON_PIC_SIZE;
}
}
gMonSpritesGfxPtr->templates[i].images = gMonSpritesGfxPtr->frameImages[i];

View File

@ -1983,7 +1983,6 @@ u8 CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer
u8 fixedIV;
s32 i, j;
u8 monsCount;
s32 ball = -1;
if (battleTypeFlags & BATTLE_TYPE_TRAINER && !(battleTypeFlags & (BATTLE_TYPE_FRONTIER
| BATTLE_TYPE_EREADER_TRAINER
| BATTLE_TYPE_TRAINER_HILL)))
@ -2005,6 +2004,7 @@ u8 CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer
for (i = 0; i < monsCount; i++)
{
s32 ball = -1;
u32 personalityHash = GeneratePartyHash(trainer, i);
if (trainer->doubleBattle == TRUE)
personalityValue = 0x80;
@ -3129,6 +3129,9 @@ static void BattleStartClearSetData(void)
gBattlerAttacker = 0;
gBattlerTarget = 0;
gEffectBattler = 0;
gBattleScripting.battler = 0;
gBattlerAbility = 0;
gBattleWeather = 0;
gHitMarker = 0;

View File

@ -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}!");
@ -799,9 +799,11 @@ static const u8 sText_ItemCuredSpeciesStatus[] = _("{B_BUFF1} had\nits status he
static const u8 sText_ItemRestoredSpeciesPP[] = _("{B_BUFF1} had its\nPP restored!");
static const u8 sText_AtkTrappedDef[] = _("{B_ATK_NAME_WITH_PREFIX} trapped\nthe {B_DEF_NAME_WITH_PREFIX}!");
static const u8 sText_MirrorHerbCopied[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} used its {B_LAST_ITEM}\nto mirror its opponent's stat changes!");
static const u8 sText_PkmnItemMelted[] = _("{B_ATK_NAME_WITH_PREFIX} corroded\n{B_DEF_NAME_WITH_PREFIX}'s {B_LAST_ITEM}!");
const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] =
{
[STRINGID_PKMNITEMMELTED - BATTLESTRINGS_TABLE_START] = sText_PkmnItemMelted,
[STRINGID_MIRRORHERBCOPIED - BATTLESTRINGS_TABLE_START] = sText_MirrorHerbCopied,
[STRINGID_THUNDERCAGETRAPPED - BATTLESTRINGS_TABLE_START] = sText_AtkTrappedDef,
[STRINGID_ITEMRESTOREDSPECIESHEALTH - BATTLESTRINGS_TABLE_START] = sText_ItemRestoredSpeciesHealth,
@ -1850,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,

View File

@ -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)
@ -1622,6 +1623,8 @@ u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u
s8 buff, accStage, evasionStage;
u8 atkParam = GetBattlerHoldEffectParam(battlerAtk);
u8 defParam = GetBattlerHoldEffectParam(battlerDef);
u8 atkAlly = BATTLE_PARTNER(battlerAtk);
u16 atkAllyAbility = GetBattlerAbility(atkAlly);
gPotentialItemEffectBattler = battlerDef;
accStage = gBattleMons[battlerAtk].statStages[STAT_ACC];
@ -1655,30 +1658,66 @@ u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u
calc = gAccuracyStageRatios[buff].dividend * moveAcc;
calc /= gAccuracyStageRatios[buff].divisor;
if (atkAbility == ABILITY_COMPOUND_EYES)
// Attacker's ability
switch (atkAbility)
{
case ABILITY_COMPOUND_EYES:
calc = (calc * 130) / 100; // 1.3 compound eyes boost
else if (atkAbility == ABILITY_VICTORY_STAR)
break;
case ABILITY_VICTORY_STAR:
calc = (calc * 110) / 100; // 1.1 victory star boost
if (IsBattlerAlive(BATTLE_PARTNER(battlerAtk)) && GetBattlerAbility(BATTLE_PARTNER(battlerAtk)) == ABILITY_VICTORY_STAR)
calc = (calc * 110) / 100; // 1.1 ally's victory star boost
break;
case ABILITY_HUSTLE:
if (IS_MOVE_PHYSICAL(move))
calc = (calc * 80) / 100; // 1.2 hustle loss
break;
}
if (defAbility == ABILITY_SAND_VEIL && WEATHER_HAS_EFFECT && gBattleWeather & B_WEATHER_SANDSTORM)
calc = (calc * 80) / 100; // 1.2 sand veil loss
else if (defAbility == ABILITY_SNOW_CLOAK && WEATHER_HAS_EFFECT && (gBattleWeather & (B_WEATHER_HAIL | B_WEATHER_SNOW)))
calc = (calc * 80) / 100; // 1.2 snow cloak loss
else if (defAbility == ABILITY_TANGLED_FEET && gBattleMons[battlerDef].status2 & STATUS2_CONFUSION)
calc = (calc * 50) / 100; // 1.5 tangled feet loss
// Target's ability
switch (defAbility)
{
case ABILITY_SAND_VEIL:
if (WEATHER_HAS_EFFECT && gBattleWeather & B_WEATHER_SANDSTORM)
calc = (calc * 80) / 100; // 1.2 sand veil loss
break;
case ABILITY_SNOW_CLOAK:
if (WEATHER_HAS_EFFECT && (gBattleWeather & (B_WEATHER_HAIL | B_WEATHER_SNOW)))
calc = (calc * 80) / 100; // 1.2 snow cloak loss
break;
case ABILITY_TANGLED_FEET:
if (gBattleMons[battlerDef].status2 & STATUS2_CONFUSION)
calc = (calc * 50) / 100; // 1.5 tangled feet loss
break;
}
if (atkAbility == ABILITY_HUSTLE && IS_MOVE_PHYSICAL(move))
calc = (calc * 80) / 100; // 1.2 hustle loss
// Attacker's ally's ability
switch (atkAllyAbility)
{
case ABILITY_VICTORY_STAR:
if (IsBattlerAlive(atkAlly))
calc = (calc * 110) / 100; // 1.1 ally's victory star boost
break;
}
if (defHoldEffect == HOLD_EFFECT_EVASION_UP)
// Attacker's hold effect
switch (atkHoldEffect)
{
case HOLD_EFFECT_WIDE_LENS:
calc = (calc * (100 + atkParam)) / 100;
break;
case HOLD_EFFECT_ZOOM_LENS:
if (GetBattlerTurnOrderNum(battlerAtk) > GetBattlerTurnOrderNum(battlerDef))
calc = (calc * (100 + atkParam)) / 100;
break;
}
// Target's hold effect
switch (defHoldEffect)
{
case HOLD_EFFECT_EVASION_UP:
calc = (calc * (100 - defParam)) / 100;
if (atkHoldEffect == HOLD_EFFECT_WIDE_LENS)
calc = (calc * (100 + atkParam)) / 100;
else if (atkHoldEffect == HOLD_EFFECT_ZOOM_LENS && GetBattlerTurnOrderNum(battlerAtk) > GetBattlerTurnOrderNum(battlerDef))
calc = (calc * (100 + atkParam)) / 100;
break;
}
if (gProtectStructs[battlerAtk].usedMicleBerry)
{
@ -3931,11 +3970,7 @@ static void Cmd_jumpifsideaffecting(void)
u32 flags;
const u8 *jumpInstr;
if (cmd->battler == BS_ATTACKER)
side = GET_BATTLER_SIDE(gBattlerAttacker);
else
side = GET_BATTLER_SIDE(gBattlerTarget);
side = GET_BATTLER_SIDE(GetBattlerForBattleScript(cmd->battler));
flags = cmd->flags;
jumpInstr = cmd->jumpInstr;
@ -7759,7 +7794,9 @@ static void Cmd_removeitem(void)
itemId = gBattleMons[gActiveBattler].item;
// Popped Air Balloon cannot be restored by any means.
if (GetBattlerHoldEffect(gActiveBattler, TRUE) != HOLD_EFFECT_AIR_BALLOON)
// Corroded items cannot be restored either.
if (GetBattlerHoldEffect(gActiveBattler, TRUE) != HOLD_EFFECT_AIR_BALLOON
&& gBattleMoves[gCurrentMove].effect != EFFECT_CORROSIVE_GAS)
gBattleStruct->usedHeldItems[gBattlerPartyIndexes[gActiveBattler]][GetBattlerSide(gActiveBattler)] = itemId; // Remember if switched out
gBattleMons[gActiveBattler].item = ITEM_NONE;
@ -9935,7 +9972,8 @@ static void Cmd_various(void)
if (gBattleMons[gBattlerAttacker].item == ITEM_NONE
|| gBattleMons[gBattlerTarget].item != ITEM_NONE
|| !CanBattlerGetOrLoseItem(gBattlerAttacker, gBattleMons[gBattlerAttacker].item)
|| !CanBattlerGetOrLoseItem(gBattlerTarget, gBattleMons[gBattlerAttacker].item))
|| !CanBattlerGetOrLoseItem(gBattlerTarget, gBattleMons[gBattlerAttacker].item)
|| gWishFutureKnock.knockedOffMons[GetBattlerSide(gBattlerTarget)] & gBitTable[gBattlerPartyIndexes[gBattlerTarget]])
{
gBattlescriptCurrInstr = cmd->failInstr;
}
@ -14024,10 +14062,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;
}
@ -16074,6 +16113,18 @@ void BS_CheckParentalBondCounter(void)
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_JumpIfCantLoseItem(void)
{
NATIVE_ARGS(u8 battler, const u8 *jumpInstr);
u8 battler = GetBattlerForBattleScript(cmd->battler);
u16 item = gBattleMons[battler].item;
if (item == ITEM_NONE || !CanBattlerGetOrLoseItem(battler, item))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_GetBattlerSide(void)
{
NATIVE_ARGS(u8 battler);

View File

@ -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;
@ -5639,7 +5636,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++;

View File

@ -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),

View File

@ -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)
{

View File

@ -1451,6 +1451,7 @@ const struct CompressedSpriteSheet gBattleAnimPicTable[] =
{gBattleAnimSpriteGfx_Orbs, 0x0180, ANIM_TAG_STEEL_BEAM},
{gBattleAnimSpriteGfx_AuraSphere, 0x200, ANIM_TAG_POLTERGEIST},
{gBattleAnimSpriteGfx_Teapot, 0x1800, ANIM_TAG_TEAPOT},
{gBattleAnimSpriteGfx_WoodHammerHammer, 0x800, ANIM_TAG_WOOD_HAMMER_HAMMER},
};
const struct CompressedSpritePalette gBattleAnimPaletteTable[] =
@ -1902,6 +1903,7 @@ const struct CompressedSpritePalette gBattleAnimPaletteTable[] =
{gBattleAnimSpritePal_SteelBeam, ANIM_TAG_STEEL_BEAM},
{gBattleAnimSpritePal_Poltergeist, ANIM_TAG_POLTERGEIST},
{gBattleAnimSpritePal_Teapot, ANIM_TAG_TEAPOT},
{gBattleAnimSpritePal_WoodHammerHammer, ANIM_TAG_WOOD_HAMMER_HAMMER},
};
const struct BattleAnimBackground gBattleAnimBackgroundTable[] =

View File

@ -12303,7 +12303,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_Z] =
[MOVE_CORROSIVE_GAS] =
{
.effect = EFFECT_PLACEHOLDER, // EFFECT_CORROSIVE_GAS, TODO
.effect = EFFECT_CORROSIVE_GAS,
.power = 0,
.type = TYPE_POISON,
.accuracy = 100,

View File

@ -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;

View File

@ -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;

View File

@ -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)
{

View File

@ -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};
@ -621,8 +621,8 @@ bool32 CanCameraMoveInDirection(int direction)
static void SetPositionFromConnection(const struct MapConnection *connection, int direction, int x, int y)
{
struct MapHeader const *mapHeader;
mapHeader = GetMapHeaderFromConnection(connection);
struct MapHeader const *mapHeader = GetMapHeaderFromConnection(connection);
switch (direction)
{
case CONNECTION_EAST:
@ -641,6 +641,9 @@ static void SetPositionFromConnection(const struct MapConnection *connection, in
gSaveBlock1Ptr->pos.x -= connection->offset;
gSaveBlock1Ptr->pos.y = mapHeader->mapLayout->height;
break;
default:
DebugPrintfLevel(MGBA_LOG_WARN, "SetPositionFromConnection was passed an invalid direction (%d)!", direction);
break;
}
}
@ -663,14 +666,22 @@ bool8 CameraMove(int x, int y)
old_x = gSaveBlock1Ptr->pos.x;
old_y = gSaveBlock1Ptr->pos.y;
connection = GetIncomingConnection(direction, gSaveBlock1Ptr->pos.x, gSaveBlock1Ptr->pos.y);
SetPositionFromConnection(connection, direction, x, y);
LoadMapFromCameraTransition(connection->mapGroup, connection->mapNum);
gCamera.active = TRUE;
gCamera.x = old_x - gSaveBlock1Ptr->pos.x;
gCamera.y = old_y - gSaveBlock1Ptr->pos.y;
gSaveBlock1Ptr->pos.x += x;
gSaveBlock1Ptr->pos.y += y;
MoveMapViewToBackup(direction);
if (connection)
{
SetPositionFromConnection(connection, direction, x, y);
LoadMapFromCameraTransition(connection->mapGroup, connection->mapNum);
gCamera.active = TRUE;
gCamera.x = old_x - gSaveBlock1Ptr->pos.x;
gCamera.y = old_y - gSaveBlock1Ptr->pos.y;
gSaveBlock1Ptr->pos.x += x;
gSaveBlock1Ptr->pos.y += y;
MoveMapViewToBackup(direction);
}
else
{
DebugPrintfLevel(MGBA_LOG_WARN, "GetIncomingConnection returned an invalid connection inside CameraMove!");
}
}
return gCamera.active;
}

View File

@ -449,6 +449,9 @@ const u32 gBattleAnimSpriteGfx_SpinningBall[] = INCBIN_U32("graphics/battle_anim
const u32 gBattleAnimSpritePal_SpinningBall[] = INCBIN_U32("graphics/battle_anims/unused/spinning_ball.gbapal.lz");
const u32 gBattleAnimSpritePal_SpinningBall2[] = INCBIN_U32("graphics/battle_anims/unused/spinning_ball_2.gbapal.lz");
const u32 gBattleAnimSpriteGfx_WoodHammerHammer[] = INCBIN_U32("graphics/battle_anims/sprites/wood_hammer_hammer.4bpp.lz");
const u32 gBattleAnimSpritePal_WoodHammerHammer[] = INCBIN_U32("graphics/battle_anims/sprites/wood_hammer_hammer.gbapal.lz");
// old battle interface data, unused
const u32 gOldBattleInterfaceGfx[] = INCBIN_U32("graphics/unused/obi1.4bpp.lz");
@ -1710,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");

View File

@ -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++;

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -556,12 +556,9 @@ static void InitMapView(void)
InitTilesetAnimations();
}
const struct MapLayout *GetMapLayout(void)
const struct MapLayout *GetMapLayout(u16 mapLayoutId)
{
u16 mapLayoutId = gSaveBlock1Ptr->mapLayoutId;
if (mapLayoutId)
return gMapLayouts[mapLayoutId - 1];
return NULL;
return gMapLayouts[mapLayoutId - 1];
}
void ApplyCurrentWarp(void)
@ -618,13 +615,13 @@ static void LoadCurrentMapData(void)
sLastMapSectionId = gMapHeader.regionMapSectionId;
gMapHeader = *Overworld_GetMapHeaderByGroupAndId(gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum);
gSaveBlock1Ptr->mapLayoutId = gMapHeader.mapLayoutId;
gMapHeader.mapLayout = GetMapLayout();
gMapHeader.mapLayout = GetMapLayout(gMapHeader.mapLayoutId);
}
static void LoadSaveblockMapHeader(void)
{
gMapHeader = *Overworld_GetMapHeaderByGroupAndId(gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum);
gMapHeader.mapLayout = GetMapLayout();
gMapHeader.mapLayout = GetMapLayout(gMapHeader.mapLayoutId);
}
static void SetPlayerCoordsFromWarp(void)
@ -1020,7 +1017,7 @@ u8 GetFlashLevel(void)
void SetCurrentMapLayout(u16 mapLayoutId)
{
gSaveBlock1Ptr->mapLayoutId = mapLayoutId;
gMapHeader.mapLayout = GetMapLayout();
gMapHeader.mapLayout = GetMapLayout(mapLayoutId);
}
void SetObjectEventLoadFlag(u8 flag)

View File

@ -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,

View File

@ -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;

View File

@ -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;

View File

@ -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");

File diff suppressed because it is too large Load Diff

View File

@ -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;

View File

@ -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);

View File

@ -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];
};

View File

@ -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;

View File

@ -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;

View File

@ -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];

View File

@ -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);
}
}

93
test/ability_rattled.c Normal file
View File

@ -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!");
}
}

89
test/ability_stamina.c Normal file
View File

@ -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);
}
}

221
test/ability_wind_power.c Normal file
View File

@ -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!");
}
}
}

View File

@ -0,0 +1,119 @@
#include "global.h"
#include "test_battle.h"
ASSUMPTIONS
{
ASSUME(gBattleMoves[MOVE_CORROSIVE_GAS].effect == EFFECT_CORROSIVE_GAS);
}
SINGLE_BATTLE_TEST("Corrosive Gas destroys the target's item or fails if the target has no item")
{
u16 item;
PARAMETRIZE {item = ITEM_NONE; }
PARAMETRIZE {item = ITEM_POTION; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) {Item(item); }
} WHEN {
TURN { MOVE(player, MOVE_CORROSIVE_GAS); }
} SCENE {
MESSAGE("Wobbuffet used CorrosiveGas!");
if (item == ITEM_POTION) {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CORROSIVE_GAS, player);
MESSAGE("Wobbuffet corroded Foe Wobbuffet's Potion!");
}
else {
MESSAGE("It had no effect on Foe Wobbuffet!");
}
} THEN {
EXPECT_EQ(opponent->item, ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Corrosive Gas doesn't destroy the item of a Pokemon with the Sticky Hold ability")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_MUK) {Item(ITEM_POISON_BARB); Ability(ABILITY_STICKY_HOLD); }
} WHEN {
TURN { MOVE(player, MOVE_CORROSIVE_GAS); }
} SCENE {
MESSAGE("Wobbuffet used CorrosiveGas!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_CORROSIVE_GAS, player);
NOT MESSAGE("Wobbuffet corroded Foe Wobbuffet's Potion!");
ABILITY_POPUP(opponent, ABILITY_STICKY_HOLD);
MESSAGE("Foe Muk's Sticky Hold made CorrosiveGas ineffective!");
} THEN {
EXPECT_EQ(opponent->item, ITEM_POISON_BARB);
}
}
SINGLE_BATTLE_TEST("Items lost to Corrosive Gas cannot be restored by Recycle")
{
GIVEN {
ASSUME(gBattleMoves[MOVE_RECYCLE].effect == EFFECT_RECYCLE);
PLAYER(SPECIES_WOBBUFFET) {Speed(15); }
OPPONENT(SPECIES_WOBBUFFET) {Item(ITEM_ORAN_BERRY); Speed(10); }
} WHEN {
TURN { MOVE(player, MOVE_CORROSIVE_GAS); MOVE(opponent, MOVE_RECYCLE); }
} SCENE {
MESSAGE("Wobbuffet used CorrosiveGas!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_CORROSIVE_GAS, player);
MESSAGE("Wobbuffet corroded Foe Wobbuffet's Oran Berry!");
MESSAGE("Foe Wobbuffet used Recycle!");
MESSAGE("But it failed!");
} THEN {
EXPECT_EQ(opponent->item, ITEM_NONE);
}
}
DOUBLE_BATTLE_TEST("Corrosive Gas destroys foes and ally's items if they have one")
{
// Check it affects all targets in all possible configurations.
u32 j, k, l;
u16 itemOpponentLeft, itemOpponentRight, itemPlayerLeft;
for (j = 0; j < 2; j++) {
for (k = 0; k < 2; k++) {
for (l = 0; l < 2; l++) {
PARAMETRIZE {itemOpponentLeft = (j & 1) ? ITEM_ORAN_BERRY : ITEM_NONE;
itemOpponentRight = (k & 1) ? ITEM_CHESTO_BERRY : ITEM_NONE;
itemPlayerLeft = (l & 1) ? ITEM_CHERI_BERRY : ITEM_NONE; }
}
}
}
GIVEN {
PLAYER(SPECIES_WOBBUFFET) {Item(itemPlayerLeft);}
PLAYER(SPECIES_WYNAUT) {Item(ITEM_SITRUS_BERRY);}
OPPONENT(SPECIES_ABRA) {Item(itemOpponentLeft);}
OPPONENT(SPECIES_KADABRA) {Item(itemOpponentRight);}
} WHEN {
TURN { MOVE(playerRight, MOVE_CORROSIVE_GAS); }
} SCENE {
MESSAGE("Wynaut used CorrosiveGas!");
if (itemPlayerLeft == ITEM_CHERI_BERRY) {
MESSAGE("Wynaut corroded Wobbuffet's Cheri Berry!");
} else {
MESSAGE("It had no effect on Wobbuffet!");
}
if (itemOpponentLeft == ITEM_ORAN_BERRY) {
MESSAGE("Wynaut corroded Foe Abra's Oran Berry!");
} else {
MESSAGE("It had no effect on Foe Abra!");
}
if (itemOpponentRight == ITEM_CHESTO_BERRY) {
MESSAGE("Wynaut corroded Foe Kadabra's Chesto Berry!");
} else {
MESSAGE("It had no effect on Foe Kadabra!");
}
} THEN {
EXPECT_EQ(playerRight->item, ITEM_SITRUS_BERRY); // Attacker doesn't lose its item.
EXPECT_EQ(playerLeft->item, ITEM_NONE);
EXPECT_EQ(opponentLeft->item, ITEM_NONE);
EXPECT_EQ(opponentRight->item, ITEM_NONE);
}
}

127
test/primal_weather.c Normal file
View File

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

View File

@ -193,3 +193,60 @@ TEST("RandomElement generates a uniform distribution")
EXPECT_LT(error, UQ_4_12(0.025));
}
TEST("RandomUniform mul-based faster than mod-based (compile-time)")
{
u32 i;
struct Benchmark mulBenchmark, modBenchmark;
u32 mulSum = 0, modSum = 0;
BENCHMARK(&mulBenchmark)
{
mulSum += RandomUniformDefault(RNG_NONE, 0, 1);
mulSum += RandomUniformDefault(RNG_NONE, 0, 2);
mulSum += RandomUniformDefault(RNG_NONE, 0, 3);
mulSum += RandomUniformDefault(RNG_NONE, 0, 4);
}
BENCHMARK(&modBenchmark)
{
modSum += Random() % 2;
modSum += Random() % 3;
modSum += Random() % 4;
modSum += Random() % 5;
}
EXPECT_FASTER(mulBenchmark, modBenchmark);
// Reference mulSum/modSum to prevent optimization.
// These numbers are different because multiplication and modulus
// have subtly different biases (so subtle that it's irrelevant for
// our purposes).
EXPECT_EQ(mulSum, 3);
EXPECT_EQ(modSum, 4);
}
TEST("RandomUniform mul-based faster than mod-based (run-time)")
{
u32 i;
struct Benchmark mulBenchmark, modBenchmark;
u32 mulSum = 0, modSum = 0;
BENCHMARK(&mulBenchmark)
{
for (i = 0; i < 32; i++)
mulSum += RandomUniformDefault(RNG_NONE, 0, i);
}
BENCHMARK(&modBenchmark)
{
for (i = 0; i < 32; i++)
modSum += Random() % (i + 1);
}
EXPECT_FASTER(mulBenchmark, modBenchmark);
// Reference mulSum/modSum to prevent optimization.
EXPECT_EQ(mulSum, 232);
EXPECT_EQ(modSum, 249);
}

303
test/sprite.c Normal file
View File

@ -0,0 +1,303 @@
#include "global.h"
#include "test.h"
#include "main.h"
#include "malloc.h"
#include "random.h"
#include "sprite.h"
#define OAM_MATRIX_COUNT 32
EWRAM_DATA static u16 sSpritePriorities[MAX_SPRITES] = {0};
EWRAM_DATA static u8 sSpriteOrder[MAX_SPRITES] = {0};
static void Old_BuildOamBuffer(void);
static void ExpectEqOamBuffers(const struct OamData *oldOamBuffer, const struct OamData *newOamBuffer)
{
u32 i;
u32 matrices = 0;
// Compare the non-matrix data.
for (i = 0; i < gOamLimit; i++)
{
EXPECT(memcmp(&oldOamBuffer[i], &newOamBuffer[i], 6) == 0);
if (newOamBuffer[i].affineMode & ST_OAM_AFFINE_ON_MASK)
matrices |= 1 << newOamBuffer[i].matrixNum;
}
// Compare the matrix data.
for (i = 0; i < OAM_MATRIX_COUNT; i++)
{
if (matrices & (1 << i))
{
u32 base = 4 * i;
EXPECT_EQ(oldOamBuffer[base + 0].affineParam, newOamBuffer[base + 0].affineParam);
EXPECT_EQ(oldOamBuffer[base + 1].affineParam, newOamBuffer[base + 1].affineParam);
EXPECT_EQ(oldOamBuffer[base + 2].affineParam, newOamBuffer[base + 2].affineParam);
EXPECT_EQ(oldOamBuffer[base + 3].affineParam, newOamBuffer[base + 3].affineParam);
}
}
}
static void ResetSpriteData_(void)
{
u32 i;
ResetSpriteData();
for (i = 0; i < MAX_SPRITES; i++)
sSpriteOrder[i] = i;
}
static void BenchmarkBuildOamBuffer(bool32 preSort)
{
struct Benchmark oldBuildOamBuffer, newBuildOamBuffer;
struct OamData *oldOamBuffer = Alloc(sizeof(gMain.oamBuffer));
if (preSort)
Old_BuildOamBuffer();
BENCHMARK(&oldBuildOamBuffer)
{
Old_BuildOamBuffer();
}
memcpy(oldOamBuffer, gMain.oamBuffer, sizeof(gMain.oamBuffer));
if (preSort)
BuildOamBuffer();
BENCHMARK(&newBuildOamBuffer)
{
BuildOamBuffer();
}
ExpectEqOamBuffers(oldOamBuffer, gMain.oamBuffer);
EXPECT_FASTER(newBuildOamBuffer, oldBuildOamBuffer);
Free(oldOamBuffer);
}
TEST("BuildOamBuffer faster with no sprites")
{
ResetSpriteData_();
BenchmarkBuildOamBuffer(FALSE);
}
TEST("BuildOamBuffer faster with max sprites (equal y/subpriority)")
{
u32 i;
ResetSpriteData_();
for (i = 0; i < MAX_SPRITES; i++)
CreateSprite(&gDummySpriteTemplate, 0, 0, 0);
BenchmarkBuildOamBuffer(FALSE);
}
TEST("BuildOamBuffer faster with max sprites (random y/subpriority)")
{
u32 i;
ResetSpriteData_();
SeedRng(0);
for (i = 0; i < MAX_SPRITES; i++)
CreateSprite(&gDummySpriteTemplate, 0, Random() % 256, Random() % 256);
BenchmarkBuildOamBuffer(FALSE);
}
TEST("BuildOamBuffer faster on already-sorted max sprites")
{
u32 i;
ResetSpriteData_();
SeedRng(0);
for (i = 0; i < MAX_SPRITES; i++)
CreateSprite(&gDummySpriteTemplate, 0, Random() % 256, Random() % 256);
BenchmarkBuildOamBuffer(TRUE);
}
TEST("BuildOamBuffer faster with mix of sprites")
{
u32 i;
ResetSpriteData_();
SeedRng(0);
for (i = 0; i < MAX_SPRITES / 2; i++)
{
u32 spriteId = CreateSprite(&gDummySpriteTemplate, 0, Random() % 256, Random() % 256);
gSprites[spriteId].invisible = Random() % 4 == 0;
}
BenchmarkBuildOamBuffer(FALSE);
}
// Old implementation.
#define UBFIX
static void UpdateOamCoords(void)
{
u8 i;
for (i = 0; i < MAX_SPRITES; i++)
{
struct Sprite *sprite = &gSprites[i];
if (sprite->inUse && !sprite->invisible)
{
if (sprite->coordOffsetEnabled)
{
sprite->oam.x = sprite->x + sprite->x2 + sprite->centerToCornerVecX + gSpriteCoordOffsetX;
sprite->oam.y = sprite->y + sprite->y2 + sprite->centerToCornerVecY + gSpriteCoordOffsetY;
}
else
{
sprite->oam.x = sprite->x + sprite->x2 + sprite->centerToCornerVecX;
sprite->oam.y = sprite->y + sprite->y2 + sprite->centerToCornerVecY;
}
}
}
}
static void BuildSpritePriorities(void)
{
u16 i;
for (i = 0; i < MAX_SPRITES; i++)
{
struct Sprite *sprite = &gSprites[i];
u16 priority = sprite->subpriority | (sprite->oam.priority << 8);
sSpritePriorities[i] = priority;
}
}
static void SortSprites(void)
{
u8 i;
for (i = 1; i < MAX_SPRITES; i++)
{
u8 j = i;
struct Sprite *sprite1 = &gSprites[sSpriteOrder[i - 1]];
struct Sprite *sprite2 = &gSprites[sSpriteOrder[i]];
u16 sprite1Priority = sSpritePriorities[sSpriteOrder[i - 1]];
u16 sprite2Priority = sSpritePriorities[sSpriteOrder[i]];
s16 sprite1Y = sprite1->oam.y;
s16 sprite2Y = sprite2->oam.y;
if (sprite1Y >= DISPLAY_HEIGHT)
sprite1Y = sprite1Y - 256;
if (sprite2Y >= DISPLAY_HEIGHT)
sprite2Y = sprite2Y - 256;
if (sprite1->oam.affineMode == ST_OAM_AFFINE_DOUBLE
&& sprite1->oam.size == ST_OAM_SIZE_3)
{
u32 shape = sprite1->oam.shape;
if (shape == ST_OAM_SQUARE || shape == ST_OAM_V_RECTANGLE)
{
if (sprite1Y > 128)
sprite1Y = sprite1Y - 256;
}
}
if (sprite2->oam.affineMode == ST_OAM_AFFINE_DOUBLE
&& sprite2->oam.size == ST_OAM_SIZE_3)
{
u32 shape = sprite2->oam.shape;
if (shape == ST_OAM_SQUARE || shape == ST_OAM_V_RECTANGLE)
{
if (sprite2Y > 128)
sprite2Y = sprite2Y - 256;
}
}
while (j > 0
&& ((sprite1Priority > sprite2Priority)
|| (sprite1Priority == sprite2Priority && sprite1Y < sprite2Y)))
{
u8 temp = sSpriteOrder[j];
sSpriteOrder[j] = sSpriteOrder[j - 1];
sSpriteOrder[j - 1] = temp;
// UB: If j equals 1, then j-- makes j equal 0.
// Then, sSpriteOrder[-1] gets accessed below.
// Although this doesn't result in a bug in the ROM,
// the behavior is undefined.
j--;
#ifdef UBFIX
if (j == 0)
break;
#endif
sprite1 = &gSprites[sSpriteOrder[j - 1]];
sprite2 = &gSprites[sSpriteOrder[j]];
sprite1Priority = sSpritePriorities[sSpriteOrder[j - 1]];
sprite2Priority = sSpritePriorities[sSpriteOrder[j]];
sprite1Y = sprite1->oam.y;
sprite2Y = sprite2->oam.y;
if (sprite1Y >= DISPLAY_HEIGHT)
sprite1Y = sprite1Y - 256;
if (sprite2Y >= DISPLAY_HEIGHT)
sprite2Y = sprite2Y - 256;
if (sprite1->oam.affineMode == ST_OAM_AFFINE_DOUBLE
&& sprite1->oam.size == ST_OAM_SIZE_3)
{
u32 shape = sprite1->oam.shape;
if (shape == ST_OAM_SQUARE || shape == ST_OAM_V_RECTANGLE)
{
if (sprite1Y > 128)
sprite1Y = sprite1Y - 256;
}
}
if (sprite2->oam.affineMode == ST_OAM_AFFINE_DOUBLE
&& sprite2->oam.size == ST_OAM_SIZE_3)
{
u32 shape = sprite2->oam.shape;
if (shape == ST_OAM_SQUARE || shape == ST_OAM_V_RECTANGLE)
{
if (sprite2Y > 128)
sprite2Y = sprite2Y - 256;
}
}
}
}
}
static void CopyMatricesToOamBuffer(void)
{
u8 i;
for (i = 0; i < OAM_MATRIX_COUNT; i++)
{
u32 base = 4 * i;
gMain.oamBuffer[base + 0].affineParam = gOamMatrices[i].a;
gMain.oamBuffer[base + 1].affineParam = gOamMatrices[i].b;
gMain.oamBuffer[base + 2].affineParam = gOamMatrices[i].c;
gMain.oamBuffer[base + 3].affineParam = gOamMatrices[i].d;
}
}
static void AddSpritesToOamBuffer(void)
{
u8 i = 0;
u8 oamIndex = 0;
while (i < MAX_SPRITES)
{
struct Sprite *sprite = &gSprites[sSpriteOrder[i]];
if (sprite->inUse && !sprite->invisible && AddSpriteToOamBuffer(sprite, &oamIndex))
return;
i++;
}
while (oamIndex < gOamLimit)
{
gMain.oamBuffer[oamIndex] = gDummyOamData;
oamIndex++;
}
}
static void Old_BuildOamBuffer(void)
{
u8 temp;
UpdateOamCoords();
BuildSpritePriorities();
SortSprites();
temp = gMain.oamLoadDisabled;
gMain.oamLoadDisabled = TRUE;
AddSpritesToOamBuffer();
CopyMatricesToOamBuffer();
gMain.oamLoadDisabled = temp;
//sShouldProcessSpriteCopyRequests = TRUE;
}

View File

@ -46,6 +46,7 @@ struct TestRunnerState
u8 result;
u8 expectedResult;
bool8 expectLeaks:1;
bool8 inBenchmark:1;
u32 timeoutSeconds;
};
@ -158,6 +159,49 @@ s32 MgbaPrintf_(const char *fmt, ...);
Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT_GE(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \
} while (0)
struct Benchmark { s32 ticks; };
static inline void BenchmarkStart(void)
{
gTestRunnerState.inBenchmark = TRUE;
REG_TM3CNT = (TIMER_ENABLE | TIMER_64CLK) << 16;
}
static inline struct Benchmark BenchmarkStop(void)
{
REG_TM3CNT_H = 0;
gTestRunnerState.inBenchmark = FALSE;
return (struct Benchmark) { REG_TM3CNT_L };
}
#define BENCHMARK(id) \
for (BenchmarkStart(); gTestRunnerState.inBenchmark; *(id) = BenchmarkStop())
// An approximation of how much overhead benchmarks introduce.
#define BENCHMARK_ABS 2
// An approximation for what percentage faster a benchmark has to be for
// us to be confident that it's faster than another.
#define BENCHMARK_REL 95
#define EXPECT_FASTER(a, b) \
do \
{ \
u32 a_ = (a).ticks; u32 b_ = (b).ticks; \
MgbaPrintf_(#a ": %d ticks, " #b ": %d ticks", a_, b_); \
if (((a_ - BENCHMARK_ABS) * BENCHMARK_REL) >= (b_ * 100)) \
Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT_FASTER(" #a ", " #b ") failed", gTestRunnerState.test->filename, __LINE__); \
} while (0)
#define EXPECT_SLOWER(a, b) \
do \
{ \
u32 a_ = (a).ticks; u32 b_ = (b).ticks; \
MgbaPrintf_(#a ": %d ticks, " #b ": %d ticks", a_, b_); \
if ((a_ * 100) <= ((b_ - BENCHMARK_ABS) * BENCHMARK_REL)) \
Test_ExitWithResult(TEST_RESULT_FAIL, "%s:%d: EXPECT_SLOWER(" #a ", " #b ") failed", gTestRunnerState.test->filename, __LINE__); \
} while (0)
#define KNOWN_FAILING \
Test_ExpectedResult(TEST_RESULT_FAIL)