mirror of
https://github.com/Ninjdai1/pokeemerald.git
synced 2024-12-29 13:50:42 +01:00
872 lines
29 KiB
C
872 lines
29 KiB
C
#include "global.h"
|
|
#include "battle.h"
|
|
#include "battle_anim.h"
|
|
#include "battle_controllers.h"
|
|
#include "recorded_battle.h"
|
|
#include "main.h"
|
|
#include "pokemon.h"
|
|
#include "random.h"
|
|
#include "event_data.h"
|
|
#include "link.h"
|
|
#include "string_util.h"
|
|
#include "palette.h"
|
|
#include "save.h"
|
|
#include "malloc.h"
|
|
#include "util.h"
|
|
#include "task.h"
|
|
#include "text.h"
|
|
#include "battle_setup.h"
|
|
#include "frontier_util.h"
|
|
#include "constants/trainers.h"
|
|
#include "constants/rgb.h"
|
|
|
|
#define BATTLER_RECORD_SIZE 664
|
|
#define ILLEGAL_BATTLE_TYPES ((BATTLE_TYPE_LINK | BATTLE_TYPE_SAFARI | BATTLE_TYPE_FIRST_BATTLE \
|
|
| BATTLE_TYPE_WALLY_TUTORIAL | BATTLE_TYPE_ROAMER | BATTLE_TYPE_EREADER_TRAINER \
|
|
| BATTLE_TYPE_KYOGRE_GROUDON | BATTLE_TYPE_LEGENDARY | BATTLE_TYPE_REGI \
|
|
| BATTLE_TYPE_RECORDED | BATTLE_TYPE_TRAINER_HILL | BATTLE_TYPE_SECRET_BASE \
|
|
| BATTLE_TYPE_GROUDON | BATTLE_TYPE_KYOGRE | BATTLE_TYPE_RAYQUAZA))
|
|
|
|
struct PlayerInfo
|
|
{
|
|
u32 trainerId;
|
|
u8 name[PLAYER_NAME_LENGTH + 1];
|
|
u8 gender;
|
|
u16 battlerId;
|
|
u16 language;
|
|
};
|
|
|
|
struct RecordedBattleSave
|
|
{
|
|
struct Pokemon playerParty[PARTY_SIZE];
|
|
struct Pokemon opponentParty[PARTY_SIZE];
|
|
u8 playersName[MAX_BATTLERS_COUNT][PLAYER_NAME_LENGTH + 1];
|
|
u8 playersGender[MAX_BATTLERS_COUNT];
|
|
u32 playersTrainerId[MAX_BATTLERS_COUNT];
|
|
u8 playersLanguage[MAX_BATTLERS_COUNT];
|
|
u32 rngSeed;
|
|
u32 battleFlags;
|
|
u8 playersBattlers[MAX_BATTLERS_COUNT];
|
|
u16 opponentA;
|
|
u16 opponentB;
|
|
u16 partnerId;
|
|
u16 multiplayerId;
|
|
u8 lvlMode;
|
|
u8 frontierFacility;
|
|
u8 frontierBrainSymbol;
|
|
u8 battleScene:1;
|
|
u8 textSpeed:3;
|
|
u32 AI_scripts;
|
|
u8 recordMixFriendName[PLAYER_NAME_LENGTH + 1];
|
|
u8 recordMixFriendClass;
|
|
u8 apprenticeId;
|
|
u16 easyChatSpeech[EASY_CHAT_BATTLE_WORDS_COUNT];
|
|
u8 recordMixFriendLanguage;
|
|
u8 apprenticeLanguage;
|
|
u8 battleRecord[MAX_BATTLERS_COUNT][BATTLER_RECORD_SIZE];
|
|
u32 checksum;
|
|
};
|
|
|
|
STATIC_ASSERT(sizeof(struct RecordedBattleSave) <= SECTOR_DATA_SIZE, RecordedBattleSaveFreeSpace);
|
|
|
|
EWRAM_DATA u32 gRecordedBattleRngSeed = 0;
|
|
EWRAM_DATA u32 gBattlePalaceMoveSelectionRngValue = 0;
|
|
EWRAM_DATA static u8 sBattleRecords[MAX_BATTLERS_COUNT][BATTLER_RECORD_SIZE] = {0};
|
|
EWRAM_DATA static u16 sBattlerRecordSizes[MAX_BATTLERS_COUNT] = {0};
|
|
EWRAM_DATA static u16 sBattlerPrevRecordSizes[MAX_BATTLERS_COUNT] = {0};
|
|
EWRAM_DATA static u16 sBattlerSavedRecordSizes[MAX_BATTLERS_COUNT] = {0};
|
|
EWRAM_DATA static u8 sRecordMode = 0;
|
|
EWRAM_DATA static u8 sLvlMode = 0;
|
|
EWRAM_DATA static u8 sFrontierFacility = 0;
|
|
EWRAM_DATA static u8 sFrontierBrainSymbol = 0;
|
|
EWRAM_DATA static MainCallback sCallback2_AfterRecordedBattle = NULL;
|
|
EWRAM_DATA u8 gRecordedBattleMultiplayerId = 0;
|
|
EWRAM_DATA static u8 sFrontierPassFlag = 0;
|
|
EWRAM_DATA static u8 sBattleScene = 0;
|
|
EWRAM_DATA static u8 sTextSpeed = 0;
|
|
EWRAM_DATA static u32 sBattleFlags = 0;
|
|
EWRAM_DATA static u32 sAI_Scripts = 0;
|
|
EWRAM_DATA static struct Pokemon sSavedPlayerParty[PARTY_SIZE] = {0};
|
|
EWRAM_DATA static struct Pokemon sSavedOpponentParty[PARTY_SIZE] = {0};
|
|
EWRAM_DATA static u16 sPlayerMonMoves[MAX_BATTLERS_COUNT / 2][MAX_MON_MOVES] = {0};
|
|
EWRAM_DATA static struct PlayerInfo sPlayers[MAX_BATTLERS_COUNT] = {0};
|
|
EWRAM_DATA static bool8 sIsPlaybackFinished = 0;
|
|
EWRAM_DATA static u8 sRecordMixFriendName[PLAYER_NAME_LENGTH + 1] = {0};
|
|
EWRAM_DATA static u8 sRecordMixFriendClass = 0;
|
|
EWRAM_DATA static u8 sApprenticeId = 0;
|
|
EWRAM_DATA static u16 sEasyChatSpeech[EASY_CHAT_BATTLE_WORDS_COUNT] = {0};
|
|
EWRAM_DATA static u8 sBattleOutcome = 0;
|
|
|
|
static u8 sRecordMixFriendLanguage;
|
|
static u8 sApprenticeLanguage;
|
|
|
|
static u8 GetNextRecordedDataByte(u8 *, u8 *, u8 *);
|
|
static bool32 CopyRecordedBattleFromSave(struct RecordedBattleSave *);
|
|
static void RecordedBattle_RestoreSavedParties(void);
|
|
static void CB2_RecordedBattle(void);
|
|
|
|
void RecordedBattle_Init(u8 mode)
|
|
{
|
|
s32 i, j;
|
|
|
|
sRecordMode = mode;
|
|
sIsPlaybackFinished = FALSE;
|
|
|
|
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
|
|
{
|
|
sBattlerRecordSizes[i] = 0;
|
|
sBattlerPrevRecordSizes[i] = 0;
|
|
sBattlerSavedRecordSizes[i] = 0;
|
|
|
|
if (mode == B_RECORD_MODE_RECORDING)
|
|
{
|
|
for (j = 0; j < BATTLER_RECORD_SIZE; j++)
|
|
sBattleRecords[i][j] = 0xFF;
|
|
sBattleFlags = gBattleTypeFlags;
|
|
sAI_Scripts = gBattleResources->ai->aiFlags;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RecordedBattle_SetTrainerInfo(void)
|
|
{
|
|
s32 i, j;
|
|
|
|
if (sRecordMode == B_RECORD_MODE_RECORDING)
|
|
{
|
|
gRecordedBattleRngSeed = gRngValue;
|
|
sFrontierFacility = VarGet(VAR_FRONTIER_FACILITY);
|
|
sFrontierBrainSymbol = GetFronterBrainSymbol();
|
|
}
|
|
else if (sRecordMode == B_RECORD_MODE_PLAYBACK)
|
|
{
|
|
gRngValue = gRecordedBattleRngSeed;
|
|
}
|
|
|
|
if (gBattleTypeFlags & BATTLE_TYPE_LINK)
|
|
{
|
|
// Link recorded battle, record info for all trainers
|
|
u8 linkPlayersCount;
|
|
u8 text[30];
|
|
|
|
gRecordedBattleMultiplayerId = GetMultiplayerId();
|
|
linkPlayersCount = GetLinkPlayerCount();
|
|
|
|
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
|
|
{
|
|
sPlayers[i].trainerId = gLinkPlayers[i].trainerId;
|
|
sPlayers[i].gender = gLinkPlayers[i].gender;
|
|
sPlayers[i].battlerId = gLinkPlayers[i].id;
|
|
sPlayers[i].language = gLinkPlayers[i].language;
|
|
|
|
// Record names
|
|
if (i < linkPlayersCount)
|
|
{
|
|
StringCopy(text, gLinkPlayers[i].name);
|
|
StripExtCtrlCodes(text);
|
|
StringCopy(sPlayers[i].name, text);
|
|
}
|
|
else
|
|
{
|
|
for (j = 0; j < PLAYER_NAME_LENGTH + 1; j++)
|
|
sPlayers[i].name[j] = gLinkPlayers[i].name[j];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Local battle, just record own info
|
|
sPlayers[0].trainerId = (gSaveBlock2Ptr->playerTrainerId[0])
|
|
| (gSaveBlock2Ptr->playerTrainerId[1] << 8)
|
|
| (gSaveBlock2Ptr->playerTrainerId[2] << 16)
|
|
| (gSaveBlock2Ptr->playerTrainerId[3] << 24);
|
|
|
|
sPlayers[0].gender = gSaveBlock2Ptr->playerGender;
|
|
sPlayers[0].battlerId = 0;
|
|
sPlayers[0].language = gGameLanguage;
|
|
|
|
for (i = 0; i < PLAYER_NAME_LENGTH + 1; i++)
|
|
sPlayers[0].name[i] = gSaveBlock2Ptr->playerName[i];
|
|
}
|
|
}
|
|
|
|
void RecordedBattle_SetBattlerAction(u8 battlerId, u8 action)
|
|
{
|
|
if (sBattlerRecordSizes[battlerId] < BATTLER_RECORD_SIZE && sRecordMode != B_RECORD_MODE_PLAYBACK)
|
|
sBattleRecords[battlerId][sBattlerRecordSizes[battlerId]++] = action;
|
|
}
|
|
|
|
void RecordedBattle_ClearBattlerAction(u8 battlerId, u8 bytesToClear)
|
|
{
|
|
s32 i;
|
|
|
|
for (i = 0; i < bytesToClear; i++)
|
|
{
|
|
sBattlerRecordSizes[battlerId]--;
|
|
sBattleRecords[battlerId][sBattlerRecordSizes[battlerId]] = 0xFF;
|
|
if (sBattlerRecordSizes[battlerId] == 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
u8 RecordedBattle_GetBattlerAction(u8 battlerId)
|
|
{
|
|
// Trying to read past array or invalid action byte, battle is over.
|
|
if (sBattlerRecordSizes[battlerId] >= BATTLER_RECORD_SIZE || sBattleRecords[battlerId][sBattlerRecordSizes[battlerId]] == 0xFF)
|
|
{
|
|
gSpecialVar_Result = gBattleOutcome = B_OUTCOME_PLAYER_TELEPORTED; // hah
|
|
ResetPaletteFadeControl();
|
|
BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 16, RGB_BLACK);
|
|
SetMainCallback2(CB2_QuitRecordedBattle);
|
|
return 0xFF;
|
|
}
|
|
else
|
|
{
|
|
return sBattleRecords[battlerId][sBattlerRecordSizes[battlerId]++];
|
|
}
|
|
}
|
|
|
|
// Unused
|
|
static u8 GetRecordedBattleMode(void)
|
|
{
|
|
return sRecordMode;
|
|
}
|
|
|
|
u8 RecordedBattle_BufferNewBattlerData(u8 *dst)
|
|
{
|
|
u8 i, j;
|
|
u8 idx = 0;
|
|
|
|
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
|
|
{
|
|
if (sBattlerRecordSizes[i] != sBattlerPrevRecordSizes[i])
|
|
{
|
|
dst[idx++] = i;
|
|
dst[idx++] = sBattlerRecordSizes[i] - sBattlerPrevRecordSizes[i];
|
|
|
|
for (j = 0; j < sBattlerRecordSizes[i] - sBattlerPrevRecordSizes[i]; j++)
|
|
dst[idx++] = sBattleRecords[i][sBattlerPrevRecordSizes[i] + j];
|
|
|
|
sBattlerPrevRecordSizes[i] = sBattlerRecordSizes[i];
|
|
}
|
|
}
|
|
|
|
return idx;
|
|
}
|
|
|
|
void RecordedBattle_RecordAllBattlerData(u8 *src)
|
|
{
|
|
s32 i;
|
|
u8 idx = 2;
|
|
u8 size;
|
|
|
|
if (!(gBattleTypeFlags & BATTLE_TYPE_LINK))
|
|
return;
|
|
|
|
for (i = 0; i < GetLinkPlayerCount(); i++)
|
|
{
|
|
if ((gLinkPlayers[i].version & 0xFF) != VERSION_EMERALD)
|
|
return;
|
|
}
|
|
|
|
if (!(gBattleTypeFlags & BATTLE_TYPE_IS_MASTER))
|
|
{
|
|
for (size = *src; size != 0;)
|
|
{
|
|
u8 battlerId = GetNextRecordedDataByte(src, &idx, &size);
|
|
u8 numActions = GetNextRecordedDataByte(src, &idx, &size);
|
|
|
|
for (i = 0; i < numActions; i++)
|
|
sBattleRecords[battlerId][sBattlerSavedRecordSizes[battlerId]++] = GetNextRecordedDataByte(src, &idx, &size);
|
|
}
|
|
}
|
|
}
|
|
|
|
static u8 GetNextRecordedDataByte(u8 *data, u8 *idx, u8 *size)
|
|
{
|
|
(*size)--;
|
|
return data[(*idx)++];
|
|
}
|
|
|
|
bool32 CanCopyRecordedBattleSaveData(void)
|
|
{
|
|
struct RecordedBattleSave *dst = AllocZeroed(sizeof(struct RecordedBattleSave));
|
|
bool32 ret = CopyRecordedBattleFromSave(dst);
|
|
Free(dst);
|
|
return ret;
|
|
}
|
|
|
|
static bool32 IsRecordedBattleSaveValid(struct RecordedBattleSave *save)
|
|
{
|
|
if (save->battleFlags == 0)
|
|
return FALSE;
|
|
if (save->battleFlags & ILLEGAL_BATTLE_TYPES)
|
|
return FALSE;
|
|
if (CalcByteArraySum((void*)(save), sizeof(*save) - 4) != save->checksum)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static bool32 RecordedBattleToSave(struct RecordedBattleSave *battleSave, struct RecordedBattleSave *saveSector)
|
|
{
|
|
memset(saveSector, 0, SECTOR_SIZE);
|
|
memcpy(saveSector, battleSave, sizeof(*battleSave));
|
|
|
|
saveSector->checksum = CalcByteArraySum((void*)(saveSector), sizeof(*saveSector) - 4);
|
|
|
|
if (TryWriteSpecialSaveSector(SECTOR_ID_RECORDED_BATTLE, (void*)(saveSector)) != SAVE_STATUS_OK)
|
|
return FALSE;
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
bool32 MoveRecordedBattleToSaveData(void)
|
|
{
|
|
s32 i, j;
|
|
bool32 ret;
|
|
struct RecordedBattleSave *battleSave, *savSection;
|
|
u8 saveAttempts;
|
|
|
|
saveAttempts = 0;
|
|
battleSave = AllocZeroed(sizeof(struct RecordedBattleSave));
|
|
savSection = AllocZeroed(SECTOR_SIZE);
|
|
|
|
for (i = 0; i < PARTY_SIZE; i++)
|
|
{
|
|
battleSave->playerParty[i] = sSavedPlayerParty[i];
|
|
battleSave->opponentParty[i] = sSavedOpponentParty[i];
|
|
}
|
|
|
|
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
|
|
{
|
|
for (j = 0; j < PLAYER_NAME_LENGTH + 1; j++)
|
|
battleSave->playersName[i][j] = sPlayers[i].name[j];
|
|
battleSave->playersGender[i] = sPlayers[i].gender;
|
|
battleSave->playersLanguage[i] = sPlayers[i].language;
|
|
battleSave->playersBattlers[i] = sPlayers[i].battlerId;
|
|
battleSave->playersTrainerId[i] = sPlayers[i].trainerId;
|
|
}
|
|
|
|
battleSave->rngSeed = gRecordedBattleRngSeed;
|
|
|
|
if (sBattleFlags & BATTLE_TYPE_LINK)
|
|
{
|
|
battleSave->battleFlags = (sBattleFlags & ~(BATTLE_TYPE_LINK | BATTLE_TYPE_LINK_IN_BATTLE)) | BATTLE_TYPE_RECORDED_LINK;
|
|
|
|
// BATTLE_TYPE_RECORDED_IS_MASTER set indicates battle will play
|
|
// out from player's perspective (i.e. player with back to camera)
|
|
// Otherwise player will appear on "opponent" side
|
|
if (sBattleFlags & BATTLE_TYPE_IS_MASTER)
|
|
{
|
|
battleSave->battleFlags |= BATTLE_TYPE_RECORDED_IS_MASTER;
|
|
}
|
|
else if (sBattleFlags & BATTLE_TYPE_MULTI)
|
|
{
|
|
switch (sPlayers[0].battlerId)
|
|
{
|
|
case 0:
|
|
case 2:
|
|
if (!(sPlayers[gRecordedBattleMultiplayerId].battlerId & 1))
|
|
battleSave->battleFlags |= BATTLE_TYPE_RECORDED_IS_MASTER;
|
|
break;
|
|
case 1:
|
|
case 3:
|
|
if ((sPlayers[gRecordedBattleMultiplayerId].battlerId & 1))
|
|
battleSave->battleFlags |= BATTLE_TYPE_RECORDED_IS_MASTER;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
battleSave->battleFlags = sBattleFlags;
|
|
}
|
|
|
|
battleSave->opponentA = gTrainerBattleOpponent_A;
|
|
battleSave->opponentB = gTrainerBattleOpponent_B;
|
|
battleSave->partnerId = gPartnerTrainerId;
|
|
battleSave->multiplayerId = gRecordedBattleMultiplayerId;
|
|
battleSave->lvlMode = gSaveBlock2Ptr->frontier.lvlMode;
|
|
battleSave->frontierFacility = sFrontierFacility;
|
|
battleSave->frontierBrainSymbol = sFrontierBrainSymbol;
|
|
battleSave->battleScene = gSaveBlock2Ptr->optionsBattleSceneOff;
|
|
battleSave->textSpeed = gSaveBlock2Ptr->optionsTextSpeed;
|
|
battleSave->AI_scripts = sAI_Scripts;
|
|
|
|
if (gTrainerBattleOpponent_A >= TRAINER_RECORD_MIXING_FRIEND && gTrainerBattleOpponent_A < TRAINER_RECORD_MIXING_APPRENTICE)
|
|
{
|
|
for (i = 0; i < PLAYER_NAME_LENGTH + 1; i++)
|
|
battleSave->recordMixFriendName[i] = gSaveBlock2Ptr->frontier.towerRecords[gTrainerBattleOpponent_A - TRAINER_RECORD_MIXING_FRIEND].name[i];
|
|
battleSave->recordMixFriendClass = gSaveBlock2Ptr->frontier.towerRecords[gTrainerBattleOpponent_A - TRAINER_RECORD_MIXING_FRIEND].facilityClass;
|
|
|
|
if (sBattleOutcome == B_OUTCOME_WON)
|
|
{
|
|
for (i = 0; i < EASY_CHAT_BATTLE_WORDS_COUNT; i++)
|
|
battleSave->easyChatSpeech[i] = gSaveBlock2Ptr->frontier.towerRecords[gTrainerBattleOpponent_A - TRAINER_RECORD_MIXING_FRIEND].speechLost[i];
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < EASY_CHAT_BATTLE_WORDS_COUNT; i++)
|
|
battleSave->easyChatSpeech[i] = gSaveBlock2Ptr->frontier.towerRecords[gTrainerBattleOpponent_A - TRAINER_RECORD_MIXING_FRIEND].speechWon[i];
|
|
}
|
|
battleSave->recordMixFriendLanguage = gSaveBlock2Ptr->frontier.towerRecords[gTrainerBattleOpponent_A - TRAINER_RECORD_MIXING_FRIEND].language;
|
|
}
|
|
else if (gTrainerBattleOpponent_B >= TRAINER_RECORD_MIXING_FRIEND && gTrainerBattleOpponent_B < TRAINER_RECORD_MIXING_APPRENTICE)
|
|
{
|
|
for (i = 0; i < PLAYER_NAME_LENGTH + 1; i++)
|
|
battleSave->recordMixFriendName[i] = gSaveBlock2Ptr->frontier.towerRecords[gTrainerBattleOpponent_B - TRAINER_RECORD_MIXING_FRIEND].name[i];
|
|
battleSave->recordMixFriendClass = gSaveBlock2Ptr->frontier.towerRecords[gTrainerBattleOpponent_B - TRAINER_RECORD_MIXING_FRIEND].facilityClass;
|
|
|
|
if (sBattleOutcome == B_OUTCOME_WON)
|
|
{
|
|
for (i = 0; i < EASY_CHAT_BATTLE_WORDS_COUNT; i++)
|
|
battleSave->easyChatSpeech[i] = gSaveBlock2Ptr->frontier.towerRecords[gTrainerBattleOpponent_B - TRAINER_RECORD_MIXING_FRIEND].speechLost[i];
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < EASY_CHAT_BATTLE_WORDS_COUNT; i++)
|
|
battleSave->easyChatSpeech[i] = gSaveBlock2Ptr->frontier.towerRecords[gTrainerBattleOpponent_B - TRAINER_RECORD_MIXING_FRIEND].speechWon[i];
|
|
}
|
|
battleSave->recordMixFriendLanguage = gSaveBlock2Ptr->frontier.towerRecords[gTrainerBattleOpponent_B - TRAINER_RECORD_MIXING_FRIEND].language;
|
|
}
|
|
else if (gPartnerTrainerId >= TRAINER_RECORD_MIXING_FRIEND && gPartnerTrainerId < TRAINER_RECORD_MIXING_APPRENTICE)
|
|
{
|
|
for (i = 0; i < PLAYER_NAME_LENGTH + 1; i++)
|
|
battleSave->recordMixFriendName[i] = gSaveBlock2Ptr->frontier.towerRecords[gPartnerTrainerId - TRAINER_RECORD_MIXING_FRIEND].name[i];
|
|
battleSave->recordMixFriendClass = gSaveBlock2Ptr->frontier.towerRecords[gPartnerTrainerId - TRAINER_RECORD_MIXING_FRIEND].facilityClass;
|
|
|
|
battleSave->recordMixFriendLanguage = gSaveBlock2Ptr->frontier.towerRecords[gPartnerTrainerId - TRAINER_RECORD_MIXING_FRIEND].language;
|
|
}
|
|
|
|
if (gTrainerBattleOpponent_A >= TRAINER_RECORD_MIXING_APPRENTICE)
|
|
{
|
|
battleSave->apprenticeId = gSaveBlock2Ptr->apprentices[gTrainerBattleOpponent_A - TRAINER_RECORD_MIXING_APPRENTICE].id;
|
|
for (i = 0; i < EASY_CHAT_BATTLE_WORDS_COUNT; i++)
|
|
battleSave->easyChatSpeech[i] = gSaveBlock2Ptr->apprentices[gTrainerBattleOpponent_A - TRAINER_RECORD_MIXING_APPRENTICE].speechWon[i];
|
|
battleSave->apprenticeLanguage = gSaveBlock2Ptr->apprentices[gTrainerBattleOpponent_A - TRAINER_RECORD_MIXING_APPRENTICE].language;
|
|
}
|
|
else if (gTrainerBattleOpponent_B >= TRAINER_RECORD_MIXING_APPRENTICE)
|
|
{
|
|
battleSave->apprenticeId = gSaveBlock2Ptr->apprentices[gTrainerBattleOpponent_B - TRAINER_RECORD_MIXING_APPRENTICE].id;
|
|
for (i = 0; i < EASY_CHAT_BATTLE_WORDS_COUNT; i++)
|
|
battleSave->easyChatSpeech[i] = gSaveBlock2Ptr->apprentices[gTrainerBattleOpponent_B - TRAINER_RECORD_MIXING_APPRENTICE].speechWon[i];
|
|
battleSave->apprenticeLanguage = gSaveBlock2Ptr->apprentices[gTrainerBattleOpponent_B - TRAINER_RECORD_MIXING_APPRENTICE].language;
|
|
}
|
|
else if (gPartnerTrainerId >= TRAINER_RECORD_MIXING_APPRENTICE)
|
|
{
|
|
battleSave->apprenticeId = gSaveBlock2Ptr->apprentices[gPartnerTrainerId - TRAINER_RECORD_MIXING_APPRENTICE].id;
|
|
|
|
battleSave->apprenticeLanguage = gSaveBlock2Ptr->apprentices[gPartnerTrainerId - TRAINER_RECORD_MIXING_APPRENTICE].language;
|
|
}
|
|
|
|
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
|
|
for (j = 0; j < BATTLER_RECORD_SIZE; j++)
|
|
battleSave->battleRecord[i][j] = sBattleRecords[i][j];
|
|
|
|
while (1)
|
|
{
|
|
ret = RecordedBattleToSave(battleSave, savSection);
|
|
if (ret == TRUE)
|
|
break;
|
|
saveAttempts++;
|
|
if (saveAttempts >= 3)
|
|
break;
|
|
}
|
|
|
|
free(battleSave);
|
|
free(savSection);
|
|
return ret;
|
|
}
|
|
|
|
static bool32 TryCopyRecordedBattleSaveData(struct RecordedBattleSave *dst, struct SaveSector *saveBuffer)
|
|
{
|
|
if (TryReadSpecialSaveSector(SECTOR_ID_RECORDED_BATTLE, (void*)(saveBuffer)) != SAVE_STATUS_OK)
|
|
return FALSE;
|
|
|
|
memcpy(dst, saveBuffer, sizeof(struct RecordedBattleSave));
|
|
|
|
if (!IsRecordedBattleSaveValid(dst))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static bool32 CopyRecordedBattleFromSave(struct RecordedBattleSave *dst)
|
|
{
|
|
struct SaveSector *savBuffer = AllocZeroed(SECTOR_SIZE);
|
|
bool32 ret = TryCopyRecordedBattleSaveData(dst, savBuffer);
|
|
Free(savBuffer);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void CB2_RecordedBattleEnd(void)
|
|
{
|
|
gSaveBlock2Ptr->frontier.lvlMode = sLvlMode;
|
|
gBattleOutcome = 0;
|
|
gBattleTypeFlags = 0;
|
|
gTrainerBattleOpponent_A = 0;
|
|
gTrainerBattleOpponent_B = 0;
|
|
gPartnerTrainerId = 0;
|
|
|
|
RecordedBattle_RestoreSavedParties();
|
|
SetMainCallback2(sCallback2_AfterRecordedBattle);
|
|
}
|
|
|
|
#define tFramesToWait data[0]
|
|
|
|
static void Task_StartAfterCountdown(u8 taskId)
|
|
{
|
|
if (--gTasks[taskId].tFramesToWait == 0)
|
|
{
|
|
gMain.savedCallback = CB2_RecordedBattleEnd;
|
|
SetMainCallback2(CB2_InitBattle);
|
|
DestroyTask(taskId);
|
|
}
|
|
}
|
|
|
|
static void SetVariablesForRecordedBattle(struct RecordedBattleSave *src)
|
|
{
|
|
bool8 var;
|
|
s32 i, j;
|
|
|
|
ZeroPlayerPartyMons();
|
|
ZeroEnemyPartyMons();
|
|
|
|
for (i = 0; i < PARTY_SIZE; i++)
|
|
{
|
|
gPlayerParty[i] = src->playerParty[i];
|
|
gEnemyParty[i] = src->opponentParty[i];
|
|
}
|
|
|
|
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
|
|
{
|
|
for (var = FALSE, j = 0; j < PLAYER_NAME_LENGTH + 1; j++)
|
|
{
|
|
gLinkPlayers[i].name[j] = src->playersName[i][j];
|
|
if (src->playersName[i][j] == EOS)
|
|
var = TRUE;
|
|
}
|
|
gLinkPlayers[i].gender = src->playersGender[i];
|
|
gLinkPlayers[i].language = src->playersLanguage[i];
|
|
gLinkPlayers[i].id = src->playersBattlers[i];
|
|
gLinkPlayers[i].trainerId = src->playersTrainerId[i];
|
|
|
|
if (var)
|
|
ConvertInternationalString(gLinkPlayers[i].name, gLinkPlayers[i].language);
|
|
}
|
|
|
|
gRecordedBattleRngSeed = src->rngSeed;
|
|
gBattleTypeFlags = src->battleFlags | BATTLE_TYPE_RECORDED;
|
|
gTrainerBattleOpponent_A = src->opponentA;
|
|
gTrainerBattleOpponent_B = src->opponentB;
|
|
gPartnerTrainerId = src->partnerId;
|
|
gRecordedBattleMultiplayerId = src->multiplayerId;
|
|
sLvlMode = gSaveBlock2Ptr->frontier.lvlMode;
|
|
sFrontierFacility = src->frontierFacility;
|
|
sFrontierBrainSymbol = src->frontierBrainSymbol;
|
|
sBattleScene = src->battleScene;
|
|
sTextSpeed = src->textSpeed;
|
|
sAI_Scripts = src->AI_scripts;
|
|
|
|
for (i = 0; i < PLAYER_NAME_LENGTH + 1; i++)
|
|
sRecordMixFriendName[i] = src->recordMixFriendName[i];
|
|
|
|
sRecordMixFriendClass = src->recordMixFriendClass;
|
|
sApprenticeId = src->apprenticeId;
|
|
sRecordMixFriendLanguage = src->recordMixFriendLanguage;
|
|
sApprenticeLanguage = src->apprenticeLanguage;
|
|
|
|
for (i = 0; i < EASY_CHAT_BATTLE_WORDS_COUNT; i++)
|
|
sEasyChatSpeech[i] = src->easyChatSpeech[i];
|
|
|
|
gSaveBlock2Ptr->frontier.lvlMode = src->lvlMode;
|
|
|
|
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
|
|
for (j = 0; j < BATTLER_RECORD_SIZE; j++)
|
|
sBattleRecords[i][j] = src->battleRecord[i][j];
|
|
}
|
|
|
|
void PlayRecordedBattle(void (*CB2_After)(void))
|
|
{
|
|
struct RecordedBattleSave *battleSave = AllocZeroed(sizeof(struct RecordedBattleSave));
|
|
if (CopyRecordedBattleFromSave(battleSave) == TRUE)
|
|
{
|
|
u8 taskId;
|
|
|
|
RecordedBattle_SaveParties();
|
|
SetVariablesForRecordedBattle(battleSave);
|
|
|
|
taskId = CreateTask(Task_StartAfterCountdown, 1);
|
|
gTasks[taskId].tFramesToWait = 128;
|
|
|
|
sCallback2_AfterRecordedBattle = CB2_After;
|
|
PlayMapChosenOrBattleBGM(FALSE);
|
|
SetMainCallback2(CB2_RecordedBattle);
|
|
}
|
|
Free(battleSave);
|
|
}
|
|
|
|
#undef tFramesToWait
|
|
|
|
static void CB2_RecordedBattle(void)
|
|
{
|
|
AnimateSprites();
|
|
BuildOamBuffer();
|
|
RunTasks();
|
|
}
|
|
|
|
u8 GetRecordedBattleFrontierFacility(void)
|
|
{
|
|
return sFrontierFacility;
|
|
}
|
|
|
|
u8 GetRecordedBattleFronterBrainSymbol(void)
|
|
{
|
|
return sFrontierBrainSymbol;
|
|
}
|
|
|
|
void RecordedBattle_SaveParties(void)
|
|
{
|
|
s32 i;
|
|
|
|
for (i = 0; i < PARTY_SIZE; i++)
|
|
{
|
|
sSavedPlayerParty[i] = gPlayerParty[i];
|
|
sSavedOpponentParty[i] = gEnemyParty[i];
|
|
}
|
|
}
|
|
|
|
static void RecordedBattle_RestoreSavedParties(void)
|
|
{
|
|
s32 i;
|
|
|
|
for (i = 0; i < PARTY_SIZE; i++)
|
|
{
|
|
gPlayerParty[i] = sSavedPlayerParty[i];
|
|
gEnemyParty[i] = sSavedOpponentParty[i];
|
|
}
|
|
}
|
|
|
|
u8 GetActiveBattlerLinkPlayerGender(void)
|
|
{
|
|
s32 i;
|
|
|
|
for (i = 0; i < MAX_LINK_PLAYERS; i++)
|
|
{
|
|
if (gLinkPlayers[i].id == gActiveBattler)
|
|
break;
|
|
}
|
|
|
|
if (i != MAX_LINK_PLAYERS)
|
|
return gLinkPlayers[i].gender;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void RecordedBattle_ClearFrontierPassFlag(void)
|
|
{
|
|
sFrontierPassFlag = 0;
|
|
}
|
|
|
|
// Set sFrontierPassFlag to received state of FLAG_SYS_FRONTIER_PASS
|
|
void RecordedBattle_SetFrontierPassFlagFromHword(u16 flags)
|
|
{
|
|
sFrontierPassFlag |= (flags & (1 << 15)) >> 15;
|
|
}
|
|
|
|
u8 RecordedBattle_GetFrontierPassFlag(void)
|
|
{
|
|
return sFrontierPassFlag;
|
|
}
|
|
|
|
u8 GetBattleSceneInRecordedBattle(void)
|
|
{
|
|
return sBattleScene;
|
|
}
|
|
|
|
u8 GetTextSpeedInRecordedBattle(void)
|
|
{
|
|
return sTextSpeed;
|
|
}
|
|
|
|
void RecordedBattle_CopyBattlerMoves(void)
|
|
{
|
|
s32 i;
|
|
|
|
if (GetBattlerSide(gActiveBattler) == B_SIDE_OPPONENT)
|
|
return;
|
|
if (gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK))
|
|
return;
|
|
if (sRecordMode == B_RECORD_MODE_PLAYBACK)
|
|
return;
|
|
|
|
for (i = 0; i < MAX_MON_MOVES; i++)
|
|
sPlayerMonMoves[gActiveBattler / 2][i] = gBattleMons[gActiveBattler].moves[i];
|
|
}
|
|
|
|
// This is a special battle action only used by this function
|
|
// It shares a value with B_ACTION_SAFARI_POKEBLOCK, which can never occur in a recorded battle.
|
|
#define ACTION_MOVE_CHANGE 6
|
|
|
|
void RecordedBattle_CheckMovesetChanges(u8 mode)
|
|
{
|
|
s32 battlerId, j, k;
|
|
|
|
if (gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK))
|
|
return;
|
|
|
|
for (battlerId = 0; battlerId < gBattlersCount; battlerId++)
|
|
{
|
|
// Player's side only
|
|
if (GetBattlerSide(battlerId) != B_SIDE_OPPONENT)
|
|
{
|
|
if (mode == B_RECORD_MODE_RECORDING)
|
|
{
|
|
// Check if any of the battler's moves have changed.
|
|
for (j = 0; j < MAX_MON_MOVES; j++)
|
|
{
|
|
if (gBattleMons[battlerId].moves[j] != sPlayerMonMoves[battlerId / 2][j])
|
|
break;
|
|
}
|
|
if (j != MAX_MON_MOVES)
|
|
{
|
|
// At least one of the moves has been changed
|
|
RecordedBattle_SetBattlerAction(battlerId, ACTION_MOVE_CHANGE);
|
|
for (j = 0; j < MAX_MON_MOVES; j++)
|
|
{
|
|
for (k = 0; k < MAX_MON_MOVES; k++)
|
|
{
|
|
if (gBattleMons[battlerId].moves[j] == sPlayerMonMoves[battlerId / 2][k])
|
|
{
|
|
RecordedBattle_SetBattlerAction(battlerId, k);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else // B_RECORD_MODE_PLAYBACK
|
|
{
|
|
if (sBattleRecords[battlerId][sBattlerRecordSizes[battlerId]] == ACTION_MOVE_CHANGE)
|
|
{
|
|
u8 ppBonuses[MAX_MON_MOVES];
|
|
u8 moveSlots[MAX_MON_MOVES];
|
|
u8 mimickedMoveSlots[MAX_MON_MOVES];
|
|
struct ChooseMoveStruct movePp;
|
|
u8 ppBonusSet;
|
|
|
|
// We know the current action is ACTION_MOVE_CHANGE, retrieve
|
|
// it without saving it to move on to the next action.
|
|
RecordedBattle_GetBattlerAction(battlerId);
|
|
|
|
for (j = 0; j < MAX_MON_MOVES; j++)
|
|
ppBonuses[j] = ((gBattleMons[battlerId].ppBonuses & (3 << (j << 1))) >> (j << 1));
|
|
|
|
for (j = 0; j < MAX_MON_MOVES; j++)
|
|
{
|
|
moveSlots[j] = RecordedBattle_GetBattlerAction(battlerId);
|
|
movePp.moves[j] = gBattleMons[battlerId].moves[moveSlots[j]];
|
|
movePp.currentPp[j] = gBattleMons[battlerId].pp[moveSlots[j]];
|
|
movePp.maxPp[j] = ppBonuses[moveSlots[j]];
|
|
mimickedMoveSlots[j] = (gDisableStructs[battlerId].mimickedMoves & gBitTable[j]) >> j;
|
|
}
|
|
for (j = 0; j < MAX_MON_MOVES; j++)
|
|
{
|
|
gBattleMons[battlerId].moves[j] = movePp.moves[j];
|
|
gBattleMons[battlerId].pp[j] = movePp.currentPp[j];
|
|
}
|
|
gBattleMons[battlerId].ppBonuses = 0;
|
|
gDisableStructs[battlerId].mimickedMoves = 0;
|
|
for (j = 0; j < MAX_MON_MOVES; j++)
|
|
{
|
|
gBattleMons[battlerId].ppBonuses |= movePp.maxPp[j] << (j << 1);
|
|
gDisableStructs[battlerId].mimickedMoves |= mimickedMoveSlots[j] << j;
|
|
}
|
|
|
|
if (!(gBattleMons[battlerId].status2 & STATUS2_TRANSFORMED))
|
|
{
|
|
for (j = 0; j < MAX_MON_MOVES; j++)
|
|
ppBonuses[j] = (GetMonData(&gPlayerParty[gBattlerPartyIndexes[battlerId]], MON_DATA_PP_BONUSES, NULL) & ((3 << (j << 1)))) >> (j << 1);
|
|
|
|
for (j = 0; j < MAX_MON_MOVES; j++)
|
|
{
|
|
movePp.moves[j] = GetMonData(&gPlayerParty[gBattlerPartyIndexes[battlerId]], MON_DATA_MOVE1 + moveSlots[j], NULL);
|
|
movePp.currentPp[j] = GetMonData(&gPlayerParty[gBattlerPartyIndexes[battlerId]], MON_DATA_PP1 + moveSlots[j], NULL);
|
|
movePp.maxPp[j] = ppBonuses[moveSlots[j]];
|
|
}
|
|
for (j = 0; j < MAX_MON_MOVES; j++)
|
|
{
|
|
SetMonData(&gPlayerParty[gBattlerPartyIndexes[battlerId]], MON_DATA_MOVE1 + j, &movePp.moves[j]);
|
|
SetMonData(&gPlayerParty[gBattlerPartyIndexes[battlerId]], MON_DATA_PP1 + j, &movePp.currentPp[j]);
|
|
}
|
|
ppBonusSet = 0;
|
|
for (j = 0; j < MAX_MON_MOVES; j++)
|
|
ppBonusSet |= movePp.maxPp[j] << (j << 1);
|
|
|
|
SetMonData(&gPlayerParty[gBattlerPartyIndexes[battlerId]], MON_DATA_PP_BONUSES, &ppBonusSet);
|
|
}
|
|
gChosenMoveByBattler[battlerId] = gBattleMons[battlerId].moves[*(gBattleStruct->chosenMovePositions + battlerId)];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
u32 GetAiScriptsInRecordedBattle(void)
|
|
{
|
|
return sAI_Scripts;
|
|
}
|
|
|
|
// Used to determine when the player is allowed to press B to end a recorded battle's playback
|
|
void RecordedBattle_SetPlaybackFinished(void)
|
|
{
|
|
sIsPlaybackFinished = TRUE;
|
|
}
|
|
|
|
bool8 RecordedBattle_CanStopPlayback(void)
|
|
{
|
|
return (sIsPlaybackFinished == FALSE);
|
|
}
|
|
|
|
void GetRecordedBattleRecordMixFriendName(u8 *dst)
|
|
{
|
|
s32 i;
|
|
|
|
for (i = 0; i < PLAYER_NAME_LENGTH + 1; i++)
|
|
dst[i] = sRecordMixFriendName[i];
|
|
dst[PLAYER_NAME_LENGTH] = EOS;
|
|
ConvertInternationalString(dst, sRecordMixFriendLanguage);
|
|
}
|
|
|
|
u8 GetRecordedBattleRecordMixFriendClass(void)
|
|
{
|
|
return sRecordMixFriendClass;
|
|
}
|
|
|
|
u8 GetRecordedBattleApprenticeId(void)
|
|
{
|
|
return sApprenticeId;
|
|
}
|
|
|
|
u8 GetRecordedBattleRecordMixFriendLanguage(void)
|
|
{
|
|
return sRecordMixFriendLanguage;
|
|
}
|
|
|
|
u8 GetRecordedBattleApprenticeLanguage(void)
|
|
{
|
|
return sApprenticeLanguage;
|
|
}
|
|
|
|
void RecordedBattle_SaveBattleOutcome(void)
|
|
{
|
|
sBattleOutcome = gBattleOutcome;
|
|
}
|
|
|
|
u16 *GetRecordedBattleEasyChatSpeech(void)
|
|
{
|
|
return sEasyChatSpeech;
|
|
}
|