// File: gameplay.agc
// Created: 20-05-07

global gBananas as integer = 0

global gGrenades as integer = 0

global gSoundRange as float 

global gCurrent as integer = 0

global GPlaces as integer[6]

function	GamePlay(env ref as tEnvironment)
	i as integer
	f as tPoint2D
	t as tPoint2D
	l as tLevel 
	
	SetGlobal3DDepth(200)	
	
	l = LoadLevel("level"+str(env.level)+"/", env)
	
	env.position = 0
	
	gVehicles.insert(CreateVehicle("player", "player", l.speed[0], FALSE, 0, env, l))
	gVehicles.insert(CreateVehicle("npc1", "npc1", l.speed[1], TRUE, 1, env, l))
	gVehicles.insert(CreateVehicle("npc2", "npc2", l.speed[2], TRUE, 2, env, l))
	gVehicles.insert(CreateVehicle("npc3", "npc3", l.speed[3], TRUE, 3, env, l))
	gVehicles.insert(CreateVehicle("npc4", "npc4", l.speed[4], TRUE, 4, env, l))
	gVehicles.insert(CreateVehicle("npc5", "npc5", l.speed[5], TRUE, 5, env, l))

	gVehicles[gCurrent].monitor = TRUE

	SetupVehiclePositions(gVehicles)
	
	ViewTrack(env, l)
	
	SetSpriteVisible(env.loading, 0)                                  
	SetSpritePosition(env.place, 0, 0)
	SetSpriteVisible(env.placebg, 1)
	SetTextVisible(env.place, 1)	
	SetSpriteVisible(env.place, 1)		
	SetTextVisible(env.lap, 1)	
	
	/*
	if DEBUG = 0
		SetCameraStart(gVehicles[0], env, l)
	endif
	*/
	
	sleep(100)
	Step3DPhysicsWorld()	
	
	MoveCamera(gVehicles[0], 1)
		
	l.lights.mode = FALSE
	
	SetLapNumber(gVehicles[0], env, l)
	
	SetMusicVolumeOGG(env.theme, 20)
	
	Transition(env)
	
	FlushEscapeKey()		
	
	repeat
		if l.lights.mode <> LIGHTS_DONE
			if StartLights(l, env) = LIGHTS_GREEN
				StartVehicles(gVehicles)
			endif
		endif
		
		if GetRawKeyPressed(KEY_ESCAPE) or GetJoystickBack()
			gGameState = GAMESTATE_SELECT
		endif
		
		MoveStuff(gVehicles, env, l)
		
		MoveVehicles(gVehicles, env, l)
	          
	    if DEBUG
			DisplayStats(gVehicles, env)
		endif
		
	    Step3DPhysicsWorld()	
	    
		CameraKeys(gVehicles[0], env, l)
	
		MoveCamera(gVehicles[gCurrent], gVehicles[gCurrent].camera.ease)
	//	MoveCamera(gVehicles[gCurrent], 1)
			
	//	DrawSensors(gVehicles[0])								// Draw the sensors if debugging
		
		LowRez(env)
		
	    Sync()
	until gGameState <> GAMESTATE_PLAY 
		
	DestroyLevel(env, l)
	
endfunction




