#include common_scripts\utility;
#include maps\mp\agents\_scriptedAgents;
#include maps\mp\agents\_agent_utility;

main()
{
	self setupDogState();

	self thread think();
	self thread watchOwnerDamage();
	self thread watchOwnerDeath();
	self thread watchOwnerTeamChange();
	self thread WaitForBadPath();
	self thread WaitForPathSet();

/#
	self thread debug_dog();
#/
}

setupDogState()
{
	self.bLockGoalPos 				= false;
	self.ownerRadiusSq				= 256 * 256;
	self.meleeRadiusSq				= 128 * 128;
	self.attackOffset				= 25 + self.radius;
	self.attackRadiusSq				= 3072 * 3072;
	self.warningRadiusSq			= 550 * 550;
	self.warningZHeight				= 128 * 0.75;	//min floor to ceiling in a map is 128 units
	self.attackZHeight				= 54;			//how high we'll allow the dog to jump to attack his target.
	self.attackZHeightDown			= -64;			//how low we'll allow the dog to jump to attack his target.
	self.ownerDamagedRadiusSq		= 1500 * 1500;	//if the owner takes damage and the attacker is within this radius
	self.dogDamagedRadiusSq			= 1500 * 1500;	//if the dog takes damage and the attacker is within this radius
	self.keepPursuingTargetRadiusSq = 3000 * 3000;	//stop pursuing the target after the target gets this far away from my owner, unless he's my favorite enemy.
	self.preferredOffsetFromOwner	= 256;
	self.minOffsetFromOwner			= 50;			//need to keep this above owner.radius + self.radius.
	self.forceAttack				= false;		//if we want to send after a target
	self.ignoreCloseFoliage 		= true;
	self.moveMode 					= "run";
	self.enableExtendedKill			= true;
	self.attackState 				= "idle";		// self.aiState comes from code, so we need something to tell us what attack state we're in
	self.moveState 					= "idle";
	self.bHasBadPath				= false;
	self.timeOfLastDamage			= 0;

	self.allowCrouch				= true;

	self ScrAgentSetGoalRadius(24);		// dog will get real twitchy about stopping if this falls below 16.
}

init()
{
	self.animCBs = SpawnStruct();
	self.animCBs.OnEnter = [];
	self.animCBs.OnEnter[ "idle" ] = maps\mp\agents\dog\_dog_idle::main;
	self.animCBs.OnEnter[ "move" ] = maps\mp\agents\dog\_dog_move::main;
	self.animCBs.OnEnter[ "traverse" ] = maps\mp\agents\dog\_dog_traverse::main;
	self.animCBs.OnEnter[ "melee" ] = maps\mp\agents\dog\_dog_melee::main;

	self.animCBs.OnExit = [];
	self.animCBs.OnExit[ "idle" ] = maps\mp\agents\dog\_dog_idle::end_script;
	self.animCBs.OnExit[ "move" ] = maps\mp\agents\dog\_dog_move::end_script;
	self.animCBs.OnExit[ "melee" ] = maps\mp\agents\dog\_dog_melee::end_script;
	self.animCBs.OnExit[ "traverse" ] = maps\mp\agents\dog\_dog_traverse::end_script;
	
	self.watchAttackStateFunc = ::watchAttackState;

	self.aiState = "idle";
	self.moveMode = "fastwalk";

	self.radius = 15;
	self.height = 40;
}

onEnterAnimState( prevState, nextState )
{
	self notify( "killanimscript" );

	if ( !IsDefined( self.animCBs.OnEnter[ nextState ] ) )
		return;

	if ( prevState == nextState && ( nextState != "traverse" ) )
		return;

	if ( IsDefined( self.animCBs.OnExit[ prevState ] ) )
		self [[ self.animCBs.OnExit[ prevState ] ]] ();

	ExitAIState( self.aiState );

	self.aiState = nextState;

	EnterAIState( nextState );

	self [[ self.animCBs.OnEnter[ nextState ] ]]();
}

think()
{
	self endon("timeoutRetreat");
	self endon( "death" );
	level endon( "game_ended" );

	if ( IsDefined( self.owner ) )
	{
		self endon( "owner_disconnect" );
		self thread destroyOnOwnerDisconnect( self.owner );
	}

	self thread [[self.watchAttackStateFunc]]();
	self thread MonitorFlash();

	while ( true )
	{
		/#
		if ( self ProcessDebugMode() )
			continue;
		#/

		if ( self.aiState != "melee" && !self.stateLocked && self readyToMeleeTarget() && !self DidPastMeleeFail() )
			self ScrAgentBeginMelee( self.curMeleeTarget );

		switch ( self.aiState )
		{
		case "idle":
			self updateIdle();
			break;
		case "move":
			self updateMove();
			break;
		case "melee":
			self updateMelee();
			break;
		}
		wait( 0.05 );
	}
}

