402 lines
14 KiB
C
402 lines
14 KiB
C
#include "global.h"
|
|
#include "battle.h"
|
|
#include "battle_anim.h"
|
|
#include "battle_controllers.h"
|
|
#include "battle_interface.h"
|
|
#include "battle_gimmick.h"
|
|
#include "battle_z_move.h"
|
|
#include "battle_setup.h"
|
|
#include "battle_util.h"
|
|
#include "item.h"
|
|
#include "palette.h"
|
|
#include "pokemon.h"
|
|
#include "sprite.h"
|
|
#include "util.h"
|
|
#include "test_runner.h"
|
|
|
|
#include "data/gimmicks.h"
|
|
|
|
// Populates gBattleStruct->gimmick.usableGimmick for each battler.
|
|
void AssignUsableGimmicks(void)
|
|
{
|
|
u32 battler, gimmick;
|
|
for (battler = 0; battler < gBattlersCount; ++battler)
|
|
{
|
|
gBattleStruct->gimmick.usableGimmick[battler] = GIMMICK_NONE;
|
|
for (gimmick = 0; gimmick < GIMMICKS_COUNT; ++gimmick)
|
|
{
|
|
if (CanActivateGimmick(battler, gimmick))
|
|
{
|
|
gBattleStruct->gimmick.usableGimmick[battler] = gimmick;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Returns whether a battler is able to use a gimmick. Checks consumption and gimmick specific functions.
|
|
bool32 CanActivateGimmick(u32 battler, enum Gimmick gimmick)
|
|
{
|
|
return gGimmicksInfo[gimmick].CanActivate != NULL && gGimmicksInfo[gimmick].CanActivate(battler);
|
|
}
|
|
|
|
// Returns whether the player has a gimmick selected while in the move selection menu.
|
|
bool32 IsGimmickSelected(u32 battler, enum Gimmick gimmick)
|
|
{
|
|
// There's no player select in tests, but some gimmicks need to test choice before they are fully activated.
|
|
if (TESTING)
|
|
return (gBattleStruct->gimmick.toActivate & (1u << battler)) && gBattleStruct->gimmick.usableGimmick[battler] == gimmick;
|
|
else
|
|
return gBattleStruct->gimmick.usableGimmick[battler] == gimmick && gBattleStruct->gimmick.playerSelect;
|
|
}
|
|
|
|
// Sets a battler as having a gimmick active using their party index.
|
|
void SetActiveGimmick(u32 battler, enum Gimmick gimmick)
|
|
{
|
|
gBattleStruct->gimmick.activeGimmick[GetBattlerSide(battler)][gBattlerPartyIndexes[battler]] = gimmick;
|
|
}
|
|
|
|
// Returns a battler's active gimmick, if any.
|
|
enum Gimmick GetActiveGimmick(u32 battler)
|
|
{
|
|
return gBattleStruct->gimmick.activeGimmick[GetBattlerSide(battler)][gBattlerPartyIndexes[battler]];
|
|
}
|
|
|
|
// Returns whether a trainer mon is intended to use an unrestrictive gimmick via .useGimmick (i.e Tera).
|
|
bool32 ShouldTrainerBattlerUseGimmick(u32 battler, enum Gimmick gimmick)
|
|
{
|
|
// There are no trainer party settings in battles, but the AI needs to know which gimmick to use.
|
|
if (TESTING)
|
|
{
|
|
return gimmick == TestRunner_Battle_GetChosenGimmick(GetBattlerSide(battler), gBattlerPartyIndexes[battler]);
|
|
}
|
|
// The player can bypass these checks because they can choose through the controller.
|
|
else if (GetBattlerSide(battler) == B_SIDE_PLAYER
|
|
&& !((gBattleTypeFlags & BATTLE_TYPE_MULTI) && battler == B_POSITION_PLAYER_RIGHT))
|
|
{
|
|
return TRUE;
|
|
}
|
|
// Check the trainer party data to see if a gimmick is intended.
|
|
else
|
|
{
|
|
bool32 isSecondTrainer = (GetBattlerPosition(battler) == B_POSITION_OPPONENT_RIGHT) && (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) && !BATTLE_TWO_VS_ONE_OPPONENT;
|
|
u16 trainerId = isSecondTrainer ? gTrainerBattleOpponent_B : gTrainerBattleOpponent_A;
|
|
const struct TrainerMon *mon = &GetTrainerPartyFromId(trainerId)[isSecondTrainer ? gBattlerPartyIndexes[battler] - MULTI_PARTY_SIZE : gBattlerPartyIndexes[battler]];
|
|
|
|
if (gimmick == GIMMICK_TERA && mon->teraType != TYPE_NONE)
|
|
return TRUE;
|
|
if (gimmick == GIMMICK_DYNAMAX && mon->shouldUseDynamax)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
// Returns whether a trainer has used a gimmick during a battle.
|
|
bool32 HasTrainerUsedGimmick(u32 battler, enum Gimmick gimmick)
|
|
{
|
|
// Check whether partner battler has used gimmick or plans to during turn.
|
|
if (IsDoubleBattle()
|
|
&& IsPartnerMonFromSameTrainer(battler)
|
|
&& (gBattleStruct->gimmick.activated[BATTLE_PARTNER(battler)][gimmick]
|
|
|| ((gBattleStruct->gimmick.toActivate & (1u << BATTLE_PARTNER(battler))
|
|
&& gBattleStruct->gimmick.usableGimmick[BATTLE_PARTNER(battler)] == gimmick))))
|
|
{
|
|
return TRUE;
|
|
}
|
|
// Otherwise, return whether current battler has used gimmick.
|
|
else
|
|
{
|
|
return gBattleStruct->gimmick.activated[battler][gimmick];
|
|
}
|
|
}
|
|
|
|
// Sets a gimmick as used by a trainer with checks for Multi Battles.
|
|
void SetGimmickAsActivated(u32 battler, enum Gimmick gimmick)
|
|
{
|
|
gBattleStruct->gimmick.activated[battler][gimmick] = TRUE;
|
|
if (IsDoubleBattle() && IsPartnerMonFromSameTrainer(battler))
|
|
gBattleStruct->gimmick.activated[BATTLE_PARTNER(battler)][gimmick] = TRUE;
|
|
}
|
|
|
|
#define SINGLES_GIMMICK_TRIGGER_POS_X_OPTIMAL (30)
|
|
#define SINGLES_GIMMICK_TRIGGER_POS_X_PRIORITY (31)
|
|
#define SINGLES_GIMMICK_TRIGGER_POS_X_SLIDE (15)
|
|
#define SINGLES_GIMMICK_TRIGGER_POS_Y_DIFF (-11)
|
|
|
|
#define DOUBLES_GIMMICK_TRIGGER_POS_X_OPTIMAL (30)
|
|
#define DOUBLES_GIMMICK_TRIGGER_POS_X_PRIORITY (31)
|
|
#define DOUBLES_GIMMICK_TRIGGER_POS_X_SLIDE (15)
|
|
#define DOUBLES_GIMMICK_TRIGGER_POS_Y_DIFF (-4)
|
|
|
|
#define tBattler data[0]
|
|
#define tHide data[1]
|
|
|
|
void ChangeGimmickTriggerSprite(u32 spriteId, u32 animId)
|
|
{
|
|
StartSpriteAnim(&gSprites[spriteId], animId);
|
|
}
|
|
|
|
void CreateGimmickTriggerSprite(u32 battler)
|
|
{
|
|
const struct GimmickInfo * gimmick = &gGimmicksInfo[gBattleStruct->gimmick.usableGimmick[battler]];
|
|
|
|
// Exit if there shouldn't be a sprite produced.
|
|
if (GetBattlerSide(battler) == B_SIDE_OPPONENT
|
|
|| gBattleStruct->gimmick.usableGimmick[battler] == GIMMICK_NONE
|
|
|| gimmick->triggerSheet == NULL
|
|
|| HasTrainerUsedGimmick(battler, gBattleStruct->gimmick.usableGimmick[battler]))
|
|
{
|
|
return;
|
|
}
|
|
|
|
LoadSpritePalette(gimmick->triggerPal);
|
|
if (GetSpriteTileStartByTag(TAG_GIMMICK_TRIGGER_TILE) == 0xFFFF)
|
|
LoadSpriteSheet(gimmick->triggerSheet);
|
|
|
|
if (gBattleStruct->gimmick.triggerSpriteId == 0xFF)
|
|
{
|
|
if (IsDoubleBattle())
|
|
gBattleStruct->gimmick.triggerSpriteId = CreateSprite(gimmick->triggerTemplate,
|
|
gSprites[gHealthboxSpriteIds[battler]].x - DOUBLES_GIMMICK_TRIGGER_POS_X_SLIDE,
|
|
gSprites[gHealthboxSpriteIds[battler]].y - DOUBLES_GIMMICK_TRIGGER_POS_Y_DIFF, 0);
|
|
else
|
|
gBattleStruct->gimmick.triggerSpriteId = CreateSprite(gimmick->triggerTemplate,
|
|
gSprites[gHealthboxSpriteIds[battler]].x - SINGLES_GIMMICK_TRIGGER_POS_X_SLIDE,
|
|
gSprites[gHealthboxSpriteIds[battler]].y - SINGLES_GIMMICK_TRIGGER_POS_Y_DIFF, 0);
|
|
}
|
|
|
|
gSprites[gBattleStruct->gimmick.triggerSpriteId].tBattler = battler;
|
|
gSprites[gBattleStruct->gimmick.triggerSpriteId].tHide = FALSE;
|
|
|
|
ChangeGimmickTriggerSprite(gBattleStruct->gimmick.triggerSpriteId, 0);
|
|
}
|
|
|
|
bool32 IsGimmickTriggerSpriteActive(void)
|
|
{
|
|
if (GetSpriteTileStartByTag(TAG_GIMMICK_TRIGGER_TILE) == 0xFFFF)
|
|
return FALSE;
|
|
else if (IndexOfSpritePaletteTag(TAG_GIMMICK_TRIGGER_PAL) != 0xFF)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
void HideGimmickTriggerSprite(void)
|
|
{
|
|
if (gBattleStruct->gimmick.triggerSpriteId != 0xFF)
|
|
{
|
|
ChangeGimmickTriggerSprite(gBattleStruct->gimmick.triggerSpriteId, 0);
|
|
gSprites[gBattleStruct->gimmick.triggerSpriteId].tHide = TRUE;
|
|
}
|
|
}
|
|
|
|
void DestroyGimmickTriggerSprite(void)
|
|
{
|
|
FreeSpritePaletteByTag(TAG_GIMMICK_TRIGGER_PAL);
|
|
FreeSpriteTilesByTag(TAG_GIMMICK_TRIGGER_TILE);
|
|
if (gBattleStruct->gimmick.triggerSpriteId != 0xFF)
|
|
DestroySprite(&gSprites[gBattleStruct->gimmick.triggerSpriteId]);
|
|
gBattleStruct->gimmick.triggerSpriteId = 0xFF;
|
|
}
|
|
|
|
static void SpriteCb_GimmickTrigger(struct Sprite *sprite)
|
|
{
|
|
s32 xSlide, xPriority, xOptimal;
|
|
s32 yDiff;
|
|
|
|
if (IsDoubleBattle())
|
|
{
|
|
xSlide = DOUBLES_GIMMICK_TRIGGER_POS_X_SLIDE;
|
|
xPriority = DOUBLES_GIMMICK_TRIGGER_POS_X_PRIORITY;
|
|
xOptimal = DOUBLES_GIMMICK_TRIGGER_POS_X_OPTIMAL;
|
|
yDiff = DOUBLES_GIMMICK_TRIGGER_POS_Y_DIFF;
|
|
}
|
|
else
|
|
{
|
|
xSlide = SINGLES_GIMMICK_TRIGGER_POS_X_SLIDE;
|
|
xPriority = SINGLES_GIMMICK_TRIGGER_POS_X_PRIORITY;
|
|
xOptimal = SINGLES_GIMMICK_TRIGGER_POS_X_OPTIMAL;
|
|
yDiff = SINGLES_GIMMICK_TRIGGER_POS_Y_DIFF;
|
|
}
|
|
|
|
if (sprite->tHide)
|
|
{
|
|
if (sprite->x != gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xSlide)
|
|
sprite->x++;
|
|
|
|
if (sprite->x >= gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xPriority)
|
|
sprite->oam.priority = 2;
|
|
else
|
|
sprite->oam.priority = 1;
|
|
|
|
sprite->y = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y - yDiff;
|
|
sprite->y2 = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y2 - yDiff;
|
|
if (sprite->x == gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xSlide)
|
|
DestroyGimmickTriggerSprite();
|
|
}
|
|
else
|
|
{
|
|
if (sprite->x != gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xOptimal)
|
|
sprite->x--;
|
|
|
|
if (sprite->x >= gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xPriority)
|
|
sprite->oam.priority = 2;
|
|
else
|
|
sprite->oam.priority = 1;
|
|
|
|
sprite->y = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y - yDiff;
|
|
sprite->y2 = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y2 - yDiff;
|
|
}
|
|
}
|
|
|
|
#undef tBattler
|
|
#undef tHide
|
|
|
|
// for sprite data fields
|
|
#define tBattler data[0]
|
|
#define tPosX data[2]
|
|
#define tLevelXDelta data[3] // X position depends whether level has 3, 2 or 1 digit
|
|
|
|
// data fields for healthboxMain
|
|
// oam.affineParam holds healthboxRight spriteId
|
|
#define hMain_Battler data[6]
|
|
|
|
void LoadIndicatorSpritesGfx(void)
|
|
{
|
|
u32 gimmick;
|
|
for (gimmick = 0; gimmick < GIMMICKS_COUNT; ++gimmick)
|
|
{
|
|
if (gimmick == GIMMICK_TERA) // special case
|
|
LoadSpriteSheets(sTeraIndicatorSpriteSheets);
|
|
else if (gGimmicksInfo[gimmick].indicatorSheet != NULL)
|
|
LoadSpriteSheet(gGimmicksInfo[gimmick].indicatorSheet);
|
|
|
|
if (gGimmicksInfo[gimmick].indicatorPal != NULL)
|
|
LoadSpritePalette(gGimmicksInfo[gimmick].indicatorPal);
|
|
}
|
|
// Primal reversion graphics aren't loaded as part of gimmick data
|
|
LoadSpriteSheet(&sSpriteSheet_AlphaIndicator);
|
|
LoadSpriteSheet(&sSpriteSheet_OmegaIndicator);
|
|
}
|
|
|
|
static void SpriteCb_GimmickIndicator(struct Sprite *sprite)
|
|
{
|
|
u32 battler = sprite->tBattler;
|
|
|
|
sprite->x = gSprites[gHealthboxSpriteIds[battler]].x + sprite->tPosX + sprite->tLevelXDelta;
|
|
sprite->x2 = gSprites[gHealthboxSpriteIds[battler]].x2;
|
|
sprite->y2 = gSprites[gHealthboxSpriteIds[battler]].y2;
|
|
}
|
|
|
|
static inline u32 GetIndicatorSpriteId(u32 healthboxId)
|
|
{
|
|
return gBattleStruct->gimmick.indicatorSpriteId[gSprites[healthboxId].hMain_Battler];
|
|
}
|
|
|
|
u32 GetIndicatorTileTag(u32 battler)
|
|
{
|
|
u32 gimmick = GetActiveGimmick(battler);
|
|
|
|
if (IsBattlerPrimalReverted(battler))
|
|
{
|
|
if (gBattleMons[battler].species == SPECIES_GROUDON_PRIMAL)
|
|
return TAG_OMEGA_INDICATOR_TILE;
|
|
else
|
|
return TAG_ALPHA_INDICATOR_TILE;
|
|
}
|
|
else if (gimmick == GIMMICK_TERA) // special case
|
|
{
|
|
return sTeraIndicatorSpriteSheets[GetBattlerTeraType(battler)].tag;
|
|
}
|
|
else if (gGimmicksInfo[gimmick].indicatorSheet != NULL)
|
|
{
|
|
return gGimmicksInfo[gimmick].indicatorSheet->tag;
|
|
}
|
|
else
|
|
{
|
|
return TAG_NONE;
|
|
}
|
|
}
|
|
|
|
u32 GetIndicatorPalTag(u32 battler)
|
|
{
|
|
u32 gimmick = GetActiveGimmick(battler);
|
|
if (IsBattlerPrimalReverted(battler))
|
|
return TAG_MISC_INDICATOR_PAL;
|
|
else if (gGimmicksInfo[gimmick].indicatorPal != NULL)
|
|
return gGimmicksInfo[gimmick].indicatorPal->tag;
|
|
else
|
|
return TAG_NONE;
|
|
}
|
|
|
|
void UpdateIndicatorVisibilityAndType(u32 healthboxId, bool32 invisible)
|
|
{
|
|
u32 battler = gSprites[healthboxId].hMain_Battler;
|
|
u32 tileTag = GetIndicatorTileTag(battler);
|
|
u32 palTag = GetIndicatorPalTag(battler);
|
|
struct Sprite *sprite = &gSprites[GetIndicatorSpriteId(healthboxId)];
|
|
|
|
if (GetIndicatorSpriteId(healthboxId) == 0) // safari zone means the player doesn't have an indicator sprite id
|
|
return;
|
|
|
|
if (tileTag != TAG_NONE && palTag != TAG_NONE)
|
|
{
|
|
sprite->oam.tileNum = GetSpriteTileStartByTag(tileTag);
|
|
sprite->oam.paletteNum = IndexOfSpritePaletteTag(palTag);
|
|
sprite->invisible = invisible;
|
|
}
|
|
else // in case of error
|
|
{
|
|
sprite->invisible = TRUE;
|
|
}
|
|
}
|
|
|
|
void UpdateIndicatorOamPriority(u32 healthboxId, u32 oamPriority)
|
|
{
|
|
gSprites[GetIndicatorSpriteId(healthboxId)].oam.priority = oamPriority;
|
|
}
|
|
|
|
void UpdateIndicatorLevelData(u32 healthboxId, u32 level)
|
|
{
|
|
s32 xDelta = 0;
|
|
|
|
if (level >= 100)
|
|
xDelta -= 4;
|
|
else if (level < 10)
|
|
xDelta += 5;
|
|
|
|
gSprites[GetIndicatorSpriteId(healthboxId)].tLevelXDelta = xDelta;
|
|
}
|
|
|
|
static const s8 sIndicatorPositions[][2] =
|
|
{
|
|
[B_POSITION_PLAYER_LEFT] = {53, -9},
|
|
[B_POSITION_OPPONENT_LEFT] = {44, -9},
|
|
[B_POSITION_PLAYER_RIGHT] = {52, -9},
|
|
[B_POSITION_OPPONENT_RIGHT] = {44, -9},
|
|
};
|
|
|
|
void CreateIndicatorSprite(u32 battler)
|
|
{
|
|
u32 position, spriteId;
|
|
s16 xHealthbox = 0, x = 0, y = 0;
|
|
|
|
position = GetBattlerPosition(battler);
|
|
GetBattlerHealthboxCoords(battler, &xHealthbox, &y);
|
|
|
|
x = sIndicatorPositions[position][0];
|
|
y += sIndicatorPositions[position][1];
|
|
|
|
spriteId = CreateSpriteAtEnd(&sSpriteTemplate_GimmickIndicator, 0, y, 0);
|
|
gBattleStruct->gimmick.indicatorSpriteId[battler] = spriteId;
|
|
gSprites[spriteId].tBattler = battler;
|
|
gSprites[spriteId].tPosX = x;
|
|
gSprites[spriteId].invisible = FALSE;
|
|
}
|
|
|
|
#undef tBattler
|
|
#undef tPosX
|
|
#undef tLevelXDelta
|
|
|
|
#undef hMain_Battler
|