
float ElevatorZLower, ElevatorZUpper, ElevatorFloorOffset;
int8 ElevatorDir;
float ElevatorPct = 0;
bool bElevatorAccess;
float AccessMsgTimeout = 0;

float CompZ;

kActor@ Faucet, Faucet2;
array<kVec3> FaucetPoints =
{
	kVec3( 337.919983f, 2176, -1000.959961f ),
	kVec3( -3161.599854f, -1881.599976f, -867.839966f ),
	kVec3( -4618.239746f, -2447.359863f, -1326.079956f ), // common shower linear sound point 1
	kVec3( -4968.959961f, -2196.479980f, -1326.079956f )  // common shower linear sound point 2
};

void InitOutpost()
{
	// check if the player canceled lockdown
	kActor@ actor = World.GetActorByTID( TID_Outpost_Button );
	if ( actor !is null && actor.IsPersistentMarked() )
		CancelLockdown();
	else
		OpenOutpostDoor( TID_Outpost_Door_Back );

	// check if commander is alive
	@actor = World.GetActorByTID( TID_Outpost_Commander );
	bElevatorAccess = actor is null || actor.IsStale();

	// set berzerker face to crazyman
//	@actor = World.GetActorByTID( TID_Outpost_Berzerker );
//	if ( actor !is null && !actor.IsStale() )
//		actor.RenderModel().SetTexture( 19, 10 );
	SetFace( TID_Outpost_Berzerker, 10 );
	// set packrat face to beady-eyed angryman
//	@actor = World.GetActorByTID( TID_Outpost_Packrat );
//	if ( actor !is null && !actor.IsStale() )
//		actor.RenderModel().SetTexture( 19, 4 );
	SetFace( TID_Outpost_Packrat, 4 );
	// give overwatch snipers glowy eyes (implying augmented vision)
	for ( int i=TID_Overwatch_Sniper1; i<TID_Overwatch_Sniper_End; ++i )
		SetFace( i, 3 );

	// prevent taxidermy from bleeding
	@actor = World.GetActorByTID( TID_Outpost_Taxidermy );
	if ( actor !is null )
		actor.ImpactType() = IT_DEFAULT;

	// elevator properties
	@actor = World.GetActorByTID( TID_Outpost_Elevator_Actor );
	if ( actor !is null )
	{
		ElevatorZLower = actor.Origin().z;
		ElevatorZUpper = ElevatorZLower + actor.SpawnParams(7) * 5 * GAME_SCALE;
		ElevatorFloorOffset = actor.GetFloorHeight() - actor.Origin().z;
	}

	// move computer sounds up out of range
	@actor = World.GetActorByTID( TID_Outpost_ComputerChatter );
	if ( actor !is null )
	{
		CompZ = actor.Origin().z;
		actor.Origin().z += (ElevatorZUpper - ElevatorZLower) * 3;
	}

	// hide the secret life force pickups, if they haven't been found yet
	@actor = World.GetActorByTID( TID_SecretMarker_GuardPost );
	if ( actor is null || !actor.IsPersistentMarked() )
	{
		for ( int i=TID_SecretItem_GuardPost_Start; i<TID_SecretItem_GuardPost_End; ++i )
		{
			kActor@ A = World.GetActorByTID( i );
			if ( A !is null )
				A.Flags() |= AAF_HIDDEN;
		}
	}

	@Faucet  = Spawn( "DummyActor", Player.Actor().Origin() );
	@Faucet2 = Spawn( "DummyActor", Player.Actor().Origin() );
}

void SetFace( int TID, int face )
{
	kActor @actor = World.GetActorByTID( TID );
	if ( actor !is null && !actor.IsStale() )
		actor.RenderModel().SetTexture( 19, face );
}

bool bWasInSludge = false;