DidPastPursuitFail( enemy )
{
	assert( IsDefined( enemy ) );

	if ( IsDefined( self.curMeleeTarget ) && enemy != self.curMeleeTarget )
		return false;

	if ( !IsDefined( self.lastPursuitFailedPos ) || !IsDefined( self.lastPursuitFailedMyPos ) )
		return false;

	if ( Distance2DSquared( enemy.origin, self.lastPursuitFailedPos ) > 4 )
		return false;
		
	if ( self.bLastPursuitFailedPosBad )
		return true;

	if ( DistanceSquared( self.origin, self.lastPursuitFailedMyPos ) > 64 * 64 && GetTime() - self.lastPursuitFailedTime > 2000 )
		return false;

	return true;
}

DidPastMeleeFail()
{
	assert( IsDefined( self.curMeleeTarget ) );

	if ( IsDefined( self.lastMeleeFailedPos ) && IsDefined( self.lastMeleeFailedMyPos )
		&& Distance2DSquared( self.curMeleeTarget.origin, self.lastMeleeFailedPos ) < 4 
		&& DistanceSquared( self.origin, self.lastMeleeFailedMyPos ) < 50 * 50 )
		return true;

	if ( self WantToAttackTargetButCant( false ) )
		return true;

	return false;
}

enterAIState( state )
{
	self ExitAIState( self.aiState );
	self.aiState = state;

	switch ( state )
	{
	case "idle":
		self.moveState = "idle";
		self.bHasBadPath = false;
		break;
	case "move":
		self.moveState = "follow";
		break;
	case "melee":
		break;
	default:
		break;
	}
}

ExitAIState( state )
{
	switch ( state )
	{
	case "move":
		self.ownerPrevPos = undefined;
		break;
	default:
		break;
	}
}


updateIdle()
{
	self UpdateMoveState();
}

updateMove()
{
	self UpdateMoveState();
}

updateMelee()
{
	self endon("timeoutRetreat");
	self ScrAgentSetGoalPos( self.origin );
}

