
float CliffHeight       = 60*GAME_SCALE;    // height of cliff static meshes at default 0.35 scale
float IslandStairHeight = 30*GAME_SCALE/4;
float IslandHeight      = 19*GAME_SCALE/2;  // z offset from first to second level of island
float IslandElevation;  // -1: lowered, [0,1): rising, 1: raised
float IslandPitchPhase, IslandRollPhase;

void InitIsland()
{
	int keys = GetGameVarI("hubPanelPieces1");

	// set up plaque keys
	kActor@ A = World.GetActorByTID( TID_Island_Plaque );
	if ( A !is null )
	{
		for ( int i=0; i<3; ++i )
		{
			A.RenderModel().SetTexture( i+1, 0 ); // set key texture
			A.RenderModel().SetVisibility( i+1, keys & (1<<i) != 0 ); // hide unless plugged in
		}
	}

	// set up portal gate emblems
	@A = World.GetActorByTID( TID_Island_Gate );
	if ( A !is null )
	{
		for ( int i=0; i<3; ++i )
			A.RenderModel().SetTexture( i+1, 7 * Int( keys & (1<<i) == 0 ) );
	}

	// turn off the teleporter if not all keys are plugged in
	if ( keys != 7 )
	{
		@A = World.GetActorByTID( TID_Island_Portal );
		if ( A !is null )
		{
			A.Flags() &= ~AF_ACTIVATED;
			A.Flags() |= AF_HIDDEN;
			World.ChangeAreaFlag( A.AreaID(), AAF_TELEPORT | AAF_EVENT, false );
		}
	}

	// lower island if button is not pressed
	@A = World.GetActorByTID( TID_Island_Button );
	if ( A is null || !A.IsPersistentMarked() )
	{
		// lower the island into the riverbed
		IslandElevation = 0;
		ApplyIslandElevation();
		// indicate that it's lowered, and not in motion
		IslandElevation = -1;
	}
	else
		IslandElevation = 1;
}

// Island Button
$script 400
{
	if ( !instigator.OnGround() || IslandElevation >= 0 )
		return;
	World.TriggerActorsByTID( instigator, TID_Island_Button );
	IslandElevation = 0;
}

void IslandTick()
{
	if ( IslandElevation < 0 || IslandElevation >= 1 )
		return;

	IslandElevation = Math::OLDMin( IslandElevation + GAME_DELTA_TIME / 10, 1 );
//	float speed = GAME_DELTA_TIME * Math::Lerp( 0.5f, 0.01f, Math::FakeSqrt( IslandElevation ) );
//	IslandElevation = Math::OLDMin( IslandElevation + speed, 1 );
	ApplyIslandElevation();
}

void SetIslandSectorWaterFlag( kActor@ actor, float floorZ )
{
	bool bWater = floorZ < actor.GetWaterHeight();
	World.ChangeAreaFlag( actor.AreaID(), AAF_WATER, bWater );
	// prevent water current from continuing to affect player
	if ( !bWater && P.AreaID() == actor.AreaID() && P.OnGround() )
		P.PlayerFlags() &= ~PF_NOAIRFRICTION;
}

void SetIslandSectorZ( kActor@ actor, float floorZ, float limit = RiverZFloor )
{
//	if ( z < RiverZFloor )
//		z = RiverZFloor;
	if ( floorZ < limit )
		floorZ = limit;
	World.ChangeSectorHeight( actor.SectorIndex(), floorZ );
//	World.ChangeAreaFlag( actor.AreaID(), AAF_WATER, floorZ < actor.GetWaterHeight() );
	SetIslandSectorWaterFlag( actor, floorZ );
}

