414 lines
13 KiB
C
414 lines
13 KiB
C
#include "global.h"
|
|
#include "battle.h"
|
|
#include "battle_anim.h"
|
|
#include "battle_controllers.h"
|
|
#include "battle_message.h"
|
|
#include "battle_setup.h"
|
|
#include "battle_tower.h"
|
|
#include "battle_z_move.h"
|
|
#include "data.h"
|
|
#include "event_data.h"
|
|
#include "frontier_util.h"
|
|
#include "graphics.h"
|
|
#include "international_string_util.h"
|
|
#include "item.h"
|
|
#include "link.h"
|
|
#include "menu.h"
|
|
#include "palette.h"
|
|
#include "recorded_battle.h"
|
|
#include "string_util.h"
|
|
#include "strings.h"
|
|
#include "test_runner.h"
|
|
#include "text.h"
|
|
#include "trainer_hill.h"
|
|
#include "window.h"
|
|
#include "line_break.h"
|
|
#include "constants/abilities.h"
|
|
#include "constants/battle_dome.h"
|
|
#include "constants/battle_string_ids.h"
|
|
#include "constants/frontier_util.h"
|
|
#include "constants/items.h"
|
|
#include "constants/moves.h"
|
|
#include "constants/opponents.h"
|
|
#include "constants/species.h"
|
|
#include "constants/trainers.h"
|
|
#include "constants/trainer_hill.h"
|
|
#include "constants/weather.h"
|
|
#include "trainer_slide.h"
|
|
#include "battle_message.h"
|
|
|
|
static u32 BattlerHPPercentage(u32 battler, u32 operation, u32 threshold);
|
|
static u32 GetEnemyMonCount(u32 firstId, u32 lastId, bool32 onlyAlive);
|
|
static bool32 DoesTrainerHaveSlideMessage(enum DifficultyLevel difficulty, u32 trainerId, u32 slideId);
|
|
static bool32 ShouldRunTrainerSlidePlayerLandsFirstCriticalHit(enum TrainerSlideType slideId);
|
|
static bool32 ShouldRunTrainerSlideEnemyLandsFirstCriticalHit(enum TrainerSlideType slideId);
|
|
static bool32 ShouldRunTrainerSlidePlayerLandsFirstSuperEffectiveHit(u32 battler, enum TrainerSlideType slideId);
|
|
static bool32 ShouldRunTrainerSlidePlayerLandsFirstSTABMove(u32 firstId, u32 lastId, enum TrainerSlideType slideId);
|
|
static bool32 ShouldRunTrainerSlidePlayerLandsFirstDown(u32 firstId, u32 lastId);
|
|
static bool32 ShouldRunTrainerSlideEnemyMonUnaffected(u32 firstId, u32 lastId, enum TrainerSlideType slideId);
|
|
static bool32 ShouldRunTrainerSlideLastSwitchIn(u32 battler);
|
|
static bool32 ShouldRunTrainerSlideLastHalfHP(u32 firstId, u32 lastId, u32 battler);
|
|
static bool32 ShouldRunTrainerSlideLastLowHp(u32 firstId, u32 lastId, u32 battler);
|
|
static void SetTrainerSlideParamters(u32 battler, u32* firstId, u32* lastId, u32* trainerId, u32* retValue);
|
|
static bool32 IsSlideInitalizedOrPlayed(enum TrainerSlideType slideId);
|
|
|
|
static const u8* const sTrainerSlides[DIFFICULTY_COUNT][TRAINERS_COUNT][TRAINER_SLIDE_COUNT] =
|
|
{
|
|
[DIFFICULTY_NORMAL] =
|
|
{
|
|
},
|
|
};
|
|
|
|
static const u8* const sFrontierTrainerSlides[DIFFICULTY_COUNT][FRONTIER_TRAINERS_COUNT][TRAINER_SLIDE_COUNT] =
|
|
{
|
|
[DIFFICULTY_NORMAL] =
|
|
{
|
|
},
|
|
};
|
|
|
|
static const u8* const sTestTrainerSlides[DIFFICULTY_COUNT][TRAINERS_COUNT][TRAINER_SLIDE_COUNT] =
|
|
{
|
|
#include "../test/battle/trainer_slides.h"
|
|
};
|
|
|
|
static u32 BattlerHPPercentage(u32 battler, u32 operation, u32 threshold)
|
|
{
|
|
switch (operation)
|
|
{
|
|
case LESS_THAN:
|
|
return gBattleMons[battler].hp < (gBattleMons[battler].maxHP / threshold);
|
|
case EQUAL:
|
|
return gBattleMons[battler].hp == (gBattleMons[battler].maxHP / threshold);
|
|
case GREATER_THAN:
|
|
return gBattleMons[battler].hp > (gBattleMons[battler].maxHP / threshold);
|
|
case LESS_THAN_OR_EQUAL:
|
|
return gBattleMons[battler].hp <= (gBattleMons[battler].maxHP / threshold);
|
|
case GREATER_THAN_OR_EQUAL:
|
|
return gBattleMons[battler].hp >= (gBattleMons[battler].maxHP / threshold);
|
|
case NOT_EQUAL:
|
|
default:
|
|
return gBattleMons[battler].hp != (gBattleMons[battler].maxHP / threshold);
|
|
}
|
|
}
|
|
|
|
static u32 GetEnemyMonCount(u32 firstId, u32 lastId, bool32 onlyAlive)
|
|
{
|
|
u32 i, count = 0;
|
|
|
|
for (i = firstId; i < lastId; i++)
|
|
{
|
|
u32 species = GetMonData(&gEnemyParty[i], MON_DATA_SPECIES_OR_EGG, NULL);
|
|
if (species != SPECIES_NONE
|
|
&& species != SPECIES_EGG
|
|
&& (!onlyAlive || GetMonData(&gEnemyParty[i], MON_DATA_HP, NULL)))
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static const u8* const *GetTrainerSlideArray(enum DifficultyLevel difficulty, u32 trainerId, u32 slideId)
|
|
{
|
|
if (gBattleTypeFlags & BATTLE_TYPE_FRONTIER)
|
|
return sFrontierTrainerSlides[difficulty][trainerId];
|
|
else if (TESTING)
|
|
return sTestTrainerSlides[difficulty][trainerId];
|
|
else
|
|
return sTrainerSlides[difficulty][trainerId];
|
|
}
|
|
|
|
static bool32 DoesTrainerHaveSlideMessage(enum DifficultyLevel difficulty, u32 trainerId, u32 slideId)
|
|
{
|
|
const u8* const *trainerSlides = GetTrainerSlideArray(difficulty, trainerId, slideId);
|
|
const u8* const *trainerSlidesNormal = GetTrainerSlideArray(DIFFICULTY_NORMAL, trainerId, slideId);
|
|
|
|
if (trainerSlides[slideId] == NULL)
|
|
return (trainerSlidesNormal[slideId] != NULL);
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
void SetTrainerSlideMessage(enum DifficultyLevel difficulty, u32 trainerId, u32 slideId)
|
|
{
|
|
const u8* const *trainerSlides = GetTrainerSlideArray(difficulty, trainerId, slideId);
|
|
const u8* const *trainerSlidesNormal = GetTrainerSlideArray(DIFFICULTY_NORMAL, trainerId, slideId);
|
|
|
|
if (trainerSlides[slideId] != NULL)
|
|
gBattleStruct->trainerSlideMsg = trainerSlides[slideId];
|
|
else
|
|
gBattleStruct->trainerSlideMsg = trainerSlidesNormal[slideId];
|
|
}
|
|
|
|
static bool32 ShouldRunTrainerSlidePlayerLandsFirstCriticalHit(enum TrainerSlideType slideId)
|
|
{
|
|
return IsTrainerSlideInitialized(slideId);
|
|
}
|
|
|
|
static bool32 ShouldRunTrainerSlideEnemyLandsFirstCriticalHit(enum TrainerSlideType slideId)
|
|
{
|
|
return IsTrainerSlideInitialized(slideId);
|
|
}
|
|
|
|
static bool32 ShouldRunTrainerSlidePlayerLandsFirstSuperEffectiveHit(u32 battler, enum TrainerSlideType slideId)
|
|
{
|
|
if (!IsTrainerSlideInitialized(slideId))
|
|
return FALSE;
|
|
|
|
if (!IsBattlerAlive(battler))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static bool32 ShouldRunTrainerSlidePlayerLandsFirstSTABMove(u32 firstId, u32 lastId, enum TrainerSlideType slideId)
|
|
{
|
|
if (!IsTrainerSlideInitialized(slideId))
|
|
return FALSE;
|
|
|
|
if (GetEnemyMonCount(firstId, lastId, TRUE) != GetEnemyMonCount(firstId, lastId, FALSE))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static bool32 ShouldRunTrainerSlidePlayerLandsFirstDown(u32 firstId, u32 lastId)
|
|
{
|
|
return ((GetEnemyMonCount(firstId, lastId, TRUE) == (GetEnemyMonCount(firstId, lastId, FALSE) - 1)));
|
|
}
|
|
|
|
static bool32 ShouldRunTrainerSlideEnemyMonUnaffected(u32 firstId, u32 lastId, enum TrainerSlideType slideId)
|
|
{
|
|
if (!IsTrainerSlideInitialized(slideId))
|
|
return FALSE;
|
|
|
|
return (GetEnemyMonCount(firstId, lastId, TRUE) == GetEnemyMonCount(firstId, lastId, FALSE));
|
|
}
|
|
|
|
static bool32 ShouldRunTrainerSlideLastSwitchIn(u32 battler)
|
|
{
|
|
return !CanBattlerSwitch(battler);
|
|
}
|
|
|
|
static bool32 ShouldRunTrainerSlideLastHalfHP(u32 firstId, u32 lastId, u32 battler)
|
|
{
|
|
if (GetEnemyMonCount(firstId, lastId, TRUE) != 1)
|
|
return FALSE;
|
|
|
|
if (BattlerHPPercentage(battler, GREATER_THAN, 2))
|
|
return FALSE;
|
|
|
|
return (BattlerHPPercentage(battler, GREATER_THAN, 4));
|
|
}
|
|
|
|
static bool32 ShouldRunTrainerSlideLastLowHp(u32 firstId, u32 lastId, u32 battler)
|
|
{
|
|
if (GetEnemyMonCount(firstId, lastId, TRUE) != 1)
|
|
return FALSE;
|
|
|
|
return (BattlerHPPercentage(battler, LESS_THAN_OR_EQUAL, 4));
|
|
}
|
|
|
|
static void SetTrainerSlideParamters(u32 battler, u32* firstId, u32* lastId, u32* trainerId, u32* retValue)
|
|
{
|
|
if (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS)
|
|
{
|
|
if (gBattlerPartyIndexes[battler] >= MULTI_PARTY_SIZE)
|
|
{
|
|
*firstId = MULTI_PARTY_SIZE;
|
|
*lastId = PARTY_SIZE;
|
|
*trainerId = SanitizeTrainerId(TRAINER_BATTLE_PARAM.opponentB);
|
|
*retValue = TRAINER_SLIDE_TARGET_TRAINER_B;
|
|
}
|
|
else
|
|
{
|
|
*firstId = 0;
|
|
*lastId = MULTI_PARTY_SIZE;
|
|
*trainerId = SanitizeTrainerId(TRAINER_BATTLE_PARAM.opponentA);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*firstId = 0;
|
|
*lastId = PARTY_SIZE;
|
|
*trainerId = SanitizeTrainerId(TRAINER_BATTLE_PARAM.opponentA);
|
|
}
|
|
}
|
|
|
|
enum TrainerSlideTargets ShouldDoTrainerSlide(u32 battler, enum TrainerSlideType slideId)
|
|
{
|
|
u32 firstId, lastId, trainerId;
|
|
u32 retValue = TRAINER_SLIDE_TARGET_TRAINER_A;
|
|
bool32 shouldRun = FALSE;
|
|
|
|
if (!(gBattleTypeFlags & BATTLE_TYPE_TRAINER) || IsOnPlayerSide(battler))
|
|
return TRAINER_SLIDE_TARGET_NONE;
|
|
|
|
SetTrainerSlideParamters(battler, &firstId, &lastId, &trainerId, &retValue);
|
|
enum DifficultyLevel difficulty = GetCurrentDifficultyLevel();
|
|
|
|
gBattleScripting.battler = battler;
|
|
|
|
if (IsTrainerSlidePlayed(slideId))
|
|
return TRAINER_SLIDE_TARGET_NONE;
|
|
|
|
if (!DoesTrainerHaveSlideMessage(difficulty,trainerId,slideId))
|
|
return TRAINER_SLIDE_TARGET_NONE;
|
|
|
|
switch (slideId)
|
|
{
|
|
case TRAINER_SLIDE_PLAYER_LANDS_FIRST_CRITICAL_HIT:
|
|
shouldRun = ShouldRunTrainerSlidePlayerLandsFirstCriticalHit(slideId);
|
|
break;
|
|
case TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT:
|
|
shouldRun = ShouldRunTrainerSlideEnemyLandsFirstCriticalHit(slideId);
|
|
break;
|
|
case TRAINER_SLIDE_PLAYER_LANDS_FIRST_SUPER_EFFECTIVE_HIT:
|
|
shouldRun = ShouldRunTrainerSlidePlayerLandsFirstSuperEffectiveHit(battler, slideId);
|
|
break;
|
|
case TRAINER_SLIDE_PLAYER_LANDS_FIRST_STAB_MOVE:
|
|
shouldRun = ShouldRunTrainerSlidePlayerLandsFirstSTABMove(firstId, lastId, slideId);
|
|
break;
|
|
case TRAINER_SLIDE_PLAYER_LANDS_FIRST_DOWN:
|
|
shouldRun = ShouldRunTrainerSlidePlayerLandsFirstDown(firstId, lastId);
|
|
break;
|
|
case TRAINER_SLIDE_ENEMY_MON_UNAFFECTED:
|
|
shouldRun = ShouldRunTrainerSlideEnemyMonUnaffected(firstId, lastId, slideId);
|
|
break;
|
|
case TRAINER_SLIDE_LAST_SWITCHIN:
|
|
shouldRun = ShouldRunTrainerSlideLastSwitchIn(battler);
|
|
break;
|
|
case TRAINER_SLIDE_LAST_HALF_HP:
|
|
shouldRun = ShouldRunTrainerSlideLastHalfHP(firstId, lastId, battler);
|
|
break;
|
|
case TRAINER_SLIDE_LAST_LOW_HP:
|
|
shouldRun = ShouldRunTrainerSlideLastLowHp(firstId, lastId, battler);
|
|
break;
|
|
case TRAINER_SLIDE_BEFORE_FIRST_TURN:
|
|
case TRAINER_SLIDE_MEGA_EVOLUTION:
|
|
case TRAINER_SLIDE_Z_MOVE:
|
|
case TRAINER_SLIDE_DYNAMAX:
|
|
shouldRun = TRUE;
|
|
break;
|
|
default:
|
|
return TRAINER_SLIDE_TARGET_NONE;
|
|
}
|
|
|
|
if (shouldRun == FALSE)
|
|
return TRAINER_SLIDE_TARGET_NONE;
|
|
|
|
MarkTrainerSlideAsPlayed(slideId);
|
|
SetTrainerSlideMessage(difficulty,trainerId,slideId);
|
|
return retValue;
|
|
}
|
|
|
|
static bool32 IsSlideInitalizedOrPlayed(enum TrainerSlideType slideId)
|
|
{
|
|
if (IsTrainerSlideInitialized(slideId))
|
|
return TRUE;
|
|
|
|
if (IsTrainerSlidePlayed(slideId))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void TryInitializeFirstSTABMoveTrainerSlide(u32 battlerDef, u32 battlerAtk, enum Type moveType)
|
|
{
|
|
enum TrainerSlideType slideId = TRAINER_SLIDE_PLAYER_LANDS_FIRST_STAB_MOVE;
|
|
|
|
if (IsSlideInitalizedOrPlayed(slideId))
|
|
return;
|
|
|
|
if ((IsOnPlayerSide(battlerDef)))
|
|
return;
|
|
|
|
if (IS_BATTLER_OF_TYPE(battlerAtk, moveType) == FALSE)
|
|
return;
|
|
|
|
InitalizeTrainerSlide(slideId);
|
|
}
|
|
|
|
void TryInitializeTrainerSlidePlayerLandsFirstCriticalHit(u32 target)
|
|
{
|
|
enum TrainerSlideType slideId = TRAINER_SLIDE_PLAYER_LANDS_FIRST_CRITICAL_HIT;
|
|
|
|
if (IsSlideInitalizedOrPlayed(slideId))
|
|
return;
|
|
|
|
if (IsOnPlayerSide(target))
|
|
return;
|
|
|
|
InitalizeTrainerSlide(slideId);
|
|
}
|
|
|
|
void TryInitializeTrainerSlideEnemyLandsFirstCriticalHit(u32 target)
|
|
{
|
|
enum TrainerSlideType slideId = TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT;
|
|
|
|
if (IsSlideInitalizedOrPlayed(slideId))
|
|
return;
|
|
|
|
if (!IsOnPlayerSide(target))
|
|
return;
|
|
|
|
InitalizeTrainerSlide(slideId);
|
|
}
|
|
|
|
void TryInitializeTrainerSlidePlayerLandsFirstSuperEffectiveHit(u32 target)
|
|
{
|
|
enum TrainerSlideType slideId = TRAINER_SLIDE_PLAYER_LANDS_FIRST_SUPER_EFFECTIVE_HIT;
|
|
|
|
if (IsSlideInitalizedOrPlayed(slideId))
|
|
return;
|
|
|
|
if (IsOnPlayerSide(target))
|
|
return;
|
|
|
|
InitalizeTrainerSlide(slideId);
|
|
}
|
|
|
|
void TryInitializeTrainerSlideEnemyMonUnaffected(u32 target)
|
|
{
|
|
enum TrainerSlideType slideId = TRAINER_SLIDE_ENEMY_MON_UNAFFECTED;
|
|
|
|
if (IsSlideInitalizedOrPlayed(slideId))
|
|
return;
|
|
|
|
if (IsOnPlayerSide(target))
|
|
return;
|
|
|
|
InitalizeTrainerSlide(slideId);
|
|
}
|
|
|
|
bool32 IsTrainerSlideInitialized(enum TrainerSlideType slideId)
|
|
{
|
|
u32 arrayIndex = slideId / TRAINER_SLIDES_PER_ARRAY;
|
|
u32 bitPosition = slideId % TRAINER_SLIDES_PER_ARRAY;
|
|
|
|
return (gBattleStruct->slideMessageStatus.messageInitalized[arrayIndex] & (1 << bitPosition)) != 0;
|
|
}
|
|
|
|
bool32 IsTrainerSlidePlayed(enum TrainerSlideType slideId)
|
|
{
|
|
u32 arrayIndex = slideId / TRAINER_SLIDES_PER_ARRAY;
|
|
u32 bitPosition = slideId % TRAINER_SLIDES_PER_ARRAY;
|
|
|
|
return (gBattleStruct->slideMessageStatus.messagePlayed[arrayIndex] & (1 << bitPosition)) != 0;
|
|
}
|
|
|
|
void InitalizeTrainerSlide(enum TrainerSlideType slideId)
|
|
{
|
|
u32 arrayIndex = slideId / TRAINER_SLIDES_PER_ARRAY;
|
|
u32 bitPosition = slideId % TRAINER_SLIDES_PER_ARRAY;
|
|
|
|
gBattleStruct->slideMessageStatus.messageInitalized[arrayIndex] |= (1 << bitPosition);
|
|
}
|
|
|
|
void MarkTrainerSlideAsPlayed(enum TrainerSlideType slideId)
|
|
{
|
|
u32 arrayIndex = slideId / TRAINER_SLIDES_PER_ARRAY;
|
|
u32 bitPosition = slideId % TRAINER_SLIDES_PER_ARRAY;
|
|
|
|
gBattleStruct->slideMessageStatus.messagePlayed[arrayIndex] |= (1 << bitPosition);
|
|
}
|