UpdateMoveState()
{
	self endon("timeoutRetreat");
	if ( self.bLockGoalPos )
		return;

	self.prevMoveState = self.moveState;

	attackPoint = undefined;
	bRefreshGoal = false;
	bWantedPursuitButFollowInstead = false;

	cBadPathTimeOut = 500;
	if ( self.bHasBadPath && GetTime() - self.lastBadPathTime < cBadPathTimeOut )
	{
		self.moveState = "follow";
		bRefreshGoal = true;
	}
	else
	{
		self.moveState = self GetMoveState();
	}

	if ( self.moveState == "pursuit" )
	{
		attackPoint = self GetAttackPoint( self.enemy );
		bLastBadMeleeTarget = false;
		if ( IsDefined( self.lastBadPathTime ) && ( GetTime() - self.lastBadPathTime < 3000 ) )
		{
			if ( Distance2DSquared( attackPoint, self.lastBadPathGoal ) < 16 )
				bLastBadMeleeTarget = true;
			else if ( IsDefined( self.lastBadPathMoveState ) && self.lastBadPathMoveState == "pursuit" && Distance2DSquared( self.lastBadPathUltimateGoal, self.enemy.origin ) < 16 )
				bLastBadMeleeTarget = true;
		}
		if ( bLastBadMeleeTarget )
		{
			self.moveState = "follow";
			bWantedPursuitButFollowInstead = true;
		}
		else if ( self wantToAttackTargetButCant( true ) )
		{
			self.moveState = "follow";
			bWantedPursuitButFollowInstead = true;
		}
		else if ( self DidPastPursuitFail( self.enemy ) )
		{
			self.moveState = "follow";
			bWantedPursuitButFollowInstead = true;
		}
	}

	self SetPastPursuitFailed( bWantedPursuitButFollowInstead );

	if ( self.moveState == "follow" )
	{
		self.curMeleeTarget = undefined;
		self.moveMode = self GetFollowMoveMode( self.moveMode );
		self.bArrivalsEnabled = true;

		myPos = self GetPathGoalPos();
		if ( !IsDefined( myPos ) )
			myPos = self.origin;

		// make sure the owner isn't in free spectator leading the dog around, even if that is fun
		if ( isdefined ( self.owner ) && self.owner.sessionstate == "spectator" )
			return;

		if ( GetTime() - self.timeOfLastDamage < 5000 )
			bRefreshGoal = true;
		
		// also take into account the owner's stance changes
		currStance = self.owner GetStance();
		if ( !IsDefined( self.owner.prevStance ) && IsDefined( self.owner ) )
			self.owner.prevStance = currStance;

		bOwnerHasMoved = !IsDefined( self.ownerPrevPos ) || Distance2DSquared( self.ownerPrevPos, self.owner.origin ) > 100;
		if ( bOwnerHasMoved )
			self.ownerPrevPos = self.owner.origin;

		distFromOwnerSq = Distance2DSquared( myPos, self.owner.origin );
		if ( bRefreshGoal || ( distFromOwnerSq > self.ownerRadiusSq && bOwnerHasMoved ) || self.owner.prevStance != currStance || (self.prevMoveState != "idle" && self.prevMoveState != self.moveState) )
		{
			//TODO make sure point is not owned by any other dog..
			PickAgain = true;
			TempGoalPos = self findPointNearOwner();
			
			trigger = spawn( "trigger_radius", self.owner.origin, 0, 256, 256);
			nodesNearPlayer = GetNodesInTrigger( trigger );
			nodesNearPlayer = SortByDistance(nodesNearPlayer, self.owner.origin);
			
			while(PickAgain == true)
			{
				PickAgain = false;
				
				foreach(dog in level.InstinctDogs)
				{
					if(PickAgain == false)
					{
						OtherGoalPos = dog ScrAgentGetGoalPos();
						DistanceBetweenPoints = Distance(TempGoalPos, OtherGoalPos);
						if(DistanceBetweenPoints<60)
							PickAgain=true;
					}
				}
				
				if(PickAgain==true)
				{
					if(nodesNearPlayer.size>0)
					{
						for(i=0;i<nodesNearPlayer.size;i++)
						{
							GoodNode = true;
							foreach(dog in level.InstinctDogs)
							{
								if(GoodNode==true)
								{
									OtherGoalPos = dog ScrAgentGetGoalPos();
									
									DistanceBetweenPoints = Distance(nodesNearPlayer[i].origin, OtherGoalPos);
									DistanceBetweenPointToPlayer = Distance(nodesNearPlayer[i].origin, self.owner.origin);
									if(DistanceBetweenPoints<60 || DistanceBetweenPointToPlayer<128)
										GoodNode = false;
								}
							}
							if(GoodNode==true)
							{
								self ScrAgentSetGoalPos( nodesNearPlayer[i].origin );
								PickAgain=false;
								break;
							}
						}
					}
					else
					{
						//fall back if the player is not near any nodes.  Just pick something random.
						empGoalPos = GetRandomNodeDestination(self.owner.origin,self.owner.angles);
						self ScrAgentSetGoalPos( empGoalPos.origin );
						PickAgain=false;
					}
				}
				else 
					self ScrAgentSetGoalPos( TempGoalPos );
					 
				waitframe();
			}
		
			self.owner.prevStance = currStance;
			trigger delete();
		}
	}
	else if ( self.moveState == "pursuit" )
	{
		self.curMeleeTarget = self.enemy;
		self.moveMode = "sprint";
		self.bArrivalsEnabled = false;

		assert( IsDefined( attackPoint ) );
		self ScrAgentSetGoalPos( attackPoint );
	}
}


GetMoveState( prevState )
{
	if ( IsDefined( self.enemy ) )
	{
		if ( IsDefined( self.favoriteEnemy ) && self.enemy == self.favoriteEnemy )
			return "pursuit";

		if ( abs( self.origin[2] - self.enemy.origin[2] ) < self.warningZHeight && Distance2DSquared( self.enemy.origin, self.origin ) < self.attackRadiusSq )
			return "pursuit";

		if ( IsDefined( self.curMeleeTarget ) && self.curMeleeTarget == self.enemy )
		{
			if ( Distance2DSquared( self.curMeleeTarget.origin, self.origin ) < self.keepPursuingTargetRadiusSq )
				return "pursuit";
		}
	}

	return "follow";
}


SetPastPursuitFailed( bWantedPursuitButFollowInstead )
{
	if ( bWantedPursuitButFollowInstead )
	{
		if ( !IsDefined( self.lastPursuitFailedPos ) )
		{
			self.lastPursuitFailedPos = self.enemy.origin;
			self.lastPursuitFailedMyPos = self.origin;
			groundPos = self DropPosToGround( self.enemy.origin );
			self.bLastPursuitFailedPosBad = !IsDefined( groundPos );
			self.lastPursuitFailedTime = GetTime();
		}
	}
	else
	{
		self.lastPursuitFailedPos = undefined;
		self.lastPursuitFailedMyPos = undefined;
		self.bLastPursuitFailedPosBad = undefined;
		self.lastPursuitFailedTime = undefined;
	}

}