void OutpostTick()
{
	FaucetSounds();

	// check if the commander has just been killed
	if ( !bElevatorAccess )
	{
		kActor@ commander = World.GetActorByTID( TID_Outpost_Commander );
		if ( commander is null || commander.Flags() & AF_DEAD != 0 )
		{
			Player.Actor().PlaySoundWithLookupID( 389 ); // pickup twing
			bElevatorAccess = true;
			AccessMsgTimeout = 1;
		}
	}

	if ( AccessMsgTimeout > 0 )
	{
		Game.PrintHelp( "Access to\nCommander's Quarters\n- GRANTED -" );
		AccessMsgTimeout -= GAME_DELTA_TIME;
	}
/*
	if ( bWasInSludge )
	{
		if ( P.OnGround() && World.GetAreaFlags(area) & AAF_WATER == 0 )
			bWasInSludge = false;
	}
	// water height check is to prevent sound from playing at the top of slope into sludge
	else if ( World.GetAreaFlags(area) & AAF_WATER != 0 &&
		((P.OnGround() && P.Origin().z < P.GetWaterHeight() - 3*2.56f)
			|| P.GetWaterLevel() > WLT_OVER) )
	{
		// uh, ah, eagh, auh
//		int i = 2 * Math::RandMax( 2 );
//		P.PlaySound( "sounds/shaders/generic_1"+ i +"_turok_injury_"+ (i+1) +".ksnd" );
// 		switch ( Math::RandMax(3) )
// 		{
// 		case 0:
// 			P.PlaySound( "sounds/shaders/generic_10_turok_injury_1.ksnd" );
// 			break;
// 		case 1:
// 			P.PlaySound( "sounds/shaders/generic_12_turok_injury_3.ksnd" );
// 			break;
// 		case 2:
// 			P.PlaySound( "sounds/shaders/generic_13_turok_injury_4.ksnd" );
// 			break;
// 		}
		P.PlaySound( "sounds/shaders/generic_12_turok_injury_3.ksnd" );
		bWasInSludge = true;
	}*/
}

void FaucetSounds()
{
	if ( Faucet is null )
		return;
	if ( Faucet.IsStale() )
	{
		Sys.Warning( "faucet is stale" );
		@Faucet = null;
		return;
	}

	uint best = 0;
	float bestDist = ( FaucetPoints[0] - P.Origin() ).UnitSq();
	for ( uint i=1; i<FaucetPoints.length(); ++i )
	{
		float dist = ( FaucetPoints[i] - P.Origin() ).UnitSq();
		if ( dist < bestDist )
		{
			bestDist = dist;
			best = i;
		}
	}

	kStr sound;
	kVec3 mult;
	// common shower
	if ( best >= 2 )
	{
		if ( Faucet2 is null )
			return;
		if ( Faucet2.IsStale() )
		{
			Sys.Warning( "faucet2 is stale" );
			@Faucet2 = null;
			return;
		}

//		mult.Set( 2.5f, 2.5f, 2.5f );
		mult.Set( 1.5f, 1.5f, 1.5f );
		sound = "sounds/shaders/Faucet3.ksnd";

		uint i = best == 2 ? 3 : 2;
		kVec3 dif = FaucetPoints[i] - P.Origin();
		dif *= mult;
		Faucet2.Origin() = ClampToBounds( FaucetPoints[i] + dif );
		Faucet2.PlaySound( sound );
	}
	// kitchen sink
	else if ( best == 0 )
	{
		// shouldn't be audible from forest key area
		if ( !InInnerCave() )
		{
			Faucet.StopSound();
			return;
		}
		mult.Set( 4, 4, 4 );
		sound = "sounds/shaders/Faucet.ksnd";
	}
	// commander's shower
	else
	{
		mult = kVec3( 3.5f, 3.5f, 5 );
		sound = "sounds/shaders/Faucet2.ksnd";
	}
	kVec3 dif = FaucetPoints[best] - P.Origin();
	dif *= mult;
	Faucet.Origin() = ClampToBounds( FaucetPoints[best] + dif );
	Faucet.PlaySound( sound );
}

//==============================================================================
//
//    Lockdown
//
//==============================================================================

