Path: blob/main/Scripts/Title/Select.txt
1319 views
//-------------------Sonic CD Select Script-------------------//1//--------Scripted by Christian Whitehead 'The Taxman'--------//2//-------Unpacked By Rubberduckycooly's Script Unpacker-------//34// Note - this script is new to Sonic Origins, so previous versions of the game5// (ie Steam or mobile releases) won't use this script67// Aliases89// The current Selection, front and centre!10// It ranges from 0-6, see right down below11#alias Object.Value0 : Object.CurrentSelection1213// Current Selection Aliases, to be paired with Object.CurrentSelection14#alias 0 : SELECTION_GAMESTART15#alias 1 : SELECTION_CONTINUE16#alias 2 : SELECTION_TIMEATTACK17#alias 3 : SELECTION_SETTINGS18#alias 4 : SELECTION_SOUNDTRACK19#alias 5 : SELECTION_EXTRAS20#alias 6 : SELECTION_EXIT2122#alias 8 : SELECTION_MAX2324// A value that holds the direction of change in Selection, used when swapping selections25#alias Object.Value1 : Object.SelectionChange2627// Selection Change Aliases, to be paired with Object.SelectionChange from right up above28#alias 0 : SELECTIONCHANGE_LEFT29#alias 1 : SELECTIONCHANGE_RIGHT3031// The position of the arrows, goes from 0 to 63, and then wraps around to 032#alias Object.Value2 : Object.ArrowsPos3334// Timer used for fading out35#alias Object.Value3 : Object.Timer3637// A bitfield for all the selections allowed currently38// Normally 0b01111101, where all selections except SELECTION_CONTINUE are enabled39#alias Object.Value5 : Object.ActiveSelections4041// The position of the currently displayed Selection's text42#alias Object.Value6 : Object.SelectionPos4344// Not to be confused with CurrentSelection, which holds the actual Selection value,45// this value is used to store what Selection should be *displayed*46// It normally matches CurrentSelection, with the exception of switching Selections where one goes off screen and another slides on in47#alias Object.Value7 : Object.DisplaySelection4849// States5051#alias 0 : SELECT_INIT52#alias 1 : SELECT_MAIN53#alias 2 : SELECT_FADEOUT5455// Each Selection has its own load State to correspond with it, to tell the game what each Selection does56#alias 3 : SELECT_GAMESTART57#alias 4 : SELECT_CONTINUE58#alias 5 : SELECT_TIMETTACK59#alias 6 : SELECT_SETTINGS60#alias 7 : SELECT_SOUNDTRACK61#alias 8 : SELECT_EXTRAS62#alias 9 : SELECT_EXIT6364#alias 10 : SELECT_CHARSELECT65#alias 11 : SELECT_RECIEVERESULT6667// Selection changing is split into two states68// - The first half, where the previous option is moving offscreen69// - And then the second half, where the new option is moving onto the screen7071#alias 12 : SELECT_LEFT_START72#alias 13 : SELECT_LEFT_END7374#alias 14 : SELECT_RIGHT_START75#alias 15 : SELECT_RIGHT_END7677// Exiting states, no idea why it gets its own unique fade to be honest78#alias 16 : SELECT_FADEOUT_E79#alias 17 : SELECT_EXITGAME8081// Start Aliases82#alias 0 : START_SETUP8384// Global SFX85#alias 27 : SFX_G_SELECT8687// Presentation Stages88#alias 1 : STAGE_P_MENU89#alias 2 : STAGE_P_TIMEATTACK9091// Regular Stage92#alias 0 : STAGE_R_R11A9394// Engine State Aliases95#alias 8 : ENGINE_WAIT9697// game.mainMenuMode Aliases98#alias -1 : MAINMENUMODE_NULL // Starts with this, but gets swapped on MenuButton99100// Game Mode Aliases101#alias 1 : MODE_SAVEGAME102103// Player List Pos Aliases104#alias 0 : PLAYER_SONIC_A105106107sub ObjectMain108switch Object.State109case SELECT_INIT110Object.SelectionPos = Screen.CenterX111112// Game Start is the default option113Object.DisplaySelection = SELECTION_GAMESTART114Object.CurrentSelection = SELECTION_GAMESTART115Object.State = SELECT_MAIN116117// Reset all the active selections118Object.ActiveSelections = 0119120SetBit(Object.ActiveSelections, SELECTION_GAMESTART, true)121// Nope, no SELECTION_CONTINUE!122SetBit(Object.ActiveSelections, SELECTION_TIMEATTACK, true)123SetBit(Object.ActiveSelections, SELECTION_SETTINGS, true)124SetBit(Object.ActiveSelections, SELECTION_SOUNDTRACK, true)125SetBit(Object.ActiveSelections, SELECTION_EXTRAS, true)126SetBit(Object.ActiveSelections, SELECTION_EXIT, true)127128// We probably could fall-through here, but we don't want the player to be able to switch their Selection on the first frame129break130131case SELECT_MAIN132133// First, backup the current selection134TempValue0 = Object.CurrentSelection135136if KeyPress[0].Left == true137if TempValue0 > SELECTION_GAMESTART138Object.SelectionChange = SELECTIONCHANGE_LEFT139140// Find the closest active selection in the left direction141TempValue1 = false142while TempValue1 == false143if TempValue0 == SELECTION_GAMESTART144// Hit the minimum, just break with the Game Start Selection145TempValue1 = true146else147TempValue0--148149// Grab the bit of the current index and see if it's allowed or not150GetBit(TempValue1, Object.ActiveSelections, TempValue0)151end if152loop153end if154end if155156if KeyPress[0].Right == true157Object.SelectionChange = SELECTIONCHANGE_RIGHT158159// Find the closest active selection in the right direction160TempValue1 = false161while TempValue1 == false162if TempValue0 >= SELECTION_MAX163// Hit the maximum, just break with whatever the current Selection is164TempValue1 = true165TempValue0 = Object.CurrentSelection166else167TempValue0++168169// Get the bit to see if this selection's allowed or not170GetBit(TempValue1, Object.ActiveSelections, TempValue0)171end if172loop173end if174175// Has the selection been updated?176if Object.CurrentSelection != TempValue0177Object.CurrentSelection = TempValue0178179if Object.SelectionChange == SELECTIONCHANGE_LEFT180Object.State = SELECT_LEFT_START181else182Object.State = SELECT_RIGHT_START183end if184else185186// Touch Controls, in my Origins?187CheckTouchRect(0, 0, Screen.XSize, 240)188189if KeyPress[0].Start == true190CheckResult = 0191end if192193if KeyPress[0].ButtonA == true194CheckResult = 0195end if196197if KeyPress[0].ButtonC == true198CheckResult = 0199end if200201if CheckResult > -1202if Object.CurrentSelection == SELECTION_GAMESTART203Object.State = SELECT_CHARSELECT204else205Object.State = SELECT_FADEOUT206207StopMusic()208PlaySfx(SFX_G_SELECT, false)209210HapticEffect(31, 0, 0, 0)211end if212else213if KeyPress[0].ButtonB == true214215// Go back a step, return to the Touch Start Object216Object[-1].State = START_SETUP217218Object.Type = TypeName[Blank Object]219end if220end if221222// Update Arrow movement223Object.ArrowsPos++224Object.ArrowsPos &= 63225end if226break227228case SELECT_FADEOUT229Object.Timer += 4230if Object.Timer == 384231Object.State = Object.CurrentSelection232Object.State += 3233end if234235SetScreenFade(0, 0, 0, Object.Timer)236break237238case SELECT_GAMESTART239ArrayPos1 = 0 // Create a new save, with properties of such:240SaveRAM[ArrayPos1] = Stage.PlayerListPos // Make the character who the player just chose241ArrayPos1++242SaveRAM[ArrayPos1] = 3 // Start with 3 lives243ArrayPos1++244SaveRAM[ArrayPos1] = 0 // Default score of 0245ArrayPos1++246SaveRAM[ArrayPos1] = STAGE_R_R11A // The game starts at Palmtree Present Act 1, of course!247ArrayPos1++248SaveRAM[ArrayPos1] = 0 // No Time Stones249ArrayPos1++250SaveRAM[ArrayPos1] = 0 // And if you have no Time Stones, then that means no Special Stages have been done yet either!251ArrayPos1++252SaveRAM[ArrayPos1] = 50000 // To get an extra life, the first benchmark will be 50000253ArrayPos1++254SaveRAM[ArrayPos1] = 0 // Since it's a new save, there haven't been any Good Futures yet too255256// And now, write all those initial values257WriteSaveRAM()258259// Now, go to the next stage where we load into the game260Object.State++261262SetScreenFade(0, 0, 0, 255)263LoadVideo("Opening")264break265266case SELECT_CONTINUE267ArrayPos1 = 0268269Options.GameMode = MODE_SAVEGAME270Options.SaveSlot = 0271272Stage.ActiveList = REGULAR_STAGE273274Stage.PlayerListPos = SaveRAM[ArrayPos1]275ArrayPos1++276277Player.Lives = SaveRAM[ArrayPos1]278ArrayPos1++279280Player.Score = SaveRAM[ArrayPos1]281ArrayPos1++282283Stage.ListPos = SaveRAM[ArrayPos1]284ArrayPos1++285286SpecialStage.TimeStones = SaveRAM[ArrayPos1]287ArrayPos1++288289SpecialStage.ListPos = SaveRAM[ArrayPos1]290ArrayPos1++291292Player.ScoreBonus = SaveRAM[ArrayPos1]293ArrayPos1++294295Good_Future_List = SaveRAM[ArrayPos1]296ArrayPos1++297298Good_Future_List &= 0xFFFF299Good_Future_Count = 0300Good_Future = false301302MetalSonic_List = Good_Future_List303MetalSonic_List >>= 16304305LampPost.Check = 0306307Stage.ListPos--308309if Stage.ListPos < STAGE_R_R11A310// If in the negatives somehow, then go back to 0 - STAGE_R_R11A311Stage.ListPos = STAGE_R_R11A312Player.Lives = 3313Player.Score = 0314end if315316// If it's beyond the normal stage length, then that means the save is317// in a Special Stage318if Stage.ListPos >= 80319// Let's bring that back down, to the actual Stage List Pos320SpecialStage.NextZone = Stage.ListPos321SpecialStage.NextZone -= 80322323// Check down below, this doesn't really work324Stage.ActiveList = SPECIAL_STAGE325326#platform: Use_Origins327// You may be thinking like "hey wait this script is already for Origins, why is this here?" and you'd be right, but328// Origins accidentally messed up the stage list order329// This makes "SPECIAL_STAGE" actually point to the Bonus Stage list instead, which isn't ideal330// Because of that, we have to set the Special Stage list ID based on a numeric value here instead, since the alias doesn't match331332Stage.ActiveList = 3333334// (also the reverse is true too;335// BONUS_STAGE points to the Special Stage list as well)336#endplatform337338Stage.ListPos = SpecialStage.ListPos339340// This is essentially a fancy way of checking if the Player's on the second act of a zone or not341TempValue0 = SpecialStage.NextZone342TempValue0 %= 10343TempValue0 >>= 2344345if TempValue0 == 1346347// Setup the Good Future count if needed348349TempValue1 = SpecialStage.NextZone350TempValue1 /= 10351TempValue1 <<= 1352353GetBit(TempValue0, Good_Future_List, TempValue1)354355Good_Future_Count += TempValue0356357end if358else359360// See if the Player's on Act 2 of their level361TempValue0 = Stage.ListPos362TempValue0 %= 10363TempValue0 >>= 2364365if TempValue0 == 1366367// Setup the Good Future count as needed368369TempValue1 = Stage.ListPos370TempValue1 /= 10371TempValue1 <<= 1372373GetBit(TempValue0, Good_Future_List, TempValue1)374375Good_Future_Count += TempValue0376end if377end if378379// Select a save slot now380// Don't mind that all the save-related stuff happened already381game.callbackParam0 = 0382EngineCallback(NOTIFY_SAVESLOT_SELECT)383384LoadStage()385SetScreenFade(0, 0, 0, 255)386break387388case SELECT_TIMETTACK389game.mainMenuMode = MAINMENUMODE_NULL390391// Unless this is explicitly set, the game will default to Touch Controls on the menu and we don't want that392Options.PhysicalControls = true393394// Load the Time Attack Menu395Stage.ActiveList = PRESENTATION_STAGE396Stage.ListPos = STAGE_P_TIMEATTACK397398LoadStage()399400SetScreenFade(0, 0, 0, 255)401break402403case SELECT_SETTINGS404// Load the Menu, specifically the "Settings" part405// -> Despite its general name, it's really only the Spindash option...406407game.mainMenuMode = MAINMENUMODE_SETTINGS408Stage.ActiveList = PRESENTATION_STAGE409Stage.ListPos = STAGE_P_MENU410411LoadStage()412413SetScreenFade(0, 0, 0, 255)414break415416case SELECT_SOUNDTRACK417// Load the Menu, but this time for the Soundtrack menu418game.mainMenuMode = MAINMENUMODE_SOUNDTRACK419Stage.ActiveList = PRESENTATION_STAGE420Stage.ListPos = STAGE_P_MENU421422LoadStage()423424SetScreenFade(0, 0, 0, 255)425break426427case SELECT_EXTRAS428// Force Sonic's SaveRAM Data to load429// (In Origins, each character has their own dedicated SaveRAM data, but we want to make sure Extras unlocks are applied regardless of character)430game.callbackParam0 = PLAYER_SONIC_A431EngineCallback(NOTIFY_PLAYER_SET)432433// Yup it's your favourite Menu again, but now the Extras tab!434game.mainMenuMode = MAINMENUMODE_EXTRAS435Stage.ActiveList = PRESENTATION_STAGE436Stage.ListPos = STAGE_P_MENU437438LoadStage()439440SetScreenFade(0, 0, 0, 255)441break442443case SELECT_EXIT444// Not much here...445Engine.State = ENGINE_WAIT446447SetScreenFade(0, 0, 0, 255)448break449450case SELECT_CHARSELECT451// Prompt the HE2 side of things to bring up the character menu452game.continueFlag = false453game.callbackResult = -1 // Reset Callback result454game.callbackParam0 = 0 // 0 - Character Select, anything else defaults to load the current player455EngineCallback(NOTIFY_CHARACTER_SELECT)456457Object.State = SELECT_RECIEVERESULT458break459460case SELECT_RECIEVERESULT461if game.callbackResult > 0462// Based on the result, either exit or actually follow the selected option463if game.continueFlag != false464// There used to be a Object.CurrentSelection = SELECTION_CONTINUE line here, but it was removed in Origins Plus465// Not sure what this really fixed?466Object.State = SELECT_FADEOUT_E467else468ReadSaveRAM()469Object.CurrentSelection = SELECTION_GAMESTART470Object.State = SELECT_FADEOUT471end if472473StopMusic()474475// ...what? a Haptic effect in new Origins code?476// isn't that strange...477HapticEffect(31, 0, 0, 0)478else479if game.callbackResult == 0480// Go back to the Selection phase481482Object.State = SELECT_MAIN483end if484end if485break486487case SELECT_LEFT_START488// Slide the current selection off to the right489Object.SelectionPos += 30490491if Object.SelectionPos >= Screen.XSize492// If it's beyond screen bounds, then move to the other half of this state493494// Make the next text start at the left of the screen495Object.SelectionPos = 0496497// Shift the shown selection to be what the actual new selection is now498Object.DisplaySelection = Object.CurrentSelection499500Object.State = SELECT_LEFT_END501end if502break503504case SELECT_LEFT_END505// Slide the text in from the left 'till it's centre screen506Object.SelectionPos += 30507508if Object.SelectionPos >= Screen.CenterX509// Just to make sure that it's in the middle510Object.SelectionPos = Screen.CenterX511512// And now, go back to the Main Selection phase513Object.State = SELECT_MAIN514Object.ArrowsPos = 0515end if516break517518case SELECT_RIGHT_START519// Move the current select off to the left520Object.SelectionPos -= 30521522if Object.SelectionPos <= 0523// Now that the selection isn't visible anymore, transition to the latter half of this state524525// Make the new Selection start at the right edge of the screen526Object.SelectionPos = Screen.XSize527528// And make the shown selection match the true current Selection529Object.DisplaySelection = Object.CurrentSelection530531Object.State = SELECT_RIGHT_END532end if533break534535case SELECT_RIGHT_END536// Move the new selection from the right side of the screen to reach the middle537Object.SelectionPos -= 30538539if Object.SelectionPos <= Screen.CenterX540// Just to make sure it's where it should be541Object.SelectionPos = Screen.CenterX542543// Now, back to the Main selection state!544Object.State = SELECT_MAIN545Object.ArrowsPos = 0546end if547break548549case SELECT_FADEOUT_E550// This state is for fading out, exclusively for Exiting551// I have no idea why it has its own unique state, to be honest552553Object.Timer += 4554if Object.Timer >= 384555Object.State++556end if557558SetScreenFade(0, 0, 0, Object.Timer)559break560561case SELECT_EXITGAME562// And now, just exit the game563564Engine.State = RESET_GAME565SetScreenFade(0, 0, 0, 255)566break567end switch568end sub569570571sub ObjectDraw572// Base Frame ID573TempValue0 = Object.DisplaySelection574575if Object.SelectionPos == Screen.CenterX576// If the Selection is in the middle of the screen, then bump up the577// frame ID to use the yellow, selected frame578TempValue0 += 7579end if580581// Huh, why are they assigned these values to temps when they could just be used directly?582TempValue1 = Object.SelectionPos583TempValue2 = 198584585// And even stranger, why exactly is this drawing with FX_INK?586// This Object doesn't use any effects like that at all...587DrawSpriteScreenFX(TempValue0, FX_INK, TempValue1, TempValue2)588589// If the Selection is in the middle of the screen, then draw arrows around it too590if Object.SelectionPos == Screen.CenterX591592// First, do the left arrow593if Object.CurrentSelection != SELECTION_GAMESTART594595// First, get the base offset for the Arrow596TempValue3 = Object.ArrowsPos597TempValue3 &= 48598TempValue3 >>= 3599600// Now, apply that offset to the middle of the screen601TempValue4 = Screen.CenterX602TempValue4 -= TempValue3603604// Similarly to before, there's not much of a reason to use FX_INK here...605DrawSpriteScreenFX(16, FX_INK, TempValue4, TempValue2)606end if607608// Now, the right arrow!609if Object.CurrentSelection < SELECTION_EXIT610611// Get the base Arrow offset612TempValue3 = Object.ArrowsPos613TempValue3 &= 48614TempValue3 >>= 3615616// And apply that in relation to centre screen617TempValue4 = Screen.CenterX618TempValue4 += TempValue3619620// And again, another use of FX_INK for no reason at all...621DrawSpriteScreenFX(17, FX_INK, TempValue4, TempValue2)622end if623end if624end sub625626627sub ObjectStartup628// Do note, this sprite is new to Origins so it doesn't exist in previous Steam or mobile data files629LoadSpriteSheet("Title/Select.gif")630631// 0-6 - Select Frames632633// White, unselected Frames634635// Game Start636SpriteFrame(-39, 2, 128, 16, 1, 171)637638// Continue639SpriteFrame(-31, 2, 128, 16, 1, 18)640641// Time Attack642SpriteFrame(-42, 2, 128, 16, 1, 35)643644// Settings645SpriteFrame(-31, 2, 128, 16, 1, 154)646647// Sound track (why are they two separate words...)648SpriteFrame(-43, 2, 128, 16, 1, 86)649650// Extras651SpriteFrame(-24, 2, 128, 16, 1, 137)652653// Exit654SpriteFrame(-15, 2, 128, 16, 1, 103)655656// 7-13 - Yellow, selected frames657658// Game Start659SpriteFrame(-39, 2, 128, 16, 130, 171)660661// Continue662SpriteFrame(-31, 2, 128, 16, 130, 18)663664// Time Attack665SpriteFrame(-42, 2, 128, 16, 130, 35)666667// Settings668SpriteFrame(-31, 2, 128, 16, 130, 154)669670// Sound track (is there supposed to be a space?)671SpriteFrame(-43, 2, 128, 16, 130, 86)672673// Extras674SpriteFrame(-24, 2, 128, 16, 130, 137)675676// Exit677SpriteFrame(-15, 2, 128, 16, 130, 103)678679// 14-15 - White Arrows680// These are unused681682// Left Arrow683SpriteFrame(-66, 0, 9, 16, 259, 1)684685// Right Arrow686SpriteFrame( 56, 0, 9, 16, 269, 1)687688// 16-17 - Yellow Arrows689690// Left Arrow691SpriteFrame(-66, 0, 9, 16, 279, 1)692693// Right Arrow694SpriteFrame( 56, 0, 9, 16, 289, 1)695696// Yup, all the remaining sprites on the sheet are unused!697// That makes a nearly half unused sheet!698end sub699700701// ========================702// Editor Subs703// ========================704705sub RSDKDraw706DrawSprite(0)707DrawSprite(1)708DrawSprite(2)709end sub710711712sub RSDKLoad713LoadSpriteSheet("Title/Select.gif")714SpriteFrame(-39, 2, 128, 16, 1, 171)715SpriteFrame(-66, 0, 9, 16, 259, 1)716SpriteFrame( 56, 0, 9, 16, 269, 1)717718SetVariableAlias(ALIAS_VAR_PROPVAL, "unused")719end sub720721722