Skip to content

Commit

Permalink
Add in-battle shadows underneath all enemy battlers (#5178)
Browse files Browse the repository at this point in the history
* Add data to SpeciesInfo entries for in-battle shadows

* Implement sized shadows in the sprite visualizer

* Implement sized shadows in game code

* Show shadows for the lead battler for opponents during their battle anim

* Feedback on shadows, round 1

* Revert removal of Goomy and Sliggoo shadows

* Fixed GEN_3 setting

* Code cleanup + remove pre-processor branches

* Fix bugs with gen-3 configuration branch

* Style corrections, final shadow coordinate adjustments

* Adjustments to Garbodor and Araquanid
  • Loading branch information
lhearachel authored Sep 10, 2024
1 parent 2477f1d commit b478881
Show file tree
Hide file tree
Showing 26 changed files with 1,908 additions and 130 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion include/battle.h
Original file line number Diff line number Diff line change
Expand Up @@ -979,7 +979,10 @@ struct BattleHealthboxInfo
u8 animationState;
u8 partyStatusDelayTimer;
u8 matrixNum;
u8 shadowSpriteId;

u8 shadowSpriteIdPrimary;
u8 shadowSpriteIdSecondary;

u8 soundTimer;
u8 introEndDelay;
u8 field_A;
Expand Down
3 changes: 3 additions & 0 deletions include/battle_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ enum

#define TAG_HEALTHBAR_PAL TAG_HEALTHBAR_PLAYER1_TILE
#define TAG_HEALTHBOX_PAL TAG_HEALTHBOX_PLAYER1_TILE
#define TAG_SHADOW_PAL TAG_HEALTHBOX_PLAYER1_TILE

#define TAG_SHADOW_TILE 0xD759

#define TAG_GIMMICK_TRIGGER_TILE 0xD777
#define TAG_MEGA_INDICATOR_TILE 0xD778
Expand Down
3 changes: 3 additions & 0 deletions include/config/battle.h
Original file line number Diff line number Diff line change
Expand Up @@ -279,4 +279,7 @@
#define SHOW_TYPES_CAUGHT 2
#define B_SHOW_TYPES SHOW_TYPES_NEVER // When defined as SHOW_TYPES_ALWAYS, after selecting "Fight" in battle, the types of all Pokemon are revealed. Whe defined as SHOW_TYPES_OWN, types are only revealed if the player owns the mon in question.

// Pokémon battle sprite settings
#define B_ENEMY_MON_SHADOW_STYLE GEN_LATEST // In Gen4+, all enemy Pokemon will have a shadow drawn beneath them.

#endif // GUARD_CONFIG_BATTLE_H
10 changes: 6 additions & 4 deletions include/constants/event_objects.h
Original file line number Diff line number Diff line change
Expand Up @@ -281,10 +281,12 @@
#define OW_SPECIES(x) (((x)->graphicsId & OBJ_EVENT_GFX_SPECIES_MASK) - OBJ_EVENT_GFX_MON_BASE)
#define OW_FORM(x) ((x)->graphicsId >> OBJ_EVENT_GFX_SPECIES_BITS)

#define SHADOW_SIZE_S 0
#define SHADOW_SIZE_M 1
#define SHADOW_SIZE_L 2
#define SHADOW_SIZE_NONE 3 // Originally SHADOW_SIZE_XL, which went unused due to shadowSize in ObjectEventGraphicsInfo being only 2 bits.
#define SHADOW_SIZE_S 0
#define SHADOW_SIZE_M 1
#define SHADOW_SIZE_L 2
#define SHADOW_SIZE_NONE 3 // Originally SHADOW_SIZE_XL, which went unused due to shadowSize in ObjectEventGraphicsInfo being only 2 bits.

#define SHADOW_SIZE_XL_BATTLE_ONLY SHADOW_SIZE_NONE // Battle-only definition for XL shadow size.

#define F_INANIMATE (1 << 6)
#define F_DISABLE_REFLECTION_PALETTE_LOAD (1 << 7)
Expand Down
1 change: 1 addition & 0 deletions include/graphics.h
Original file line number Diff line number Diff line change
Expand Up @@ -3104,6 +3104,7 @@ extern const u32 gBattleAnimBgPalette_Surf[];
extern const u32 gBattleAnimBackgroundImageMuddyWater_Pal[];

extern const u32 gEnemyMonShadow_Gfx[];
extern const u32 gEnemyMonShadowsSized_Gfx[];

extern const u32 gBattleAnimFogTilemap[];

Expand Down
6 changes: 6 additions & 0 deletions include/pokemon.h
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,12 @@ struct SpeciesInfo /*0xC4*/
u32 tmIlliterate:1; // This species will be unable to learn the universal moves.
u32 isFrontierBanned:1; // This species is not allowed to participate in Battle Frontier facilities.
u32 padding4:11;
// Shadow settings
s8 enemyShadowXOffset; // This determines the X-offset for an enemy Pokémon's shadow during battle; negative values point left, positive values point right.
s8 enemyShadowYOffset; // This determines the Y-offset for an enemy Pokémon's shadow during battle; negative values point up, positive values point down.
u16 enemyShadowSize:3; // This determines the size of the shadow sprite used for an enemy Pokémon's front sprite during battle.
u16 suppressEnemyShadow:1; // If set to true, then a shadow will not be drawn beneath an enemy Pokémon's front sprite during battle.
u16 padding5:12;
// Move Data
/* 0x80 */ const struct LevelUpMove *levelUpLearnset;
/* 0x84 */ const u16 *teachableLearnset;
Expand Down
20 changes: 19 additions & 1 deletion include/pokemon_sprite_visualizer.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef GUARD_POKEMON_SPRITE_VISUALIZER_H
#define GUARD_POKEMON_SPRITE_VISUALIZER_H

#include "constants/global.h"
#include "constants/pokemon_sprite_visualizer.h"

//Structs
Expand Down Expand Up @@ -43,6 +44,17 @@ struct PokemonSpriteOffsets
s8 offset_front_elevation;
};