void ApplyIslandElevation()
{
	float z = Math::Lerp( RiverZFloor - 1.75f*CliffHeight, RiverZFloor,
//		( 1 - Math::Cos( IslandElevation * Math::pi ) ) / 2 );
//		Math::Sin( IslandElevation * Math::pi/2 ) );
		Math::FakeSqrt( IslandElevation ) );
//		Math::Pow( IslandElevation, 1.0f/3.0f ) );
//	float z = Math::Lerp( RiverZFloor - 1.75f*CliffHeight, RiverZFloor, IslandElevation );

	// shake player's view (based on quakeSource, but smooth rather than jitter every 4 frames)
	if ( P !is null )
	{
		kVec3 v = P.Origin() - kVec3(3600,4160,-1540);
		v.z = 0;
		float dist = Math::OLDMax( v.Unit() - 600, 0 ); // island radius is ~600
		dist = Math::OLDMax( 1024 - dist, 0 ) / 768; // quakeSource seems to use 256, but that seems really strong?
		float pct = 1 - IslandElevation * IslandElevation;
		float strength = pct * Math::Deg2Rad( 1 ) * dist;
		float speed    = 1;//Math::FakeSqrt( pct + 1 / 2 );
		IslandPitchPhase += 90 * GAME_DELTA_TIME * speed * Math::RandFloat();
		IslandRollPhase  += 90 * GAME_DELTA_TIME * speed * Math::RandFloat();
		P.RecoilPitch() = Math::Sin( IslandPitchPhase ) * strength;
		P.Roll()        = Math::Sin( IslandRollPhase )  * strength;
	}

	// first level
	kActor@ actor;
	float sectorZ;
	for ( int i=TID_Island_Cliff1_Start; i<TID_Island_Cliff1_End; ++i )
	{
		@actor = World.GetActorByTID( i );
		if ( actor is null ) continue;
		actor.Origin().z = z;
		if ( IslandElevation < 0.9f + (i%2)*0.1f )
		{
			actor.PlaySoundWithLookupID(523); // deep rumble
			actor.PlaySoundWithLookupID(526); // stone rumble
		}
		else
			actor.StopSound();
		// use first concave piece as sector probe
		if ( i == TID_Island_Cliff1_Start+1 )
			SetIslandSectorZ( actor, z + CliffHeight );
	}
	for ( int i=TID_Island_Floor1_Start; i<TID_Island_Floor1_End; ++i )
	{
		@actor = World.GetActorByTID( i );
		if ( actor is null ) continue;
		actor.Origin().z = z + CliffHeight;
		// handle water flag for stair slopetest sectors
		switch ( i )
		{
			case TID_Island_Stairs:
//				World.ChangeAreaFlag( actor.AreaID(), AAF_WATER, actor.Origin().z < actor.GetWaterHeight() );
				SetIslandSectorWaterFlag( actor, actor.Origin().z );
				break;
			case TID_Island_Stair_Top3:
//				World.ChangeAreaFlag( actor.AreaID(), AAF_WATER,
//					actor.Origin().z + IslandStairHeight < actor.GetWaterHeight() );
				SetIslandSectorWaterFlag( actor, actor.Origin().z + IslandStairHeight );
		}
	}
	for ( int i=TID_Island_Rock_Peak_Start; i<TID_Island_Rock_Peak_End; ++i )
	{
		@actor = World.GetActorByTID( i );
		if ( actor !is null )
			SetIslandSectorZ( actor, z+CliffHeight, actor.Health() );
	}
	for ( int i=TID_Island_Rock_Body_Start; i<TID_Island_Rock_Body_End; ++i )
	{
		@actor = World.GetActorByTID( i );
		if ( actor !is null )
//			World.ChangeAreaFlag( actor.AreaID(), AAF_WATER, z+CliffHeight < actor.GetWaterHeight() );
			SetIslandSectorWaterFlag( actor, z+CliffHeight );
	}

	// top of stairs
	@actor = World.GetActorByTID( TID_Island_Stair_Probe );
	if ( actor !is null )
		SetIslandSectorZ( actor, z + CliffHeight + IslandStairHeight );

	// second level
	z += IslandHeight;
	for ( int i=TID_Island_Cliff2_Start; i<TID_Island_Cliff2_End; ++i )
	{
		@actor = World.GetActorByTID( i );
		if ( actor is null ) continue;
		actor.Origin().z = z;
		// use concave piece as sector probe
		if ( i == TID_Island_Cliff2_End-2 )
			SetIslandSectorZ( actor, z + CliffHeight );
	}
	z += CliffHeight;
	for ( int i=TID_Island_Floor2_Start; i<TID_Island_Floor2_End; ++i )
	{
		@actor = World.GetActorByTID( i );
		if ( actor !is null )
			actor.Origin().z = z;
	}

	// gate
	@actor = World.GetActorByTID( TID_Island_Gate );
	if ( actor !is null )
	{
		actor.Origin().z = z - GAME_SCALE/4; // gate mesh is a tiny bit above its origin
		// handle water flag for teleport sector
//		World.ChangeAreaFlag( actor.AreaID(), AAF_WATER, z < actor.GetWaterHeight() );
		SetIslandSectorWaterFlag( actor, z );
	}
	for ( int i=TID_Island_Gate_Probe1; i<=TID_Island_Gate_Probe2; ++i )
	{
		@actor = World.GetActorByTID( i );
		if ( actor !is null )
			SetIslandSectorZ( actor, z + 121*GAME_SCALE/4 );
	}

	// plaque
	@actor = World.GetActorByTID( TID_Island_Plaque );
	if ( actor !is null )
	{
		actor.Origin().z = z - 3*GAME_SCALE; // plaque mesh is above its origin
		SetIslandSectorZ( actor, z + 23*GAME_SCALE/4 );
	}
	// handle plaque event sector flags
	@actor = World.GetActorByTID( TID_Island_Plaque_Probe );
	if ( actor !is null )
	{
//		World.ChangeAreaFlag( actor.AreaID(), AAF_WATER, z < actor.GetWaterHeight() );
		SetIslandSectorWaterFlag( actor, z );
		World.ChangeAreaFlag( actor.AreaID(), AAF_EVENT, z > actor.GetWaterHeight() );
	}
}

