#include "weapon_hlhandgrenade"

namespace cnpc_sawgrunt
{
	
const uint g_MaxAmmoPrimary		= 200;
const string sWeaponName		= "weapon_sawgrunt";
const float SAWGRUNT_MELEE_CD		= 1.0;

const float SAWGRUNT_MELEE_DMG		= 50.0;
const float SAWGRUNT_MELEE_DMG_RANGE	= 115.0;

const array<string> pAttackMissSounds = 
{
	"zombie/claw_miss1.wav",
	"zombie/claw_miss2.wav"
};

const array<string> pPainSounds = 
{
	"fgrunt/gr_pain1.wav",
	"fgrunt/gr_pain2.wav",
	"fgrunt/gr_pain3.wav",
	"fgrunt/gr_pain4.wav",
	"fgrunt/gr_pain5.wav"
};

const array<string> pDieSounds = 
{
	"fgrunt/death1.wav",
	"fgrunt/death2.wav",
	"fgrunt/death3.wav"
};

const array<string> pIdleSounds = 
{
	"hgrunt/gr_idle1.wav",
	"hgrunt/gr_idle2.wav",
	"hgrunt/gr_idle3.wav"
};

enum states_e
{
	STATE_IDLE = 0,
	STATE_IDLE_COMBAT,
	STATE_WALK,
	STATE_RUN,
	STATE_DEATH,
	STATE_SAWGRUNT_ATTACK_MELEE,
	STATE_SAWGRUNT_ATTACK_MGUN,
	STATE_RELOAD,
	STATE_THROW_GRENADE
};

class weapon_sawgrunt : CBaseDriveWeapon
{
	int m_iShell;
	float m_flRechargeTime;
	float m_flNextIdleSound;
	float m_fNextCombatSound;
	float m_fNextRegenTick;
	float m_fNextHealthCheck;
	private int m_iState;
	private int m_iGunMuzzleFlash;
	private int m_iBitchSlap;
	private int m_iAutoDeploy;
	private bool m_bIsInCombat;
	private float m_flStopHornetAttack;

	void Spawn()
	{
		Precache();

		g_EntityFuncs.SetModel( self, "models/cnpc/hgrunt_opforf.mdl" );
		g_EntityFuncs.DispatchKeyValue( self.edict(), "renderamt", 128 );
		g_EntityFuncs.DispatchKeyValue( self.edict(), "rendermode", 2 );
		g_EntityFuncs.DispatchKeyValue( self.edict(), "renderfx", 15 );

		self.pev.sequence = self.LookupSequence("idle1");
		self.pev.frame = 0;
		self.ResetSequenceInfo();

		self.SetBodygroup( 1, 3 );
		self.SetBodygroup( 2, 1 );
		self.SetBodygroup( 3, 2 );

		self.m_iDefaultAmmo = 1199;
		m_iAutoDeploy = 1;
		m_iState = STATE_IDLE;
		m_iBitchSlap = 0;

		self.FallInit();
	}

	void Precache()
	{
		BaseClass.Precache();
		self.PrecacheCustomModels();
		//g_Game.PrecacheGeneric( "sprites/controlnpc/weapon_sawgrunt.txt" );
		g_Game.PrecacheModel( "sprites/controlnpc/crosshairs.spr" );
		g_Game.PrecacheModel( "models/cnpc/hgrunt_opforf.mdl" );
		g_Game.PrecacheModel( "models/cnpc/hgrunt_opforfT.mdl" );
		m_iGunMuzzleFlash = g_Game.PrecacheModel( "sprites/muzzleflash1.spr" );
		m_iShell = g_Game.PrecacheModel( "models/saw_shell.mdl" );

		for( uint i = 0; i < pAttackMissSounds.length(); i++ )
			g_SoundSystem.PrecacheSound( pAttackMissSounds[i] );

		for( uint i = 0; i < pPainSounds.length(); i++ )
			g_SoundSystem.PrecacheSound( pPainSounds[i] );

		for( uint i = 0; i < pDieSounds.length(); i++ )
			g_SoundSystem.PrecacheSound( pDieSounds[i] );

		for( uint i = 0; i < pDieSounds.length(); i++ )
			g_SoundSystem.PrecacheSound( pIdleSounds[i] );
			g_SoundSystem.PrecacheSound( "weapons/saw_reload.wav" );
	}

	bool GetItemInfo( ItemInfo& out info )
	{
		info.iMaxAmmo1	= 999;
		info.iMaxClip		= 200;
		info.iSlot		= 8;
		info.iPosition		= 1;
		info.iFlags 			= ITEM_FLAG_SELECTONEMPTY; //to prevent monster from being despawned if out of ammo TODO
		info.iWeight			= 0; //-1 ??

		return true;
	}