WaitForBadPath()
{
	self endon( "death" );
	level endon( "game_ended" );

	while ( true )
	{
		self waittill( "bad_path", badGoalPos );
		self.bHasBadPath = true;
		self.lastBadPathTime = GetTime();
		self.lastBadPathGoal = badGoalPos;
		self.lastBadPathMoveState = self.moveState;
		if ( self.moveState == "follow" && IsDefined( self.owner ) )
			self.lastBadPathUltimateGoal = self.owner.origin;
		else if ( self.moveState == "pursuit" && IsDefined( self.enemy ) )
			self.lastBadPathUltimateGoal = self.enemy.origin;
	}
}

WaitForPathSet()
{
	self endon( "death" );
	level endon( "game_ended" );

	while ( true )
	{
		self waittill( "path_set" );
		self.bHasBadPath = false;
	}
}

GetFollowMoveMode( currentMoveMode )
{
	cRunToFastWalkDistSq = 200 * 200;
	cFastWalkToRunDistSq = 256 * 256;

	pathGoalPos = self GetPathGoalPos();
	if ( IsDefined( pathGoalPos ) )
	{
		distSq = DistanceSquared( pathGoalPos, self.origin );
		if ( currentMoveMode == "run" || currentMoveMode == "sprint" )
		{
			if ( distSq < cRunToFastWalkDistSq )
				return "fastwalk";
			else if ( currentMoveMode == "sprint" )
				return "run";
		}
		else if ( currentMoveMode == "fastwalk" )
		{
			if ( distSq > cFastWalkToRunDistSq )
				return "run";
		}
	}

	return currentMoveMode;
}


IsWithinAttackHeight( targetPos )
{
	zDiff = targetPos[2] - self.origin[2];

	return zDiff <= self.attackZHeight && zDiff >= self.attackZHeightDown;
}


WantToAttackTargetButCant( bCheckSight )
{
	if ( !IsDefined( self.curMeleeTarget ) )
		return false;

	return !self IsWithinAttackHeight( self.curMeleeTarget.origin )
		&& Distance2DSquared( self.origin, self.curMeleeTarget.origin ) < self.meleeRadiusSq * 0.75 * 0.75
		&& ( !bCheckSight || self AgentCanSeeSentient( self.curMeleeTarget ) );
}

readyToMeleeTarget()
{
	if ( !IsDefined( self.curMeleeTarget ) )
		return false;

	if ( !maps\mp\_utility::IsReallyAlive( self.curMeleeTarget ) )
		return false;

	if ( self.aiState == "traverse" )
		return false;

	if ( Distance2DSquared( self.origin, self.curMeleeTarget.origin ) > self.meleeRadiusSq )
		return false;

	if ( !self IsWithinAttackHeight( self.curMeleeTarget.origin ) )
		return false;

	return true;
}

//wantsToContinueRunningToTarget()
//{
//	if ( !IsDefined( self.curMeleeTarget ) || !IsDefined( self.enemy ) )
//		return false;
//
//	if ( self.curMeleeTarget != self.enemy )
//		return false;
//
//	if ( DistanceSquared( self.origin, self.curMeleeTarget.origin ) > self.warningRadiusSq )
//		return false;
//
//	if ( IsDefined( self.owner ) )
//	{
//		if ( !IsDefined( self.favoriteEnemy ) || self.curMeleeTarget != self.favoriteEnemy )
//		{
//			if ( DistanceSquared( self.owner.origin, self.curMeleeTarget.origin ) > self.keepPursuingTargetRadiusSq )
//				return false;
//		}
//	}
//
//	return true;
//}

//wantsToAttackTarget()
//{
//	if ( !IsDefined( self.enemy ) )
//		return false;
//
//	if( self.forceAttack )
//		return true;
//
//	// first make sure the enemy is within the same height as the dog
//	if( abs( self.origin[ 2 ] - self.enemy.origin[ 2 ] ) > self.warningZHeight )
//		return false;
//
//	if ( DistanceSquared( self.origin, self.enemy.origin ) > self.attackRadiusSq )
//		return false;
//
//	if ( IsDefined( self.owner ) )
//	{
//		if ( !IsDefined( self.favoriteEnemy ) || self.enemy != self.favoriteEnemy )
//		{
//			if ( DistanceSquared( self.owner.origin, self.enemy.origin ) > self.keepPursuingTargetRadiusSq )
//				return false;
//		}
//	}
//
//	return true;
//}

wantsToGrowlAtTarget()
{
	if ( !IsDefined( self.enemy ) )
		return false;

	// first make sure the enemy is within the same height as the dog, or i can see him (which means he's not on a different 'floor')
	if( abs( self.origin[ 2 ] - self.enemy.origin[ 2 ] ) <= self.warningZHeight || self AgentCanSeeSentient( self.enemy ) )
	{
		// now see if the enemy is within the warning radius
		distSq = Distance2DSquared( self.origin, self.enemy.origin );
		if ( distSq < self.warningRadiusSq )
			return true;
	}

	return false;
}

