#using scripts\mp\_teamops;
#using scripts\mp\_util;
#using scripts\mp\gametypes\_dogtags;
#using scripts\mp\gametypes\_globallogic;
#using scripts\mp\gametypes\_globallogic_audio;
#using scripts\mp\gametypes\_globallogic_score;
#using scripts\mp\gametypes\_globallogic_spawn;
#using scripts\mp\gametypes\_globallogic_utils;
#using scripts\mp\gametypes\_prop_dev;
#using scripts\mp\gametypes\_spawning;
#using scripts\mp\gametypes\_spawnlogic;
#using scripts\mp\gametypes\prop;
#using scripts\mp\killstreaks\_killstreaks;
#using scripts\shared\clientfield_shared;
#using scripts\shared\damagefeedback_shared;
#using scripts\shared\flag_shared;
#using scripts\shared\fx_shared;
#using scripts\shared\gameobjects_shared;
#using scripts\shared\hostmigration_shared;
#using scripts\shared\hud_util_shared;
#using scripts\shared\math_shared;
#using scripts\shared\scoreevents_shared;
#using scripts\shared\tweakables_shared;
#using scripts\shared\util_shared;

    	   	                                                                                                                                         	                                	                                                                                                                                                                                                                                                                                                                                                                    	        	     	              	    	   	               	                                              	                                                           	                               	                     	                                                                                                           	                                                                 	                                                              	                                                                                                                                             	                            	                                     	                                       	                                                                                                              	   	                  	       	                                                    	                   	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            	                                                                                                 	                                    	                                                                                        	           	                        	                                            	                                             	                                                   	                                                             	                                                         	                                                                    	                                                                                                                                                                                                                                	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  	    	         	                                                              	                                                          	                                   	                                   	                                                    	                                       	                                           	     	                                                                                                                                                                                                                                                      	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         	                                                               	        	                                                                                                                              	                                                                                                                     	                               	                                                      	                          
           	               	           	                                                    	      	       

#namespace prop_controls;

/*
	Prop Hunt
*/

function NotifyOnPlayerCommand( command, key )
{
	assert( isPlayer( self ) );
	self thread NotifyOnPlayerCommandInternal( command, key );
}

function NotifyOnPlayerCommandRemove( command, key )
{
	self notify( command + "_" + key );
}

function NotifyOnPlayerCommandInternal( command, key )
{
	self endon( "disconnect" );
	level endon( "game_ended" );
	
	self notify( command + "_" + key );
	self endon( command + "_" + key );
	
	switch( key )
	{
		case "+attack":
			PressButtonNotify( &AttackButtonPressed, command );
			break;
		case "+toggleads_throw":
			PressButtonNotify( &AdsButtonPressed, command );
			break;
		case "weapnext":
			PressButtonNotify( &WeaponSwitchButtonPressed, command );
			break;
		case "+usereload":
			PressButtonNotify( &UseButtonPressed, command );
			break;
		case "+smoke":
			PressButtonNotify( &SecondaryOffhandButtonPressed, command );
			break;
		case "+frag":
			PressButtonNotify( &FragButtonPressed, command );
			break;
		case "+actionslot 1":
			PressButtonNotify( &ActionSlotOneButtonPressed, command );
			break;
		case "+actionslot 2":
			PressButtonNotify( &ActionSlotTwoButtonPressed, command );
			break;
		case "+actionslot 3":
			PressButtonNotify( &ActionSlotThreeButtonPressed, command );
			break;
		case "+actionslot 4":
			PressButtonNotify( &ActionSlotFourButtonPressed, command );
			break;
		case "-actionslot 1":
			ReleaseButtonNotify( &ActionSlotOneButtonPressed, command );
			break;
		case "-actionslot 2":
			ReleaseButtonNotify( &ActionSlotTwoButtonPressed, command );
			break;
		case "-actionslot 3":
			ReleaseButtonNotify( &ActionSlotThreeButtonPressed, command );
			break;
		case "-actionslot 4":
			ReleaseButtonNotify( &ActionSlotFourButtonPressed, command );
			break;
		case "+stance":
			ReleaseButtonNotify( &StanceButtonPressed, command );
			break;
		case "+breath_sprint":
			ReleaseButtonNotify( &SprintButtonPressed, command );
			break;
		case "+melee":
			ReleaseButtonNotify( &MeleeButtonPressed, command );
			break;
	}
}

function PressButtonNotify( buttonFunc, command )
{
	while ( true )
	{
		while ( !self [[ buttonFunc ]]() )
			{wait(.05);};
		
		self notify( command );
		
		while ( self [[ buttonFunc ]]() )
			{wait(.05);};
	}
}

function ReleaseButtonNotify( buttonFunc, command )
{
	while ( true )
	{
		while ( !self [[ buttonFunc ]]() )
			{wait(.05);};
		
		while ( self [[ buttonFunc ]]() )
			{wait(.05);};
		
		self notify( command );
	}
}

function setupKeyBindings() // self == player
{
	if ( IsAI( self ) )
		return;
	
	self NotifyOnPlayerCommand( "lock", "+attack" );
	self NotifyOnPlayerCommand( "spin", "+toggleads_throw" );
	self NotifyOnPlayerCommand( "changeProp", "weapnext" );
	self NotifyOnPlayerCommand( "setToSlope", "+usereload" );
	self NotifyOnPlayerCommand( "propAbility", "+smoke" );
	self NotifyOnPlayerCommand( "cloneProp", "+actionslot 2" );
	self NotifyOnPlayerCommand( "zoomin", "+actionslot 3" );
	self NotifyOnPlayerCommand( "zoomout", "+actionslot 4" );
	self NotifyOnPlayerCommand( "hide", "+melee" );
}

function cleanupPropKeyBindings()
{
	if ( IsAI( self ) )
		return;
	
	self NotifyOnPlayerCommandRemove( "lock", "+attack" );
	self NotifyOnPlayerCommandRemove( "spin", "+toggleads_throw" );	
	self NotifyOnPlayerCommandRemove( "changeProp", "+weapnext" );
	self NotifyOnPlayerCommandRemove( "setToSlope", "+usereload" );	
	self NotifyOnPlayerCommandRemove( "propAbility", "+smoke" );
	self NotifyOnPlayerCommandRemove( "cloneProp", "+actionslot 2" );
	self NotifyOnPlayerCommandRemove( "zoomin", "+actionslot 3" );
	self NotifyOnPlayerCommandRemove( "zoomout", "+actionslot 4" );
	self NotifyOnPlayerCommandRemove( "hide", "+melee" );
}

function is_player_gamepad_enabled()
{
	return self GamepadUsedLast();
}