	bool AddToPlayer( CBasePlayer@ pPlayer )
	{
		if( !BaseClass.AddToPlayer(pPlayer) )
			return false;

		@m_pPlayer = pPlayer;

		/*NetworkMessage m1( MSG_ONE, NetworkMessages::WeapPickup, pPlayer.edict() );
			m1.WriteLong( g_ItemRegistry.GetIdForName(sWeaponName) );
		m1.End();*/

		m_pPlayer.SwitchWeapon(self);

		return true;
	}

	bool Deploy()
	{
		spawn_driveent();
		self.m_flNextPrimaryAttack = self.m_flNextSecondaryAttack = g_Engine.time + 0.1;

		return self.DefaultDeploy( "", "", 0, "" );
	}

	bool CanHolster()
	{
		return false;
	}

	void DoIdleSound()
	{
		//if( m_iState != STATE_IDLE and m_iState != STATE_WALK ) return;
		if( m_flNextIdleSound > g_Engine.time ) return;

		if( Math.RandomLong(0, 99) == 0 )
			IdleSound();
	}

	void Holster( int skipLocal = 0 )
	{
		if( m_pDriveEnt !is null )
			@m_pDriveEnt.pev.owner = null;

		ResetPlayer();

		BaseClass.Holster( skipLocal );
	}

	void PrimaryAttack()
	{
		if( m_iState == STATE_RUN )
		{
			self.m_flNextPrimaryAttack = g_Engine.time + 0.1;
		}

		if( self.m_iClip <= 0 )
		{
			self.m_flNextPrimaryAttack = self.m_flNextSecondaryAttack = self.m_flTimeWeaponIdle = g_Engine.time + 0.08;

			if( m_bIsInCombat )
			{
				DoCombatIdleAnimation();
			}
			else
			{
				DoIdleAnimation();
			}
			
			return;
		}

		if( m_pDriveEnt !is null )
		{
			if( m_iState != STATE_DEATH and self.m_iClip != 0 )
			{
				m_iState = STATE_SAWGRUNT_ATTACK_MGUN;
				m_bIsInCombat = true;

				m_pDriveEnt.pev.sequence = m_pDriveEnt.LookupSequence("standing_mp5");
				m_pDriveEnt.pev.frame = 0;
				m_pDriveEnt.ResetSequenceInfo();
				m_pPlayer.SetMaxSpeedOverride( 0 );

				--self.m_iClip;

				entvars_t@ pevOwner;
				if( self.pev.owner !is null )
					@pevOwner = @self.pev.owner.vars;
				else
					@pevOwner = self.pev;

				TraceResult tr;
				float x, y;
				g_Utility.GetCircularGaussianSpread( x, y );
				Vector vecAiming = m_pPlayer.GetAutoaimVector( AUTOAIM_5DEGREES );
				Vector vecDir = vecAiming 
							+ x * VECTOR_CONE_6DEGREES.x * g_Engine.v_right 
							+ y * VECTOR_CONE_6DEGREES.y * g_Engine.v_up;
				Vector vecAngle, vecOrigin, vecSrc, vecMuzzle;
				vecAngle = m_pPlayer.pev.v_angle;
				vecSrc = m_pPlayer.GetGunPosition();

				Vector vecEnd	= vecSrc + vecDir * 8192;

				g_Utility.TraceLine( vecSrc, vecEnd, dont_ignore_monsters, m_pPlayer.edict(), tr );
				m_pDriveEnt.GetAttachment( 0, vecOrigin, void );

				m_pDriveEnt.pev.effects |= EF_MUZZLEFLASH;
				m_pPlayer.m_iWeaponVolume = NORMAL_GUN_VOLUME;

				vecMuzzle = vecOrigin + g_Engine.v_forward * -11;

				NetworkMessage m1( MSG_PVS, NetworkMessages::SVC_TEMPENTITY, vecMuzzle );
					m1.WriteByte( TE_SPRITE );
					m1.WriteCoord( vecMuzzle.x );
					m1.WriteCoord( vecMuzzle.y );
					m1.WriteCoord( vecMuzzle.z );
					m1.WriteShort( m_iGunMuzzleFlash );
					m1.WriteByte( 4 ); // size * 10
					m1.WriteByte( 128 ); // brightness
				m1.End();

				m_pPlayer.FireBullets( 1, vecSrc, vecAiming, VECTOR_CONE_6DEGREES, 8192, BULLET_PLAYER_SAW, 1 );
				g_SoundSystem.EmitSound( m_pDriveEnt.edict(), CHAN_WEAPON, "weapons/saw_fire1.wav", VOL_NORM, 0.6 );

				Vector vecShellVelocity, vecShellOrigin;
				CS16GetDefaultShellInfo( m_pPlayer, vecShellVelocity, vecShellOrigin, 14, 6, -4, false, false );
				vecShellVelocity.y *= 1;
				g_EntityFuncs.EjectBrass( vecShellOrigin, vecShellVelocity, m_pPlayer.pev.angles.y, m_iShell, TE_BOUNCE_SHELL );

				WW2DynamicLight( m_pPlayer.pev.origin, 8, 240, 180, 0, 8, 50 );
				switch( ( self.m_iClip ) % 2 )
				{
					case 0: WW2DynamicTracer( vecSrc + g_Engine.v_forward * 4 + g_Engine.v_right * 10 + g_Engine.v_up * -2, tr.vecEndPos ); break;
				}

				if( tr.flFraction < 1.0 )
				{
					if( tr.pHit !is null )
					{
						CBaseEntity@ pHit = g_EntityFuncs.Instance( tr.pHit );
				
						if( pHit is null || pHit.IsBSPModel() )
							g_WeaponFuncs.DecalGunshot( tr, BULLET_PLAYER_MP5 );
					}
				}

				m_flNextIdleSound = g_Engine.time + 4.0;
				m_fNextRegenTick = g_Engine.time + 6.0;
				m_pPlayer.SetMaxSpeedOverride( 0 );

				if ( m_fNextCombatSound < g_Engine.time )
				{
					m_fNextCombatSound = g_Engine.time + 9999.0;

					switch( Math.RandomLong(1, 2) )
					{
						case 1: g_SoundSystem.PlaySentenceGroup( m_pDriveEnt.edict(), "FG_ALERT", 1.0, ATTN_NORM, 0, PITCH_NORM ); break;
						case 2: g_SoundSystem.PlaySentenceGroup( m_pDriveEnt.edict(), "FG_ATTACK", 1.0, ATTN_NORM, 0, PITCH_NORM ); break;
					}
				}
			}
		}
		else
		{
			spawn_driveent();
			self.m_flNextPrimaryAttack = self.m_flNextSecondaryAttack = self.m_flTimeWeaponIdle = g_Engine.time + 0.5;

			return;
		}

		self.m_flNextPrimaryAttack = g_Engine.time + 0.08;
		self.m_flNextSecondaryAttack = g_Engine.time + 0.25;
		self.m_flTimeWeaponIdle = g_Engine.time + 0.0815;
	}

