
array<kActor@> GauntletDoors(2);

array<LavaSegment> LavaSegments( TID_Cave_Gauntlet_Lava_End - TID_Cave_Gauntlet_Lava0 );
float LavaFloorZ = -1740.799927f; // z-coord of the floor at the doorways

enum eLavaState
{
	Lava_Ready,
	Lava_Active,
	Lava_Receding,
	Lava_Drained
}
int LavaState = Lava_Ready;
float LavaDamageTime = 0;
int OldGauntletEnemyCount;

kActor@ RadioChatter;
kVec3 RadioOrigin;

int GauntletEnemyCount()
{
	int count = 0;
	for ( int TID = TID_Cave_Gauntlet_Enemy1; TID < TID_Cave_Gauntlet_Enemy_End; ++TID )
	{
		kActor@ enemy = World.GetActorByTID( TID );
		if ( enemy !is null && enemy.Flags() & AF_DEAD == 0 )
			++count;
	}
	return count;
}

void InitGauntlet()
{
	// default gauntlet doors open
	@GauntletDoors[0] = World.GetActorByTID( TID_Cave_Gauntlet_Door1 );
	@GauntletDoors[1] = World.GetActorByTID( TID_Cave_Gauntlet_Door2 );
	for ( int i=0; i<2; ++i )
	{
		// finagle the exit door to occupy the correct sector
		if ( i == 1 )
		{
			GauntletDoors[i].SetPosition( GauntletDoors[i].Origin() + kVec3(0,0,21*GAME_SCALE) );
			GauntletDoors[i].Origin().z -= 21*GAME_SCALE;
		}
		if ( GauntletDoors[i] !is null )
			GauntletDoors[i].AnimState().Set( anim_doorOpen, 0, 0 );
	}

	//----------------------------------
	// Lava Setup

	@LavaSegments[0].self = World.GetActorByTID( TID_Cave_Gauntlet_Lava0 );

	// 0th segment is offset by one texture wrap to prevent clipping into the wall,
	// so first flat segment needs to counter-offset to get back into position
//	LavaSegments[1].yDir = 1.0f/3.0f;

	for ( uint i=1; i<LavaSegments.length(); ++i )
	{
		int TID = TID_Cave_Gauntlet_Lava0 + i;
		@LavaSegments[i].self = World.GetActorByTID( TID );
		switch ( TID )
		{
			case TID_Cave_Gauntlet_Lava_Turn1:
				@LavaSegments[i].parent  = World.GetActorByTID( TID - 2 );
				@LavaSegments[i].sibling = World.GetActorByTID( TID - 1 );
				LavaSegments[i].WeirdPitch = Math::Deg2Rad( -90 );
				LavaSegments[i].WeirdAxis.Set( 1, 0, 0 );
				LavaSegments[i].yDir = -1;
				break;
			case TID_Cave_Gauntlet_Lava_Turn2:
				@LavaSegments[i].parent  = World.GetActorByTID( TID - 2 );
				@LavaSegments[i].sibling = World.GetActorByTID( TID - 1 );
				LavaSegments[i].WeirdPitch = Math::Deg2Rad( -90 );
				LavaSegments[i].WeirdAxis.Set( 1, 0, 0 );
				LavaSegments[i].yDir = -1;
				break;
			default:
				@LavaSegments[i].parent = LavaSegments[i-1].self;
				// second leg - lean with first turn segment
				if ( TID > TID_Cave_Gauntlet_Lava_Turn1 && TID < TID_Cave_Gauntlet_Lava_Turn2 )
				{
					@LavaSegments[i].sibling = World.GetActorByTID( TID_Cave_Gauntlet_Lava_Turn1 );
					LavaSegments[i].WeirdPitch = Math::Deg2Rad( -90 );
					LavaSegments[i].WeirdAxis.Set( 0, 1, 0 );
				}
				// last leg - lean with second turn segment
				else if ( TID > TID_Cave_Gauntlet_Lava_Turn2 )
				{
					@LavaSegments[i].sibling = World.GetActorByTID( TID_Cave_Gauntlet_Lava_Turn2 );
					LavaSegments[i].WeirdPitch = Math::Deg2Rad( 90 );
					LavaSegments[i].WeirdAxis.Set( 0, 1, 0 );
					// first segment after turn shifts one texture wrap to the side to fill the wider hall part
					if ( TID == TID_Cave_Gauntlet_Lava_Turn2 + 1 )
						LavaSegments[i].yDir = -1.0f/3.0f;
				}
		}

		// first and last legs flow to the left
		if ( TID <= TID_Cave_Gauntlet_Lava_Turn1 || TID > TID_Cave_Gauntlet_Lava_Turn2 )
			LavaSegments[i].xDir = -1;
		// middle leg flows to the right
		else
			LavaSegments[i].xDir = 1;
	}

	// OLD METHOD
	// hide lava out of view above the ceiling
//	LavaSegments[0].self.Origin().z = -1211;
//	for ( uint i=1; i<LavaSegments.length(); ++i )
//		LavaSegments[i].self.Origin().z = -1111;

	// NEW METHOD
	// init to pose that the lava trigger used, and use hidden flag to keep out of view until needed
	LavaSegments[0].self.Roll() = Math::Deg2Rad( 90 );
	LavaSegments[0].self.Origin().z = LavaFloorZ;
	LavaSegments[0].self.Flags() |= AF_HIDDEN;

	for ( uint i=1; i<LavaSegments.length(); ++i )
	{
		LavaSegments[i].self.Roll() = Math::Deg2Rad( -90 ) * LavaSegments[i].xDir;
		// setting Roll() doesn't affect Rotation() until a tick later or something
		// so to make sure LavaSegment::Update() knows that this segment is vertical, manually update Rotation() to match right now
//		LavaSegments[i].self.Rotation() = kQuat( LavaSegments[i].self.Roll(), 0,-1,0 );
		LavaSegments[i].self.Origin().z = LavaFloorZ - 30*GAME_SCALE;
		LavaSegments[i].self.Flags() |= AF_HIDDEN;
	}

	//----------------------------------
	// Encampment

	@RadioChatter = World.GetActorByTID( TID_Cave_Encampment_RadioChatter );
	if ( RadioChatter !is null )
		RadioOrigin = RadioChatter.Origin();
}