struct PokemonShadowSettings
{
s8 definedX;
s8 definedY;
u8 definedSize;

s8 overrideX;
s8 overrideY;
u8 overrideSize;
};

struct PokemonSpriteVisualizer
{
u16 currentmonId;
Expand All @@ -52,14 +64,20 @@ struct PokemonSpriteVisualizer
u8 backspriteId;
u8 iconspriteId;
u8 followerspriteId;
u8 frontShadowSpriteId;

bool8 isShiny;
bool8 isFemale;

u8 frontShadowSpriteIdPrimary;
u8 frontShadowSpriteIdSecondary;
struct PokemonShadowSettings shadowSettings;

struct PokemonSpriteVisualizerModifyArrows modifyArrows;
struct PokemonSpriteVisualizerOptionArrows optionArrows;
struct PokemonSpriteVisualizerYPosModifiyArrows yPosModifyArrows;
struct PokemonSpriteConstValues constSpriteValues;
struct PokemonSpriteOffsets offsetsSpriteValues;

u8 animIdBack;
u8 animIdFront;
u8 battleBgType;
Expand Down
27 changes: 22 additions & 5 deletions src/battle_anim_effects_1.c
Original file line number Diff line number Diff line change
Expand Up @@ -6616,12 +6616,29 @@ static void ReloadBattlerSprites(u32 battler, struct Pokemon *party)
UpdateIndicatorVisibilityAndType(gHealthboxSpriteIds[battler], TRUE);

// Try to recreate shadow sprite
if (gBattleSpritesDataPtr->healthBoxesData[battler].shadowSpriteId < MAX_SPRITES)
if (B_ENEMY_MON_SHADOW_STYLE >= GEN_4)
{
DestroySprite(&gSprites[gBattleSpritesDataPtr->healthBoxesData[battler].shadowSpriteId]);
gBattleSpritesDataPtr->healthBoxesData[battler].shadowSpriteId = MAX_SPRITES;
CreateEnemyShadowSprite(battler);
SetBattlerShadowSpriteCallback(battler, GetMonData(mon, MON_DATA_SPECIES));
// Both of these *should* be true, but use an OR just to be certain
if (gBattleSpritesDataPtr->healthBoxesData[battler].shadowSpriteIdPrimary < MAX_SPRITES
|| gBattleSpritesDataPtr->healthBoxesData[battler].shadowSpriteIdSecondary < MAX_SPRITES)
{
DestroySprite(&gSprites[gBattleSpritesDataPtr->healthBoxesData[battler].shadowSpriteIdPrimary]);
DestroySprite(&gSprites[gBattleSpritesDataPtr->healthBoxesData[battler].shadowSpriteIdSecondary]);
gBattleSpritesDataPtr->healthBoxesData[battler].shadowSpriteIdPrimary = MAX_SPRITES;
gBattleSpritesDataPtr->healthBoxesData[battler].shadowSpriteIdSecondary = MAX_SPRITES;
CreateEnemyShadowSprite(battler);
SetBattlerShadowSpriteCallback(battler, GetMonData(mon, MON_DATA_SPECIES));
}
}
else
{
if (gBattleSpritesDataPtr->healthBoxesData[battler].shadowSpriteIdPrimary < MAX_SPRITES)
{
DestroySprite(&gSprites[gBattleSpritesDataPtr->healthBoxesData[battler].shadowSpriteIdPrimary]);
gBattleSpritesDataPtr->healthBoxesData[battler].shadowSpriteIdPrimary = MAX_SPRITES;
CreateEnemyShadowSprite(battler);
SetBattlerShadowSpriteCallback(battler, GetMonData(mon, MON_DATA_SPECIES));
}
}
}

