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

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

private alias object.value0  : object.timer
private alias object.value1  : object.duration
private alias object.value2  : object.stood
private alias object.value3  : object.lastPos.y
private alias object.value4  : object.collisionOffset.y
private alias object.value5  : object.pilotFrame
private alias object.value6  : object.pilotFrameDelay
private alias object.value7  : object.pilotAnimPos
private alias object.value8  : object.velocityTablePos
private alias object.value9  : object.boosterActive
private alias object.value10 : object.jumpAbilityFunc

// States
private alias 0  : TORNADO_SETUPINTRO
private alias 1  : TORNADO_FLYINTRO
private alias 2  : TORNADO_SHOTDOWN
private alias 3  : TORNADO_FALLING
private alias 4  : TORNADO_SETUPOUTRO
private alias 5  : TORNADO_WAITFORPLAYER
private alias 6  : TORNADO_MOVEPLAYERTOEDGE
private alias 7  : TORNADO_JUMPONTOTORNADO
private alias 8  : TORNADO_LANDONTORNADO
private alias 9  : TORNADO_CHASESEQUENCE
private alias 10 : TORNADO_FINISHACT

// Player Aliases
private alias object.type    : player.type
private alias object.state   : player.state
private alias object.xpos    : player.xpos
private alias object.ypos    : player.ypos
private alias object.xvel    : player.xvel
private alias object.yvel    : player.yvel
private alias object.speed   : player.speed
private alias object.gravity : player.gravity
private alias object.controlMode : player.controlMode
private alias object.scrollTracking : player.scrollTracking

private alias object.jumpPress	:	player.jumpPress
private alias object.jumpHold	:	player.jumpHold
private alias object.up			:	player.up
private alias object.down		:	player.down
private alias object.left		:	player.left
private alias object.right		:	player.right

private alias object.value0  : player.rings
private alias object.value1  : player.timer
private alias object.value9  : player.skidSpeed
private alias object.value20 : player.topSpeed 			
private alias object.value21 : player.acceleration
private alias object.value22 : player.deceleration
private alias object.value23 : player.airAcceleration
private alias object.value24 : player.airDeceleration
private alias object.value25 : player.gravityStrength 		// Also used in underwater checks, 0x1000 if underwater, otherwise 0x3800. Not to be confused with player.gravity
private alias object.value27 : player.jumpStrength
private alias object.value28 : player.jumpCap
private alias object.value29 : player.rollingFriction		// Active rolling deceleration - With the player holding the opposite direction
private alias object.value32 : player.jumpAbility			// Used to store whatever function this player has for its jump ability
private alias object.value35 : player.jumpAbilityState

// WFZEggmanThruster Aliases
private alias object.value0 : wfzEggmanThruster.interval

// Super States
private alias 2 : SUPERSTATE_FADEOUT

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

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

reserve function Tornado_Explode


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

private table Tornado_TailsPilotFrameTable
	5, 32
	6, 8
	7, 8
	8, 8
	7, 8
	6, 16
	9, 32
	6, 16
end table

private table Tornado_SonicPilotFrameTable
	10, 9
	11, 9
	10, 9
	11, 9
	10, 9
	11, 9
	10, 9
	11, 9
end table

private table Tornado_velocityDurationTable
	520
	640
	712
	744
	808
	968
	1048
	1160
	1224
	1448
	65535
end table

private table Tornado_moveVelocityTable
	-0x10000, -0x10000
	 0x10000,  0x00000
	 0x00000,  0x10000
	 0x10000, -0x10000
	 0x10000,  0x10000
	 0x10000, -0x10000
	-0x10000,  0x10000
	-0x10000, -0x10000
	-0x10000,  0x10000
	-0x20000,  0x00000
	 0x00000,  0x00000
end table


// Called to create explosions after the Tornado gets shot
// Poor Tails...
private function Tornado_Explode
	if object.duration == 0
		object.duration = 15
		temp0 = object.xpos
		Rand(temp1, 8)
		temp1 <<= 18
		temp0 -= temp1
		temp1 = object.ypos
		temp1 += 0x100000
		CreateTempObject(TypeName[T Explosion], 0, temp0, temp1)
	else
		object.duration--
	end if
end function


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

