#include common_scripts\utility;
#include maps\mp\_utility;
#include maps\mp\_art;

CONST_SWIMMING_DEPTH = 48;

init()
{
	/*---HOW TO SET UP----/
	
	In Script:
	 * add this line to your map.gsc main() function: maps\mp\_water::init();
	 * Add this to your level.csv file:
	 	* include,_water
	In Radiant:
	 * In your level in radiant add a trigger_multiple with a KVP of: "targetname" / "trigger_underwater". Place the trigger where you want the player to be considered touching the water.
	 * give the trigger_multiple a KVP of: "script_noteworthy" / "<name>". Name can be anything.
	 * link the trigger_multiple to a script struct. Place the script struct at the waterline.
	 * In your level in radiant add a client_trigger that encompasses the water (about 8 units below the ground and about 64 units above the waterline). Give it a KVP of "script_water" / <name>. the name needs to match the script_noteworthy of the trigger_multiple.
	
	
	/----OPTIONAL----/
	
	Customize: the lightset, fogset, vfx, sound fx, visionset, and depth of field. To do this:
		* Settings for the water triggers should be added to the <mapname>_water.csv file.  That csv file should be included in <mapname>.csv as a stringtable type.
			* stringtable,maps/createart/<mapname>_water.csv
			* You can copy maps/createart/default_water.csv to start with and add rows as needed. Please make a copy and do not edit the default as it would effect other levels. The name column should be filled in with whatever you used for the script_water KVP in the client trigger.
		* Columns:
			* plunge vfx/sfx - The vfx/sfx to play when going under the water.
			* emerge vfx/sfx - The vfx/sfx to play when coming out of the water.
			* bubble vfx - The vfx to play while underwater.
			* bubble update ms - The bubble vfx play over and over, this is the time in mS between successive plays of the vfx.
			* breath sfx - The "player breathing heavily" sound to play when emerging.
			* underwater vision set - The name of the vision set to apply when underwater. (from <mapname>_fog.gsc and <mapname>_fog_hdr.gsc files).
			* underwater light set - The name of the light set to apply when underwater. ( from <mapname>_lightsets_hdr.gsc )
			* dof* - The depth of field settings to apply when underwater.
	
	Moving water:
	 * If your water is animated or moves you can link the waterline ent(s) to the animated or moving water.
	 * The water ents are stored in level.waterline_ents which is an array of tag_origin script models that are created in script based on the location of the script_struct you place in radiant. 
	 * To identify a specific one use the .script_noteworthy of one of the waterline_ents in the array. The script noteworthy of it will match the script noteworthy of the trigger_multiple you placed in radiant in step 2 above.
	
	*TODO: make swimming better, and add dolphin death! (for map boundaries or map killstreak)
	/---------------------------------------------------------------------------------------------------------------------*/
	
	level._effect[ "water_wake" ]			                                             = loadfx( "vfx/treadfx/body_wake_water" );
	level._effect[ "water_wake_stationary" ]			                           = loadfx( "vfx/treadfx/body_wake_water_stationary" );
	level._effect[ "water_splash_emerge" ]	                                       = loadfx( "vfx/water/body_splash_exit" );
	level._effect[ "water_splash_enter" ]	                                         = loadfx( "vfx/water/body_splash" );
	
	PreCacheShellShock( "underwater" );
	
	if( !isDefined( level.waterline_ents ) )
		level.waterline_ents = [];
	
	if( !isDefined( level.waterline_offset ) )
		level.waterline_offset = 0;
	if( !isDefined( level.shallow_water_weapon ) )
		SetShallowWaterWeapon( "iw5_combatknife_mp" );
	if( !isDefined( level.deep_water_weapon ) )
		SetDeepWaterWeapon( "iw5_underwater_mp" );
	if( !isDefined( level.allow_swimming ) )
		level.allow_swimming = true;
	if( level.deep_water_weapon == level.shallow_water_weapon )
		level.allow_swimming = false;
	if( !isDefined( level.swimming_depth ) )
	   level.swimming_depth = CONST_SWIMMING_DEPTH;
	
	triggers = getentarray( "trigger_underwater", "targetname" );
	level.water_triggers = triggers;
	
	foreach( trig in triggers )
	{
		trig create_clientside_water_ents();
		trig thread watchPlayerEnterWater();
		level thread clearWaterVarsOnSpawn( trig );
	}
	
	level thread OnPlayerConnectFunctions();
}