function addUpperRightHudElem( label, value, text, labelPC )
{
	hudElem = hud::createFontString( "default", 1.2 );
	hudElem.x = -15;
	hudElem.y = self.currentHudY;
	hudElem.alignX = "right";
	hudElem.alignY = "bottom";
	hudElem.horzAlign = "right";
	hudElem.vertAlign = "bottom";
	hudElem.archived = true;
	hudElem.alpha = 1;
	hudElem.glowAlpha = 0;
	hudElem.hidewheninmenu = true;
	hudelem.hidewheninkillcam = true;
	hudelem.startFontScale = hudelem.fontscale;

	if ( IsDefined( label ) && IsDefined( labelPC ) )
	{
		if ( self is_player_gamepad_enabled() )
			hudElem.label = label;
		else
			hudElem.label = labelPC;
	}
	else if ( IsDefined( label ) )
	{
		hudElem.label = label;
	}
	else if ( IsDefined( text ) )
	{
		hudElem SetText( text );
	}
	
	if ( IsDefined( value ) )
		hudElem SetValue( value );
	
	self.currentHudY -= 20;
	
	return hudElem;
}

// Prop Controls
// ---------------------------------------------------------------------------------------------

function propControlsHUD()
{
	Assert( !IsDefined( self.changePropKey ) );
	if ( self IsSplitscreen() )
		self.currentHudY = -10;
	else
		self.currentHudY = -80;
	
	self.abilityKey = addUpperRightHudElem();
	self.cloneKey = addUpperRightHudElem( &"MP_PH_CLONE" );
	self.changePropKey = addUpperRightHudElem( &"MP_PH_CHANGE", 0 );
	
	self.currentHudY -= 20;

	self.hidePropKey = addUpperRightHudElem( &"MP_PH_HIDEPROP" );
	self.matchSlopeKey = addUpperRightHudElem( &"MP_PH_SLOPE", undefined, undefined, &"MP_PH_SLOPE_PC" );
	self.lockPropKey = addUpperRightHudElem( &"MP_PH_LOCK" );
	self.spinPropKey = addUpperRightHudElem( &"MP_PH_SPIN", undefined, undefined, &"MP_PH_SPIN_PC" );
	
	self setNewAbilityHUD();
	self.zoomKey = addUpperRightHudElem( &"MP_PH_ZOOM" );
	self thread updateTextOnGamepadChange();
}

function cleanupPropControlsHUDOnDeath()
{
	level endon( "game_ended" );
	self endon( "disconnect" );
	
	self waittill( "death" );
	
	self thread cleanupPropKeyBindings();
	self thread cleanupPropControlsHUD();
}

function safeDestroy( hudElem )
{
	if ( IsDefined( hudElem ) )
	{
		hudElem Destroy();
	}
}
	
function cleanupPropControlsHUD()
{
	safeDestroy( self.changePropKey );
	safeDestroy( self.spinPropKey );
	safeDestroy( self.lockPropKey );
	safeDestroy( self.matchSlopeKey );
	safeDestroy( self.abilityKey );
	safeDestroy( self.zoomKey );
	safeDestroy( self.spectateKey );
	safeDestroy( self.cloneKey );
	safeDestroy( self.hidePropKey );
}

function updateTextOnGamepadChange()
{
	level endon( "game_ended" );
	self endon( "disconnect" );
	self endon( "death" );
	
	if ( level.Console )
		return;
	
	waittillframeend;
	
	lastGamepad = self is_player_gamepad_enabled();
	
	while ( true )
	{
		nowGamepad = self is_player_gamepad_enabled();
		if ( nowGamepad != lastGamepad )
		{
			lastGamepad = nowGamepad;

			if ( nowGamepad )
			{
				if ( !( isdefined( self.slopeLocked ) && self.slopeLocked ) )
					self.matchSlopeKey.label = &"MP_PH_SLOPE";
				else
					self.matchSlopeKey.label = &"MP_PH_SLOPED";
				
				self.spinPropKey.label = &"MP_PH_SPIN";
			}
			else
			{
				if ( !( isdefined( self.slopeLocked ) && self.slopeLocked ) )
					self.matchSlopeKey.label = &"MP_PH_SLOPE_PC";
				else
					self.matchSlopeKey.label = &"MP_PH_SLOPED_PC";
				
				self.spinPropKey.label = &"MP_PH_SPIN_PC";
			}

		}
		
		{wait(.05);};
	}
}

function propInputWatch()
{
	self endon( "death" );
	self endon( "disconnect" );
	level endon( "game_ended" );

	if ( IsAI( self ) )
		return;
		
	self.lock = false;
	self.slopeLocked = false;
	
	prop::waittillPrematchDone();
	
	self thread propMoveUnlock();
	self thread propCameraZoom();
	
	self.debugNextPropIndex = 1;
	
	while(1)
	{
		msg = self util::waittill_any_return( "lock", "spin", "changeProp", "setToSlope", "propAbility", "cloneProp", "hide" );
		
		if ( !IsDefined( msg ) )
			continue;
		
		waittillframeend;
		
		if ( msg == "lock" )
			self propLockUnlock();
		else if ( msg == "spin" )
			self propSpin();
		else if ( msg == "changeProp" )
			self propChange();
		else if ( msg == "setToSlope" )
			self propMatchSlope();
		else if ( msg == "propAbility" )
			self propAbility();
		else if ( msg == "cloneProp" )
			self propClonePower();
		else if ( msg == "hide" )
			self propLocalHide();
	}
}

function propLockUnlock()
{
	if ( self IsMantling() )
		return;
	
	if( self.lock )
	{
		self unlockProp();
	}
	else
	{
		self lockProp();
	}
}

function propSpin()
{
	self.propEnt Unlink();

	self.propEnt.angles += ( 0, 45, 0 );
	self.propEnt.origin = self.propAnchor.origin;

	if ( self.slopeLocked && ( isdefined( self.lock ) && self.lock ) )
	{
		self.propEnt set_pitch_roll_for_ground_normal( self.prop );
	}

	self.propEnt LinkTo( self.propAnchor );
}

function registerPreviousProp( inPlayer )
{
	CONST_MAX_REMEMBERED_PROPS = 3;
	
	if (!IsDefined(inPlayer.usedPropsIndex))
		inPlayer.usedPropsIndex = 0;
	
	inPlayer.usedProps[inPlayer.usedPropsIndex] = inPlayer.prop.info;
	
	inPlayer.usedPropsIndex++;
	if ( inPlayer.usedPropsIndex >= CONST_MAX_REMEMBERED_PROPS )
		inPlayer.usedPropsIndex = 0;	
}