event ObjectUpdate
	object.lastPos.y = object.ypos
	object.drawOrder = 4
	
	// States TORNADO_SETUPINTRO -> TORNADO_FALLING are for the stage's intro
	// States after that are for the chase cutscene when Tails helps you hitch a ride on Robotnik's ship

	switch object.state
	case TORNADO_SETUPINTRO
		temp0 = player[0].xpos
		temp0 -= 0x100000
		if object.xpos < temp0
			object.type = TypeName[Blank Object]
		else
			if stage.playerListPos == PLAYER_TAILS_ALONE
				// Main player is Tails, shift him down a bit to account for his shorter height
				player[0].ypos += 0x40000
			end if
			object.state++
		end if
		break

	case TORNADO_FLYINTRO
		// Just flying straight for a brief moment
		object.xpos += object.xvel

		object.timer--
		if object.timer == 0
			object.yvel 	= 0x10000
			object.timer 	= 96
			object.state 	= TORNADO_SHOTDOWN
		else
			temp0 = object.xpos
			temp0 -= 0x100000
			if player[0].xpos < temp0
				player[0].xpos = temp0
			end if

			temp0 += 0x200000
			if player[0].xpos > temp0
				player[0].xpos = temp0
			end if
		end if
		break

	case TORNADO_SHOTDOWN
		// Tornado getting shot
		object.xpos += object.xvel
		object.ypos += object.yvel

		temp0 = oscillation
		temp0 &= 0x1F
		if temp0 == 0
			PlaySfx(SfxName[Laser Skim], false)
		end if

		object.timer--
		if object.timer == 0
			object.xvel 	= 0x20000
			object.yvel 	= 0x20000
			object.timer 	= 96
			object.state 	= TORNADO_FALLING
		end if
		CallFunction(Tornado_Explode)
		break

	case TORNADO_FALLING
		// Tornado after being shot, it's just falling now...
		object.xpos += object.xvel
		object.ypos += object.yvel
		if object.outOfBounds == true
			// Despawn it when out of view
			object.type = TypeName[Blank Object]
		end if
		CallFunction(Tornado_Explode)
		break

	case TORNADO_SETUPOUTRO
		// Start of end-of-level cutscene, start removing control from the player, 
		BoxCollisionTest(C_TOUCH, object.entityPos, -640, -192, 0, 0, 0, C_BOX, C_BOX, C_BOX, C_BOX)
		if checkResult == true
			if player[0].gravity == GRAVITY_GROUND
				player[0].controlMode 	= CONTROLMODE_NONE
				player[0].jumpPress 	= false
				player[0].jumpHold 		= false
				player[0].up 			= false
				player[0].down 			= false
				player[0].left 			= false
				player[0].right 		= false
				Player_superState = SUPERSTATE_FADEOUT
				object.state 			= TORNADO_WAITFORPLAYER
			end if
		end if
		break

	case TORNADO_WAITFORPLAYER
		if player[0].state == Player_State_Ground
			player[0].timer = 0
		end if

		object.timer++
		if object.timer >= 120
			object.timer 	= 0
			object.state 	= TORNADO_MOVEPLAYERTOEDGE
		end if
		break

	case TORNADO_MOVEPLAYERTOEDGE
		player[0].right = true

		temp0 = object.xpos
		temp0 -= 0x100000
		if player[0].xpos >= temp0
			player[0].xpos 	= temp0
			player[0].xvel 	= 0
			player[0].speed = 0
			player[0].right = 0
			object.xvel 	= 0x10000
			object.yvel 	= -0x10000
			object.state 	= TORNADO_JUMPONTOTORNADO
		end if
		break

	case TORNADO_JUMPONTOTORNADO
		object.xpos += object.xvel
		object.ypos += object.yvel

		object.timer++
		if object.timer == 72
			player[0].jumpPress 		= true
			player[0].jumpHold 			= true
			player[0].right 			= true
			object.jumpAbilityFunc 		= player[0].jumpAbility

			// Reset the physics values to match Sonic's, even Knux' jump height is ignored here so that the cutscene doesn't desync
			player[0].jumpAbility 		= Player_State_Static
			player[0].gravityStrength 	= 0x3800
			player[0].topSpeed 			= 0x60000
			player[0].acceleration 		= 0xC00
			player[0].deceleration 		= 0xC00
			player[0].airAcceleration 	= 0x1800
			player[0].airDeceleration 	= 0x600
			player[0].skidSpeed			= 0x8000
			player[0].rollingFriction 	= 0x600
			player[0].jumpStrength 		= 0x68000
			player[0].jumpCap 			= -0x40000

			object.state 		= TORNADO_LANDONTORNADO
		end if
		break

	case TORNADO_LANDONTORNADO
		object.xpos += object.xvel
		object.ypos += object.yvel

		object.timer++
		BoxCollisionTest(C_PLATFORM, object.entityPos, -20, -8, 20, 8, 0, C_BOX, C_BOX, C_BOX, C_BOX)
		if checkResult == true
			player[0].jumpPress 	= false
			player[0].jumpHold 		= false
			player[0].xvel 			= 0
			player[0].speed 		= 0
			player[0].right 		= false
			player[0].jumpAbility 	= object.jumpAbilityFunc
			GetTableValue(object.duration, object.velocityTablePos, Tornado_velocityDurationTable)
			object.velocityTablePos++
			object.state = TORNADO_CHASESEQUENCE
		end if
		break

	case TORNADO_CHASESEQUENCE
		if object.timer < 1160
			tileLayer[1].scrollSpeed -= 256
			if tileLayer[1].scrollSpeed < -0x60000
				tileLayer[1].scrollSpeed = -0x60000
			end if
		else
			if object.timer > 2048
				tileLayer[1].scrollSpeed += 0x600
				if tileLayer[1].scrollSpeed > 0
					tileLayer[1].scrollSpeed = 0
				end if

				tileLayer[3].scrollSpeed += 0x4E0
				if tileLayer[3].scrollSpeed > 0
					tileLayer[3].scrollSpeed = 0
					if stage.activeLayer[1] != 2
						stage.activeLayer[1] = 2  // Show Death Egg Dock BG
						stage.activeLayer[3] = 2  // Show Death Egg Dock BG
						tileLayer[2].scrollPos = 0
						tileLayer[2].scrollSpeed = 0
						hParallax[4].scrollPos = 0
						hParallax[4].scrollSpeed = 0x20000
					end if
				end if
			end if
		end if
		player[0].timer = 0

		object.timer++
		if object.timer >= object.duration
			temp0 = object.velocityTablePos
			temp0 <<= 1
			GetTableValue(object.xvel, temp0, Tornado_moveVelocityTable)
			temp0++
			GetTableValue(object.yvel, temp0, Tornado_moveVelocityTable)
			GetTableValue(object.duration, object.velocityTablePos, Tornado_velocityDurationTable)
			object.velocityTablePos++
		end if
		
		// "Checkpoints" or "keyframes" of sorts are used here, to mark whenever a change should happen
		// Any value not listed here just does nothing
		switch object.timer
		case 296
			object.boosterActive = true
			hParallax[0].scrollSpeed <<= 1
			hParallax[1].scrollSpeed <<= 1
			hParallax[2].scrollSpeed <<= 1
			hParallax[3].scrollSpeed <<= 1
			break

		case 1148
			player[0].jumpPress = true
			player[0].jumpHold  = true
			break

		case 1149
			player[0].jumpPress = false
			break

		case 1160
			wfzEggmanThruster[+4].interval = 1
			wfzEggmanThruster[+5].interval = 1
			wfzEggmanThruster[+6].interval = 1
			hParallax[0].scrollSpeed <<= 2
			hParallax[1].scrollSpeed <<= 2
			hParallax[2].scrollSpeed <<= 2
			hParallax[3].scrollSpeed <<= 2
			player[0].gravity 			= GRAVITY_GROUND
			player[0].scrollTracking 	= false
			tileLayer[1].scrollSpeed 	= -0x80000
			tileLayer[3].scrollSpeed 	= -0x80000
			break

		case 2200
			stage.activeLayer[0] 		= 3 // Show the Sky & Space BG
			tileLayer[3].scrollPos 		= 0x2490000
			tileLayer[3].scrollSpeed 	= tileLayer[1].scrollSpeed
			break

		case 2624
			object.timer 	= 0
			object.state 	= TORNADO_FINISHACT
			break
			
		end switch

		object.xpos += object.xvel
		object.ypos += object.yvel
		break

	case TORNADO_FINISHACT
		// This stage doesn't have an [Act Finish] object so this object takes its place
		music.volume -= 2
		object.timer += 4
		SetScreenFade(0x00, 0x00, 0x00, object.timer)
		if object.timer == 384
			StopMusic()
			fadeColor 		= 0
			object.timer 	= 0
			starPostID 		= 0
			object.direction = FACING_RIGHT // this doesn't really matter much