getAttackPoint( enemy )
{
	meToTarget = enemy.origin - self.origin;
	meToTarget = VectorNormalize( meToTarget );

	pathGoalPos = self GetPathGoalPos();
	closeEnough = self.attackOffset + 4;
	if ( IsDefined( pathGoalPos ) && Distance2DSquared( pathGoalPos, enemy.origin ) < closeEnough * closeEnough && self CanMovePointToPoint( enemy.origin, pathGoalPos ) )
		return pathGoalPos;

	attackPoint = enemy.origin - meToTarget * self.attackOffset;
	attackPoint = self DropPosToGround( attackPoint );

	if ( !IsDefined( attackPoint ) )
		return enemy.origin;

	if ( !self CanMovePointToPoint( enemy.origin, attackPoint ) )
	{
		enemyFacing = AnglesToForward( enemy.angles );
		attackPoint = enemy.origin + enemyFacing * self.attackOffset;

		if ( !self CanMovePointToPoint( enemy.origin, attackPoint ) )
			return enemy.origin;
	}

	return attackPoint;
}

// > 0 right
cross2D( a, b )
{
	return a[0] * b[1] - b[0] * a[1];
}

// finds a valid point near the owner with two traces (nearest node and ground trace) by finding
// a point on the pathgraph somewhere.  nodes and points on node links are pre-determined to be valid.
// except i might get in trouble if the player's closest node is a negotation begin node...
findPointNearOwner()
{
	assert( IsDefined( self.owner ) );

	ownerToMe = VectorNormalize( self.origin - self.owner.origin );
	ownerForward = AnglesToForward( self.owner.angles );
	ownerForward = ( ownerForward[0], ownerForward[1], 0 );
	ownerForward = VectorNormalize( ownerForward );
	currentDirFromOwner = cross2D( ownerToMe, ownerForward );

	nodeClosestToOwner = GetClosestNodeInSight( self.owner.origin );
	if ( !IsDefined( nodeClosestToOwner ) )
		return self.origin;

	links = GetLinkedNodes( nodeClosestToOwner );

	distanceWeight = 5;
	angleWeight = 10;
	sideWeight = 15;
	avoidWeight = -15;

	bDoAvoid = ( GetTime() - self.timeOfLastDamage ) < 5000;

	// prefer nodes to side which i'm already on (and slightly ahead).  else behind owner.  else in front of.
	bestScore = 0;
	bestLink = 0;

	links[ links.size ] = nodeClosestToOwner;
	foreach ( link in links )
	{
		score = 0;

		ownerToLink = link.origin - self.owner.origin;
		ownerToLinkDist = Length( ownerToLink );
		if ( ownerToLinkDist >= self.preferredOffsetFromOwner )
			score += distanceWeight;
		else if ( ownerToLinkDist < self.minOffsetFromOwner )
		{
			scale = 1 - ( self.minOffsetFromOwner - ownerToLinkDist ) / self.minOffsetFromOwner;
			score += distanceWeight * scale * scale;
		}
		else
			score += distanceWeight * ownerToLinkDist / self.preferredOffsetFromOwner;

		if(ownerToLinkDist==0)
			ownerToLinkDist = 1;
		
		ownerToLink = ownerToLink / ownerToLinkDist;
		angleCos = VectorDot( ownerForward, ownerToLink );
		
		// situate the dog in position depending on the owner's stance
		//	standing == ahead
		//	crouching == next to
		//	prone == behind
		currStance = self.owner GetStance();
		switch ( currStance )
		{
		case "stand":
			if( angleCos < cos( 35 ) && angleCos > cos( 45 ) )
				score += angleWeight;
			break;
		case "crouch":
			if( angleCos < cos( 75 ) && angleCos > cos( 90 ) )
				score += angleWeight;
			break;
		case "prone":
			if( angleCos < cos( 125 ) && angleCos > cos( 135 ) )
				score += angleWeight;
			break;
		}
		
		dirFromOwner = cross2D( ownerToLink, ownerForward );
		if ( dirFromOwner * currentDirFromOwner > 0 )		// i.e. both the same sign
			score += sideWeight;

		if ( bDoAvoid )
		{
			assert( IsDefined( self.damagedOwnerToMe ) );
			avoid = VectorDot( self.damagedOwnerToMe, ownerToLink );
			score += avoid * avoidWeight;
		}

		if ( score > bestScore )
		{
			bestScore = score;
			bestLink = link;
		}
	}

	if ( !IsDefined( bestLink ) )
		return self.origin;

	ownerToNode = bestLink.origin - self.owner.origin;
	ownerToNodeDist = Length( ownerToNode );
	if ( ownerToNodeDist > self.preferredOffsetFromOwner )
	{
		ownerToOwnerNode = nodeClosestToOwner.origin - self.owner.origin;
		if ( VectorDot( ownerToOwnerNode, ownerToNode / ownerToNodeDist ) < 0 )	// owner is between nodeClosest and bestLink.
		{
			resultPos = bestLink.origin;
		}
		else
		{
			ownerNodeToNode = VectorNormalize( bestLink.origin - nodeClosestToOwner.origin );
			resultPos = nodeClosestToOwner.origin + ownerNodeToNode * self.preferredOffsetFromOwner;
		}
	}
	else
	{
		resultPos = bestLink.origin;
	}
	
	resultPos = self DropPosToGround( resultPos );

	if ( !IsDefined( resultPos ) )	// technically, we should probably go down through the list of nodes until we find a good one.
		return self.origin;

	if ( self.bHasBadPath && Distance2DSquared( resultPos, self.lastBadPathGoal ) < 4 )
		return self.origin;

	return resultPos;
}