function propChange()
{
	if ( !self propHasChangesLeft() )
	{
		return;
	}
	
	// Debounce prop changes on PC due to the mouse wheel being the default binding
	if ( !level.console )
	{
		debounceMS = 300;
		if ( IsDefined( self.lastPropChangeTime ) && ( ( GetTime() - self.lastPropChangeTime ) < debounceMS ) )
		{
			return;
		}
		
		self.lastPropChangeTime = GetTime();
	}

	self notify( "changed_prop" );
	
	registerPreviousProp(self);
	
	self.prop.info = prop::getNextProp( self );

	/#
	if ( GetDvarInt( "scr_ph_cycleModels", 0 ) != 0 )
	{
		self.prop.info = level.propList[ self.debugNextPropIndex ];
		self.debugNextPropIndex++;
		if ( self.debugNextPropIndex >= level.propList.size )
			self.debugNextPropIndex = 0;
	}
	#/
	
	self propChangeTo( self.prop.info );
	
	if ( level.phSettings.nuketownVariant )
		PlayFxOnTag( "player/fx_plyr_clone_reaper_appear", self.prop, "tag_origin" );

	self.maxhealth = Int( prop::getPropHealth( self.prop.info ) );
	self SetNormalHealth( 1.0 );

	self setNewAbilityCount( self.currentAbility );
	self setNewAbilityCount( "CLONE" );
	
	if ( prop::usePropHudServer() )
	{
		self.abilityKey.alpha = 1;
		self.cloneKey.alpha = 1;
	}

	/#
	if ( GetDvarInt( "scr_ph_cycleModels", 0 ) != 0 )
	{
		return;
	}
	#/

	self propDeductChange();
}

function propHasChangesLeft()
{
	/# 
	if ( ( isdefined( self.unlimitedChanges ) && self.unlimitedChanges ) )
	{
		return true;
	}
	#/
	
	return ( self.changesLeft > 0 );
}

function propDeductChange()
{
	/# 
	if ( ( isdefined( self.unlimitedChanges ) && self.unlimitedChanges ) )
	{
		return;
	}
	#/
	
	propSetChangesLeft( self.changesLeft - 1 );
}

function propSetChangesLeft( newValue )
{
	self.changesLeft = newValue;

	if ( prop::usePropHudServer() )
	{
		self.changePropKey setvalue( self.changesLeft );
		if ( self.changesLeft <= 0 )
			self.changePropKey.alpha = 0.5;
	}
}

function propChangeTo( info )
{
	self.prop.info = info;
	self.propInfo = info;
	
	if ( level.phSettings.nuketownVariant )
	{
		NTX_old_propEnt_ang = self.propEnt.angles;
		NTX_old_prop_ang = self.prop.angles;
		NTX_old_ang = self.angles;
	}
	
	self.prop SetModel( info.modelName );
	self.prop.xyzOffset	   = info.xyzOffset;
	self.prop.anglesOffset = info.anglesOffset;
	self.prop SetScale( info.propScale, true );

	self.prop Unlink();
	self.propEnt Unlink();

	self.propEnt.origin = self.propAnchor.origin;
	self.prop.origin	= self.propEnt.origin;

	self.propEnt.angles = ( self.angles[ 0 ], self.propEnt.angles[ 1 ], self.angles[ 2 ] );
	self.prop.angles	= self.propEnt.angles;
	
	if ( ( isdefined( self.isAngleOffset ) && self.isAngleOffset ) )
	{
		self.prop.angles   = self.angles;
		self.isAngleOffset = false;
	}

	self prop::applyXYZOffset();
	self prop::applyAnglesOffset();

	if ( level.phSettings.nuketownVariant )
	{
		self.propEnt.angles = NTX_old_propEnt_ang;
		self.prop.angles = NTX_old_prop_ang;
		self.angles = NTX_old_ang;
	}
	
	self.prop LinkTo( self.propEnt );
	
	if ( self.slopeLocked && ( isdefined( self.lock ) && self.lock ) )
	{
		self.propEnt set_pitch_roll_for_ground_normal( self.prop );
	}

	self.propEnt LinkTo( self.propAnchor );
	
	self.thirdPersonRange = info.propRange;
	self.thirdPersonHeightOffset = info.propHeight;
	self SetClientThirdPerson( true, self.thirdPersonRange, self.thirdPersonHeightOffset );
}

function propMatchSlope()
{
	if ( !( isdefined( self.slopeLocked ) && self.slopeLocked ) )
	{
		self.slopeLocked = true;

		if ( ( isdefined( self.lock ) && self.lock ) )
		{
			self.propEnt Unlink();
			self.propEnt set_pitch_roll_for_ground_normal( self.prop );
			self.propEnt LinkTo( self.propAnchor );
		}

		if ( prop::usePropHudServer() )
		{
			if ( self is_player_gamepad_enabled() )
				//"[{+usereload}]: Matching Slope"
				self.matchSlopeKey.label = &"MP_PH_SLOPED";
			else
				//"[{+activate}]: Matching Slope"
				self.matchSlopeKey.label = &"MP_PH_SLOPED_PC";
		}
	}
	else
	{
		self.slopeLocked = false;

		if ( ( isdefined( self.lock ) && self.lock ) )
		{
			self.propEnt Unlink();
			self.propEnt.angles = ( self.angles[ 0 ], self.propEnt.angles[ 1 ], self.angles[ 2 ] );
			self.propEnt.origin = self.propAnchor.origin;
			self.propEnt LinkTo( self.propAnchor );
		}

		if ( prop::usePropHudServer() )
		{
			if ( self is_player_gamepad_enabled() )
				//"[{+usereload}]: Match Slope"
				self.matchSlopeKey.label = &"MP_PH_SLOPE";
			else
				//"[{+activate}]: Match Slope"
				self.matchSlopeKey.label = &"MP_PH_SLOPE_PC";
		}
	}
}

function propAbility()
{
	if ( !level flag::get( "props_hide_over" ) )
	{
		return;
	}
	
	if ( self propHasFlashesLeft() )
	{
		self thread flashEnemies();
		self propDeductFlash();
	}
}

function propClonePower()
{
	if ( propHasClonesLeft() )
	{
		self thread cloneProp();
		self thread propDeductCloneChange();
	}
}

function propHasClonesLeft()
{
	/# 
	if ( ( isdefined( self.unlimitedClones ) && self.unlimitedClones ) )
	{
		return true;
	}
	#/
	
	return ( self.clonesLeft > 0 );
}

function propDeductCloneChange()
{
	/# 
	if ( ( isdefined( self.unlimitedClones ) && self.unlimitedClones ) )
	{
		return;
	}
	#/
	
	propSetClonesLeft( self.clonesLeft - 1 );
}

function propSetClonesLeft( newValue )
{
	self.clonesLeft = newValue;
	
	if ( prop::usePropHudServer() && isdefined( self ) && IsAlive( self ) && isdefined( self.cloneKey ) )
	{
		self.cloneKey setvalue( self.clonesLeft );
		if ( self.clonesLeft <= 0 )
			self.cloneKey.alpha = 0.5;
		else
			self.cloneKey.alpha = 1;
	}
}

function propHasFlashesLeft()
{
	/# 
	if ( ( isdefined( self.unlimitedFlashes ) && self.unlimitedFlashes ) )
	{
		return true;
	}
	#/
	
	return ( self.abilityLeft > 0 );
}

function propDeductFlash()
{
	/# 
	if ( ( isdefined( self.unlimitedFlashes ) && self.unlimitedFlashes ) )
	{
		return;
	}
	#/
	
	propSetFlashesLeft( self.abilityLeft - 1 );
}