#platform: USE_ORIGINS
			if game.playMode == BOOT_PLAYMODE_BOSSRUSH
				CallNativeFunction2(NotifyCallback, NOTIFY_BOSS_END, 1) // the [Eggman Laser] object already does this, so the Tornado never really gets a chance to do it
				LoadStage()
			else
#endplatform
				stage.listPos++

				// Huh?
				// If the stage we're going to is after HPZ, then go back to OOZ1?
				// if you really wanna, ig...
				if stage.listPos > 20
					stage.listPos = 12
				end if

				if options.gameMode == MODE_SAVEGAME
					arrayPos1 = options.saveSlot
					arrayPos1 <<= 3
					if stage.player2Enabled == true
						saveRAM[arrayPos1] = 3
					else
						saveRAM[arrayPos1] = stage.playerListPos
					end if
					arrayPos1++

					saveRAM[arrayPos1] = player.lives
					arrayPos1++

					saveRAM[arrayPos1] = player.score
					arrayPos1++

					saveRAM[arrayPos1] = player.scoreBonus
					arrayPos1++

					// Bug Details:
					// This is missing the check for if your save file is already past the next act
					// This means the save file's act will be overwritten to Death Egg Zone, even if you're on a Complete save file...
					saveRAM[arrayPos1] = stage.listPos
					saveRAM[arrayPos1]++
					arrayPos1++

					saveRAM[arrayPos1] = specialStage.emeralds
					arrayPos1++

					saveRAM[arrayPos1] = specialStage.listPos
					WriteSaveRAM()
				end if

				if options.gameMode == MODE_TIMEATTACK
					timeAttack.result = stage.seconds
					timeAttack.result *= 100
					temp0 = stage.minutes
					temp0 *= 6000
					timeAttack.result += temp0
					timeAttack.result += stage.milliSeconds
					stage.listPos--
					CallNativeFunction2(SetLeaderboard, stage.listPos, timeAttack.result)
					engine.state = 8
				else
					temp0 = engine.trialMode
					if stage.listPos >= stage.listSize
						temp0 = true
					end if

					if temp0 == false
