#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; }; 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; }