player_set_in_water( in_water )
{
	if ( in_water )
	{
		self.inWater = true;
		if ( IsAIGameParticipant( self ) )
			self BotSetFlag("in_water",true);
	}
	else
	{
		self.inWater = undefined;
		if ( IsAIGameParticipant( self ) )
			self BotSetFlag("in_water",false);
	}
}

OnPlayerConnectFunctions()
{
	level endon( "game_ended" );
	
	while( true )
	{
		level waittill( "connected", player );
		
		foreach( waterline_ent in level.waterline_ents )
		{
			player InitWaterClientTrigger( waterline_ent.script_noteworthy, waterline_ent );
		}
	}
}

create_clientside_water_ents()
{
	water_ent_struct = getstruct( self.target, "targetname" );
	AssertEx( isDefined( water_ent_struct ), "waterline needs to be defined. Place a script_struct at the height of the waterline and link your underwater trigger to it." );
	
	water_ent_struct.origin = water_ent_struct.origin + (0,0,level.waterline_offset);
	
	waterline_ent = water_ent_struct spawn_tag_origin();
	waterline_ent show();
	
	if( isDefined( self.script_noteworthy ) )
	{
		waterline_ent.script_noteworthy = self.script_noteworthy;
		level.waterline_ents = array_add( level.waterline_ents, waterline_ent );
	}
}

clearWaterVarsOnSpawn( underWater )
{
	level endon ( "game_ended" );

	while( true )
	{
		level waittill( "player_spawned", player );

		if ( !player IsTouching( underWater ) )
		{
			player player_set_in_water(false);
			player.underWater = undefined;
			player.inThickWater = undefined;
			player.isSwimming = undefined;
			player.isWading = undefined;
			player.water_last_weapon = undefined;
			player.isShocked = undefined;
			player notify( "out_of_water" );
		}
	}
}

watchPlayerEnterWater()
{
	level endon( "game_ended" );

	while( true )
	{
		self waittill( "trigger", ent );

		if( IsDefined(level.isHorde) && level.isHorde && IsAgent( ent ) && IsDefined( ent.horde_type ) && ent.horde_type == "Quad" && !isDefined(ent.inWater))
			ent thread hordeDogInWater( self );
					   
		if ( !IsPlayer( ent ) && !IsAI(ent) )
			continue;
		
		if ( !isAlive( ent ) )
			continue;

		if ( !isDefined( ent.inWater ) )
		{
			ent player_set_in_water(true);
			ent thread playerInWater( self );
		}
	}
}

hordeDogInWater(trig)
{
	level endon( "game_ended" );
	self endon( "death" );
	self endon( "disconnect" );
	self player_set_in_water(true);
	
	while( 1 )
	{
		if (!self inShallowWater( trig, 40 ))
		{
			wait 2.5;
			if( !self inShallowWater( trig, 20 ))
				self DoDamage( self.health, self.origin );	
		}
		waitframe();
	}
}

