
float RiverZUpper = -1372.159912f;
float RiverZLower = -1597.439941f;
float RiverZFloor = -2150.399902f;
float RiverSlopeYUpper = 1229;
float RiverSlopeYLower = 2427;
// properties of the first sloped part of lower waterfall (the second segment is further out than the water sectors)
float WaterfallZ = -1684.479980f;
float WaterfallSlopeYUpper = 5191.679688f;
float WaterfallSlopeYLower = 5404.159668f;
float WaterfallYOuter = 5498.879883f;

kVec3 WaterfallUpperLeft(   3460, -1880, -1375 );
kVec3 WaterfallUpperRight(  2385, -1880, -1375 );
kVec3 WaterfallUpperMiddle( (WaterfallUpperLeft + WaterfallUpperRight) / 2 );
kVec3 WaterfallLowerSoundLeft(   4690, 5200, -2045 );
kVec3 WaterfallLowerSoundRight(  2250, 5200, -2045 );
kVec3 WaterfallLowerSoundMiddle( (WaterfallLowerSoundLeft + WaterfallLowerSoundRight) / 2 );
kVec3 RiverLowerLeft(   WaterfallLowerSoundLeft.x,   WaterfallLowerSoundLeft.y,   RiverZLower );
kVec3 RiverLowerRight(  WaterfallLowerSoundRight.x,  WaterfallLowerSoundRight.y,  RiverLowerLeft.z );
kVec3 RiverLowerMiddle( WaterfallLowerSoundMiddle.x, WaterfallLowerSoundMiddle.y, RiverLowerLeft.z );

float OutdoorSoundFade = 0;
kActor@ WaterfallSound;
kActor@ DistantWaterfall;
kActor@ RiverSoundArea, RiverSoundLinear;

//==============================================================================
//
//    Init / Tick
//
//==============================================================================

void InitRiver()
{
	@WaterfallSound   = Spawn( "DummyActor", Player.Actor().Origin() );
	@DistantWaterfall = Spawn( "DummyActor", Player.Actor().Origin() );
	@RiverSoundArea   = Spawn( "DummyActor", Player.Actor().Origin() );
	@RiverSoundLinear = Spawn( "DummyActor", Player.Actor().Origin() );
}

void RiverTick()
{
	RiverSlope();

	if ( World.GetAreaFlags(area) & AAF_CEILING == 0 )
	{
		if ( P.GetWaterLevel() > WLT_OVER ||
			// keep forcing velocity after player is thrown from lower waterfall
			// to keep them from air controlling back into the water
			(P.Origin().y > WaterfallSlopeYUpper && P.PlayerFlags() & PF_NOAIRFRICTION != 0) )
				RiverCurrent();
	}

	RiverSounds();
}

//==============================================================================
//
//    Slope / Current
//
//==============================================================================

void RiverSlope()
{
	// middle slope
	// 0 when player is at the top of the slope, 1 when player is at the bottom
	slopePct = (P.Origin().y - RiverSlopeYUpper) / (RiverSlopeYLower - RiverSlopeYUpper);
	// approximate the curve
	float f = Math::FClamp( slopePct, 0, 1 );
	f = Math::Cos( f * Math::pi );
	f = (1-f) / 2;
	// calculate the height at player's location
	height = Math::Lerp( RiverZUpper, RiverZLower, f );
	for ( int i=TID_RiverProbe_Slope_Begin; i<TID_RiverProbe_Slope_End; ++i )
	{
		kActor@ probe = World.GetActorByTID( i );
		if ( probe !is null )
			World.ChangeAreaWaterHeight( probe.AreaID(), height );
	}

	// waterfall slope
	f = (P.Origin().y - WaterfallSlopeYUpper) / (WaterfallSlopeYLower - WaterfallSlopeYUpper);
	f = Math::FClamp( f, 0, 1 );
	height = Math::Lerp( RiverZLower, WaterfallZ, f );
	for ( int i=TID_RiverProbe_Waterfall_Begin; i<TID_RiverProbe_Waterfall_End; ++i )
	{
		kActor@ probe = World.GetActorByTID( i );
		if ( probe !is null )
			World.ChangeAreaWaterHeight( probe.AreaID(), height );
	}
}

