Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rubberduckycooly
GitHub Repository: rubberduckycooly/Sonic-CD-2011-Script-Decompilation
Path: blob/main/Scripts/Title/Select.txt
1319 views
1
//-------------------Sonic CD Select Script-------------------//
2
//--------Scripted by Christian Whitehead 'The Taxman'--------//
3
//-------Unpacked By Rubberduckycooly's Script Unpacker-------//
4
5
// Note - this script is new to Sonic Origins, so previous versions of the game
6
// (ie Steam or mobile releases) won't use this script
7
8
// Aliases
9
10
// The current Selection, front and centre!
11
// It ranges from 0-6, see right down below
12
#alias Object.Value0 : Object.CurrentSelection
13
14
// Current Selection Aliases, to be paired with Object.CurrentSelection
15
#alias 0 : SELECTION_GAMESTART
16
#alias 1 : SELECTION_CONTINUE
17
#alias 2 : SELECTION_TIMEATTACK
18
#alias 3 : SELECTION_SETTINGS
19
#alias 4 : SELECTION_SOUNDTRACK
20
#alias 5 : SELECTION_EXTRAS
21
#alias 6 : SELECTION_EXIT
22
23
#alias 8 : SELECTION_MAX
24
25
// A value that holds the direction of change in Selection, used when swapping selections
26
#alias Object.Value1 : Object.SelectionChange
27
28
// Selection Change Aliases, to be paired with Object.SelectionChange from right up above
29
#alias 0 : SELECTIONCHANGE_LEFT
30
#alias 1 : SELECTIONCHANGE_RIGHT
31
32
// The position of the arrows, goes from 0 to 63, and then wraps around to 0
33
#alias Object.Value2 : Object.ArrowsPos
34
35
// Timer used for fading out
36
#alias Object.Value3 : Object.Timer
37
38
// A bitfield for all the selections allowed currently
39
// Normally 0b01111101, where all selections except SELECTION_CONTINUE are enabled
40
#alias Object.Value5 : Object.ActiveSelections
41
42
// The position of the currently displayed Selection's text
43
#alias Object.Value6 : Object.SelectionPos
44
45
// Not to be confused with CurrentSelection, which holds the actual Selection value,
46
// this value is used to store what Selection should be *displayed*
47
// It normally matches CurrentSelection, with the exception of switching Selections where one goes off screen and another slides on in
48
#alias Object.Value7 : Object.DisplaySelection
49
50
// States
51
52
#alias 0 : SELECT_INIT
53
#alias 1 : SELECT_MAIN
54
#alias 2 : SELECT_FADEOUT
55
56
// Each Selection has its own load State to correspond with it, to tell the game what each Selection does
57
#alias 3 : SELECT_GAMESTART
58
#alias 4 : SELECT_CONTINUE
59
#alias 5 : SELECT_TIMETTACK
60
#alias 6 : SELECT_SETTINGS
61
#alias 7 : SELECT_SOUNDTRACK
62
#alias 8 : SELECT_EXTRAS
63
#alias 9 : SELECT_EXIT
64
65
#alias 10 : SELECT_CHARSELECT
66
#alias 11 : SELECT_RECIEVERESULT
67
68
// Selection changing is split into two states
69
// - The first half, where the previous option is moving offscreen
70
// - And then the second half, where the new option is moving onto the screen
71
72
#alias 12 : SELECT_LEFT_START
73
#alias 13 : SELECT_LEFT_END
74
75
#alias 14 : SELECT_RIGHT_START
76
#alias 15 : SELECT_RIGHT_END
77
78
// Exiting states, no idea why it gets its own unique fade to be honest
79
#alias 16 : SELECT_FADEOUT_E
80
#alias 17 : SELECT_EXITGAME
81
82
// Start Aliases
83
#alias 0 : START_SETUP
84
85
// Global SFX
86
#alias 27 : SFX_G_SELECT
87
88
// Presentation Stages
89
#alias 1 : STAGE_P_MENU
90
#alias 2 : STAGE_P_TIMEATTACK
91
92
// Regular Stage
93
#alias 0 : STAGE_R_R11A
94
95
// Engine State Aliases
96
#alias 8 : ENGINE_WAIT
97
98
// game.mainMenuMode Aliases
99
#alias -1 : MAINMENUMODE_NULL // Starts with this, but gets swapped on MenuButton
100
101
// Game Mode Aliases
102
#alias 1 : MODE_SAVEGAME
103
104
// Player List Pos Aliases
105
#alias 0 : PLAYER_SONIC_A
106
107
108
sub ObjectMain
109
switch Object.State
110
case SELECT_INIT
111
Object.SelectionPos = Screen.CenterX
112
113
// Game Start is the default option
114
Object.DisplaySelection = SELECTION_GAMESTART
115
Object.CurrentSelection = SELECTION_GAMESTART
116
Object.State = SELECT_MAIN
117
118
// Reset all the active selections
119
Object.ActiveSelections = 0
120
121
SetBit(Object.ActiveSelections, SELECTION_GAMESTART, true)
122
// Nope, no SELECTION_CONTINUE!
123
SetBit(Object.ActiveSelections, SELECTION_TIMEATTACK, true)
124
SetBit(Object.ActiveSelections, SELECTION_SETTINGS, true)
125
SetBit(Object.ActiveSelections, SELECTION_SOUNDTRACK, true)
126
SetBit(Object.ActiveSelections, SELECTION_EXTRAS, true)
127
SetBit(Object.ActiveSelections, SELECTION_EXIT, true)
128
129
// We probably could fall-through here, but we don't want the player to be able to switch their Selection on the first frame
130
break
131
132
case SELECT_MAIN
133
134
// First, backup the current selection
135
TempValue0 = Object.CurrentSelection
136
137
if KeyPress[0].Left == true
138
if TempValue0 > SELECTION_GAMESTART
139
Object.SelectionChange = SELECTIONCHANGE_LEFT
140
141
// Find the closest active selection in the left direction
142
TempValue1 = false
143
while TempValue1 == false
144
if TempValue0 == SELECTION_GAMESTART
145
// Hit the minimum, just break with the Game Start Selection
146
TempValue1 = true
147
else
148
TempValue0--
149
150
// Grab the bit of the current index and see if it's allowed or not
151
GetBit(TempValue1, Object.ActiveSelections, TempValue0)
152
end if
153
loop
154
end if
155
end if
156
157
if KeyPress[0].Right == true
158
Object.SelectionChange = SELECTIONCHANGE_RIGHT
159
160
// Find the closest active selection in the right direction
161
TempValue1 = false
162
while TempValue1 == false
163
if TempValue0 >= SELECTION_MAX
164
// Hit the maximum, just break with whatever the current Selection is
165
TempValue1 = true
166
TempValue0 = Object.CurrentSelection
167
else
168
TempValue0++
169
170
// Get the bit to see if this selection's allowed or not
171
GetBit(TempValue1, Object.ActiveSelections, TempValue0)
172
end if
173
loop
174
end if
175
176
// Has the selection been updated?
177
if Object.CurrentSelection != TempValue0
178
Object.CurrentSelection = TempValue0
179
180
if Object.SelectionChange == SELECTIONCHANGE_LEFT
181
Object.State = SELECT_LEFT_START
182
else
183
Object.State = SELECT_RIGHT_START
184
end if
185
else
186
187
// Touch Controls, in my Origins?
188
CheckTouchRect(0, 0, Screen.XSize, 240)
189
190
if KeyPress[0].Start == true
191
CheckResult = 0
192
end if
193
194
if KeyPress[0].ButtonA == true
195
CheckResult = 0
196
end if
197
198
if KeyPress[0].ButtonC == true
199
CheckResult = 0
200
end if
201
202
if CheckResult > -1
203
if Object.CurrentSelection == SELECTION_GAMESTART
204
Object.State = SELECT_CHARSELECT
205
else
206
Object.State = SELECT_FADEOUT
207
208
StopMusic()
209
PlaySfx(SFX_G_SELECT, false)
210
211
HapticEffect(31, 0, 0, 0)
212
end if
213
else
214
if KeyPress[0].ButtonB == true
215
216
// Go back a step, return to the Touch Start Object
217
Object[-1].State = START_SETUP
218
219
Object.Type = TypeName[Blank Object]
220
end if
221
end if
222
223
// Update Arrow movement
224
Object.ArrowsPos++
225
Object.ArrowsPos &= 63
226
end if
227
break
228
229
case SELECT_FADEOUT
230
Object.Timer += 4
231
if Object.Timer == 384
232
Object.State = Object.CurrentSelection
233
Object.State += 3
234
end if
235
236
SetScreenFade(0, 0, 0, Object.Timer)
237
break
238
239
case SELECT_GAMESTART
240
ArrayPos1 = 0 // Create a new save, with properties of such:
241
SaveRAM[ArrayPos1] = Stage.PlayerListPos // Make the character who the player just chose
242
ArrayPos1++
243
SaveRAM[ArrayPos1] = 3 // Start with 3 lives
244
ArrayPos1++
245
SaveRAM[ArrayPos1] = 0 // Default score of 0
246
ArrayPos1++
247
SaveRAM[ArrayPos1] = STAGE_R_R11A // The game starts at Palmtree Present Act 1, of course!
248
ArrayPos1++
249
SaveRAM[ArrayPos1] = 0 // No Time Stones
250
ArrayPos1++
251
SaveRAM[ArrayPos1] = 0 // And if you have no Time Stones, then that means no Special Stages have been done yet either!
252
ArrayPos1++
253
SaveRAM[ArrayPos1] = 50000 // To get an extra life, the first benchmark will be 50000
254
ArrayPos1++
255
SaveRAM[ArrayPos1] = 0 // Since it's a new save, there haven't been any Good Futures yet too
256
257
// And now, write all those initial values
258
WriteSaveRAM()
259
260
// Now, go to the next stage where we load into the game
261
Object.State++
262
263
SetScreenFade(0, 0, 0, 255)
264
LoadVideo("Opening")
265
break
266
267
case SELECT_CONTINUE
268
ArrayPos1 = 0
269
270
Options.GameMode = MODE_SAVEGAME
271
Options.SaveSlot = 0
272
273
Stage.ActiveList = REGULAR_STAGE
274
275
Stage.PlayerListPos = SaveRAM[ArrayPos1]
276
ArrayPos1++
277
278
Player.Lives = SaveRAM[ArrayPos1]
279
ArrayPos1++
280
281
Player.Score = SaveRAM[ArrayPos1]
282
ArrayPos1++
283
284
Stage.ListPos = SaveRAM[ArrayPos1]
285
ArrayPos1++
286
287
SpecialStage.TimeStones = SaveRAM[ArrayPos1]
288
ArrayPos1++
289
290
SpecialStage.ListPos = SaveRAM[ArrayPos1]
291
ArrayPos1++
292
293
Player.ScoreBonus = SaveRAM[ArrayPos1]
294
ArrayPos1++
295
296
Good_Future_List = SaveRAM[ArrayPos1]
297
ArrayPos1++
298
299
Good_Future_List &= 0xFFFF
300
Good_Future_Count = 0
301
Good_Future = false
302
303
MetalSonic_List = Good_Future_List
304
MetalSonic_List >>= 16
305
306
LampPost.Check = 0
307
308
Stage.ListPos--
309
310
if Stage.ListPos < STAGE_R_R11A
311
// If in the negatives somehow, then go back to 0 - STAGE_R_R11A
312
Stage.ListPos = STAGE_R_R11A
313
Player.Lives = 3
314
Player.Score = 0
315
end if
316
317
// If it's beyond the normal stage length, then that means the save is
318
// in a Special Stage
319
if Stage.ListPos >= 80
320
// Let's bring that back down, to the actual Stage List Pos
321
SpecialStage.NextZone = Stage.ListPos
322
SpecialStage.NextZone -= 80
323
324
// Check down below, this doesn't really work
325
Stage.ActiveList = SPECIAL_STAGE
326
327
#platform: Use_Origins
328
// You may be thinking like "hey wait this script is already for Origins, why is this here?" and you'd be right, but
329
// Origins accidentally messed up the stage list order
330
// This makes "SPECIAL_STAGE" actually point to the Bonus Stage list instead, which isn't ideal
331
// Because of that, we have to set the Special Stage list ID based on a numeric value here instead, since the alias doesn't match
332
333
Stage.ActiveList = 3
334
335
// (also the reverse is true too;
336
// BONUS_STAGE points to the Special Stage list as well)
337
#endplatform
338
339
Stage.ListPos = SpecialStage.ListPos
340
341
// This is essentially a fancy way of checking if the Player's on the second act of a zone or not
342
TempValue0 = SpecialStage.NextZone
343
TempValue0 %= 10
344
TempValue0 >>= 2
345
346
if TempValue0 == 1
347
348
// Setup the Good Future count if needed
349
350
TempValue1 = SpecialStage.NextZone
351
TempValue1 /= 10
352
TempValue1 <<= 1
353
354
GetBit(TempValue0, Good_Future_List, TempValue1)
355
356
Good_Future_Count += TempValue0
357
358
end if
359
else
360
361
// See if the Player's on Act 2 of their level
362
TempValue0 = Stage.ListPos
363
TempValue0 %= 10
364
TempValue0 >>= 2
365
366
if TempValue0 == 1
367
368
// Setup the Good Future count as needed
369
370
TempValue1 = Stage.ListPos
371
TempValue1 /= 10
372
TempValue1 <<= 1
373
374
GetBit(TempValue0, Good_Future_List, TempValue1)
375
376
Good_Future_Count += TempValue0
377
end if
378
end if
379
380
// Select a save slot now
381
// Don't mind that all the save-related stuff happened already
382
game.callbackParam0 = 0
383
EngineCallback(NOTIFY_SAVESLOT_SELECT)
384
385
LoadStage()
386
SetScreenFade(0, 0, 0, 255)
387
break
388
389
case SELECT_TIMETTACK
390
game.mainMenuMode = MAINMENUMODE_NULL
391
392
// Unless this is explicitly set, the game will default to Touch Controls on the menu and we don't want that
393
Options.PhysicalControls = true
394
395
// Load the Time Attack Menu
396
Stage.ActiveList = PRESENTATION_STAGE
397
Stage.ListPos = STAGE_P_TIMEATTACK
398
399
LoadStage()
400
401
SetScreenFade(0, 0, 0, 255)
402
break
403
404
case SELECT_SETTINGS
405
// Load the Menu, specifically the "Settings" part
406
// -> Despite its general name, it's really only the Spindash option...
407
408
game.mainMenuMode = MAINMENUMODE_SETTINGS
409
Stage.ActiveList = PRESENTATION_STAGE
410
Stage.ListPos = STAGE_P_MENU
411
412
LoadStage()
413
414
SetScreenFade(0, 0, 0, 255)
415
break
416
417
case SELECT_SOUNDTRACK
418
// Load the Menu, but this time for the Soundtrack menu
419
game.mainMenuMode = MAINMENUMODE_SOUNDTRACK
420
Stage.ActiveList = PRESENTATION_STAGE
421
Stage.ListPos = STAGE_P_MENU
422
423
LoadStage()
424
425
SetScreenFade(0, 0, 0, 255)
426
break
427
428
case SELECT_EXTRAS
429
// Force Sonic's SaveRAM Data to load
430
// (In Origins, each character has their own dedicated SaveRAM data, but we want to make sure Extras unlocks are applied regardless of character)
431
game.callbackParam0 = PLAYER_SONIC_A
432
EngineCallback(NOTIFY_PLAYER_SET)
433
434
// Yup it's your favourite Menu again, but now the Extras tab!
435
game.mainMenuMode = MAINMENUMODE_EXTRAS
436
Stage.ActiveList = PRESENTATION_STAGE
437
Stage.ListPos = STAGE_P_MENU
438
439
LoadStage()
440
441
SetScreenFade(0, 0, 0, 255)
442
break
443
444
case SELECT_EXIT
445
// Not much here...
446
Engine.State = ENGINE_WAIT
447
448
SetScreenFade(0, 0, 0, 255)
449
break
450
451
case SELECT_CHARSELECT
452
// Prompt the HE2 side of things to bring up the character menu
453
game.continueFlag = false
454
game.callbackResult = -1 // Reset Callback result
455
game.callbackParam0 = 0 // 0 - Character Select, anything else defaults to load the current player
456
EngineCallback(NOTIFY_CHARACTER_SELECT)
457
458
Object.State = SELECT_RECIEVERESULT
459
break
460
461
case SELECT_RECIEVERESULT
462
if game.callbackResult > 0
463
// Based on the result, either exit or actually follow the selected option
464
if game.continueFlag != false
465
// There used to be a Object.CurrentSelection = SELECTION_CONTINUE line here, but it was removed in Origins Plus
466
// Not sure what this really fixed?
467
Object.State = SELECT_FADEOUT_E
468
else
469
ReadSaveRAM()
470
Object.CurrentSelection = SELECTION_GAMESTART
471
Object.State = SELECT_FADEOUT
472
end if
473
474
StopMusic()
475
476
// ...what? a Haptic effect in new Origins code?
477
// isn't that strange...
478
HapticEffect(31, 0, 0, 0)
479
else
480
if game.callbackResult == 0
481
// Go back to the Selection phase
482
483
Object.State = SELECT_MAIN
484
end if
485
end if
486
break
487
488
case SELECT_LEFT_START
489
// Slide the current selection off to the right
490
Object.SelectionPos += 30
491
492
if Object.SelectionPos >= Screen.XSize
493
// If it's beyond screen bounds, then move to the other half of this state
494
495
// Make the next text start at the left of the screen
496
Object.SelectionPos = 0
497
498
// Shift the shown selection to be what the actual new selection is now
499
Object.DisplaySelection = Object.CurrentSelection
500
501
Object.State = SELECT_LEFT_END
502
end if
503
break
504
505
case SELECT_LEFT_END
506
// Slide the text in from the left 'till it's centre screen
507
Object.SelectionPos += 30
508
509
if Object.SelectionPos >= Screen.CenterX
510
// Just to make sure that it's in the middle
511
Object.SelectionPos = Screen.CenterX
512
513
// And now, go back to the Main Selection phase
514
Object.State = SELECT_MAIN
515
Object.ArrowsPos = 0
516
end if
517
break
518
519
case SELECT_RIGHT_START
520
// Move the current select off to the left
521
Object.SelectionPos -= 30
522
523
if Object.SelectionPos <= 0
524
// Now that the selection isn't visible anymore, transition to the latter half of this state
525
526
// Make the new Selection start at the right edge of the screen
527
Object.SelectionPos = Screen.XSize
528
529
// And make the shown selection match the true current Selection
530
Object.DisplaySelection = Object.CurrentSelection
531
532
Object.State = SELECT_RIGHT_END
533
end if
534
break
535
536
case SELECT_RIGHT_END
537
// Move the new selection from the right side of the screen to reach the middle
538
Object.SelectionPos -= 30
539
540
if Object.SelectionPos <= Screen.CenterX
541
// Just to make sure it's where it should be
542
Object.SelectionPos = Screen.CenterX
543
544
// Now, back to the Main selection state!
545
Object.State = SELECT_MAIN
546
Object.ArrowsPos = 0
547
end if
548
break
549
550
case SELECT_FADEOUT_E
551
// This state is for fading out, exclusively for Exiting
552
// I have no idea why it has its own unique state, to be honest
553
554
Object.Timer += 4
555
if Object.Timer >= 384
556
Object.State++
557
end if
558
559
SetScreenFade(0, 0, 0, Object.Timer)
560
break
561
562
case SELECT_EXITGAME
563
// And now, just exit the game
564
565
Engine.State = RESET_GAME
566
SetScreenFade(0, 0, 0, 255)
567
break
568
end switch
569
end sub
570
571
572
sub ObjectDraw
573
// Base Frame ID
574
TempValue0 = Object.DisplaySelection
575
576
if Object.SelectionPos == Screen.CenterX
577
// If the Selection is in the middle of the screen, then bump up the
578
// frame ID to use the yellow, selected frame
579
TempValue0 += 7
580
end if
581
582
// Huh, why are they assigned these values to temps when they could just be used directly?
583
TempValue1 = Object.SelectionPos
584
TempValue2 = 198
585
586
// And even stranger, why exactly is this drawing with FX_INK?
587
// This Object doesn't use any effects like that at all...
588
DrawSpriteScreenFX(TempValue0, FX_INK, TempValue1, TempValue2)
589
590
// If the Selection is in the middle of the screen, then draw arrows around it too
591
if Object.SelectionPos == Screen.CenterX
592
593
// First, do the left arrow
594
if Object.CurrentSelection != SELECTION_GAMESTART
595
596
// First, get the base offset for the Arrow
597
TempValue3 = Object.ArrowsPos
598
TempValue3 &= 48
599
TempValue3 >>= 3
600
601
// Now, apply that offset to the middle of the screen
602
TempValue4 = Screen.CenterX
603
TempValue4 -= TempValue3
604
605
// Similarly to before, there's not much of a reason to use FX_INK here...
606
DrawSpriteScreenFX(16, FX_INK, TempValue4, TempValue2)
607
end if
608
609
// Now, the right arrow!
610
if Object.CurrentSelection < SELECTION_EXIT
611
612
// Get the base Arrow offset
613
TempValue3 = Object.ArrowsPos
614
TempValue3 &= 48
615
TempValue3 >>= 3
616
617
// And apply that in relation to centre screen
618
TempValue4 = Screen.CenterX
619
TempValue4 += TempValue3
620
621
// And again, another use of FX_INK for no reason at all...
622
DrawSpriteScreenFX(17, FX_INK, TempValue4, TempValue2)
623
end if
624
end if
625
end sub
626
627
628
sub ObjectStartup
629
// Do note, this sprite is new to Origins so it doesn't exist in previous Steam or mobile data files
630
LoadSpriteSheet("Title/Select.gif")
631
632
// 0-6 - Select Frames
633
634
// White, unselected Frames
635
636
// Game Start
637
SpriteFrame(-39, 2, 128, 16, 1, 171)
638
639
// Continue
640
SpriteFrame(-31, 2, 128, 16, 1, 18)
641
642
// Time Attack
643
SpriteFrame(-42, 2, 128, 16, 1, 35)
644
645
// Settings
646
SpriteFrame(-31, 2, 128, 16, 1, 154)
647
648
// Sound track (why are they two separate words...)
649
SpriteFrame(-43, 2, 128, 16, 1, 86)
650
651
// Extras
652
SpriteFrame(-24, 2, 128, 16, 1, 137)
653
654
// Exit
655
SpriteFrame(-15, 2, 128, 16, 1, 103)
656
657
// 7-13 - Yellow, selected frames
658
659
// Game Start
660
SpriteFrame(-39, 2, 128, 16, 130, 171)
661
662
// Continue
663
SpriteFrame(-31, 2, 128, 16, 130, 18)
664
665
// Time Attack
666
SpriteFrame(-42, 2, 128, 16, 130, 35)
667
668
// Settings
669
SpriteFrame(-31, 2, 128, 16, 130, 154)
670
671
// Sound track (is there supposed to be a space?)
672
SpriteFrame(-43, 2, 128, 16, 130, 86)
673
674
// Extras
675
SpriteFrame(-24, 2, 128, 16, 130, 137)
676
677
// Exit
678
SpriteFrame(-15, 2, 128, 16, 130, 103)
679
680
// 14-15 - White Arrows
681
// These are unused
682
683
// Left Arrow
684
SpriteFrame(-66, 0, 9, 16, 259, 1)
685
686
// Right Arrow
687
SpriteFrame( 56, 0, 9, 16, 269, 1)
688
689
// 16-17 - Yellow Arrows
690
691
// Left Arrow
692
SpriteFrame(-66, 0, 9, 16, 279, 1)
693
694
// Right Arrow
695
SpriteFrame( 56, 0, 9, 16, 289, 1)
696
697
// Yup, all the remaining sprites on the sheet are unused!
698
// That makes a nearly half unused sheet!
699
end sub
700
701
702
// ========================
703
// Editor Subs
704
// ========================
705
706
sub RSDKDraw
707
DrawSprite(0)
708
DrawSprite(1)
709
DrawSprite(2)
710
end sub
711
712
713
sub RSDKLoad
714
LoadSpriteSheet("Title/Select.gif")
715
SpriteFrame(-39, 2, 128, 16, 1, 171)
716
SpriteFrame(-66, 0, 9, 16, 259, 1)
717
SpriteFrame( 56, 0, 9, 16, 269, 1)
718
719
SetVariableAlias(ALIAS_VAR_PROPVAL, "unused")
720
end sub
721
722