function UpdatePosition(vehicle ref as tVehicle, env ref as tEnvironment, level ref as tLevel)
	currentNode	as tPoint
	nextNode		as tPoint	
	vehiclePos	as tPoint
	d1			as float
	d2			as float
	c			as integer
	node		as tFNode
	g			as tpointInt
	line		as tLine
	f			as tPoint2D
	t			as tPoint2D
	v			as float
	i			as integer
	l1			as tPoint
	l2			as tPoint	
	dot			as float
	
	v = Distance3D(CreatePoint(0, 0, 0), GetVelocityPoint(vehicle))
	
	vehiclePos = GetObjectPosition(vehicle.hit)
	
	g.x = round(vehiclePos.x / TO_TILE_X) - gGrid.offset.x
	g.y = round(vehiclePos.y / TO_TILE_Y) - gGrid.offset.y
	g.z = round(vehiclePos.z / TO_TILE_Z) - gGrid.offset.z

	if g.z < 0 then g.z = 0
	if g.x < 0 then g.x = 0	
	if g.y < 0 then g.y = 0	

	if g.z > gGrid.cells.length
		g.z = gGrid.cells.length
	endif

	if g.x > gGrid.cells[g.z].length
		g.x = gGrid.cells[g.z].length
	endif
			
	if g.y > gGrid.cells[g.z, g.x].length
		g.y = gGrid.cells[g.z, g.x].length	
	endif
	
	ProcessCoins(vehicle, gGrid.cells[g.z, g.x, g.y], env)
	ProcessPickups(vehicle, gGrid.cells[g.z, g.x, g.y], env)	
	ProcessBoost(vehicle, gGrid.cells[g.z, g.x, g.y], env)		
	
	currentNode = GetFollowPos(vehicle.follow)
	nextNode = GetFollowPos(vehicle.follow + 1)
	
	d1 = Distance3D(vehiclePos, currentNode)				
	d2 = Distance3D(vehiclePos, nextNode)	
	
	l1 = GlobalToLocal(vehicle.point, currentNode)
	l2 = GlobalToLocal(vehicle.point, nextNode)

	dot = CreateDotFromPoints(SubtractPoints(GetObjectPos(vehicle.center), GetObjectPos(vehicle.dotID)), SubtractPoints(currentNode, nextNode))

/*				
	if vehicle.npc = FALSE
		print("") : print("") 
		print(str(d1,0)+":"+str(d2,0))
		print(str(vehicle.follow))
		print("Dot:"+str(dot,2))		
	endif
	*/
	
	if abs(vehiclePos.y - currentNode.y) > 400 	// Falling
		ResetVehicle(vehicle)
	endif
	
	if vehicle.follow > 102
		vehicle.follow = vehicle.follow
	endif
	
	if vehicle.follow > 0
		if d1 > RESET_DISTANCE
			if vehicle.npc = FALSE
				vehicle.npc = vehicle.npc
			endif
			ResetVehicle(vehicle)						
		endif
	else
		if d1 > RESET_DISTANCE * 1.5
			ResetVehicle(vehicle)						
		endif
	endif

	if d2 < d1 
		inc vehicle.follow
		
		if vehicle.follow = 1
			inc vehicle.lap					
			if vehicle.lap < level.laps
				if vehicle.npc = FALSE
					SetLapNumber(vehicle, env, level)
				endif
			else
				if vehicle.npc = TRUE
					vehicle.action = GetAction(ACTION_SLOW, vehicle)
					vehicle.action.start = Timer()					
				else
					vehicle.action = GetAction(ACTION_STOP, vehicle)	
					gGameState = GAMESTATE_FINISH	
				endif
			endif			
		endif
		
		if vehicle.follow > gFollow.nodes.length	
			vehicle.follow = 0
		endif
		
		/*
		if vehicle.npc = FALSE
			SetObjectPos(vehicle.marker, gFollow.nodes[vehicle.follow].pos)
		endif
		*/
		
		c = gFollow.nodes[vehicle.follow].laps[vehicle.lap] 
		inc 	c
		if c > gVehicles.length + 1
			c = 1
		endif
		vehicle.position = c
		gFollow.nodes[vehicle.follow].laps[vehicle.lap] = c		
		GPlaces[c - 1] = vehicle.no + 1
	endif
	
	if vehicle.name = "player"
		
		if dot < 0 and d1 > RESET_DISTANCE * 0.5
			SetSpriteVisible(env.stop.id, 1)								
		else
			SetSpriteVisible(env.stop.id, 0)								
		endif
		
		if vehicle.finished > 0
			i = vehicle.finished
		else
			i = vehicle.position
		endif			

		SetTextString(env.place, str(i))
		
		if i = 1
			SetTextPosition(env.place, 13, 1)
		else
			SetTextPosition(env.place, 10, 1)
		endif
	endif

	if vehicle.npc = FALSE
		if vehicle.lap = level.laps - 1
			if vehicle.finished = 0
				line.f = GetObjectPos(vehicle.line[0])
				line.t = GetObjectPos(vehicle.line[1])				
				if LineLineXZ(line, level.start.line)
					vehicle.finished = vehicle.position
					env.position = vehicle.position
				endif
			endif
		endif
	endif

endfunction