#platform: USE_ORIGINS
						CallNativeFunction4(NotifyCallback, NOTIFY_STATS_ENEMY, StageStatsUsabilityParam1, StageStatsUsabilityParam2, StageStatsUsabilityParam3)
						CallNativeFunction2(NotifyCallback, NOTIFY_STATS_RING, player[0].rings)
						temp0 = StageStatsUsabilityParam5
						temp0 &= 1
						temp1 = StageStatsUsabilityParam5
						temp1 &= 2
						temp2 = StageStatsUsabilityParam5
						temp2 &= 4
						CallNativeFunction4(NotifyCallback, NOTIFY_STATS_CHARA_ACTION, temp0, temp1, temp2)
						CallNativeFunction2(NotifyCallback, NOTIFY_ACT_FINISH, 0)
						StageStatsUsabilityParam1 = 0
						StageStatsUsabilityParam2 = 0
						StageStatsUsabilityParam3 = 0
						StageStatsUsabilityParam4 = 0
						StageStatsUsabilityParam5 = 0
						StatsUsabilityParam1 = 0
						StatsUsabilityParam2 = 0
						StatsUsabilityParam3 = 0
						StatsUsabilityParam4 = 0
						StatsUsabilityParam5 = 0
#endplatform

						LoadStage()
					else
						stage.activeList = PRESENTATION_STAGE
						stage.listPos = 0
						LoadStage()
					end if
				end if
#platform: USE_ORIGINS
			end if
#endplatform
		end if
		break
		
	end switch

	temp0 = object.ypos

	object.lastPos.y 			&= 0xFFFF0000
	object.collisionOffset.y 	= object.ypos
	object.collisionOffset.y 	&= 0xFFFF0000
	object.collisionOffset.y 	-= object.lastPos.y
	object.ypos 				= object.lastPos.y

	object.stood = false
	foreach (GROUP_PLAYERS, currentPlayer, ACTIVE_ENTITIES)
		temp2 = player[currentPlayer].yvel
		BoxCollisionTest(C_PLATFORM, object.entityPos, -20, -8, 20, 8, currentPlayer, C_BOX, C_BOX, C_BOX, C_BOX)
		if checkResult == true
			player[currentPlayer].xpos += object.xvel
			player[currentPlayer].ypos += object.yvel
			object.stood |= true
		end if
	next
	object.ypos = temp0

	object.frame++
	object.frame &= 3

	if object.pilotFrameDelay < 2
		if stage.playerListPos == PLAYER_TAILS_ALONE
			GetTableValue(object.pilotFrame, object.pilotAnimPos, Tornado_SonicPilotFrameTable)
			object.pilotAnimPos++

			GetTableValue(object.pilotFrameDelay, object.pilotAnimPos, Tornado_SonicPilotFrameTable)
			object.pilotAnimPos++
		else
			GetTableValue(object.pilotFrame, object.pilotAnimPos, Tornado_TailsPilotFrameTable)
			object.pilotAnimPos++

			GetTableValue(object.pilotFrameDelay, object.pilotAnimPos, Tornado_TailsPilotFrameTable)
			object.pilotAnimPos++
		end if

		object.pilotAnimPos &= 15
	else
		object.pilotFrameDelay--
	end if
