#include "global.h" #include "malloc.h" #include "random.h" #include "constants/items.h" #include "text.h" #include "item.h" #include "task.h" #include "save.h" #include "load_save.h" #include "pokemon.h" #include "cable_club.h" #include "link.h" #include "link_rfu.h" #include "tv.h" #include "battle_tower.h" #include "window.h" #include "mystery_event_script.h" #include "secret_base.h" #include "mauville_old_man.h" #include "sound.h" #include "constants/songs.h" #include "menu.h" #include "overworld.h" #include "field_screen_effect.h" #include "fldeff_misc.h" #include "script.h" #include "event_data.h" #include "lilycove_lady.h" #include "strings.h" #include "string_util.h" #include "record_mixing.h" #include "new_game.h" #include "daycare.h" #include "international_string_util.h" #include "constants/battle_frontier.h" #include "dewford_trend.h" // Number of bytes of the record transferred at a time #define BUFFER_CHUNK_SIZE 200 #define NUM_SWAP_COMBOS 3 // Used by several tasks in this file #define tState data[0] struct RecordMixingHallRecords { struct RankingHall1P hallRecords1P[HALL_FACILITIES_COUNT][FRONTIER_LVL_MODE_COUNT][HALL_RECORDS_COUNT * 2]; struct RankingHall2P hallRecords2P[FRONTIER_LVL_MODE_COUNT][HALL_RECORDS_COUNT * 2]; }; struct PlayerRecordRS { struct SecretBase secretBases[SECRET_BASES_COUNT]; TVShow tvShows[TV_SHOWS_COUNT]; PokeNews pokeNews[POKE_NEWS_COUNT]; OldMan oldMan; struct DewfordTrend dewfordTrends[SAVED_TRENDS_COUNT]; struct RecordMixingDaycareMail daycareMail; struct RSBattleTowerRecord battleTowerRecord; u16 giftItem; u16 padding[50]; }; struct PlayerRecordEmerald { /* 0x0000 */ struct SecretBase secretBases[SECRET_BASES_COUNT]; /* 0x0c80 */ TVShow tvShows[TV_SHOWS_COUNT]; /* 0x1004 */ PokeNews pokeNews[POKE_NEWS_COUNT]; /* 0x1044 */ OldMan oldMan; /* 0x1084 */ struct DewfordTrend dewfordTrends[SAVED_TRENDS_COUNT]; /* 0x10ac */ struct RecordMixingDaycareMail daycareMail; /* 0x1124 */ struct EmeraldBattleTowerRecord battleTowerRecord; /* 0x1210 */ u16 giftItem; /* 0x1214 */ LilycoveLady lilycoveLady; /* 0x1254 */ struct Apprentice apprentices[2]; /* 0x12dc */ struct PlayerHallRecords hallRecords; /* 0x1434 */ u8 padding[16]; }; // 0x1444 union PlayerRecord { struct PlayerRecordRS ruby; struct PlayerRecordEmerald emerald; }; static bool8 sReadyToReceive; static struct SecretBase *sSecretBasesSave; static TVShow *sTvShowsSave; static PokeNews *sPokeNewsSave; static OldMan *sOldManSave; static struct DewfordTrend *sDewfordTrendsSave; static struct RecordMixingDaycareMail *sRecordMixMailSave; static void *sBattleTowerSave; static LilycoveLady *sLilycoveLadySave; static void *sApprenticesSave; static void *sBattleTowerSave_Duplicate; static u32 sRecordStructSize; static u8 sDaycareMailRandSum; static struct PlayerHallRecords *sPartnerHallRecords[HALL_RECORDS_COUNT]; static EWRAM_DATA struct RecordMixingDaycareMail sRecordMixMail = {0}; static EWRAM_DATA union PlayerRecord *sReceivedRecords = NULL; static EWRAM_DATA union PlayerRecord *sSentRecord = NULL; static void Task_RecordMixing_Main(u8); static void Task_MixingRecordsRecv(u8); static void Task_SendPacket(u8); static void Task_CopyReceiveBuffer(u8); static void Task_SendPacket_SwitchToReceive(u8); static void *LoadPtrFromTaskData(const u16 *); static void StorePtrInTaskData(void *, u16 *); static u8 GetMultiplayerId_(void); static void *GetPlayerRecvBuffer(u8); static void ReceiveOldManData(OldMan *, size_t, u8); static void ReceiveBattleTowerData(void *, size_t, u8); static void ReceiveLilycoveLadyData(LilycoveLady *, size_t, u8); static void CalculateDaycareMailRandSum(const u8 *); static void ReceiveDaycareMailData(struct RecordMixingDaycareMail *, size_t, u8, TVShow *); static void ReceiveGiftItem(u16 *, u8 ); static void Task_DoRecordMixing(u8); static void GetSavedApprentices(struct Apprentice *, struct Apprentice *); static void ReceiveApprenticeData(struct Apprentice *, size_t, u32); static void ReceiveRankingHallRecords(struct PlayerHallRecords *, size_t, u32); static void GetRecordMixingDaycareMail(struct RecordMixingDaycareMail *); static void SanitizeDaycareMailForRuby(struct RecordMixingDaycareMail *); static void SanitizeEmeraldBattleTowerRecord(struct EmeraldBattleTowerRecord *); static void SanitizeRubyBattleTowerRecord(struct RSBattleTowerRecord *); static const u8 sPlayerIdxOrders_2Player[] = {1, 0}; static const u8 sPlayerIdxOrders_3Player[][3] = { {1, 2, 0}, {2, 0, 1}, }; static const u8 sPlayerIdxOrders_4Player[][4] = { {1, 0, 3, 2}, {3, 0, 1, 2}, {2, 0, 3, 1}, {1, 3, 0, 2}, {2, 3, 0, 1}, {3, 2, 0, 1}, {1, 2, 3, 0}, {2, 3, 1, 0}, {3, 2, 1, 0}, }; // When 3 players can swap mail 2 players are randomly selected and the 3rd is left out static const u8 sDaycareMailSwapIds_3Player[NUM_SWAP_COMBOS][2] = { {0, 1}, {1, 2}, {2, 0}, }; static const u8 sDaycareMailSwapIds_4Player[NUM_SWAP_COMBOS][4] = { {0, 1, 2, 3}, // 0 swaps with 1, 2 swaps with 3 {0, 2, 1, 3}, {0, 3, 2, 1}, }; void RecordMixingPlayerSpotTriggered(void) { CreateTask_EnterCableClubSeat(Task_RecordMixing_Main); } // these variables were const in R/S, but had to become changeable because of saveblocks changing RAM position static void SetSrcLookupPointers(void) { sSecretBasesSave = gSaveBlock1Ptr->secretBases; sTvShowsSave = gSaveBlock1Ptr->tvShows; sPokeNewsSave = gSaveBlock1Ptr->pokeNews; sOldManSave = &gSaveBlock1Ptr->oldMan; sDewfordTrendsSave = gSaveBlock1Ptr->dewfordTrends; sRecordMixMailSave = &sRecordMixMail; sBattleTowerSave = &gSaveBlock2Ptr->frontier.towerPlayer; sLilycoveLadySave = &gSaveBlock1Ptr->lilycoveLady; sApprenticesSave = gSaveBlock2Ptr->apprentices; sBattleTowerSave_Duplicate = &gSaveBlock2Ptr->frontier.towerPlayer; } static void PrepareUnknownExchangePacket(struct PlayerRecordRS *dest) { memcpy(dest->secretBases, sSecretBasesSave, sizeof(dest->secretBases)); memcpy(dest->tvShows, sTvShowsSave, sizeof(dest->tvShows)); SanitizeTVShowLocationsForRuby(dest->tvShows); memcpy(dest->pokeNews, sPokeNewsSave, sizeof(dest->pokeNews)); memcpy(&dest->oldMan, sOldManSave, sizeof(dest->oldMan)); memcpy(dest->dewfordTrends, sDewfordTrendsSave, sizeof(dest->dewfordTrends)); GetRecordMixingDaycareMail(&dest->daycareMail); EmeraldBattleTowerRecordToRuby(sBattleTowerSave, &dest->battleTowerRecord); if (GetMultiplayerId() == 0) dest->giftItem = GetRecordMixingGift(); } static void PrepareExchangePacketForRubySapphire(struct PlayerRecordRS *dest) { memcpy(dest->secretBases, sSecretBasesSave, sizeof(dest->secretBases)); ClearJapaneseSecretBases(dest->secretBases); memcpy(dest->tvShows, sTvShowsSave, sizeof(dest->tvShows)); SanitizeTVShowsForRuby(dest->tvShows); memcpy(dest->pokeNews, sPokeNewsSave, sizeof(dest->pokeNews)); memcpy(&dest->oldMan, sOldManSave, sizeof(dest->oldMan)); SanitizeMauvilleOldManForRuby(&dest->oldMan); memcpy(dest->dewfordTrends, sDewfordTrendsSave, sizeof(dest->dewfordTrends)); GetRecordMixingDaycareMail(&dest->daycareMail); SanitizeDaycareMailForRuby(&dest->daycareMail); EmeraldBattleTowerRecordToRuby(sBattleTowerSave, &dest->battleTowerRecord); SanitizeRubyBattleTowerRecord(&dest->battleTowerRecord); if (GetMultiplayerId() == 0) dest->giftItem = GetRecordMixingGift(); } static void PrepareExchangePacket(void) { SetPlayerSecretBaseParty(); DeactivateAllNormalTVShows(); SetSrcLookupPointers(); if (Link_AnyPartnersPlayingRubyOrSapphire()) { if (LinkDummy_Return2() == 0) PrepareUnknownExchangePacket(&sSentRecord->ruby); else PrepareExchangePacketForRubySapphire(&sSentRecord->ruby); } else { memcpy(sSentRecord->emerald.secretBases, sSecretBasesSave, sizeof(sSentRecord->emerald.secretBases)); memcpy(sSentRecord->emerald.tvShows, sTvShowsSave, sizeof(sSentRecord->emerald.tvShows)); memcpy(sSentRecord->emerald.pokeNews, sPokeNewsSave, sizeof(sSentRecord->emerald.pokeNews)); memcpy(&sSentRecord->emerald.oldMan, sOldManSave, sizeof(sSentRecord->emerald.oldMan)); memcpy(&sSentRecord->emerald.lilycoveLady, sLilycoveLadySave, sizeof(sSentRecord->emerald.lilycoveLady)); memcpy(sSentRecord->emerald.dewfordTrends, sDewfordTrendsSave, sizeof(sSentRecord->emerald.dewfordTrends)); GetRecordMixingDaycareMail(&sSentRecord->emerald.daycareMail); memcpy(&sSentRecord->emerald.battleTowerRecord, sBattleTowerSave, sizeof(sSentRecord->emerald.battleTowerRecord)); SanitizeEmeraldBattleTowerRecord(&sSentRecord->emerald.battleTowerRecord); if (GetMultiplayerId() == 0) sSentRecord->emerald.giftItem = GetRecordMixingGift(); GetSavedApprentices(sSentRecord->emerald.apprentices, sApprenticesSave); GetPlayerHallRecords(&sSentRecord->emerald.hallRecords); } } static void ReceiveExchangePacket(u32 multiplayerId) { if (Link_AnyPartnersPlayingRubyOrSapphire()) { // Ruby/Sapphire CalculateDaycareMailRandSum((void *)sReceivedRecords->ruby.tvShows); ReceiveSecretBasesData(sReceivedRecords->ruby.secretBases, sizeof(sReceivedRecords->ruby), multiplayerId); ReceiveDaycareMailData(&sReceivedRecords->ruby.daycareMail, sizeof(sReceivedRecords->ruby), multiplayerId, sReceivedRecords->ruby.tvShows); ReceiveBattleTowerData(&sReceivedRecords->ruby.battleTowerRecord, sizeof(sReceivedRecords->ruby), multiplayerId); ReceiveTvShowsData(sReceivedRecords->ruby.tvShows, sizeof(sReceivedRecords->ruby), multiplayerId); ReceivePokeNewsData(sReceivedRecords->ruby.pokeNews, sizeof(sReceivedRecords->ruby), multiplayerId); ReceiveOldManData(&sReceivedRecords->ruby.oldMan, sizeof(sReceivedRecords->ruby), multiplayerId); ReceiveDewfordTrendData(sReceivedRecords->ruby.dewfordTrends, sizeof(sReceivedRecords->ruby), multiplayerId); ReceiveGiftItem(&sReceivedRecords->ruby.giftItem, multiplayerId); } else { // Emerald CalculateDaycareMailRandSum((void *)sReceivedRecords->emerald.tvShows); ReceiveSecretBasesData(sReceivedRecords->emerald.secretBases, sizeof(sReceivedRecords->emerald), multiplayerId); ReceiveTvShowsData(sReceivedRecords->emerald.tvShows, sizeof(sReceivedRecords->emerald), multiplayerId); ReceivePokeNewsData(sReceivedRecords->emerald.pokeNews, sizeof(sReceivedRecords->emerald), multiplayerId); ReceiveOldManData(&sReceivedRecords->emerald.oldMan, sizeof(sReceivedRecords->emerald), multiplayerId); ReceiveDewfordTrendData(sReceivedRecords->emerald.dewfordTrends, sizeof(sReceivedRecords->emerald), multiplayerId); ReceiveDaycareMailData(&sReceivedRecords->emerald.daycareMail, sizeof(sReceivedRecords->emerald), multiplayerId, sReceivedRecords->emerald.tvShows); ReceiveBattleTowerData(&sReceivedRecords->emerald.battleTowerRecord, sizeof(sReceivedRecords->emerald), multiplayerId); ReceiveGiftItem(&sReceivedRecords->emerald.giftItem, multiplayerId); ReceiveLilycoveLadyData(&sReceivedRecords->emerald.lilycoveLady, sizeof(sReceivedRecords->emerald), multiplayerId); ReceiveApprenticeData(sReceivedRecords->emerald.apprentices, sizeof(sReceivedRecords->emerald), (u8)multiplayerId); ReceiveRankingHallRecords(&sReceivedRecords->emerald.hallRecords, sizeof(sReceivedRecords->emerald), (u8)multiplayerId); } } static void PrintTextOnRecordMixing(const u8 *src) { DrawDialogueFrame(0, 0); AddTextPrinterParameterized(0, FONT_NORMAL, src, 0, 1, 0, NULL); CopyWindowToVram(0, COPYWIN_FULL); } #define tCounter data[0] static void Task_RecordMixing_SoundEffect(u8 taskId) { if (++gTasks[taskId].tCounter == 50) { PlaySE(SE_M_ATTRACT); gTasks[taskId].tCounter = 0; } } #undef tCounter #define tTimer data[8] #define tLinkTaskId data[10] #define tSoundTaskId data[15] // Note: gSpecialVar_0x8005 here contains the player's spot id. static void Task_RecordMixing_Main(u8 taskId) { s16 *data = gTasks[taskId].data; switch (tState) { case 0: // init sSentRecord = malloc(sizeof(*sSentRecord)); sReceivedRecords = malloc(sizeof(*sReceivedRecords) * MAX_LINK_PLAYERS); SetLocalLinkPlayerId(gSpecialVar_0x8005); VarSet(VAR_TEMP_0, 1); sReadyToReceive = FALSE; PrepareExchangePacket(); CreateRecordMixingLights(); tState = 1; tLinkTaskId = CreateTask(Task_MixingRecordsRecv, 80); tSoundTaskId = CreateTask(Task_RecordMixing_SoundEffect, 81); break; case 1: // wait for Task_MixingRecordsRecv if (!gTasks[tLinkTaskId].isActive) { tState = 2; FlagSet(FLAG_SYS_MIX_RECORD); DestroyRecordMixingLights(); DestroyTask(tSoundTaskId); } break; case 2: tLinkTaskId = CreateTask(Task_DoRecordMixing, 10); tState = 3; PlaySE(SE_M_BATON_PASS); break; case 3: // wait for Task_DoRecordMixing if (!gTasks[tLinkTaskId].isActive) { tState = 4; if (gWirelessCommType == 0) tLinkTaskId = CreateTask_ReestablishCableClubLink(); PrintTextOnRecordMixing(gText_RecordMixingComplete); tTimer = 0; } break; case 4: // wait 60 frames if (++tTimer > 60) tState = 5; break; case 5: // Wait for the task created by CreateTask_ReestablishCableClubLink if (!gTasks[tLinkTaskId].isActive) { free(sReceivedRecords); free(sSentRecord); SetLinkWaitingForScript(); if (gWirelessCommType != 0) CreateTask(Task_ReturnToFieldRecordMixing, 10); ClearDialogWindowAndFrame(0, 1); DestroyTask(taskId); EnableBothScriptContexts(); } break; } } #undef tTimer #undef tLinkTaskId #undef tSoundTaskId // Task data for Task_MixingRecordsRecv and subsequent tasks #define tSentRecord data[2] // Used to store a ptr, so data[2] and data[3] #define tNumChunksSent data[4] #define tMultiplayerId data[5] #define tCopyTaskId data[10] // Task data for Task_CopyReceiveBuffer #define tParentTaskId data[0] #define tNumChunksRecv(i) data[1 + (i)] // Number of chunks of the record received per player #define tRecvRecords data[5] // Used to store a ptr, so data[5] and data[6] static void Task_MixingRecordsRecv(u8 taskId) { struct Task *task = &gTasks[taskId]; switch (task->tState) { case 0: PrintTextOnRecordMixing(gText_MixingRecords); task->data[8] = 0x708; task->tState = 400; ClearLinkCallback_2(); break; case 100: // wait 20 frames if (++task->data[12] > 20) { task->data[12] = 0; task->tState = 101; } break; case 101: { u8 players = GetLinkPlayerCount_2(); if (IsLinkMaster() == TRUE) { if (players == GetSavedPlayerCount()) { PlaySE(SE_PIN); task->tState = 201; task->data[12] = 0; } } else { PlaySE(SE_BOO); task->tState = 301; } } break; case 201: // We're the link master. Delay for 30 frames per connected player. if (GetSavedPlayerCount() == GetLinkPlayerCount_2() && ++task->data[12] > (GetLinkPlayerCount_2() * 30)) { CheckShouldAdvanceLinkState(); task->tState = 1; } break; case 301: if (GetSavedPlayerCount() == GetLinkPlayerCount_2()) task->tState = 1; break; case 400: // wait 20 frames if (++task->data[12] > 20) { task->tState = 1; task->data[12] = 0; } break; case 1: // wait for handshake if (gReceivedRemoteLinkPlayers != 0) { ConvertIntToDecimalStringN(gStringVar1, GetMultiplayerId_(), STR_CONV_MODE_LEADING_ZEROS, 2); task->tState = 5; } break; case 2: { u8 subTaskId; task->data[6] = GetLinkPlayerCount_2(); task->tState = 0; task->tMultiplayerId = GetMultiplayerId_(); task->func = Task_SendPacket; if (Link_AnyPartnersPlayingRubyOrSapphire()) { StorePtrInTaskData(sSentRecord, &task->tSentRecord); subTaskId = CreateTask(Task_CopyReceiveBuffer, 80); task->tCopyTaskId = subTaskId; gTasks[subTaskId].tParentTaskId = taskId; StorePtrInTaskData(sReceivedRecords, &gTasks[subTaskId].tRecvRecords); sRecordStructSize = sizeof(struct PlayerRecordRS); } else { StorePtrInTaskData(sSentRecord, &task->tSentRecord); subTaskId = CreateTask(Task_CopyReceiveBuffer, 80); task->tCopyTaskId = subTaskId; gTasks[subTaskId].tParentTaskId = taskId; StorePtrInTaskData(sReceivedRecords, &gTasks[subTaskId].tRecvRecords); sRecordStructSize = sizeof(struct PlayerRecordEmerald); } } break; case 5: // wait 60 frames if (++task->data[10] > 60) { task->data[10] = 0; task->tState = 2; } break; } } static void Task_SendPacket(u8 taskId) { struct Task *task = &gTasks[taskId]; switch (task->tState) { case 0: // Copy record data chunk to send buffer { void *recordData = LoadPtrFromTaskData(&task->tSentRecord) + task->tNumChunksSent * BUFFER_CHUNK_SIZE; memcpy(gBlockSendBuffer, recordData, BUFFER_CHUNK_SIZE); task->tState++; } break; case 1: if (GetMultiplayerId() == 0) SendBlockRequest(BLOCK_REQ_SIZE_200); task->tState++; break; case 2: break; case 3: // If sent final chunk of record, move on to next state. // Otherwise return to first state and send next chunk. task->tNumChunksSent++; if (task->tNumChunksSent == sRecordStructSize / BUFFER_CHUNK_SIZE + 1) task->tState++; else task->tState = 0; break; case 4: if (!gTasks[task->tCopyTaskId].isActive) task->func = Task_SendPacket_SwitchToReceive; break; } } static void Task_CopyReceiveBuffer(u8 taskId) { struct Task *task = &gTasks[taskId]; u8 status = GetBlockReceivedStatus(); u8 handledPlayers = 0; if (status == GetLinkPlayerCountAsBitFlags()) { u8 i; for (i = 0; i < GetLinkPlayerCount(); i++) { if ((status >> i) & 1) { void *dest = LoadPtrFromTaskData(&task->tRecvRecords) + task->tNumChunksRecv(i) * BUFFER_CHUNK_SIZE + sRecordStructSize * i; void *src = GetPlayerRecvBuffer(i); if ((task->tNumChunksRecv(i) + 1) * BUFFER_CHUNK_SIZE > sRecordStructSize) memcpy(dest, src, sRecordStructSize - task->tNumChunksRecv(i) * BUFFER_CHUNK_SIZE); else memcpy(dest, src, BUFFER_CHUNK_SIZE); ResetBlockReceivedFlag(i); task->tNumChunksRecv(i)++; if (task->tNumChunksRecv(i) == sRecordStructSize / BUFFER_CHUNK_SIZE + 1) handledPlayers++; } } gTasks[task->tParentTaskId].tState++; } if (handledPlayers == GetLinkPlayerCount()) DestroyTask(taskId); } static void Task_WaitReceivePacket(u8 taskId) { struct Task *task = &gTasks[taskId]; // Wait for Task_CopyReceiveBuffer to finish if (!gTasks[task->tCopyTaskId].isActive) DestroyTask(taskId); } static void Task_ReceivePacket(u8 taskId) { struct Task *task = &gTasks[taskId]; task->func = Task_WaitReceivePacket; if (sReadyToReceive == TRUE) ReceiveExchangePacket(task->tMultiplayerId); } static void Task_SendPacket_SwitchToReceive(u8 taskId) { gTasks[taskId].func = Task_ReceivePacket; sReadyToReceive = TRUE; } static void *LoadPtrFromTaskData(const u16 *asShort) { return (void *)(asShort[0] | (asShort[1] << 16)); } static void StorePtrInTaskData(void *records, u16 *asShort) { asShort[0] = (u32)records; asShort[1] = ((u32)records >> 16); } static u8 GetMultiplayerId_(void) { return GetMultiplayerId(); } static void *GetPlayerRecvBuffer(u8 id) { return gBlockRecvBuffer[id]; } static void ShufflePlayerIndices(u32 *data) { u32 i; u32 linkTrainerId; u32 players = GetLinkPlayerCount(); switch (players) { case 2: for (i = 0; i < ARRAY_COUNT(sPlayerIdxOrders_2Player); i++) data[i] = sPlayerIdxOrders_2Player[i]; break; case 3: linkTrainerId = GetLinkPlayerTrainerId(0) % ARRAY_COUNT(sPlayerIdxOrders_3Player); for (i = 0; i < ARRAY_COUNT(sPlayerIdxOrders_3Player[0]); i++) data[i] = sPlayerIdxOrders_3Player[linkTrainerId][i]; break; case 4: linkTrainerId = GetLinkPlayerTrainerId(0) % ARRAY_COUNT(sPlayerIdxOrders_4Player); for (i = 0; i < ARRAY_COUNT(sPlayerIdxOrders_4Player[0]); i++) data[i] = sPlayerIdxOrders_4Player[linkTrainerId][i]; break; } } static void ReceiveOldManData(OldMan *records, size_t recordSize, u8 multiplayerId) { u8 version; u16 language; OldMan *oldMan; u32 mixIndices[MAX_LINK_PLAYERS]; ShufflePlayerIndices(mixIndices); oldMan = (void *)records + recordSize * mixIndices[multiplayerId]; version = gLinkPlayers[mixIndices[multiplayerId]].version; language = gLinkPlayers[mixIndices[multiplayerId]].language; if (Link_AnyPartnersPlayingRubyOrSapphire()) SanitizeReceivedRubyOldMan(oldMan, version, language); else SanitizeReceivedEmeraldOldMan(oldMan, version, language); memcpy(sOldManSave, (void *)records + recordSize * mixIndices[multiplayerId], sizeof(OldMan)); ResetMauvilleOldManFlag(); } static void ReceiveBattleTowerData(void *records, size_t recordSize, u8 multiplayerId) { struct EmeraldBattleTowerRecord *battleTowerRecord; struct BattleTowerPokemon *btPokemon; u32 mixIndices[MAX_LINK_PLAYERS]; s32 i; ShufflePlayerIndices(mixIndices); if (Link_AnyPartnersPlayingRubyOrSapphire()) { if (RubyBattleTowerRecordToEmerald((void *)records + recordSize * mixIndices[multiplayerId], (void *)records + recordSize * multiplayerId) == TRUE) { battleTowerRecord = (void *)records + recordSize * multiplayerId; battleTowerRecord->language = gLinkPlayers[mixIndices[multiplayerId]].language; CalcEmeraldBattleTowerChecksum(battleTowerRecord); } } else { memcpy((void *)records + recordSize * multiplayerId, (void *)records + recordSize * mixIndices[multiplayerId], sizeof(struct EmeraldBattleTowerRecord)); battleTowerRecord = (void *)records + recordSize * multiplayerId; for (i = 0; i < MAX_FRONTIER_PARTY_SIZE; i++) { btPokemon = &battleTowerRecord->party[i]; if (btPokemon->species != SPECIES_NONE && IsStringJapanese(btPokemon->nickname)) ConvertInternationalString(btPokemon->nickname, LANGUAGE_JAPANESE); } CalcEmeraldBattleTowerChecksum(battleTowerRecord); } PutNewBattleTowerRecord((void *)records + recordSize * multiplayerId); } static void ReceiveLilycoveLadyData(LilycoveLady *records, size_t recordSize, u8 multiplayerId) { LilycoveLady *lilycoveLady; u32 mixIndices[MAX_LINK_PLAYERS]; ShufflePlayerIndices(mixIndices); memcpy((void *)records + recordSize * multiplayerId, sLilycoveLadySave, sizeof(LilycoveLady)); if (GetLilycoveLadyId() == 0) { lilycoveLady = malloc(sizeof(*lilycoveLady)); if (lilycoveLady == NULL) return; memcpy(lilycoveLady, sLilycoveLadySave, sizeof(LilycoveLady)); } else { lilycoveLady = NULL; } memcpy(sLilycoveLadySave, (void *)records + recordSize * mixIndices[multiplayerId], sizeof(LilycoveLady)); ResetLilycoveLadyForRecordMix(); if (lilycoveLady != NULL) { QuizLadyClearQuestionForRecordMix(lilycoveLady); free(lilycoveLady); } } static u8 GetDaycareMailItemId(struct DaycareMail *mail) { return mail->message.itemId; } // Indexes for a 2 element array used to store the multiplayer id and daycare // slot that correspond to a daycare Pokémon that can hold an item. enum { MULTIPLAYER_ID, DAYCARE_SLOT, }; static void SwapDaycareMail(struct RecordMixingDaycareMail *records, size_t recordSize, u8 (*idxs)[2], u8 playerSlot1, u8 playerSlot2) { struct DaycareMail temp; struct RecordMixingDaycareMail *mixMail1, *mixMail2; // 1st player's daycare mail --> temp mixMail1 = (void *)records + recordSize * idxs[playerSlot1][MULTIPLAYER_ID]; memcpy(&temp, &mixMail1->mail[idxs[playerSlot1][DAYCARE_SLOT]], sizeof(struct DaycareMail)); // 2nd player's daycare mail --> 1st player's daycare mail mixMail2 = (void *)records + recordSize * idxs[playerSlot2][MULTIPLAYER_ID]; memcpy(&mixMail1->mail[idxs[playerSlot1][DAYCARE_SLOT]], &mixMail2->mail[idxs[playerSlot2][DAYCARE_SLOT]], sizeof(struct DaycareMail)); // temp --> 2nd player's daycare mail memcpy(&mixMail2->mail[idxs[playerSlot2][DAYCARE_SLOT]], &temp, sizeof(struct DaycareMail)); } // This sum is used to determine which players will swap daycare mail if there are more than 2 players who can. // The TV show data is used to calculate this sum. static void CalculateDaycareMailRandSum(const u8 *src) { u8 sum; s32 i; sum = 0; for (i = 0; i < 256; i++) sum += src[i]; sDaycareMailRandSum = sum; } static u8 GetDaycareMailRandSum(void) { return sDaycareMailRandSum; } static void ReceiveDaycareMailData(struct RecordMixingDaycareMail *records, size_t recordSize, u8 multiplayerId, TVShow *shows) { u16 i, j; u8 linkPlayerCount; u8 tableId; struct RecordMixingDaycareMail *mixMail; u8 playerSlot1, playerSlot2; void *ptr; u8 unusedArr1[MAX_LINK_PLAYERS]; u8 unusedArr2[MAX_LINK_PLAYERS]; struct RecordMixingDaycareMail *unusedMixMail[MAX_LINK_PLAYERS]; bool8 canHoldItem[MAX_LINK_PLAYERS][DAYCARE_MON_COUNT]; u8 idxs[MAX_LINK_PLAYERS][2]; u8 numDaycareCanHold; u16 oldSeed; bool32 anyRS; // Seed RNG to the first player's trainer id so that // every player has the same random swap occur // (see the other use of Random2 in this function) oldSeed = Random2(); SeedRng2(gLinkPlayers[0].trainerId); linkPlayerCount = GetLinkPlayerCount(); for (i = 0; i < MAX_LINK_PLAYERS; i++) { unusedArr1[i] = 0xFF; unusedArr2[i] = 0; canHoldItem[i][0] = FALSE; canHoldItem[i][1] = FALSE; } // Handle language differences if RS / Japanese players are present anyRS = Link_AnyPartnersPlayingRubyOrSapphire(); for (i = 0; i < GetLinkPlayerCount(); i++) { u32 language, version; mixMail = (void *)records + i * recordSize; language = gLinkPlayers[i].language; version = gLinkPlayers[i].version & 0xFF; for (j = 0; j < mixMail->numDaycareMons; j++) { u16 otNameLanguage, nicknameLanguage; struct DaycareMail *daycareMail = &mixMail->mail[j]; if (daycareMail->message.itemId == ITEM_NONE) continue; if (anyRS) { // Handle OT name language if (StringLength(daycareMail->otName) <= 5) { otNameLanguage = LANGUAGE_JAPANESE; } else { StripExtCtrlCodes(daycareMail->otName); otNameLanguage = language; } // Handle nickname langugae if (daycareMail->monName[0] == EXT_CTRL_CODE_BEGIN && daycareMail->monName[1] == EXT_CTRL_CODE_JPN) { StripExtCtrlCodes(daycareMail->monName); nicknameLanguage = LANGUAGE_JAPANESE; } else { nicknameLanguage = language; } // Set languages if (version == VERSION_RUBY || version == VERSION_SAPPHIRE) { daycareMail->gameLanguage = otNameLanguage; daycareMail->monLanguage = nicknameLanguage; } } else if (language == LANGUAGE_JAPANESE) { if (IsStringJapanese(daycareMail->otName)) daycareMail->gameLanguage = LANGUAGE_JAPANESE; else daycareMail->gameLanguage = GAME_LANGUAGE; if (IsStringJapanese(daycareMail->monName)) daycareMail->monLanguage = LANGUAGE_JAPANESE; else daycareMail->monLanguage = GAME_LANGUAGE; } } } // For each player, get which of their daycare Pokémon can hold items // (can't hold items if already holding one, or if daycare slot is empty). // Note that when deposited in the daycare, Pokémon have their mail taken // from them and returned upon withdrawal, which means daycare Pokémon that // have associated mail do not have a held item. // Because not holding an item is the only determination for a swap, this also // means that a "swap" can occur even if neither Pokémon has associated mail. numDaycareCanHold = 0; for (i = 0; i < linkPlayerCount; i++) { mixMail = (void *)records + i * recordSize; if (mixMail->numDaycareMons == 0) continue; for (j = 0; j < mixMail->numDaycareMons; j++) { if (!mixMail->cantHoldItem[j]) canHoldItem[i][j] = TRUE; } } // Fill the idxs array with data about which players // and which daycare slots should swap mail. j = 0; for (i = 0; i < linkPlayerCount; i++) { mixMail = (void *)records + i * recordSize; // Count number of players that have at least // one daycare Pokémon with no held item if (canHoldItem[i][0] == TRUE || canHoldItem[i][1] == TRUE) numDaycareCanHold++; if (canHoldItem[i][0] == TRUE && canHoldItem[i][1] == FALSE) { // Only daycare slot 0 can hold an item for this player, record it idxs[j][MULTIPLAYER_ID] = i; idxs[j][DAYCARE_SLOT] = 0; j++; } else if (canHoldItem[i][0] == FALSE && canHoldItem[i][1] == TRUE) { // Only daycare slot 1 can hold an item for this player, record it idxs[j][MULTIPLAYER_ID] = i; idxs[j][DAYCARE_SLOT] = 1; j++; } else if (canHoldItem[i][0] == TRUE && canHoldItem[i][1] == TRUE) { // Both daycare slots can hold an item, choose which one to use. // If either one is the only one to have associated mail, use that one. // If both do or don't have associated mail, choose one randomly. u32 itemId1, itemId2; idxs[j][MULTIPLAYER_ID] = i; itemId1 = GetDaycareMailItemId(&mixMail->mail[0]); itemId2 = GetDaycareMailItemId(&mixMail->mail[1]); if ((!itemId1 && !itemId2) || (itemId1 && itemId2)) idxs[j][DAYCARE_SLOT] = Random2() % 2; else if (itemId1 && !itemId2) idxs[j][DAYCARE_SLOT] = 0; else if (!itemId1 && itemId2) idxs[j][DAYCARE_SLOT] = 1; j++; } } // Copy the player's record mix mail 4 times to an array that's never read. for (i = 0; i < MAX_LINK_PLAYERS; i++) { mixMail = &records[multiplayerId * recordSize]; unusedMixMail[i] = mixMail; } // Choose a random table id to determine who will // swap if there are more than 2 candidate players. tableId = GetDaycareMailRandSum() % NUM_SWAP_COMBOS; switch (numDaycareCanHold) { case 2: // 2 players can swap, just perform swap. SwapDaycareMail(records, recordSize, idxs, 0, 1); break; case 3: // 3 players can swap, select 2 and leave the 3rd out playerSlot1 = sDaycareMailSwapIds_3Player[tableId][0]; playerSlot2 = sDaycareMailSwapIds_3Player[tableId][1]; SwapDaycareMail(records, recordSize, idxs, playerSlot1, playerSlot2); break; case 4: // 4 players can swap, select which 2 pairings will swap ptr = idxs; // Swap pair 1 playerSlot1 = sDaycareMailSwapIds_4Player[tableId][0]; playerSlot2 = sDaycareMailSwapIds_4Player[tableId][1]; SwapDaycareMail(records, recordSize, ptr, playerSlot1, playerSlot2); // Swap pair 2 playerSlot1 = sDaycareMailSwapIds_4Player[tableId][2]; playerSlot2 = sDaycareMailSwapIds_4Player[tableId][3]; SwapDaycareMail(records, recordSize, ptr, playerSlot1, playerSlot2); break; } // Save player's record mixed mail to the daycare (in case it has changed) mixMail = (void *)records + multiplayerId * recordSize; memcpy(&gSaveBlock1Ptr->daycare.mons[0].mail, &mixMail->mail[0], sizeof(struct DaycareMail)); memcpy(&gSaveBlock1Ptr->daycare.mons[1].mail, &mixMail->mail[1], sizeof(struct DaycareMail)); SeedRng(oldSeed); } static void ReceiveGiftItem(u16 *item, u8 multiplayerId) { if (multiplayerId != 0 && *item != ITEM_NONE && GetPocketByItemId(*item) == POCKET_KEY_ITEMS) { if (!CheckBagHasItem(*item, 1) && !CheckPCHasItem(*item, 1) && AddBagItem(*item, 1)) { VarSet(VAR_TEMP_1, *item); StringCopy(gStringVar1, gLinkPlayers[0].name); if (*item == ITEM_EON_TICKET) FlagSet(FLAG_ENABLE_SHIP_SOUTHERN_ISLAND); } else { VarSet(VAR_TEMP_1, ITEM_NONE); } } } static void Task_DoRecordMixing(u8 taskId) { struct Task *task = &gTasks[taskId]; switch (task->tState) { case 0: task->tState++; break; case 1: if (Link_AnyPartnersPlayingRubyOrSapphire()) task->tState++; else task->tState = 6; break; case 2: // Mixing Ruby/Sapphire records. SetContinueGameWarpStatusToDynamicWarp(); WriteSaveBlock2(); task->tState++; break; case 3: if (WriteSaveBlock1Sector()) { ClearContinueGameWarpStatus2(); task->tState = 4; task->data[1] = 0; } break; case 4: // Wait 10 frames if (++task->data[1] > 10) { SetCloseLinkCallback(); task->tState++; } break; case 5: // Finish mixing Ruby/Sapphire records if (gReceivedRemoteLinkPlayers == FALSE) DestroyTask(taskId); break; // Mixing Emerald records. case 6: if (!Rfu_SetLinkRecovery(FALSE)) { CreateTask(Task_LinkFullSave, 5); task->tState++; } break; case 7: // wait for Task_LinkFullSave to finish. if (!FuncIsActiveTask(Task_LinkFullSave)) { if (gWirelessCommType) { Rfu_SetLinkRecovery(TRUE); task->tState = 8; } else { task->tState = 4; } } break; case 8: SetLinkStandbyCallback(); task->tState++; break; case 9: if (IsLinkTaskFinished()) DestroyTask(taskId); break; } } static void GetSavedApprentices(struct Apprentice *dst, struct Apprentice *src) { s32 i, id; s32 apprenticeSaveId, oldPlayerApprenticeSaveId; s32 numOldPlayerApprentices, numMixApprentices; dst[0].playerName[0] = EOS; dst[1].playerName[0] = EOS; dst[0] = src[0]; oldPlayerApprenticeSaveId = 0; numOldPlayerApprentices = 0; apprenticeSaveId = 0; numMixApprentices = 0; for (i = 0; i < 2; i++) { id = (i + gSaveBlock2Ptr->playerApprentice.saveId) % (APPRENTICE_COUNT - 1) + 1; if (src[id].playerName[0] != EOS) { if (GetTrainerId(src[id].playerId) != GetTrainerId(gSaveBlock2Ptr->playerTrainerId)) { numMixApprentices++; apprenticeSaveId = id; } if (GetTrainerId(src[id].playerId) == GetTrainerId(gSaveBlock2Ptr->playerTrainerId)) { numOldPlayerApprentices++; oldPlayerApprenticeSaveId = id; } } } // Prefer passing on other mixed Apprentices rather than old player's Apprentices if (numMixApprentices == 0 && numOldPlayerApprentices != 0) { numMixApprentices = numOldPlayerApprentices; apprenticeSaveId = oldPlayerApprenticeSaveId; } switch (numMixApprentices) { case 1: dst[1] = src[apprenticeSaveId]; break; case 2: if (Random2() > 0x3333) dst[1] = src[gSaveBlock2Ptr->playerApprentice.saveId + 1]; else dst[1] = src[((gSaveBlock2Ptr->playerApprentice.saveId + 1) % (APPRENTICE_COUNT - 1) + 1)]; break; } } void GetPlayerHallRecords(struct PlayerHallRecords *dst) { s32 i, j; for (i = 0; i < HALL_FACILITIES_COUNT; i++) { for (j = 0; j < FRONTIER_LVL_MODE_COUNT; j++) { CopyTrainerId(dst->onePlayer[i][j].id, gSaveBlock2Ptr->playerTrainerId); dst->onePlayer[i][j].language = GAME_LANGUAGE; StringCopy(dst->onePlayer[i][j].name, gSaveBlock2Ptr->playerName); } } for (j = 0; j < FRONTIER_LVL_MODE_COUNT; j++) { dst->twoPlayers[j].language = GAME_LANGUAGE; CopyTrainerId(dst->twoPlayers[j].id1, gSaveBlock2Ptr->playerTrainerId); CopyTrainerId(dst->twoPlayers[j].id2, gSaveBlock2Ptr->frontier.opponentTrainerIds[j]); StringCopy(dst->twoPlayers[j].name1, gSaveBlock2Ptr->playerName); StringCopy(dst->twoPlayers[j].name2, gSaveBlock2Ptr->frontier.opponentNames[j]); } for (i = 0; i < FRONTIER_LVL_MODE_COUNT; i++) { dst->onePlayer[RANKING_HALL_TOWER_SINGLES][i].winStreak = gSaveBlock2Ptr->frontier.towerRecordWinStreaks[FRONTIER_MODE_SINGLES][i]; dst->onePlayer[RANKING_HALL_TOWER_DOUBLES][i].winStreak = gSaveBlock2Ptr->frontier.towerRecordWinStreaks[FRONTIER_MODE_DOUBLES][i]; dst->onePlayer[RANKING_HALL_TOWER_MULTIS][i].winStreak = gSaveBlock2Ptr->frontier.towerRecordWinStreaks[FRONTIER_MODE_MULTIS][i]; dst->onePlayer[RANKING_HALL_DOME][i].winStreak = gSaveBlock2Ptr->frontier.domeRecordWinStreaks[FRONTIER_MODE_SINGLES][i]; dst->onePlayer[RANKING_HALL_PALACE][i].winStreak = gSaveBlock2Ptr->frontier.palaceRecordWinStreaks[FRONTIER_MODE_SINGLES][i]; dst->onePlayer[RANKING_HALL_ARENA][i].winStreak = gSaveBlock2Ptr->frontier.arenaRecordStreaks[i]; dst->onePlayer[RANKING_HALL_FACTORY][i].winStreak = gSaveBlock2Ptr->frontier.factoryRecordWinStreaks[FRONTIER_MODE_SINGLES][i]; dst->onePlayer[RANKING_HALL_PIKE][i].winStreak = gSaveBlock2Ptr->frontier.pikeRecordStreaks[i]; dst->onePlayer[RANKING_HALL_PYRAMID][i].winStreak = gSaveBlock2Ptr->frontier.pyramidRecordStreaks[i]; dst->twoPlayers[i].winStreak = gSaveBlock2Ptr->frontier.towerRecordWinStreaks[FRONTIER_MODE_LINK_MULTIS][i]; } } static bool32 IsApprenticeAlreadySaved(struct Apprentice *mixApprentice, struct Apprentice *apprentices) { s32 i; for (i = 0; i < APPRENTICE_COUNT; i++) { if (GetTrainerId(mixApprentice->playerId) == GetTrainerId(apprentices[i].playerId) && mixApprentice->number == apprentices[i].number) return TRUE; } return FALSE; } static void ReceiveApprenticeData(struct Apprentice *records, size_t recordSize, u32 multiplayerId) { s32 i, numApprentices, apprenticeId; struct Apprentice *mixApprentice; u32 mixIndices[MAX_LINK_PLAYERS]; u32 apprenticeSaveId; ShufflePlayerIndices(mixIndices); mixApprentice = (void*)records + (recordSize * mixIndices[multiplayerId]); numApprentices = 0; apprenticeId = 0; for (i = 0; i < 2; i++) { if (mixApprentice[i].playerName[0] != EOS && !IsApprenticeAlreadySaved(&mixApprentice[i], &gSaveBlock2Ptr->apprentices[0])) { numApprentices++; apprenticeId = i; } } switch (numApprentices) { case 1: apprenticeSaveId = gSaveBlock2Ptr->playerApprentice.saveId + 1; gSaveBlock2Ptr->apprentices[apprenticeSaveId] = mixApprentice[apprenticeId]; gSaveBlock2Ptr->playerApprentice.saveId = (gSaveBlock2Ptr->playerApprentice.saveId + 1) % (APPRENTICE_COUNT - 1); break; case 2: for (i = 0; i < 2; i++) { apprenticeSaveId = ((i ^ 1) + gSaveBlock2Ptr->playerApprentice.saveId) % (APPRENTICE_COUNT - 1) + 1; gSaveBlock2Ptr->apprentices[apprenticeSaveId] = mixApprentice[i]; } gSaveBlock2Ptr->playerApprentice.saveId = (gSaveBlock2Ptr->playerApprentice.saveId + 2) % (APPRENTICE_COUNT - 1); break; } } static void GetNewHallRecords(struct RecordMixingHallRecords *dst, void *records, size_t recordSize, u32 multiplayerId, s32 linkPlayerCount) { s32 i, j, k, l; s32 repeatTrainers; // Load sPartnerHallRecords with link partners' hall records k = 0; for (i = 0; i < linkPlayerCount; i++) { if (i != multiplayerId) sPartnerHallRecords[k++] = records; if (k == HALL_RECORDS_COUNT) break; records += recordSize; } // Get improved 1P hall records for (i = 0; i < HALL_FACILITIES_COUNT; i++) { for (j = 0; j < FRONTIER_LVL_MODE_COUNT; j++) { // First get the existing saved records for (k = 0; k < HALL_RECORDS_COUNT; k++) dst->hallRecords1P[i][j][k] = gSaveBlock2Ptr->hallRecords1P[i][j][k]; // Then read the new mixed records for (k = 0; k < linkPlayerCount - 1; k++) { repeatTrainers = 0; for (l = 0; l < HALL_RECORDS_COUNT; l++) { // If the new trainer is already in the existing saved records, only // use the new one if the win streak is better if (GetTrainerId(dst->hallRecords1P[i][j][l].id) == GetTrainerId(sPartnerHallRecords[k]->onePlayer[i][j].id)) { repeatTrainers++; if (dst->hallRecords1P[i][j][l].winStreak < sPartnerHallRecords[k]->onePlayer[i][j].winStreak) dst->hallRecords1P[i][j][l] = sPartnerHallRecords[k]->onePlayer[i][j]; } } // If all of the mixed records are new trainers, just save them if (repeatTrainers == 0) dst->hallRecords1P[i][j][k + HALL_RECORDS_COUNT] = sPartnerHallRecords[k]->onePlayer[i][j]; } } } // Get improved 2P hall records for (j = 0; j < FRONTIER_LVL_MODE_COUNT; j++) { // First get the existing saved records for (k = 0; k < HALL_RECORDS_COUNT; k++) dst->hallRecords2P[j][k] = gSaveBlock2Ptr->hallRecords2P[j][k]; // Then read the new mixed records for (k = 0; k < linkPlayerCount - 1; k++) { repeatTrainers = 0; for (l = 0; l < HALL_RECORDS_COUNT; l++) { // If the new trainer pair is already in the existing saved records, only // use the new pair if the win streak is better if (GetTrainerId(dst->hallRecords2P[j][l].id1) == GetTrainerId(sPartnerHallRecords[k]->twoPlayers[j].id1) && GetTrainerId(dst->hallRecords2P[j][l].id2) == GetTrainerId(sPartnerHallRecords[k]->twoPlayers[j].id2)) { repeatTrainers++; if (dst->hallRecords2P[j][l].winStreak < sPartnerHallRecords[k]->twoPlayers[j].winStreak) dst->hallRecords2P[j][l] = sPartnerHallRecords[k]->twoPlayers[j]; } } // If all of the mixed records are new trainer pairs, just save them if (repeatTrainers == 0) dst->hallRecords2P[j][k + HALL_RECORDS_COUNT] = sPartnerHallRecords[k]->twoPlayers[j]; } } } static void FillWinStreakRecords1P(struct RankingHall1P *playerRecords, struct RankingHall1P *mixRecords) { s32 i, j; // Fill the player's 1P records with the highest win streaks from the mixed records for (i = 0; i < HALL_RECORDS_COUNT; i++) { // Get the highest remaining win streak in the mixed hall records s32 highestWinStreak = 0; s32 highestId = -1; for (j = 0; j < HALL_RECORDS_COUNT * 2; j++) { if (mixRecords[j].winStreak > highestWinStreak) { highestId = j; highestWinStreak = mixRecords[j].winStreak; } } // Save the win streak to the player's records, then clear it from the mixed records if (highestId >= 0) { playerRecords[i] = mixRecords[highestId]; mixRecords[highestId].winStreak = 0; } } } static void FillWinStreakRecords2P(struct RankingHall2P *playerRecords, struct RankingHall2P *mixRecords) { s32 i, j; // Fill the player's 2P records with the highest win streaks from the mixed records for (i = 0; i < HALL_RECORDS_COUNT; i++) { // Get the highest remaining win streak in the mixed hall records s32 highestWinStreak = 0; s32 highestId = -1; for (j = 0; j < HALL_RECORDS_COUNT * 2; j++) { if (mixRecords[j].winStreak > highestWinStreak) { highestId = j; highestWinStreak = mixRecords[j].winStreak; } } // Save the win streak to the player's records, then clear it from the mixed records if (highestId >= 0) { playerRecords[i] = mixRecords[highestId]; mixRecords[highestId].winStreak = 0; } } } static void SaveHighestWinStreakRecords(struct RecordMixingHallRecords *mixHallRecords) { s32 i, j; for (i = 0; i < HALL_FACILITIES_COUNT; i++) { for (j = 0; j < FRONTIER_LVL_MODE_COUNT; j++) FillWinStreakRecords1P(gSaveBlock2Ptr->hallRecords1P[i][j], mixHallRecords->hallRecords1P[i][j]); } for (j = 0; j < FRONTIER_LVL_MODE_COUNT; j++) FillWinStreakRecords2P(gSaveBlock2Ptr->hallRecords2P[j], mixHallRecords->hallRecords2P[j]); } static void ReceiveRankingHallRecords(struct PlayerHallRecords *records, size_t recordSize, u32 multiplayerId) { u8 linkPlayerCount = GetLinkPlayerCount(); struct RecordMixingHallRecords *mixHallRecords = AllocZeroed(sizeof(*mixHallRecords)); GetNewHallRecords(mixHallRecords, records, recordSize, multiplayerId, linkPlayerCount); SaveHighestWinStreakRecords(mixHallRecords); Free(mixHallRecords); } static void GetRecordMixingDaycareMail(struct RecordMixingDaycareMail *dst) { sRecordMixMail.mail[0] = gSaveBlock1Ptr->daycare.mons[0].mail; sRecordMixMail.mail[1] = gSaveBlock1Ptr->daycare.mons[1].mail; InitDaycareMailRecordMixing(&gSaveBlock1Ptr->daycare, &sRecordMixMail); *dst = *sRecordMixMailSave; } static void SanitizeDaycareMailForRuby(struct RecordMixingDaycareMail *src) { s32 i; for (i = 0; i < src->numDaycareMons; i++) { struct DaycareMail *mail = &src->mail[i]; if (mail->message.itemId != ITEM_NONE) { if (mail->gameLanguage != LANGUAGE_JAPANESE) PadNameString(mail->otName, EXT_CTRL_CODE_BEGIN); ConvertInternationalString(mail->monName, mail->monLanguage); } } } static void SanitizeRubyBattleTowerRecord(struct RSBattleTowerRecord *src) { } static void SanitizeEmeraldBattleTowerRecord(struct EmeraldBattleTowerRecord *dst) { s32 i; for (i = 0; i < MAX_FRONTIER_PARTY_SIZE; i++) { struct BattleTowerPokemon *towerMon = &dst->party[i]; if (towerMon->species != SPECIES_NONE) StripExtCtrlCodes(towerMon->nickname); } CalcEmeraldBattleTowerChecksum(dst); }