// Lockdown Begin
$script 175
{
	if ( instigator.Flags() & AF_ENTEREDAREAEVENT != 0 )
		return;

	// wake up guard around the corner
	World.TriggerActorsByTID( instigator, TID_Outpost_Guard );

	// if the player came in the front, just wake up the sentinel
	if ( instigator.Origin().x > -1890 )
	{
		World.TriggerActorsByTID( instigator, TID_Outpost_Sentinel );
		return;
	}

	// if the player came in the back, sentinel pops up from behind cover, and activates lockdown
	kActor @actor = World.GetActorByTID( TID_Outpost_Sentinel );
	// no lockdown if sentinel is dead, or if he's already made his move
	if ( actor is null || actor.Flags() & AF_ACTIVATED != 0 ) // could also check if AF_DISABLED not set, instead
		return;
	World.TriggerActorsByTID( instigator, TID_Outpost_Sentinel );
	// set to crouched fire anim so he stands up from behind the fence as he tweens to normal anim
	actor.AnimState().Set( anim_aiRangeAttack7, 0, ANF_NOINTERRUPT );

	// no lockdown if player already cancelled it
	// TODO: unpress button, and close ALL the doors, not just entrance
	@actor = World.GetActorByTID( TID_Outpost_Button );
	if ( actor !is null && actor.IsPersistentMarked() )
		return;

	// close the entrance door behind the player
	@actor = World.GetActorByTID( TID_Outpost_Door_Back );
	if ( actor is null ) return;
	// already open
	if ( actor.Flags() & AF_HIDDEN == 0 ) return;
	actor.Flags() &= ~AF_HIDDEN;
	World.FloodFillAreaFlags( actor.Origin() + kVec3(0,0,GAME_SCALE), AAF_BLOCK, true );
	actor.RenderModel().Offset().z = 40 * GAME_SCALE * actor.Scale().z;
	actor.PlaySoundWithLookupID( 461 ); // button click
	Game.CallDelayedMapScript( TID_Outpost_Lockdown_Beginning, instigator, 0 );
}

// Lockdown Beginning
$script 176
{
	// animate entrance closing
	kActor@ door = World.GetActorByTID( TID_Outpost_Door_Back );
	if ( door is null ) return;
	door.RenderModel().Offset().z -= 40 * GAME_SCALE * door.Scale().z * GAME_DELTA_TIME * 6.5f;
	if ( door.RenderModel().Offset().z < 0 )
		door.RenderModel().Offset().z = 0;
	else
		$restart;
}

// Lockdown Cancel Button
$script 177
{
	if ( !instigator.OnGround() )
		return;

	World.TriggerActorsByTID( instigator, TID_Outpost_Button );
	CancelLockdown();
}

void OpenOutpostDoor( int TID )
{
	kActor@ door = World.GetActorByTID( TID );
	if ( door is null || door.Flags() & AF_HIDDEN != 0 ) return;
	door.Flags() |= AF_HIDDEN;
	// this doesn't work for some reason
//	World.ChangeAreaFlag( door.AreaID(), AAF_BLOCK, false );
	// this works, but requires some vertical padding for some reason
	World.FloodFillAreaFlags( door.Origin() + kVec3(0,0,GAME_SCALE), AAF_BLOCK, false );
}
void CancelLockdown()
{
	for ( int i=TID_Outpost_Doors_Begin; i<TID_Outpost_Doors_End; ++i )
		OpenOutpostDoor( i );
}

//==============================================================================
//
//    Elevator
//
//==============================================================================

bool PlayDoorAnim( int doorTID, int anim )
{
	kActor@ door = World.GetActorByTID( doorTID );
	if ( door !is null ) // immediate
//	if ( door !is null && door.AnimState().Stopped() ) // strict
	{
		int playing = door.AnimState().PlayingID();
		if ( playing == anim_doorIdle )
			playing = anim_doorClose;

		if ( playing != anim )
		{
			if ( door.AnimState().Stopped() )
				door.AnimState().Set( anim, 4, 0 );
			else
				door.AnimState().Blend( anim, 4, 48, 0 );
		}
		else
//			return true; // door is done playing anim
			return door.AnimState().Stopped();
	}
	return false; // door is busy (or doesn't exist)
}

void ElevatorTransit( kActor@ elevator )
{
	ElevatorPct += GAME_DELTA_TIME * ElevatorDir / elevator.SpawnParams(4);
	elevator.Origin().z = Math::Lerp( ElevatorZLower, ElevatorZUpper,
		( 1 - Math::Cos( ElevatorPct * Math::pi ) ) / 2 );
	World.ChangeSectorHeight( elevator.SectorIndex(), elevator.Origin().z + ElevatorFloorOffset );
	if ( PlayLoop.Ticks() % 35 == 0 )
//		elevator.PlaySoundWithLookupID( 452 ); // low pitch gear clunk
		elevator.PlaySoundWithLookupID( 459 ); // lower pitch gear clunk

	// move computer sounds up out of range when the elevator is at the bottom
	kActor@ a = World.GetActorByTID( TID_Outpost_ComputerChatter );
	if ( a !is null )
		a.Origin().z = CompZ + (ElevatorZUpper - elevator.Origin().z) * 3;
}

