Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rubberduckycooly
GitHub Repository: rubberduckycooly/Sonic-Mania-Decompilation
Path: blob/master/SonicMania/Objects/Menu/TimeAttackMenu.c
338 views
1
// ---------------------------------------------------------------------
2
// RSDK Project: Sonic Mania
3
// Object Description: TimeAttackMenu Object
4
// Object Author: Christian Whitehead/Simon Thomley/Hunter Bridges
5
// Decompiled by: Rubberduckycooly & RMGRich
6
// ---------------------------------------------------------------------
7
8
#include "Game.h"
9
10
#if MANIA_USE_PLUS
11
ObjectTimeAttackMenu *TimeAttackMenu;
12
13
void TimeAttackMenu_Update(void)
14
{
15
RSDK_THIS(TimeAttackMenu);
16
17
StateMachine_Run(self->state);
18
}
19
20
void TimeAttackMenu_LateUpdate(void) {}
21
22
void TimeAttackMenu_StaticUpdate(void)
23
{
24
#if MANIA_USE_EGS
25
if (TimeAttackMenu->taDetailsControl && !API.CheckLeaderboardsEnabled()) {
26
for (int32 p = 0; p < TimeAttackMenu->taDetailsControl->promptCount; ++p) {
27
EntityUIButtonPrompt *prompt = TimeAttackMenu->taDetailsControl->prompts[p];
28
if (!prompt)
29
continue;
30
31
if (prompt->promptID == 5)
32
prompt->visible = false;
33
}
34
}
35
#endif
36
37
EntityUIButtonPrompt *switchModePrompt = TimeAttackMenu->switchModePrompt;
38
39
if (switchModePrompt) {
40
if (API.CheckDLC(DLC_PLUS)) {
41
switchModePrompt->visible = true;
42
}
43
else {
44
if (TimeAttackMenu->encoreMode)
45
TimeAttackMenu_SetEncoreLayouts(false);
46
47
switchModePrompt->visible = false;
48
}
49
}
50
}
51
52
void TimeAttackMenu_Draw(void) {}
53
54
void TimeAttackMenu_Create(void *data) {}
55
56
void TimeAttackMenu_StageLoad(void)
57
{
58
TimeAttackMenu->encoreMode = false;
59
TimeAttackMenu->prevIsUser = false;
60
TimeAttackMenu->isUser = false;
61
}
62
63
void TimeAttackMenu_Initialize(void)
64
{
65
String tag;
66
INIT_STRING(tag);
67
68
foreach_all(UIControl, control)
69
{
70
RSDK.SetString(&tag, "Time Attack");
71
if (RSDK.CompareStrings(&tag, &control->tag, false))
72
TimeAttackMenu->timeAttackControl = control;
73
74
RSDK.SetString(&tag, "Time Attack Legacy");
75
if (RSDK.CompareStrings(&tag, &control->tag, false))
76
TimeAttackMenu->timeAttackControl_Legacy = control;
77
78
RSDK.SetString(&tag, "Time Attack Zones");
79
if (RSDK.CompareStrings(&tag, &control->tag, false))
80
TimeAttackMenu->taZoneSelControl = control;
81
82
RSDK.SetString(&tag, "Time Attack Detail");
83
if (RSDK.CompareStrings(&tag, &control->tag, false))
84
TimeAttackMenu->taDetailsControl = control;
85
86
RSDK.SetString(&tag, "Leaderboards");
87
if (RSDK.CompareStrings(&tag, &control->tag, false)) {
88
TimeAttackMenu->leaderboardsControl = control;
89
control->backPressCB = TimeAttackMenu_LeaderboardsBackPressCB;
90
}
91
92
RSDK.SetString(&tag, "Replays");
93
if (RSDK.CompareStrings(&tag, &control->tag, false))
94
TimeAttackMenu->replaysControl = control;
95
}
96
97
EntityUIControl *zoneControl = TimeAttackMenu->taZoneSelControl;
98
EntityUIControl *lbControl = TimeAttackMenu->leaderboardsControl;
99
EntityUIControl *replayControl = TimeAttackMenu->replaysControl;
100
EntityUIControl *detailsControl = TimeAttackMenu->taDetailsControl;
101
102
foreach_all(UIButtonPrompt, prompt)
103
{
104
if (UIControl_ContainsPos(zoneControl, &prompt->position) && prompt->buttonID == 3)
105
TimeAttackMenu->switchModePrompt = prompt;
106
107
if (UIControl_ContainsPos(lbControl, &prompt->position) && prompt->buttonID == 3)
108
TimeAttackMenu->topRankPrompt = prompt;
109
110
if (UIControl_ContainsPos(replayControl, &prompt->position) && prompt->buttonID == 2)
111
TimeAttackMenu->replayPrompt = prompt;
112
}
113
114
foreach_all(UILeaderboard, leaderboard) { TimeAttackMenu->leaderboard = leaderboard; }
115
116
foreach_all(UIReplayCarousel, carousel) { TimeAttackMenu->replayCarousel = carousel; }
117
118
foreach_all(UITABanner, banner)
119
{
120
if (UIControl_ContainsPos(detailsControl, &banner->position)) {
121
TimeAttackMenu->detailsBanner = banner;
122
banner->parent = TimeAttackMenu->taDetailsControl;
123
}
124
125
if (UIControl_ContainsPos(lbControl, &banner->position)) {
126
TimeAttackMenu->leaderboardsBanner = banner;
127
banner->parent = TimeAttackMenu->leaderboardsControl;
128
}
129
}
130
}
131
132
void TimeAttackMenu_HandleUnlocks(void)
133
{
134
foreach_all(UITAZoneModule, module) { module->disabled = !GameProgress_GetZoneUnlocked(module->zoneID); }
135
}
136
137
void TimeAttackMenu_SetupActions(void)
138
{
139
EntityUIControl *control = TimeAttackMenu->timeAttackControl;
140
EntityUIControl *leaderboardsControl = TimeAttackMenu->leaderboardsControl;
141
EntityUIControl *replayControl = TimeAttackMenu->replaysControl;
142
EntityUIControl *zoneSelControl = TimeAttackMenu->taZoneSelControl;
143
EntityUIControl *detailsControl = TimeAttackMenu->taDetailsControl;
144
145
control->menuUpdateCB = TimeAttackMenu_MenuUpdateCB;
146
147
EntityUIButton *replaysButton = control->buttons[control->buttonCount - 1];
148
replaysButton->actionCB = TimeAttackMenu_ReplayButton_ActionCB;
149
150
int32 newCount = (control->buttonCount - 1) + control->columnCount;
151
for (int32 i = control->buttonCount; i < newCount; ++i) {
152
control->buttons[i] = control->buttons[i - 1];
153
}
154
control->buttonCount = newCount;
155
156
foreach_all(UICharButton, charButton)
157
{
158
if (charButton->parent == (Entity *)TimeAttackMenu->timeAttackControl
159
|| charButton->parent == (Entity *)TimeAttackMenu->timeAttackControl_Legacy)
160
charButton->actionCB = TimeAttackMenu_CharButton_ActionCB;
161
}
162
163
foreach_all(UITAZoneModule, module) { module->actionCB = TimeAttackMenu_TAModule_ActionCB; }
164
165
for (int32 i = 0; i < leaderboardsControl->buttonCount; ++i) {
166
EntityUIRankButton *rankButton = (EntityUIRankButton *)leaderboardsControl->buttons[i];
167
168
UIRankButton_SetRankText(rankButton, i);
169
}
170
171
leaderboardsControl->menuUpdateCB = TimeAttackMenu_MenuUpdateCB_LB;
172
leaderboardsControl->yPressCB = TimeAttackMenu_YPressCB_LB;
173
174
replayControl->menuSetupCB = TimeAttackMenu_MenuSetupCB_Replay;
175
replayControl->menuUpdateCB = TimeAttackMenu_MenuUpdateCB_Replay;
176
replayControl->xPressCB = TimeAttackMenu_DeleteReplayActionCB;
177
replayControl->yPressCB = TimeAttackMenu_YPressCB_Replay;
178
179
zoneSelControl->yPressCB = TimeAttackMenu_YPressCB_ZoneSel;
180
zoneSelControl->backPressCB = TimeAttackMenu_BackPressCB_ZoneSel;
181
182
#if MANIA_USE_EGS
183
if (API.CheckLeaderboardsEnabled())
184
#endif
185
detailsControl->yPressCB = TimeAttackMenu_YPressCB_Details;
186
detailsControl->xPressCB = TimeAttackMenu_XPressCB_Details;
187
detailsControl->menuSetupCB = TimeAttackMenu_MenuSetupCB_Details;
188
189
EntityUIButton *replayButton = detailsControl->buttons[0];
190
// Actually a UIChoice not a TAZoneModule but it fits better with pre-plus this way
191
replayButton->actionCB = TimeAttackMenu_TAZoneModule_ActionCB;
192
replayButton->choiceChangeCB = TimeAttackMenu_TAZoneModule_ChoiceChangeCB;
193
194
EntityUIReplayCarousel *replayCarousel = TimeAttackMenu->replayCarousel;
195
replayCarousel->actionCB = TimeAttackMenu_ReplayCarousel_ActionCB;
196
replayControl->buttons[0]->choiceChangeCB = TimeAttackMenu_SortReplayChoiceCB;
197
}
198
199
void TimeAttackMenu_HandleMenuReturn(void)
200
{
201
EntityMenuParam *param = MenuParam_GetParam();
202
203
if (param->inTimeAttack)
204
TimeAttackMenu_SetEncoreLayouts(param->isEncoreMode);
205
206
EntityUIControl *control = TimeAttackMenu->timeAttackControl;
207
if (param->inTimeAttack) {
208
int32 charID = param->characterID - 1;
209
control->buttonID = charID;
210
control->buttons[charID]->isSelected = true;
211
}
212
213
EntityUIControl *legacyControl = TimeAttackMenu->timeAttackControl_Legacy;
214
if (param->inTimeAttack) {
215
int32 charID = param->characterID - 1;
216
if (param->characterID - 1 >= UICHARBUTTON_MIGHTY)
217
charID = UICHARBUTTON_SONIC;
218
219
legacyControl->buttonID = charID;
220
legacyControl->buttons[charID]->isSelected = true;
221
}
222
223
EntityUIControl *zoneControl = TimeAttackMenu->taZoneSelControl;
224
if (param->inTimeAttack) {
225
zoneControl->buttonID = param->zoneID;
226
zoneControl->buttons[param->zoneID]->isSelected = true;
227
}
228
229
EntityUIControl *detailsControl = TimeAttackMenu->taDetailsControl;
230
if (param->inTimeAttack) {
231
UITABanner_SetupDetails(TimeAttackMenu->detailsBanner, param->zoneID, param->actID, param->characterID, param->isEncoreMode);
232
TimeAttackMenu_SetupDetailsView();
233
234
UIButton_SetChoiceSelectionWithCB(detailsControl->buttons[0], param->actID);
235
detailsControl->buttonID = param->menuSelection;
236
237
TimeAttackMenu_TAZoneModule_ChoiceChangeCB();
238
}
239
240
EntityUIControl *replayControl = TimeAttackMenu->replaysControl;
241
if (replayControl->active == ACTIVE_ALWAYS) {
242
replayControl->buttonID = 1; // Select Replay Carousel
243
UIButton_SetChoiceSelectionWithCB(replayControl->buttons[0], param->selectedReplay & 0xFF);
244
245
EntityUIReplayCarousel *carousel = TimeAttackMenu->replayCarousel;
246
int32 replayCount = API.GetSortedUserDBRowCount(globals->replayTableID);
247
int32 targetID = API.GetUserDBRowByID(globals->replayTableID, param->replayUUID);
248
249
int32 replayID = 0;
250
for (; replayID < replayCount; ++replayID) {
251
if (API.GetSortedUserDBRowID(globals->replayTableID, replayID) == targetID)
252
break;
253
}
254
255
if (replayCount <= 0 || replayID >= replayCount)
256
carousel->curReplayID = param->replayID;
257
else
258
carousel->curReplayID = replayID;
259
}
260
}
261
262
void TimeAttackMenu_SetEncoreLayouts(bool32 enabled)
263
{
264
LogHelpers_Print("SetEncoreLayouts(%d)", enabled);
265
TimeAttackMenu->encoreMode = enabled;
266
267
EntityUIButtonPrompt *prompt = TimeAttackMenu->switchModePrompt;
268
prompt->promptID = 21 - (enabled != false);
269
270
foreach_all(UITAZoneModule, module) { module->isEncore = enabled; }
271
}
272
273
void TimeAttackMenu_DeleteReplayActionCB(void)
274
{
275
EntityUIControl *replayControl = TimeAttackMenu->replaysControl;
276
EntityUIReplayCarousel *carousel = TimeAttackMenu->replayCarousel;
277
278
if (replayControl->buttonID == 1 && carousel->stateDraw == UIReplayCarousel_Draw_Carousel
279
&& API.GetSortedUserDBRowCount(globals->replayTableID)) {
280
String string;
281
INIT_STRING(string);
282
283
Localization_GetString(&string, STR_DELETEREPLAY);
284
UIDialog_CreateDialogYesNo(&string, TimeAttackMenu_ConfirmDeleteReplay_Yes_CB, StateMachine_None, true, true);
285
}
286
}
287
288
void TimeAttackMenu_ConfirmDeleteReplay_Yes_CB(void)
289
{
290
EntityUIReplayCarousel *carousel = TimeAttackMenu->replayCarousel;
291
292
int32 row = API.GetSortedUserDBRowID(globals->replayTableID, carousel->curReplayID);
293
ReplayDB_DeleteReplay(row, TimeAttackMenu_DeleteReplayCB, false);
294
}
295
296
void TimeAttackMenu_DeleteReplayCB(bool32 success)
297
{
298
TimeAttackMenu_SortReplayChoiceCB();
299
300
EntityUIReplayCarousel *carousel = TimeAttackMenu->replayCarousel;
301
302
int32 count = API.GetSortedUserDBRowCount(globals->replayTableID) - 1;
303
if (carousel->curReplayID > count)
304
carousel->curReplayID = count;
305
}
306
307
void TimeAttackMenu_MenuUpdateCB_LB(void)
308
{
309
EntityUIControl *control = TimeAttackMenu->leaderboardsControl;
310
311
if (control->active == ACTIVE_ALWAYS) {
312
EntityUICarousel *carousel = control->carousel;
313
LeaderboardAvail avail = API.LeaderboardEntryViewSize();
314
315
if (avail.start <= 1 || carousel->scrollOffset >= avail.start + 2) {
316
if (carousel->scrollOffset > avail.length - control->buttonCount + avail.start - 2) {
317
// Load Down
318
API.LoadLeaderboardEntries(avail.start, avail.length + 20, LEADERBOARD_LOAD_NEXT);
319
}
320
}
321
else {
322
// Load Up
323
API.LoadLeaderboardEntries(avail.start - 20, avail.length + 20, LEADERBOARD_LOAD_PREV);
324
}
325
326
// Load the new entry count after (possibly) reloading new entries
327
avail = API.LeaderboardEntryViewSize();
328
carousel->minOffset = MAX(avail.start, 1);
329
carousel->maxOffset = MAX(avail.start + avail.length, carousel->minOffset + 5);
330
331
for (int32 i = 0; i < control->buttonCount; ++i) {
332
EntityUIRankButton *button = (EntityUIRankButton *)control->buttons[i];
333
EntityUIControl *parent = carousel->parent;
334
335
int32 max = (i - carousel->scrollOffset) % parent->buttonCount;
336
if (max < 0)
337
max += parent->buttonCount;
338
339
LeaderboardEntry *entry = API.ReadLeaderboardEntry(max + carousel->scrollOffset);
340
if (entry != button->leaderboardEntry)
341
UIRankButton_SetupLeaderboardRank(button, entry);
342
}
343
}
344
}
345
346
void TimeAttackMenu_SetupLeaderboards(int32 zoneID, int32 characterID, int32 act, bool32 isEncore, bool32 isUser, void (*callback)(void))
347
{
348
String string;
349
INIT_STRING(string);
350
351
Localization_GetString(&string, STR_CONNECTING);
352
EntityUIDialog *dialog = UIDialog_CreateActiveDialog(&string);
353
354
if (dialog) {
355
UIDialog_Setup(dialog);
356
TimeAttackMenu->connectingDlg = dialog;
357
358
EntityTimeAttackMenu *entity = CREATE_ENTITY(TimeAttackMenu, NULL, -0x100000, -0x100000);
359
entity->active = ACTIVE_NORMAL;
360
entity->visible = true;
361
entity->delay = 120;
362
entity->state = TimeAttackMenu_State_SetupLeaderboards;
363
entity->callback = callback;
364
TimeAttackMenu->isUser = API.GetSortedUserDBRowCount(globals->taTableID) != 0;
365
TimeAttackMenu->prevIsUser = isUser ? false : TimeAttackMenu->isUser;
366
367
LeaderboardID *leaderboardInfo = TimeAttackData_GetLeaderboardInfo(zoneID, act, characterID, isEncore);
368
API.FetchLeaderboard(leaderboardInfo, TimeAttackMenu->prevIsUser);
369
370
UITABanner_SetupDetails(TimeAttackMenu->leaderboardsBanner, zoneID, act, characterID, isEncore);
371
}
372
}
373
374
void TimeAttackMenu_ReplayCarousel_ActionCB(void)
375
{
376
RSDK_THIS(UIReplayCarousel);
377
378
EntityUIPopover *popover = UIPopover_CreatePopover();
379
if (popover) {
380
int32 y = self->position.y;
381
if (!self->curReplayID)
382
y += self->popoverPos;
383
384
UIPopover_AddButton(popover, POPOVER_WATCH, TimeAttackMenu_WatchReplayActionCB_ReplaysMenu, false);
385
UIPopover_AddButton(popover, POPOVER_CHALLENGE, TimeAttackMenu_ChallengeReplayActionCB_ReplaysMenu, false);
386
UIPopover_AddButton(popover, POPOVER_DELETE, TimeAttackMenu_DeleteReplayActionCB, true);
387
UIPopover_Setup(popover, self->position.x, y);
388
}
389
}
390
391
void TimeAttackMenu_WatchReplay(int32 row, bool32 showGhost)
392
{
393
EntityMenuParam *param = MenuParam_GetParam();
394
395
int32 id = API_GetFilteredInputDeviceID(false, false, 0);
396
API_ResetInputSlotAssignments();
397
API_AssignInputSlotToDevice(CONT_P1, id);
398
399
uint32 uuid = API.GetUserDBRowUUID(globals->replayTableID, row);
400
LogHelpers_Print("Go_Replay(%d, %d)", row, showGhost);
401
LogHelpers_Print("uuid: %08X", uuid);
402
403
int32 score = 0;
404
uint8 zoneID = 0;
405
uint8 act = 0;
406
uint8 characterID = 0;
407
uint8 encore = 0;
408
API.GetUserDBValue(globals->replayTableID, row, DBVAR_UINT32, "score", &score);
409
API.GetUserDBValue(globals->replayTableID, row, DBVAR_UINT8, "zoneID", &zoneID);
410
API.GetUserDBValue(globals->replayTableID, row, DBVAR_UINT8, "act", &act);
411
API.GetUserDBValue(globals->replayTableID, row, DBVAR_UINT8, "characterID", &characterID);
412
API.GetUserDBValue(globals->replayTableID, row, DBVAR_UINT8, "encore", &encore);
413
414
param->viewReplay = true;
415
param->showGhost = showGhost;
416
param->replayUUID = uuid;
417
param->zoneID = zoneID;
418
param->actID = act;
419
param->characterID = characterID;
420
param->isEncoreMode = encore;
421
422
int32 replayID = 0;
423
if (!showGhost) {
424
if (!TimeAttackData->loaded || characterID != TimeAttackData->characterID || zoneID != TimeAttackData->zoneID || act != TimeAttackData->act
425
|| encore != (int32)TimeAttackData->encore) {
426
TimeAttackData_ConfigureTableView(zoneID, act, characterID, encore);
427
}
428
429
int32 entryCount = API.GetSortedUserDBRowCount(globals->taTableID);
430
for (int32 rank = 1; rank < entryCount; ++rank) {
431
if (uuid == (uint32)TimeAttackData_GetReplayID(zoneID, act, characterID, encore, rank))
432
break;
433
434
++replayID;
435
}
436
}
437
param->selectedReplay = replayID;
438
439
EntityUIPopover *popover = UIPopover->activePopover;
440
if (popover)
441
popover->parent->selectionDisabled = true;
442
443
UIWaitSpinner_StartWait();
444
445
char fileName[0x20];
446
sprintf_s(fileName, (int32)sizeof(fileName), "Replay_%08X.bin", uuid);
447
448
memset(globals->replayTempRBuffer, 0, sizeof(globals->replayTempRBuffer));
449
memset(globals->replayReadBuffer, 0, sizeof(globals->replayReadBuffer));
450
ReplayRecorder_Buffer_LoadFile(fileName, globals->replayTempRBuffer, TimeAttackMenu_ReplayLoad_CB);
451
}
452
453
void TimeAttackMenu_ReplayLoad_CB(bool32 success)
454
{
455
UIWaitSpinner_FinishWait();
456
457
int32 strID = 0;
458
if (success) {
459
Replay *replayPtr = (Replay *)globals->replayTempRBuffer;
460
461
if (replayPtr->header.version == GAME_VERSION) {
462
LogHelpers_Print("WARNING: Replay Load OK");
463
ReplayRecorder_Buffer_Unpack(globals->replayReadBuffer, globals->replayTempRBuffer);
464
TimeAttackMenu_LoadScene_Fadeout();
465
return;
466
}
467
468
strID = STR_CANNOTLOADREPLAY;
469
}
470
else {
471
strID = STR_ERRORLOADINGREPLAY;
472
}
473
474
String message;
475
INIT_STRING(message);
476
Localization_GetString(&message, strID);
477
478
EntityUIDialog *dialog = UIDialog_CreateDialogOk(&message, StateMachine_None, true);
479
if (dialog) {
480
EntityUIPopover *popover = UIPopover->activePopover;
481
if (popover)
482
popover->parent->selectionDisabled = false;
483
}
484
}
485
486
void TimeAttackMenu_WatchReplayActionCB_ReplaysMenu(void)
487
{
488
EntityMenuParam *param = MenuParam_GetParam();
489
490
EntityUIControl *control = TimeAttackMenu->replaysControl;
491
EntityUIReplayCarousel *carousel = TimeAttackMenu->replayCarousel;
492
EntityUIButton *button = control->buttons[0];
493
494
sprintf_s(param->menuTag, (int32)sizeof(param->menuTag), "Replays");
495
param->replayRankID = button->selection;
496
param->replayID = carousel->curReplayID;
497
498
int32 id = API.GetSortedUserDBRowID(globals->replayTableID, carousel->curReplayID);
499
TimeAttackMenu_WatchReplay(id, false);
500
}
501
502
void TimeAttackMenu_ChallengeReplayActionCB_ReplaysMenu(void)
503
{
504
EntityMenuParam *param = MenuParam_GetParam();
505
506
EntityUIControl *control = TimeAttackMenu->replaysControl;
507
EntityUIReplayCarousel *carousel = TimeAttackMenu->replayCarousel;
508
EntityUIButton *button = control->buttons[0];
509
510
sprintf_s(param->menuTag, (int32)sizeof(param->menuTag), "Replays");
511
param->replayRankID = button->selection;
512
param->replayID = carousel->curReplayID;
513
514
int32 id = API.GetSortedUserDBRowID(globals->replayTableID, carousel->curReplayID);
515
TimeAttackMenu_WatchReplay(id, true);
516
}
517
518
void TimeAttackMenu_WatchReplayCB_RanksMenu(void)
519
{
520
EntityMenuParam *param = MenuParam_GetParam();
521
EntityUIPopover *popover = UIPopover->activePopover;
522
EntityUIRankButton *button = (EntityUIRankButton *)popover->storedEntity;
523
EntityUIControl *parent = (EntityUIControl *)button->parent;
524
525
int32 uuid = API.GetUserDBRowByID(globals->replayTableID, button->replayID);
526
if (uuid != -1) {
527
RSDK.GetCString(param->menuTag, &parent->tag);
528
param->menuSelection = parent->buttonID;
529
param->inTimeAttack = true;
530
TimeAttackMenu_WatchReplay(uuid, false);
531
}
532
}
533
534
void TimeAttackMenu_ChallengeReplayCB_RanksMenu(void)
535
{
536
EntityMenuParam *param = MenuParam_GetParam();
537
EntityUIPopover *popover = UIPopover->activePopover;
538
EntityUIRankButton *button = (EntityUIRankButton *)popover->storedEntity;
539
EntityUIControl *parent = (EntityUIControl *)button->parent;
540
541
int32 uuid = API.GetUserDBRowByID(globals->replayTableID, button->replayID);
542
if (uuid != -1) {
543
RSDK.GetCString(param->menuTag, &parent->tag);
544
param->menuSelection = parent->buttonID;
545
param->inTimeAttack = true;
546
TimeAttackMenu_WatchReplay(uuid, true);
547
}
548
}
549
550
void TimeAttackMenu_LoadScene_Fadeout(void)
551
{
552
Music_FadeOut(0.05);
553
MenuSetup_StartTransition(TimeAttackMenu_LoadScene, 32);
554
}
555
556
void TimeAttackMenu_MenuSetupCB_Replay(void)
557
{
558
if (API.GetUserDBRowsChanged(globals->replayTableID))
559
TimeAttackMenu_SortReplayChoiceCB();
560
}
561
562
void TimeAttackMenu_MenuUpdateCB_Replay(void)
563
{
564
EntityUIControl *replayControl = TimeAttackMenu->replaysControl;
565
EntityUIReplayCarousel *carousel = TimeAttackMenu->replayCarousel;
566
567
if (replayControl->lastButtonID != 1)
568
carousel->curReplayID = -1;
569
570
EntityUIButtonPrompt *prompt = TimeAttackMenu->replayPrompt;
571
if (prompt)
572
prompt->visible = API.GetSortedUserDBRowCount(globals->replayTableID) != 0;
573
}
574
575
void TimeAttackMenu_MenuUpdateCB(void)
576
{
577
EntityUIControl *control = TimeAttackMenu->timeAttackControl;
578
579
// Keep "Replays" button centered
580
control->buttons[control->buttonCount - 1]->position.x = control->position.x;
581
}
582
583
void TimeAttackMenu_ReplayButton_ActionCB(void)
584
{
585
EntityUIControl *control = TimeAttackMenu->replaysControl;
586
EntityUIReplayCarousel *carousel = TimeAttackMenu->replayCarousel;
587
588
control->buttonID = 1;
589
carousel->curReplayID = 0;
590
UIControl_MatchMenuTag("Replays");
591
592
UIButton_SetChoiceSelectionWithCB(control->buttons[0], 0);
593
}
594
595
void TimeAttackMenu_YPressCB_Replay(void)
596
{
597
EntityUIControl *control = TimeAttackMenu->replaysControl;
598
EntityUIReplayCarousel *carousel = TimeAttackMenu->replayCarousel;
599
600
if (carousel->stateDraw == UIReplayCarousel_Draw_Carousel) {
601
control->buttonID = 0;
602
603
carousel->curReplayID = -1;
604
carousel->curViewOffset = 0;
605
carousel->targetViewOffset = 0;
606
607
UIButton_SetChoiceSelectionWithCB(control->buttons[0], control->buttons[0]->selection ^ 1);
608
609
RSDK.PlaySfx(UIWidgets->sfxBleep, false, 255);
610
}
611
}
612
613
void TimeAttackMenu_SortReplayChoiceCB(void)
614
{
615
EntityUIControl *control = TimeAttackMenu->replaysControl;
616
EntityUIReplayCarousel *carousel = TimeAttackMenu->replayCarousel;
617
618
API.SetupUserDBRowSorting(globals->replayTableID);
619
620
if (control->buttons[0]->selection == 1)
621
API.SortDBRows(globals->replayTableID, DBVAR_UINT32, "zoneSortVal", false);
622
else
623
API.SortDBRows(globals->replayTableID, 0, NULL, true);
624
625
carousel->stateDraw = UIReplayCarousel_Draw_Loading;
626
}
627
628
void TimeAttackMenu_SetupDetailsView(void)
629
{
630
EntityMenuParam *param = MenuParam_GetParam();
631
EntityUIControl *control = TimeAttackMenu->taDetailsControl;
632
EntityUITABanner *banner = TimeAttackMenu->detailsBanner;
633
634
EntityUIChoice *act1 = (EntityUIChoice *)UIButton_GetChoicePtr(control->buttons[0], 0);
635
EntityUIChoice *act2 = (EntityUIChoice *)UIButton_GetChoicePtr(control->buttons[0], 1);
636
637
if (!TimeAttackMenu->encoreMode && banner->zoneID == 7 && param->characterID != 3) {
638
// Prevent us from selecting MSZ1 in mania mode if we're not knux
639
act1->disabled = true;
640
UIButton_SetChoiceSelectionWithCB(control->buttons[0], 1);
641
act2->arrowWidth = 0;
642
}
643
else {
644
act1->disabled = false;
645
UIButton_SetChoiceSelectionWithCB(control->buttons[0], 0);
646
act2->arrowWidth = 48;
647
}
648
}
649
650
void TimeAttackMenu_TAModule_ActionCB(void)
651
{
652
RSDK_THIS(UITAZoneModule);
653
654
EntityMenuParam *param = MenuParam_GetParam();
655
EntityUITABanner *banner = TimeAttackMenu->detailsBanner;
656
EntityUIControl *control = TimeAttackMenu->taDetailsControl;
657
658
param->zoneID = self->zoneID;
659
control->buttonID = 0;
660
661
UITABanner_SetupDetails(banner, self->zoneID, 0, self->characterID, TimeAttackMenu->encoreMode);
662
TimeAttackMenu_SetupDetailsView();
663
664
UITransition_SetNewTag("Time Attack Detail");
665
}
666
667
void TimeAttackMenu_StartTAAttempt(void)
668
{
669
EntityMenuParam *param = MenuParam_GetParam();
670
671
sprintf_s(param->menuTag, (int32)sizeof(param->menuTag), "Time Attack Detail");
672
param->menuSelection = 0;
673
param->inTimeAttack = true;
674
param->isEncoreMode = TimeAttackMenu->encoreMode;
675
676
Replay *replayPtr = (Replay *)globals->replayReadBuffer;
677
if (replayPtr->header.isNotEmpty && replayPtr->header.signature == REPLAY_SIGNATURE)
678
memset(globals->replayReadBuffer, 0, sizeof(globals->replayReadBuffer));
679
680
TimeAttackMenu_LoadScene();
681
}
682
683
void TimeAttackMenu_LoadScene(void)
684
{
685
EntityMenuParam *param = MenuParam_GetParam();
686
687
SaveGame_ResetPlayerState();
688
689
// Bug Details(?):
690
// sizeof(globals->noSaveSlot) and sizeof(saveData) is 4096 (sizeof(int32) * 0x400)
691
// but the memset size is only 1024 (sizeof(uint8) * 0x400)
692
// so only about 1/4th of the save slot is cleared, though nothin uses the extra space so it's not a big deal
693
memset(globals->noSaveSlot, 0, 0x400);
694
695
globals->continues = 0;
696
globals->saveSlotID = NO_SAVE_SLOT;
697
globals->gameMode = MODE_TIMEATTACK;
698
globals->medalMods = 0;
699
700
if (param->isEncoreMode)
701
RSDK.SetScene("Encore Mode", "");
702
else
703
RSDK.SetScene("Mania Mode", "");
704
705
if (param->isEncoreMode)
706
SceneInfo->listPos += TimeAttackData_GetEncoreListPos(param->zoneID, param->actID, param->characterID);
707
else
708
SceneInfo->listPos += TimeAttackData_GetManiaListPos(param->zoneID, param->actID, param->characterID);
709
710
switch (param->characterID) {
711
case 1: globals->playerID = ID_SONIC; break;
712
case 2: globals->playerID = ID_TAILS; break;
713
case 3: globals->playerID = ID_KNUCKLES; break;
714
case 4: globals->playerID = ID_MIGHTY; break;
715
case 5: globals->playerID = ID_RAY; break;
716
default: break;
717
}
718
719
RSDK.LoadScene();
720
}
721
722
void TimeAttackMenu_YPressCB_ZoneSel(void)
723
{
724
if (API.CheckDLC(DLC_PLUS)) {
725
TimeAttackMenu_SetEncoreLayouts(!TimeAttackMenu->encoreMode);
726
RSDK.PlaySfx(UIWidgets->sfxWoosh, false, 255);
727
}
728
}
729
730
bool32 TimeAttackMenu_BackPressCB_ZoneSel(void)
731
{
732
if (!API.CheckDLC(DLC_PLUS))
733
UITransition_SetNewTag("Time Attack Legacy");
734
else
735
UITransition_SetNewTag("Time Attack");
736
737
return true;
738
}
739
740
void TimeAttackMenu_YPressCB_Details(void)
741
{
742
EntityUITABanner *banner = TimeAttackMenu->detailsBanner;
743
744
int32 rowCount = API.GetSortedUserDBRowCount(globals->taTableID);
745
TimeAttackMenu_SetupLeaderboards(banner->zoneID, banner->characterID, banner->actID, TimeAttackMenu->encoreMode, !rowCount,
746
UITAZoneModule_ShowLeaderboards_CB);
747
}
748
749
void TimeAttackMenu_ResetTimes_YesCB(void)
750
{
751
EntityUITABanner *banner = TimeAttackMenu->detailsBanner;
752
EntityUIControl *control = TimeAttackMenu->taDetailsControl;
753
754
int32 act = control->buttons[0]->selection;
755
while (API.GetSortedUserDBRowCount(globals->taTableID) > 0) {
756
int32 rowID = API.GetSortedUserDBRowID(globals->taTableID, 0);
757
API.RemoveDBRow(globals->taTableID, rowID);
758
759
TimeAttackData_ConfigureTableView(banner->zoneID, act, banner->characterID, TimeAttackMenu->encoreMode);
760
}
761
762
control->buttonID = 0;
763
TimeAttackData_SaveDB(NULL);
764
765
TimeAttackMenu_TAZoneModule_ChoiceChangeCB();
766
}
767
768
void TimeAttackMenu_XPressCB_Details(void)
769
{
770
String message;
771
INIT_STRING(message);
772
773
Localization_GetString(&message, STR_RESETTIMESWARNING);
774
UIDialog_CreateDialogYesNo(&message, TimeAttackMenu_ResetTimes_YesCB, StateMachine_None, true, true);
775
}
776
777
void TimeAttackMenu_TAZoneModule_ActionCB(void)
778
{
779
RSDK_THIS(UIButton);
780
781
EntityMenuParam *param = MenuParam_GetParam();
782
EntityUIControl *parent = (EntityUIControl *)self->parent;
783
784
parent->selectionDisabled = true;
785
param->actID = self->selection;
786
787
MenuSetup_StartTransition(TimeAttackMenu_StartTAAttempt, 32);
788
}
789
790
void TimeAttackMenu_RankButton_ActionCB(void)
791
{
792
RSDK_THIS(UIRankButton);
793
794
EntityUIPopover *popover = UIPopover_CreatePopover();
795
if (popover) {
796
popover->storedEntity = (Entity *)self;
797
798
UIPopover_AddButton(popover, POPOVER_WATCH, TimeAttackMenu_WatchReplayCB_RanksMenu, false);
799
UIPopover_AddButton(popover, POPOVER_CHALLENGE, TimeAttackMenu_ChallengeReplayCB_RanksMenu, false);
800
UIPopover_Setup(popover, self->popoverPos.x, self->popoverPos.y);
801
}
802
}
803
804
void TimeAttackMenu_MenuSetupCB_Details(void) { TimeAttackMenu_TAZoneModule_ChoiceChangeCB(); }
805
806
void TimeAttackMenu_TAZoneModule_ChoiceChangeCB(void)
807
{
808
EntityMenuParam *param = MenuParam_GetParam();
809
EntityUIControl *control = TimeAttackMenu->taDetailsControl;
810
811
int32 act = control->buttons[0]->selection;
812
813
UITABanner_SetupDetails(TimeAttackMenu->detailsBanner, param->zoneID, act, param->characterID, TimeAttackMenu->encoreMode);
814
TimeAttackData_ConfigureTableView(param->zoneID, act, param->characterID, TimeAttackMenu->encoreMode);
815
816
int32 rowCount = 1;
817
for (int32 rank = 1; rank < 4; ++rank) {
818
EntityUIRankButton *rankButton = (EntityUIRankButton *)control->buttons[rank];
819
820
int32 score = TimeAttackData_GetScore(param->zoneID, act, param->characterID, TimeAttackMenu->encoreMode, rank);
821
int32 replayID = TimeAttackData_GetReplayID(param->zoneID, act, param->characterID, TimeAttackMenu->encoreMode, rank);
822
823
UIRankButton_SetTimeAttackRank(rankButton, rank, score, replayID);
824
825
if (score)
826
++rowCount;
827
828
if (API.CheckDLC(DLC_PLUS) && replayID)
829
rankButton->actionCB = TimeAttackMenu_RankButton_ActionCB;
830
else
831
rankButton->actionCB = StateMachine_None;
832
}
833
834
control->rowCount = rowCount;
835
}
836
837
void TimeAttackMenu_CharButton_ActionCB(void)
838
{
839
RSDK_THIS(UICharButton);
840
841
EntityMenuParam *param = MenuParam_GetParam();
842
EntityUIControl *control = TimeAttackMenu->taZoneSelControl;
843
844
TimeAttackData_Clear();
845
846
int32 characterID = self->characterID + 1;
847
param->characterID = characterID;
848
849
API.InitLeaderboards();
850
851
for (int32 i = 0; i < control->buttonCount; ++i) {
852
EntityUITAZoneModule *charButton = (EntityUITAZoneModule *)control->buttons[i];
853
charButton->characterID = characterID;
854
}
855
856
UIControl_MatchMenuTag("Time Attack Zones");
857
}
858
859
void TimeAttackMenu_TransitionToDetailsCB(void)
860
{
861
UIControl_SetInactiveMenu(TimeAttackMenu->leaderboardsControl);
862
UIControl_SetActiveMenu(TimeAttackMenu->taDetailsControl);
863
}
864
865
bool32 TimeAttackMenu_LeaderboardsBackPressCB(void)
866
{
867
UITransition_StartTransition(TimeAttackMenu_TransitionToDetailsCB, 0);
868
869
return true;
870
}
871
872
void TimeAttackMenu_YPressCB_LB(void)
873
{
874
EntityUITABanner *banner = TimeAttackMenu->leaderboardsBanner;
875
876
if (TimeAttackMenu->isUser)
877
TimeAttackMenu_SetupLeaderboards(banner->zoneID, banner->characterID, banner->actID, banner->isEncore, TimeAttackMenu->prevIsUser,
878
StateMachine_None);
879
}
880
881
void TimeAttackMenu_State_SetupLeaderboards(void)
882
{
883
RSDK_THIS(TimeAttackMenu);
884
885
String string;
886
INIT_STRING(string);
887
888
EntityUIDialog *dialog = TimeAttackMenu->connectingDlg;
889
int32 status = API.GetLeaderboardsStatus();
890
891
switch (status) {
892
case STATUS_CONTINUE:
893
Localization_GetString(&string, STR_CONNECTING);
894
UIDialog_SetupText(dialog, &string);
895
break;
896
897
default:
898
case STATUS_ERROR: {
899
if (status < STATUS_ERROR) {
900
int32 strID = status == STATUS_TIMEOUT ? STR_COMMERROR : STR_NOWIFI;
901
Localization_GetString(&string, strID);
902
903
UIDialog_SetupText(dialog, &string);
904
UIDialog_AddButton(DIALOG_OK, dialog, StateMachine_None, true);
905
906
EntityUIControl *control = dialog->parent;
907
control->rowCount = 1;
908
control->columnCount = 1;
909
control->buttonID = 0;
910
TimeAttackMenu->prevIsUser = !TimeAttackMenu->prevIsUser;
911
912
EntityUIButtonPrompt *topRankPrompt = TimeAttackMenu->topRankPrompt;
913
int32 id = -(TimeAttackMenu->prevIsUser != 0);
914
topRankPrompt->prevPrompt = -1;
915
topRankPrompt->promptID = (id + 15);
916
topRankPrompt->visible = !TimeAttackMenu->isUser;
917
918
self->callback = StateMachine_None;
919
TimeAttackMenu->connectingDlg = NULL;
920
destroyEntity(self);
921
break;
922
}
923
// otherwise, fallthrough and do this
924
}
925
926
case STATUS_OK: {
927
EntityUIButtonPrompt *topRankPrompt = TimeAttackMenu->topRankPrompt;
928
int32 id = -(TimeAttackMenu->prevIsUser != false);
929
topRankPrompt->prevPrompt = -1;
930
topRankPrompt->promptID = id + 15;
931
topRankPrompt->visible = TimeAttackMenu->isUser;
932
933
EntityUIControl *leaderboardsControl = TimeAttackMenu->leaderboardsControl;
934
TimeAttackMenu_SetupLeaderboardsCarousel(leaderboardsControl->carousel);
935
UIDialog_CloseOnSel_HandleSelection(dialog, self->callback);
936
937
self->callback = StateMachine_None;
938
TimeAttackMenu->connectingDlg = NULL;
939
destroyEntity(self);
940
break;
941
}
942
}
943
}
944
945
void TimeAttackMenu_SetupLeaderboardsCarousel(EntityUICarousel *carousel)
946
{
947
EntityUIControl *parent = (EntityUIControl *)carousel->parent;
948
949
LeaderboardAvail avail = API.LeaderboardEntryViewSize();
950
carousel->minOffset = MAX(avail.start, 1);
951
carousel->maxOffset = MAX(avail.start + avail.length, carousel->minOffset + 5);
952
953
if (TimeAttackMenu->prevIsUser && avail.length) {
954
int32 userID = 0;
955
956
int32 end = avail.start + avail.length;
957
for (int32 entryID = avail.start; entryID < end; ++entryID) {
958
LeaderboardEntry *entry = API.ReadLeaderboardEntry(entryID);
959
if (entry->isUser) {
960
userID = entryID;
961
break;
962
}
963
}
964
965
carousel->scrollOffset = userID != parent->buttonCount >> 1 ? userID - (parent->buttonCount >> 1) : 0;
966
carousel->virtualIndex = !userID ? avail.start : userID;
967
}
968
else {
969
carousel->virtualIndex = 1;
970
carousel->scrollOffset = 0;
971
}
972
973
for (int32 i = 0; i < parent->buttonCount; ++i) {
974
int32 max = (i - carousel->scrollOffset) % parent->buttonCount;
975
if (max < 0)
976
max += parent->buttonCount;
977
978
int32 vIndex = max + carousel->scrollOffset;
979
LogHelpers_Print("i = %d, vIndex = %d", i, vIndex);
980
981
if (vIndex == carousel->virtualIndex) {
982
parent->buttonID = i;
983
break;
984
}
985
}
986
987
LogHelpers_Print("avail.start = %d, length = %d", avail.start, avail.length);
988
LogHelpers_Print("scrollOffset = %d", carousel->scrollOffset);
989
LogHelpers_Print("virtualIndex = %d", carousel->virtualIndex);
990
LogHelpers_Print("minOffset = %d", carousel->minOffset);
991
LogHelpers_Print("maxOffset = %d", carousel->maxOffset);
992
993
carousel->buttonID = -1;
994
}
995
996
#if GAME_INCLUDE_EDITOR
997
void TimeAttackMenu_EditorDraw(void) {}
998
999
void TimeAttackMenu_EditorLoad(void) {}
1000
#endif
1001
1002
void TimeAttackMenu_Serialize(void) {}
1003
#endif
1004
1005