end event


event ObjectDraw
	DrawSprite(object.frame)
	DrawSprite(4)
	DrawSprite(object.pilotFrame)

	if object.state >= TORNADO_JUMPONTOTORNADO
		DrawSprite(12)

		if object.boosterActive == true
			temp0 = oscillation
			temp0 &= 1
			temp0 += 13
			DrawSprite(temp0)
		end if
	end if
end event


event ObjectStartup
	LoadSpriteSheet("SCZ/Objects.gif")
	SpriteFrame(21, 8, 4, 24, 378, 215)			// Propeller - Frame 1 		- #0
	SpriteFrame(21, 11, 4, 18, 383, 218)		// Propeller - Frame 2 		- #1
	SpriteFrame(21, 16, 4, 8, 388, 223)			// Propeller - Frame 3 		- #2
	SpriteFrame(21, 11, 4, 18, 393, 218)		// Propeller - Frame 4 		- #3
	SpriteFrame(-95, -16, 116, 64, 261, 191)	// Tornado 					- #4
	SpriteFrame(-45, -8, 21, 16, 261, 174)		// Pilot (Tails) - Frame 1 	- #5
	SpriteFrame(-45, -8, 21, 16, 283, 174)		// Pilot (Tails) - Frame 2 	- #6
	SpriteFrame(-45, -8, 21, 16, 305, 174)		// Pilot (Tails) - Frame 3 	- #7
	SpriteFrame(-45, -8, 21, 16, 327, 174)		// Pilot (Tails) - Frame 4 	- #8
	SpriteFrame(-47, -8, 23, 16, 349, 174)		// Pilot (Tails) - Frame 5 	- #9
	SpriteFrame(-50, -8, 24, 16, 294, 157)		// Pilot (Sonic) - Frame 6 	- #10
	SpriteFrame(-50, -8, 24, 16, 319, 157)		// Pilot (Sonic) - Frame 2 	- #11
	SpriteFrame(-39, 32, 56, 16, 375, 90)		// Booster					- #12
	SpriteFrame(-55, 32, 16, 16, 386, 107)		// Booster Flame - Frame 1 	- #13
	SpriteFrame(-71, 32, 32, 16, 386, 124)		// Booster Flame - Frame 2 	- #14

	if playerCount > 1 // Remove P2
		player[1].type = TypeName[Blank Object]
		playerCount = 1
	end if

	foreach (TypeName[Tornado], arrayPos0, ALL_ENTITIES)
		object[arrayPos0].priority = PRIORITY_ACTIVE
		if object[arrayPos0].propertyValue == 0
			object[arrayPos0].xvel 		= 0x10000
			object[arrayPos0].timer 	= 192
		else
			if options.gameMode == MODE_TIMEATTACK
				ResetObjectEntity(arrayPos0, TypeName[Blank Object], 0, 0, 0)
			else
				object[arrayPos0].state = TORNADO_SETUPOUTRO
			end if
		end if
	next
end event


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

event RSDKEdit
	if editor.returnVariable == true
		switch editor.variableID
		case EDIT_VAR_PROPVAL // property value
			checkResult = object.propertyValue
			break
			
		case 0 // type
			checkResult = object.propertyValue
			break
			
		end switch
	else
		switch editor.variableID
		case EDIT_VAR_PROPVAL // property value
			object.propertyValue = editor.variableValue
			break
			
		case 0 // type
			object.propertyValue = editor.variableValue
			break
			
		end switch
	end if
end event


event RSDKDraw
	DrawSprite(0)
	DrawSprite(1)
	DrawSprite(2)
end event


event RSDKLoad
	LoadSpriteSheet("SCZ/Objects.gif")
	SpriteFrame(21, 8, 4, 24, 378, 215)
	SpriteFrame(-95, -16, 116, 64, 261, 191)
	SpriteFrame(-45, -8, 21, 16, 283, 174)

	AddEditorVariable("type")
	SetActiveVariable("type")
	AddEnumVariable("Intro Cutscene", 0)
	AddEnumVariable("Outro Cutscene", 1)
end event
