// ----------------------------------
// RSDK Project: Sonic 2
// 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 here for general reference

// 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
private alias 7 : TRACK_SUPER

// 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.xpos : player.xpos
private alias object.ypos : player.ypos
private alias object.xvel : player.xvel
private alias object.speed : player.speed
private alias object.collisionLeft : player.collisionLeft
private alias object.controlMode : player.controlMode
private alias object.right : player.right
private alias object.value18 : player.sortedDrawOrder
private alias object.visible : player.visible

// Shield Types Aliases
private alias 0 : SHIELDTYPE_S2


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

reserve function StageSetup_HandleOscillationTable


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

public value StageSetup_monitorOverlayFrame = 0
private value StageSetup_oscillateFlipFlags = 0


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

// Format - oscillatePos, oscillateInc
private table StageSetup_initOscillationTable
	128, 0
	128, 0
	128, 0
	128, 0
	128, 0
	128, 0
	128, 0
	128, 0
	128, 0
	0x3848, 238
	0x2080, 180
	0x3080, 270
	0x5080, 450
	0x7080, 630
	128, 0
	0x4000, 254
end table

// (The values in this table initially don't really matter, as long as it's the same size as StageSetup_initOscillationTable everything should be good)
public table StageSetup_oscillationTable
	128, 0
	128, 0
	128, 0
	128, 0
	128, 0
	128, 0
	128, 0
	128, 0
	128, 0
	0x3848, 238
	0x2080, 180
	0x3080, 270
	0x5080, 450
	0x7080, 630
	128, 0
	0x4000, 254
end table

// Format - oscillateSpeed, oscillateMax
private table StageSetup_oscillateLimits
	2, 0x1000
	2, 0x1800
	2, 0x2000
	2, 0x3000
	4, 0x2000
	8, 0x800
	8, 0x4000
	4, 0x4000
	2, 0x3800
	2, 0x3800
	2, 0x2000
	3, 0x3000
	5, 0x5000
	7, 0x7000
	2, 0x4000
	2, 0x4000
end table


// ========================
// Function Defintions
// ========================

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
	switch stage.state
	case STAGE_PAUSED
	case STAGE_FROZEN
		break

	default
		// Handle ring animation
		// 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

		// Handle Monitor overlay frame
		StageSetup_monitorOverlayFrame++
		if StageSetup_monitorOverlayFrame > 17
			StageSetup_monitorOverlayFrame = 0
		end if
		
		// Handle Player Score Bonus
		if options.gameMode != MODE_TIMEATTACK
			if player.score >= player.scoreBonus
#platform: USE_ORIGINS
				if game.coinMode == false
					player.lives++
				else
					CallNativeFunction2(NotifyCallback, NOTIFY_ADD_COIN, 1)
				end if
#endplatform
#platform: USE_STANDALONE
				player.lives++
#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 &= 0x1FF
		CallFunction(StageSetup_HandleOscillationTable)

		// Handle Time Overs
		if stage.timeEnabled == true
#platform: USE_ORIGINS
			if game.coinMode == false
				CheckEqual(game.timeOver, false)
				temp0 = checkResult
				CheckEqual(stage.minutes, 10)
				temp0 &= checkResult
				
				if temp0 != false
					CheckEqual(stage.debugMode, true)
					temp0 = checkResult
					CheckEqual(options.gameMode, MODE_TIMEATTACK)
					temp0 |= checkResult
					CheckEqual(game.playMode, BOOT_PLAYMODE_BOSSRUSH)
					temp0 |= checkResult
					
					if temp0 == false
#endplatform
#platform: USE_STANDALONE
				if stage.minutes == 10
					CheckEqual(stage.debugMode, true)
					temp0 = checkResult
					CheckEqual(options.gameMode, MODE_TIMEATTACK)
					temp0 |= checkResult
					
					if temp0 == false
#endplatform
						stage.timeOver = true
						currentPlayer = 0
						player[0].type = TypeName[Player Object]
						CallFunction(Player_Kill)
					end if
					
#platform: USE_ORIGINS
					if game.playMode == BOOT_PLAYMODE_CLASSIC
						// Set the clock to 9:59:59 and disable timer progression so that it stays that way
						stage.minutes 		= 9
						stage.seconds 		= 59
						stage.milliSeconds 	= 99
						stage.timeEnabled 	= false
					end if
#endplatform
#platform: USE_STANDALONE
					// Set the clock to 9:59:59 and disable timer progression so that it stays that way
					stage.minutes 		= 9
					stage.seconds 		= 59
					stage.milliSeconds 	= 99
					stage.timeEnabled 	= false
#endplatform
				end if
#platform: USE_ORIGINS
			end if
#endplatform
		end if

		// Boundaries: Left & Bottom
		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 	= 0
					player[currentPlayer].speed = 0
				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

#platform: USE_ORIGINS
		if game.forceKillPlayer == true
			game.forceKillPlayer = false
			currentPlayer = 0
			player[0].type = TypeName[Player Object]
			CallFunction(Player_Kill)
		end if
#endplatform

		// (yeah there's no "break" here, though this is the end of the switch so it doesn't matter)
	end switch

#platform: USE_STANDALONE
	// Show/hide touch controls based on whether the player's control is locked or not
	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
	SetMusicTrack("ActComplete.ogg", TRACK_ACTFINISH, false)
	SetMusicTrack("Invincibility.ogg", TRACK_INVINCIBLE, 38679)
	SetMusicTrack("Continue.ogg", TRACK_CONTINUE, false)
	SetMusicTrack("Boss.ogg", TRACK_BOSS, true)
	SetMusicTrack("GameOver.ogg", TRACK_GAMEOVER, false)
	SetMusicTrack("Drowning.ogg", TRACK_DROWNING, false)
	SetMusicTrack("Super.ogg", TRACK_SUPER, true)

	stage.timeOver = false

	if options.attractMode == false
#platform: USE_ORIGINS
		stage.pauseEnabled = true
#endplatform
	else
		// Set Object Range to a set value so that attract mode demos always looks the same
		SetObjectRange(424)
	end if

	SpeedUpMusic 	= 0
	SlowDownMusic 	= 0
	stage.musicFlag = MUSICEVENT_FLAG_NOCHANGE

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

	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)
		object[arrayPos0].type = TypeName[Blank Object]
		if credits.screen == 0
			object[SLOT_HUD].type 			= TypeName[HUD]
			object[SLOT_HUD].priority 		= PRIORITY_ACTIVE
			object[SLOT_HUD].drawOrder 		= 6
			object[SLOT_HUD].propertyValue = object[arrayPos0].propertyValue // Leftover from CD, doesn't actually do anything here
		end if
	next

	ringExtraLife 						= 100
	oscillation 						= 0
	StageSetup_oscillateFlipFlags 		= 0xBE00 // 0b1011111000000000

	// Reset the oscillation table
	temp0 = 0
	while temp0 < 32
		GetTableValue(temp1, temp0, StageSetup_initOscillationTable)
		SetTableValue(temp1, temp0, StageSetup_oscillationTable)
		temp0++
	loop
	
	// Extra shield options are for no save only
	// Note this check is actually bugged, this only checks for the last three save slots, ignoring the first one
	if options.saveSlot > 0
		options.shieldType = SHIELDTYPE_S2
	end if

	blueShieldType    = TypeName[Blue Shield]
	invincibilityType = TypeName[Invincibility]
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 white 
	// 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 aren't world-space, to be 1:1 values instead (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