function propSetFlashesLeft( newValue )
{
	self.abilityLeft = newValue;
	
	if ( prop::usePropHudServer() )
	{
		self.abilityKey setvalue( self.abilityLeft );
		if ( self.abilityLeft <= 0 )
			self.abilityKey.alpha = 0.5;
	}
}

function set_pitch_roll_for_ground_normal( traceIgnore )
{
	groundNormal = get_ground_normal( traceIgnore, false );
    if(!IsDefined(groundNormal))
    	return;
    
    ovf = AnglesToForward( self.angles );
    ovr = AnglesToRight( self.angles );

    new_angles = VectorToAngles( groundNormal );
    pitch = AngleClamp180( new_angles[0] + 90 );
    new_angles = (0, new_angles[1], 0 );

    nvf = AnglesToForward( new_angles );

    mod = VectorDot( nvf, ovr );

    if ( mod < 0 )
        mod = -1;
    else
        mod = 1;

    dot = VectorDot( nvf, ovf );

    newPitch = dot * pitch;
    newRoll = ((1-abs(dot)) * pitch * mod);
    
    self.angles = (newPitch, self.angles[1], newRoll );
}

function allPropsSetIgnoreBulletCollision( shouldIgnore )
{
	foreach ( player in level.players )
	{
		if ( isdefined( player.prop ) )
		{
			if ( shouldIgnore )
				player.prop NotSolid();
			else
				player.prop Solid();
		}
	}
}

function allClonesSetIgnoreBulletCollision( shouldIgnore )
{
	foreach ( player in level.players )
	{
		if ( isdefined( player.propClones ) )
		{
			foreach ( clone in player.propClones )
			{
				if ( isdefined( clone ) )
				{
					if ( shouldIgnore )
						clone NotSolid();
					else
						clone Solid();
				}
			}
		}
	}
}

function get_ground_normal( traceIgnore, debug )
{
	if( !isDefined( traceIgnore ) )
	    ignore = self;
	else
		ignore = traceIgnore;
	
	tracePoints = Array( self.origin );
	
	if( GetDvarInt("scr_ph_useBoundsForGroundNormal", 1) )
	{
		for(i=-1.0; i<=1.0; i+=2.0)
		{
			for(j=-1.0; j<=1.0; j+=2.0)
			{
				corner = ignore GetPointInBounds( i, j, 0.0 );
				corner = (corner[0], corner[1], self.origin[2]);
				
				tracePoints[tracePoints.size] = corner;
			}
		}
	}
	
	allPropsSetIgnoreBulletCollision( true );
	
	avgNormal = (0,0,0);
	traceHitCount = 0;
	foreach(point in tracePoints)
	{
		trace = BulletTrace( point+(0,0,4), point+(0,0,-16), false, ignore );
		traceHit = (trace["fraction"] > 0.0) && (trace["fraction"] < 1);
		if ( traceHit )
		{
    		avgNormal += trace["normal"];
    		traceHitCount++;
		}
		
		/#
		if( debug )
		{
			if( traceHit )
				line(point, point + trace["normal"] * 30, (0,1,0));
			else
				Sphere(point, 3.0, (1,0,0));
		}
		#/
	}
	
	allPropsSetIgnoreBulletCollision( false );
	
	if ( traceHitCount > 0 )
	{
    	avgNormal /= traceHitCount;
    	/#
    	if( debug )
    		line(self.origin, self.origin + avgNormal * 20, (1,1,1));
    	#/
    	
    	return avgNormal;
	}
	else
	{
		/#
		if( debug )
		   	Sphere(self.origin, 5.0, (1,0,0));
		#/
		return undefined;
	}
}

function propMoveUnlock()
{
	self endon( "death" );
	self endon( "disconnect" );
	level endon( "game_ended" );

	wasLocked = false;
	wasMoving = false;
	waitingToUnlock = false;
	
	while( 1 )
	{
		{wait(.05);};
		
		movement = self GetNormalizedMovement();
		jumping = self JumpButtonPressed();
			
		if( !isdefined( movement ) )
			continue;
		
		isMoving = ( movement[ 0 ] != 0 || movement[ 1 ] != 0 || jumping );
		
		if ( self.lock && waitingToUnlock && !isMoving )
		{
			waitingToUnlock = false;
		}
		else if ( self.lock && !wasLocked && isMoving )
		{
			waitingToUnlock = true;
		}
		else if( self.lock && isMoving && !waitingToUnlock )
		{	
			self unlockProp();
		}
		wasLocked = self.lock;
		wasMoving = isMoving;
	}
}

function unlockProp()
{
	self unlink();
	self ResetDoubleJumpRechargeTime();
	
	if( self.slopeLocked )
	{
		self.propEnt unlink();
		self.propEnt.angles = ( self.angles[0], self.propEnt.angles[1], self.angles[2] ) ;
		
		self.propEnt.origin = self.propAnchor.origin;
		self.propEnt LinkTo( self.propAnchor );
	}
		
	self.propAnchor LinkTo( self );

	self.lock = false;
	if ( prop::usePropHudServer() )
	{
		self.lockPropKey.label = &"MP_PH_LOCK";
		self thread flashLockPropKey();
	}
}

function lockProp()
{
	if ( !canLock() )
		return;
	
	self.propAnchor unlink();
	self.propAnchor.origin = self.origin;
	
	self PlayerLinkTo( self.propAnchor );
	
	if( self.slopeLocked )
	{
		self.propEnt unlink();
		self.propEnt set_pitch_roll_for_ground_normal( self.prop );
		self.propEnt.origin = self.origin;
		self.propEnt LinkTo(self.propAnchor);
	}
	
	self.lock = true;
	self notify( "locked" );
	if ( prop::usePropHudServer() )
	{
		self.lockPropKey.label = &"MP_PH_LOCKED";
		self thread flashLockPropKey();
	}
}

function flashLockPropKey()
{
	self endon( "death" );
	self endon( "disconnect" );
	level endon( "game_ended" );
	
	self notify( "flashLockPropKey" );
	self endon( "flashLockPropKey" );
	
	newScale = ( self.lockPropKey.startFontScale + 0.75 );
	
	self.lockPropKey changefontscaleovertime( 0.1 );
	self.lockPropKey.fontScale = newScale;
	
	wait 0.1;
	
	if ( isdefined( self.lockPropKey ) )
	{
		self.lockPropKey changefontscaleovertime( 0.1 );
		self.lockPropKey.fontScale = self.lockPropKey.startFontScale;
	}
}

function GetPhysicsBoxGroundPosition()
{
	assert( IsPlayer( self ) );
	
	start = self.origin;
	end = start + ( 0, 0, -2000 );
	return PlayerPhysicsTrace( start, end );
}

function GetBulletGroundTrace()
{
	assert( IsPlayer( self ) );
	
	start = self.origin;
	end = start + ( 0, 0, -2000 );
	trace = BulletTrace( start, end, false, self.prop );
	return trace;
}

