Path: blob/master/SonicMania/Objects/Menu/TimeAttackMenu.c
338 views
// ---------------------------------------------------------------------1// RSDK Project: Sonic Mania2// Object Description: TimeAttackMenu Object3// Object Author: Christian Whitehead/Simon Thomley/Hunter Bridges4// Decompiled by: Rubberduckycooly & RMGRich5// ---------------------------------------------------------------------67#include "Game.h"89#if MANIA_USE_PLUS10ObjectTimeAttackMenu *TimeAttackMenu;1112void TimeAttackMenu_Update(void)13{14RSDK_THIS(TimeAttackMenu);1516StateMachine_Run(self->state);17}1819void TimeAttackMenu_LateUpdate(void) {}2021void TimeAttackMenu_StaticUpdate(void)22{23#if MANIA_USE_EGS24if (TimeAttackMenu->taDetailsControl && !API.CheckLeaderboardsEnabled()) {25for (int32 p = 0; p < TimeAttackMenu->taDetailsControl->promptCount; ++p) {26EntityUIButtonPrompt *prompt = TimeAttackMenu->taDetailsControl->prompts[p];27if (!prompt)28continue;2930if (prompt->promptID == 5)31prompt->visible = false;32}33}34#endif3536EntityUIButtonPrompt *switchModePrompt = TimeAttackMenu->switchModePrompt;3738if (switchModePrompt) {39if (API.CheckDLC(DLC_PLUS)) {40switchModePrompt->visible = true;41}42else {43if (TimeAttackMenu->encoreMode)44TimeAttackMenu_SetEncoreLayouts(false);4546switchModePrompt->visible = false;47}48}49}5051void TimeAttackMenu_Draw(void) {}5253void TimeAttackMenu_Create(void *data) {}5455void TimeAttackMenu_StageLoad(void)56{57TimeAttackMenu->encoreMode = false;58TimeAttackMenu->prevIsUser = false;59TimeAttackMenu->isUser = false;60}6162void TimeAttackMenu_Initialize(void)63{64String tag;65INIT_STRING(tag);6667foreach_all(UIControl, control)68{69RSDK.SetString(&tag, "Time Attack");70if (RSDK.CompareStrings(&tag, &control->tag, false))71TimeAttackMenu->timeAttackControl = control;7273RSDK.SetString(&tag, "Time Attack Legacy");74if (RSDK.CompareStrings(&tag, &control->tag, false))75TimeAttackMenu->timeAttackControl_Legacy = control;7677RSDK.SetString(&tag, "Time Attack Zones");78if (RSDK.CompareStrings(&tag, &control->tag, false))79TimeAttackMenu->taZoneSelControl = control;8081RSDK.SetString(&tag, "Time Attack Detail");82if (RSDK.CompareStrings(&tag, &control->tag, false))83TimeAttackMenu->taDetailsControl = control;8485RSDK.SetString(&tag, "Leaderboards");86if (RSDK.CompareStrings(&tag, &control->tag, false)) {87TimeAttackMenu->leaderboardsControl = control;88control->backPressCB = TimeAttackMenu_LeaderboardsBackPressCB;89}9091RSDK.SetString(&tag, "Replays");92if (RSDK.CompareStrings(&tag, &control->tag, false))93TimeAttackMenu->replaysControl = control;94}9596EntityUIControl *zoneControl = TimeAttackMenu->taZoneSelControl;97EntityUIControl *lbControl = TimeAttackMenu->leaderboardsControl;98EntityUIControl *replayControl = TimeAttackMenu->replaysControl;99EntityUIControl *detailsControl = TimeAttackMenu->taDetailsControl;100101foreach_all(UIButtonPrompt, prompt)102{103if (UIControl_ContainsPos(zoneControl, &prompt->position) && prompt->buttonID == 3)104TimeAttackMenu->switchModePrompt = prompt;105106if (UIControl_ContainsPos(lbControl, &prompt->position) && prompt->buttonID == 3)107TimeAttackMenu->topRankPrompt = prompt;108109if (UIControl_ContainsPos(replayControl, &prompt->position) && prompt->buttonID == 2)110TimeAttackMenu->replayPrompt = prompt;111}112113foreach_all(UILeaderboard, leaderboard) { TimeAttackMenu->leaderboard = leaderboard; }114115foreach_all(UIReplayCarousel, carousel) { TimeAttackMenu->replayCarousel = carousel; }116117foreach_all(UITABanner, banner)118{119if (UIControl_ContainsPos(detailsControl, &banner->position)) {120TimeAttackMenu->detailsBanner = banner;121banner->parent = TimeAttackMenu->taDetailsControl;122}123124if (UIControl_ContainsPos(lbControl, &banner->position)) {125TimeAttackMenu->leaderboardsBanner = banner;126banner->parent = TimeAttackMenu->leaderboardsControl;127}128}129}130131void TimeAttackMenu_HandleUnlocks(void)132{133foreach_all(UITAZoneModule, module) { module->disabled = !GameProgress_GetZoneUnlocked(module->zoneID); }134}135136void TimeAttackMenu_SetupActions(void)137{138EntityUIControl *control = TimeAttackMenu->timeAttackControl;139EntityUIControl *leaderboardsControl = TimeAttackMenu->leaderboardsControl;140EntityUIControl *replayControl = TimeAttackMenu->replaysControl;141EntityUIControl *zoneSelControl = TimeAttackMenu->taZoneSelControl;142EntityUIControl *detailsControl = TimeAttackMenu->taDetailsControl;143144control->menuUpdateCB = TimeAttackMenu_MenuUpdateCB;145146EntityUIButton *replaysButton = control->buttons[control->buttonCount - 1];147replaysButton->actionCB = TimeAttackMenu_ReplayButton_ActionCB;148149int32 newCount = (control->buttonCount - 1) + control->columnCount;150for (int32 i = control->buttonCount; i < newCount; ++i) {151control->buttons[i] = control->buttons[i - 1];152}153control->buttonCount = newCount;154155foreach_all(UICharButton, charButton)156{157if (charButton->parent == (Entity *)TimeAttackMenu->timeAttackControl158|| charButton->parent == (Entity *)TimeAttackMenu->timeAttackControl_Legacy)159charButton->actionCB = TimeAttackMenu_CharButton_ActionCB;160}161162foreach_all(UITAZoneModule, module) { module->actionCB = TimeAttackMenu_TAModule_ActionCB; }163164for (int32 i = 0; i < leaderboardsControl->buttonCount; ++i) {165EntityUIRankButton *rankButton = (EntityUIRankButton *)leaderboardsControl->buttons[i];166167UIRankButton_SetRankText(rankButton, i);168}169170leaderboardsControl->menuUpdateCB = TimeAttackMenu_MenuUpdateCB_LB;171leaderboardsControl->yPressCB = TimeAttackMenu_YPressCB_LB;172173replayControl->menuSetupCB = TimeAttackMenu_MenuSetupCB_Replay;174replayControl->menuUpdateCB = TimeAttackMenu_MenuUpdateCB_Replay;175replayControl->xPressCB = TimeAttackMenu_DeleteReplayActionCB;176replayControl->yPressCB = TimeAttackMenu_YPressCB_Replay;177178zoneSelControl->yPressCB = TimeAttackMenu_YPressCB_ZoneSel;179zoneSelControl->backPressCB = TimeAttackMenu_BackPressCB_ZoneSel;180181#if MANIA_USE_EGS182if (API.CheckLeaderboardsEnabled())183#endif184detailsControl->yPressCB = TimeAttackMenu_YPressCB_Details;185detailsControl->xPressCB = TimeAttackMenu_XPressCB_Details;186detailsControl->menuSetupCB = TimeAttackMenu_MenuSetupCB_Details;187188EntityUIButton *replayButton = detailsControl->buttons[0];189// Actually a UIChoice not a TAZoneModule but it fits better with pre-plus this way190replayButton->actionCB = TimeAttackMenu_TAZoneModule_ActionCB;191replayButton->choiceChangeCB = TimeAttackMenu_TAZoneModule_ChoiceChangeCB;192193EntityUIReplayCarousel *replayCarousel = TimeAttackMenu->replayCarousel;194replayCarousel->actionCB = TimeAttackMenu_ReplayCarousel_ActionCB;195replayControl->buttons[0]->choiceChangeCB = TimeAttackMenu_SortReplayChoiceCB;196}197198void TimeAttackMenu_HandleMenuReturn(void)199{200EntityMenuParam *param = MenuParam_GetParam();201202if (param->inTimeAttack)203TimeAttackMenu_SetEncoreLayouts(param->isEncoreMode);204205EntityUIControl *control = TimeAttackMenu->timeAttackControl;206if (param->inTimeAttack) {207int32 charID = param->characterID - 1;208control->buttonID = charID;209control->buttons[charID]->isSelected = true;210}211212EntityUIControl *legacyControl = TimeAttackMenu->timeAttackControl_Legacy;213if (param->inTimeAttack) {214int32 charID = param->characterID - 1;215if (param->characterID - 1 >= UICHARBUTTON_MIGHTY)216charID = UICHARBUTTON_SONIC;217218legacyControl->buttonID = charID;219legacyControl->buttons[charID]->isSelected = true;220}221222EntityUIControl *zoneControl = TimeAttackMenu->taZoneSelControl;223if (param->inTimeAttack) {224zoneControl->buttonID = param->zoneID;225zoneControl->buttons[param->zoneID]->isSelected = true;226}227228EntityUIControl *detailsControl = TimeAttackMenu->taDetailsControl;229if (param->inTimeAttack) {230UITABanner_SetupDetails(TimeAttackMenu->detailsBanner, param->zoneID, param->actID, param->characterID, param->isEncoreMode);231TimeAttackMenu_SetupDetailsView();232233UIButton_SetChoiceSelectionWithCB(detailsControl->buttons[0], param->actID);234detailsControl->buttonID = param->menuSelection;235236TimeAttackMenu_TAZoneModule_ChoiceChangeCB();237}238239EntityUIControl *replayControl = TimeAttackMenu->replaysControl;240if (replayControl->active == ACTIVE_ALWAYS) {241replayControl->buttonID = 1; // Select Replay Carousel242UIButton_SetChoiceSelectionWithCB(replayControl->buttons[0], param->selectedReplay & 0xFF);243244EntityUIReplayCarousel *carousel = TimeAttackMenu->replayCarousel;245int32 replayCount = API.GetSortedUserDBRowCount(globals->replayTableID);246int32 targetID = API.GetUserDBRowByID(globals->replayTableID, param->replayUUID);247248int32 replayID = 0;249for (; replayID < replayCount; ++replayID) {250if (API.GetSortedUserDBRowID(globals->replayTableID, replayID) == targetID)251break;252}253254if (replayCount <= 0 || replayID >= replayCount)255carousel->curReplayID = param->replayID;256else257carousel->curReplayID = replayID;258}259}260261void TimeAttackMenu_SetEncoreLayouts(bool32 enabled)262{263LogHelpers_Print("SetEncoreLayouts(%d)", enabled);264TimeAttackMenu->encoreMode = enabled;265266EntityUIButtonPrompt *prompt = TimeAttackMenu->switchModePrompt;267prompt->promptID = 21 - (enabled != false);268269foreach_all(UITAZoneModule, module) { module->isEncore = enabled; }270}271272void TimeAttackMenu_DeleteReplayActionCB(void)273{274EntityUIControl *replayControl = TimeAttackMenu->replaysControl;275EntityUIReplayCarousel *carousel = TimeAttackMenu->replayCarousel;276277if (replayControl->buttonID == 1 && carousel->stateDraw == UIReplayCarousel_Draw_Carousel278&& API.GetSortedUserDBRowCount(globals->replayTableID)) {279String string;280INIT_STRING(string);281282Localization_GetString(&string, STR_DELETEREPLAY);283UIDialog_CreateDialogYesNo(&string, TimeAttackMenu_ConfirmDeleteReplay_Yes_CB, StateMachine_None, true, true);284}285}286287void TimeAttackMenu_ConfirmDeleteReplay_Yes_CB(void)288{289EntityUIReplayCarousel *carousel = TimeAttackMenu->replayCarousel;290291int32 row = API.GetSortedUserDBRowID(globals->replayTableID, carousel->curReplayID);292ReplayDB_DeleteReplay(row, TimeAttackMenu_DeleteReplayCB, false);293}294295void TimeAttackMenu_DeleteReplayCB(bool32 success)296{297TimeAttackMenu_SortReplayChoiceCB();298299EntityUIReplayCarousel *carousel = TimeAttackMenu->replayCarousel;300301int32 count = API.GetSortedUserDBRowCount(globals->replayTableID) - 1;302if (carousel->curReplayID > count)303carousel->curReplayID = count;304}305306void TimeAttackMenu_MenuUpdateCB_LB(void)307{308EntityUIControl *control = TimeAttackMenu->leaderboardsControl;309310if (control->active == ACTIVE_ALWAYS) {311EntityUICarousel *carousel = control->carousel;312LeaderboardAvail avail = API.LeaderboardEntryViewSize();313314if (avail.start <= 1 || carousel->scrollOffset >= avail.start + 2) {315if (carousel->scrollOffset > avail.length - control->buttonCount + avail.start - 2) {316// Load Down317API.LoadLeaderboardEntries(avail.start, avail.length + 20, LEADERBOARD_LOAD_NEXT);318}319}320else {321// Load Up322API.LoadLeaderboardEntries(avail.start - 20, avail.length + 20, LEADERBOARD_LOAD_PREV);323}324325// Load the new entry count after (possibly) reloading new entries326avail = API.LeaderboardEntryViewSize();327carousel->minOffset = MAX(avail.start, 1);328carousel->maxOffset = MAX(avail.start + avail.length, carousel->minOffset + 5);329330for (int32 i = 0; i < control->buttonCount; ++i) {331EntityUIRankButton *button = (EntityUIRankButton *)control->buttons[i];332EntityUIControl *parent = carousel->parent;333334int32 max = (i - carousel->scrollOffset) % parent->buttonCount;335if (max < 0)336max += parent->buttonCount;337338LeaderboardEntry *entry = API.ReadLeaderboardEntry(max + carousel->scrollOffset);339if (entry != button->leaderboardEntry)340UIRankButton_SetupLeaderboardRank(button, entry);341}342}343}344345void TimeAttackMenu_SetupLeaderboards(int32 zoneID, int32 characterID, int32 act, bool32 isEncore, bool32 isUser, void (*callback)(void))346{347String string;348INIT_STRING(string);349350Localization_GetString(&string, STR_CONNECTING);351EntityUIDialog *dialog = UIDialog_CreateActiveDialog(&string);352353if (dialog) {354UIDialog_Setup(dialog);355TimeAttackMenu->connectingDlg = dialog;356357EntityTimeAttackMenu *entity = CREATE_ENTITY(TimeAttackMenu, NULL, -0x100000, -0x100000);358entity->active = ACTIVE_NORMAL;359entity->visible = true;360entity->delay = 120;361entity->state = TimeAttackMenu_State_SetupLeaderboards;362entity->callback = callback;363TimeAttackMenu->isUser = API.GetSortedUserDBRowCount(globals->taTableID) != 0;364TimeAttackMenu->prevIsUser = isUser ? false : TimeAttackMenu->isUser;365366LeaderboardID *leaderboardInfo = TimeAttackData_GetLeaderboardInfo(zoneID, act, characterID, isEncore);367API.FetchLeaderboard(leaderboardInfo, TimeAttackMenu->prevIsUser);368369UITABanner_SetupDetails(TimeAttackMenu->leaderboardsBanner, zoneID, act, characterID, isEncore);370}371}372373void TimeAttackMenu_ReplayCarousel_ActionCB(void)374{375RSDK_THIS(UIReplayCarousel);376377EntityUIPopover *popover = UIPopover_CreatePopover();378if (popover) {379int32 y = self->position.y;380if (!self->curReplayID)381y += self->popoverPos;382383UIPopover_AddButton(popover, POPOVER_WATCH, TimeAttackMenu_WatchReplayActionCB_ReplaysMenu, false);384UIPopover_AddButton(popover, POPOVER_CHALLENGE, TimeAttackMenu_ChallengeReplayActionCB_ReplaysMenu, false);385UIPopover_AddButton(popover, POPOVER_DELETE, TimeAttackMenu_DeleteReplayActionCB, true);386UIPopover_Setup(popover, self->position.x, y);387}388}389390void TimeAttackMenu_WatchReplay(int32 row, bool32 showGhost)391{392EntityMenuParam *param = MenuParam_GetParam();393394int32 id = API_GetFilteredInputDeviceID(false, false, 0);395API_ResetInputSlotAssignments();396API_AssignInputSlotToDevice(CONT_P1, id);397398uint32 uuid = API.GetUserDBRowUUID(globals->replayTableID, row);399LogHelpers_Print("Go_Replay(%d, %d)", row, showGhost);400LogHelpers_Print("uuid: %08X", uuid);401402int32 score = 0;403uint8 zoneID = 0;404uint8 act = 0;405uint8 characterID = 0;406uint8 encore = 0;407API.GetUserDBValue(globals->replayTableID, row, DBVAR_UINT32, "score", &score);408API.GetUserDBValue(globals->replayTableID, row, DBVAR_UINT8, "zoneID", &zoneID);409API.GetUserDBValue(globals->replayTableID, row, DBVAR_UINT8, "act", &act);410API.GetUserDBValue(globals->replayTableID, row, DBVAR_UINT8, "characterID", &characterID);411API.GetUserDBValue(globals->replayTableID, row, DBVAR_UINT8, "encore", &encore);412413param->viewReplay = true;414param->showGhost = showGhost;415param->replayUUID = uuid;416param->zoneID = zoneID;417param->actID = act;418param->characterID = characterID;419param->isEncoreMode = encore;420421int32 replayID = 0;422if (!showGhost) {423if (!TimeAttackData->loaded || characterID != TimeAttackData->characterID || zoneID != TimeAttackData->zoneID || act != TimeAttackData->act424|| encore != (int32)TimeAttackData->encore) {425TimeAttackData_ConfigureTableView(zoneID, act, characterID, encore);426}427428int32 entryCount = API.GetSortedUserDBRowCount(globals->taTableID);429for (int32 rank = 1; rank < entryCount; ++rank) {430if (uuid == (uint32)TimeAttackData_GetReplayID(zoneID, act, characterID, encore, rank))431break;432433++replayID;434}435}436param->selectedReplay = replayID;437438EntityUIPopover *popover = UIPopover->activePopover;439if (popover)440popover->parent->selectionDisabled = true;441442UIWaitSpinner_StartWait();443444char fileName[0x20];445sprintf_s(fileName, (int32)sizeof(fileName), "Replay_%08X.bin", uuid);446447memset(globals->replayTempRBuffer, 0, sizeof(globals->replayTempRBuffer));448memset(globals->replayReadBuffer, 0, sizeof(globals->replayReadBuffer));449ReplayRecorder_Buffer_LoadFile(fileName, globals->replayTempRBuffer, TimeAttackMenu_ReplayLoad_CB);450}451452void TimeAttackMenu_ReplayLoad_CB(bool32 success)453{454UIWaitSpinner_FinishWait();455456int32 strID = 0;457if (success) {458Replay *replayPtr = (Replay *)globals->replayTempRBuffer;459460if (replayPtr->header.version == GAME_VERSION) {461LogHelpers_Print("WARNING: Replay Load OK");462ReplayRecorder_Buffer_Unpack(globals->replayReadBuffer, globals->replayTempRBuffer);463TimeAttackMenu_LoadScene_Fadeout();464return;465}466467strID = STR_CANNOTLOADREPLAY;468}469else {470strID = STR_ERRORLOADINGREPLAY;471}472473String message;474INIT_STRING(message);475Localization_GetString(&message, strID);476477EntityUIDialog *dialog = UIDialog_CreateDialogOk(&message, StateMachine_None, true);478if (dialog) {479EntityUIPopover *popover = UIPopover->activePopover;480if (popover)481popover->parent->selectionDisabled = false;482}483}484485void TimeAttackMenu_WatchReplayActionCB_ReplaysMenu(void)486{487EntityMenuParam *param = MenuParam_GetParam();488489EntityUIControl *control = TimeAttackMenu->replaysControl;490EntityUIReplayCarousel *carousel = TimeAttackMenu->replayCarousel;491EntityUIButton *button = control->buttons[0];492493sprintf_s(param->menuTag, (int32)sizeof(param->menuTag), "Replays");494param->replayRankID = button->selection;495param->replayID = carousel->curReplayID;496497int32 id = API.GetSortedUserDBRowID(globals->replayTableID, carousel->curReplayID);498TimeAttackMenu_WatchReplay(id, false);499}500501void TimeAttackMenu_ChallengeReplayActionCB_ReplaysMenu(void)502{503EntityMenuParam *param = MenuParam_GetParam();504505EntityUIControl *control = TimeAttackMenu->replaysControl;506EntityUIReplayCarousel *carousel = TimeAttackMenu->replayCarousel;507EntityUIButton *button = control->buttons[0];508509sprintf_s(param->menuTag, (int32)sizeof(param->menuTag), "Replays");510param->replayRankID = button->selection;511param->replayID = carousel->curReplayID;512513int32 id = API.GetSortedUserDBRowID(globals->replayTableID, carousel->curReplayID);514TimeAttackMenu_WatchReplay(id, true);515}516517void TimeAttackMenu_WatchReplayCB_RanksMenu(void)518{519EntityMenuParam *param = MenuParam_GetParam();520EntityUIPopover *popover = UIPopover->activePopover;521EntityUIRankButton *button = (EntityUIRankButton *)popover->storedEntity;522EntityUIControl *parent = (EntityUIControl *)button->parent;523524int32 uuid = API.GetUserDBRowByID(globals->replayTableID, button->replayID);525if (uuid != -1) {526RSDK.GetCString(param->menuTag, &parent->tag);527param->menuSelection = parent->buttonID;528param->inTimeAttack = true;529TimeAttackMenu_WatchReplay(uuid, false);530}531}532533void TimeAttackMenu_ChallengeReplayCB_RanksMenu(void)534{535EntityMenuParam *param = MenuParam_GetParam();536EntityUIPopover *popover = UIPopover->activePopover;537EntityUIRankButton *button = (EntityUIRankButton *)popover->storedEntity;538EntityUIControl *parent = (EntityUIControl *)button->parent;539540int32 uuid = API.GetUserDBRowByID(globals->replayTableID, button->replayID);541if (uuid != -1) {542RSDK.GetCString(param->menuTag, &parent->tag);543param->menuSelection = parent->buttonID;544param->inTimeAttack = true;545TimeAttackMenu_WatchReplay(uuid, true);546}547}548549void TimeAttackMenu_LoadScene_Fadeout(void)550{551Music_FadeOut(0.05);552MenuSetup_StartTransition(TimeAttackMenu_LoadScene, 32);553}554555void TimeAttackMenu_MenuSetupCB_Replay(void)556{557if (API.GetUserDBRowsChanged(globals->replayTableID))558TimeAttackMenu_SortReplayChoiceCB();559}560561void TimeAttackMenu_MenuUpdateCB_Replay(void)562{563EntityUIControl *replayControl = TimeAttackMenu->replaysControl;564EntityUIReplayCarousel *carousel = TimeAttackMenu->replayCarousel;565566if (replayControl->lastButtonID != 1)567carousel->curReplayID = -1;568569EntityUIButtonPrompt *prompt = TimeAttackMenu->replayPrompt;570if (prompt)571prompt->visible = API.GetSortedUserDBRowCount(globals->replayTableID) != 0;572}573574void TimeAttackMenu_MenuUpdateCB(void)575{576EntityUIControl *control = TimeAttackMenu->timeAttackControl;577578// Keep "Replays" button centered579control->buttons[control->buttonCount - 1]->position.x = control->position.x;580}581582void TimeAttackMenu_ReplayButton_ActionCB(void)583{584EntityUIControl *control = TimeAttackMenu->replaysControl;585EntityUIReplayCarousel *carousel = TimeAttackMenu->replayCarousel;586587control->buttonID = 1;588carousel->curReplayID = 0;589UIControl_MatchMenuTag("Replays");590591UIButton_SetChoiceSelectionWithCB(control->buttons[0], 0);592}593594void TimeAttackMenu_YPressCB_Replay(void)595{596EntityUIControl *control = TimeAttackMenu->replaysControl;597EntityUIReplayCarousel *carousel = TimeAttackMenu->replayCarousel;598599if (carousel->stateDraw == UIReplayCarousel_Draw_Carousel) {600control->buttonID = 0;601602carousel->curReplayID = -1;603carousel->curViewOffset = 0;604carousel->targetViewOffset = 0;605606UIButton_SetChoiceSelectionWithCB(control->buttons[0], control->buttons[0]->selection ^ 1);607608RSDK.PlaySfx(UIWidgets->sfxBleep, false, 255);609}610}611612void TimeAttackMenu_SortReplayChoiceCB(void)613{614EntityUIControl *control = TimeAttackMenu->replaysControl;615EntityUIReplayCarousel *carousel = TimeAttackMenu->replayCarousel;616617API.SetupUserDBRowSorting(globals->replayTableID);618619if (control->buttons[0]->selection == 1)620API.SortDBRows(globals->replayTableID, DBVAR_UINT32, "zoneSortVal", false);621else622API.SortDBRows(globals->replayTableID, 0, NULL, true);623624carousel->stateDraw = UIReplayCarousel_Draw_Loading;625}626627void TimeAttackMenu_SetupDetailsView(void)628{629EntityMenuParam *param = MenuParam_GetParam();630EntityUIControl *control = TimeAttackMenu->taDetailsControl;631EntityUITABanner *banner = TimeAttackMenu->detailsBanner;632633EntityUIChoice *act1 = (EntityUIChoice *)UIButton_GetChoicePtr(control->buttons[0], 0);634EntityUIChoice *act2 = (EntityUIChoice *)UIButton_GetChoicePtr(control->buttons[0], 1);635636if (!TimeAttackMenu->encoreMode && banner->zoneID == 7 && param->characterID != 3) {637// Prevent us from selecting MSZ1 in mania mode if we're not knux638act1->disabled = true;639UIButton_SetChoiceSelectionWithCB(control->buttons[0], 1);640act2->arrowWidth = 0;641}642else {643act1->disabled = false;644UIButton_SetChoiceSelectionWithCB(control->buttons[0], 0);645act2->arrowWidth = 48;646}647}648649void TimeAttackMenu_TAModule_ActionCB(void)650{651RSDK_THIS(UITAZoneModule);652653EntityMenuParam *param = MenuParam_GetParam();654EntityUITABanner *banner = TimeAttackMenu->detailsBanner;655EntityUIControl *control = TimeAttackMenu->taDetailsControl;656657param->zoneID = self->zoneID;658control->buttonID = 0;659660UITABanner_SetupDetails(banner, self->zoneID, 0, self->characterID, TimeAttackMenu->encoreMode);661TimeAttackMenu_SetupDetailsView();662663UITransition_SetNewTag("Time Attack Detail");664}665666void TimeAttackMenu_StartTAAttempt(void)667{668EntityMenuParam *param = MenuParam_GetParam();669670sprintf_s(param->menuTag, (int32)sizeof(param->menuTag), "Time Attack Detail");671param->menuSelection = 0;672param->inTimeAttack = true;673param->isEncoreMode = TimeAttackMenu->encoreMode;674675Replay *replayPtr = (Replay *)globals->replayReadBuffer;676if (replayPtr->header.isNotEmpty && replayPtr->header.signature == REPLAY_SIGNATURE)677memset(globals->replayReadBuffer, 0, sizeof(globals->replayReadBuffer));678679TimeAttackMenu_LoadScene();680}681682void TimeAttackMenu_LoadScene(void)683{684EntityMenuParam *param = MenuParam_GetParam();685686SaveGame_ResetPlayerState();687688// Bug Details(?):689// sizeof(globals->noSaveSlot) and sizeof(saveData) is 4096 (sizeof(int32) * 0x400)690// but the memset size is only 1024 (sizeof(uint8) * 0x400)691// so only about 1/4th of the save slot is cleared, though nothin uses the extra space so it's not a big deal692memset(globals->noSaveSlot, 0, 0x400);693694globals->continues = 0;695globals->saveSlotID = NO_SAVE_SLOT;696globals->gameMode = MODE_TIMEATTACK;697globals->medalMods = 0;698699if (param->isEncoreMode)700RSDK.SetScene("Encore Mode", "");701else702RSDK.SetScene("Mania Mode", "");703704if (param->isEncoreMode)705SceneInfo->listPos += TimeAttackData_GetEncoreListPos(param->zoneID, param->actID, param->characterID);706else707SceneInfo->listPos += TimeAttackData_GetManiaListPos(param->zoneID, param->actID, param->characterID);708709switch (param->characterID) {710case 1: globals->playerID = ID_SONIC; break;711case 2: globals->playerID = ID_TAILS; break;712case 3: globals->playerID = ID_KNUCKLES; break;713case 4: globals->playerID = ID_MIGHTY; break;714case 5: globals->playerID = ID_RAY; break;715default: break;716}717718RSDK.LoadScene();719}720721void TimeAttackMenu_YPressCB_ZoneSel(void)722{723if (API.CheckDLC(DLC_PLUS)) {724TimeAttackMenu_SetEncoreLayouts(!TimeAttackMenu->encoreMode);725RSDK.PlaySfx(UIWidgets->sfxWoosh, false, 255);726}727}728729bool32 TimeAttackMenu_BackPressCB_ZoneSel(void)730{731if (!API.CheckDLC(DLC_PLUS))732UITransition_SetNewTag("Time Attack Legacy");733else734UITransition_SetNewTag("Time Attack");735736return true;737}738739void TimeAttackMenu_YPressCB_Details(void)740{741EntityUITABanner *banner = TimeAttackMenu->detailsBanner;742743int32 rowCount = API.GetSortedUserDBRowCount(globals->taTableID);744TimeAttackMenu_SetupLeaderboards(banner->zoneID, banner->characterID, banner->actID, TimeAttackMenu->encoreMode, !rowCount,745UITAZoneModule_ShowLeaderboards_CB);746}747748void TimeAttackMenu_ResetTimes_YesCB(void)749{750EntityUITABanner *banner = TimeAttackMenu->detailsBanner;751EntityUIControl *control = TimeAttackMenu->taDetailsControl;752753int32 act = control->buttons[0]->selection;754while (API.GetSortedUserDBRowCount(globals->taTableID) > 0) {755int32 rowID = API.GetSortedUserDBRowID(globals->taTableID, 0);756API.RemoveDBRow(globals->taTableID, rowID);757758TimeAttackData_ConfigureTableView(banner->zoneID, act, banner->characterID, TimeAttackMenu->encoreMode);759}760761control->buttonID = 0;762TimeAttackData_SaveDB(NULL);763764TimeAttackMenu_TAZoneModule_ChoiceChangeCB();765}766767void TimeAttackMenu_XPressCB_Details(void)768{769String message;770INIT_STRING(message);771772Localization_GetString(&message, STR_RESETTIMESWARNING);773UIDialog_CreateDialogYesNo(&message, TimeAttackMenu_ResetTimes_YesCB, StateMachine_None, true, true);774}775776void TimeAttackMenu_TAZoneModule_ActionCB(void)777{778RSDK_THIS(UIButton);779780EntityMenuParam *param = MenuParam_GetParam();781EntityUIControl *parent = (EntityUIControl *)self->parent;782783parent->selectionDisabled = true;784param->actID = self->selection;785786MenuSetup_StartTransition(TimeAttackMenu_StartTAAttempt, 32);787}788789void TimeAttackMenu_RankButton_ActionCB(void)790{791RSDK_THIS(UIRankButton);792793EntityUIPopover *popover = UIPopover_CreatePopover();794if (popover) {795popover->storedEntity = (Entity *)self;796797UIPopover_AddButton(popover, POPOVER_WATCH, TimeAttackMenu_WatchReplayCB_RanksMenu, false);798UIPopover_AddButton(popover, POPOVER_CHALLENGE, TimeAttackMenu_ChallengeReplayCB_RanksMenu, false);799UIPopover_Setup(popover, self->popoverPos.x, self->popoverPos.y);800}801}802803void TimeAttackMenu_MenuSetupCB_Details(void) { TimeAttackMenu_TAZoneModule_ChoiceChangeCB(); }804805void TimeAttackMenu_TAZoneModule_ChoiceChangeCB(void)806{807EntityMenuParam *param = MenuParam_GetParam();808EntityUIControl *control = TimeAttackMenu->taDetailsControl;809810int32 act = control->buttons[0]->selection;811812UITABanner_SetupDetails(TimeAttackMenu->detailsBanner, param->zoneID, act, param->characterID, TimeAttackMenu->encoreMode);813TimeAttackData_ConfigureTableView(param->zoneID, act, param->characterID, TimeAttackMenu->encoreMode);814815int32 rowCount = 1;816for (int32 rank = 1; rank < 4; ++rank) {817EntityUIRankButton *rankButton = (EntityUIRankButton *)control->buttons[rank];818819int32 score = TimeAttackData_GetScore(param->zoneID, act, param->characterID, TimeAttackMenu->encoreMode, rank);820int32 replayID = TimeAttackData_GetReplayID(param->zoneID, act, param->characterID, TimeAttackMenu->encoreMode, rank);821822UIRankButton_SetTimeAttackRank(rankButton, rank, score, replayID);823824if (score)825++rowCount;826827if (API.CheckDLC(DLC_PLUS) && replayID)828rankButton->actionCB = TimeAttackMenu_RankButton_ActionCB;829else830rankButton->actionCB = StateMachine_None;831}832833control->rowCount = rowCount;834}835836void TimeAttackMenu_CharButton_ActionCB(void)837{838RSDK_THIS(UICharButton);839840EntityMenuParam *param = MenuParam_GetParam();841EntityUIControl *control = TimeAttackMenu->taZoneSelControl;842843TimeAttackData_Clear();844845int32 characterID = self->characterID + 1;846param->characterID = characterID;847848API.InitLeaderboards();849850for (int32 i = 0; i < control->buttonCount; ++i) {851EntityUITAZoneModule *charButton = (EntityUITAZoneModule *)control->buttons[i];852charButton->characterID = characterID;853}854855UIControl_MatchMenuTag("Time Attack Zones");856}857858void TimeAttackMenu_TransitionToDetailsCB(void)859{860UIControl_SetInactiveMenu(TimeAttackMenu->leaderboardsControl);861UIControl_SetActiveMenu(TimeAttackMenu->taDetailsControl);862}863864bool32 TimeAttackMenu_LeaderboardsBackPressCB(void)865{866UITransition_StartTransition(TimeAttackMenu_TransitionToDetailsCB, 0);867868return true;869}870871void TimeAttackMenu_YPressCB_LB(void)872{873EntityUITABanner *banner = TimeAttackMenu->leaderboardsBanner;874875if (TimeAttackMenu->isUser)876TimeAttackMenu_SetupLeaderboards(banner->zoneID, banner->characterID, banner->actID, banner->isEncore, TimeAttackMenu->prevIsUser,877StateMachine_None);878}879880void TimeAttackMenu_State_SetupLeaderboards(void)881{882RSDK_THIS(TimeAttackMenu);883884String string;885INIT_STRING(string);886887EntityUIDialog *dialog = TimeAttackMenu->connectingDlg;888int32 status = API.GetLeaderboardsStatus();889890switch (status) {891case STATUS_CONTINUE:892Localization_GetString(&string, STR_CONNECTING);893UIDialog_SetupText(dialog, &string);894break;895896default:897case STATUS_ERROR: {898if (status < STATUS_ERROR) {899int32 strID = status == STATUS_TIMEOUT ? STR_COMMERROR : STR_NOWIFI;900Localization_GetString(&string, strID);901902UIDialog_SetupText(dialog, &string);903UIDialog_AddButton(DIALOG_OK, dialog, StateMachine_None, true);904905EntityUIControl *control = dialog->parent;906control->rowCount = 1;907control->columnCount = 1;908control->buttonID = 0;909TimeAttackMenu->prevIsUser = !TimeAttackMenu->prevIsUser;910911EntityUIButtonPrompt *topRankPrompt = TimeAttackMenu->topRankPrompt;912int32 id = -(TimeAttackMenu->prevIsUser != 0);913topRankPrompt->prevPrompt = -1;914topRankPrompt->promptID = (id + 15);915topRankPrompt->visible = !TimeAttackMenu->isUser;916917self->callback = StateMachine_None;918TimeAttackMenu->connectingDlg = NULL;919destroyEntity(self);920break;921}922// otherwise, fallthrough and do this923}924925case STATUS_OK: {926EntityUIButtonPrompt *topRankPrompt = TimeAttackMenu->topRankPrompt;927int32 id = -(TimeAttackMenu->prevIsUser != false);928topRankPrompt->prevPrompt = -1;929topRankPrompt->promptID = id + 15;930topRankPrompt->visible = TimeAttackMenu->isUser;931932EntityUIControl *leaderboardsControl = TimeAttackMenu->leaderboardsControl;933TimeAttackMenu_SetupLeaderboardsCarousel(leaderboardsControl->carousel);934UIDialog_CloseOnSel_HandleSelection(dialog, self->callback);935936self->callback = StateMachine_None;937TimeAttackMenu->connectingDlg = NULL;938destroyEntity(self);939break;940}941}942}943944void TimeAttackMenu_SetupLeaderboardsCarousel(EntityUICarousel *carousel)945{946EntityUIControl *parent = (EntityUIControl *)carousel->parent;947948LeaderboardAvail avail = API.LeaderboardEntryViewSize();949carousel->minOffset = MAX(avail.start, 1);950carousel->maxOffset = MAX(avail.start + avail.length, carousel->minOffset + 5);951952if (TimeAttackMenu->prevIsUser && avail.length) {953int32 userID = 0;954955int32 end = avail.start + avail.length;956for (int32 entryID = avail.start; entryID < end; ++entryID) {957LeaderboardEntry *entry = API.ReadLeaderboardEntry(entryID);958if (entry->isUser) {959userID = entryID;960break;961}962}963964carousel->scrollOffset = userID != parent->buttonCount >> 1 ? userID - (parent->buttonCount >> 1) : 0;965carousel->virtualIndex = !userID ? avail.start : userID;966}967else {968carousel->virtualIndex = 1;969carousel->scrollOffset = 0;970}971972for (int32 i = 0; i < parent->buttonCount; ++i) {973int32 max = (i - carousel->scrollOffset) % parent->buttonCount;974if (max < 0)975max += parent->buttonCount;976977int32 vIndex = max + carousel->scrollOffset;978LogHelpers_Print("i = %d, vIndex = %d", i, vIndex);979980if (vIndex == carousel->virtualIndex) {981parent->buttonID = i;982break;983}984}985986LogHelpers_Print("avail.start = %d, length = %d", avail.start, avail.length);987LogHelpers_Print("scrollOffset = %d", carousel->scrollOffset);988LogHelpers_Print("virtualIndex = %d", carousel->virtualIndex);989LogHelpers_Print("minOffset = %d", carousel->minOffset);990LogHelpers_Print("maxOffset = %d", carousel->maxOffset);991992carousel->buttonID = -1;993}994995#if GAME_INCLUDE_EDITOR996void TimeAttackMenu_EditorDraw(void) {}997998void TimeAttackMenu_EditorLoad(void) {}999#endif10001001void TimeAttackMenu_Serialize(void) {}1002#endif100310041005