destroyOnOwnerDisconnect( owner )
{
	self endon( "death" );
	owner waittill_any( "disconnect", "joined_team" );
	
	self notify( "owner_disconnect" );

	// Wait till host migration finishes before suiciding
	if ( maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone() )
		wait 0.05;
	
	self notify( "killanimscript" );
	if ( IsDefined( self.animCBs.OnExit[ self.aiState ] ) )
		self [[ self.animCBs.OnExit[ self.aiState ] ]] ();

	self Suicide();
}

watchAttackState() // self == dog
{
	self endon( "death" );
	level endon( "game_ended" );

	while ( true )
	{
		if ( self.aiState == "melee" )
		{
			if ( self.attackState != "melee" )
			{
				self.attackState = "melee";
				self SetSoundState( undefined );
			}
		}
		else if ( self.moveState == "pursuit" ) //( self wantsToAttackTarget() )
		{
			if ( self.attackState != "attacking" )
			{
				self.attackState = "attacking";
				self SetSoundState( "bark", "attacking" );
			}
		}
		else //if( !self wantsToAttackTarget() )
		{
			if ( self.attackState != "warning" )
			{
				if ( self wantsToGrowlAtTarget() )
				{
					self.attackState = "warning";
					self SetSoundState( "growl", "warning" );
				}
				else
				{
					self.attackState = self.aiState;
					self SetSoundState( "pant" );
				}
			}
			else
			{
				if ( !self wantsToGrowlAtTarget() )
				{
					self.attackState = self.aiState;
					self SetSoundState( "pant" );
				}
			}
		}

		wait( 0.05 );
	}
}

SetSoundState( state, attackState )
{
	if ( !IsDefined( state ) )
	{
		self notify( "end_dog_sound" );
		self.soundState = undefined;
		return;
	}

	if ( !IsDefined( self.soundState ) || self.soundState != state )
	{
		self notify( "end_dog_sound" );
		self.soundState = state;

		if ( state == "bark" )
		{
			self thread playBark( attackState );
		}
		else if ( state == "growl" )
		{
			self thread playGrowl( attackState );
		}
		else if ( state == "pant" )
		{
			self thread playPanting();
		}
		else
		{
			assertmsg( "unknown sound state " + state );
		}
	}
}

playBark( state ) // self == dog
{
	self endon( "death" );
	level endon( "game_ended" );
	self endon( "end_dog_sound" );

	if( !isDefined( self.barking_sound ) )
	{
		//self PlaySoundOnMovingEnt( "anml_dog_bark" );
		self.barking_sound = true;
		self thread watchBarking();
	}
}

watchBarking() // self == dog
{
	self endon( "death" );
	level endon( "game_ended" );
	self endon( "end_dog_sound" );

	wait( RandomIntRange( 5, 10 ) );
	self.barking_sound = undefined;
}

playGrowl( state ) // self == dog
{
	self endon( "death" );
	level endon( "game_ended" );
	self endon( "end_dog_sound" );

	if ( IsDefined( self.lastGrowlPlayedTime ) && GetTime() - self.lastGrowlPlayedTime < 3000 )
		wait( 3 );

	// while the dog is in this state randomly play growl
	while ( true )
	{
		self.lastGrowlPlayedTime = GetTime();
		//self PlaySoundOnMovingEnt( "anml_dog_growl" );

		wait( RandomIntRange( 3, 6 ) );
	}
}