function SetLapNumber(vehicle ref as tVehicle, env ref as tEnvironment, level ref as tLevel)
	i as integer
	
	if vehicle.lap < 0
		i = 0
	else
		i = vehicle.lap
	endif
		
	SetTextString(env.lap, str(i+1)+"/"+str(level.laps))
	
endfunction

function PoopGrenade(vehicle ref as tVehicle, env ref as tEnvironment)
	id 	as integer
	p	as tPoint
	v	as float
	
	inc gGrenades
	
	id = GRENADES+gGrenades
	
	CloneObject(id, env.grenade.id)
	SetObjectVisible(id, 1)
	SetObjectCastShadow(id, 1)
	SetObjectPos(id, GetObjectPos(vehicle.poop))
		
	Create3DPhysicsDynamicBody(id)						// Set out hitbox to be a convex hull
	SetObjectShapeSphere(id)
	SetObject3DPhysicsCanSleep(id, 0)	
	SetObject3DPhysicsRestitution(id, .5)
	
	p = GetVelocityPoint(vehicle)
	
	v = Distance3D(CreatePoint(0, 0, 0), p) * .8
	
	SetObject3DPhysicsLinearVelocity(id, p.x, p.y, p.z, v)
	SetObject3DPhysicsAngularVelocity(id, 0, 1, 0, 10 * SCALE_3D)
	//SetObject3DPhysicsDamping(id , 0.8, 0.8 ) 		// Grenade
endfunction

function PoopBanana(vehicle ref as tVehicle, env ref as tEnvironment)
	id 	as integer
	p	as tPoint
	v	as float
	
	inc gBananas
	
	id = BANANAS+gbananas
	
	CloneObject(id, env.banana.id)
	SetObjectVisible(id, 1)
		SetObjectCastShadow(id, 1)
	SetObjectPos(id, GetObjectPos(vehicle.poop))
		
	Create3DPhysicsDynamicBody(id)						// Set out hitbox to be a convex hull
	SetObjectShapeBox(id)
	SetObject3DPhysicsCanSleep(id, 0)	
	
	p = GetVelocityPoint(vehicle)
	
	v = Distance3D(CreatePoint(0, 0, 0), p) * .8
	
	SetObject3DPhysicsLinearVelocity(id, p.x, p.y, p.z, v)
	SetObject3DPhysicsAngularVelocity(id, 0, 1, 0, 20 * SCALE_3D)
	
endfunction

function ResetVehicle(vehicle ref as tVehicle)
	node		as tFNode	
	node = GetFollowNode(vehicle.follow)			
	while node.grounded = FALSE
		dec vehicle.follow
		node = GetFollowNode(vehicle.follow)
	endwhile
	SetupVehiclePhysics(vehicle, FALSE, CreatePoint(0, 0, 0))							
endfunction


function UpdateMiniMap(v ref as tVehicle, env ref as tEnvironment, level ref as tLevel)
	p as tPoint
	w as tPoint
	i as tPoint
	
	p = GetObjectPosition(v.hit)
	
	i.x = (p.x - level.map.offset.x) * level.map.scale.x
	i.z = GetSpriteHeight(level.map.id) - (p.z - level.map.offset.z) * level.map.scale.z	
	
	w.x = GetWorldXFromSprite(level.map.id, i.x, i.z)
	w.y = GetWorldYFromSprite(level.map.id, i.x, i.z)		
	
	SetSpritePositionByOffset(v.spot, w.x, w.y)
	
endfunction


function MoveStuff(vehicles as tVehicle[], env ref as tEnvironment, level ref as tLevel)
	i as integer
	
	RotateCoins()
	 
	if GetObjectExists(level.vm.id)
		RotateObjectLocalY(level.vm.id, 1)
	endif
	
	for i=0 to gTweens.length
		PlayTweenChain(gTweens[i].chain)
	next
				
	UpdateAllTweens(GetFrameTime())		
	
endfunction

