#include maps\mp\agents\_scriptedAgents;
#include maps\mp\_utility;

main()
{
	self endon( "death" );
	self endon( "killanimscript" );
	
	assert( IsDefined( self.curMeleeTarget ) );

	self.curMeleeTarget endon( "disconnect" );
	
	// get desired end pos.
	meToTarget = self.curMeleeTarget.origin - self.origin;
	distMeToTarget = Length( meToTarget );

	bTestCanMove = true;
	if ( distMeToTarget < self.attackOffset )
	{
		attackPos = self.origin;
		bTestCanMove = false;
	}
	else
	{
		meToTarget = meToTarget / distMeToTarget;
		attackPos = self.curMeleeTarget.origin - meToTarget * self.attackOffset;
	}

	bLerp = false;
	
	startPos = self.origin + (0,0,30);
	endPos = self.curMeleeTarget.origin + (0,0,30);
	hitPos = PhysicsTrace( startPos, endPos );
	if ( DistanceSquared( hitPos, endPos ) > 1 )
	{
		self MeleeFailed();
		return;
	}

	if ( bTestCanMove )
		bCanMoveToAttackPos = self CanMovePointToPoint( self.origin, attackPos );
	else
		bCanMoveToAttackPos = true;

	animEntry = undefined;
	if ( !bCanMoveToAttackPos )
	{
		bShouldDoExtendedKill = false;
	}
	else
	{
		animEntry = self ShouldDoExtendedKill( self.curMeleeTarget );
		bShouldDoExtendedKill = IsDefined( animEntry );
	}

	self.bLockGoalPos = true;
	
	if ( bShouldDoExtendedKill )
	{
		assert( IsDefined( animEntry ) );
		self DoExtendedKill( animEntry );
	}
	else
	{
		self DoStandardKill( attackPos, bCanMoveToAttackPos );
	}
}

end_script()
{
	self ScrAgentSetAnimScale( 1, 1 );
	self.bLockGoalPos = false;
}

GetMeleeAnimState()
{
	return "attack_run_and_jump";
}

// returns kill direction, if any.
ShouldDoExtendedKill( victim )
{
	if( !self.enableExtendedKill )
		return undefined;
		
	cMaxHeightDiff = 4;

	if ( !IsGameParticipant( victim ) )	// humans only.
		return undefined;
	if ( self IsProtectedByRiotshield( victim ) )
		return undefined;
	if ( victim IsJuggernaut() )
		return undefined;

	victimToMe = self.origin - victim.origin;
	if ( abs( victimToMe[2] ) > cMaxHeightDiff )
		return undefined;

	victimToMe2D = VectorNormalize( (victimToMe[0], victimToMe[1], 0) );
	victimFacing = AnglesToForward( victim.angles );
	angleToMe = VectorDot( victimFacing, victimToMe2D );

	if ( angleToMe > 0.707 )
	{
		animEntry = 0;		// front
		snappedVictimToMe = RotateVector( ( 1, 0, 0 ), victim.angles );
	}
	else if ( angleToMe < -0.707 )
	{
		animEntry = 1;		// back
		snappedVictimToMe = RotateVector( (-1, 0, 0), victim.angles );
	}
	else
	{
		cross = maps\mp\agents\dog\_dog_think::cross2D( victimToMe, victimFacing );
		if ( cross > 0 )
		{
			animEntry = 3;	// right
			snappedVictimToMe = RotateVector( (0, -1, 0), victim.angles );
		}
		else
		{
			animEntry = 2;	// left
			snappedVictimToMe = RotateVector( (0, 1, 0), victim.angles );
		}
	}

	if ( animEntry == 1 )
		cClearanceRequired = 128;
	else
		cClearanceRequired = 96;
	landPos = victim.origin - cClearanceRequired * snappedVictimToMe;

	landPosDropped = self DropPosToGround( landPos );
	if ( !IsDefined( landPosDropped ) )
		return undefined;

	if ( abs( landPosDropped[2] - landPos[2] ) > cMaxHeightDiff )
		return undefined;

	if ( !self AIPhysicsTracePassed( victim.origin + (0,0,4), landPosDropped + (0,0,4), self.radius, self.height ) )
		return undefined;

	return animEntry;
}

DoExtendedKill( animEntry )
{
	meleeAnimState = "attack_extended";

	self DoMeleeDamage( self.curMeleeTarget, self.curMeleeTarget.health, "MOD_MELEE_DOG" );

	attackAnim = self GetAnimEntry( meleeAnimState, animEntry );
	self thread ExtendedKill_StickToVictim( attackAnim, self.curMeleeTarget.origin, self.curMeleeTarget.angles );

//	if ( animEntry == 1 )	// back
//		self PlaySoundOnMovingEnt( "mp_dog_attack_quick_back_npc" );
//	else
//		self PlaySoundOnMovingEnt( "mp_dog_attack_short_npc" );

	self PlayAnimNUntilNotetrack( meleeAnimState, animEntry, "attack", "end" );

	self notify( "kill_stick" );
	self.curMeleeTarget = undefined;

	self ScrAgentSetAnimMode( "anim deltas" );
	self Unlink();
}