/#
function recordDebugLock( success, type, player, origin1, text1, origin2, text2, origin3, text3 )
{
	if ( !isdefined( level.phLockDebug ) )
		level.phLockDebug = SpawnStruct();
	
	level.phLockDebug.success = success;
	level.phLockDebug.type = type;
	level.phLockDebug.playerOrg = player.origin;
	level.phLockDebug.playerAngles = player.angles;
	level.phLockDebug.playerMins = player GetMins();
	level.phLockDebug.playerMaxs = player GetMaxs();
	level.phLockDebug.origin1 = origin1;
	level.phLockDebug.text1 = text1;
	level.phLockDebug.origin2 = origin2;
	level.phLockDebug.text2 = text2;
	level.phLockDebug.origin3 = origin3;
	level.phLockDebug.text3 = text3;
}
#/

function canLock()
{
	killTriggers = GetEntArray( "trigger_hurt", "classname" );
	oobTriggers = GetEntArray( "trigger_out_of_bounds","classname" );
	triggers = ArrayCombine( killtriggers, oobTriggers, false, false );
	propNoLockTriggers = GetEntArray( "prop_no_lock", "targetname" );
	if ( propNoLockTriggers.size > 0 )
		triggers = ArrayCombine( triggers, propNoLockTriggers, false, false );
	
	foreach ( trigger in triggers )
	{
		if ( trigger IsTouchingVolume( self.origin, self GetMins(), self GetMaxs() ) )
		{
			/# recordDebugLock( false, "touch", self, trigger.origin, trigger.classname ); #/
			return false;
		}
	}
	
	// don't allow the player to lock above kill or oob triggers
	if ( self IsPlayerSwimming() )
	{
		/# recordDebugLock( true, "swim", self ); #/
		return true;
	}
	else if ( !self IsOnGround() || self IsWallRunning() )
	{
		trace1 = self GetBulletGroundTrace();
		frac = trace1["fraction"];
		org1 = trace1["position"];
		
		if ( frac == 1 )
		{
			/# recordDebugLock( false, "frac=1", self, org1, ""); #/
			return false;
		}
			
		foreach ( trigger in triggers )
		{
			if ( trigger IsTouchingVolume( org1, self GetMins(), self GetMaxs() ) )
			{
				/# recordDebugLock( false, "bullet", self, trigger.origin, trigger.classname ); #/
				return false;
			}
		}
		
		point = GetNearestPathPoint( org1, 256 );
		if ( !isdefined( point ) )
		{
			/# recordDebugLock( false, "path", self, org1 ); #/
			return false;
		}
		distZ = point[2] - org1[2];
		if ( distZ > 50 )
		{
			point2 = GetNearestPathPoint( org1, 50 );
			if ( !isdefined( point2 ) )
			{
				/# recordDebugLock( false, "belowPath", self, org1, "trace", point, "path" ); #/
				return false;
			}
		}
		dist2d = Distance2d( point, org1 );
		if ( dist2d > 100 )
		{
			/# recordDebugLock( false, "2dPath", self, org1, "trace", point, "path" ); #/
			return false;
		}

		org2 = self GetPhysicsBoxGroundPosition();
		
		foreach ( trigger in triggers )
		{
			if ( trigger IsTouchingVolume( org2, self GetMins(), self GetMaxs() ) )
			{
				/# recordDebugLock( false, "box", self, trigger.origin, trigger.classname ); #/
				return false;
			}
		}
		
		/# recordDebugLock( true, "air", self, org1, "bullet", org2, "box", point, "path " + Distance( org1, point ) ); #/
		return true;
	}
	else
	{
		/# recordDebugLock( true, "ground", self ); #/
		return true;
	}
}

function propCameraZoom()
{
	self endon( "death" );
	self endon( "disconnect" );
	level endon( "game_ended" );
	
	CONST_PROP_ZOOM_INCREMENT = 10;

	self.thirdPersonRange = self.prop.info.propRange;

	while( 1 )
	{
		zoom = self util::waittill_any_return( "zoomin", "zoomout" );
		
		if( !isdefined( zoom ) )
			continue;
		
		if( zoom == "zoomin" )
		{
			if( ( self.thirdPersonRange - CONST_PROP_ZOOM_INCREMENT ) < 50 )
				continue;

			self.thirdPersonRange -= CONST_PROP_ZOOM_INCREMENT;
			self SetClientThirdPerson( true, self.thirdPersonRange, self.thirdPersonHeightOffset );
		}
		else if( zoom == "zoomout" )
		{
			maxZoomOutRange = math::clamp( self.prop.info.propRange + 50, 50, 360 );

			if( ( self.thirdPersonRange + CONST_PROP_ZOOM_INCREMENT ) > maxZoomOutRange )
				continue;
		
			self.thirdPersonRange += CONST_PROP_ZOOM_INCREMENT;
			self SetClientThirdPerson( true, self.thirdPersonRange, self.thirdPersonHeightOffset );
		}
	}
}

function setNewAbilityHUD()
{
	switch( self.currentAbility )
	{
		case "FLASH":
			self.abilityKey.label = &"MP_PH_FLASH";
			break;
		default:
			AssertMsg( "Only flash is supported" );
			break;
	}
}

function setNewAbilityCount( propPower, count )
{
	switch( propPower )
	{
		case "FLASH":
			if ( !isdefined( count ) )
				count = level.phSettings.propNumFlashes;
			propSetFlashesLeft( count );
			break;
		case "CLONE":
			if ( !isdefined( count ) )
				count = level.phSettings.propNumClones;
			propSetClonesLeft( count );
			break;
		default:
			AssertMsg( "Unknown ability: " + propPower );
			break;
	}
}

function endOnDeath()
{
	self waittill( "death" );
	waittillframeend;
	self notify ( "end_explode" );
}

function flashTheProps( thrownBy )
{
	level endon( "game_ended" );
	thrownBy endon( "disconnect" );

	self thread endOnDeath();
	self endon( "end_explode" );
	
	self waittill( "explode", position );
	
	if( !isDefined( thrownBy ) )
		return;
	
	flashEnemies( thrownBy, position );
}

function flashEnemies( thrownBy, position )
{
	if( !isDefined( thrownBy ) )
		thrownBy = self;
			
	if( !isDefined( position ) )
		position = self.origin;
			
	PlayFX( fx::get( "propFlash" ), ( position + ( 0, 0, 4 ) ) );
	PlaySoundAtPosition( "mpl_emp_equip_stun", position );
	
	foreach( otherPlayer in level.players )
	{
		if ( otherPlayer == thrownBy )
			continue;
		
		if( ( isdefined( otherPlayer.flashImmune ) && otherPlayer.flashImmune ) )
			continue;
		
		if ( !IsDefined( otherPlayer ) || !IsAlive( otherPlayer ) || !IsDefined( otherPlayer.team ) || otherPlayer.team != game[ "attackers" ] )
			continue;
		
		vec = ( position + (0,0,4) ) - (otherPlayer GetEye());
		dist = Length( vec );
		radius_max = 500.0;
		radius_min = 150.0;
		if ( dist <= radius_max )
		{
			if ( dist <= radius_min )
				dist_amt = 1;
			else
				dist_amt = 1 - (dist-radius_min)/(radius_max-radius_min);
			dir = VectorNormalize( vec );
			fwd = AnglesToForward( (otherPlayer GetPlayerAngles()) );
			fov_amt = VectorDot( fwd, dir );
			otherPlayer notify( "flashbang", dist_amt, fov_amt, thrownBy );
			thrownBy thread damagefeedback::update();
		}
	}
}