function MoveVehicles(vehicles ref as tVehicle[], env ref as tEnvironment, level ref as tLevel)
	i as integer
	p as tPoint
	w as tPoint
	v as tVehicle
	s as float
	r as float 
	player as tVehicle
	button as integer
	
	player = gVehicles[VEHICLE_PLAYER]

	button = AnyJoystickButton(FALSE)

	if GetRawKeyPressed(KEY_SPACE) or button = JOYSTICK_RIGHT_TRIGGER or button = JOYSTICK_LEFT_TRIGGER
		v = vehicles[0]
		select v.pickup
			case PICKUP_BOOST
				if v.velocity.boost = 0
					v.velocity.time = timer() + 10							
					v.velocity.boost = v.velocity.max
					v.velocity.max = v.velocity.max * 1.2
				else
					inc	v.velocity.time, 10
				endif
				SetObject3DPhysicsMaxLinearVelocity( v.hit, v.velocity.max ) 								
			 vehicles[0]	 = v			
			endcase
			case PICKUP_SPIN_ONE
				v = vehicles[gPlaces[0]]
				if v.npc = FALSE
					i = 1
				else
					i = 0
				endif
				v = vehicles[gPlaces[i]]					
				v.state = VEHICLE_STATE_SPINOUT
				v.delay.count = timer() + 5
				SetObject3DPhysicsAngularVelocity( v.hit, 0, 1, 0, 50 * SCALE_3D )
				vehicles[gPlaces[i]] = v
			endcase
			case PICKUP_RESET
			endcase
			case PICKUP_SPIN_ALL
				for i=0 to vehicles.length
					v = vehicles[i]
					if v.npc = FALSE
						continue
					endif
					v.state = VEHICLE_STATE_SPINOUT
					v.delay.count = timer() + 5
					SetObject3DPhysicsAngularVelocity( v.hit, 0, 1, 0, 50 * SCALE_3D )
					vehicles[i] = v
				next
			endcase
		endselect
		vehicles[0].pickup = 0
		SetSpriteVisible(env.pickups, 0)
	endif
		
				
	for i=0 to vehicles.length
		v = vehicles[i]
		
		s = 0
		
		SoundFX(player, v, env)
			
		MoveVehicle(v, env, level)

		UpdateBoostEffect(v)
	
		UpdateMiniMap(v, env, level)
		
		UpdatePosition(v, env, level)
		
		if v.crashed 
			if v.bumpLast = FALSE
				PlayCrash(v, env.bump, FALSE)
			endif
			v.bumpLast = TRUE
		elseif v.landed 
			PlayCrash(v, env.land, FALSE)
		else
			v.bumpLast = FALSE
		endif
		
		vehicles[i] = v
	next i
	
endfunction


function UpdateBoostEffect(v ref as tvehicle)
	SetObjectVisible(v.boost, 0)
		
	if v.velocity.boost > 0 
		if v.flying = 0
			if  v.velocity.current > ( v.velocity.boost * 0.8 )
				SetObjectVisible(v.boost, 1)
				inc v.boostOffset, 25
				if v.boostOffset > 100.0
					v.boostOffset = 0
				endif
				SetObjectUVOffset(v.boost, 0, round(v.boostOffset + 25) / 100, 0)
			endif
		endif
	endif	
endfunction

function SoundFX(player ref as tVehicle, v ref as tVehicle, e ref as tEnvironment)
	s as float
	b as float
	r as float
	d as float
	f as tPoint
	t as tPoint
	p as tPoint
	top as float
	
	if v.engine = 0
		if random(1, 100) = 1
			v.engine = PlaySound(e.engine,0,1)
		else
			exitfunction
		endif
	endif
	
	d = DistancePoint(SubtractPoints(GetObjectPos(player.hit), GetObjectPos(v.hit)))
	d = clamp(d, 0, gSoundRange)
	
	s = (gSoundRange - d) * 100.0 / (gSoundRange)
	//r = (s / 100)
	
	if v.npc = TRUE
		top = 1
		r = inCubic((gSoundRange - d), 0, 1, gSoundRange) + 1
		s = inCubic((gSoundRange - d), 0, 40, gSoundRange)
	else
		top = 1.3
		if v.velocity.boost > 0
			top = 1.7
		endif
		r = linear(v.velocity.current, 0, top, v.velocity.max)		
		s = inCubic((gSoundRange - d), 0, 50, gSoundRange)
	endif

	r = clamp(r, 0, top)			
	s = clamp(s, 0.0, 100.0)


	SetSoundInstanceVolume(v.engine, s) 
	SetSoundInstanceRate(v.engine, r) 	
	
	f = GetObjectPos(player.center)
	t = GetObjectPos(player.dotID)
	p = GetObjectPos(v.center)
	
	b = -PointSideLineZX(f, t, p)
	b = clamp(b, -10000, 10000) / 10000
	
	SetSoundInstanceBalance(v.engine, b)
	
	//print(v.name+" ("+str(s)+":"+str(1+r)+":"+str(b)+")")	
					
