pokefirered/src/battle_interface.c
2026-02-14 12:54:29 +01:00

3172 lines
109 KiB
C

#include "global.h"
#include "gflib.h"
#include "battle_anim.h"
#include "battle_debug.h"
#include "battle_interface.h"
#include "battle_message.h"
#include "battle_z_move.h"
#include "decompress.h"
#include "graphics.h"
#include "item.h"
#include "item_icon.h"
#include "item_use.h"
#include "menu.h"
#include "pokedex.h"
#include "pokemon_summary_screen.h"
#include "safari_zone.h"
#include "test_runner.h"
#include "util.h"
#include "constants/songs.h"
#undef abs
#define abs(a) ((a) < 0 ? -(a) : (a))
// These are used as indexes for each "section of tiles" in gBattleInterface_Gfx
enum
{
B_INTERFACE_GFX_TRANSPARENT,
B_INTERFACE_GFX_HP_BAR_HP_TEXT,
B_INTERFACE_GFX_HP_BAR_HP_TEXT_2,
B_INTERFACE_GFX_HP_BAR_GREEN,
B_INTERFACE_GFX_HP_BAR_GREEN_2,
B_INTERFACE_GFX_HP_BAR_GREEN_3,
B_INTERFACE_GFX_HP_BAR_GREEN_4,
B_INTERFACE_GFX_HP_BAR_GREEN_5,
B_INTERFACE_GFX_HP_BAR_GREEN_6,
B_INTERFACE_GFX_HP_BAR_GREEN_7,
B_INTERFACE_GFX_HP_BAR_GREEN_8,
B_INTERFACE_GFX_HP_BAR_GREEN_9,
B_INTERFACE_GFX_EXP_BAR,
B_INTERFACE_GFX_EXP_BAR_2,
B_INTERFACE_GFX_EXP_BAR_3,
B_INTERFACE_GFX_EXP_BAR_4,
B_INTERFACE_GFX_EXP_BAR_5,
B_INTERFACE_GFX_EXP_BAR_6,
B_INTERFACE_GFX_EXP_BAR_7,
B_INTERFACE_GFX_EXP_BAR_8,
B_INTERFACE_GFX_EXP_BAR_9,
B_INTERFACE_GFX_STATUS_PSN_BATTLER0, // BATTLER0: B_POSITION_PLAYER_LEFT
B_INTERFACE_GFX_STATUS_PSN_BATTLER0_2,
B_INTERFACE_GFX_STATUS_PSN_BATTLER0_3,
B_INTERFACE_GFX_STATUS_PAR_BATTLER0,
B_INTERFACE_GFX_STATUS_PAR_BATTLER0_2,
B_INTERFACE_GFX_STATUS_PAR_BATTLER0_3,
B_INTERFACE_GFX_STATUS_SLP_BATTLER0,
B_INTERFACE_GFX_STATUS_SLP_BATTLER0_2,
B_INTERFACE_GFX_STATUS_SLP_BATTLER0_3,
B_INTERFACE_GFX_STATUS_FRZ_BATTLER0,
B_INTERFACE_GFX_STATUS_FRZ_BATTLER0_2,
B_INTERFACE_GFX_STATUS_FRZ_BATTLER0_3,
B_INTERFACE_GFX_STATUS_BRN_BATTLER0,
B_INTERFACE_GFX_STATUS_BRN_BATTLER0_2,
B_INTERFACE_GFX_STATUS_BRN_BATTLER0_3,
B_INTERFACE_GFX_STATUS_FRB_BATTLER0,
B_INTERFACE_GFX_STATUS_FRB_BATTLER0_2,
B_INTERFACE_GFX_STATUS_FRB_BATTLER0_3,
B_INTERFACE_GFX_UNUSED_0,
B_INTERFACE_GFX_UNUSED_1,
B_INTERFACE_GFX_UNUSED_2,
B_INTERFACE_GFX_STATUS_NONE,
B_INTERFACE_GFX_UNUSED_3,
B_INTERFACE_GFX_UNUSED_4,
B_INTERFACE_GFX_UNUSED_5,
B_INTERFACE_GFX_SAFARI_HEALTHBOX_0,
B_INTERFACE_GFX_SAFARI_HEALTHBOX_1,
B_INTERFACE_GFX_SAFARI_HEALTHBOX_2,
B_INTERFACE_GFX_UNUSED_6,
B_INTERFACE_GFX_HP_BAR_YELLOW,
B_INTERFACE_GFX_HP_BAR_YELLOW_2,
B_INTERFACE_GFX_HP_BAR_YELLOW_3,
B_INTERFACE_GFX_HP_BAR_YELLOW_4,
B_INTERFACE_GFX_HP_BAR_YELLOW_5,
B_INTERFACE_GFX_HP_BAR_YELLOW_6,
B_INTERFACE_GFX_HP_BAR_YELLOW_7,
B_INTERFACE_GFX_HP_BAR_YELLOW_8,
B_INTERFACE_GFX_HP_BAR_YELLOW_9,
B_INTERFACE_GFX_HP_BAR_RED,
B_INTERFACE_GFX_HP_BAR_RED_2,
B_INTERFACE_GFX_HP_BAR_RED_3,
B_INTERFACE_GFX_HP_BAR_RED_4,
B_INTERFACE_GFX_HP_BAR_RED_5,
B_INTERFACE_GFX_HP_BAR_RED_6,
B_INTERFACE_GFX_HP_BAR_RED_7,
B_INTERFACE_GFX_HP_BAR_RED_8,
B_INTERFACE_GFX_HP_BAR_RED_9,
B_INTERFACE_GFX_HP_BAR_LEFT_BORDER, // Used in place of the HP text graphic if the pokemon is statused
B_INTERFACE_GFX_BALL_PARTY_SUMMARY,
B_INTERFACE_GFX_BALL_PARTY_SUMMARY_2,
B_INTERFACE_GFX_BALL_PARTY_SUMMARY_3,
B_INTERFACE_GFX_BALL_PARTY_SUMMARY_4,
B_INTERFACE_GFX_BALL_CAUGHT,
B_INTERFACE_GFX_STATUS_PSN_BATTLER1, // BATTLER1: B_POSITION_OPPONENT_LEFT
B_INTERFACE_GFX_STATUS_PSN_BATTLER1_2,
B_INTERFACE_GFX_STATUS_PSN_BATTLER1_3,
B_INTERFACE_GFX_STATUS_PAR_BATTLER1,
B_INTERFACE_GFX_STATUS_PAR_BATTLER1_2,
B_INTERFACE_GFX_STATUS_PAR_BATTLER1_3,
B_INTERFACE_GFX_STATUS_SLP_BATTLER1,
B_INTERFACE_GFX_STATUS_SLP_BATTLER1_2,
B_INTERFACE_GFX_STATUS_SLP_BATTLER1_3,
B_INTERFACE_GFX_STATUS_FRZ_BATTLER1,
B_INTERFACE_GFX_STATUS_FRZ_BATTLER1_2,
B_INTERFACE_GFX_STATUS_FRZ_BATTLER1_3,
B_INTERFACE_GFX_STATUS_BRN_BATTLER1,
B_INTERFACE_GFX_STATUS_BRN_BATTLER1_2,
B_INTERFACE_GFX_STATUS_BRN_BATTLER1_3,
B_INTERFACE_GFX_STATUS_FRB_BATTLER1,
B_INTERFACE_GFX_STATUS_FRB_BATTLER1_2,
B_INTERFACE_GFX_STATUS_FRB_BATTLER1_3,
B_INTERFACE_GFX_STATUS_PSN_BATTLER2, // BATTLER2: B_POSITION_PLAYER_RIGHT
B_INTERFACE_GFX_STATUS_PSN_BATTLER2_2,
B_INTERFACE_GFX_STATUS_PSN_BATTLER2_3,
B_INTERFACE_GFX_STATUS_PAR_BATTLER2,
B_INTERFACE_GFX_STATUS_PAR_BATTLER2_2,
B_INTERFACE_GFX_STATUS_PAR_BATTLER2_3,
B_INTERFACE_GFX_STATUS_SLP_BATTLER2,
B_INTERFACE_GFX_STATUS_SLP_BATTLER2_2,
B_INTERFACE_GFX_STATUS_SLP_BATTLER2_3,
B_INTERFACE_GFX_STATUS_FRZ_BATTLER2,
B_INTERFACE_GFX_STATUS_FRZ_BATTLER2_2,
B_INTERFACE_GFX_STATUS_FRZ_BATTLER2_3,
B_INTERFACE_GFX_STATUS_BRN_BATTLER2,
B_INTERFACE_GFX_STATUS_BRN_BATTLER2_2,
B_INTERFACE_GFX_STATUS_BRN_BATTLER2_3,
B_INTERFACE_GFX_STATUS_FRB_BATTLER2,
B_INTERFACE_GFX_STATUS_FRB_BATTLER2_2,
B_INTERFACE_GFX_STATUS_FRB_BATTLER2_3,
B_INTERFACE_GFX_STATUS_PSN_BATTLER3, // BATTLER3: B_POSITION_OPPONENT_RIGHT
B_INTERFACE_GFX_STATUS_PSN_BATTLER3_2,
B_INTERFACE_GFX_STATUS_PSN_BATTLER3_3,
B_INTERFACE_GFX_STATUS_PAR_BATTLER3,
B_INTERFACE_GFX_STATUS_PAR_BATTLER3_2,
B_INTERFACE_GFX_STATUS_PAR_BATTLER3_3,
B_INTERFACE_GFX_STATUS_SLP_BATTLER3,
B_INTERFACE_GFX_STATUS_SLP_BATTLER3_2,
B_INTERFACE_GFX_STATUS_SLP_BATTLER3_3,
B_INTERFACE_GFX_STATUS_FRZ_BATTLER3,
B_INTERFACE_GFX_STATUS_FRZ_BATTLER3_2,
B_INTERFACE_GFX_STATUS_FRZ_BATTLER3_3,
B_INTERFACE_GFX_STATUS_BRN_BATTLER3,
B_INTERFACE_GFX_STATUS_BRN_BATTLER3_2,
B_INTERFACE_GFX_STATUS_BRN_BATTLER3_3,
B_INTERFACE_GFX_STATUS_FRB_BATTLER3,
B_INTERFACE_GFX_STATUS_FRB_BATTLER3_2,
B_INTERFACE_GFX_STATUS_FRB_BATTLER3_3,
B_INTERFACE_GFX_BOTTOM_RIGHT_CORNER_HP_AS_TEXT, // healthbox in double battles
B_INTERFACE_GFX_BOTTOM_RIGHT_CORNER_HP_AS_BAR, // healthbox in double battles
};
static void SpriteCB_HealthBoxOther(struct Sprite *sprite);
static void SpriteCB_HealthBar(struct Sprite *sprite);
static const u8 *GetBattleInterfaceGfxPtr(u8 which);
static void UpdateHpTextInHealthboxInDoubles(u8 healthboxSpriteId, u32 maxOrCurrent, s16 currHp, s16 maxHp);
static void Task_HidePartyStatusSummary_BattleStart_1(u8 taskId);
static void Task_HidePartyStatusSummary_BattleStart_2(u8 taskId);
static void SpriteCB_PartySummaryBar_Exit(struct Sprite *sprite);
static void SpriteCB_PartySummaryBall_Exit(struct Sprite *sprite);
static void Task_HidePartyStatusSummary_DuringBattle(u8 taskId);
static void SpriteCB_PartySummaryBall_OnSwitchout(struct Sprite *sprite);
static void UpdateStatusIconInHealthbox(u8 spriteId);
static void SpriteCB_PartySummaryBar(struct Sprite *sprite);
static void SpriteCB_PartySummaryBall_OnBattleStart(struct Sprite *sprite);
static u8 GetStatusIconForBattlerId(u8 statusElementId, u8 battlerId);
static void MoveBattleBarGraphically(u8 battlerId, u8 whichBar);
static u8 GetScaledExpFraction(s32 oldValue, s32 receivedValue, s32 maxValue, u8 scale);
static u8 CalcBarFilledPixels(s32 maxValue, s32 oldValue, s32 receivedValue, s32 *currValue, u8 *arg4, u8 scale);
static s32 CalcNewBarValue(s32 maxValue, s32 currValue, s32 receivedValue, s32 *arg3, u8 arg4, u16 arg5);
static void SafariTextIntoHealthboxObject(void *dest, u8 *windowTileData, u32 windowWidth);
static u8 *AddTextPrinterAndCreateWindowOnHealthbox(const u8 *str, u32 x, u32 y, u32 *windowId);
static void RemoveWindowOnHealthbox(u32 windowId);
static void TextIntoHealthboxObject(void *dest, u8 *windowTileData, s32 windowWidth);
static void SpriteCb_AbilityPopUp(struct Sprite *);
static void Task_FreeAbilityPopUpGfx(u8);
static void SpriteCB_LastUsedBall(struct Sprite *);
static void SpriteCB_LastUsedBallWin(struct Sprite *);
static void SpriteCB_MoveInfoWin(struct Sprite *sprite);
static const struct OamData sOamData_Healthbox = {
.shape = SPRITE_SHAPE(64x32),
.size = SPRITE_SIZE(64x32),
.priority = 1
};
static const struct SpriteTemplate sHealthboxPlayerSpriteTemplates[] = {
[B_POSITION_PLAYER_LEFT / 2] = {
.tileTag = TAG_HEALTHBOX_PLAYER1_TILE,
.paletteTag = TAG_HEALTHBOX_PAL,
.oam = &sOamData_Healthbox,
.anims = gDummySpriteAnimTable,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCallbackDummy
},
[B_POSITION_PLAYER_RIGHT / 2] = {
.tileTag = TAG_HEALTHBOX_PLAYER2_TILE,
.paletteTag = TAG_HEALTHBOX_PAL,
.oam = &sOamData_Healthbox,
.anims = gDummySpriteAnimTable,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCallbackDummy
}
};
static const struct SpriteTemplate sHealthboxOpponentSpriteTemplates[] = {
[B_POSITION_OPPONENT_LEFT / 2] = {
.tileTag = TAG_HEALTHBOX_OPPONENT1_TILE,
.paletteTag = TAG_HEALTHBOX_PAL,
.oam = &sOamData_Healthbox,
.anims = gDummySpriteAnimTable,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCallbackDummy
},
[B_POSITION_OPPONENT_RIGHT / 2] = {
.tileTag = TAG_HEALTHBOX_OPPONENT2_TILE,
.paletteTag = TAG_HEALTHBOX_PAL,
.oam = &sOamData_Healthbox,
.anims = gDummySpriteAnimTable,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCallbackDummy
}
};
static const struct SpriteTemplate sHealthboxSafariSpriteTemplate =
{
.tileTag = TAG_HEALTHBOX_SAFARI_TILE,
.paletteTag = TAG_HEALTHBOX_PAL,
.oam = &sOamData_Healthbox,
.anims = gDummySpriteAnimTable,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCallbackDummy
};
static const struct OamData sOamData_Healthbar = {
.shape = SPRITE_SHAPE(32x8),
.size = SPRITE_SIZE(32x8),
.priority = 1
};
static const struct SpriteTemplate sHealthbarSpriteTemplates[] = {
[B_POSITION_PLAYER_LEFT] = {
.tileTag = TAG_HEALTHBAR_PLAYER1_TILE,
.paletteTag = TAG_HEALTHBAR_PAL,
.oam = &sOamData_Healthbar,
.anims = gDummySpriteAnimTable,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCB_HealthBar
},
[B_POSITION_OPPONENT_LEFT] = {
.tileTag = TAG_HEALTHBAR_OPPONENT1_TILE,
.paletteTag = TAG_HEALTHBAR_PAL,
.oam = &sOamData_Healthbar,
.anims = gDummySpriteAnimTable,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCB_HealthBar
},
[B_POSITION_PLAYER_RIGHT] = {
.tileTag = TAG_HEALTHBAR_PLAYER2_TILE,
.paletteTag = TAG_HEALTHBAR_PAL,
.oam = &sOamData_Healthbar,
.anims = gDummySpriteAnimTable,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCB_HealthBar
},
[B_POSITION_OPPONENT_RIGHT] = {
.tileTag = TAG_HEALTHBAR_OPPONENT2_TILE,
.paletteTag = TAG_HEALTHBAR_PAL,
.oam = &sOamData_Healthbar,
.anims = gDummySpriteAnimTable,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCB_HealthBar
}
};
static const struct Subsprite sUnused_Subsprites_0[] = {
{ -16, 0, SPRITE_SHAPE(64x32), SPRITE_SIZE(64x32), 0, 1 },
{ 48, 0, SPRITE_SHAPE(32x32), SPRITE_SIZE(32x32), 32, 1 },
{ -16, 32, SPRITE_SHAPE(32x8), SPRITE_SIZE(32x8), 48, 1 },
{ 16, 32, SPRITE_SHAPE(32x8), SPRITE_SIZE(32x8), 52, 1 },
{ 48, 32, SPRITE_SHAPE(32x8), SPRITE_SIZE(32x8), 56, 1 }
};
static const struct Subsprite sUnused_Subsprites_2[] = {
{ -16, 0, SPRITE_SHAPE(64x32), SPRITE_SIZE(64x32), 64, 1 },
{ 48, 0, SPRITE_SHAPE(32x32), SPRITE_SIZE(32x32), 96, 1 },
{ -16, 32, SPRITE_SHAPE(32x8), SPRITE_SIZE(32x8), 112, 1 },
{ 16, 32, SPRITE_SHAPE(32x8), SPRITE_SIZE(32x8), 116, 1 },
{ 48, 32, SPRITE_SHAPE(32x8), SPRITE_SIZE(32x8), 120, 1 }
};
static const struct Subsprite sUnused_Subsprites_1[] = {
{ -16, 0, SPRITE_SHAPE(64x32), SPRITE_SIZE(64x32), 0, 1 },
{ 48, 0, SPRITE_SHAPE(32x32), SPRITE_SIZE(32x32), 32, 1 }
};
static const struct Subsprite sUnused_Subsprites_3[] = {
{ -16, 0, SPRITE_SHAPE(64x32), SPRITE_SIZE(64x32), 0, 1 },
{ 48, 0, SPRITE_SHAPE(32x32), SPRITE_SIZE(32x32), 32, 1 }
};
static const struct Subsprite sHealthBar_Subsprites_Player[] = {
{
.x = -16,
.y = 0,
.shape = SPRITE_SHAPE(32x8),
.size = SPRITE_SIZE(32x8),
.tileOffset = 0,
.priority = 1
},
{
.x = 16,
.y = 0,
.shape = SPRITE_SHAPE(32x8),
.size = SPRITE_SIZE(32x8),
.tileOffset = 4,
.priority = 1
}
};
static const struct Subsprite sHealthBar_Subsprites_Opponent[] = {
{
.x = -16,
.y = 0,
.shape = SPRITE_SHAPE(32x8),
.size = SPRITE_SIZE(32x8),
.tileOffset = 0,
.priority = 1
},
{
.x = 16,
.y = 0,
.shape = SPRITE_SHAPE(32x8),
.size = SPRITE_SIZE(32x8),
.tileOffset = 4,
.priority = 1
},
{
.x = -32,
.y = 0,
.shape = SPRITE_SHAPE(8x8),
.size = SPRITE_SIZE(8x8),
.tileOffset = 8,
.priority = 1
},
};
// Unused
static const struct SubspriteTable sUnused_SubspriteTable[] = {
{ARRAY_COUNT(sUnused_Subsprites_0), sUnused_Subsprites_0},
{ARRAY_COUNT(sUnused_Subsprites_1), sUnused_Subsprites_1},
{ARRAY_COUNT(sUnused_Subsprites_2), sUnused_Subsprites_2},
{ARRAY_COUNT(sUnused_Subsprites_3), sUnused_Subsprites_3}
};
static const struct SubspriteTable sHealthBar_SubspriteTable[] = {
[B_SIDE_PLAYER] = {ARRAY_COUNT(sHealthBar_Subsprites_Player), sHealthBar_Subsprites_Player},
[B_SIDE_OPPONENT] = {ARRAY_COUNT(sHealthBar_Subsprites_Opponent), sHealthBar_Subsprites_Opponent},
};
static const struct Subsprite sStatusSummaryBar_Subsprites_Enter[] = {
{
.x = -96,
.y = 0,
.shape = SPRITE_SHAPE(32x8),
.size = SPRITE_SIZE(32x8),
.tileOffset = 0,
.priority = 1
},
{
.x = -64,
.y = 0,
.shape = SPRITE_SHAPE(32x8),
.size = SPRITE_SIZE(32x8),
.tileOffset = 4,
.priority = 1
},
{
.x = -32,
.y = 0,
.shape = SPRITE_SHAPE(32x8),
.size = SPRITE_SIZE(32x8),
.tileOffset = 8,
.priority = 1
},
{
.x = 0,
.y = 0,
.shape = SPRITE_SHAPE(32x8),
.size = SPRITE_SIZE(32x8),
.tileOffset = 12,
.priority = 1
}
};
static const struct Subsprite sStatusSummaryBar_Subsprites_Exit[] = {
{
.x = -96,
.y = 0,
.shape = SPRITE_SHAPE(32x8),
.size = SPRITE_SIZE(32x8),
.tileOffset = 0,
.priority = 1
},
{
.x = -64,
.y = 0,
.shape = SPRITE_SHAPE(32x8),
.size = SPRITE_SIZE(32x8),
.tileOffset = 4,
.priority = 1
},
{
.x = -32,
.y = 0,
.shape = SPRITE_SHAPE(32x8),
.size = SPRITE_SIZE(32x8),
.tileOffset = 8,
.priority = 1
},
{
.x = 0,
.y = 0,
.shape = SPRITE_SHAPE(32x8),
.size = SPRITE_SIZE(32x8),
.tileOffset = 8,
.priority = 1
},
{
.x = 32,
.y = 0,
.shape = SPRITE_SHAPE(32x8),
.size = SPRITE_SIZE(32x8),
.tileOffset = 8,
.priority = 1
},
{
.x = 64,
.y = 0,
.shape = SPRITE_SHAPE(32x8),
.size = SPRITE_SIZE(32x8),
.tileOffset = 12,
.priority = 1
}
};
static const struct SubspriteTable sStatusSummaryBar_SubspriteTable_Enter[] = {
{ARRAY_COUNT(sStatusSummaryBar_Subsprites_Enter), sStatusSummaryBar_Subsprites_Enter}
};
static const struct SubspriteTable sStatusSummaryBar_SubspriteTable_Exit[] = {
{ARRAY_COUNT(sStatusSummaryBar_Subsprites_Exit), sStatusSummaryBar_Subsprites_Exit}
};
static const u16 sBattleInterface_Unused[] = INCBIN_U16("graphics/battle_interface/unused.4bpp");
static const struct CompressedSpriteSheet sStatusSummaryBarSpriteSheet =
{
.data = gBattleInterface_PartySummaryBar_Gfx,
.size = 16 * TILE_SIZE_4BPP,
.tag = TAG_PARTY_SUMMARY_BAR_TILE,
};
static const struct SpritePalette sPartySummaryBarSpritePals =
{
.data = gBattleInterface_Healthbox_Pal,
.tag = TAG_PARTY_SUMMARY_BAR_PAL,
};
static const struct SpritePalette sPartySummaryBallSpritePals =
{
.data = gBattleInterface_Healthbar_Pal,
.tag = TAG_PARTY_SUMMARY_BALL_PAL,
};
static const struct SpriteSheet sPartySummaryBallSpriteSheets =
{
.data = &gHealthboxElementsGfxTable[B_INTERFACE_GFX_BALL_PARTY_SUMMARY],
.size = 4 * TILE_SIZE_4BPP,
.tag = TAG_PARTY_SUMMARY_BALL_TILE,
};
// Unused
static const struct OamData sOamData_Healthbox2 = {
.shape = SPRITE_SHAPE(64x32),
.size = SPRITE_SIZE(64x32),
.priority = 1
};
static const struct OamData sOamData_PartySummaryBall = {
.shape = SPRITE_SHAPE(8x8),
.size = SPRITE_SIZE(8x8),
.priority = 1
};
static const struct SpriteTemplate sPartySummaryBarSpriteTemplates[] = {
{
.tileTag = TAG_PARTY_SUMMARY_BAR_TILE,
.paletteTag = TAG_PARTY_SUMMARY_BAR_PAL,
.oam = &sOamData_Healthbox,
.anims = gDummySpriteAnimTable,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCB_PartySummaryBar
}, {
.tileTag = TAG_PARTY_SUMMARY_BAR_TILE,
.paletteTag = TAG_PARTY_SUMMARY_BAR_PAL,
.oam = &sOamData_Healthbox,
.anims = gDummySpriteAnimTable,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCB_PartySummaryBar
}
};
static const struct SpriteTemplate sPartySummaryBallSpriteTemplates[] = {
{
.tileTag = TAG_PARTY_SUMMARY_BALL_TILE,
.paletteTag = TAG_PARTY_SUMMARY_BALL_PAL,
.oam = &sOamData_PartySummaryBall,
.anims = gDummySpriteAnimTable,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCB_PartySummaryBall_OnBattleStart
}, {
.tileTag = TAG_PARTY_SUMMARY_BALL_TILE,
.paletteTag = TAG_PARTY_SUMMARY_BALL_PAL,
.oam = &sOamData_PartySummaryBall,
.anims = gDummySpriteAnimTable,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCB_PartySummaryBall_OnBattleStart
}
};
// Because the healthbox is too large to fit into one sprite, it is divided
// into two sprites. The left sprite is used as the 'main' healthbox sprite,
// while the right sprite is the 'other' healthbox sprite.
// There is also a third sprite for the healthbar, visible on the healthbox.
// sprite data for main (left) healthbox sprite
#define sHealthboxOtherSpriteId oam.affineParam
#define sHealthBarSpriteId data[5]
#define sBattlerId data[6]
// sprite data for other (right) healthbox sprite
#define sHealthboxSpriteId data[5]
// sprite data for healthbar sprite
#define sHealthboxSpriteId data[5]
#define sHealthbarType data[6]
enum
{
HEALTHBAR_TYPE_PLAYER_SINGLE,
HEALTHBAR_TYPE_PLAYER_DOUBLE,
HEALTHBAR_TYPE_OPPONENT,
};
// This function is here to cover a specific case - one player's mon in a 2 vs 1 double battle. In this scenario - display singles layout.
// The same goes for a 2 vs 1 where opponent has only one pokemon.
enum BattleCoordTypes GetBattlerCoordsIndex(u32 battler)
{
if (GetBattlerPosition(battler) == B_POSITION_PLAYER_LEFT && gPlayerPartyCount == 1 && !(gBattleTypeFlags & BATTLE_TYPE_MULTI))
return BATTLE_COORDS_SINGLES;
else if (GetBattlerPosition(battler) == B_POSITION_OPPONENT_LEFT && gEnemyPartyCount == 1 && !(gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS))
return BATTLE_COORDS_SINGLES;
else if (IsDoubleBattle())
return BATTLE_COORDS_DOUBLES;
else
return BATTLE_COORDS_SINGLES;
}
u8 CreateBattlerHealthboxSprites(u8 battlerId)
{
s16 healthbarType = HEALTHBAR_TYPE_PLAYER_SINGLE;
u8 healthboxSpriteId;
u8 healthboxOtherSpriteId;
u8 healthbarSpriteId;
struct Sprite *healthbarSprite;
if (GetBattlerCoordsIndex(battlerId) == 0)
{
if (GetBattlerSide(battlerId) == B_SIDE_PLAYER)
{
healthboxSpriteId = CreateSprite(&sHealthboxPlayerSpriteTemplates[B_POSITION_PLAYER_LEFT / 2], 240, 160, 1);
healthboxOtherSpriteId = CreateSpriteAtEnd(&sHealthboxPlayerSpriteTemplates[B_POSITION_PLAYER_LEFT / 2], 240, 160, 1);
gSprites[healthboxSpriteId].oam.shape = SPRITE_SHAPE(64x64);
gSprites[healthboxOtherSpriteId].oam.shape = SPRITE_SHAPE(64x64);
gSprites[healthboxOtherSpriteId].oam.tileNum += 2 * TILE_SIZE_4BPP;
}
else
{
healthboxSpriteId = CreateSprite(&sHealthboxOpponentSpriteTemplates[B_POSITION_OPPONENT_LEFT / 2], 240, 160, 1);
healthboxOtherSpriteId = CreateSpriteAtEnd(&sHealthboxOpponentSpriteTemplates[B_POSITION_OPPONENT_LEFT / 2], 240, 160, 1);
gSprites[healthboxOtherSpriteId].oam.tileNum += 1 * TILE_SIZE_4BPP;
healthbarType = HEALTHBAR_TYPE_OPPONENT;
}
gSprites[healthboxSpriteId].sHealthboxOtherSpriteId = healthboxOtherSpriteId;
gSprites[healthboxOtherSpriteId].sHealthboxSpriteId = healthboxSpriteId;
gSprites[healthboxOtherSpriteId].callback = SpriteCB_HealthBoxOther;
}
else
{
if (GetBattlerSide(battlerId) == B_SIDE_PLAYER)
{
healthboxSpriteId = CreateSprite(&sHealthboxPlayerSpriteTemplates[GetBattlerPosition(battlerId) / 2], 240, 160, 1);
healthboxOtherSpriteId = CreateSpriteAtEnd(&sHealthboxPlayerSpriteTemplates[GetBattlerPosition(battlerId) / 2], 240, 160, 1);
gSprites[healthboxSpriteId].sHealthboxOtherSpriteId = healthboxOtherSpriteId;
gSprites[healthboxOtherSpriteId].sHealthboxSpriteId = healthboxSpriteId;
gSprites[healthboxOtherSpriteId].oam.tileNum += 1 * TILE_SIZE_4BPP;
gSprites[healthboxOtherSpriteId].callback = SpriteCB_HealthBoxOther;
healthbarType = HEALTHBAR_TYPE_PLAYER_DOUBLE;
}
else
{
healthboxSpriteId = CreateSprite(&sHealthboxOpponentSpriteTemplates[GetBattlerPosition(battlerId) / 2], 240, 160, 1);
healthboxOtherSpriteId = CreateSpriteAtEnd(&sHealthboxOpponentSpriteTemplates[GetBattlerPosition(battlerId) / 2], 240, 160, 1);
gSprites[healthboxSpriteId].sHealthboxOtherSpriteId = healthboxOtherSpriteId;
gSprites[healthboxOtherSpriteId].sHealthboxSpriteId = healthboxSpriteId;
gSprites[healthboxOtherSpriteId].oam.tileNum += 1 * TILE_SIZE_4BPP;
gSprites[healthboxOtherSpriteId].callback = SpriteCB_HealthBoxOther;
healthbarType = HEALTHBAR_TYPE_OPPONENT;
}
}
healthbarSpriteId = CreateSpriteAtEnd(&sHealthbarSpriteTemplates[gBattlerPositions[battlerId]], 140, 60, 0);
healthbarSprite = &gSprites[healthbarSpriteId];
SetSubspriteTables(healthbarSprite, &sHealthBar_SubspriteTable[GetBattlerSide(battlerId)]);
healthbarSprite->subspriteMode = SUBSPRITES_IGNORE_PRIORITY;
healthbarSprite->oam.priority = 1;
CpuCopy32(GetBattleInterfaceGfxPtr(B_INTERFACE_GFX_HP_BAR_HP_TEXT), OBJ_VRAM0 + healthbarSprite->oam.tileNum * TILE_SIZE_4BPP, 2 * TILE_SIZE_4BPP);
gSprites[healthboxSpriteId].sHealthBarSpriteId = healthbarSpriteId;
gSprites[healthboxSpriteId].sBattlerId = battlerId;
gSprites[healthboxSpriteId].invisible = TRUE;
gSprites[healthboxOtherSpriteId].invisible = TRUE;
healthbarSprite->sHealthboxSpriteId = healthboxSpriteId;
healthbarSprite->sHealthbarType = healthbarType;
healthbarSprite->invisible = TRUE;
CreateIndicatorSprite(battlerId);
gBattleStruct->ballSpriteIds[0] = MAX_SPRITES;
gBattleStruct->ballSpriteIds[1] = MAX_SPRITES;
gBattleStruct->moveInfoSpriteId = MAX_SPRITES;
return healthboxSpriteId;
}
u8 CreateSafariPlayerHealthboxSprites(void)
{
u8 healthboxSpriteId = CreateSprite(&sHealthboxSafariSpriteTemplate, 240, 160, 1);
u8 healthboxOtherSpriteId = CreateSpriteAtEnd(&sHealthboxSafariSpriteTemplate, 240, 160, 1);
gSprites[healthboxSpriteId].oam.shape = SPRITE_SHAPE(64x64);
gSprites[healthboxOtherSpriteId].oam.shape = SPRITE_SHAPE(64x64);
gSprites[healthboxOtherSpriteId].oam.tileNum += 2 * TILE_SIZE_4BPP;
gSprites[healthboxSpriteId].sHealthboxOtherSpriteId = healthboxOtherSpriteId;
gSprites[healthboxOtherSpriteId].sHealthboxSpriteId = healthboxSpriteId;
gSprites[healthboxOtherSpriteId].callback = SpriteCB_HealthBoxOther;
return healthboxSpriteId;
}
static const u8 *GetBattleInterfaceGfxPtr(u8 elementId)
{
return gHealthboxElementsGfxTable[elementId];
}
// Syncs the position of healthbar accordingly with the healthbox.
static void SpriteCB_HealthBar(struct Sprite *sprite)
{
u8 healthboxSpriteId = sprite->sHealthboxSpriteId;
switch (sprite->sHealthbarType)
{
case HEALTHBAR_TYPE_PLAYER_SINGLE:
sprite->x = gSprites[healthboxSpriteId].x + 16;
sprite->y = gSprites[healthboxSpriteId].y;
break;
case HEALTHBAR_TYPE_PLAYER_DOUBLE:
sprite->x = gSprites[healthboxSpriteId].x + 16;
sprite->y = gSprites[healthboxSpriteId].y;
break;
default:
case HEALTHBAR_TYPE_OPPONENT:
sprite->x = gSprites[healthboxSpriteId].x + 8;
sprite->y = gSprites[healthboxSpriteId].y;
break;
}
sprite->x2 = gSprites[healthboxSpriteId].x2;
sprite->y2 = gSprites[healthboxSpriteId].y2;
}
static void SpriteCB_HealthBoxOther(struct Sprite *sprite)
{
u8 healthboxSpriteId = sprite->sHealthboxSpriteId;
sprite->x = gSprites[healthboxSpriteId].x + 64;
sprite->y = gSprites[healthboxSpriteId].y;
sprite->x2 = gSprites[healthboxSpriteId].x2;
sprite->y2 = gSprites[healthboxSpriteId].y2;
}
void SetBattleBarStruct(u8 battlerId, u8 healthboxSpriteId, s32 maxVal, s32 oldVal, s32 receivedValue)
{
gBattleSpritesDataPtr->battleBars[battlerId].healthboxSpriteId = healthboxSpriteId;
gBattleSpritesDataPtr->battleBars[battlerId].maxValue = maxVal;
gBattleSpritesDataPtr->battleBars[battlerId].oldValue = oldVal;
gBattleSpritesDataPtr->battleBars[battlerId].receivedValue = receivedValue;
gBattleSpritesDataPtr->battleBars[battlerId].currValue = -32768;
}
void SetHealthboxSpriteInvisible(u8 healthboxSpriteId)
{
gSprites[healthboxSpriteId].invisible = TRUE;
gSprites[gSprites[healthboxSpriteId].sHealthBarSpriteId].invisible = TRUE;
gSprites[gSprites[healthboxSpriteId].sHealthboxOtherSpriteId].invisible = TRUE;
UpdateIndicatorVisibilityAndType(healthboxSpriteId, TRUE);
}
void SetHealthboxSpriteVisible(u8 healthboxSpriteId)
{
gSprites[healthboxSpriteId].invisible = FALSE;
gSprites[gSprites[healthboxSpriteId].sHealthBarSpriteId].invisible = FALSE;
gSprites[gSprites[healthboxSpriteId].sHealthboxOtherSpriteId].invisible = FALSE;
UpdateIndicatorVisibilityAndType(healthboxSpriteId, FALSE);
}
static void UpdateSpritePos(u8 spriteId, s16 x, s16 y)
{
gSprites[spriteId].x = x;
gSprites[spriteId].y = y;
}
void DestoryHealthboxSprite(u8 healthboxSpriteId)
{
DestroySprite(&gSprites[gSprites[healthboxSpriteId].sHealthboxOtherSpriteId]);
DestroySprite(&gSprites[gSprites[healthboxSpriteId].sHealthBarSpriteId]);
DestroySprite(&gSprites[healthboxSpriteId]);
}
void DummyBattleInterfaceFunc(u8 healthboxSpriteId, bool8 isDoubleBattleBattlerOnly)
{
}
static void TryToggleHealboxVisibility(u32 priority, u32 healthboxLeftSpriteId, u32 healthboxRightSpriteId, u32 healthbarSpriteId)
{
bool32 invisible = FALSE;
if (priority == 0) // start of anim -> make invisible
invisible = TRUE;
else if (priority == 1) // end of anim -> make visible
invisible = FALSE;
gSprites[healthboxLeftSpriteId].invisible = invisible;
gSprites[healthboxRightSpriteId].invisible = invisible;
gSprites[healthbarSpriteId].invisible = invisible;
UpdateIndicatorVisibilityAndType(healthboxLeftSpriteId, invisible);
}
void UpdateOamPriorityInAllHealthboxes(u8 priority, bool32 hideHPBoxes)
{
s32 i;
for (i = 0; i < gBattlersCount; i++)
{
u8 healthboxLeftSpriteId = gHealthboxSpriteIds[i];
u8 healthboxRightSpriteId = gSprites[gHealthboxSpriteIds[i]].oam.affineParam;
u8 healthbarSpriteId = gSprites[gHealthboxSpriteIds[i]].sHealthBarSpriteId;
gSprites[healthboxLeftSpriteId].oam.priority = priority;
gSprites[healthboxRightSpriteId].oam.priority = priority;
gSprites[healthbarSpriteId].oam.priority = priority;
UpdateIndicatorOamPriority(healthboxLeftSpriteId, priority);
if (B_HIDE_HEALTHBOX_IN_ANIMS == TRUE && hideHPBoxes && IsBattlerAlive(i))
TryToggleHealboxVisibility(priority, healthboxLeftSpriteId, healthboxRightSpriteId, healthbarSpriteId);
}
}
static const s16 sBattlerHealthboxCoords[BATTLE_COORDS_COUNT][MAX_BATTLERS_COUNT][2] =
{
[BATTLE_COORDS_SINGLES] =
{
[B_POSITION_PLAYER_LEFT] = { 158, 88 },
[B_POSITION_OPPONENT_LEFT] = { 44, 30 },
},
[BATTLE_COORDS_DOUBLES] =
{
[B_POSITION_PLAYER_LEFT] = { 159, 75 },
[B_POSITION_PLAYER_RIGHT] = { 171, 100 },
[B_POSITION_OPPONENT_LEFT] = { 44, 19 },
[B_POSITION_OPPONENT_RIGHT] = { 32, 44 },
},
};
void GetBattlerHealthboxCoords(u8 battler, s16 *x, s16 *y)
{
enum BattlerPosition position = GetBattlerPosition(battler);
enum BattleCoordTypes index = GetBattlerCoordsIndex(battler);
*x = sBattlerHealthboxCoords[index][position][0];
*y = sBattlerHealthboxCoords[index][position][1];
}
void InitBattlerHealthboxCoords(u8 battler)
{
s16 x, y;
GetBattlerHealthboxCoords(battler, &x, &y);
UpdateSpritePos(gHealthboxSpriteIds[battler], x, y);
}
static void UpdateLvlInHealthbox(u8 healthboxSpriteId, u8 lvl)
{
u32 windowId, spriteTileNum;
u8 *windowTileData;
u8 text[16];
u32 xPos;
u8 *objVram;
u8 battler = gSprites[healthboxSpriteId].sBattlerId;
// Don't print Lv char if mon has a gimmick with an indicator active.
if (GetIndicatorPalTag(battler) != TAG_NONE)
{
objVram = ConvertIntToDecimalStringN(text, lvl, STR_CONV_MODE_LEFT_ALIGN, 3);
xPos = 5 * (3 - (objVram - (text + 2))) - 1;
UpdateIndicatorLevelData(healthboxSpriteId, lvl);
UpdateIndicatorVisibilityAndType(healthboxSpriteId, FALSE);
}
else
{
text[0] = CHAR_EXTRA_SYMBOL;
text[1] = CHAR_LV_2;
objVram = ConvertIntToDecimalStringN(text + 2, lvl, STR_CONV_MODE_LEFT_ALIGN, 3);
xPos = 5 * (3 - (objVram - (text + 2)));
UpdateIndicatorVisibilityAndType(healthboxSpriteId, TRUE);
}
windowTileData = AddTextPrinterAndCreateWindowOnHealthbox(text, xPos, 3, &windowId);
spriteTileNum = gSprites[healthboxSpriteId].oam.tileNum * TILE_SIZE_4BPP;
if (IsOnPlayerSide(battler))
{
objVram = (void *)(OBJ_VRAM0);
switch (GetBattlerCoordsIndex(battler))
{
case BATTLE_COORDS_SINGLES:
objVram += spriteTileNum + 0x820;
break;
default:
objVram += spriteTileNum + 0x420;
break;
}
}
else
{
objVram = (void *)(OBJ_VRAM0);
objVram += spriteTileNum + 0x400;
}
TextIntoHealthboxObject(objVram, windowTileData, 3);
RemoveWindowOnHealthbox(windowId);
}
void UpdateHpTextInHealthbox(u32 healthboxSpriteId, u32 maxOrCurrent, s16 currHp, s16 maxHp)
{
u32 windowId, spriteTileNum;
u8 *windowTileData;
if (GetBattlerSide(gSprites[healthboxSpriteId].sBattlerId) == B_SIDE_PLAYER && !GetBattlerCoordsIndex(gSprites[healthboxSpriteId].sBattlerId))
{
// Only in the Japanese release can HP be displayed as text outside of double battles
u8 text[8];
if (maxOrCurrent != HP_CURRENT) // singles, max
{
ConvertIntToDecimalStringN(text, maxHp, STR_CONV_MODE_RIGHT_ALIGN, 3);
windowTileData = AddTextPrinterAndCreateWindowOnHealthbox(text, 0, 5, &windowId);
spriteTileNum = gSprites[healthboxSpriteId].oam.tileNum;
TextIntoHealthboxObject((void *)(OBJ_VRAM0) + spriteTileNum * TILE_SIZE_4BPP + 0xA40, windowTileData, 2);
RemoveWindowOnHealthbox(windowId);
}
else // singles, current
{
u8 *strptr;
strptr = ConvertIntToDecimalStringN(text, currHp, STR_CONV_MODE_RIGHT_ALIGN, 3);
*strptr++ = CHAR_SLASH;
*strptr++ = EOS;
windowTileData = AddTextPrinterAndCreateWindowOnHealthbox(text, 4, 5, &windowId);
spriteTileNum = gSprites[healthboxSpriteId].oam.tileNum;
TextIntoHealthboxObject((void *)(OBJ_VRAM0) + spriteTileNum * TILE_SIZE_4BPP + 0x2E0, windowTileData, 1);
TextIntoHealthboxObject((void *)(OBJ_VRAM0) + spriteTileNum * TILE_SIZE_4BPP + 0xA00, windowTileData + 0x20, 2);
RemoveWindowOnHealthbox(windowId);
}
}
else
{
u8 battler;
u8 text[20] = __("{COLOR 01}{HIGHLIGHT 02}");
battler = gSprites[healthboxSpriteId].sBattlerId;
if (GetBattlerCoordsIndex(battler) == TRUE || GetBattlerSide(battler) == B_SIDE_OPPONENT)
{
if (maxOrCurrent != HP_CURRENT)
UpdateHpTextInHealthboxInDoubles(healthboxSpriteId, maxOrCurrent, currHp, maxHp);
else
UpdateHpTextInHealthboxInDoubles(healthboxSpriteId, maxOrCurrent, currHp, maxHp);
}
else
{
// Only in the Japanese release can HP be displayed as text outside of double battles
u32 var;
u8 i;
if (GetBattlerSide(gSprites[healthboxSpriteId].sBattlerId) == B_SIDE_PLAYER)
{
if (maxOrCurrent == HP_CURRENT)
var = 29;
else
var = 89;
}
else
{
if (maxOrCurrent == HP_CURRENT)
var = 20;
else
var = 48;
}
if (maxOrCurrent != HP_CURRENT)
ConvertIntToDecimalStringN(text + 6, maxHp, STR_CONV_MODE_RIGHT_ALIGN, 3);
else
ConvertIntToDecimalStringN(text + 6, currHp, STR_CONV_MODE_RIGHT_ALIGN, 3);
RenderTextHandleBold(gMonSpritesGfxPtr->barFontGfx, 0, text);
for (i = 0; i < 3; i++)
{
CpuCopy32(&gMonSpritesGfxPtr->barFontGfx[i * 64 + 32],
(void *)((OBJ_VRAM0) + TILE_SIZE_4BPP * (gSprites[healthboxSpriteId].oam.tileNum + var + i)),
1 * TILE_SIZE_4BPP);
}
}
}
}
static const u8 sText_Slash[] = _("/");
static void UpdateHpTextInHealthboxInDoubles(u8 healthboxSpriteId, u32 maxOrCurrent, s16 currHp, s16 maxHp)
{
u8 battlerId;
u8 text[20] = __("{COLOR 01}{HIGHLIGHT 00}");
battlerId = gSprites[healthboxSpriteId].sBattlerId;
if (gBattleSpritesDataPtr->battlerData[battlerId].hpNumbersNoBars)
{
u8 var = 4;
u8 healthBarSpriteId;
u8 *txtPtr;
u8 i;
if (maxOrCurrent == HP_CURRENT)
var = 0;
healthBarSpriteId = gSprites[healthboxSpriteId].sHealthBarSpriteId;
if (maxOrCurrent == HP_CURRENT)
txtPtr = ConvertIntToDecimalStringN(text + 6, currHp, STR_CONV_MODE_RIGHT_ALIGN, 3);
else
txtPtr = ConvertIntToDecimalStringN(text + 6, maxHp, STR_CONV_MODE_RIGHT_ALIGN, 3);
if (maxOrCurrent == HP_CURRENT)
StringCopy(txtPtr, sText_Slash);
RenderTextHandleBold(gMonSpritesGfxPtr->barFontGfx, 0, text);
for (i = var; i < var + 3; i++)
{
if (i < 3)
{
CpuCopy32(&gMonSpritesGfxPtr->barFontGfx[((i - var) * 64) + 32],
(void *)((OBJ_VRAM0) + (1 + gSprites[healthBarSpriteId].oam.tileNum + i) * TILE_SIZE_4BPP),
1 * TILE_SIZE_4BPP);
}
else
{
CpuCopy32(&gMonSpritesGfxPtr->barFontGfx[((i - var) * 64) + 32],
(void *)((OBJ_VRAM0 + 0x20) + (i + gSprites[healthBarSpriteId].oam.tileNum) * TILE_SIZE_4BPP),
1 * TILE_SIZE_4BPP);
}
}
if (maxOrCurrent == HP_CURRENT)
{
CpuCopy32(&gMonSpritesGfxPtr->barFontGfx[224],
(void *)((OBJ_VRAM0) + ((gSprites[healthBarSpriteId].oam.tileNum + 4) * TILE_SIZE_4BPP)),
1 * TILE_SIZE_4BPP);
CpuFill32(0,
(void *)((OBJ_VRAM0) + (gSprites[healthBarSpriteId].oam.tileNum * TILE_SIZE_4BPP)),
1 * TILE_SIZE_4BPP);
}
else
{
if (GetBattlerSide(battlerId) == B_SIDE_PLAYER)
{
CpuCopy32(GetBattleInterfaceGfxPtr(B_INTERFACE_GFX_BOTTOM_RIGHT_CORNER_HP_AS_TEXT),
(void *)(OBJ_VRAM0) + ((gSprites[healthboxSpriteId].oam.tileNum + 52) * TILE_SIZE_4BPP),
1 * TILE_SIZE_4BPP);
}
}
}
}
// Prints mon's nature, catch and flee rate. Probably used to test pokeblock-related features.
static void PrintSafariMonInfo(u8 healthboxSpriteId, struct Pokemon *mon)
{
u8 text[20] = __("{COLOR 01}{HIGHLIGHT 02}");
s32 j, spriteTileNum;
u8 *barFontGfx;
u8 i, var, nature, healthBarSpriteId;
barFontGfx = &gMonSpritesGfxPtr->barFontGfx[0x520 + (GetBattlerPosition(gSprites[healthboxSpriteId].sBattlerId) * 384)];
var = 5;
nature = GetNature(mon);
StringCopy(text + 6, gNaturesInfo[nature].name);
RenderTextHandleBold(barFontGfx, 0, text);
for (j = 6, i = 0; i < var; i++, j++)
{
u8 elementId;
if ((text[j] >= 55 && text[j] <= 74) || (text[j] >= 135 && text[j] <= 154))
elementId = B_INTERFACE_GFX_SAFARI_HEALTHBOX_1;
else if ((text[j] >= 75 && text[j] <= 79) || (text[j] >= 155 && text[j] <= 159))
elementId = B_INTERFACE_GFX_SAFARI_HEALTHBOX_2;
else
elementId = B_INTERFACE_GFX_SAFARI_HEALTHBOX_0;
CpuCopy32(GetBattleInterfaceGfxPtr(elementId), barFontGfx + (i * 64), 0x20);
}
for (j = 1; j < var + 1; j++)
{
spriteTileNum = (gSprites[healthboxSpriteId].oam.tileNum + (j - (j / 8 * 8)) + (j / 8 * 64)) * TILE_SIZE_4BPP;
CpuCopy32(barFontGfx, (void *)(OBJ_VRAM0) + (spriteTileNum), 0x20);
barFontGfx += 0x20;
spriteTileNum = (8 + gSprites[healthboxSpriteId].oam.tileNum + (j - (j / 8 * 8)) + (j / 8 * 64)) * TILE_SIZE_4BPP;
CpuCopy32(barFontGfx, (void *)(OBJ_VRAM0) + (spriteTileNum), 0x20);
barFontGfx += 0x20;
}
healthBarSpriteId = gSprites[healthboxSpriteId].sHealthBarSpriteId;
ConvertIntToDecimalStringN(text + 6, gBattleStruct->safariCatchFactor, STR_CONV_MODE_RIGHT_ALIGN, 2);
ConvertIntToDecimalStringN(text + 9, gBattleStruct->safariEscapeFactor, STR_CONV_MODE_RIGHT_ALIGN, 2);
text[5] = CHAR_SPACE;
text[8] = CHAR_SLASH;
RenderTextHandleBold(gMonSpritesGfxPtr->barFontGfx, 0, text);
j = healthBarSpriteId; // Needed to match for some reason.
for (j = 0; j < 5; j++)
{
if (j <= 1)
{
CpuCopy32(&gMonSpritesGfxPtr->barFontGfx[0x40 * j + 0x20],
(void *)(OBJ_VRAM0) + (gSprites[healthBarSpriteId].oam.tileNum + 2 + j) * TILE_SIZE_4BPP,
32);
}
else
{
CpuCopy32(&gMonSpritesGfxPtr->barFontGfx[0x40 * j + 0x20],
(void *)(OBJ_VRAM0 + 0xC0) + (j + gSprites[healthBarSpriteId].oam.tileNum) * TILE_SIZE_4BPP,
32);
}
}
}
void SwapHpBarsWithHpText(void)
{
s32 i;
u8 healthBarSpriteId;
for (i = 0; i < gBattlersCount; i++)
{
if (gSprites[gHealthboxSpriteIds[i]].callback == SpriteCallbackDummy
&& GetBattlerSide(i) != B_SIDE_OPPONENT
&& (GetBattlerCoordsIndex(i) || GetBattlerSide(i) != B_SIDE_PLAYER))
{
bool8 noBars;
gBattleSpritesDataPtr->battlerData[i].hpNumbersNoBars ^= 1;
noBars = gBattleSpritesDataPtr->battlerData[i].hpNumbersNoBars;
if (GetBattlerSide(i) == B_SIDE_PLAYER)
{
if (!GetBattlerCoordsIndex(i))
continue;
if (gBattleTypeFlags & BATTLE_TYPE_SAFARI)
continue;
if (noBars == TRUE) // bars to text
{
s16 currHp = GetMonData(&gPlayerParty[gBattlerPartyIndexes[i]], MON_DATA_HP);
s16 maxHp = GetMonData(&gPlayerParty[gBattlerPartyIndexes[i]], MON_DATA_MAX_HP);
healthBarSpriteId = gSprites[gHealthboxSpriteIds[i]].sHealthBarSpriteId;
CpuFill32(0, (void *)(OBJ_VRAM0 + gSprites[healthBarSpriteId].oam.tileNum * TILE_SIZE_4BPP), 8 * TILE_SIZE_4BPP);
UpdateHpTextInHealthboxInDoubles(gHealthboxSpriteIds[i], HP_CURRENT, currHp, maxHp);
UpdateHpTextInHealthboxInDoubles(gHealthboxSpriteIds[i], HP_MAX, currHp, maxHp);
}
else // text to bars
{
UpdateStatusIconInHealthbox(gHealthboxSpriteIds[i]);
UpdateHealthboxAttribute(gHealthboxSpriteIds[i], &gPlayerParty[gBattlerPartyIndexes[i]], HEALTHBOX_HEALTH_BAR);
CpuCopy32(GetBattleInterfaceGfxPtr(B_INTERFACE_GFX_BOTTOM_RIGHT_CORNER_HP_AS_BAR),
(void *)(OBJ_VRAM0 + 0x680 + gSprites[gHealthboxSpriteIds[i]].oam.tileNum * TILE_SIZE_4BPP),
1 * TILE_SIZE_4BPP);
}
}
else
{
if (noBars == TRUE) // bars to text
{
if (gBattleTypeFlags & BATTLE_TYPE_SAFARI)
{
// Most likely a debug function.
PrintSafariMonInfo(gHealthboxSpriteIds[i], &gEnemyParty[gBattlerPartyIndexes[i]]);
}
else
{
s16 currHp = GetMonData(&gEnemyParty[gBattlerPartyIndexes[i]], MON_DATA_HP);
s16 maxHp = GetMonData(&gEnemyParty[gBattlerPartyIndexes[i]], MON_DATA_MAX_HP);
healthBarSpriteId = gSprites[gHealthboxSpriteIds[i]].sHealthBarSpriteId;
CpuFill32(0, (void *)(OBJ_VRAM0 + gSprites[healthBarSpriteId].oam.tileNum * 32), 8 * TILE_SIZE_4BPP);
UpdateHpTextInHealthboxInDoubles(gHealthboxSpriteIds[i], HP_CURRENT, currHp, maxHp);
UpdateHpTextInHealthboxInDoubles(gHealthboxSpriteIds[i], HP_MAX, currHp, maxHp);
}
}
else // text to bars
{
UpdateStatusIconInHealthbox(gHealthboxSpriteIds[i]);
UpdateHealthboxAttribute(gHealthboxSpriteIds[i], &gEnemyParty[gBattlerPartyIndexes[i]], HEALTHBOX_HEALTH_BAR);
if (gBattleTypeFlags & BATTLE_TYPE_SAFARI)
UpdateHealthboxAttribute(gHealthboxSpriteIds[i], &gEnemyParty[gBattlerPartyIndexes[i]], HEALTHBOX_NICK);
}
}
gSprites[gHealthboxSpriteIds[i]].data[7] ^= 1;
}
}
}
#define tBattler data[0]
#define tSummaryBarSpriteId data[1]
#define tBallIconSpriteId(n) data[3 + n]
#define tIsBattleStart data[10]
#define tTimer data[11]
#define tBlendWeight data[15]
// sprite data for party summary bar
#define sEnterSpeed data[0]
#define sExitSpeed data[1]
// sprite data for party summary ball icon
#define sSummaryBarSpriteId data[0]
#define sTimer data[1]
#define sIsOpponent data[2]
#define sSpeed data[3]
#define sIsEmptyBall data[7]
u8 CreatePartyStatusSummarySprites(enum BattlerId battler, struct HpAndStatus *partyInfo, bool8 skipPlayer, bool8 isBattleStart)
{
bool8 isOpponent;
s16 bar_X, bar_Y, bar_pos2_X, bar_data0;
s32 i, j, var;
u8 summaryBarSpriteId;
u8 ballIconSpritesIds[PARTY_SIZE];
u8 taskId;
if (!skipPlayer || GetBattlerPosition(battler) != B_POSITION_OPPONENT_RIGHT)
{
if (IsOnPlayerSide(battler))
{
isOpponent = FALSE;
bar_X = 136, bar_Y = 96;
bar_pos2_X = 100;
bar_data0 = -5;
}
else
{
isOpponent = TRUE;
if (!skipPlayer || GetBattlerCoordsIndex(battler) == BATTLE_COORDS_SINGLES)
bar_X = 104, bar_Y = 40;
else
bar_X = 104, bar_Y = 16;
bar_pos2_X = -100;
bar_data0 = 5;
}
}
else
{
isOpponent = TRUE;
bar_X = 104, bar_Y = 40;
bar_pos2_X = -100;
bar_data0 = 5;
}
LoadCompressedSpriteSheetUsingHeap(&sStatusSummaryBarSpriteSheet);
LoadSpriteSheet(&sPartySummaryBallSpriteSheets);
LoadSpritePalette(&sPartySummaryBarSpritePals);
LoadSpritePalette(&sPartySummaryBallSpritePals);
summaryBarSpriteId = CreateSprite(&sPartySummaryBarSpriteTemplates[isOpponent], bar_X, bar_Y, 10);
SetSubspriteTables(&gSprites[summaryBarSpriteId], sStatusSummaryBar_SubspriteTable_Enter);
gSprites[summaryBarSpriteId].x2 = bar_pos2_X;
gSprites[summaryBarSpriteId].sEnterSpeed = bar_data0;
if (isOpponent)
{
gSprites[summaryBarSpriteId].x -= 96;
gSprites[summaryBarSpriteId].oam.matrixNum = ST_OAM_HFLIP;
}
else
{
gSprites[summaryBarSpriteId].x += 96;
}
for (i = 0; i < PARTY_SIZE; i++)
{
ballIconSpritesIds[i] = CreateSpriteAtEnd(&sPartySummaryBallSpriteTemplates[isOpponent], bar_X, bar_Y - 4, 9);
if (!isBattleStart)
gSprites[ballIconSpritesIds[i]].callback = SpriteCB_PartySummaryBall_OnSwitchout;
if (!isOpponent)
{
gSprites[ballIconSpritesIds[i]].x2 = 0;
gSprites[ballIconSpritesIds[i]].y2 = 0;
}
gSprites[ballIconSpritesIds[i]].sSummaryBarSpriteId = summaryBarSpriteId;
if (!isOpponent)
{
gSprites[ballIconSpritesIds[i]].x += 10 * i + 24;
gSprites[ballIconSpritesIds[i]].sTimer = i * 7 + 10;
gSprites[ballIconSpritesIds[i]].x2 = 120;
}
else
{
gSprites[ballIconSpritesIds[i]].x -= 10 * (5 - i) + 24;
gSprites[ballIconSpritesIds[i]].sTimer = (6 - i) * 7 + 10;
gSprites[ballIconSpritesIds[i]].x2 = -120;
}
gSprites[ballIconSpritesIds[i]].sIsOpponent = isOpponent;
}
if (IsOnPlayerSide(battler))
{
if (gBattleTypeFlags & BATTLE_TYPE_MULTI)
{
for (i = 0; i < PARTY_SIZE; i++)
{
if (partyInfo[i].hp == HP_EMPTY_SLOT)
{
// empty slot or an egg
gSprites[ballIconSpritesIds[i]].oam.tileNum += 1;
gSprites[ballIconSpritesIds[i]].sIsEmptyBall = TRUE;
}
else if (partyInfo[i].hp == 0)
{
// fainted mon
gSprites[ballIconSpritesIds[i]].oam.tileNum += 3;
}
else if (partyInfo[i].status != STATUS1_NONE)
{
// mon with major status
gSprites[ballIconSpritesIds[i]].oam.tileNum += 2;
}
}
}
else
{
for (i = 0, var = PARTY_SIZE - 1, j = 0; j < PARTY_SIZE; j++)
{
if (partyInfo[j].hp == HP_EMPTY_SLOT)
{
// empty slot or an egg
gSprites[ballIconSpritesIds[var]].oam.tileNum += 1;
gSprites[ballIconSpritesIds[var]].sIsEmptyBall = TRUE;
var--;
continue;
}
else if (partyInfo[j].hp == 0)
{
// fainted mon
gSprites[ballIconSpritesIds[i]].oam.tileNum += 3;
}
else if (gBattleTypeFlags & BATTLE_TYPE_ARENA && gBattleStruct->arenaLostPlayerMons & (1u << j))
{
// fainted arena mon
gSprites[ballIconSpritesIds[i]].oam.tileNum += 3;
}
else if (partyInfo[j].status != STATUS1_NONE)
{
// mon with primary status
gSprites[ballIconSpritesIds[i]].oam.tileNum += 2;
}
i++;
}
}
}
else
{
if (gBattleTypeFlags & (BATTLE_TYPE_MULTI | BATTLE_TYPE_TWO_OPPONENTS))
{
for (var = PARTY_SIZE - 1, i = 0; i < PARTY_SIZE; i++)
{
if (partyInfo[i].hp == HP_EMPTY_SLOT)
{
// empty slot or an egg
gSprites[ballIconSpritesIds[var]].oam.tileNum += 1;
gSprites[ballIconSpritesIds[var]].sIsEmptyBall = TRUE;
}
else if (partyInfo[i].hp == 0)
{
// fainted mon
gSprites[ballIconSpritesIds[var]].oam.tileNum += 3;
}
else if (partyInfo[i].status != STATUS1_NONE)
{
// mon with primary status
gSprites[ballIconSpritesIds[var]].oam.tileNum += 2;
}
var--;
}
}
else
{
for (var = 0, i = 0, j = 0; j < PARTY_SIZE; j++)
{
if (partyInfo[j].hp == HP_EMPTY_SLOT)
{
// empty slot or an egg
gSprites[ballIconSpritesIds[i]].oam.tileNum += 1;
gSprites[ballIconSpritesIds[i]].sIsEmptyBall = TRUE;
i++;
continue;
}
else if (partyInfo[j].hp == 0)
{
// fainted mon
gSprites[ballIconSpritesIds[PARTY_SIZE - 1 - var]].oam.tileNum += 3;
}
else if (gBattleTypeFlags & BATTLE_TYPE_ARENA && gBattleStruct->arenaLostOpponentMons & (1u << j))
{
// fainted arena mon
gSprites[ballIconSpritesIds[PARTY_SIZE - 1 - var]].oam.tileNum += 3;
}
else if (partyInfo[j].status != STATUS1_NONE)
{
// mon with primary status
gSprites[ballIconSpritesIds[PARTY_SIZE - 1 - var]].oam.tileNum += 2;
}
var++;
}
}
}
taskId = CreateTask(TaskDummy, 5);
gTasks[taskId].tBattler = battler;
gTasks[taskId].tSummaryBarSpriteId = summaryBarSpriteId;
for (i = 0; i < PARTY_SIZE; i++)
gTasks[taskId].tBallIconSpriteId(i) = ballIconSpritesIds[i];
gTasks[taskId].tIsBattleStart = isBattleStart;
if (isBattleStart)
{
gBattleSpritesDataPtr->animationData->field_9_x1C++;
}
PlaySE12WithPanning(SE_BALL_TRAY_ENTER, 0);
return taskId;
}
void Task_HidePartyStatusSummary(u8 taskId)
{
u8 ballIconSpriteIds[PARTY_SIZE];
bool8 isBattleStart;
u8 summaryBarSpriteId;
u8 battlerId;
s32 i;
isBattleStart = gTasks[taskId].tIsBattleStart;
summaryBarSpriteId = gTasks[taskId].tSummaryBarSpriteId;
battlerId = gTasks[taskId].tBattler;
for (i = 0; i < PARTY_SIZE; i++)
ballIconSpriteIds[i] = gTasks[taskId].tBallIconSpriteId(i);
SetGpuReg(REG_OFFSET_BLDCNT, BLDCNT_TGT2_ALL | BLDCNT_EFFECT_BLEND);
SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(16, 0));
gTasks[taskId].tBlendWeight = 16;
for (i = 0; i < PARTY_SIZE; i++)
gSprites[ballIconSpriteIds[i]].oam.objMode = ST_OAM_OBJ_BLEND;
gSprites[summaryBarSpriteId].oam.objMode = ST_OAM_OBJ_BLEND;
if (isBattleStart)
{
for (i = 0; i < PARTY_SIZE; i++)
{
if (GetBattlerSide(battlerId) != B_SIDE_PLAYER)
{
gSprites[ballIconSpriteIds[5 - i]].sTimer = 7 * i;
gSprites[ballIconSpriteIds[5 - i]].sSpeed = 0;
gSprites[ballIconSpriteIds[5 - i]].data[4] = 0;
gSprites[ballIconSpriteIds[5 - i]].callback = SpriteCB_PartySummaryBall_Exit;
}
else
{
gSprites[ballIconSpriteIds[i]].sTimer = 7 * i;
gSprites[ballIconSpriteIds[i]].sSpeed = 0;
gSprites[ballIconSpriteIds[i]].data[4] = 0;
gSprites[ballIconSpriteIds[i]].callback = SpriteCB_PartySummaryBall_Exit;
}
}
gSprites[summaryBarSpriteId].sEnterSpeed /= 2;
gSprites[summaryBarSpriteId].sExitSpeed = 0;
gSprites[summaryBarSpriteId].callback = SpriteCB_PartySummaryBar_Exit;
SetSubspriteTables(&gSprites[summaryBarSpriteId], sStatusSummaryBar_SubspriteTable_Exit);
gTasks[taskId].func = Task_HidePartyStatusSummary_BattleStart_1;
}
else
gTasks[taskId].func = Task_HidePartyStatusSummary_DuringBattle;
}
static void Task_HidePartyStatusSummary_BattleStart_1(u8 taskId)
{
if ((gTasks[taskId].tTimer++ % 2) == 0)
{
if (--gTasks[taskId].tBlendWeight < 0)
return;
SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(gTasks[taskId].tBlendWeight, 16 - gTasks[taskId].tBlendWeight));
}
if (gTasks[taskId].tBlendWeight == 0)
gTasks[taskId].func = Task_HidePartyStatusSummary_BattleStart_2;
}
static void Task_HidePartyStatusSummary_BattleStart_2(u8 taskId)
{
u8 ballIconSpriteIds[PARTY_SIZE];
s32 i;
if (--gTasks[taskId].tBlendWeight == -1)
{
u8 summaryBarSpriteId = gTasks[taskId].tSummaryBarSpriteId;
for (i = 0; i < PARTY_SIZE; i++)
ballIconSpriteIds[i] = gTasks[taskId].tBallIconSpriteId(i);
DestroySpriteAndFreeResources(&gSprites[summaryBarSpriteId]);
DestroySpriteAndFreeResources(&gSprites[ballIconSpriteIds[0]]);
for (i = 1; i < PARTY_SIZE; i++)
DestroySprite(&gSprites[ballIconSpriteIds[i]]);
}
else if (gTasks[taskId].tBlendWeight == -3)
{
SetGpuReg(REG_OFFSET_BLDCNT, 0);
SetGpuReg(REG_OFFSET_BLDALPHA, 0);
DestroyTask(taskId);
}
}
static void Task_HidePartyStatusSummary_DuringBattle(u8 taskId)
{
u8 ballIconSpriteIds[PARTY_SIZE];
s32 i;
if (--gTasks[taskId].tBlendWeight >= 0)
SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(gTasks[taskId].tBlendWeight, 16 - gTasks[taskId].tBlendWeight));
else if (gTasks[taskId].tBlendWeight == -1)
{
u8 summaryBarSpriteId = gTasks[taskId].tSummaryBarSpriteId;
for (i = 0; i < PARTY_SIZE; i++)
ballIconSpriteIds[i] = gTasks[taskId].tBallIconSpriteId(i);
DestroySpriteAndFreeResources(&gSprites[summaryBarSpriteId]);
DestroySpriteAndFreeResources(&gSprites[ballIconSpriteIds[0]]);
for (i = 1; i < PARTY_SIZE; i++)
DestroySprite(&gSprites[ballIconSpriteIds[i]]);
}
else if (gTasks[taskId].tBlendWeight == -3)
{
SetGpuReg(REG_OFFSET_BLDCNT, 0);
SetGpuReg(REG_OFFSET_BLDALPHA, 0);
DestroyTask(taskId);
}
}
#undef tBattler
#undef tSummaryBarSpriteId
#undef tBallIconSpriteId
#undef tIsBattleStart
#undef tBlendWeight
static void SpriteCB_PartySummaryBar(struct Sprite *sprite)
{
if (sprite->x2 != 0)
sprite->x2 += sprite->sEnterSpeed;
}
static void SpriteCB_PartySummaryBar_Exit(struct Sprite *sprite)
{
sprite->sExitSpeed += 32;
if (sprite->sEnterSpeed > 0)
sprite->x2 += sprite->sExitSpeed >> 4;
else
sprite->x2 -= sprite->sExitSpeed >> 4;
sprite->sExitSpeed &= 0xF;
}
static void SpriteCB_PartySummaryBall_OnBattleStart(struct Sprite *sprite)
{
bool8 isOpponent;
u16 speed;
if (sprite->sTimer > 0)
{
sprite->sTimer--;
return;
}
isOpponent = sprite->sIsOpponent;
speed = sprite->sSpeed;
speed += 56;
sprite->sSpeed = speed & 0xFFF0;
if (isOpponent != FALSE)
{
sprite->x2 += speed / 16;
if (sprite->x2 > 0)
sprite->x2 = 0;
}
else
{
sprite->x2 -= speed / 16;
if (sprite->x2 < 0)
sprite->x2 = 0;
}
if (sprite->x2 == 0)
{
s8 pan = isOpponent ? SOUND_PAN_ATTACKER : SOUND_PAN_TARGET;
if (sprite->sIsEmptyBall != FALSE)
PlaySE2WithPanning(SE_BALL_TRAY_EXIT, pan);
else
PlaySE1WithPanning(SE_BALL_TRAY_BALL, pan);
sprite->callback = SpriteCallbackDummy;
}
}
static void SpriteCB_PartySummaryBall_Exit(struct Sprite *sprite)
{
bool8 isOpponent;
u16 speed;
if (sprite->sTimer > 0)
{
sprite->sTimer--;
return;
}
isOpponent = sprite->sIsOpponent;
speed = sprite->sSpeed;
speed += 56;
sprite->sSpeed = speed & 0xFFF0;
if (isOpponent != FALSE)
sprite->x2 += speed / 16;
else
sprite->x2 -= speed / 16;
if (sprite->x2 + sprite->x > DISPLAY_WIDTH + 8 || sprite->x2 + sprite->x < -8)
{
sprite->invisible = TRUE;
sprite->callback = SpriteCallbackDummy;
}
}
static void SpriteCB_PartySummaryBall_OnSwitchout(struct Sprite *sprite)
{
u8 summaryBarSpriteId = sprite->sSummaryBarSpriteId;
sprite->x2 = gSprites[summaryBarSpriteId].x2;
sprite->y2 = gSprites[summaryBarSpriteId].y2;
}
#undef sSummaryBarSpriteId
#undef sTimer
#undef sIsOpponent
#undef sSpeed
#undef sIsEmptyBall
#undef sEnterSpeed
#undef sExitSpeed
static const u8 sText_HealthboxNickname[] = _("{HIGHLIGHT 02}");
void UpdateNickInHealthbox(u8 healthboxSpriteId, struct Pokemon *mon)
{
u8 nickname[POKEMON_NAME_LENGTH + 1];
u8 *ptr;
u32 windowId, spriteTileNum;
u8 *windowTileData;
u16 species;
u8 gender;
ptr = StringCopy(gDisplayedStringBattle, sText_HealthboxNickname);
GetMonData(mon, MON_DATA_NICKNAME, nickname);
StringGet_Nickname(nickname);
ptr = StringCopy(ptr, nickname);
*ptr++ = EXT_CTRL_CODE_BEGIN;
*ptr++ = EXT_CTRL_CODE_COLOR;
gender = GetMonGender(mon);
species = GetMonData(mon, MON_DATA_SPECIES);
if ((species == SPECIES_NIDORAN_F || species == SPECIES_NIDORAN_M) && StringCompare(nickname, gSpeciesInfo[species].speciesName) == 0)
gender = 100;
if (CheckBattleTypeGhost(mon, gSprites[healthboxSpriteId].sBattlerId))
gender = 100;
// AddTextPrinterAndCreateWindowOnHealthbox's arguments are the same in all 3 cases.
// It's possible they may have been different in early development phases.
switch (gender)
{
default:
*ptr++ = TEXT_DYNAMIC_COLOR_2;
*ptr++ = EOS;
windowTileData = AddTextPrinterAndCreateWindowOnHealthbox(gDisplayedStringBattle, 0, 3, &windowId);
break;
case MON_MALE:
*ptr++ = TEXT_DYNAMIC_COLOR_2;
*ptr++ = CHAR_MALE;
*ptr++ = EOS;
windowTileData = AddTextPrinterAndCreateWindowOnHealthbox(gDisplayedStringBattle, 0, 3, &windowId);
break;
case MON_FEMALE:
*ptr++ = TEXT_DYNAMIC_COLOR_1;
*ptr++ = CHAR_FEMALE;
*ptr++ = EOS;
windowTileData = AddTextPrinterAndCreateWindowOnHealthbox(gDisplayedStringBattle, 0, 3, &windowId);
break;
}
spriteTileNum = gSprites[healthboxSpriteId].oam.tileNum * TILE_SIZE_4BPP;
if (GetBattlerSide(gSprites[healthboxSpriteId].sBattlerId) == B_SIDE_PLAYER)
{
TextIntoHealthboxObject((void *)(OBJ_VRAM0 + 0x40 + spriteTileNum), windowTileData, 6);
ptr = (void *)(OBJ_VRAM0);
if (!GetBattlerCoordsIndex(gSprites[healthboxSpriteId].sBattlerId))
ptr += spriteTileNum + 0x800;
else
ptr += spriteTileNum + 0x400;
TextIntoHealthboxObject(ptr, windowTileData + 0xC0, 1);
}
else
TextIntoHealthboxObject((void *)(OBJ_VRAM0 + 0x20 + spriteTileNum), windowTileData, 7);
RemoveWindowOnHealthbox(windowId);
}
void TryAddPokeballIconToHealthbox(u8 healthboxSpriteId, bool8 noStatus)
{
u8 battlerId, healthBarSpriteId;
if (gBattleTypeFlags & (BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_CATCH_TUTORIAL | BATTLE_TYPE_POKEDUDE))
return;
if (gBattleTypeFlags & BATTLE_TYPE_TRAINER)
return;
battlerId = gSprites[healthboxSpriteId].sBattlerId;
if (GetBattlerSide(battlerId) == B_SIDE_PLAYER)
return;
if (CheckBattleTypeGhost(&gEnemyParty[gBattlerPartyIndexes[battlerId]], battlerId))
return;
if (!GetSetPokedexFlag(SpeciesToNationalPokedexNum(GetMonData(&gEnemyParty[gBattlerPartyIndexes[battlerId]], MON_DATA_SPECIES)), FLAG_GET_CAUGHT))
return;
healthBarSpriteId = gSprites[healthboxSpriteId].sHealthBarSpriteId;
if (noStatus)
CpuCopy32(GetBattleInterfaceGfxPtr(B_INTERFACE_GFX_BALL_CAUGHT), (void *)(OBJ_VRAM0 + (gSprites[healthBarSpriteId].oam.tileNum + 8) * TILE_SIZE_4BPP), 1 * TILE_SIZE_4BPP);
else
CpuFill32(0, (void *)(OBJ_VRAM0 + (gSprites[healthBarSpriteId].oam.tileNum + 8) * TILE_SIZE_4BPP), 1 * TILE_SIZE_4BPP);
}
enum
{
PAL_STATUS_PSN,
PAL_STATUS_PAR,
PAL_STATUS_SLP,
PAL_STATUS_FRZ,
PAL_STATUS_BRN
};
static const u16 sStatusIconColors[] = {
[PAL_STATUS_PSN] = RGB(24, 12, 24),
[PAL_STATUS_PAR] = RGB(23, 23, 3),
[PAL_STATUS_SLP] = RGB(20, 20, 17),
[PAL_STATUS_FRZ] = RGB(17, 22, 28),
[PAL_STATUS_BRN] = RGB(28, 14, 10)
};
static void UpdateStatusIconInHealthbox(u8 healthboxSpriteId)
{
s32 i;
u8 battlerId, healthBarSpriteId;
u32 status, pltAdder;
const u8 *statusGfxPtr;
s16 tileNumAdder;
u8 statusPalId;
battlerId = gSprites[healthboxSpriteId].sBattlerId;
healthBarSpriteId = gSprites[healthboxSpriteId].sHealthBarSpriteId;
if (GetBattlerSide(battlerId) == B_SIDE_PLAYER)
{
status = GetMonData(&gPlayerParty[gBattlerPartyIndexes[battlerId]], MON_DATA_STATUS);
if (!GetBattlerCoordsIndex(battlerId))
tileNumAdder = 0x1A;
else
tileNumAdder = 0x12;
}
else
{
status = GetMonData(&gEnemyParty[gBattlerPartyIndexes[battlerId]], MON_DATA_STATUS);
tileNumAdder = 0x11;
}
if (status & STATUS1_SLEEP)
{
statusGfxPtr = GetBattleInterfaceGfxPtr(GetStatusIconForBattlerId(B_INTERFACE_GFX_STATUS_SLP_BATTLER0, battlerId));
statusPalId = PAL_STATUS_SLP;
}
else if (status & STATUS1_PSN_ANY)
{
statusGfxPtr = GetBattleInterfaceGfxPtr(GetStatusIconForBattlerId(B_INTERFACE_GFX_STATUS_PSN_BATTLER0, battlerId));
statusPalId = PAL_STATUS_PSN;
}
else if (status & STATUS1_BURN)
{
statusGfxPtr = GetBattleInterfaceGfxPtr(GetStatusIconForBattlerId(B_INTERFACE_GFX_STATUS_BRN_BATTLER0, battlerId));
statusPalId = PAL_STATUS_BRN;
}
else if (status & STATUS1_FREEZE)
{
statusGfxPtr = GetBattleInterfaceGfxPtr(GetStatusIconForBattlerId(B_INTERFACE_GFX_STATUS_FRZ_BATTLER0, battlerId));
statusPalId = PAL_STATUS_FRZ;
}
else if (status & STATUS1_FROSTBITE)
{
statusGfxPtr = GetBattleInterfaceGfxPtr(GetStatusIconForBattlerId(B_INTERFACE_GFX_STATUS_FRB_BATTLER0, battlerId));
statusPalId = PAL_STATUS_FRZ;
}
else if (status & STATUS1_PARALYSIS)
{
statusGfxPtr = GetBattleInterfaceGfxPtr(GetStatusIconForBattlerId(B_INTERFACE_GFX_STATUS_PAR_BATTLER0, battlerId));
statusPalId = PAL_STATUS_PAR;
}
else
{
statusGfxPtr = GetBattleInterfaceGfxPtr(B_INTERFACE_GFX_STATUS_NONE);
for (i = 0; i < 3; i++)
CpuCopy32(statusGfxPtr,
(void *)(OBJ_VRAM0 + (gSprites[healthboxSpriteId].oam.tileNum + tileNumAdder + i) * TILE_SIZE_4BPP),
1 * TILE_SIZE_4BPP);
if (!gBattleSpritesDataPtr->battlerData[battlerId].hpNumbersNoBars)
CpuCopy32(GetBattleInterfaceGfxPtr(B_INTERFACE_GFX_HP_BAR_HP_TEXT),
(void *)(OBJ_VRAM0 + gSprites[healthBarSpriteId].oam.tileNum * TILE_SIZE_4BPP),
2 * TILE_SIZE_4BPP);
TryAddPokeballIconToHealthbox(healthboxSpriteId, TRUE);
return;
}
pltAdder = PLTT_ID(gSprites[healthboxSpriteId].oam.paletteNum);
pltAdder += battlerId + 12;
FillPalette(sStatusIconColors[statusPalId], pltAdder + OBJ_PLTT_OFFSET, PLTT_SIZEOF(1));
CpuCopy16(&gPlttBufferUnfaded[OBJ_PLTT_OFFSET + pltAdder], (u16 *)OBJ_PLTT + pltAdder, PLTT_SIZEOF(1));
CpuCopy32(statusGfxPtr, (void *)(OBJ_VRAM0 + (gSprites[healthboxSpriteId].oam.tileNum + tileNumAdder) * TILE_SIZE_4BPP), 3 * TILE_SIZE_4BPP);
if (GetBattlerCoordsIndex(battlerId) == BATTLE_COORDS_DOUBLES || !IsOnPlayerSide(battlerId))
{
if (!gBattleSpritesDataPtr->battlerData[battlerId].hpNumbersNoBars)
{
CpuCopy32(GetBattleInterfaceGfxPtr(B_INTERFACE_GFX_TRANSPARENT),
(void *)(OBJ_VRAM0 + gSprites[healthBarSpriteId].oam.tileNum * TILE_SIZE_4BPP),
1 * TILE_SIZE_4BPP);
CpuCopy32(GetBattleInterfaceGfxPtr(B_INTERFACE_GFX_HP_BAR_LEFT_BORDER),
(void *)(OBJ_VRAM0 + (gSprites[healthBarSpriteId].oam.tileNum + 1) * TILE_SIZE_4BPP),
1 * TILE_SIZE_4BPP);
}
}
TryAddPokeballIconToHealthbox(healthboxSpriteId, FALSE);
}
static u8 GetStatusIconForBattlerId(u8 statusElementId, u8 battlerId)
{
u8 ret = statusElementId;
switch (statusElementId)
{
case B_INTERFACE_GFX_STATUS_PSN_BATTLER0:
if (battlerId == B_BATTLER_0)
ret = B_INTERFACE_GFX_STATUS_PSN_BATTLER0;
else if (battlerId == B_BATTLER_1)
ret = B_INTERFACE_GFX_STATUS_PSN_BATTLER1;
else if (battlerId == B_BATTLER_2)
ret = B_INTERFACE_GFX_STATUS_PSN_BATTLER2;
else
ret = B_INTERFACE_GFX_STATUS_PSN_BATTLER3;
break;
case B_INTERFACE_GFX_STATUS_PAR_BATTLER0:
if (battlerId == 0)
ret = B_INTERFACE_GFX_STATUS_PAR_BATTLER0;
else if (battlerId == 1)
ret = B_INTERFACE_GFX_STATUS_PAR_BATTLER1;
else if (battlerId == 2)
ret = B_INTERFACE_GFX_STATUS_PAR_BATTLER2;
else
ret = B_INTERFACE_GFX_STATUS_PAR_BATTLER3;
break;
case B_INTERFACE_GFX_STATUS_SLP_BATTLER0:
if (battlerId == 0)
ret = B_INTERFACE_GFX_STATUS_SLP_BATTLER0;
else if (battlerId == 1)
ret = B_INTERFACE_GFX_STATUS_SLP_BATTLER1;
else if (battlerId == 2)
ret = B_INTERFACE_GFX_STATUS_SLP_BATTLER2;
else
ret = B_INTERFACE_GFX_STATUS_SLP_BATTLER3;
break;
case B_INTERFACE_GFX_STATUS_FRZ_BATTLER0:
if (battlerId == 0)
ret = B_INTERFACE_GFX_STATUS_FRZ_BATTLER0;
else if (battlerId == 1)
ret = B_INTERFACE_GFX_STATUS_FRZ_BATTLER1;
else if (battlerId == 2)
ret = B_INTERFACE_GFX_STATUS_FRZ_BATTLER2;
else
ret = B_INTERFACE_GFX_STATUS_FRZ_BATTLER3;
break;
case B_INTERFACE_GFX_STATUS_FRB_BATTLER0:
if (battlerId == 0)
ret = B_INTERFACE_GFX_STATUS_FRB_BATTLER0;
else if (battlerId == 1)
ret = B_INTERFACE_GFX_STATUS_FRB_BATTLER1;
else if (battlerId == 2)
ret = B_INTERFACE_GFX_STATUS_FRB_BATTLER2;
else
ret = B_INTERFACE_GFX_STATUS_FRB_BATTLER3;
break;
case B_INTERFACE_GFX_STATUS_BRN_BATTLER0:
if (battlerId == 0)
ret = B_INTERFACE_GFX_STATUS_BRN_BATTLER0;
else if (battlerId == 1)
ret = B_INTERFACE_GFX_STATUS_BRN_BATTLER1;
else if (battlerId == 2)
ret = B_INTERFACE_GFX_STATUS_BRN_BATTLER2;
else
ret = B_INTERFACE_GFX_STATUS_BRN_BATTLER3;
break;
}
return ret;
}
static void UpdateSafariBallsTextOnHealthbox(u8 healthboxSpriteId)
{
u32 windowId, spriteTileNum;
u8 *windowTileData;
windowTileData = AddTextPrinterAndCreateWindowOnHealthbox(gText_SafariBalls, 0, 3, &windowId);
spriteTileNum = gSprites[healthboxSpriteId].oam.tileNum * TILE_SIZE_4BPP;
TextIntoHealthboxObject((void *)(OBJ_VRAM0 + 0x40) + spriteTileNum, windowTileData, 6);
TextIntoHealthboxObject((void *)(OBJ_VRAM0 + 0x800) + spriteTileNum, windowTileData + 0xC0, 2);
RemoveWindowOnHealthbox(windowId);
}
static void UpdateLeftNoOfBallsTextOnHealthbox(u8 healthboxSpriteId)
{
u8 text[16];
u8 *txtPtr;
u32 windowId, spriteTileNum;
u8 *windowTileData;
txtPtr = StringCopy(text, gText_HighlightRed_Left);
ConvertIntToDecimalStringN(txtPtr, gNumSafariBalls, STR_CONV_MODE_LEFT_ALIGN, 2);
windowTileData = AddTextPrinterAndCreateWindowOnHealthbox(text, 47 - GetStringWidth(FONT_SMALL, text, 0), 3, &windowId);
spriteTileNum = gSprites[healthboxSpriteId].oam.tileNum * TILE_SIZE_4BPP;
SafariTextIntoHealthboxObject((void *)(OBJ_VRAM0 + 0x2C0) + spriteTileNum, windowTileData, 2);
SafariTextIntoHealthboxObject((void *)(OBJ_VRAM0 + 0xA00) + spriteTileNum, windowTileData + 0x40, 4);
RemoveWindowOnHealthbox(windowId);
}
void UpdateHealthboxAttribute(u8 healthboxSpriteId, struct Pokemon *mon, u8 elementId)
{
u8 battlerId = gSprites[healthboxSpriteId].sBattlerId;
s32 maxHp = GetMonData(mon, MON_DATA_MAX_HP);
s32 currHp = GetMonData(mon, MON_DATA_HP);
if (GetBattlerSide(battlerId) == B_SIDE_PLAYER)
{
u8 isDoubles = GetBattlerCoordsIndex(battlerId);
if (elementId == HEALTHBOX_LEVEL || elementId == HEALTHBOX_ALL)
UpdateLvlInHealthbox(healthboxSpriteId, GetMonData(mon, MON_DATA_LEVEL));
if (elementId == HEALTHBOX_CURRENT_HP || elementId == HEALTHBOX_ALL)
UpdateHpTextInHealthbox(healthboxSpriteId, HP_CURRENT, currHp, maxHp);
if (elementId == HEALTHBOX_MAX_HP || elementId == HEALTHBOX_ALL)
UpdateHpTextInHealthbox(healthboxSpriteId, HP_MAX, currHp, maxHp);
if (elementId == HEALTHBOX_HEALTH_BAR || elementId == HEALTHBOX_ALL)
{
LoadBattleBarGfx(0);
SetBattleBarStruct(battlerId, healthboxSpriteId, maxHp, currHp, 0);
MoveBattleBar(battlerId, healthboxSpriteId, HEALTH_BAR, 0);
}
if (!isDoubles && (elementId == HEALTHBOX_EXP_BAR || elementId == HEALTHBOX_ALL))
{
u16 species;
u32 exp, currLevelExp;
s32 currExpBarValue, maxExpBarValue;
u8 level;
LoadBattleBarGfx(3);
species = GetMonData(mon, MON_DATA_SPECIES);
level = GetMonData(mon, MON_DATA_LEVEL);
exp = GetMonData(mon, MON_DATA_EXP);
currLevelExp = gExperienceTables[gSpeciesInfo[species].growthRate][level];
currExpBarValue = exp - currLevelExp;
maxExpBarValue = gExperienceTables[gSpeciesInfo[species].growthRate][level + 1] - currLevelExp;
SetBattleBarStruct(battlerId, healthboxSpriteId, maxExpBarValue, currExpBarValue, isDoubles);
MoveBattleBar(battlerId, healthboxSpriteId, EXP_BAR, 0);
}
if (elementId == HEALTHBOX_NICK || elementId == HEALTHBOX_ALL)
UpdateNickInHealthbox(healthboxSpriteId, mon);
if (elementId == HEALTHBOX_STATUS_ICON || elementId == HEALTHBOX_ALL)
UpdateStatusIconInHealthbox(healthboxSpriteId);
if (elementId == HEALTHBOX_SAFARI_ALL_TEXT)
UpdateSafariBallsTextOnHealthbox(healthboxSpriteId);
if (elementId == HEALTHBOX_SAFARI_ALL_TEXT || elementId == HEALTHBOX_SAFARI_BALLS_TEXT)
UpdateLeftNoOfBallsTextOnHealthbox(healthboxSpriteId);
}
else
{
if (elementId == HEALTHBOX_LEVEL || elementId == HEALTHBOX_ALL)
UpdateLvlInHealthbox(healthboxSpriteId, GetMonData(mon, MON_DATA_LEVEL));
if (elementId == HEALTHBOX_HEALTH_BAR || elementId == HEALTHBOX_ALL)
{
LoadBattleBarGfx(0);
maxHp = GetMonData(mon, MON_DATA_MAX_HP);
currHp = GetMonData(mon, MON_DATA_HP);
SetBattleBarStruct(battlerId, healthboxSpriteId, maxHp, currHp, 0);
MoveBattleBar(battlerId, healthboxSpriteId, HEALTH_BAR, 0);
}
if (elementId == HEALTHBOX_NICK || elementId == HEALTHBOX_ALL)
UpdateNickInHealthbox(healthboxSpriteId, mon);
if (elementId == HEALTHBOX_STATUS_ICON || elementId == HEALTHBOX_ALL)
UpdateStatusIconInHealthbox(healthboxSpriteId);
}
}
#define B_HEALTHBAR_NUM_PIXELS 48
#define B_HEALTHBAR_NUM_TILES (B_HEALTHBAR_NUM_PIXELS / 8)
#define B_EXPBAR_NUM_PIXELS 64
#define B_EXPBAR_NUM_TILES (B_EXPBAR_NUM_PIXELS / 8)
s32 MoveBattleBar(u8 battlerId, u8 healthboxSpriteId, u8 whichBar, u8 unused)
{
s32 currentBarValue;
if (whichBar == HEALTH_BAR)
{
u16 hpFraction = B_FAST_HP_DRAIN == FALSE ? 1 : max(gBattleSpritesDataPtr->battleBars[battlerId].maxValue / B_HEALTHBAR_NUM_PIXELS, 1);
currentBarValue = CalcNewBarValue(gBattleSpritesDataPtr->battleBars[battlerId].maxValue,
gBattleSpritesDataPtr->battleBars[battlerId].oldValue,
gBattleSpritesDataPtr->battleBars[battlerId].receivedValue,
&gBattleSpritesDataPtr->battleBars[battlerId].currValue,
B_HEALTHBAR_NUM_TILES,
hpFraction);
}
else // exp bar
{
u16 expFraction = GetScaledExpFraction(gBattleSpritesDataPtr->battleBars[battlerId].oldValue,
gBattleSpritesDataPtr->battleBars[battlerId].receivedValue,
gBattleSpritesDataPtr->battleBars[battlerId].maxValue, B_EXPBAR_NUM_TILES);
if (expFraction == 0)
expFraction = 1;
expFraction = abs(gBattleSpritesDataPtr->battleBars[battlerId].receivedValue / expFraction);
currentBarValue = CalcNewBarValue(gBattleSpritesDataPtr->battleBars[battlerId].maxValue,
gBattleSpritesDataPtr->battleBars[battlerId].oldValue,
gBattleSpritesDataPtr->battleBars[battlerId].receivedValue,
&gBattleSpritesDataPtr->battleBars[battlerId].currValue,
B_EXPBAR_NUM_TILES,
expFraction);
}
if (whichBar == EXP_BAR || (whichBar == HEALTH_BAR && !gBattleSpritesDataPtr->battlerData[battlerId].hpNumbersNoBars))
MoveBattleBarGraphically(battlerId, whichBar);
if (currentBarValue == -1)
gBattleSpritesDataPtr->battleBars[battlerId].currValue = 0;
return currentBarValue;
}
static void MoveBattleBarGraphically(u8 battlerId, u8 whichBar)
{
u8 filledPixels[B_HEALTHBAR_NUM_TILES > B_EXPBAR_NUM_TILES ? B_HEALTHBAR_NUM_TILES : B_EXPBAR_NUM_TILES];
u8 totalFilledPixels, level;
u8 barElementId;
u8 i;
switch (whichBar)
{
case HEALTH_BAR:
totalFilledPixels = CalcBarFilledPixels(gBattleSpritesDataPtr->battleBars[battlerId].maxValue,
gBattleSpritesDataPtr->battleBars[battlerId].oldValue,
gBattleSpritesDataPtr->battleBars[battlerId].receivedValue,
&gBattleSpritesDataPtr->battleBars[battlerId].currValue,
filledPixels,
B_HEALTHBAR_NUM_TILES);
if (totalFilledPixels > (B_HEALTHBAR_NUM_PIXELS * 50 / 100)) // more than 50 % hp
barElementId = B_INTERFACE_GFX_HP_BAR_GREEN;
else if (totalFilledPixels > (B_HEALTHBAR_NUM_PIXELS * 20 / 100)) // more than 20% hp
barElementId = B_INTERFACE_GFX_HP_BAR_YELLOW;
else
barElementId = B_INTERFACE_GFX_HP_BAR_RED; // 20 % or less
for (i = 0; i < B_HEALTHBAR_NUM_TILES; i++)
{
u8 healthbarSpriteId = gSprites[gBattleSpritesDataPtr->battleBars[battlerId].healthboxSpriteId].sHealthBarSpriteId;
if (i < 2) // first 2 tiles are on left healthbar sprite
CpuCopy32(GetBattleInterfaceGfxPtr(barElementId) + filledPixels[i] * TILE_SIZE_4BPP,
(void *)(OBJ_VRAM0 + (gSprites[healthbarSpriteId].oam.tileNum + 2 + i) * TILE_SIZE_4BPP), // + 2 here is due to B_INTERFACE_GFX_HP_BAR_HP_TEXT
1 * TILE_SIZE_4BPP);
else // remaining 4 tiles are on right healthbar sprite
CpuCopy32(GetBattleInterfaceGfxPtr(barElementId) + filledPixels[i] * TILE_SIZE_4BPP,
(void *)(OBJ_VRAM0 + 64 + (i + gSprites[healthbarSpriteId].oam.tileNum) * TILE_SIZE_4BPP),
1 * TILE_SIZE_4BPP);
}
break;
case EXP_BAR:
CalcBarFilledPixels(gBattleSpritesDataPtr->battleBars[battlerId].maxValue,
gBattleSpritesDataPtr->battleBars[battlerId].oldValue,
gBattleSpritesDataPtr->battleBars[battlerId].receivedValue,
&gBattleSpritesDataPtr->battleBars[battlerId].currValue,
filledPixels,
B_EXPBAR_NUM_TILES);
level = GetMonData(&gPlayerParty[gBattlerPartyIndexes[battlerId]], MON_DATA_LEVEL);
if (level == MAX_LEVEL)
{
for (i = 0; i < B_EXPBAR_NUM_TILES; i++)
filledPixels[i] = 0;
}
for (i = 0; i < B_EXPBAR_NUM_TILES; i++)
{
if (i < 4) // first 4 tiles are on left healthbox sprite
CpuCopy32(GetBattleInterfaceGfxPtr(B_INTERFACE_GFX_EXP_BAR) + filledPixels[i] * TILE_SIZE_4BPP,
(void *)(OBJ_VRAM0 + (gSprites[gBattleSpritesDataPtr->battleBars[battlerId].healthboxSpriteId].oam.tileNum + 36 + i) * TILE_SIZE_4BPP),
1 * TILE_SIZE_4BPP);
else // remaining 4 tiles are on right healthbox sprite
CpuCopy32(GetBattleInterfaceGfxPtr(B_INTERFACE_GFX_EXP_BAR) + filledPixels[i] * TILE_SIZE_4BPP,
(void *)(OBJ_VRAM0 + 0xB80 + (i + gSprites[gBattleSpritesDataPtr->battleBars[battlerId].healthboxSpriteId].oam.tileNum) * TILE_SIZE_4BPP),
1 * TILE_SIZE_4BPP);
}
break;
}
}
static s32 CalcNewBarValue(s32 maxValue, s32 oldValue, s32 receivedValue, s32 *currValue, u8 scale, u16 toAdd)
{
s32 ret, newValue;
scale *= 8;
if (*currValue == -32768) // first function call
{
if (maxValue < scale)
*currValue = Q_24_8(oldValue);
else
*currValue = oldValue;
}
newValue = oldValue - receivedValue;
if (newValue < 0)
newValue = 0;
else if (newValue > maxValue)
newValue = maxValue;
if (maxValue < scale)
{
if (newValue == Q_24_8_TO_INT(*currValue) && (*currValue & 0xFF) == 0)
return -1;
}
else
{
if (newValue == *currValue) // we're done, the bar's value has been updated
return -1;
}
if (maxValue < scale) // handle cases of max var having less pixels than the whole bar
{
s32 toAdd = Q_24_8(maxValue) / scale;
if (receivedValue < 0) // fill bar right
{
*currValue += toAdd;
ret = Q_24_8_TO_INT(*currValue);
if (ret >= newValue)
{
*currValue = Q_24_8(newValue);
ret = newValue;
}
}
else // move bar left
{
*currValue -= toAdd;
ret = Q_24_8_TO_INT(*currValue);
// try round up
if ((*currValue & 0xFF) > 0)
ret++;
if (ret <= newValue)
{
*currValue = Q_24_8(newValue);
ret = newValue;
}
}
}
else
{
if (receivedValue < 0) // fill bar right
{
*currValue += toAdd;
if (*currValue > newValue)
*currValue = newValue;
ret = *currValue;
}
else // move bar left
{
*currValue -= toAdd;
if (*currValue < newValue)
*currValue = newValue;
ret = *currValue;
}
}
return ret;
}
static u8 CalcBarFilledPixels(s32 maxValue, s32 oldValue, s32 receivedValue, s32 *currValue, u8 *filledPixels, u8 numTiles)
{
u8 numPixelsToFill, totalFilledPixels, totalPixels;
u8 i;
s32 newValue = oldValue - receivedValue;
if (newValue < 0)
newValue = 0;
else if (newValue > maxValue)
newValue = maxValue;
totalPixels = numTiles * 8;
for (i = 0; i < numTiles; i++)
filledPixels[i] = 0;
if (maxValue < totalPixels)
numPixelsToFill = Q_24_8_TO_INT(*currValue * totalPixels / maxValue);
else
numPixelsToFill = *currValue * totalPixels / maxValue;
totalFilledPixels = numPixelsToFill;
if (numPixelsToFill == 0 && newValue > 0)
{
filledPixels[0] = 1;
totalFilledPixels = 1;
}
else
{
for (i = 0; i < numTiles; i++)
{
if (numPixelsToFill >= 8) // too many to fill into a single tile
filledPixels[i] = 8;
else
{
filledPixels[i] = numPixelsToFill;
break;
}
numPixelsToFill -= 8;
}
}
return totalFilledPixels;
}
static u8 GetScaledExpFraction(s32 oldValue, s32 receivedValue, s32 maxValue, u8 scale)
{
s32 newVal, result;
s8 oldToMax, newToMax;
scale *= (B_FAST_EXP_GROW) ? 2 : 8;
newVal = oldValue - receivedValue;
if (newVal < 0)
newVal = 0;
else if (newVal > maxValue)
newVal = maxValue;
oldToMax = oldValue * scale / maxValue;
newToMax = newVal * scale / maxValue;
result = oldToMax - newToMax;
return abs(result);
}
u8 GetScaledHPFraction(s16 hp, s16 maxhp, u8 scale)
{
u8 result = hp * scale / maxhp;
if (result == 0 && hp > 0)
return 1;
return result;
}
u8 GetHPBarLevel(s16 hp, s16 maxhp)
{
u8 result;
if (hp == maxhp)
result = HP_BAR_FULL;
else
{
u8 fraction = GetScaledHPFraction(hp, maxhp, B_HEALTHBAR_NUM_PIXELS);
if (fraction > (B_HEALTHBAR_NUM_PIXELS * 50 / 100))
result = HP_BAR_GREEN;
else if (fraction > (B_HEALTHBAR_NUM_PIXELS * 20 / 100))
result = HP_BAR_YELLOW;
else if (fraction > 0)
result = HP_BAR_RED;
else
result = HP_BAR_EMPTY;
}
return result;
}
static const struct WindowTemplate sHealthboxWindowTemplate = {
.bg = 0,
.tilemapLeft = 0,
.tilemapTop = 0,
.width = 8,
.height = 2,
.paletteNum = 0,
.baseBlock = 0
};
static u8 *AddTextPrinterAndCreateWindowOnHealthbox(const u8 *str, u32 x, u32 y, u32 *windowId)
{
u16 winId;
u8 color[3];
struct WindowTemplate winTemplate = sHealthboxWindowTemplate;
winId = AddWindow(&winTemplate);
FillWindowPixelBuffer(winId, PIXEL_FILL(2));
color[0] = 2;
color[1] = 1;
color[2] = 3;
AddTextPrinterParameterized4(winId, FONT_SMALL, x, y, 0, 0, color, -1, str);
*windowId = winId;
return (u8 *)(GetWindowAttribute(winId, WINDOW_TILE_DATA));
}
static void RemoveWindowOnHealthbox(u32 windowId)
{
RemoveWindow(windowId);
}
static void TextIntoHealthboxObject(void *dest, u8 *windowTileData, s32 windowWidth)
{
CpuCopy32(windowTileData + 256, dest + 256, windowWidth * TILE_SIZE_4BPP);
// + 256 as that prevents the top 4 blank rows of sHealthboxWindowTemplate from being copied
if (windowWidth > 0)
{
do
{
CpuCopy32(windowTileData + 20, dest + 20, 12);
dest += 32, windowTileData += 32;
windowWidth--;
} while (windowWidth != 0);
}
}
static void SafariTextIntoHealthboxObject(void *dest, u8 *windowTileData, u32 windowWidth)
{
CpuCopy32(windowTileData, dest, windowWidth * TILE_SIZE_4BPP);
CpuCopy32(windowTileData + 256, dest + 256, windowWidth * TILE_SIZE_4BPP);
}
#undef sBattlerId
#define ABILITY_POP_UP_POS_X_DIFF 64
#define ABILITY_POP_UP_POS_X_SLIDE 128
#define ABILITY_POP_UP_POS_X_SPEED 4
#define ABILITY_POP_UP_WIN_WIDTH 10
#define ABILITY_POP_UP_STR_WIDTH (ABILITY_POP_UP_WIN_WIDTH * 8)
#define ABILITY_POP_UP_PLAYER_LEFT_WIN_W 6
#define ABILITY_POP_UP_PLAYER_RIGHT_WIN_W 4
#define ABILITY_POP_UP_OPPONENT_LEFT_WIN_W 7
#define ABILITY_POP_UP_OPPONENT_RIGHT_WIN_W 3
#define ABILITY_POP_UP_WAIT_FRAMES 48
/*
* BG = BackGround
* FG = ForeGround
* SH = SHadow
*/
#define ABILITY_POP_UP_BATTLER_BG_TXTCLR 2
#define ABILITY_POP_UP_BATTLER_FG_TXTCLR 7
#define ABILITY_POP_UP_BATTLER_SH_TXTCLR 1
#define ABILITY_POP_UP_ABILITY_BG_TXTCLR 7
#define ABILITY_POP_UP_ABILITY_FG_TXTCLR 9
#define ABILITY_POP_UP_ABILITY_SH_TXTCLR 1
#define sState data[0]
#define sAutoDestroy data[1]
#define sTimer data[2]
#define sIsPlayerSide data[3]
#define sBattlerId data[4]
#define sIsMain data[5]
enum
{
APU_STATE_SLIDE_IN = 0,
APU_STATE_IDLE,
APU_STATE_SLIDE_OUT,
APU_STATE_END
};
enum
{
TAG_ABILITY_POP_UP = 0xD720, // Only used for the SpritePalette, the rest below is for the SpriteSheets.
TAG_ABILITY_POP_UP_PLAYER1 = TAG_ABILITY_POP_UP,
TAG_ABILITY_POP_UP_OPPONENT1,
TAG_ABILITY_POP_UP_PLAYER2,
TAG_ABILITY_POP_UP_OPPONENT2,
TAG_LAST_BALL_WINDOW,
};
static const u32 sAbilityPopUpGfx[] = INCBIN_U32("graphics/battle_interface/ability_pop_up.4bpp");
static const u16 sAbilityPopUpPalette[] = INCBIN_U16("graphics/battle_interface/ability_pop_up.gbapal");
static const struct SpriteSheet sSpriteSheet_AbilityPopUp =
{
sAbilityPopUpGfx, sizeof(sAbilityPopUpGfx), TAG_ABILITY_POP_UP
};
static const struct SpritePalette sSpritePalette_AbilityPopUp =
{
sAbilityPopUpPalette, TAG_ABILITY_POP_UP
};
static const struct OamData sOamData_AbilityPopUp =
{
.affineMode = ST_OAM_AFFINE_OFF,
.objMode = ST_OAM_OBJ_NORMAL,
.shape = SPRITE_SHAPE(64x32),
.size = SPRITE_SIZE(64x32),
.priority = 0,
};
static const struct SpriteTemplate sSpriteTemplate_AbilityPopUp =
{
.tileTag = TAG_NONE, // Changed on the fly.
.paletteTag = TAG_ABILITY_POP_UP,
.oam = &sOamData_AbilityPopUp,
.anims = gDummySpriteAnimTable,
.images = NULL,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCb_AbilityPopUp
};
static const s16 sAbilityPopUpCoordsDoubles[MAX_BATTLERS_COUNT][2] =
{
{ 24, 80}, // Player left
{178, 19}, // Opponent left
{ 24, 97}, // Player right
{178, 36}, // Opponent right
};
static const s16 sAbilityPopUpCoordsSingles[MAX_BATTLERS_COUNT][2] =
{
{ 24, 97}, // Player
{178, 57}, // Opponent
};
static u8 *AddTextPrinterAndCreateWindowOnAbilityPopUp(const u8 *str, u32 x, u32 y, u32 bgColor, u32 fgColor, u32 shadowColor, u32 *windowId)
{
u32 fontId;
u8 color[3] = {bgColor, fgColor, shadowColor};
struct WindowTemplate winTemplate = {0};
winTemplate.width = ABILITY_POP_UP_WIN_WIDTH;
winTemplate.height = 2;
*windowId = AddWindow(&winTemplate);
FillWindowPixelBuffer(*windowId, PIXEL_FILL(bgColor));
fontId = GetFontIdToFit(str, FONT_SMALL, 0, ABILITY_POP_UP_STR_WIDTH);
AddTextPrinterParameterized4(*windowId, fontId, x, y, 0, 0, color, TEXT_SKIP_DRAW, str);
return (u8 *)(GetWindowAttribute(*windowId, WINDOW_TILE_DATA));
}
static void TextIntoAbilityPopUp(void *dest, u8 *windowTileData, s32 windowWidth, bool32 printNickname)
{
#define PIXELS(n) (n * 4)
if (windowWidth > 0)
{
do
{
if (printNickname)
{
CpuCopy32(windowTileData + PIXELS(3), dest + PIXELS(3), PIXELS(5));
CpuCopy32(windowTileData + TILE_OFFSET_4BPP(ABILITY_POP_UP_WIN_WIDTH), dest + TILE_OFFSET_4BPP(8), PIXELS(5));
}
else
{
CpuCopy32(windowTileData + PIXELS(7), dest + PIXELS(7), PIXELS(1));
CpuCopy32(windowTileData + TILE_OFFSET_4BPP(ABILITY_POP_UP_WIN_WIDTH), dest + TILE_OFFSET_4BPP(8), TILE_SIZE_4BPP);
}
dest += TILE_SIZE_4BPP, windowTileData += TILE_SIZE_4BPP;
windowWidth--;
} while (windowWidth != 0);
}
#undef PIXELS
}
static void PrintOnAbilityPopUp(const u8 *str, u8 *spriteTileData1, u8 *spriteTileData2, u32 x, u32 y, u32 bgColor, u32 fgColor, u32 shadowColor, u32 printNickname, u32 battler)
{
u32 windowId, fontId;
u8 *windowTileData = AddTextPrinterAndCreateWindowOnAbilityPopUp(str, x, y, bgColor, fgColor, shadowColor, &windowId);
u32 size1 = ABILITY_POP_UP_OPPONENT_LEFT_WIN_W, size2 = ABILITY_POP_UP_OPPONENT_RIGHT_WIN_W;
spriteTileData1 += TILE_OFFSET_4BPP(1);
if (IsOnPlayerSide(battler))
{
size1 = ABILITY_POP_UP_PLAYER_LEFT_WIN_W, size2 = ABILITY_POP_UP_PLAYER_RIGHT_WIN_W;
// Increment again as the *first* column of the sprite
// is not shown for player's pop up when sliding in.
spriteTileData1 += TILE_OFFSET_4BPP(1);
}
TextIntoAbilityPopUp(spriteTileData1, windowTileData, size1, printNickname);
fontId = GetFontIdToFit(str, FONT_SMALL, 0, ABILITY_POP_UP_STR_WIDTH);
if (GetStringWidth(fontId, str, 0) > (size1 * 8))
{
windowTileData += TILE_OFFSET_4BPP(size1);
TextIntoAbilityPopUp(spriteTileData2, windowTileData, size2, printNickname);
}
RemoveWindow(windowId);
}
static void PrintBattlerOnAbilityPopUp(u8 battler, u8 spriteId1, u8 spriteId2)
{
u32 totalChar = 0, lastChar;
struct Pokemon *illusionMon = GetIllusionMonPtr(battler);
if (illusionMon != NULL)
GetMonData(illusionMon, MON_DATA_NICKNAME, gStringVar1);
else
GetMonData(GetBattlerMon(battler), MON_DATA_NICKNAME, gStringVar1);
while (gStringVar1[totalChar] != EOS)
totalChar++;
lastChar = gStringVar1[totalChar - 1];
StringAppend(gStringVar1, COMPOUND_STRING("'"));
if (lastChar != CHAR_S && lastChar != CHAR_s)
StringAppend(gStringVar1, COMPOUND_STRING("s"));
PrintOnAbilityPopUp(gStringVar1,
(void *)(OBJ_VRAM0) + TILE_OFFSET_4BPP(gSprites[spriteId1].oam.tileNum),
(void *)(OBJ_VRAM0) + TILE_OFFSET_4BPP(gSprites[spriteId2].oam.tileNum),
0, 0,
ABILITY_POP_UP_BATTLER_BG_TXTCLR, ABILITY_POP_UP_BATTLER_FG_TXTCLR, ABILITY_POP_UP_BATTLER_SH_TXTCLR,
TRUE, gSprites[spriteId1].sBattlerId);
}
static void PrintAbilityOnAbilityPopUp(enum Ability ability, u8 spriteId1, u8 spriteId2)
{
PrintOnAbilityPopUp(COMPOUND_STRING(" "),
(void *)(OBJ_VRAM0) + TILE_OFFSET_4BPP(gSprites[spriteId1].oam.tileNum) + TILE_OFFSET_4BPP(8),
(void *)(OBJ_VRAM0) + TILE_OFFSET_4BPP(gSprites[spriteId2].oam.tileNum) + TILE_OFFSET_4BPP(8),
0, 4,
ABILITY_POP_UP_ABILITY_BG_TXTCLR, ABILITY_POP_UP_ABILITY_FG_TXTCLR, ABILITY_POP_UP_ABILITY_SH_TXTCLR,
FALSE, gSprites[spriteId1].sBattlerId);
PrintOnAbilityPopUp(gAbilitiesInfo[ability].name,
(void *)(OBJ_VRAM0) + TILE_OFFSET_4BPP(gSprites[spriteId1].oam.tileNum) + TILE_OFFSET_4BPP(8),
(void *)(OBJ_VRAM0) + TILE_OFFSET_4BPP(gSprites[spriteId2].oam.tileNum) + TILE_OFFSET_4BPP(8),
0, 4,
ABILITY_POP_UP_ABILITY_BG_TXTCLR, ABILITY_POP_UP_ABILITY_FG_TXTCLR, ABILITY_POP_UP_ABILITY_SH_TXTCLR,
FALSE, gSprites[spriteId1].sBattlerId);
}
static inline bool32 IsAnyAbilityPopUpActive(void)
{
u32 activeAbilityPopUps = 0;
for (u32 battler = 0; battler < gBattlersCount; battler++)
{
if (gBattleStruct->battlerState[battler].activeAbilityPopUps)
activeAbilityPopUps++;
}
return activeAbilityPopUps;
}
void CreateAbilityPopUp(u8 battler, enum Ability ability, bool32 isDoubleBattle)
{
u8 *spriteIds;
u32 xSlide, tileTag, battlerPosition = GetBattlerPosition(battler);
struct SpriteTemplate template;
const s16 (*coords)[2];
if (gBattleScripting.abilityPopupOverwrite)
ability = gBattleScripting.abilityPopupOverwrite;
if (gTestRunnerEnabled)
{
TestRunner_Battle_RecordAbilityPopUp(battler, ability);
if (gTestRunnerHeadless)
return;
}
if (!IsAnyAbilityPopUpActive())
LoadSpritePalette(&sSpritePalette_AbilityPopUp);
tileTag = (TAG_ABILITY_POP_UP_PLAYER1 + battler);
if (IndexOfSpriteTileTag(tileTag) == 0xFF)
{
struct SpriteSheet sheet = sSpriteSheet_AbilityPopUp;
sheet.tag = tileTag;
LoadSpriteSheet(&sheet);
}
coords = isDoubleBattle ? sAbilityPopUpCoordsDoubles : sAbilityPopUpCoordsSingles;
xSlide = IsOnPlayerSide(battler) ? -ABILITY_POP_UP_POS_X_SLIDE : ABILITY_POP_UP_POS_X_SLIDE;
template = sSpriteTemplate_AbilityPopUp;
template.tileTag = tileTag;
spriteIds = gBattleStruct->abilityPopUpSpriteIds[battler];
spriteIds[0] = CreateSprite(&template, coords[battlerPosition][0] + xSlide,
coords[battlerPosition][1], 0);
spriteIds[1] = CreateSprite(&template, coords[battlerPosition][0] + xSlide + ABILITY_POP_UP_POS_X_DIFF,
coords[battlerPosition][1], 0);
if (IsOnPlayerSide(battler))
{
gSprites[spriteIds[0]].sIsPlayerSide = TRUE;
gSprites[spriteIds[1]].sIsPlayerSide = TRUE;
}
gSprites[spriteIds[1]].oam.tileNum += 32; // Second half of the pop up tiles.
// Create only one instance, as it's only used for
// tracking the SpriteSheet(s) and SpritePalette.
if (!IsAnyAbilityPopUpActive())
CreateTask(Task_FreeAbilityPopUpGfx, 5);
gBattleStruct->battlerState[battler].activeAbilityPopUps = TRUE;
gSprites[spriteIds[0]].sIsMain = TRUE;
gSprites[spriteIds[0]].sBattlerId = battler;
gSprites[spriteIds[1]].sBattlerId = battler;
PrintBattlerOnAbilityPopUp(battler, spriteIds[0], spriteIds[1]);
PrintAbilityOnAbilityPopUp(ability, spriteIds[0], spriteIds[1]);
}
void UpdateAbilityPopup(u8 battler)
{
u8 *spriteIds = gBattleStruct->abilityPopUpSpriteIds[battler];
enum Ability ability = (gBattleScripting.abilityPopupOverwrite) ? gBattleScripting.abilityPopupOverwrite
: gBattleMons[battler].ability;
PrintAbilityOnAbilityPopUp(ability, spriteIds[0], spriteIds[1]);
}
static void SpriteCb_AbilityPopUp(struct Sprite *sprite)
{
s16 *data = sprite->data;
u32 battlerPosition = GetBattlerPosition(sBattlerId);
u32 fullX = sprite->x + sprite->x2;
u32 speed;
switch (sState)
{
case APU_STATE_SLIDE_IN:
{
const s16 (*coords)[2] = IsDoubleBattle() ? sAbilityPopUpCoordsDoubles : sAbilityPopUpCoordsSingles;
u32 xCoord = coords[battlerPosition][0];
if (sIsMain && ++sTimer == 4)
PlaySE(SE_BALL_TRAY_ENTER);
if (!sIsMain)
xCoord += ABILITY_POP_UP_POS_X_DIFF;
if (fullX == xCoord)
{
sTimer = ABILITY_POP_UP_WAIT_FRAMES;
sState = APU_STATE_IDLE;
break;
}
speed = sIsPlayerSide ? ABILITY_POP_UP_POS_X_SPEED : -ABILITY_POP_UP_POS_X_SPEED;
sprite->x2 += speed;
break;
}
case APU_STATE_IDLE:
{
if (!sTimer || sAutoDestroy)
{
sState = APU_STATE_SLIDE_OUT;
break;
}
if (!gBattleScripting.fixedPopup)
sTimer--;
break;
}
case APU_STATE_SLIDE_OUT:
{
if (fullX == sprite->x)
{
sState = APU_STATE_END;
break;
}
speed = sIsPlayerSide ? -ABILITY_POP_UP_POS_X_SPEED : ABILITY_POP_UP_POS_X_SPEED;
sprite->x2 += speed;
break;
}
case APU_STATE_END:
{
if (sIsMain)
gBattleStruct->battlerState[sBattlerId].activeAbilityPopUps = FALSE;
DestroySprite(sprite);
break;
}
}
}
void DestroyAbilityPopUp(u8 battler)
{
if (gBattleStruct->battlerState[battler].activeAbilityPopUps)
{
gSprites[gBattleStruct->abilityPopUpSpriteIds[battler][0]].sAutoDestroy = TRUE;
gSprites[gBattleStruct->abilityPopUpSpriteIds[battler][1]].sAutoDestroy = TRUE;
}
}
static void Task_FreeAbilityPopUpGfx(u8 taskId)
{
if (!IsAnyAbilityPopUpActive())
{
for (u32 battler = 0; battler < gBattlersCount; battler++)
{
if (IndexOfSpriteTileTag(TAG_ABILITY_POP_UP_PLAYER1 + battler) != 0xFF)
FreeSpriteTilesByTag(TAG_ABILITY_POP_UP_PLAYER1 + battler);
}
FreeSpritePaletteByTag(TAG_ABILITY_POP_UP);
DestroyTask(taskId);
}
}
#undef sState
#undef sAutoDestroy
#undef sTimer
#undef sIsPlayerSide
#undef sBattlerId
#undef sIsMain
// last used ball
static const struct OamData sOamData_LastUsedBall =
{
.y = 0,
.affineMode = 0,
.objMode = 0,
.mosaic = 0,
.bpp = 0,
.shape = (B_LAST_USED_BALL_CYCLE == TRUE ? SPRITE_SHAPE(32x64) : SPRITE_SHAPE(32x32)),
.x = 0,
.matrixNum = 0,
.size = (B_LAST_USED_BALL_CYCLE == TRUE ? SPRITE_SIZE(32x64) : SPRITE_SIZE(32x32)),
.tileNum = 0,
.priority = 1,
.paletteNum = 0,
.affineParam = 0,
};
static const struct SpriteTemplate sSpriteTemplate_LastUsedBallWindow =
{
.tileTag = TAG_LAST_BALL_WINDOW,
.paletteTag = TAG_ABILITY_POP_UP,
.oam = &sOamData_LastUsedBall,
.anims = gDummySpriteAnimTable,
.images = NULL,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCB_LastUsedBallWin
};
#define MOVE_INFO_WINDOW_TAG 0xE722
static const struct OamData sOamData_MoveInfoWindow =
{
.y = 0,
.affineMode = 0,
.objMode = 0,
.mosaic = 0,
.bpp = 0,
.shape = SPRITE_SHAPE(32x32),
.x = 0,
.matrixNum = 0,
.size = SPRITE_SIZE(32x32),
.tileNum = 0,
.priority = 1,
.paletteNum = 0,
.affineParam = 0,
};
static const struct SpriteTemplate sSpriteTemplate_MoveInfoWindow =
{
.tileTag = MOVE_INFO_WINDOW_TAG,
.paletteTag = TAG_ABILITY_POP_UP,
.oam = &sOamData_MoveInfoWindow,
.anims = gDummySpriteAnimTable,
.images = NULL,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCB_MoveInfoWin
};
#if B_LAST_USED_BALL_BUTTON == R_BUTTON && B_LAST_USED_BALL_CYCLE == TRUE
static const u8 ALIGNED(4) sLastUsedBallWindowGfx[] = INCBIN_U8("graphics/battle_interface/last_used_ball_r_cycle.4bpp");
#elif B_LAST_USED_BALL_CYCLE == TRUE
static const u8 ALIGNED(4) sLastUsedBallWindowGfx[] = INCBIN_U8("graphics/battle_interface/last_used_ball_l_cycle.4bpp");
#elif B_LAST_USED_BALL_BUTTON == R_BUTTON
static const u8 ALIGNED(4) sLastUsedBallWindowGfx[] = INCBIN_U8("graphics/battle_interface/last_used_ball_r.4bpp");
#else
static const u8 ALIGNED(4) sLastUsedBallWindowGfx[] = INCBIN_U8("graphics/battle_interface/last_used_ball_l.4bpp");
#endif
static const struct SpriteSheet sSpriteSheet_LastUsedBallWindow =
{
sLastUsedBallWindowGfx, sizeof(sLastUsedBallWindowGfx), TAG_LAST_BALL_WINDOW
};
#if B_MOVE_DESCRIPTION_BUTTON == R_BUTTON
static const u8 sMoveInfoWindowGfx[] = INCBIN_U8("graphics/battle_interface/move_info_window_r.4bpp");
#else
static const u8 sMoveInfoWindowGfx[] = INCBIN_U8("graphics/battle_interface/move_info_window_l.4bpp");
#endif
static const struct SpriteSheet sSpriteSheet_MoveInfoWindow =
{
sMoveInfoWindowGfx, sizeof(sMoveInfoWindowGfx), MOVE_INFO_WINDOW_TAG
};
#define LAST_USED_BALL_X_F 14
#define LAST_USED_BALL_X_0 -14
#define LAST_USED_BALL_Y ((IsDoubleBattle()) ? 78 : 68)
#define LAST_USED_BALL_Y_BNC ((IsDoubleBattle()) ? 76 : 66)
#define LAST_BALL_WIN_X_F (LAST_USED_BALL_X_F - 0)
#define LAST_BALL_WIN_X_0 (LAST_USED_BALL_X_0 - 0)
#define LAST_USED_WIN_Y (LAST_USED_BALL_Y - 8)
#define sHide data[0]
#define sTimer data[1]
#define sMoving data[2]
#define sBounce data[3] // 0 = Bounce down; 1 = Bounce up
#define sState data[0]
#define sSameBall data[1]
bool32 CanThrowLastUsedBall(void)
{
if (B_LAST_USED_BALL == FALSE)
return FALSE;
if (!CanThrowBall())
return FALSE;
if (gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FRONTIER))
return FALSE;
if (!CheckBagHasItem(gBallToDisplay, 1))
return FALSE;
return TRUE;
}
void TryAddLastUsedBallItemSprites(void)
{
if (B_LAST_USED_BALL == FALSE)
return;
if (gLastThrownBall == 0
|| (gLastThrownBall != 0 && !CheckBagHasItem(gLastThrownBall, 1)))
{
// we're out of the last used ball, so just set it to the first ball in the bag
u16 firstBall;
// we have to compact the bag first bc it is typically only compacted when you open it
CompactItemsInBagPocket(POCKET_POKE_BALLS);
firstBall = GetBagItemId(POCKET_POKE_BALLS, 0);
if (firstBall > ITEM_NONE)
gBallToDisplay = firstBall;
}
if (!CanThrowLastUsedBall())
return;
// ball
if (gBattleStruct->ballSpriteIds[0] == MAX_SPRITES)
{
gBattleStruct->ballSpriteIds[0] = AddItemIconSprite(102, 102, gBallToDisplay);
gSprites[gBattleStruct->ballSpriteIds[0]].x = LAST_USED_BALL_X_0;
gSprites[gBattleStruct->ballSpriteIds[0]].y = LAST_USED_BALL_Y;
gSprites[gBattleStruct->ballSpriteIds[0]].sHide = FALSE;
gLastUsedBallMenuPresent = TRUE;
gSprites[gBattleStruct->ballSpriteIds[0]].callback = SpriteCB_LastUsedBall;
}
// window
LoadSpritePalette(&sSpritePalette_AbilityPopUp);
if (GetSpriteTileStartByTag(TAG_LAST_BALL_WINDOW) == 0xFFFF)
LoadSpriteSheet(&sSpriteSheet_LastUsedBallWindow);
if (gBattleStruct->ballSpriteIds[1] == MAX_SPRITES)
{
gBattleStruct->ballSpriteIds[1] = CreateSprite(&sSpriteTemplate_LastUsedBallWindow,
LAST_BALL_WIN_X_0,
LAST_USED_WIN_Y, 5);
gSprites[gBattleStruct->ballSpriteIds[1]].sHide = FALSE;
gSprites[gBattleStruct->moveInfoSpriteId].sHide = TRUE;
gLastUsedBallMenuPresent = TRUE;
}
if (B_LAST_USED_BALL_CYCLE == TRUE)
ArrowsChangeColorLastBallCycle(0); //Default the arrows to be invisible
}
static void DestroyLastUsedBallWinGfx(struct Sprite *sprite)
{
FreeSpriteTilesByTag(TAG_LAST_BALL_WINDOW);
FreeSpritePaletteByTag(TAG_ABILITY_POP_UP);
DestroySprite(sprite);
gBattleStruct->ballSpriteIds[1] = MAX_SPRITES;
}
static void DestroyLastUsedBallGfx(struct Sprite *sprite)
{
FreeSpriteTilesByTag(102);
FreeSpritePaletteByTag(102);
DestroySprite(sprite);
gBattleStruct->ballSpriteIds[0] = MAX_SPRITES;
}
void TryToAddMoveInfoWindow(void)
{
if (!B_SHOW_MOVE_DESCRIPTION)
return;
LoadSpritePalette(&sSpritePalette_AbilityPopUp);
if (GetSpriteTileStartByTag(MOVE_INFO_WINDOW_TAG) == 0xFFFF)
LoadSpriteSheet(&sSpriteSheet_MoveInfoWindow);
if (gBattleStruct->moveInfoSpriteId == MAX_SPRITES)
{
gBattleStruct->moveInfoSpriteId = CreateSprite(&sSpriteTemplate_MoveInfoWindow, LAST_BALL_WIN_X_0, LAST_USED_WIN_Y + 32, 6);
gSprites[gBattleStruct->moveInfoSpriteId].sHide = FALSE;
}
}
void TryToHideMoveInfoWindow(void)
{
gSprites[gBattleStruct->moveInfoSpriteId].sHide = TRUE;
}
static void DestroyMoveInfoWinGfx(struct Sprite *sprite)
{
FreeSpriteTilesByTag(MOVE_INFO_WINDOW_TAG);
FreeSpritePaletteByTag(TAG_ABILITY_POP_UP);
DestroySprite(sprite);
gBattleStruct->moveInfoSpriteId = MAX_SPRITES;
}
static void SpriteCB_LastUsedBallWin(struct Sprite *sprite)
{
if (sprite->sHide)
{
if (sprite->x != LAST_BALL_WIN_X_0)
sprite->x--;
if (sprite->x == LAST_BALL_WIN_X_0)
DestroyLastUsedBallWinGfx(sprite);
}
else
{
if (sprite->x != LAST_BALL_WIN_X_F)
sprite->x++;
}
}
static void SpriteCB_LastUsedBall(struct Sprite *sprite)
{
if (sprite->sHide)
{
if (sprite->y < LAST_USED_BALL_Y) // Used to recover from an incomplete bounce before hiding the window
sprite->y++;
if (sprite->x != LAST_USED_BALL_X_0)
sprite->x--;
if (sprite->x == LAST_USED_BALL_X_0)
DestroyLastUsedBallGfx(sprite);
}
else
{
if (sprite->x != LAST_USED_BALL_X_F)
sprite->x++;
}
}
static void SpriteCB_MoveInfoWin(struct Sprite *sprite)
{
if (sprite->sHide)
{
if (sprite->x != LAST_BALL_WIN_X_0)
sprite->x--;
if (sprite->x == LAST_BALL_WIN_X_0)
DestroyMoveInfoWinGfx(sprite);
}
else
{
if (sprite->x != LAST_BALL_WIN_X_F)
sprite->x++;
}
}
static void TryHideOrRestoreLastUsedBall(u8 caseId)
{
if (B_LAST_USED_BALL == FALSE)
return;
if (gBattleStruct->ballSpriteIds[0] == MAX_SPRITES)
return;
switch (caseId)
{
case 0: // hide
if (gBattleStruct->ballSpriteIds[0] != MAX_SPRITES)
gSprites[gBattleStruct->ballSpriteIds[0]].sHide = TRUE;
if (gBattleStruct->ballSpriteIds[1] != MAX_SPRITES)
gSprites[gBattleStruct->ballSpriteIds[1]].sHide = TRUE;
gLastUsedBallMenuPresent = FALSE;
break;
case 1: // restore
if (gBattleStruct->ballSpriteIds[0] != MAX_SPRITES)
gSprites[gBattleStruct->ballSpriteIds[0]].sHide = FALSE;
if (gBattleStruct->ballSpriteIds[1] != MAX_SPRITES)
gSprites[gBattleStruct->ballSpriteIds[1]].sHide = FALSE;
gLastUsedBallMenuPresent = TRUE;
break;
}
if (B_LAST_USED_BALL_CYCLE == TRUE)
ArrowsChangeColorLastBallCycle(0); //Default the arrows to be invisible
}
void TryHideLastUsedBall(void)
{
if (B_LAST_USED_BALL == TRUE)
TryHideOrRestoreLastUsedBall(0);
}
void TryRestoreLastUsedBall(void)
{
if (B_LAST_USED_BALL == FALSE)
return;
if (gBattleStruct->ballSpriteIds[0] != MAX_SPRITES)
TryHideOrRestoreLastUsedBall(1);
else
TryAddLastUsedBallItemSprites();
}
static void SpriteCB_LastUsedBallBounce(struct Sprite *sprite)
{
if ((sprite->sTimer++ % 4) != 0) // Change the image every 4 frame
return;
if (sprite->sBounce)
{
if (sprite->y > LAST_USED_BALL_Y_BNC)
sprite->y--;
else
sprite->sMoving = FALSE;
}
else
{
if (sprite->y < LAST_USED_BALL_Y)
sprite->y++;
else
sprite->sMoving = FALSE;
}
}
static void Task_BounceBall(u8 taskId)
{
struct Sprite *sprite = &gSprites[gBattleStruct->ballSpriteIds[0]];
struct Task *task = &gTasks[taskId];
switch(task->sState)
{
case 0: // Bounce up
sprite->sBounce = TRUE;
sprite->sMoving = TRUE;
sprite->callback = SpriteCB_LastUsedBallBounce;
if (task->sSameBall)
task->sState = 3;
else
task->sState = 1;
break;
case 1: // Destroy Icon
if (!sprite->sMoving)
{
DestroyLastUsedBallGfx(sprite);
task->sState++;
} // Passthrough
case 2: //Create New Icon
if (!sprite->inUse)
{
gBattleStruct->ballSpriteIds[0] = AddItemIconSprite(102, 102, gBallToDisplay);
gSprites[gBattleStruct->ballSpriteIds[0]].x = LAST_USED_BALL_X_F;
gSprites[gBattleStruct->ballSpriteIds[0]].y = LAST_USED_BALL_Y_BNC;
task->sState++;
} // Fallthrough
case 3: // Bounce Down
if (!sprite->sMoving)
{
sprite->sBounce = FALSE;
sprite->sMoving = TRUE;
sprite->callback = SpriteCB_LastUsedBallBounce; //Show and bounce down
task->sState++;
}
break;
case 4: // Destroy Task
if(!sprite->sMoving)
{
sprite->callback = SpriteCB_LastUsedBall;
DestroyTask(taskId);
}
}
if (!gLastUsedBallMenuPresent)
{
// Used to check if the R button was released before the animation was complete
sprite->callback = SpriteCB_LastUsedBall;
DestroyTask(taskId);
}
}
void SwapBallToDisplay(bool32 sameBall)
{
u8 taskId;
taskId = CreateTask(Task_BounceBall, 10);
gTasks[taskId].sSameBall = sameBall;
}
void ArrowsChangeColorLastBallCycle(bool32 showArrows)
{
#if B_LAST_USED_BALL == TRUE && B_LAST_USED_BALL_CYCLE == TRUE
u16 paletteNum = 16 + gSprites[gBattleStruct->ballSpriteIds[1]].oam.paletteNum;
struct PlttData *defaultPlttArrow;
struct PlttData *defaultPlttOutline;
struct PlttData *pltArrow;
struct PlttData *pltOutline;
if (gBattleStruct->ballSpriteIds[1] == MAX_SPRITES)
return;
paletteNum *= 16;
pltArrow = (struct PlttData *)&gPlttBufferFaded[paletteNum + 9]; // Arrow color is in idx 9
pltOutline = (struct PlttData *)&gPlttBufferFaded[paletteNum + 8]; // Arrow outline is in idx 8
if (!showArrows) //Make invisible
{
defaultPlttArrow = (struct PlttData *)&gPlttBufferFaded[paletteNum + 13]; // Background color is idx 13
pltArrow->r = defaultPlttArrow->r;
pltArrow->g = defaultPlttArrow->g;
pltArrow->b = defaultPlttArrow->b;
pltOutline->r = defaultPlttArrow->r;
pltOutline->g = defaultPlttArrow->g;
pltOutline->b = defaultPlttArrow->b;
}
else // Make gray
{
defaultPlttArrow = (struct PlttData *)&gPlttBufferFaded[paletteNum + 11]; // Grey color is idx 11
defaultPlttOutline = (struct PlttData *)&gPlttBufferFaded[paletteNum + 10]; //Light grey color for outline is idx 10
pltArrow->r = defaultPlttArrow->r;
pltArrow->g = defaultPlttArrow->g;
pltArrow->b = defaultPlttArrow->b;
pltOutline->r = defaultPlttOutline->r;
pltOutline->g = defaultPlttOutline->g;
pltOutline->b = defaultPlttOutline->b;
}
#endif
}
void CategoryIcons_LoadSpritesGfx(void)
{
LoadCompressedSpriteSheet(&gSpriteSheet_CategoryIcons);
LoadSpritePalette(&gSpritePal_CategoryIcons);
}