function deletePropsIfAtMax() // self == player
{
	MAX_CLONES_PER_PLAYER = 9;
	
	if ( level.phSettings.altModeManyClones )
		MAX_CLONES_PER_PLAYER = 27;
	
	if ( ( self.propClones.size + 1 ) <= MAX_CLONES_PER_PLAYER )
		return;
	
	numValidClones = 0;
	foreach ( clone in self.propClones )
	{
		if ( IsDefined( clone ) )
			numValidClones++;
	}
	
	if ( ( numValidClones + 1 ) <= MAX_CLONES_PER_PLAYER )
		return;
	
	clones = [];
	cloneToDelete = undefined;
	for ( i = 0; i < self.propClones.size; i++ )
	{
		clone = self.propClones[i];
		if ( !IsDefined( clone ) )
			continue;
		
		if ( !IsDefined( cloneToDelete ) )
		{
			cloneToDelete = clone;
			continue;
		}
		
		clones[ clones.size ] = clone;
	}
	
	Assert( IsDefined( cloneToDelete ) );	
	cloneToDelete notify( "maxDelete" );
	cloneToDelete Delete();
	
	self.propClones = clones;
}

function cloneProp()
{
	if( !isDefined( self.propClones ) )
		self.propClones = [];
	else
		deletePropsIfAtMax();

    newClone = Spawn("script_model", self.prop.origin);
    newClone.targetname = "propClone";
    newClone setModel( self.prop.model );
    newClone SetScale( self.prop.info.propScale, true );
    newClone.angles = self.prop.angles;
    newClone SetCanDamage( true );
    newClone.fakeHealth = 50;
	newClone.health = 99999;
	newClone.maxhealth = 99999;
	newClone.playerOwner = self;
	newClone thread prop::entityDamageWatcher( &damageCloneWatch );
	newClone SetPlayerCollision( false );
	newClone MakeSentient();
	newClone NotSolidCapsule();
	newClone SetTeam( self.team );
	newClone clientfield::set( "enemyequip", 2 );
	
	if ( prop::propMiniGameActive() )
	{
		newClone HideFromTeam( game["attackers"] );
		newClone NotSolid();
	}
	
	if ( level.phSettings.altModeManyClones )
		newClone.fakeHealth = 100;

    self.propClones[self.propClones.size] = newClone;
}

function damageCloneWatch( damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, weapon, iDFlags )
{
    if ( !IsDefined( attacker ) )
        return;
    
    if ( isPlayer( attacker ) )
    {
    	if( ( isdefined( self.isDying ) && self.isDying ) )
    	{
    		return;
    	}
    	
    	if ( isdefined( weapon ) && weapon.rootWeapon.name == "concussion_grenade" && isdefined( meansOfDeath ) && meansOfDeath != "MOD_IMPACT" )
    		registerConcussionEvent( attacker, undefined, meansOfDeath, damage, point, weapon );
    	
        attacker thread damagefeedback::update();
        
        self.lastattacker = attacker;
        
    	self.fakeHealth -= damage;
    	if ( self.fakeHealth <= 0 )
    	{
    		self deleteClone();
    		return;
    	}
    }
    
    self.health += damage;
}

function flashCloneDestroyedIndicator()
{
	self.decoyDestroyedIndicator.alpha = 1;
	self.decoyDestroyedIndicator FadeOverTime( 3 );
	self.decoyDestroyedIndicator.alpha = 0;
}

function createAltDecoyDestroyedIndicator()
{
	self.decoyDestroyedIndicator = hud::createFontString( "objective", 1 );
	self.decoyDestroyedIndicator.label = &"SCORE_DECOY_KILLED";
	self.decoyDestroyedIndicator.x = 0;
	self.decoyDestroyedIndicator.y = 20;
	self.decoyDestroyedIndicator.alignX = "center";
	self.decoyDestroyedIndicator.alignY = "middle"; 
	self.decoyDestroyedIndicator.horzAlign = "user_center";
	self.decoyDestroyedIndicator.vertAlign = "middle";
	self.decoyDestroyedIndicator.archived = true;
	self.decoyDestroyedIndicator.fontscale = 1;
	self.decoyDestroyedIndicator.alpha = 0;
	self.decoyDestroyedIndicator.glowAlpha = 0.5;
	self.decoyDestroyedIndicator.hidewheninmenu = false;
}

function altDecoyDestroyedIndicator( decoyOwner, attacker )
{
	if ( isdefined( decoyOwner ) )
	{
		if ( !isdefined( decoyOwner.decoyDestroyedIndicator ) )
			decoyOwner createAltDecoyDestroyedIndicator();
		
		decoyOwner flashCloneDestroyedIndicator();
	}
	
	if ( isdefined( attacker ) )
	{
		if ( !isdefined( attacker.decoyDestroyedIndicator ) )
			attacker createAltDecoyDestroyedIndicator();
		
		attacker flashCloneDestroyedIndicator();
	}
}

function deleteClone()
{
	if ( level.phSettings.altModeManyClones )
	{
		thread altDecoyDestroyedIndicator( self.playerOwner, self.lastattacker );
		
	    if ( isdefined( self.playerOwner ) )
	    	self.playerOwner propSetClonesLeft( self.playerOwner.clonesLeft + 1 );
	}
    else if( isDefined( self.lastattacker ) )
    {
    	scoreevents::processScoreEvent( "clone_destroyed", self.lastattacker );
    	if ( isdefined( self.playerOwner ) )
    		scoreevents::processScoreEvent( "clone_was_destroyed", self.playerOwner );
    }
    
    if( !isDefined( self.isDying ) )
    	self.isDying = true;
    
    PlaySoundAtPosition( "wpn_flash_grenade_explode", self.origin + ( 0, 0, 4 ) );
    PlayFx( fx::get( "propDeathFX" ), ( self.origin + ( 0, 0, 4 ) ) );
        
    if( isDefined( self ) )
        self Delete();
}