endfunction


type tSortPos
	distance	as float	
	index 	as integer
endtype

function SetupVehiclePositions(vehicles ref as tVehicle[])
	i as integer
	v as tVehicle
	d as tSortPos[]
	s as tSortPos
	
	for i=0 to vehicles.length
		v = vehicles[i]
		s.index = i
		s.distance = DistanceXZ(GetObjectPos(v.hit), gFollow.nodes[0].pos)
		d.insertsorted(s)
	next
	
	for i=0 to d.length
		vehicles[d[i].index].position = i+1
	next
		
endfunction

function StartVehicles(vehicles ref as tVehicle[])
	i as integer
	v as tVehicle
	
	for i=0 to vehicles.length
		vehicles[i].action = GetAction(ACTION_GO, vehicles[i])
	next i
endfunction


function MoveCamera(vehicle as tVehicle, easing as float)
	p as tPoint
	
	//SetObjectPosition(TILE_MARKER, round(GetObjectX(vehicle.hit) / TO_TILE_X) * TO_TILE_X, GetObjectY(vehicle.hit), round(GetObjectZ(vehicle.hit) / TO_TILE_Z) * TO_TILE_Z)
		
	if DEBUG
		SetCameraPosition(1, GetObjectX(gVehicles[1].hit), GetCameraY(1), GetObjectZ(gVehicles[1].hit))
		exitfunction
	endif	

	vlook as tPoint
	vlook = CreatePoint(GetObjectWorldX(vehicle.camera.look), GetObjectWorldY(vehicle.camera.look), GetObjectWorldZ(vehicle.camera.look))
			
	if vehicle.camera.style = CAMERA_3RD
		SetObjectPosition(vehicle.camera.dolly, GetObjectWorldX(vehicle.hit), GetObjectWorldY(vehicle.hit), GetObjectWorldZ(vehicle.hit))
		SetObjectRotation(vehicle.camera.dolly, vehicle.camera.adjust.x , GetObjectAngleY(vehicle.hit) + vehicle.camera.adjust.y, 0)
	endif
		
	cam as tPoint
	cam = CreatePoint(GetCameraX(1), GetCameraY(1), GetCameraZ(1))
	
	vcam as tPoint
	vcam = CreatePoint(GetObjectWorldX(vehicle.camera.id), GetObjectWorldY(vehicle.camera.id), GetObjectWorldZ(vehicle.camera.id))
	
	look as tPoint
	look = CreatePoint(GetObjectWorldX(vehicle.camera.look), GetObjectWorldY(vehicle.camera.look), GetObjectWorldZ(vehicle.camera.look))
	
	if vehicle.camera.style = CAMERA_3RD
		//SetCameraPosition(1, vcam.x, vcam.y, vcam.z)		
		SetCameraPosition(1, cam.x + (vcam.x - cam.x) * easing, cam.y + (vcam.y - cam.y) * easing, cam.z + (vcam.z - cam.z) * easing)
	else
		SetCameraPosition(1, vcam.x, vcam.y, vcam.z)
	endif

	SetCameraLookAt(1, look.x, look.y, look.z, 0)	

endfunction