class LavaSegment
{
	kActor@ self, parent, sibling;
	float WeirdPitch, xDir, yDir=0;
	kVec3 WeirdAxis;

	float Flatness()
	{
		return ( kVec3(0,0,1) * self.Rotation() ).z;
	}

	void Update( float speed )
	{
		kVec3 v(0,0,0);
		float flatness = 1;

		if ( sibling !is null )
		{
			if ( self.TID() == TID_Cave_Gauntlet_Lava_Turn1 || self.TID() == TID_Cave_Gauntlet_Lava_Turn2 )
				flatness = ( kVec3(0,0,1) * sibling.Rotation() ).z;
			else
				flatness = ( kVec3(0,0,1) * parent.Rotation() ).z;
			if ( flatness <= 0 )
				return;

			self.Rotation() = sibling.Rotation();
			self.Rotation() = self.Rotation() * kQuat( WeirdPitch, WeirdAxis );
		}
		else if ( parent !is null )
			flatness = ( kVec3(0,0,1) * parent.Rotation() ).z;

		v.y = 15*GAME_SCALE * yDir;

		if ( parent !is null )
		{
			self.Origin() = parent.Origin() + (kVec3( 15*GAME_SCALE*xDir*parent.Scale().x/0.35f, 0, 0 ) + v) * parent.Rotation();
			self.Origin() += (kVec3( 15*GAME_SCALE*xDir*self.Scale().x/0.35f, 0, 0 ) + v) * self.Rotation();
		}

		float threshold;
		switch ( self.TID() )
		{
			case TID_Cave_Gauntlet_Lava0:       threshold = 0;     break;
			case TID_Cave_Gauntlet_Lava_Turn1:  threshold = 0.9f;  break;
			case TID_Cave_Gauntlet_Lava_Turn2:  threshold = 0.8f;  break;
			default:  threshold = 0.95f;
		}

		if ( flatness > threshold )
		{
			speed *= GAME_DELTA_TIME;
			if ( self.TID() == TID_Cave_Gauntlet_Lava0 )
				speed *= Math::Lerp( 6, 0.5f, ( kVec3(0,0,1) * self.Rotation() ).z );
			else
				speed /= 2;
			speed = 1 - speed;

			if ( sibling is null )
				self.Roll() = self.Roll() * speed;
			else
				WeirdPitch *= speed;
		}
	}