playerInWater( underWater )
{
	level endon( "game_ended" );
	self endon( "death" );
	self endon( "disconnect" );
	
	self thread inWaterWake(underWater);
	self thread playerWaterClearWait();
	
	self.eyeHeightLastFrame = 0;
	self.eye_velocity = 0;
	
	//note: go from out of the water to in for the order in the if statements below
	while( true )
	{
		if ( self maps\mp\_utility::isUsingRemote() )
		{
			if ( isDefined( self.underWater ) && isDefined( self.isShocked ) )
			{
				self StopShellShock();			
				self.isShocked = undefined;
			}
		}
		else
		{
			if ( isDefined( self.underWater ) && !IsDefined( self.isShocked ) )
			{
				self shellShock( "underwater", 19 );
				self.isShocked = true;
			}
		}
		
		//player is not touching the water
		if ( !self IsTouching( underWater ) )
		{
			self player_set_in_water(false);
			self.underWater = undefined;
			self.inThickWater = undefined;
			self.isSwimming = undefined;
			self.moveSpeedScaler = level.basePlayerMoveScale;
			self maps\mp\gametypes\_weapons::updateMoveSpeedScale();
			self notify( "out_of_water" );
			break;
		}
		
		//player is walking in shallow water after being in thick water
		if( isDefined( self.inThickWater ) && self inShallowWater( underwater, 32 ) )
		{
			self.inThickWater = undefined;
			self.moveSpeedScaler = level.basePlayerMoveScale;
			self maps\mp\gametypes\_weapons::updateMoveSpeedScale();
		}
		
		//player is walking in thick water
		if( !isDefined( self.inThickWater ) && !self inShallowWater( underwater, 32 ) )
		{
			self.inThickWater = true;
			self.moveSpeedScaler = .7*level.basePlayerMoveScale;
			self maps\mp\gametypes\_weapons::updateMoveSpeedScale();
		}
		
		//player's eye is under water
		if ( !isDefined( self.underWater ) && !self isAboveWaterLine( underWater, 0 ) )
		{
			self.underWater = true;
			self thread playerHandleDamage();
			if( isAugmentedGameMode() )
				self DisableExo();
			if ( !self maps\mp\_utility::isUsingRemote() )
			{
				self shellShock( "underwater", 19 );
				self.isShocked = true;
			}
						
			currentWeapon = self GetCurrentWeapon();
			if ( currentWeapon != "none" && WeaponInventoryType( currentWeapon ) == "primary" )
			{
				self.water_last_weapon = currentWeapon;
			}
								
			if(IsDefined(level.gamemodeOnUnderWater))
				self [[level.gamemodeOnUnderWater]](underWater);
			self maps\mp\killstreaks\_coop_util::playerStopPromptForStreakSupport();
		}
		
		//player's eye is underwater and player's in shallow water or prone or is not allowed to swim
		if ( isDefined( self.underWater ) && ( isDefined( self.isSwimming ) || !isDefined( self.isWading ) ) && ( self inShallowWater( underwater, level.swimming_depth ) || self GetStance() == "prone" || !level.allow_swimming ) )
		{
			self.isWading = true;
			self.isSwimming = undefined;
			self PlayerDisableUnderwater();
			if ( IsDefined( self.isJuggernaut ) && self.isJuggernaut == true )
			{
				self PlayerEnableUnderwater( "none" );		// Special case for when the player is using the Goliath Scorestreak.
				self AllowFire( false );		// Disable main weapons and lethal offhands. Does not include second offhand weapons.
				self DisableOffhandSecondaryWeapons();
			}
			else
			{
				self PlayerEnableUnderwater( "shallow" );
			}
		}
		
		//player's eye is underwater and player's in deep water and the player is allowed to swim
		if ( isDefined( self.underWater ) && ( isDefined( self.isWading ) || !isDefined( self.isSwimming ) ) && ( !self inShallowWater( underwater, level.swimming_depth ) && self GetStance() != "prone" && level.allow_swimming ) )
		{
			self.isSwimming = true;
			self.isWading = undefined;
			self PlayerDisableUnderwater();
			if ( IsDefined( self.isJuggernaut ) && self.isJuggernaut == true )
			{
				self PlayerEnableUnderwater( "none" );		// Special case for when the player is using the Goliath Scorestreak.
				self AllowFire( false );		// Disable main weapons and lethal offhands. Does not include second offhand weapons.
				self DisableOffhandSecondaryWeapons();
			}
			else
			{
				self PlayerEnableUnderwater( "deep" );
			}
		}
		
		//player's eye above the waterline
		if ( isDefined( self.underWater ) && self isAboveWaterLine( underWater, 0 ) )
		{
			self.underWater = undefined;
			self.isSwimming = undefined;
			self.isWading	 = undefined;		
			self notify( "above_water" );
			speed = Distance( self GetVelocity(), ( 0, 0, 0 ) );
			point = (self.origin[0], self.origin[1], getWaterLine(underwater));
			playFX( level._effect[ "water_splash_emerge" ], point, AnglesToForward((0, self.angles[1], 0) + (270, 180, 0) ) );
			if ( !self maps\mp\_utility::isUsingRemote() )
			{
				self StopShellshock();
				self.isShocked = undefined;
			}
			self PlayerDisableUnderwater();
			if( isAugmentedGameMode() )
				self EnableExo();
			self maps\mp\killstreaks\_coop_util::playerStartPromptForStreakSupport();
		}
		
		wait ( 0.05 );
	}
}