Expand Down
36 changes: 0 additions & 36 deletions src/battle_anim_smokescreen.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@

#define TAG_SMOKESCREEN 55019

#define PALTAG_SHADOW 55039
#define GFXTAG_SHADOW 55129

static void SpriteCB_SmokescreenImpactMain(struct Sprite *);
static void SpriteCB_SmokescreenImpact(struct Sprite *);

Expand Down Expand Up @@ -95,39 +92,6 @@ static const struct SpriteTemplate sSmokescreenImpactSpriteTemplate =
.callback = SpriteCB_SmokescreenImpact
};

const struct CompressedSpriteSheet gSpriteSheet_EnemyShadow =
{
.data = gEnemyMonShadow_Gfx, .size = 0x80, .tag = GFXTAG_SHADOW
};

static const struct OamData sOamData_EnemyShadow =
{
.y = 0,
.affineMode = ST_OAM_AFFINE_OFF,
.objMode = ST_OAM_OBJ_NORMAL,
.mosaic = FALSE,
.bpp = ST_OAM_4BPP,
.shape = SPRITE_SHAPE(32x8),
.x = 0,
.matrixNum = 0,
.size = SPRITE_SIZE(32x8),
.tileNum = 0,
.priority = 3,
.paletteNum = 0,
.affineParam = 0
};

const struct SpriteTemplate gSpriteTemplate_EnemyShadow =
{
.tileTag = GFXTAG_SHADOW,
.paletteTag = PALTAG_SHADOW,
.oam = &sOamData_EnemyShadow,
.anims = gDummySpriteAnimTable,
.images = NULL,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCB_SetInvisible
};

#define sActiveSprites data[0]
#define sPersist data[1]

Expand Down
37 changes: 26 additions & 11 deletions src/battle_controller_opponent.c
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,18 @@ static void Intro_WaitForShinyAnimAndHealthbox(u32 battler)
}
}

static void TrySetBattlerShadowSpriteCallback(u32 battler)
{
if (gSprites[gBattleSpritesDataPtr->healthBoxesData[battler].shadowSpriteIdPrimary].callback == SpriteCallbackDummy)
{
if (B_ENEMY_MON_SHADOW_STYLE <= GEN_3
|| gSprites[gBattleSpritesDataPtr->healthBoxesData[battler].shadowSpriteIdSecondary].callback == SpriteCallbackDummy)
{
SetBattlerShadowSpriteCallback(battler, GetMonData(&gEnemyParty[gBattlerPartyIndexes[battler]], MON_DATA_SPECIES));
}
}
}

static void Intro_TryShinyAnimShowHealthbox(u32 battler)
{
bool32 bgmRestored = FALSE;
Expand Down Expand Up @@ -269,33 +281,36 @@ static void Intro_TryShinyAnimShowHealthbox(u32 battler)

if (!twoMons || (twoMons && gBattleTypeFlags & BATTLE_TYPE_MULTI && !BATTLE_TWO_VS_ONE_OPPONENT))
{
if (gSprites[gBattleControllerData[battler]].callback == SpriteCallbackDummy
&& gSprites[gBattlerSpriteIds[battler]].callback == SpriteCallbackDummy)
if (gSprites[gBattleControllerData[battler]].callback == SpriteCallbackDummy)
{
battlerAnimsDone = TRUE;
TrySetBattlerShadowSpriteCallback(battler);
if (gSprites[gBattlerSpriteIds[battler]].callback == SpriteCallbackDummy)
{
battlerAnimsDone = TRUE;
}
}
}
else
{
if (gSprites[gBattleControllerData[battler]].callback == SpriteCallbackDummy
&& gSprites[gBattlerSpriteIds[battler]].callback == SpriteCallbackDummy
&& gSprites[gBattleControllerData[BATTLE_PARTNER(battler)]].callback == SpriteCallbackDummy
&& gSprites[gBattlerSpriteIds[BATTLE_PARTNER(battler)]].callback == SpriteCallbackDummy)
&& gSprites[gBattleControllerData[BATTLE_PARTNER(battler)]].callback == SpriteCallbackDummy)
{
battlerAnimsDone = TRUE;
TrySetBattlerShadowSpriteCallback(battler);
TrySetBattlerShadowSpriteCallback(BATTLE_PARTNER(battler));
if (gSprites[gBattlerSpriteIds[battler]].callback == SpriteCallbackDummy
&& gSprites[gBattlerSpriteIds[BATTLE_PARTNER(battler)]].callback == SpriteCallbackDummy)
{
battlerAnimsDone = TRUE;
}
}
}