// Plaque Event
$script 403
{
	if ( !instigator.OnGround() )
		return;

	kActor@ A;
	kStr secrets = "Secrets Found:\n \n";
	for ( int i=TID_SecretMarker_Start; i<TID_SecretMarker_End; ++i )
	{
		if ( i > TID_SecretMarker_Start )
			secrets += " ";
		@A = World.GetActorByTID( i );
		if ( A is null || !A.IsPersistentMarked() )
			secrets += "-";
		else
			secrets += "" + (i - TID_SecretMarker_Start + 1);
	}
	Game.PrintHelp( secrets + "\n " );

	// check if more keys have been found
	int found, remaining, bits;
	if ( !Game.GetHubKeyInfo( 2, found, remaining, bits ) || bits == GetGameVarI("hubPanelPieces1") )
		return;
	GameVariables.SetValue( "hubPanelPieces1", ""+bits );

	// plug keys into the plaque
	@A = World.GetActorByTID( TID_Island_Plaque );
	if ( A !is null )
	{
		A.PlaySound( "sounds/shaders/health_pickup_1.ksnd" );
		for ( int i=0; i<3; ++i )
			if ( bits & (1<<i) != 0 )
				A.RenderModel().SetVisibility( i+1, true );
	}

	// light emblems on the gate
	@A = World.GetActorByTID( TID_Island_Gate );
	if ( A !is null )
	{
		for ( int i=0; i<3; ++i )
			A.RenderModel().SetTexture( i+1, 7 * Int( bits & (1<<i) == 0 ) );
	}

	// activate portal if all keys are in
	if ( bits == 7 )
	{
		@A = World.GetActorByTID( TID_Island_Portal );
		if ( A !is null )
		{
			A.Flags() |= AF_ACTIVATED;
			// unhiding flashes the portal for a frame before fade in actor fx kicks in, so delay this by a frame
			Game.CallDelayedMapScript( TID_Island_Portal_Unhide, instigator, GAME_DELTA_TIME*1.1f );
			A.RunFxEvent( "Portal_Init" );
			World.ChangeAreaFlag( A.AreaID(), AAF_EVENT, true );
		}
	}
}

// Portal Unhide
$script 406
{
	kActor@ portal = World.GetActorByTID( TID_Island_Portal );
	if ( portal !is null )
		portal.Flags() &= ~AF_HIDDEN;
}

// Portal Teleport
$script 407
{
	if ( instigator.Flags() & AF_ENTEREDAREAEVENT == 0 )
	{
		Camera.StartCinematic( CMF_LOCK_PLAYER|CMF_NO_LETTERBOX|CMF_SHOW_CREDITS );
		Camera.ClearLookAtActor();
		Camera.ClearFinalView();
		Camera.ClearViewTracks();
		Camera.fov = 74;
		Camera.origin.Set( 2400, -500, -1320 );
		Camera.pitch = Math::Deg2Rad( -20 );
		Camera.yaw   = Math::Deg2Rad( 165 );
	}
	else if ( Camera.UserInterrupted() )
	{
		Game.Restart();
		return;
	}
	// after fadeout, move player to camera's position, so sounds and particles will reflect camera location
	else if ( Camera.CinematicState() == CS_BEGIN_FADEIN )
	{
		Player.Actor().Origin() = Camera.origin;
		Player.Actor().Pitch()  = Camera.pitch;
		Player.Actor().Yaw()    = Camera.yaw;
	}
	$restart;
}