function countdownFadeToBlackForXSec( overlay_time, fade_in_time, fade_out_time )
{
	level endon( "game_ended" );
	self endon( "disconnect" );
	
	if( !isDefined( overlay_time ) )
		overlay_time = 5;
	
	if ( !IsDefined( fade_in_time ) )
		fade_in_time = 1;
	
	if ( !IsDefined( fade_out_time ) )
		fade_out_time = 1;
	
	Assert( fade_out_time + fade_in_time < overlay_time );
	
	overlay = NewClientHudElem( self );
    overlay.foreground = false;
	overlay.x = 0;
	overlay.y = 0;
	overlay setshader( "black", 640, 480 );
	overlay.alignX = "left";
	overlay.alignY = "top";
	overlay.horzAlign = "fullscreen";
	overlay.vertAlign = "fullscreen";
	overlay.alpha = 0;
	
	{wait(.05);};
	
	if ( fade_in_time > 0 )
	{
		overlay fadeovertime( fade_in_time );
	}
	overlay.alpha = 1;
	
	self setClientUIVisibilityFlag( "hud_visible", 0 );
	
	self prop::waittillCountdownComplete( fade_in_time );

	self UseServerVisionset( true ); // cover behind the scoreboard
	self SetVisionSetForPlayer( "blackout_ph", fade_in_time );
	
	self prop::waittillCountdownComplete( overlay_time - fade_out_time - fade_in_time );
	
	if ( fade_out_time > 0 )
	{
		overlay fadeovertime( fade_out_time );
	}
	overlay.alpha = 0;
	
	self UseServerVisionset( false );
	self SetVisionSetForPlayer( "blackout_ph", fade_out_time );

	self prop::waittillCountdownComplete( fade_out_time );
	
	self setClientUIVisibilityFlag( "hud_visible", 1 );
	
	{wait(.05);};
	
	safeDestroy( overlay );
}

function watchSpecialGrenadeThrow()
{
	self endon( "death" );
	self endon( "disconnect" );
	level endon( "game_ended" );
	self notify( "watchSpecialGrenadeThrow" );
	self endon( "watchSpecialGrenadeThrow" );
	
	while ( true )
	{
		self waittill ( "grenade_fire", grenade, weapon );
		
		self thread trackGrenade( grenade );
		
		self.thrownSpecialCount = self.thrownSpecialCount + 1;
	}
}

function trackGrenade( grenade )
{
	level endon( "game_ended" );
	self endon( "disconnect" );
	
	weapon = grenade.weapon;
	
	grenade waittill( "explode", damageOrigin );
	
	if ( !isdefined( level.phConcussionEvents ) )
		level.phConcussionEvents = [];
	
	index = getConcussionEventIndex( damageOrigin );

	if ( !isdefined( index ) )
		index = initializeConcussionEvent( self, damageOrigin, weapon, 1, "MOD_GRENADE_SPLASH" );
	
	{wait(.05);}; // wait for concussion notifies to process
	{wait(.05);};
	
	self concussOtherProps( index ); // run the radius damage again under certain conditions to concuss props hiding insside of decoys and other props
	
	{wait(.05);}; // wait for other radius damage notifies to finish
	
	level.phConcussionEvents[index] = undefined;
}

function initializeConcussionEvent( attacker, damageOrigin, weapon, damage, meansOfDeath )
{
	index = level.phConcussionEvents.size;
	level.phConcussionEvents[index] = SpawnStruct();
	level.phConcussionEvents[index].players = [];
	level.phConcussionEvents[index].attacker = self;
	level.phConcussionEvents[index].damageOrigin = damageOrigin;
	level.phConcussionEvents[index].damage = damage;
	level.phConcussionEvents[index].meansOfDeath = meansOfDeath;
	level.phConcussionEvents[index].weapon = weapon;
	
	return index;
}

function registerConcussionEvent( attacker, playerConcussed, meansOfDeath, damage, damageOrigin, weapon )
{
	if ( !isdefined( level.phConcussionEvents ) )
		level.phConcussionEvents = [];
	
	index = getConcussionEventIndex( damageOrigin );
	
	if ( !isdefined( index ) )
		index = initializeConcussionEvent( attacker, damageOrigin, weapon, damage, meansOfDeath );
	
	if ( isdefined( playerConcussed ) )
	{
		playerIndex = level.phConcussionEvents[index].players.size;
		level.phConcussionEvents[index].players[playerIndex] = playerConcussed;
	}
}

function getConcussionEventIndex( damageOrigin )
{
	if ( !isdefined( level.phConcussionEvents ) )
		return;
	
	foreach ( index, event in level.phConcussionEvents )
	{
		if ( event.damageOrigin == damageOrigin )
		{
			return index;
		}
	}
}

function concussOtherProps( index )
{
	if ( !isdefined( level.phConcussionEvents ) || !isdefined( level.phConcussionEvents[index].attacker ) )
		return;
	
	weapon = level.phConcussionEvents[index].weapon;
	damageOrigin = level.phConcussionEvents[index].damageOrigin;
	expRadius = weapon.explosionradius;
	expRadiusSq = expRadius * expRadius;
	
	foreach ( player in level.players )
	{
		if ( !player util::IsProp() || !IsAlive( player ) || player wasConcussed( index ) )
			continue;
		
		distSq = DistanceSquared( damageOrigin, player.origin );
		
		if ( distSq <= expRadiusSq )
		{
			// turn off everything that previously got in the way of the radius trace
			allPropsSetSolidContents( false );
			allClonesSetSolidContents( false );
			allPropPlayersSetSolidContents( true );
			allHunterPlayersSetSolidContents( false );
			
			damage = level.phConcussionEvents[index].damage;
			attacker = level.phConcussionEvents[index].attacker;
			meansOfDeath = level.phConcussionEvents[index].meansOfDeath;
			
			attacker RadiusDamage( damageOrigin, expRadius, damage, damage, attacker, meansOfDeath, weapon );
			
			allPropsSetSolidContents( true );
			allClonesSetSolidContents( true );
			allPropPlayersSetSolidContents( false );
			allHunterPlayersSetSolidContents( true );
			
			break;
		}
	}
}

function allPropsSetSolidContents( isSolid )
{
	foreach ( player in level.players )
	{
		if ( isdefined( player.prop ) )
		{
			if ( isSolid )
			{
				if ( isdefined( player.prop.propContents ) )
					player.prop SetContents( player.prop.propContents );
				
				player.prop Solid();
			}
			else
			{
				if ( !isdefined( player.prop.propContents ) )
					player.prop.propContents = player.prop SetContents( 0 );
				else
					player.prop SetContents( 0 );
				
				player.prop NotSolid();
			}
		}
	}
}

function allClonesSetSolidContents( isSolid )
{
	foreach ( player in level.players )
	{
		if ( isdefined( player.propClones ) )
		{
			foreach ( clone in player.propClones )
			{
				if ( isdefined( clone ) )
				{
					if ( isSolid )
					{
						if ( isdefined( clone.cloneContents ) )
							clone SetContents( clone.cloneContents);
						
						clone Solid();
					}
					else
					{
						if ( !isdefined( clone.cloneContents ) )
							clone.cloneContents = clone SetContents( 0 );
						else
							clone SetContents( 0 );
						
						clone NotSolid();
					}
				}
			}
		}
	}
}

function allPropPlayersSetSolidContents( isSolid )
{
	foreach ( player in level.players )
	{
		if ( !player util::IsProp() || !IsAlive( player ) )
			continue;
		
		if ( isSolid )
		{
			player SetContents( level.phSettings.playerContents );
			player Solid();
		}
		else
		{
			player SetContents( 0 );
			player NotSolid();
		}
	}
}