IsActiveKillstreakWaterRestricted( player )//TODO: move this check into the killstreak functions
{
	if ( IsDefined( player.killstreakIndexWeapon ) )
	{
		streakName = self.pers["killstreaks"][self.killstreakIndexWeapon].streakName;
		if ( IsDefined( streakName ) ) 
		{
			if( string_find( streakName, "turret" ) > 0 || string_find( streakName, "sentry" ) > 0 )
			{
				return true;		
			}
		}	
	}
	return false;
}

playerWaterClearWait()
{
	msg = self waittill_any_return( "death", "out_of_water" );
	
	self.underwaterMotionType = undefined;
	self.dont_give_or_take_weapon = undefined;
	self player_set_in_water(false);
	self.underWater = undefined;
	self.inThickWater = undefined;	
	self.water_last_weapon = undefined;
	self.moveSpeedScaler = level.basePlayerMoveScale;
	self maps\mp\gametypes\_weapons::updateMoveSpeedScale();
}

inWaterWake(underwater)
{
	level endon( "game_ended" );
	self endon( "death" );
	self endon( "disconnect" );
	self endon( "out_of_water" );
	
	speed = Distance( self GetVelocity(), ( 0, 0, 0 ) );
	if (speed > 90)
	{
		point = (self.origin[0], self.origin[1], getWaterLine(underwater));
		playFX( level._effect[ "water_splash_enter" ], point, AnglesToForward((0, self.angles[1], 0) + (270, 180, 0) ) );
	}

	while( true )
	{
		//loop fx based off of player speed
		velocity = self GetVelocity();
		speed = Distance( velocity, ( 0, 0, 0 ) );
		if (speed > 0)
			wait(max(( 1 - (speed / 120)),0.1) );
		else
			wait (0.3);
		
		if (speed > 5)
		{
			movementDir = VectorNormalize((velocity[0], velocity[1], 0));
			forwardVec = AnglesToForward(VectorToAngles(movementDir) + (270, 180, 0));
			point = (self.origin[0], self.origin[1], getWaterLine(underwater)) + ((speed / 4) * movementDir);
			playFX( level._effect[ "water_wake" ], point, forwardVec );
		}
		else
		{
			point = (self.origin[0], self.origin[1], getWaterLine(underwater));
			playFX( level._effect[ "water_wake_stationary" ], point, AnglesToForward((0, self.angles[1], 0) + (270, 180, 0) ) );
		}
	}	
}


