sovereignx/src/battle_gimmick.c
2024-09-03 14:39:50 -04:00

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