	void SecondaryAttack()
	{
		if( m_iState == STATE_RUN )
		{
			self.m_flNextSecondaryAttack = g_Engine.time + 0.1;
		}

		if( m_pDriveEnt !is null and m_iState != STATE_DEATH )
		{
			m_iState = STATE_SAWGRUNT_ATTACK_MELEE;
			m_bIsInCombat = true;
			m_pPlayer.SetMaxSpeedOverride( 0 );

			m_iBitchSlap = 0;
			m_pDriveEnt.pev.sequence = m_pDriveEnt.LookupSequence("frontkick");
			pev.nextthink = g_Engine.time + 0.4;

			m_pDriveEnt.pev.frame = 0;
			m_pDriveEnt.ResetSequenceInfo();

			SetThink( ThinkFunction(this.MeleeAttackThink) );
		}

		m_flNextIdleSound = g_Engine.time + 4.0;
		self.m_flNextPrimaryAttack = g_Engine.time + 1.0;
		self.m_flNextSecondaryAttack = g_Engine.time + SAWGRUNT_MELEE_CD;
		self.m_flTimeWeaponIdle = g_Engine.time + SAWGRUNT_MELEE_CD;
	}

	void TertiaryAttack()
	{
		if( m_pDriveEnt !is null and m_iState != STATE_DEATH )
		{
			m_iState = STATE_THROW_GRENADE;
			m_bIsInCombat = true;
			m_pPlayer.SetMaxSpeedOverride( 0 );

			m_pDriveEnt.pev.sequence = m_pDriveEnt.LookupSequence("throwgrenade");
			pev.nextthink = g_Engine.time + 1.25;
			m_pDriveEnt.pev.frame = 0;
			m_pDriveEnt.ResetSequenceInfo();

			SetThink( ThinkFunction(this.GrenadeThrowThink) );
			g_SoundSystem.PlaySentenceGroup( m_pDriveEnt.edict(), "FG_THROW", 1.0, ATTN_NORM, 0, PITCH_NORM );
		}

		m_flNextIdleSound = g_Engine.time + 4.0;
		m_fNextRegenTick = g_Engine.time + 6.0;
		self.m_flNextPrimaryAttack = g_Engine.time + 1.35;
		self.m_flNextSecondaryAttack = g_Engine.time + 1.35;
		self.m_flNextTertiaryAttack = g_Engine.time + 10.0;
		self.m_flTimeWeaponIdle = g_Engine.time + 1.35;
	}