playPanting( state )
{
	self endon( "death" );
	level endon( "game_ended" );
	self endon( "end_dog_sound" );

	if ( IsDefined( self.lastPantPlayedTime ) && GetTime() - self.lastPantPlayedTime < 3000 )
		wait( 3 );

	self.lastPantPlayedTime = GetTime();

	while ( true )
	{
		// idle can handle panting on its own.  just check periodically.
		if ( self.aiState == "idle" )
		{
			wait ( 3 );
			continue;
		}

		self.lastPantPlayedTime = GetTime();
		//if ( self.moveMode == "run" || self.moveMode == "sprint" )
		//	self PlaySoundOnMovingEnt( "anml_dog_pants_mp_fast" );
		//else
			//self PlaySoundOnMovingEnt( "anml_dog_pants_mp_med" );

		wait( RandomIntRange( 6, 8 ) );
	}
}

watchOwnerDamage() // self == dog
{
	self endon( "death" );
	level endon( "game_ended" );

	while( true )
	{
		if( !IsDefined( self.owner ) )
			return;
		
		self.owner waittill( "damage", damage, attacker );

		if ( IsPlayer( attacker ) && attacker != self.owner )
		{
			// is the dog already attacking?
			if ( self.attackState == "attacking" )
				continue;

			// is the dog close to the owner?
			if ( DistanceSquared( self.owner.origin, self.origin ) > self.ownerDamagedRadiusSq )
				continue;

			// is the attacker within the owner damaged range?
			if ( DistanceSquared( self.owner.origin, attacker.origin ) > self.ownerDamagedRadiusSq )
				continue;

			self.favoriteEnemy = attacker;
			self.forceAttack = true;
			self thread watchFavoriteEnemyDeath();
		}
	}
}

watchOwnerDeath() // self == dog
{
	self endon( "death" );
	level endon( "game_ended" );

	while( true )
	{
		if( !IsDefined( self.owner ) )
			return;
		
		self.owner waittill( "death" );

		switch( level.gameType )
		{
			case "sd":
				// the dog needs to die when the owner dies because killstreaks go away when the owner dies in sd
				killDog();
				break;
			case "sr":
				// the dog needs to die when the owner is eliminated because killstreaks go away when the owner dies in sr
				result = level waittill_any_return( "sr_player_eliminated", "sr_player_respawned" );
				if( IsDefined( result ) && result == "sr_player_eliminated" )
					killDog();
				break;
		}
	}
}

watchOwnerTeamChange() // self == dog
{
	self endon( "death" );
	level endon( "game_ended" );

	while( true )
	{
		if( !IsDefined( self.owner ) )
			return;
		
		result = self.owner waittill_any_return_no_endon_death( "joined_team", "joined_spectators" );

		if( IsDefined( result ) && ( result == "joined_team" || result == "joined_spectators" ) )
			killDog();
	}
}


watchFavoriteEnemyDeath() // self == dog
{
	self notify( "watchFavoriteEnemyDeath" );
	self endon( "watchFavoriteEnemyDeath" );

	self endon( "death" );

	self.favoriteEnemy waittill_any_timeout( 5.0, "death", "disconnect" );

	self.favoriteEnemy = undefined;
	self.forceAttack = false;
}

OnDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset )
{
	self.timeOfLastDamage = GetTime();
	if ( IsDefined( self.owner ) )
		self.damagedOwnerToMe = VectorNormalize( self.origin - self.owner.origin );

	if ( self ShouldPlayHitReaction( iDamage, sWeapon, sMeansOfDeath ) )
	{
		switch ( self.aiState )
		{
		case "idle":
			self thread maps\mp\agents\dog\_dog_idle::OnDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset );
			break;
		case "move":
			self thread maps\mp\agents\dog\_dog_move::OnDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset );
			break;
		}
	}
}

ShouldPlayHitReaction( iDamage, sWeapon, sMeansOfDeath )
{
	// being specific on certain damage to do the damage reaction
	if ( IsDefined( sWeapon ) && WeaponClass( sWeapon ) == "sniper" )
		return true;
	if ( IsDefined( sMeansOfDeath ) && IsExplosiveDamageMOD( sMeansOfDeath ) && iDamage >= 10 )
		return true;
	if ( IsDefined( sMeansOfDeath ) && sMeansOfDeath == "MOD_MELEE" )
		return true;
	if ( IsDefined( sWeapon ) && sWeapon == "concussion_grenade_mp" )
		return true;
	// flash grenade is monitored using MonitorFlash()

	return false;		
}

MonitorFlash()
{
	self endon( "death" );

	while ( true )
	{
		self waittill( "flashbang", origin, percent_distance, percent_angle, attacker, teamName, extraDuration );

		if ( IsDefined( attacker ) && attacker == self.owner )
			continue;

		switch ( self.aiState )
		{
		case "idle":
			self maps\mp\agents\dog\_dog_idle::onFlashbanged();
			break;
		case "move":
			self maps\mp\agents\dog\_dog_move::onFlashbanged();
			break;
		}
	}
}