playerhandleDamage()
{
	level endon( "game_ended" );
	self endon( "death" );
	self endon( "stopped_using_remote" );
	self endon( "disconnect" );
	self endon( "above_water" );
	
	self thread onPlayerDeath();
	
	wait( 13 );
	
	while( true )
	{
		if ( !IsDefined( self.isJuggernaut ) || self.isJuggernaut == false )
		{
			RadiusDamage( self.origin + anglesToForward( self.angles ) * 5, 1, 20, 20, undefined, "MOD_TRIGGER_HURT" );
		}
		
		wait( 1 );
	}
}


onPlayerDeath()
{
	level endon( "game_ended" );
	self endon( "disconnect" );
	self endon( "above_water" );	
	
	self waittill( "death" );
	
	self player_set_in_water(false);
	self.underWater = undefined;
	self.inThickWater = undefined;
	self.isSwimming = undefined;
	self.isWading = undefined;
	self.water_last_weapon = undefined;
	self.underwaterMotionType = undefined;
	self.eye_velocity = 0;
	self.eyeHeightLastFrame = 0;
	self maps\mp\killstreaks\_coop_util::playerStartPromptForStreakSupport();
}

/////////////////////////
//water check functions
/////////////////////////

inShallowWater( trig, depth )
{
	if( !isDefined( depth ) )
		depth = 32;
	if( level GetWaterLine( trig ) - self.origin[2] <= depth )
		return true;
	return false;
}

isAboveWaterLine( trig, offset )
{
	if( (self GetPlayerEyeHeight() + offset ) >= level GetWaterLine( trig ))
		return true;
	else
		return false;
}

GetPlayerEyeHeight()
{
	player_eye = self geteye();
	self.eye_velocity = player_eye[2] - self.eyeHeightLastFrame;
	self.eyeHeightLastFrame = player_eye[2];
	return player_eye[2];
}

GetWaterLine( trig )
{
	water_line_struct = getstruct( trig.target, "targetname" );//TODO: assert if the struct is not defined!
	water_line = water_line_struct.origin[2];
	
	return water_line;
}


////////////////////////
//SWIMMING
////////////////////////

PlayerEnableUnderwater( type )
{
	level endon( "game_ended" );
	
	self endon( "death" );
	self endon( "disconnect" );
	self endon( "end_swimming" );
	
	if( !isDefined( type ) )
		type = "shallow";
	
	/* Taking this out since it causes a bug where the player could switch weapons before the disableWeaponSwitch() call causing the player to get a weapon underwater;
	if( ( !self IsSwitchingWeapon() && ( type == "shallow" && CurrentWeapon == level.shallow_water_weapon ) || ( type == "deep" && CurrentWeapon == level.deep_water_weapon ) ) )
		type = "none";
	*/
	if( ( type == "shallow" && self HasWeapon( level.shallow_water_weapon ) ) || ( type == "deep" && self HasWeapon( level.deep_water_weapon ) ) )
		self.dont_give_or_take_weapon = true;
	
	switch( type )
	{
		case "deep":
			self give_water_weapon( level.deep_water_weapon );
			self SwitchToWeaponImmediate( level.deep_water_weapon );
			self.underwaterMotionType = "deep";
			break;
		case "shallow":
			self give_water_weapon( level.shallow_water_weapon );
			self SwitchToWeaponImmediate( level.shallow_water_weapon );
			self.underwaterMotionType = "shallow";
			break;
		case "none":
			self.underwaterMotionType = "none";
			break;
		default:
			self give_water_weapon( level.shallow_water_weapon );
			self SwitchToWeaponImmediate( level.shallow_water_weapon );
			self.underwaterMotionType = "shallow";
			break;
	}
	
	self DisableWeaponPickup();
	self _disableWeaponSwitch();
	self _disableOffhandWeapons();
}