function allHunterPlayersSetSolidContents( isSolid )
{
	foreach ( player in level.players )
	{
		if ( !player prop::IsHunter() || !IsAlive( player ) )
			continue;
		
		if ( isSolid )
		{
			player SetContents( level.phSettings.playerContents );
			player Solid();
		}
		else
		{
			player SetContents( 0 );
			player NotSolid();
		}
	}
}

function wasConcussedAtOrigin( damageOrigin )
{
	index = getConcussionEventIndex( damageOrigin );
	
	if ( isdefined( index ) )
	{
		return self wasConcussed( index );
	}
	
	return false;
}

function wasConcussed( index )
{
	foreach ( concussedPlayer in level.phConcussionEvents[index].players )
	{
		if ( isdefined( concussedPlayer ) && concussedPlayer == self )
			return true;
	}
	
	return false;
}

function safeSetAlpha( hudElem, newAlphaValue )
{
	if ( IsDefined( hudElem ) )
	{
		hudElem.alpha = newAlphaValue;
	}
}

function propAbilityKeysVisible( visible, override )
{
	if( ( isdefined( visible ) && visible ) )
	{
		alphaValue = 1;
	}
	else
	{
		alphaValue = 0;
	}
	
	if ( prop::usePropHudServer() || ( isdefined( override ) && override ) )
	{
		safeSetAlpha( self.changePropKey, alphaValue );
		safeSetAlpha( self.spinPropKey, alphaValue );
		safeSetAlpha( self.lockPropKey, alphaValue );
		safeSetAlpha( self.matchSlopeKey, alphaValue );
		safeSetAlpha( self.abilityKey, alphaValue );
		safeSetAlpha( self.cloneKey, alphaValue );
		safeSetAlpha( self.zoomKey, alphaValue );
		safeSetAlpha( self.hidePropKey, alphaValue );
		
		if ( !( isdefined( level.noPropsSpectate ) && level.noPropsSpectate ) )
		{
			safeSetAlpha( self.spectateKey, alphaValue );
		}
	}
}

function propLocalHide()
{
	if ( !isdefined( self.propHidden ) )
		self.propHidden = true;
	else
		self.propHidden = !self.propHidden;
	
	if ( self.propHidden )
	{
		self.prop clientfield::set( "retrievable", 1 );
		if ( prop::usePropHudServer() )
			self.hidePropKey.label = &"MP_PH_SHOWPROP";
	}
	else
	{
		self.prop clientfield::set( "retrievable", 0 );	
		if ( prop::usePropHudServer() )
			self.hidePropKey.label = &"MP_PH_HIDEPROP";
	}
}

// Hunter Controls
// ---------------------------------------------------------------------------------------------

function hunterControlsHUD()
{
	Assert( !IsDefined( self.pingKey ) );

	if ( self IsSplitscreen() )
		self.currentHudY = -50;
	else
		self.currentHudY = -100;
	
	self.pingKey = addUpperRightHudElem( &"MP_PH_PING" );
}

function cleanupHunterControlsHUDOnDeath()
{
	level endon( "game_ended" );
	self endon( "disconnect" );
	
	self waittill( "death" );
	
	self thread cleanupHunterKeyBindings();
	self thread cleanupHunterControlsHUD();
}
	
function cleanupHunterControlsHUD()
{
	safeDestroy( self.pingKey );
}

function setupHunterKeyBindings() // self == player
{
	if ( IsAI( self ) )
		return;
	
	self NotifyOnPlayerCommand( "ping", "+smoke" );
}

function cleanupHunterKeyBindings()
{
	if ( IsAI( self ) )
		return;
	
	self NotifyOnPlayerCommandRemove( "ping", "+smoke" );
}

function hunterHasPingsLeft()
{
	/# 
	if ( ( isdefined( self.unlimitedPings ) && self.unlimitedPings ) )
	{
		return true;
	}
	#/
	
	return ( self.pingsLeft > 0 );
}

function hunterDeductPing()
{
	/# 
	if ( ( isdefined( self.unlimitedPings ) && self.unlimitedPings ) )
	{
		return;
	}
	#/
	
	hunterSetPingsLeft( self.pingsLeft - 1 );
}

function hunterSetPingsLeft( newValue )
{
	self.pingsLeft = newValue;

	if ( prop::usePropHudServer() )
	{
		self.pingKey setvalue( self.pingsLeft );
		if ( self.pingsLeft <= 0 )
			self.pingKey.alpha = 0.5;
	}
}

function hunterInputWatch()
{
	self endon( "death" );
	self endon( "disconnect" );
	level endon( "game_ended" );

	if ( IsAI( self ) )
		return;
		
	prop::waittillPrematchDone();
	
	while(1)
	{
		msg = self util::waittill_any_return( "ping" );
		
		if ( !IsDefined( msg ) )
			continue;
		
		if ( msg == "ping" )
			self hunterPing();
	}
}

function hunterPing()
{
	if ( !level flag::get( "props_hide_over" ) )
	{
		return;
	}
	
	if ( ( isdefined( self.pingRunning ) && self.pingRunning ) )
	{
		return;
	}
	
	if ( self hunterHasPingsLeft() )
	{
		self thread hunterDoPing();
		self hunterDeductPing();
	}
}

function hunterDoPing()
{
	level endon( "game_ended" );
	self endon( "disconnect" );
	self endon( "death" );
		
	self.pingRunning = true;
	
	if ( prop::usePropHudServer() )
		self.pingKey.alpha = 0.5;
	
	pingObjectiveId = gameobjects::get_next_obj_id();
	Objective_Add( pingObjectiveId, "active" );
	Objective_Team( pingObjectiveId, game["attackers"] );
	Objective_Position( pingObjectiveId, self.origin );
	Objective_Icon( pingObjectiveId, "t7_hud_waypoints_safeguard_location" );
	Objective_SetColor( pingObjectiveId, &"FriendlyBlue" );
	Objective_OnEntity( pingObjectiveId, self );
	
	self thread pingIconCleanupOnDisconnect( pingObjectiveId );
	
	self clientfield::set( "pingHighlight", 1 );
	
	foreach ( player in level.players )
	{
		if ( IsAlive( player ) && player.team == self.team )
			player PlayLocalSound( "evt_hacker_raise" );
	}
	// if using a 3D sound, do this instead of above
//	self PlaySoundToTeam( HUNTER_PING_SND, game["attackers"] );	
	
	hostmigration::waitLongDurationWithHostMigrationPause( 4 );
	
	self clientfield::set( "pingHighlight", 0 );
	
	gameobjects::release_obj_id( pingObjectiveId );
	
	if ( prop::usePropHudServer() && isdefined( self.pingKey ) )
		self.pingKey.alpha = 1;

	self.pingRunning = false;
	
	self notify( "pingComplete" );
}

function pingIconCleanupOnDisconnect( pingObjectiveId )
{
	level endon( "game_ended" );
	self endon( "pingComplete" );
	
	self util::waittill_either( "disconnect", "death" );
	
	gameobjects::release_obj_id( pingObjectiveId );
}