mirror of
https://github.com/pret/pokefirered.git
synced 2026-05-14 16:20:24 -05:00
403 lines
14 KiB
C
403 lines
14 KiB
C
#include "global.h"
|
|
#include "main.h"
|
|
#include "battle.h"
|
|
#include "battle_main.h"
|
|
#include "battle_frontier.h"
|
|
#include "battle_setup.h"
|
|
#include "battle_factory.h"
|
|
#include "battle_partner.h"
|
|
#include "battle_tower.h"
|
|
#include "battle_transition.h"
|
|
#include "event_data.h"
|
|
#include "frontier_util.h"
|
|
#include "overworld.h"
|
|
#include "script.h"
|
|
#include "string_util.h"
|
|
#include "task.h"
|
|
#include "text.h"
|
|
#include "constants/abilities.h"
|
|
#include "constants/battle_dome.h"
|
|
#include "constants/battle_frontier.h"
|
|
#include "constants/battle_frontier_mons.h"
|
|
|
|
static void FillTrainerParty(u16 trainerId, u8 firstMonId, u8 monCount);
|
|
|
|
// EWRAM vars.
|
|
EWRAM_DATA const struct BattleFrontierTrainer *gFacilityTrainers = NULL;
|
|
EWRAM_DATA const struct TrainerMon *gFacilityTrainerMons = NULL;
|
|
|
|
// IWRAM common
|
|
COMMON_DATA u16 gFrontierTempParty[MAX_FRONTIER_PARTY_SIZE] = {0};
|
|
|
|
static void HandleFacilityTrainerBattleEnd(void)
|
|
{
|
|
u8 facility = gBattleScripting.specialTrainerBattleType;
|
|
switch (facility)
|
|
{
|
|
case FACILITY_BATTLE_TOWER:
|
|
case FACILITY_BATTLE_DOME:
|
|
case FACILITY_BATTLE_PALACE:
|
|
case FACILITY_BATTLE_ARENA:
|
|
case FACILITY_BATTLE_FACTORY:
|
|
case FACILITY_BATTLE_PIKE_SINGLE:
|
|
case FACILITY_BATTLE_PIKE_DOUBLE:
|
|
case FACILITY_BATTLE_PYRAMID:
|
|
if (gSaveBlock2Ptr->frontier.battlesCount < 0xFFFFFF)
|
|
{
|
|
gSaveBlock2Ptr->frontier.battlesCount++;
|
|
// if (gSaveBlock2Ptr->frontier.battlesCount % 20 == 0)
|
|
// UpdateGymLeaderRematch();
|
|
}
|
|
else
|
|
{
|
|
gSaveBlock2Ptr->frontier.battlesCount = 0xFFFFFF;
|
|
}
|
|
break;
|
|
case FACILITY_BATTLE_TRAINER_HILL:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
SetMainCallback2(CB2_ReturnToFieldContinueScriptPlayMapMusic);
|
|
}
|
|
|
|
static void Task_StartBattleAfterTransition(u8 taskId)
|
|
{
|
|
if (IsBattleTransitionDone() == TRUE)
|
|
{
|
|
gMain.savedCallback = HandleFacilityTrainerBattleEnd;
|
|
SetMainCallback2(CB2_InitBattle);
|
|
DestroyTask(taskId);
|
|
}
|
|
}
|
|
|
|
static void DoFacilityTrainerBattleInternal(u8 facility)
|
|
{
|
|
gBattleScripting.specialTrainerBattleType = facility;
|
|
|
|
switch (facility)
|
|
{
|
|
case FACILITY_BATTLE_TOWER:
|
|
gBattleTypeFlags = BATTLE_TYPE_TRAINER | BATTLE_TYPE_BATTLE_TOWER;
|
|
switch (VarGet(VAR_FRONTIER_BATTLE_MODE))
|
|
{
|
|
case FRONTIER_MODE_SINGLES:
|
|
FillFrontierTrainerParty(FRONTIER_PARTY_SIZE);
|
|
break;
|
|
case FRONTIER_MODE_DOUBLES:
|
|
FillFrontierTrainerParty(FRONTIER_DOUBLES_PARTY_SIZE);
|
|
gBattleTypeFlags |= BATTLE_TYPE_DOUBLE;
|
|
break;
|
|
case FRONTIER_MODE_MULTIS:
|
|
FillFrontierTrainersParties(FRONTIER_MULTI_PARTY_SIZE);
|
|
gPartnerTrainerId = gSaveBlock2Ptr->frontier.trainerIds[17];
|
|
FillPartnerParty(gPartnerTrainerId);
|
|
gBattleTypeFlags |= BATTLE_TYPE_DOUBLE | BATTLE_TYPE_INGAME_PARTNER | BATTLE_TYPE_MULTI | BATTLE_TYPE_TWO_OPPONENTS;
|
|
break;
|
|
case FRONTIER_MODE_LINK_MULTIS:
|
|
gBattleTypeFlags |= BATTLE_TYPE_DOUBLE | BATTLE_TYPE_LINK | BATTLE_TYPE_MULTI | BATTLE_TYPE_TOWER_LINK_MULTI;
|
|
FillFrontierTrainersParties(FRONTIER_MULTI_PARTY_SIZE);
|
|
break;
|
|
}
|
|
CreateTask(Task_StartBattleAfterTransition, 1);
|
|
PlayMapChosenOrBattleBGM(0);
|
|
BattleTransition_StartOnField(GetSpecialBattleTransition(B_TRANSITION_GROUP_B_TOWER));
|
|
break;
|
|
case FACILITY_BATTLE_DOME:
|
|
gBattleTypeFlags = BATTLE_TYPE_TRAINER | BATTLE_TYPE_DOME;
|
|
if (VarGet(VAR_FRONTIER_BATTLE_MODE) == FRONTIER_MODE_DOUBLES)
|
|
gBattleTypeFlags |= BATTLE_TYPE_DOUBLE;
|
|
if (TRAINER_BATTLE_PARAM.opponentA == TRAINER_FRONTIER_BRAIN)
|
|
FillFrontierTrainerParty(DOME_BATTLE_PARTY_SIZE);
|
|
CreateTask(Task_StartBattleAfterTransition, 1);
|
|
CreateTask_PlayMapChosenOrBattleBGM(0);
|
|
BattleTransition_StartOnField(GetSpecialBattleTransition(B_TRANSITION_GROUP_B_DOME));
|
|
break;
|
|
case FACILITY_BATTLE_PALACE:
|
|
gBattleTypeFlags = BATTLE_TYPE_TRAINER | BATTLE_TYPE_PALACE;
|
|
|
|
if (VarGet(VAR_FRONTIER_BATTLE_MODE) == FRONTIER_MODE_DOUBLES)
|
|
gBattleTypeFlags |= BATTLE_TYPE_DOUBLE;
|
|
|
|
FillFrontierTrainerParty(FRONTIER_PARTY_SIZE);
|
|
// if (gSaveBlock2Ptr->frontier.lvlMode != FRONTIER_LVL_TENT)
|
|
// FillFrontierTrainerParty(FRONTIER_PARTY_SIZE);
|
|
// else
|
|
// FillTentTrainerParty(FRONTIER_PARTY_SIZE);
|
|
|
|
CreateTask(Task_StartBattleAfterTransition, 1);
|
|
PlayMapChosenOrBattleBGM(0);
|
|
BattleTransition_StartOnField(GetSpecialBattleTransition(B_TRANSITION_GROUP_B_PALACE));
|
|
break;
|
|
case FACILITY_BATTLE_ARENA:
|
|
gBattleTypeFlags = BATTLE_TYPE_TRAINER | BATTLE_TYPE_ARENA;
|
|
|
|
FillFrontierTrainerParty(FRONTIER_PARTY_SIZE);
|
|
// if (gSaveBlock2Ptr->frontier.lvlMode != FRONTIER_LVL_TENT)
|
|
// FillFrontierTrainerParty(FRONTIER_PARTY_SIZE);
|
|
// else
|
|
// FillTentTrainerParty(FRONTIER_PARTY_SIZE);
|
|
|
|
CreateTask(Task_StartBattleAfterTransition, 1);
|
|
PlayMapChosenOrBattleBGM(0);
|
|
BattleTransition_StartOnField(GetSpecialBattleTransition(B_TRANSITION_GROUP_B_ARENA));
|
|
break;
|
|
case FACILITY_BATTLE_FACTORY:
|
|
gBattleTypeFlags = BATTLE_TYPE_TRAINER | BATTLE_TYPE_FACTORY;
|
|
if (VarGet(VAR_FRONTIER_BATTLE_MODE) == FRONTIER_MODE_DOUBLES)
|
|
gBattleTypeFlags |= BATTLE_TYPE_DOUBLE;
|
|
FillFactoryTrainerParty();
|
|
CreateTask(Task_StartBattleAfterTransition, 1);
|
|
PlayMapChosenOrBattleBGM(0);
|
|
BattleTransition_StartOnField(GetSpecialBattleTransition(B_TRANSITION_GROUP_B_FACTORY));
|
|
break;
|
|
case FACILITY_BATTLE_PIKE_SINGLE:
|
|
gBattleTypeFlags = BATTLE_TYPE_TRAINER | BATTLE_TYPE_BATTLE_TOWER;
|
|
FillFrontierTrainerParty(FRONTIER_PARTY_SIZE);
|
|
CreateTask(Task_StartBattleAfterTransition, 1);
|
|
PlayMapChosenOrBattleBGM(0);
|
|
BattleTransition_StartOnField(GetSpecialBattleTransition(B_TRANSITION_GROUP_B_PIKE));
|
|
break;
|
|
case FACILITY_BATTLE_PIKE_DOUBLE:
|
|
gBattleTypeFlags = BATTLE_TYPE_TRAINER | BATTLE_TYPE_BATTLE_TOWER | BATTLE_TYPE_DOUBLE | BATTLE_TYPE_TWO_OPPONENTS;
|
|
FillFrontierTrainersParties(1);
|
|
CreateTask(Task_StartBattleAfterTransition, 1);
|
|
PlayMapChosenOrBattleBGM(0);
|
|
BattleTransition_StartOnField(GetSpecialBattleTransition(B_TRANSITION_GROUP_B_PIKE));
|
|
break;
|
|
case FACILITY_BATTLE_PYRAMID:
|
|
gBattleTypeFlags = BATTLE_TYPE_TRAINER | BATTLE_TYPE_PYRAMID;
|
|
FillFrontierTrainerParty(FRONTIER_PARTY_SIZE);
|
|
CreateTask(Task_StartBattleAfterTransition, 1);
|
|
PlayMapChosenOrBattleBGM(0);
|
|
BattleTransition_StartOnField(GetSpecialBattleTransition(B_TRANSITION_GROUP_B_PYRAMID));
|
|
break;
|
|
case FACILITY_BATTLE_TRAINER_HILL:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void DoFacilityTrainerBattle(struct ScriptContext *ctx)
|
|
{
|
|
u8 facility = ScriptReadByte(ctx);
|
|
|
|
DoFacilityTrainerBattleInternal(facility);
|
|
}
|
|
|
|
void FacilityTrainerBattle(struct ScriptContext *ctx)
|
|
{
|
|
InitTrainerBattleParameter();
|
|
|
|
u8 facility = ScriptReadByte(ctx);
|
|
ctx->scriptPtr = BattleSetup_ConfigureFacilityTrainerBattle(facility, ctx->scriptPtr);
|
|
}
|
|
|
|
void FillFrontierTrainerParty(u8 monsCount)
|
|
{
|
|
ZeroEnemyPartyMons();
|
|
FillTrainerParty(TRAINER_BATTLE_PARAM.opponentA, 0, monsCount);
|
|
}
|
|
|
|
void FillFrontierTrainersParties(u8 monsCount)
|
|
{
|
|
ZeroEnemyPartyMons();
|
|
FillTrainerParty(TRAINER_BATTLE_PARAM.opponentA, 0, monsCount);
|
|
FillTrainerParty(TRAINER_BATTLE_PARAM.opponentB, 3, monsCount);
|
|
}
|
|
|
|
static void FillTrainerParty(u16 trainerId, u8 firstMonId, u8 monCount)
|
|
{
|
|
s32 i, j;
|
|
u16 chosenMonIndices[MAX_FRONTIER_PARTY_SIZE];
|
|
u8 level = SetFacilityPtrsGetLevel();
|
|
u8 fixedIV = 0;
|
|
u8 bfMonCount;
|
|
const u16 *monSet = NULL;
|
|
u32 otID = 0;
|
|
|
|
if (trainerId < FRONTIER_TRAINERS_COUNT)
|
|
{
|
|
// Normal battle frontier trainer.
|
|
fixedIV = GetFrontierTrainerFixedIvs(trainerId);
|
|
monSet = gFacilityTrainers[TRAINER_BATTLE_PARAM.opponentA].monSet;
|
|
}
|
|
else if (trainerId == TRAINER_EREADER)
|
|
{
|
|
#if FREE_BATTLE_TOWER_E_READER == FALSE
|
|
for (i = firstMonId; i < firstMonId + FRONTIER_PARTY_SIZE; i++)
|
|
CreateBattleTowerMon(&gEnemyParty[i], &gSaveBlock2Ptr->frontier.ereaderTrainer.party[i - firstMonId]);
|
|
#endif //FREE_BATTLE_TOWER_E_READER
|
|
return;
|
|
}
|
|
else if (trainerId == TRAINER_FRONTIER_BRAIN)
|
|
{
|
|
CreateFrontierBrainPokemon();
|
|
return;
|
|
}
|
|
else if (trainerId < TRAINER_RECORD_MIXING_APPRENTICE)
|
|
{
|
|
// Record mixed player.
|
|
for (j = 0, i = firstMonId; i < firstMonId + monCount; j++, i++)
|
|
{
|
|
if (gSaveBlock2Ptr->frontier.towerRecords[trainerId - TRAINER_RECORD_MIXING_FRIEND].party[j].species != SPECIES_NONE
|
|
&& gSaveBlock2Ptr->frontier.towerRecords[trainerId - TRAINER_RECORD_MIXING_FRIEND].party[j].level <= level)
|
|
{
|
|
CreateBattleTowerMon_HandleLevel(&gEnemyParty[i], &gSaveBlock2Ptr->frontier.towerRecords[trainerId - TRAINER_RECORD_MIXING_FRIEND].party[j], FALSE);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Apprentice.
|
|
for (i = firstMonId; i < firstMonId + FRONTIER_PARTY_SIZE; i++)
|
|
CreateApprenticeMon(&gEnemyParty[i], &gSaveBlock1Ptr->apprentices[trainerId - TRAINER_RECORD_MIXING_APPRENTICE], i - firstMonId);
|
|
return;
|
|
}
|
|
|
|
// Regular battle frontier trainer.
|
|
// Attempt to fill the trainer's party with random Pokémon until 3 have been
|
|
// successfully chosen. The trainer's party may not have duplicate Pokémon species
|
|
// or duplicate held items.
|
|
for (bfMonCount = 0; monSet[bfMonCount] != 0xFFFF; bfMonCount++)
|
|
;
|
|
i = 0;
|
|
otID = Random32();
|
|
while (i != monCount)
|
|
{
|
|
u16 monId = monSet[Random() % bfMonCount];
|
|
|
|
// "High tier" Pokémon are only allowed on open level mode
|
|
// 20 is not a possible value for level here
|
|
if ((level == FRONTIER_MAX_LEVEL_50 || level == 20) && monId > FRONTIER_MONS_HIGH_TIER)
|
|
continue;
|
|
|
|
// Ensure this Pokémon species isn't a duplicate.
|
|
for (j = 0; j < i + firstMonId; j++)
|
|
{
|
|
if (GetMonData(&gEnemyParty[j], MON_DATA_SPECIES) == gFacilityTrainerMons[monId].species)
|
|
break;
|
|
}
|
|
if (j != i + firstMonId)
|
|
continue;
|
|
|
|
// Ensure this Pokemon's held item isn't a duplicate.
|
|
for (j = 0; j < i + firstMonId; j++)
|
|
{
|
|
if (GetMonData(&gEnemyParty[j], MON_DATA_HELD_ITEM) != ITEM_NONE
|
|
&& GetMonData(&gEnemyParty[j], MON_DATA_HELD_ITEM) == gFacilityTrainerMons[monId].heldItem)
|
|
break;
|
|
}
|
|
if (j != i + firstMonId)
|
|
continue;
|
|
|
|
// Ensure this exact Pokémon index isn't a duplicate. This check doesn't seem necessary
|
|
// because the species and held items were already checked directly above.
|
|
for (j = 0; j < i; j++)
|
|
{
|
|
if (chosenMonIndices[j] == monId)
|
|
break;
|
|
}
|
|
if (j != i)
|
|
continue;
|
|
|
|
chosenMonIndices[i] = monId;
|
|
|
|
// Place the chosen Pokémon into the trainer's party.
|
|
CreateFacilityMon(&gFacilityTrainerMons[monId], level, fixedIV, otID, 0, &gEnemyParty[i + firstMonId]);
|
|
|
|
// The Pokémon was successfully added to the trainer's party, so it's safe to move on to
|
|
// the next party slot.
|
|
i++;
|
|
}
|
|
}
|
|
|
|
void CreateFacilityMon(const struct TrainerMon *fmon, u16 level, u8 fixedIV, u32 otID, u32 flags, struct Pokemon *dst)
|
|
{
|
|
u8 ball = (fmon->ball == 0xFF) ? Random() % POKEBALL_COUNT : fmon->ball;
|
|
enum Move move;
|
|
u32 personality = 0, ability, friendship, j;
|
|
|
|
if (fmon->gender == TRAINER_MON_MALE)
|
|
{
|
|
personality = GeneratePersonalityForGender(MON_MALE, fmon->species);
|
|
}
|
|
else if (fmon->gender == TRAINER_MON_FEMALE)
|
|
{
|
|
personality = GeneratePersonalityForGender(MON_FEMALE, fmon->species);
|
|
}
|
|
|
|
ModifyPersonalityForNature(&personality, fmon->nature);
|
|
CreateMonWithIVs(dst, fmon->species, level, personality, OTID_STRUCT_PRESET(otID), fixedIV);
|
|
|
|
friendship = MAX_FRIENDSHIP;
|
|
// Give the chosen Pokémon its specified moves.
|
|
for (j = 0; j < MAX_MON_MOVES; j++)
|
|
{
|
|
move = fmon->moves[j];
|
|
if (flags & FLAG_FRONTIER_MON_FACTORY && move == MOVE_RETURN)
|
|
move = MOVE_FRUSTRATION;
|
|
|
|
SetMonMoveSlot(dst, move, j);
|
|
if (GetMoveEffect(move) == EFFECT_FRUSTRATION)
|
|
friendship = 0; // Frustration is more powerful the lower the pokemon's friendship is.
|
|
}
|
|
|
|
SetMonData(dst, MON_DATA_FRIENDSHIP, &friendship);
|
|
SetMonData(dst, MON_DATA_HELD_ITEM, &fmon->heldItem);
|
|
|
|
// try to set ability. Otherwise, random of non-hidden as per vanilla
|
|
if (fmon->ability != ABILITY_NONE)
|
|
{
|
|
const struct SpeciesInfo *speciesInfo = &gSpeciesInfo[fmon->species];
|
|
u32 maxAbilities = ARRAY_COUNT(speciesInfo->abilities);
|
|
for (ability = 0; ability < maxAbilities; ++ability)
|
|
{
|
|
if (speciesInfo->abilities[ability] == fmon->ability)
|
|
break;
|
|
}
|
|
if (ability >= maxAbilities)
|
|
ability = 0;
|
|
SetMonData(dst, MON_DATA_ABILITY_NUM, &ability);
|
|
}
|
|
|
|
if (fmon->ev != NULL)
|
|
{
|
|
SetMonData(dst, MON_DATA_HP_EV, &(fmon->ev[0]));
|
|
SetMonData(dst, MON_DATA_ATK_EV, &(fmon->ev[1]));
|
|
SetMonData(dst, MON_DATA_DEF_EV, &(fmon->ev[2]));
|
|
SetMonData(dst, MON_DATA_SPATK_EV, &(fmon->ev[3]));
|
|
SetMonData(dst, MON_DATA_SPDEF_EV, &(fmon->ev[4]));
|
|
SetMonData(dst, MON_DATA_SPEED_EV, &(fmon->ev[5]));
|
|
}
|
|
|
|
if (fmon->iv)
|
|
SetMonData(dst, MON_DATA_IVS, &(fmon->iv));
|
|
|
|
if (fmon->isShiny)
|
|
{
|
|
u32 data = TRUE;
|
|
SetMonData(dst, MON_DATA_IS_SHINY, &data);
|
|
}
|
|
if (fmon->dynamaxLevel > 0)
|
|
{
|
|
u32 data = fmon->dynamaxLevel;
|
|
SetMonData(dst, MON_DATA_DYNAMAX_LEVEL, &data);
|
|
}
|
|
if (fmon->gigantamaxFactor)
|
|
{
|
|
u32 data = fmon->gigantamaxFactor;
|
|
SetMonData(dst, MON_DATA_GIGANTAMAX_FACTOR, &data);
|
|
}
|
|
if (fmon->teraType)
|
|
{
|
|
u32 data = fmon->teraType;
|
|
SetMonData(dst, MON_DATA_TERA_TYPE, &data);
|
|
}
|
|
|
|
|
|
SetMonData(dst, MON_DATA_POKEBALL, &ball);
|
|
CalculateMonStats(dst);
|
|
}
|