	bool PlayerTouching()
	{
		kVec3 toPlayer( Player.Actor().Origin() - self.Origin() );
		kVec3 v = kVec3( 15 * GAME_SCALE * self.Scale().x/0.35f, 0, 0 ) * self.Rotation();
		if ( Math::Fabs( toPlayer.x ) > Math::Fabs( v.x ) )
			return false;
		v = kVec3( 0, 15 * GAME_SCALE * self.Scale().y/0.35f, 0 ) * self.Rotation();
		if ( Math::Fabs( toPlayer.y ) > Math::Fabs( v.y ) )
			return false;
		v = kVec3( 0, 0, 1 ) * self.Rotation();
		if ( toPlayer.Dot( v ) > 0 )
			return false;
		return true;
	}
}

void GauntletTick()
{
	// hide the gauntlet doors when underground, because I left myself zero clearance between levels...
	if ( Player.Actor().Origin().z < LavaFloorZ - 2*GAME_SCALE )
	{
		for ( int i=0; i<2; ++i )
			if ( GauntletDoors[i] !is null )
				GauntletDoors[i].Flags() |= AF_HIDDEN;
	}
	else
	{
		for ( int i=0; i<2; ++i )
			if ( GauntletDoors[i] !is null )
				GauntletDoors[i].Flags() &= ~AF_HIDDEN;
	}

	// offset doors so they don't totally sink into the floor when open
	float max = GAME_SCALE * 0.125f;
	for ( int i=0; i<2; ++i )
	{
		if ( GauntletDoors[i] is null ) continue;
		switch ( GauntletDoors[i].AnimState().PlayingID() )
		{
			case anim_doorIdle:
				GauntletDoors[i].RenderModel().Offset().z =
					Math::OLDMax( GauntletDoors[i].RenderModel().Offset().z - max*15*GAME_DELTA_TIME, 0 );
				break;
			case anim_doorOpen:
				GauntletDoors[i].RenderModel().Offset().z =
					Math::OLDMin( GauntletDoors[i].RenderModel().Offset().z + max*GAME_DELTA_TIME, max );
				break;
		}
	}

	UpdateLava();

	if ( RadioChatter !is null )
	{
		if ( RadioChatter.IsStale() )
		{
			Sys.Warning( "radio chatter is stale" );
			@RadioChatter = null;
		}
		else
		{
			kVec3 dif = RadioOrigin - P.Origin();
			// triple horizontal distance so it can't be heard through walls
			// quintuple vertical distance to make sure it can't be heard from the mess hall
			dif *= kVec3( 2, 2, 4 );
			RadioChatter.Origin() = ClampToBounds( RadioOrigin + dif, RadioOrigin );
		}
	}
}