function CameraKeys(vehicle ref as tVehicle, env ref as tEnvironment, level ref as tLevel)
	
	
	if GetRawKeyPressed(asc("D")) and GetRawKeyState(KEY_SHIFT)	
		if DEBUG = 0
			DEBUG = 1
			ViewTrack(env, level)
		else
			DEBUG = 0
		endif
	endif

	if GetRawKeyPressed(asc("R")) = 1							// R-Reset
		if GetRawKeyState(KEY_CONTROL)	
			SetupVehiclePhysics(vehicle, FALSE, GetPathPoint(vehicle, 0))
		elseif GetRawKeyState(KEY_SHIFT)	
			SetupVehiclePhysics(vehicle, TRUE, GetPathPoint(vehicle, 0))
		endif
	endif

	if GetRawKeyPressed(asc("C")) = 1							// H-Hit shape
		if vehicle.camera.style = CAMERA_1ST
			vehicle.camera = vehicle.camera3rd
		else
			vehicle.camera = vehicle.camera1st		
		endif
	endif
	
	if GetRawKeyPressed(asc("V")) = 1								// H-Hit shape
		ToggleVisible(vehicle)
	endif
	
	if GetRawKeyPressed(asc("H")) = 1								// H-Hit shape
		ToggleHitShape(vehicle)
	endif
	
	if GetRawKeyPressed(asc("G")) = 1								// G-Ground contact
		if vehicle.ground = FALSE
			vehicle.ground = TRUE
		else
			SetObjectColor(vehicle.chassis, 0xff, 0xff, 0xff, 0xff)
			vehicle.ground = FALSE
		endif
	endif	
	
	if GetRawKeyState(asc("S")) = 1									// S-Roll right or SHIFT+S-Sideways right
		if GetRawKeyState(KEY_CONTROL)		
			MoveObjectLocalX(vehicle.camera.dolly , 5)		
		elseif GetRawKeyState(KEY_SHIFT)	
			dec vehicle.camera.adjust.y
		endif
	endif
	
	if GetRawKeyState(asc("A"))								// A-Roll left or SHIFT+A-Sidways left
		if GetRawKeyState(KEY_CONTROL)		
			MoveObjectLocalX(vehicle.camera.id , -5)		
		elseif GetRawKeyState(KEY_SHIFT)		
			inc vehicle.camera.adjust.y
		endif
	endif
	
	if GetRawKeyState(asc("Z")) 								// Z-Roll down or SHIFT+Z-Zoom out 
		if GetRawKeyState(KEY_CONTROL)		
			MoveObjectLocalZ(vehicle.camera.id , -5)				
		elseif GetRawKeyState(KEY_SHIFT)		
			dec vehicle.camera.adjust.x				
		endif
	endif
	
	
	if GetRawKeyState(ASC("W"))								// W-Roll up or SHIFT+W-Zoom in 
		if GetRawKeyState(KEY_CONTROL)		
			MoveObjectLocalZ(vehicle.camera.id , 5)						
		elseif GetRawKeyState(KEY_SHIFT)		
			inc vehicle.camera.adjust.x
		endif
	endif		
endfunction

function GetFollowNode(n as integer)
	p as tPoint
	
	if n > gFollow.nodes.length
		n = 0
	endif
	
	if n < 0
		n = gFollow.nodes.length
	endif
	
endfunction gFollow.nodes[n]
	
function GetFollowPos(n as integer)
	p as tPoint
	
	if n > gFollow.nodes.length
		n = 0
	endif
	
	if n < 0
		n = gFollow.nodes.length
	endif
	
endfunction gFollow.nodes[n].pos


function PlayCrash(v ref as tVehicle, sound as integer, force as integer)
	player as tVehicle
	d	   as float
	vol	   as float
	side		as float
	f		as tpoint
	t		as tpoint
	
	if GetSoundInstancePlaying(v.bump) and force = FALSE
		exitfunction
	endif
	
	player = gVehicles[VEHICLE_PLAYER]
	
	f = GetObjectPos(player.center)
	t = GetObjectPos(player.dotID)
	
	d = DistancePoint(SubtractPoints(GetObjectPos(player.hit), GetObjectPos(v.hit)))
	
	d = clamp(d, 0, gSoundRange)

	
	vol = inCubic((gSoundRange - d), 0, 100, gSoundRange)
	vol = clamp(vol, 0.0, 100.0)
	
//	vol = (gSoundRange - d) * 100 / gSoundRange
	
	if v.bump = 0
		v.bump = -1
	else
		if v.npc = FALSE
			v.bump = PlaySound(sound, vol)
		else
			v.bump = PlaySound(sound, vol)			
		endif
	endif

	side = PointSide(f, t, GetObjectPos(v.center))
	
	//print(v.name+"("+str(side)+")")
	
	
endfunction