ExtendedKill_StickToVictim( attackAnim, targetOrigin, targetAngles )
{
	self endon( "death" );
	self endon( "killanimscript" );
	self endon( "kill_stick" );
	
	wait( 0.05 );		// must wait for anim to kick in before we can properly calculate our offsets.
	assert( IsDefined( self.curMeleeTarget ) );

	if ( IsAlive( self.curMeleeTarget ) )	// godmode, etc.
		return;

	corpse = self.curMeleeTarget GetCorpseEntity();
	assert( IsDefined( corpse ) );
	self LinkTo( corpse );

	self ScrAgentDoAnimRelative( attackAnim, targetOrigin, targetAngles );
}

DoStandardKill( attackPos, bCanMoveToAttackPos )
{
	meleeAnimState = self GetMeleeAnimState();

	bLerp = false;

	if ( !bCanMoveToAttackPos )
	{
		if ( self AgentCanSeeSentient( self.curMeleeTarget ) )
		{
			groundPos = self DropPosToGround( self.curMeleeTarget.origin );
			if ( IsDefined( groundPos ) )
			{
				bLerp = true;
				attackPos = groundPos;	// i'm going to clip the heck through him, but i need a guaranteed safe spot.
			}
			else
			{
				self MeleeFailed();
				return;
			}
		}
		else
		{
			self MeleeFailed();
			return;
		}
	}

	self.lastMeleeFailedMyPos = undefined;
	self.lastMeleeFailedPos = undefined;

	attackAnim = self GetAnimEntry( meleeAnimState, 0 );
	animLength = GetAnimLength( attackAnim );
	meleeNotetracks = GetNotetrackTimes( attackAnim, "dog_melee" );
	if ( meleeNotetracks.size > 0 )
		lerpTime = meleeNotetracks[0] * animLength;
	else
		lerpTime = animLength;

	self ScrAgentDoAnimLerp( self.origin, attackPos, lerpTime );

	self thread UpdateLerpPos( self.curMeleeTarget, lerpTime, bCanMoveToAttackPos );

	self PlayAnimNUntilNotetrack( meleeAnimState, 0, "attack", "dog_melee" );

	self notify( "cancel_updatelerppos" );

	damageDealt = 0;
	if( IsDefined( self.curMeleeTarget ) )
		damageDealt = self.curMeleeTarget.health;
	if( IsDefined( self.meleeDamage ) )
		damageDealt = self.meleeDamage;
	
	if( IsDefined( self.curMeleeTarget ) )
		self DoMeleeDamage( self.curMeleeTarget, damageDealt, "MOD_IMPACT" );

	self.curMeleeTarget = undefined;	// dude's dead now, or soon will be.

	if ( bLerp )
		self ScrAgentSetAnimScale( 0, 1 );
	else
		self ScrAgentSetAnimScale( 1, 1 );

	self ScrAgentSetPhysicsMode( "gravity" );
	self ScrAgentSetAnimMode( "anim deltas" );

	self WaitUntilNotetrack( "attack", "end" );
}

UpdateLerpPos( enemy, lerpTime, bCanMoveToAttackPos )
{
	self endon( "killanimscript" );
	self endon( "death" );
	self endon( "cancel_updatelerppos" );
	enemy endon( "disconnect" );
	enemy endon( "death" );

	timeRemaining = lerpTime;
	interval = 0.05;
	while ( true )
	{
		wait( interval );
		timeRemaining -= interval;

		if ( timeRemaining <= 0 )
			break;

		attackPos = GetUpdatedAttackPos( enemy, bCanMoveToAttackPos );
		if ( !IsDefined( attackPos ) )
			break;

		self ScrAgentDoAnimLerp( self.origin, attackPos, timeRemaining );
	}
}

GetUpdatedAttackPos( enemy, bCanMove )
{
	if ( !bCanMove )
	{
		droppedPos = self DropPosToGround( enemy.origin );
		return droppedPos;
	}
	else
	{
		meToTarget = enemy.origin - self.origin;
		distMeToTarget = Length( meToTarget );

		if ( distMeToTarget < self.attackOffset )
		{
			return self.origin;
		}
		else
		{
			meToTarget = meToTarget / distMeToTarget;
			attackPos = enemy.origin - meToTarget * self.attackOffset;
			if ( self CanMovePointToPoint( self.origin, attackPos ) )
				return attackPos;
			else
				return undefined;
		}
	}
}

IsProtectedByRiotshield( enemy )
{
	if ( enemy maps\mp\_riotshield::hasRiotShield() )
	{
		enemyToMe = self.origin - enemy.origin;
		meToEnemy = VectorNormalize( ( enemyToMe[0], enemyToMe[1], 0 ) );

		enemyFacing = AnglesToForward( enemy.angles );
		angleToMe = VectorDot( enemyFacing, enemyToMe );

		if ( enemy maps\mp\_riotshield::hasRiotShieldEquipped() )
		{
			if ( angleToMe > 0.766 )
				return true;
		}
		else
		{
			if ( angleToMe < -0.766 )
				return true;
		}
	}

	return false;
}

DoMeleeDamage( enemy, damage, meansOfDeath )
{
	if ( self IsProtectedByRiotshield( enemy ) )
		return;

	enemy DoDamage( damage, self.origin, self, self, meansOfDeath );
}

MeleeFailed()
{
	self.lastMeleeFailedMyPos = self.origin;
	self.lastMeleeFailedPos = self.curMeleeTarget.origin;
}