// ----------------------------------
// RSDK Project: Sonic 1
// Script Description: Stage Setup Object
// Script Author: Christian Whitehead/Simon Thomley
// Unpacked by Rubberduckycooly's script unpacker
// ----------------------------------

// ========================
// Aliases
// ========================

// Not all these aliases are actually used in this script necessarily, a good amount of them are just for general reference as well

// Game Modes
private alias 0 : MODE_NOSAVE
private alias 1 : MODE_SAVEGAME
private alias 2 : MODE_TIMEATTACK

// Tracks
private alias 0 : TRACK_STAGE
private alias 1 : TRACK_ACTFINISH
private alias 2 : TRACK_INVINCIBLE
private alias 3 : TRACK_CONTINUE
private alias 4 : TRACK_BOSS
private alias 5 : TRACK_GAMEOVER
private alias 6 : TRACK_DROWNING

// Reserved object slots
private alias 0  : SLOT_PLAYER1
private alias 1  : SLOT_PLAYER2
private alias 2  : SLOT_PLAYER1_POWERUP // will be in slot[1] if player2 isn't enabled (aka it's slot[playerCount])
private alias 3  : SLOT_PLAYER2_POWERUP
private alias 8  : SLOT_STAGESETUP
private alias 9  : SLOT_HUD
private alias 10 : SLOT_ZONESETUP
private alias 11 : SLOT_TITLECARD
private alias 25 : SLOT_MUSICEVENT_CHANGE
private alias 26 : SLOT_MUSICEVENT_BOSS
private alias 30 : SLOT_ACTFINISH

// Music Events
private alias 0 : MUSICEVENT_FADETOBOSS
private alias 1 : MUSICEVENT_FADETOSTAGE
private alias 2 : MUSICEVENT_TRANSITION

private alias 0 : MUSICEVENT_FLAG_NOCHANGE
private alias 1 : MUSICEVENT_FLAG_SPEEDUP
private alias 2 : MUSICEVENT_FLAG_SLOWDOWN

// Player aliases
private alias object.type 			: player.type
private alias object.controlMode 	: player.controlMode
private alias object.visible 		: player.visible
private alias object.left 			: player.left
private alias object.right 			: player.right
private alias object.xvel 			: player.xvel
private alias object.yvel 			: player.yvel
private alias object.speed			: player.speed
private alias object.xpos 			: player.xpos
private alias object.ypos 			: player.ypos
private alias object.collisionLeft 	: player.collisionLeft
private alias object.value18 		: player.sortedDrawOrder


// ========================
// Function Declarations
// ========================

reserve function StageSetup_HandleOscillationTable


// ========================
// Static Values
// ========================

public  value StageSetup_monitorOverlayFrame = 0 // Used by monitors to determine whether to show static or not and what frame of it to show
private value StageSetup_oscillateFlipFlags  = 0


// ========================
// Tables
// ========================

private table StageSetup_initOscillationTable
	128, 0, 128, 0, 128, 0, 128, 0, 128, 0, 128, 0, 128, 0, 128, 0
	128, 0, 0x50F0, 286, 0x2080, 180, 0x3080, 270, 0x5080, 450, 0x7080, 630, 128, 0, 128, 0
end table

public table StageSetup_oscillationTable
	128, 0, 128, 0, 128, 0, 128, 0, 128, 0, 128, 0, 128, 0, 128, 0
	128, 0, 0x50F0, 286, 0x2080, 180, 0x3080, 270, 0x5080, 450, 0x7080, 630, 128, 0, 128, 0
end table

private table StageSetup_oscillateLimits
	2, 0x1000, 2, 0x1800, 2, 0x2000, 2, 0x3000, 4, 0x2000, 8, 0x800,  8, 0x4000, 4, 0x4000
	2, 0x5000, 2, 0x5000, 2, 0x2000, 3, 0x3000, 5, 0x5000, 7, 0x7000, 2, 0x1000, 2, 0x1000
end table


// ========================
// Function Definitions
// ========================