/#
debug_dog() // self == dog
{
	self endon( "death" );
	level endon( "game_ended" );

	while( true )
	{
		if( GetDvarInt( "scr_debugdog" ) > 0 )
		{
			start = self.origin;
			end = self.origin;
			if( IsDefined( self.enemy ) )
				end = self.enemy.origin;
			color = [ 1, 1, 1 ];

			switch( self.attackState )
			{
			case "idle":
				color = [ 1, 1, 1 ];
				break;
			case "move":
				color = [ 0, 1, 0 ];
				break;
			case "traverse":
				color = [ 0.5, 0.5, 0.5 ];
				break;
			case "melee":
			case "attacking":
				color = [ 1, 0, 0 ];
				break;
			case "warning":
				color = [ 0.8, 0.8, 0 ];
				break;
			default:
				break;
			}
			
			Print3d( self.origin + ( 0, 0, 10 ), self.attackState, ( color[0], color[1], color[2] ) );
			Line( start, end, ( color[0], color[1], color[2] ) );
		}

		wait( 0.05 );
	}
}
#/

/#
ProcessDebugMode()
{
	if ( getdvarint( "scr_dogDebugMode" ) == 1 )
	{
		if ( !IsDefined( self.bDebugMode ) || !self.bDebugMode )
			self thread DoDebugMode();
		self.bDebugMode = true;
		wait( 0.05 );
		return true;
	}
	else
	{
		if ( IsDefined( self.bDebugMode ) && self.bDebugMode )
			self EndDebugMode();
		self.bDebugMode = false;
		return false;
	}
}

DoDebugPrint3D( str, color )
{
	self notify( "enddebugprint3D" );
	level endon( "game_ended" );
	self endon( "death" );
	self endon( "enddebugprint3D" );

	nTimes = 0;
	while ( nTimes < 60 )
	{
		printPos = self.origin + (0,0,48);
		print3D( printPos, str, color, 1, 1 );
		nTimes++;
		wait( 0.05 );
	}
}

DebugDrawHitPos( pos, normal, color )
{
	self notify( "enddebugdrawhitpos" );
	level endon( "game_ended" );
	self endon( "death" );
	self endon( "enddebugdrawhitpos" );

	lineEnd = pos + normal * 100;

	while ( true )
	{
		Line( pos, lineEnd, color, 1, true, 1 );
		wait( 0.05 );
	}
}

DoDebugMode()
{
	self endon( "death" );
	level endon( "game_ended" );
	self endon( "enddebugmode" );

	if ( IsDefined( self.owner ) && IsPlayer( self.owner ) )
		player = self.owner;
	else
		player = level.players[0];

	if ( IsAI( player ) )
		return;

	red = (255,0,0);
	green = (0,255,0);

	while ( true )
	{
		player NotifyOnPlayerCommand( "debug_setgoal", "+usereload" );
		player waittill( "debug_setgoal" );

		viewAngles = player GetPlayerAngles();
		viewEye = player GetEye();

		playerForward = AnglesToForward( viewAngles );
		traceEndPos = viewEye + playerForward * 2048;

		trace = self AIPhysicsTrace( viewEye, traceEndPos, 1, 2, true, true );

		if ( trace[ "fraction" ] < 1 )
		{
			hitNormal = trace[ "normal" ];
			hitPos = trace[ "position" ];
			drawColor = red;
			if ( hitNormal[2] > 0.707 )
			{
				goalPos = self DropPosToGround( hitPos );
				if ( IsDefined( goalPos ) )
				{
					closestNode = GetClosestNodeInSight( goalPos );
					if ( IsDefined( closestNode ) )
					{
						self ScrAgentSetGoalPos( goalPos );
						drawColor = green;
						self notify("enddebugprint3D");
						hitPos = goalPos;
					}
					else
					{
						thread DoDebugPrint3D( "unable to find closest node to pos. (cannot path.)", red );
						self ScrAgentSetGoalPos( self.origin );
					}
				}
				else
				{
					thread DoDebugPrint3D( "dog cannot stand there.", red );
					self ScrAgentSetGoalPos( self.origin );
				}
			}
			else
			{
				thread DoDebugPrint3D( "hit pos too steep.", red );
				self ScrAgentSetGoalPos( self.origin );
			}

			thread DebugDrawHitPos( hitPos, hitNormal, drawColor );
		}
		else
		{
			thread DoDebugPrint3D( "trace did not hit anything.", red );
			self ScrAgentSetGoalPos( self.origin );
			self notify( "enddebugdrawhitpos" );
		}
	}
}

EndDebugMode()
{
	self notify("enddebugprint3D");
	self notify("enddebugdrawhitpos");
	self notify("enddebugmode");
}
#/