playerDisableUnderwater()
{
	level endon( "game_ended" );
	
	self endon( "death" );
	self endon( "disconnect" );
	
	if( isDefined( self.underwaterMotionType ) )
	{
		type = self.underwaterMotionType;
		
		self notify( "end_swimming" );
		
		self EnableWeaponPickup();
		self _enableWeaponSwitch();
		self _enableOffhandWeapons();
		
		if ( IsDefined( self.isJuggernaut ) && self.isJuggernaut == true && IsDefined( self.heavyExoData ) )
		{
			self AllowFire( true );		// Re-enable main weapon and lethal offhands (does not include secondary offhands).
			
			// Disable lethals if the Goliath doesn't have the underbarrel rocket launcher module.
			if ( !IsDefined( self.heavyExoData.hasLongPunch ) || self.heavyExoData.hasLongPunch == false )
			{
				self DisableOffhandWeapons();
			}
			
			// Disable tacticals if the Goliath doesn't have the swarm rockets module.
			if ( !IsDefined( self.heavyExoData.hasRockets ) || self.heavyExoData.hasRockets == false )
			{
				self DisableOffhandSecondaryWeapons();
			}
			else
			{
				self EnableOffhandSecondaryWeapons();
			}
		}
		
		if( isDefined( self.water_last_weapon ) )
		{
			self switch_to_last_weapon( self.water_last_weapon );
		}
		
		switch( type )
		{
			case "deep":
				self take_water_weapon( level.deep_water_weapon );
				break;
			case "shallow":
				self take_water_weapon( level.shallow_water_weapon );
				break;
			case "none":
				break;
			default:
				self take_water_weapon( level.shallow_water_weapon );
				break;
		}
		
		self.underwaterMotionType = undefined;
		self.dont_give_or_take_weapon = undefined;
	}
}

give_water_weapon( weapon )
{
	if( !isDefined( self.dont_give_or_take_weapon ) || !self.dont_give_or_take_weapon )
		self giveWeapon( weapon );
}

take_water_weapon( weapon )
{
	if( !isDefined( self.dont_give_or_take_weapon ) || !self.dont_give_or_take_weapon )
		self takeWeapon( weapon );
}

EnableExo()
{
	self playerAllowHighJump(true);
	self playerAllowHighJumpDrop(true);
	self playerAllowBoostJump(true);
	self playerAllowPowerSlide(true);
	self playerAllowDodge(true);
}

DisableExo()
{
	self playerAllowHighJump(false);
	self playerAllowHighJumpDrop(false);
	self playerAllowBoostJump(false);
	self playerAllowPowerSlide(false);
	self playerAllowDodge(false);
}

/*
=============
///ScriptDocBegin
"Name: SetShallowWaterWeapon( weapon )"
"Summary: forces the player to use this weapon when underwater in shallow water"
"Module: Water"
"MandatoryArg: <weapon> : the weapon to use when underwater in shallow water"
"CallOn: Level"
"Example: Level SetUnderWaterWeapon( "iw5_combatknife_mp" )"
"SPMP: MP"
///ScriptDocEnd
=============
*/

SetShallowWaterWeapon( weapon )
{
	
	assertEx( isValidUnderWaterWeapon( weapon ), "weapon is not a valid underwater weapon" );

	level.shallow_water_weapon = weapon;
}

/*
=============
///ScriptDocBegin
"Name: SetDeepWaterWeapon( weapon )"
"Summary: forces the player to use this weapon when underwater in deep water"
"Module: Water"
"MandatoryArg: <weapon> : the weapon to use when underwater in deep water"
"CallOn: Level"
"Example: Level SetUnderWaterWeapon( "iw5_underwater_mp" )"
"SPMP: MP"
///ScriptDocEnd
=============
*/

SetDeepWaterWeapon( weapon )
{
	assertEx( isValidUnderWaterWeapon( weapon ), "weapon is not a valid underwater weapon" );

	level.deep_water_weapon = weapon;
}

isValidUnderWaterWeapon( weapon )
{
	switch( weapon )
	{
		case "iw5_underwater_mp":	//swim
		case "iw5_combatknife_mp":	//knife
		case "none":				//no weapon
			return true;
		default:
			return false;
	}
}