	void Reload()
	{
	}

	void WeaponIdle()
	{
		if( self.m_flTimeWeaponIdle > g_Engine.time )
			return;

		DoIdleAnimation();

		self.m_flTimeWeaponIdle = g_Engine.time + 1.0;
	}

	void ItemPreFrame()
	{
		if( m_pDriveEnt !is null )
		{
			m_pPlayer.pev.friction = 2;
			
			CheckMaxHealth();
			DoIdleSound();

			if( m_pPlayer.pev.button & (IN_FORWARD|IN_BACK|IN_MOVELEFT|IN_MOVERIGHT) != 0 )
			{
				if( m_iState != STATE_THROW_GRENADE and m_iState != STATE_RELOAD )
				{
					m_pPlayer.SetMaxSpeedOverride( 270 );
					DoMovementAnimation();
					self.m_flTimeWeaponIdle = g_Engine.time + 0.1;
				}
			}
			else if( (m_pPlayer.pev.flags & FL_FAKECLIENT) != 0 and m_pPlayer.pev.velocity.Length2D() > 0 )
			{
				if( m_iState != STATE_SAWGRUNT_ATTACK_MGUN and m_iState != STATE_SAWGRUNT_ATTACK_MELEE and m_iState != STATE_RELOAD )
				{
					m_pPlayer.SetMaxSpeedOverride( 270 );
					DoMovementAnimation();
					self.m_flTimeWeaponIdle = g_Engine.time + 0.1;
				}
			}

			if( m_pPlayer.pev.velocity.Length() <= 0 and m_iState != STATE_SAWGRUNT_ATTACK_MELEE and m_iState != STATE_SAWGRUNT_ATTACK_MGUN and m_iState != STATE_THROW_GRENADE and m_iState != STATE_RELOAD )
			{
				if( m_bIsInCombat )
				{
					DoCombatIdleAnimation();
				}
				else
				{
					DoIdleAnimation();
				}		
			}		

			if( m_iState == STATE_SAWGRUNT_ATTACK_MGUN )
			{
				m_pPlayer.SetMaxSpeedOverride( 0 );

				Vector angDir = m_pPlayer.pev.v_angle;

				if( angDir.x > 360 )
					angDir.x = angDir.x - 360;

				m_pDriveEnt.SetBlending( 0, -angDir.x );
				m_pPlayer.SetViewMode( ViewMode_ThirdPerson );
			}

			if( m_iState == STATE_IDLE )
			{
				RegenerateHealth();
				m_pPlayer.pev.view_ofs = Vector( 0, 0, 40 );
				m_pPlayer.SetViewMode( ViewMode_ThirdPerson );
			}

			if( m_iState == STATE_IDLE_COMBAT )
			{
				RegenerateHealth();
				m_pPlayer.pev.view_ofs = Vector( 0, 0, 40 );
				m_pPlayer.SetViewMode( ViewMode_ThirdPerson );
			}
			if( m_iState == STATE_WALK )
			{
				m_pPlayer.pev.view_ofs = Vector( 0, 0, 40 );
				m_pPlayer.SetViewMode( ViewMode_ThirdPerson );
			}

			if( m_iState == STATE_RUN )
			{
				m_pPlayer.pev.view_ofs = Vector( 0, 0, 40 );
				m_pPlayer.SetViewMode( ViewMode_ThirdPerson );
			}
			
			DoReload();
		}
	}

	void RegenerateHealth ()
	{
		if( m_pDriveEnt !is null )
		{
			if ( m_fNextRegenTick < g_Engine.time and m_pPlayer.pev.health < 100 )
			{
				m_pPlayer.pev.health += 10;
				m_fNextRegenTick = g_Engine.time + 1.0;
			}
		}
	}

	void CheckMaxHealth ()
	{
		if( m_pDriveEnt !is null )
		{
			if( (m_pPlayer.pev.flags & FL_FAKECLIENT) == 0 and m_fNextHealthCheck < g_Engine.time and m_pPlayer.pev.health > 100 )
			{
				m_pPlayer.pev.health = 100;
				m_fNextHealthCheck = g_Engine.time + 0.1;
			}
		}
	}