void UpdateLava()
{
	if ( LavaState < Lava_Active || LavaState > Lava_Receding )
		return;

	// check for enemy deaths
	if ( LavaState < Lava_Receding )
	{
		int count = GauntletEnemyCount();
		if ( count < OldGauntletEnemyCount )
		{
			// play a small click when an enemy dies, to indicate that killing them is doing something
			Player.Actor().PlaySoundWithLookupID( 457 );
			// if everyone's dead, start receding
			if ( count <= 0 )
			{
				Player.Actor().PlaySoundWithLookupID( 461 );
				PlayMusic( 10 );
				LavaState = Lava_Receding;
			}
			OldGauntletEnemyCount = count;
		}
	}

	float speed = (2 + Game.GetDifficulty()) / 2.0f;
	if ( LavaState == Lava_Receding )
	{
		speed /= 2;
		LavaSegments[0].self.Origin().z -= GAME_SCALE*GAME_DELTA_TIME/3;
		// open doors when lava is low enough
		if ( LavaSegments[0].self.Origin().z < LavaFloorZ - GAME_SCALE/2 )
		{
			for ( int i=0; i<2; ++i )
				if ( GauntletDoors[i] !is null && GauntletDoors[i].AnimState().PlayingID() != anim_doorOpen )
					GauntletDoors[i].AnimState().Set( anim_doorOpen, 4, 0 );
		}
		// hide lava, and end lava state once fully out of view
		if ( LavaSegments[0].self.Origin().z < LavaFloorZ - GAME_SCALE * 3.25f )
		{
/*			LavaSegments[0].self.RenderModel().Offset().z = 0;
			for ( uint i=0; i<LavaSegments.length(); ++i )
			{
				LavaSegments[i].self.Pitch() = 0;
				LavaSegments[i].self.Yaw() = 0;
				LavaSegments[i].self.Roll() = 0;
				LavaSegments[i].self.Origin().z = -1111;
			}
			LavaSegments[0].self.Origin().z = -1211;*/
			for ( uint i=0; i<LavaSegments.length(); ++i )
				LavaSegments[i].self.Flags() |= AF_HIDDEN;
			LavaState = Lava_Drained;
			return; // skip update phase, since it'll mess up lava hiding
		}
	}
	else// if ( LavaSegments[0].self.Origin().z < LavaFloorZ + 3*GAME_SCALE )
	{
//		LavaSegments[0].self.Origin().z += GAME_SCALE*GAME_DELTA_TIME/4*speed;
		float target = LavaFloorZ + 3*GAME_SCALE;
		if ( LavaSegments[0].self.Origin().z < target )
		{
			float initial = LavaFloorZ;
			float f = 1 - (LavaSegments[0].self.Origin().z - initial) / (target - initial);
			f = Math::Lerp( 0.1f, 4, Math::Pow(f,3) );
			LavaSegments[0].self.Origin().z = Math::OLDMin( LavaSegments[0].self.Origin().z + GAME_SCALE*GAME_DELTA_TIME*speed * f, target );
		}
	}

	// update lava segments
	bool bTouching = false;
	for ( uint i=0; i<LavaSegments.length(); ++i )
	{
		LavaSegments[i].Update( speed );
		if ( !bTouching )
			bTouching = LavaSegments[i].PlayerTouching();
	}

	// slow and damage player if they're in the lava
	float defFric;
	Player.Actor().Definition().GetFloat( "friction", defFric );
	if ( bTouching )
	{
		Player.Actor().Friction() = defFric / 2;
		if ( (LavaDamageTime -= GAME_DELTA_TIME) <= 0 )
		{
			Player.Actor().InflictGenericDamage( Player.Actor().CastToActor(), 3 );
			LavaDamageTime = 1;
		}
	}
	else
	{
		Player.Actor().Friction() = defFric;
		LavaDamageTime = 0;
	}

	// spawn lava bubbles
	if ( LavaState < Lava_Receding )
	{
		int i = 1 + Math::RandMax( TID_Cave_Gauntlet_Lava_End - TID_Cave_Gauntlet_Lava1 );
		if ( LavaSegments[i].Flatness() > 0.99f )
			Game.SpawnFx( "fx/generic_52.kfx", LavaSegments[i].self.Origin(), -1 );
	}
}

// Gauntlet Lava Trigger
$script 72
{
	if ( LavaState > Lava_Ready || instigator.Flags() & AF_ENTEREDAREAEVENT != 0 )
		return;

	OldGauntletEnemyCount = GauntletEnemyCount();
	if ( OldGauntletEnemyCount <= 0 )
		return;

	// close the doors
	for ( int i=0; i<2; ++i )
	{
		if ( GauntletDoors[i] is null ) continue;
		GauntletDoors[i].AnimState().Blend( anim_doorIdle, 4, 4, 0 );
		GauntletDoors[i].PlaySoundWithLookupID( 461 );
		World.FloodFillAreaFlags( GauntletDoors[i].Origin() + kVec3( 0, 13.75f, 20.5f ) * GAME_SCALE * GauntletDoors[i].Rotation(), AAF_BLOCK, true );
	}

	// activate enemies
	for ( int TID = TID_Cave_Gauntlet_Enemy1; TID < TID_Cave_Gauntlet_Enemy_End; ++TID )
		World.TriggerActorsByTID( instigator, TID );

	// prepare the lava

/*	LavaSegments[0].self.Roll() = Math::Deg2Rad( 90 );
	LavaSegments[0].self.Origin().z = LavaFloorZ;

	for ( uint i=1; i<LavaSegments.length(); ++i )
	{
		LavaSegments[i].self.Roll() = Math::Deg2Rad( -90 ) * LavaSegments[i].xDir;
		// setting Roll() doesn't affect Rotation() until a tick later or something
		// so to make sure LavaSegment::Update() knows that this segment is vertical, manually update Rotation() to match right now
		LavaSegments[i].self.Rotation() = kQuat( LavaSegments[i].self.Roll(), 0,-1,0 );
		LavaSegments[i].self.Origin().z = LavaFloorZ - 30*GAME_SCALE;
	}
*/
	for ( uint i=0; i<LavaSegments.length(); ++i )
		LavaSegments[i].self.Flags() &= ~AF_HIDDEN;

	// cue the dramatic music
	// the immediate start of the N64 version is better, but the volume is much louder
	// so to avoid killing the ears of those using the PC version, honor their setting
	PlayMusic( 12 );

	LavaState = Lava_Active;
}