private function StageSetup_HandleOscillationTable
	temp0 = 0
	temp1 = 0
	while temp0 < 16
		GetTableValue(temp4, temp1, StageSetup_oscillateLimits)		// temp4 = oscillateSpeed
		GetTableValue(temp6, temp1, StageSetup_oscillationTable)	// temp6 = oscillatePos
		temp1++

		GetTableValue(temp5, temp1, StageSetup_oscillateLimits)		// temp5 = oscillateMax
		GetTableValue(temp7, temp1, StageSetup_oscillationTable)	// temp7 = oscillateInc
		temp1--

		GetBit(temp2, StageSetup_oscillateFlipFlags, temp0)
		if temp2 == false
			temp7 += temp4
			temp6 += temp7
			if temp6 >= temp5
				SetBit(StageSetup_oscillateFlipFlags, temp0, true)
			end if
		else
			temp7 -= temp4
			temp6 += temp7
			if temp6 < temp5
				SetBit(StageSetup_oscillateFlipFlags, temp0, false)
			end if
		end if

		SetTableValue(temp6, temp1, StageSetup_oscillationTable)
		temp1++

		SetTableValue(temp7, temp1, StageSetup_oscillationTable)
		temp1++

		temp0++
	loop
end function


// ========================
// Events
// ========================

event ObjectUpdate
	// Only do these actions if the stage is active
	if stage.state == STAGE_RUNNING
		// Do ring animation here
		// -> Not done in the ring object itself because all rings should share the same frame
		ringTimer++
		if ringTimer == 4
			ringTimer = 0
			ringFrame++
			ringFrame &= 7
		end if

		// Update monitor static
		StageSetup_monitorOverlayFrame++
		if StageSetup_monitorOverlayFrame > 17
			StageSetup_monitorOverlayFrame = 0
		end if

		// Not for time attack
		if options.gameMode != MODE_TIMEATTACK
			if player.score >= player.scoreBonus
#platform: USE_STANDALONE
				player.lives++
#endplatform
#platform: USE_ORIGINS
				if game.coinMode == false
					player.lives++
				else
					CallNativeFunction2(NotifyCallback, NOTIFY_ADD_COIN, 1)
				end if
#endplatform
				player.scoreBonus += 50000
				PlaySfx(SfxName[Life], false)
				PauseMusic()
				ResetObjectEntity(SLOT_MUSICEVENT_CHANGE, TypeName[Music Event], MUSICEVENT_TRANSITION, 0, 0)
				object[SLOT_MUSICEVENT_CHANGE].priority = PRIORITY_ACTIVE
			end if
		end if

		// Handle Oscillations & Platform Array movements
		oscillation++
		oscillation &= 511
		CallFunction(StageSetup_HandleOscillationTable)


		// Check for Time Over
		// A bit messy with Standalone/Origins code combined, though hopefully not too bad
		if stage.timeEnabled == true
#platform: USE_STANDALONE
			if stage.minutes == 10
#endplatform
#platform: USE_ORIGINS
			if game.coinMode == false
				CheckEqual(game.timeOver, false)
				temp0 = checkResult
				CheckEqual(stage.minutes, 10)
				temp0 &= checkResult
				if temp0 != false
#endplatform
					CheckEqual(stage.debugMode, true)
					temp0 = checkResult
					CheckEqual(options.gameMode, MODE_TIMEATTACK)
					temp0 |= checkResult
#platform: USE_ORIGINS
					CheckEqual(game.playMode, BOOT_PLAYMODE_BOSSRUSH)
					temp0 |= checkResult
#endplatform
					if temp0 == false
						currentPlayer = SLOT_PLAYER1
						player[0].type = TypeName[Player Object]
						CallFunction(Player_Kill)
					end if

#platform: USE_ORIGINS
					if game.playMode == BOOT_PLAYMODE_CLASSIC
#endplatform
						// Reset time to 9:59:99 and disable time progression so that it stays that way
						stage.minutes 		= 9
						stage.seconds 		= 59
						stage.milliSeconds 	= 99
						stage.timeEnabled 	= false
#platform: USE_ORIGINS
					end if
				end if