if (bgmRestored && battlerAnimsDone)
{
if (twoMons && (!(gBattleTypeFlags & BATTLE_TYPE_MULTI) || BATTLE_TWO_VS_ONE_OPPONENT))
{
DestroySprite(&gSprites[gBattleControllerData[BATTLE_PARTNER(battler)]]);
SetBattlerShadowSpriteCallback(BATTLE_PARTNER(battler), GetMonData(&gEnemyParty[gBattlerPartyIndexes[BATTLE_PARTNER(battler)]], MON_DATA_SPECIES));
}

DestroySprite(&gSprites[gBattleControllerData[battler]]);
SetBattlerShadowSpriteCallback(battler, GetMonData(&gEnemyParty[gBattlerPartyIndexes[battler]], MON_DATA_SPECIES));
gBattleSpritesDataPtr->animationData->introAnimActive = FALSE;
gBattleSpritesDataPtr->healthBoxesData[battler].bgmRestored = FALSE;
gBattleSpritesDataPtr->healthBoxesData[battler].healthboxSlideInStarted = FALSE;
Expand Down
36 changes: 25 additions & 11 deletions src/battle_controller_recorded_opponent.c
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,19 @@ static void Intro_WaitForShinyAnimAndHealthbox(u32 battler)
}
}

static void TrySetBattlerShadowSpriteCallback(u32 battler)
{

if (gSprites[gBattleSpritesDataPtr->healthBoxesData[battler].shadowSpriteIdPrimary].callback == SpriteCallbackDummy)
{
if (B_ENEMY_MON_SHADOW_STYLE <= GEN_3
|| gSprites[gBattleSpritesDataPtr->healthBoxesData[battler].shadowSpriteIdSecondary].callback == SpriteCallbackDummy)
{
SetBattlerShadowSpriteCallback(battler, GetMonData(&gEnemyParty[gBattlerPartyIndexes[battler]], MON_DATA_SPECIES));
}
}
}

static void Intro_TryShinyAnimShowHealthbox(u32 battler)
{
bool32 bgmRestored = FALSE;
Expand Down Expand Up @@ -253,33 +266,34 @@ static void Intro_TryShinyAnimShowHealthbox(u32 battler)

if (!IsDoubleBattle())
{
if (gSprites[gBattleControllerData[battler]].callback == SpriteCallbackDummy
&& gSprites[gBattlerSpriteIds[battler]].callback == SpriteCallbackDummy)
if (gSprites[gBattleControllerData[battler]].callback == SpriteCallbackDummy)
{
battlerAnimsDone = TRUE;
TrySetBattlerShadowSpriteCallback(battler);
if (gSprites[gBattlerSpriteIds[battler]].callback == SpriteCallbackDummy)
battlerAnimsDone = TRUE;
}
}
else
{
if (gSprites[gBattleControllerData[battler]].callback == SpriteCallbackDummy
&& gSprites[gBattlerSpriteIds[battler]].callback == SpriteCallbackDummy
&& gSprites[gBattleControllerData[BATTLE_PARTNER(battler)]].callback == SpriteCallbackDummy
&& gSprites[gBattlerSpriteIds[BATTLE_PARTNER(battler)]].callback == SpriteCallbackDummy)
&& gSprites[gBattleControllerData[BATTLE_PARTNER(battler)]].callback == SpriteCallbackDummy)
{
battlerAnimsDone = TRUE;
TrySetBattlerShadowSpriteCallback(battler);
TrySetBattlerShadowSpriteCallback(BATTLE_PARTNER(battler));
if (gSprites[gBattlerSpriteIds[battler]].callback == SpriteCallbackDummy
&& gSprites[gBattlerSpriteIds[BATTLE_PARTNER(battler)]].callback == SpriteCallbackDummy)
{
battlerAnimsDone = TRUE;
}
}
}

if (bgmRestored && battlerAnimsDone)
{
if (IsDoubleBattle() && !(gBattleTypeFlags & BATTLE_TYPE_MULTI))
{
DestroySprite(&gSprites[gBattleControllerData[BATTLE_PARTNER(battler)]]);
SetBattlerShadowSpriteCallback(BATTLE_PARTNER(battler), GetMonData(&gEnemyParty[gBattlerPartyIndexes[BATTLE_PARTNER(battler)]], MON_DATA_SPECIES));
}

DestroySprite(&gSprites[gBattleControllerData[battler]]);
SetBattlerShadowSpriteCallback(battler, GetMonData(&gEnemyParty[gBattlerPartyIndexes[battler]], MON_DATA_SPECIES));

gBattleSpritesDataPtr->animationData->introAnimActive = FALSE;
gBattleSpritesDataPtr->healthBoxesData[battler].bgmRestored = FALSE;
Expand Down
Loading

0 comments on commit b478881

Please sign in to comment.