	void IdleSound ()
	{
		if( m_pDriveEnt !is null )
		{
			if ( m_flNextIdleSound < g_Engine.time )
			{
				m_flNextIdleSound = g_Engine.time + 5.0;
				m_fNextCombatSound = 0.0;
				m_bIsInCombat = false;

				switch( Math.RandomLong(0, 4) )
				{
					case 0: g_SoundSystem.PlaySentenceGroup( m_pDriveEnt.edict(), "FG_IDLE", 1.0, ATTN_NORM, 0, PITCH_NORM ); break;
					case 1: g_SoundSystem.PlaySentenceGroup( m_pDriveEnt.edict(), "FG_QUEST", 1.0, ATTN_NORM, 0, PITCH_NORM ); break;
					case 2: g_SoundSystem.PlaySentenceGroup( m_pDriveEnt.edict(), "FG_ANSWER", 1.0, ATTN_NORM, 0, PITCH_NORM ); break;
					case 3: g_SoundSystem.PlaySentenceGroup( m_pDriveEnt.edict(), "FG_CLEAR", 1.0, ATTN_NORM, 0, PITCH_NORM ); break;
					case 4: g_SoundSystem.PlaySentenceGroup( m_pDriveEnt.edict(), "FG_CHECK", 1.0, ATTN_NORM, 0, PITCH_NORM ); break;
				}

			}
		}
	}

	void DoReload()
	{
		if( (m_pPlayer.pev.flags & FL_FAKECLIENT) != 0 and m_pDriveEnt !is null and m_pPlayer.IsAlive() and self.m_iClip <= 0 )
		{
			m_iState == STATE_RELOAD;
			m_pPlayer.SetMaxSpeedOverride( 0 );

			m_pDriveEnt.pev.sequence = m_pDriveEnt.LookupSequence("reload_mp5");
			m_pDriveEnt.pev.frame = 0;
			m_pDriveEnt.ResetSequenceInfo();

			//m_pPlayer.m_rgAmmo( self.m_iPrimaryAmmoType, m_pPlayer.m_rgAmmo( self.m_iPrimaryAmmoType ) - 30 );
			self.m_iClip = 200;

			SetThink( ThinkFunction(this.ReloadThink) );
			pev.nextthink = g_Engine.time + 1.6;

			m_fNextRegenTick = g_Engine.time + 6.0;
			self.m_flNextPrimaryAttack = g_Engine.time + 2.0;
			self.m_flNextSecondaryAttack = g_Engine.time + 2.0;
			self.m_flTimeWeaponIdle = g_Engine.time + 2.0;
		}
		else if( (m_pPlayer.pev.flags & FL_FAKECLIENT) == 0 and (m_pPlayer.pev.button & IN_RELOAD) != 0 and m_pDriveEnt.pev.sequence != m_pDriveEnt.LookupSequence("reload_mp5") and m_pDriveEnt !is null and self.m_iClip < 200 )
		{
			m_iState == STATE_RELOAD;
			m_pPlayer.SetMaxSpeedOverride( 0 );

			m_pDriveEnt.pev.sequence = m_pDriveEnt.LookupSequence("reload_mp5");
			m_pDriveEnt.pev.frame = 0;
			m_pDriveEnt.ResetSequenceInfo();

			//m_pPlayer.m_rgAmmo( self.m_iPrimaryAmmoType, m_pPlayer.m_rgAmmo( self.m_iPrimaryAmmoType ) - 30 );
			//self.m_iClip = 30;

			SetThink( ThinkFunction(this.ReloadThink) );
			pev.nextthink = g_Engine.time + 1.6;

			m_fNextRegenTick = g_Engine.time + 6.0;
			self.m_flNextPrimaryAttack = g_Engine.time + 2.0;
			self.m_flNextSecondaryAttack = g_Engine.time + 2.0;
			self.m_flTimeWeaponIdle = g_Engine.time + 2.0;
		}
	}

	void ReloadThink()
	{
		if( m_pDriveEnt !is null and m_pPlayer.pev.velocity.Length() <= 0 )
		{
			g_SoundSystem.EmitSound( m_pDriveEnt.edict(), CHAN_ITEM, "weapons/saw_reload.wav", VOL_NORM, 1.0 );
			self.m_iClip = 200;
			m_pPlayer.SetMaxSpeedOverride( 270 );
			m_fNextCombatSound = 0.0;
		}

		SetThink( null );
		if( m_bIsInCombat )
		{
			DoCombatIdleAnimation();
		}
		else
		{
			DoIdleAnimation();
		}	
	}