#endplatform
			end if
		end if

		// Check for death bounds
		foreach (GROUP_PLAYERS, currentPlayer, ACTIVE_ENTITIES)
			temp0 = player[currentPlayer].collisionLeft
			temp0 <<= 16
			temp0 += player[currentPlayer].xpos
			temp1 = stage.curXBoundary1
			temp1 <<= 16

			if temp0 < temp1
				if player[currentPlayer].right == true
					player[currentPlayer].xvel  = 0x10000
					player[currentPlayer].speed = 0x10000
				else
					player[currentPlayer].xvel  = 0x00000
					player[currentPlayer].speed = 0x00000
				end if

				player[currentPlayer].xpos = temp1

				temp0 = player[currentPlayer].collisionLeft
				temp0 <<= 16

				player[currentPlayer].xpos -= temp0
			end if

			temp1 = stage.curYBoundary2
			temp1 <<= 16

			if temp1 < stage.deathBoundary
				if player[currentPlayer].ypos > stage.deathBoundary
					CallFunction(Player_Kill)
				end if
			else
				if player[currentPlayer].ypos > temp1
					CallFunction(Player_Kill)
				end if
			end if
		next
	end if

#platform: USE_STANDALONE
	// Show or hide touch controls UI based on what the player's situation is
	// Do note, options.touchControls only controls whether or not the UI shows up,
	// actual touch interaction is handled by the Player Object and its controlMode value
	if options.attractMode == false
		if player[0].controlMode > CONTROLMODE_NONE
			options.touchControls = true
		else
			options.touchControls = false
		end if
	else
		options.touchControls = false
	end if
#endplatform
	
	// Sort players and shields, player 1 should always be on top of player 2 and likewise for their shields
	currentPlayer = playerCount
	currentPlayer--
	while currentPlayer > -1
		if player[currentPlayer].visible == true
			currentPlayer += playerCount
			
			if player[currentPlayer].sortedDrawOrder == 0 // If the shield should be drawn in front of the player
				// The player should be drawn first...
				currentPlayer -= playerCount
				arrayPos0 = player[currentPlayer].sortedDrawOrder
				AddDrawListEntityRef(arrayPos0, currentPlayer)
				
				// ...and then their shield
				currentPlayer += playerCount
				AddDrawListEntityRef(arrayPos0, currentPlayer)
				currentPlayer -= playerCount
			else
				// The player's shield should be drawn first...
				currentPlayer -= playerCount
				arrayPos0 = player[currentPlayer].sortedDrawOrder
				currentPlayer += playerCount
				AddDrawListEntityRef(arrayPos0, currentPlayer)
				
				// ...and then theirself
				currentPlayer -= playerCount
				arrayPos0 = player[currentPlayer].sortedDrawOrder
				AddDrawListEntityRef(arrayPos0, currentPlayer)
			end if
		end if
		
		currentPlayer--
	loop
end event


event ObjectStartup
	// Setup the common music tracks used in every level
	SetMusicTrack("ActComplete.ogg", TRACK_ACTFINISH, false)
	SetMusicTrack("Invincibility.ogg", TRACK_INVINCIBLE, 39528)
	SetMusicTrack("Continue.ogg", TRACK_CONTINUE, false)
	SetMusicTrack("Boss.ogg", TRACK_BOSS, true)
	SetMusicTrack("GameOver.ogg", TRACK_GAMEOVER, false)
	SetMusicTrack("Drowning.ogg", TRACK_DROWNING, false)

	// Setup object range
	if options.attractMode == false
#platform: USE_ORIGINS
		stage.pauseEnabled = true