// TODO: sometimes going down the slope is choppy (need to lower the player to keep them nicely in the water?)
void RiverCurrent()
{
	// try to avoid catapulting the player *too* much from the waterfall
	if ( P.Origin().y >= WaterfallYOuter )
	{
		if ( P.AnimState().IsPlaying(anim_player_falling) )
			P.Velocity().x = P.Velocity().y = 0;
		return;
	}

	float current = 2;

	float heightPct = Math::FClamp( (P.Origin().z - RiverZFloor) / (height - RiverZFloor), 0, 1 );
	heightPct *= heightPct;

	// increase current in the middle of the slope
	// start 5% before; end 50% after
	float f = Math::FClamp( (slopePct+0.05f) / 1.55f, 0, 1 );
	float bonus = 2 * Math::Sin( f * Math::pi );

	// increase current near the top waterfall
	if ( P.Origin().y < -1075 )
		bonus += 1 - Math::FClamp( (P.Origin().y+1889) / (-1075+1889), 0, 1 );

	// apply bonuses (but only near the surface)
	current *= 1 + bonus * heightPct;

	// start pulling really hard toward the killer waterfall
	if ( P.Origin().y > 3543 )
		current *= 1 + 3 * Math::FClamp( (P.Origin().y-3543) / (5376-3543), 0, 1 );

	// decrease current at riverbed
	current *= ( 1 + heightPct ) / 2;

	kVec3 oldLoc = P.Origin();
	kVec3 newLoc = P.Origin() + kVec3( 0, current, 0 );
	// this fucks up when passing over cliffs
//	P.MoveToPosition( newLoc.x, newLoc.y );
	// this sometimes fucks up when hitting walls (fixed with CheckPosition())
	// this seems to deposite you out of the water at the lower waterfall more cleanly than SetPosition()
	if ( P.CheckPosition(newLoc) )
		P.Origin().y += current;
	// when hitting a wall, this snaps you to the top
//	P.SetPosition( newLoc );
	// this fixes nothing
//	if ( P.Origin().x != newLoc.x || P.Origin().z != newLoc.z || P.Origin().z != newLoc.z )
//		P.Origin() = oldLoc;

//	if ( P.GetWaterLevel() == WLT_INVALID )
	{
//		Sys.Print( "thrown off cliff" );
		P.Velocity().y = current;
		P.PlayerFlags() |= PF_NOAIRFRICTION;
	}
}

//==============================================================================
//
//  Sounds
//
//==============================================================================

void RiverSounds()
{
	if ( InInnerCave() )
	{
		OutdoorSoundFade = Math::Lerp( OutdoorSoundFade, 1, GAME_DELTA_TIME );
		if ( OutdoorSoundFade > 0.9f )
		{
			WaterfallSound.StopSound();
			DistantWaterfall.StopSound();
			RiverSoundArea.StopSound();
			RiverSoundLinear.StopSound();
			return;
		}
	}
	else
		OutdoorSoundFade = Math::Lerp( OutdoorSoundFade, 0, GAME_DELTA_TIME*2 );
	float offset = 3072 * OutdoorSoundFade;

	// Waterfall, Upper
	if ( P.Origin().y < (WaterfallUpperLeft.y + WaterfallLowerSoundLeft.y) / 2 )
		LinearSound( WaterfallUpperLeft, WaterfallUpperRight, WaterfallSound, "sounds/shaders/waterfall_rush.ksnd" );
	// Waterfall, Lower, Main
	else
		LinearSound( WaterfallLowerSoundLeft, WaterfallLowerSoundRight, WaterfallSound, "sounds/shaders/WaterfallHuge.ksnd" );
	WaterfallSound.Origin().x += offset;

	// Waterfall, Lower, Distant
	DistantWaterfall.Origin() = ( WaterfallLowerSoundMiddle + P.Origin() ) / 2 + kVec3( offset, 0, 0 );
	DistantWaterfall.PlaySound( "sounds/shaders/WaterfallHugeDistant.ksnd" );

	// River, Omni
	AreaSound( RiverLowerLeft, RiverLowerRight, WaterfallUpperLeft, WaterfallUpperRight, RiverSoundArea, "sounds/shaders/River.ksnd" );
	RiverSoundArea.Origin().x += offset;
	// River, Central
	LinearSound( RiverLowerMiddle, WaterfallUpperMiddle, RiverSoundLinear, "sounds/shaders/River.ksnd" );
	RiverSoundLinear.Origin() = ( RiverSoundLinear.Origin() + P.Origin() ) / 2 + kVec3( offset, 0, 0 );
}

void LinearSound( kVec3&in v1, kVec3&in v2, kActor@ source, const kStr&in sound )
{
	kVec3 line = v2 - v1;
	float length = line.Unit();
	kVec3 dir = line / length;
	float dot = ( Player.Actor().Origin() - v1 ).Dot( dir );
	source.Origin() = v1 + dir * Math::FClamp( dot, 0, length );
	source.PlaySound( sound );
}

void AreaSound( kVec3&in v11, kVec3&in v12, kVec3&in v21, kVec3&in v22, kActor@ source, const kStr&in sound )
{
	float pct = ( Player.Actor().Origin().y - v11.y ) / ( v21.y - v11.y );
	pct = Math::FClamp( pct, 0, 1 );
	kVec3 v1 = v11;
	v1.Lerp( v21, pct );
	kVec3 v2 = v12;
	v2.Lerp( v22, pct );
	LinearSound( v1, v2, source, sound );
}