	void MeleeAttackThink()
	{
		m_flNextIdleSound = g_Engine.time + 6.0;
		m_fNextRegenTick = g_Engine.time + 6.0;

		if( m_pPlayer is null or !m_pPlayer.IsAlive() or m_pDriveEnt is null )
		{
			SetThink( null );
			return;
		}

		if( m_iBitchSlap == 0 ) //left slap
		{
			g_SoundSystem.EmitSoundDyn( m_pDriveEnt.edict(), CHAN_WEAPON, pAttackMissSounds[Math.RandomLong(0,(pAttackMissSounds.length() - 1))], VOL_NORM, ATTN_NORM, 0, Math.RandomLong(95, 105) );
			CBaseEntity@ pHurt = CheckTraceHullAttack( SAWGRUNT_MELEE_DMG_RANGE, SAWGRUNT_MELEE_DMG, DMG_ENERGYBEAM | DMG_NEVERGIB );

			if( pHurt !is null )
			{
				int rel = m_pPlayer.IRelationship(pHurt);
				bool isFriendly = rel == R_AL or rel == R_NO;

				if( pHurt.pev.flags & (FL_MONSTER | FL_CLIENT) != 0 and !isFriendly )
				{
					pHurt.pev.punchangle.z = 20;
					pHurt.pev.punchangle.x = -20;
				}
			}

		}

		SetThink( null );
	}

	void GrenadeThrowThink()
	{
		if( m_pDriveEnt !is null and m_pPlayer.IsAlive() )
		{
			Vector angThrow = m_pPlayer.pev.v_angle + m_pPlayer.pev.punchangle;
			if ( angThrow.x < 0 )
				angThrow.x = -10 + angThrow.x * ( ( 90 - 10 ) / 90.0 );
			else
				angThrow.x = -10 + angThrow.x * ( ( 90 + 10 ) / 90.0 );
			float flVel = ( 90 - angThrow.x ) * 4;
			if ( flVel > 500 )
				flVel = 500;
			g_EngineFuncs.MakeVectors( angThrow );
			Vector vecSrc = m_pPlayer.pev.origin + m_pPlayer.pev.view_ofs + g_Engine.v_forward * 16;
			Vector vecThrowBot = g_Engine.v_forward * 600 +  g_Engine.v_up * 100;

			HLSDK_CGrenade@ pGrenadeBot = ShootTimed( m_pPlayer.pev, vecSrc, vecThrowBot, 4.0 );
		}

		SetThink( null );
		if( m_bIsInCombat )
		{
			DoCombatIdleAnimation();
		}
		else
		{
			DoIdleAnimation();
		}	
	}

	void DoMovementAnimation()
	{
		float flMinWalkVelocity = -150.0;
		float flMaxWalkVelocity = 150.0;

		if( (m_pPlayer.pev.button & IN_USE) != 0 or IsBetween(m_pPlayer.pev.velocity.Length(), flMinWalkVelocity, flMaxWalkVelocity) )
		{
			if( m_pDriveEnt.pev.sequence != m_pDriveEnt.LookupSequence("walk_new") )
			{
				m_iState = STATE_WALK;
				m_pDriveEnt.pev.sequence = m_pDriveEnt.LookupSequence("walk_new");
				m_pDriveEnt.pev.frame = 0;
				m_pDriveEnt.ResetSequenceInfo();
			}

			m_fNextRegenTick = g_Engine.time + 6.0;
			
			if( (m_pPlayer.pev.flags & FL_FAKECLIENT) != 0 )
			{
				//nada
			}
			else
			{
				self.m_flNextPrimaryAttack = g_Engine.time + 0.15;
				self.m_flNextSecondaryAttack = g_Engine.time + 0.15;
			}
		}
		else if( (m_pPlayer.pev.flags & FL_FAKECLIENT) != 0 )
		{
			if( m_pDriveEnt.pev.sequence != m_pDriveEnt.LookupSequence("run") )
			{
				m_iState = STATE_RUN;
				m_pDriveEnt.pev.sequence = m_pDriveEnt.LookupSequence("run");
				m_pDriveEnt.pev.frame = 0;
				m_pDriveEnt.ResetSequenceInfo();
			}

			m_pPlayer.pev.flTimeStepSound = 9999; //prevents normal footsteps from playing, kinda? :hehe:
		}
		else
		{
			if( m_pDriveEnt.pev.sequence != m_pDriveEnt.LookupSequence("run") )
			{
				m_iState = STATE_RUN;
				m_pDriveEnt.pev.sequence = m_pDriveEnt.LookupSequence("run");
				m_pDriveEnt.pev.frame = 0;
				m_pDriveEnt.ResetSequenceInfo();
			}

			m_fNextRegenTick = g_Engine.time + 6.0;
			self.m_flNextPrimaryAttack = g_Engine.time + 0.15;
			self.m_flNextSecondaryAttack = g_Engine.time + 0.15;
			m_pPlayer.pev.flTimeStepSound = 9999; //prevents normal footsteps from playing, kinda? :hehe:
		}
	}