#endplatform
	else
		// Set Object Range, changes internal bounds to a set value so that demos always looks the same and don't desync
		SetObjectRange(424)
	end if

	// Reset the music effect functions
	// They should later get their proper music functions assigned by object 39 (stage-specific setup) but this doesn't hurt
	SpeedUpMusic    = 0
	SlowDownMusic   = 0
	stage.musicFlag = MUSICEVENT_FLAG_NOCHANGE

	stage.deathBoundary = stage.curYBoundary2
	stage.deathBoundary <<= 16

	// Remove all instances of this object in case one is accidentally placed in the scene
	foreach (TypeName[Stage Setup], arrayPos0, ALL_ENTITIES)
		ResetObjectEntity(arrayPos0, TypeName[Blank Object], 0, 0, 0)
	next

	object[SLOT_STAGESETUP].type = TypeName[Stage Setup]
	object[SLOT_STAGESETUP].priority = PRIORITY_ALWAYS

	foreach (TypeName[HUD], arrayPos0, ALL_ENTITIES)
		// Remove all HUD objects in case one is placed in the scene
		object[arrayPos0].type = TypeName[Blank Object]

		if credits.screen == false
			// Unless in the credits, place the HUD object in its dedicated slot
			object[SLOT_HUD].type 		= TypeName[HUD]
			object[SLOT_HUD].priority 	= PRIORITY_ACTIVE
			object[SLOT_HUD].drawOrder = 6

			// Store the current time period in the HUD object
			// ...or at least it should - in reality this is just a leftover from CD so it doesn't really do much in this game
			object[SLOT_HUD].propertyValue = object[arrayPos0].propertyValue
		end if
	next
	
	ringExtraLife 					= 100
	oscillation 					= 0
	StageSetup_oscillateFlipFlags 	= 0x3E00
	temp0 = 0
	while temp0 < 32
		GetTableValue(temp1, temp0, StageSetup_initOscillationTable)
		SetTableValue(temp1, temp0, StageSetup_oscillationTable)
		temp0++
	loop

	// Bonus options can only be used in no save mode
	// Note this is actually bugged as this only checks for the last three save slots, ignoring the first one
	if options.saveSlot > 0
		options.shieldType  = 0
		options.superStates = false
	end if

	switch options.shieldType
	case 0
	case 2
		blueShieldType    = TypeName[Blue Shield]
		invincibilityType = TypeName[Invincibility]
		break

	case 1
	case 3
		blueShieldType    = TypeName[Blue Shield 2]
		invincibilityType = TypeName[Invincibility 2]
		break
	end switch
end event


// ========================
// Editor Events
// ========================

event RSDKDraw
	DrawSprite(0)
end event


event RSDKLoad
	LoadSpriteSheet("Global/Display.gif")
	SpriteFrame(-16, -16, 32, 32, 1, 143)
	
	SetVariableAlias(ALIAS_VAR_PROPVAL, "unused")
end event


// ========================
// Editor Helpers
// ========================

// All the following is only to be used from Editor subs, using these in the game itself won't give desired results
// None of these exist in the original game, these are all custom to the decomp

reserve function EditorHelpers_DrawHitbox
reserve function EditorHelpers_DrawX


public function EditorHelpers_DrawHitbox
#platform: EDITOR
	// This function is called in order to draw an object's hitbox, as a yellow rectangle
	// editor.drawingOverlay isn't set here, that's to be determined by the object calling this function
	
	// Preconditions:
	// - object.xpos and object.ypos are the position for the hitbox to be centered around
	// - temp0 is the left hitbox size
	// - temp1 is the top hitbox size
	// - temp2 is the right hitbox size
	// - temp3 is the bottom hitbox size
	//  - Hitboxes are in screen-space instead of world-space (ie just a basic "-15" instead of "-0xF0000")
	
	// Get the base 1:1 position of the object
	temp4 = object.ixpos
	temp5 = object.iypos
	
	// Move to its top left corner
	temp4 -= temp0
	temp5 -= temp1
	
	// Get the hitbox's total width and height
	temp2 += temp0
	temp3 += temp1
	
	temp4 <<= 16
	temp5 <<= 16
	
	DrawRectOutline(temp4, temp5, temp2, temp3, 255, 255, 0, 255)
#endplatform
end function


public function EditorHelpers_DrawX
#platform: EDITOR
	// This function is called to draw a red X, just for when you wanna show something's *really* wrong
	// editor.drawingOverlay isn't set here, that's for the object calling this function to decide
	
	// Preconditions:
	// - temp0 and temp1 are the position for the X to be centered around, in world-space
	
	// Left
	temp2 = temp0
	temp2 -= 0x100000
	
	// Right
	temp3 = temp0
	temp3 += 0x100000
	
	// Top
	temp4 = temp1
	temp4 -= 0x100000
	
	// Bottom
	temp5 = temp1
	temp5 += 0x100000
	
	// TL-BR
	DrawLine(temp2, temp4, temp3, temp5, 255, 0, 0)
	
	// TR-BL
	DrawLine(temp3, temp4, temp2, temp5, 255, 0, 0)
#endplatform
end function