// Elevator Event - Lower, Near
$script 200
{
	if ( bElevatorAccess )
	{
		if ( instigator.Flags() & AF_ENTEREDAREAEVENT == 0 )
			ElevatorDir = 1;
		PlayDoorAnim( TID_Outpost_Elevator_Door_Lower, anim_doorOpen );
	}
	else
	{
		if ( instigator.Flags() & AF_ENTEREDAREAEVENT == 0 )
		{
			Game.PrintHelp( "Access to\nCommander's Quarters\nis restricted" );
			instigator.PlaySound( "sounds/shaders/SadBoops.ksnd" );
		}
	}
}

// Elevator Event - Lower, Far
$script 201
{
	if ( instigator.Flags() & AF_ENTEREDAREAEVENT == 0 )
		PlayDoorAnim( TID_Outpost_Elevator_Door_Lower, anim_doorClose );
}

// Elevator Event - Upper, Near
$script 202
{
	if ( instigator.Flags() & AF_ENTEREDAREAEVENT == 0 )
		ElevatorDir = -1;
	PlayDoorAnim( TID_Outpost_Elevator_Door_Upper, anim_doorOpen );
}

// Elevator Event - Upper, Far
$script 203
{
	if ( instigator.Flags() & AF_ENTEREDAREAEVENT == 0 )
		PlayDoorAnim( TID_Outpost_Elevator_Door_Upper, anim_doorClose );
}

// Elevator Event - Shaft
$script 204
{
	kActor@ elevator = World.GetActorByTID( TID_Outpost_Elevator_Actor );
	if ( elevator is null ) return;
	kActor@ door;
	int anim;

	// going up
	if ( ElevatorDir > 0 )
	{
		// open the door at the top
		if ( elevator.Origin().z >= ElevatorZUpper )
			PlayDoorAnim( TID_Outpost_Elevator_Door_Upper, anim_doorOpen );
		// close the door at the bottom
		else if ( elevator.Origin().z <= ElevatorZLower )
		{
			if ( PlayDoorAnim( TID_Outpost_Elevator_Door_Lower, anim_doorClose ) )
				ElevatorTransit( elevator );
		}
		else
			ElevatorTransit( elevator );
	}
	// going down
	else
	{
		// close the door at the top
		if ( elevator.Origin().z >= ElevatorZUpper )
		{
			if ( PlayDoorAnim( TID_Outpost_Elevator_Door_Upper, anim_doorClose ) )
				ElevatorTransit( elevator );
		}
		// open the door at the bottom
		else if ( elevator.Origin().z <= ElevatorZLower )
			PlayDoorAnim( TID_Outpost_Elevator_Door_Lower, anim_doorOpen );
		else
			ElevatorTransit( elevator );
	}
}

//==============================================================================
//
//    Overwatch
//
//==============================================================================

// Sniper Fodder
$script 255
{
	if ( instigator.Flags() & AF_ENTEREDAREAEVENT != 0 )
		return;
	for ( int i=TID_Overwatch_SniperFodder1; i<TID_Overwatch_SniperFodder_End; ++i )
	{
		kActor@ actor = World.GetActorByTID( i );
		if ( actor is null ) continue;
		World.TriggerActorsByTID( instigator, i );
		// prevent suspending AI when far away
		actor.Flags() |= AF_ALWAYSACTIVE;
		// and make sure it isn't already suspended
		actor.Flags() &= ~AF_DORMANT;
	}
}

//==============================================================================
//
//    Secrets
//
//==============================================================================

// guard post roof
$script 1100
{
	if ( instigator.Flags() & AF_ENTEREDAREAEVENT != 0 )
		return;
	World.TriggerActorsByTID( instigator, TID_SecretMarker_GuardPost );
	for ( int i=TID_SecretItem_GuardPost_Start; i<TID_SecretItem_GuardPost_End; ++i )
	{
		kActor@ A = World.GetActorByTID( i );
		if ( A !is null )
			A.Flags() &= ~AAF_HIDDEN;
	}
}