	void DoCombatIdleAnimation()
	{
		if( m_pDriveEnt is null ) return;

		if( m_iState != STATE_IDLE_COMBAT )
		{
			m_pPlayer.SetMaxSpeedOverride( 270 );
			m_iState = STATE_IDLE_COMBAT;
			m_pDriveEnt.pev.sequence = m_pDriveEnt.LookupSequence("combatidle");
			m_pDriveEnt.pev.frame = 0;
			m_pDriveEnt.ResetSequenceInfo();
		}
	}

	void DoIdleAnimation()
	{
		if( m_pDriveEnt is null ) return;
		if( m_pDriveEnt.pev.sequence == m_pDriveEnt.LookupSequence("reload_mp5") and !m_pDriveEnt.m_fSequenceFinished ) return;

		if( m_iState != STATE_IDLE )
		{
			m_pPlayer.SetMaxSpeedOverride( 270 );
			m_iState = STATE_IDLE;
			m_pDriveEnt.pev.sequence = m_pDriveEnt.LookupSequence("idle1");
			m_pDriveEnt.pev.frame = 0;
			m_pDriveEnt.ResetSequenceInfo();
		}
	}

	void spawn_driveent()
	{
		if( !m_pPlayer.pev.FlagBitSet(FL_ONGROUND) )
		{
			g_EngineFuncs.ClientPrintf( m_pPlayer, print_center, "Unable to deploy\nwhile in the air" );
			return;
		}

		@m_pDriveEnt = cast<CBaseAnimating@>( g_EntityFuncs.Create("cnpc_sawgrunt", m_pPlayer.pev.origin, m_pPlayer.pev.angles, true, m_pPlayer.edict()) );

		m_pDriveEnt.pev.set_controller( 0,  127 );

		g_EntityFuncs.DispatchSpawn( m_pDriveEnt.edict() );

		m_pPlayer.pev.effects |= EF_NODRAW;
		m_pPlayer.pev.flags &= ~FL_NOTARGET;
		m_pPlayer.pev.iuser3 = 1;
		m_pPlayer.pev.fuser4 = 1; //disable jump
		self.m_bExclusiveHold = true;
		m_bIsInCombat = false;

		m_pPlayer.SetViewMode( ViewMode_ThirdPerson );

		NetworkMessage m1( MSG_ONE, NetworkMessages::SVC_STUFFTEXT, m_pPlayer.edict() );
			m1.WriteString( "cam_idealdist 128\n" );
		m1.End();

		CustomKeyvalues@ pCustom = m_pPlayer.GetCustomKeyvalues();
		pCustom.SetKeyvalue( CNPC::sCNPCKV, CNPC::CNPC_SAWGRUNT );
		m_pPlayer.pev.view_ofs = Vector( 0, 0, 40 );

		/*if( (m_pPlayer.pev.flags & FL_FAKECLIENT) != 0 )
		{
			m_pPlayer.pev.netname = "Grunt";
		}*/
	}

	void ResetPlayer()
	{
		m_pPlayer.pev.iuser3 = 0;
		m_pPlayer.pev.fuser4 = 0; //enable jump
		m_pPlayer.SetMaxSpeedOverride( -1 );

		CustomKeyvalues@ pCustom = m_pPlayer.GetCustomKeyvalues();
		pCustom.SetKeyvalue( CNPC::sCNPCKV, 0 );
	}
}

class cnpc_sawgrunt : ScriptBaseAnimating//ScriptBaseMonsterEntity
{
	protected CBasePlayer@ m_pOwner
	{
		get { return cast<CBasePlayer@>( g_EntityFuncs.Instance(pev.owner) ); }
	}

	private float m_flNextOriginUpdate; //hopefully fixes hacky movement on other players

	void Spawn()
	{
		g_EntityFuncs.SetModel( self, "models/cnpc/hgrunt_opforf.mdl" );
		g_EntityFuncs.SetSize( self.pev, Vector(-16, -16, 0), Vector(16, 16, 72) );
		g_EntityFuncs.SetOrigin( self, pev.origin );
		g_EngineFuncs.DropToFloor( self.edict() );

		pev.solid = SOLID_NOT;
		pev.movetype = MOVETYPE_NOCLIP;

		pev.sequence = self.LookupSequence("idle1");
		pev.frame = 0;
		self.ResetSequenceInfo();
		self.SetBodygroup( 2, 1 );
		self.SetBodygroup( 3, 2 );
		switch( Math.RandomLong(1, 2) )
		{
			case 1: self.SetBodygroup( 1, 1 ); break;
			case 2: self.SetBodygroup( 1, 3 ); break;
		}

		SetThink( ThinkFunction(this.DriveThink) );
		pev.nextthink = g_Engine.time;
	}

	void DriveThink()
	{
		if( m_pOwner is null or !m_pOwner.IsConnected() or m_pOwner.pev.deadflag != DEAD_NO )
		{
			pev.velocity = g_vecZero;
			SetThink( ThinkFunction(this.DieThink) );
			pev.nextthink = g_Engine.time;

			return;
		}

		if( m_flNextOriginUpdate < g_Engine.time )
		{
			Vector vecOrigin = m_pOwner.pev.origin;
			vecOrigin.z -= 32.0;
			g_EntityFuncs.SetOrigin( self, vecOrigin );
			m_flNextOriginUpdate = g_Engine.time + 0.1;
		}

		pev.velocity = m_pOwner.pev.velocity;

		pev.angles.x = 0;

		if( pev.velocity.Length2D() > 0.0 and (pev.sequence == self.LookupSequence("walk_new") or pev.sequence == self.LookupSequence("run")) )
			pev.angles.y = Math.VecToAngles( pev.velocity ).y;
		else
			pev.angles.y = m_pOwner.pev.angles.y;

		pev.angles.z = 0;

		self.StudioFrameAdvance();

		pev.nextthink = g_Engine.time + 0.01;
	}

	void DieThink()
	{
		array<string> arrsDeathAnims = 
		{
			"dieback1",
			"diebackwards",
			"diegutshot",
			"diesimple",
			"dieheadshot"
		};

		pev.sequence = self.LookupSequence( arrsDeathAnims[Math.RandomLong(0, arrsDeathAnims.length()-1)] );
		pev.frame = 0;
		self.ResetSequenceInfo();
		g_EngineFuncs.DropToFloor( self.edict() );

		g_SoundSystem.EmitSound( self.edict(), CHAN_VOICE, pDieSounds[Math.RandomLong(0, pDieSounds.length()-1)], VOL_NORM, ATTN_NORM );

		SetThink( ThinkFunction(this.SUB_StartFadeOut) );
		pev.nextthink = g_Engine.time + 15.0;
	}

	void SUB_StartFadeOut()
	{
		if( pev.rendermode == kRenderNormal )
		{
			pev.renderamt = 255;
			pev.rendermode = kRenderTransTexture;
		}

		pev.solid = SOLID_NOT;
		pev.avelocity = g_vecZero;

		pev.nextthink = g_Engine.time + 0.1;
		SetThink( ThinkFunction(this.SUB_FadeOut) );
	}

	void SUB_FadeOut()
	{
		if( pev.renderamt > 7 )
		{
			pev.renderamt -= 7;
			pev.nextthink = g_Engine.time + 0.1;
		}
		else 
		{
			pev.renderamt = 0;
			pev.nextthink = g_Engine.time + 0.2;
			SetThink( ThinkFunction(this.SUB_Remove) );
		}
	}

	void SUB_Remove()
	{
		self.UpdateOnRemove();

		if( pev.health > 0 )
			pev.health = 0;

		NetworkMessage killbeam( MSG_BROADCAST, NetworkMessages::SVC_TEMPENTITY, null );
			killbeam.WriteByte(TE_KILLBEAM);
			killbeam.WriteShort(self.entindex());
		killbeam.End();

		g_EntityFuncs.Remove(self);
	}
}

void Register()
{
	g_CustomEntityFuncs.RegisterCustomEntity( "cnpc_sawgrunt::cnpc_sawgrunt", "cnpc_sawgrunt" );
	g_Game.PrecacheOther( "cnpc_sawgrunt" );

	g_CustomEntityFuncs.RegisterCustomEntity( "cnpc_sawgrunt::weapon_sawgrunt", sWeaponName );
	g_ItemRegistry.RegisterWeapon( sWeaponName, "controlnpc", "sawgrunt" );
	g_Game.PrecacheOther( sWeaponName );
}

} //namespace cnpc_sawgrunt END

/* FIXME
Holding any button after attacking will cause the animation to freeze at the last frame until released
*/

/* TODO
Use turning animations 
*/
