//#include "weapon_ofsporelauncher"

namespace cnpc_strooper
{
	
const uint g_MaxAmmoPrimary		= 999;
const string sWeaponName		= "weapon_strooper";
const float STROOPER_MELEE_CD		= 1.25;

const float STROOPER_MELEE_DMG		= 75.0;
const float STROOPER_MELEE_DMG_RANGE	= 115.0;

const float SHOCK_REFIRE_TIME		= 0.4;

const int SF_SPRITE_TEMPORARY		= 0x8000;

int g_sModelIndexTinySpit;
int g_sModelIndexSpore1;
int g_sModelIndexSpore3;


const array<string> pAttackMissSounds = 
{
	"zombie/claw_miss1.wav",
	"zombie/claw_miss2.wav"
};

const array<string> pPainSounds = 
{
	"shocktrooper/shock_trooper_pain1.wav",
	"shocktrooper/shock_trooper_pain2.wav",
	"shocktrooper/shock_trooper_pain3.wav",
	"shocktrooper/shock_trooper_pain4.wav",
	"shocktrooper/shock_trooper_pain5.wav"
};

const array<string> pDieSounds = 
{
	"shocktrooper/shock_trooper_die1.wav",
	"shocktrooper/shock_trooper_die2.wav",
	"shocktrooper/shock_trooper_die3.wav",
	"shocktrooper/shock_trooper_die4.wav"
};

const array<string> pIdleSounds = 
{
	"shocktrooper/blis.wav",
	"shocktrooper/dit.wav",
	"shocktrooper/dup.wav",
	"shocktrooper/ga.wav",
	"shocktrooper/hyu.wav",
	"shocktrooper/ka.wav",
	"shocktrooper/blis.wav",
	"shocktrooper/blis.wav",
	"shocktrooper/blis.wav",
	"shocktrooper/blis.wav",
};

enum states_e
{
	STATE_IDLE = 0,
	STATE_WALK,
	STATE_RUN,
	STATE_DEATH,
	STATE_ATTACK_MELEE,
	STATE_ATTACK_SHOCK,
	STATE_THROW_SPORE,
	STATE_IDLE_COMBAT
};

class opfor_spore : ScriptBaseMonsterEntity
{
	private CSprite@ m_pSprite;

	void Spawn()
	{
		Precache();

		pev.solid = SOLID_BBOX;

		g_EntityFuncs.SetModel( self, "models/spore.mdl" );
		g_EntityFuncs.SetSize( self.pev, Vector(-4, -4, -4), Vector(4, 4, 4) );

		g_EntityFuncs.SetOrigin( self, pev.origin );
		Math.MakeVectors(pev.angles);

		pev.gravity = 0.5f;
		pev.friction = 0.2f;
		Glow();

		if( pev.movetype == MOVETYPE_FLY )
		{
			SetThink( ThinkFunction(this.FlyThink) );
			SetTouch( TouchFunction(this.ExplodeThink) );
		}
		else
		{
			SetThink( ThinkFunction(this.FlyThink) );
			SetTouch( TouchFunction(this.BounceThink) );
		}

		pev.dmgtime = (g_Engine.time + pev.dmgtime);
		pev.dmg = 150;
		SetNextThink(0.1f);
	}

	void Precache()
	{
		g_Game.PrecacheModel( "models/spore.mdl" );
		g_Game.PrecacheModel( "sprites/glow02.spr" );
		g_sModelIndexTinySpit = g_Game.PrecacheModel( "sprites/tinyspit.spr" );
		g_sModelIndexSpore1 = g_Game.PrecacheModel( "sprites/spore_exp_01.spr" );
		g_sModelIndexSpore3 = g_Game.PrecacheModel( "sprites/spore_exp_c_01.spr" );

		g_SoundSystem.PrecacheSound( "weapons/splauncher_impact.wav" );
		g_SoundSystem.PrecacheSound( "weapons/spore_hit1.wav" );
		g_SoundSystem.PrecacheSound( "weapons/spore_hit2.wav" );
		g_SoundSystem.PrecacheSound( "weapons/spore_hit3.wav" );
	}

	void Glow()
	{
		if( m_pSprite is null )
		{
			@m_pSprite = g_EntityFuncs.CreateSprite( "sprites/glow02.spr", pev.origin, false ); 
			m_pSprite.SetAttachment( self.edict(), 0 );
			m_pSprite.pev.scale = 0.75f;
			m_pSprite.SetTransparency( kRenderTransAdd, 150, 158, 19, 155, kRenderFxNoDissipation );
			m_pSprite.pev.spawnflags |= SF_SPRITE_TEMPORARY;
			m_pSprite.pev.flags |= FL_SKIPLOCALHOST;
		}
	}

	void FlyThink()
	{
		TraceResult tr;
		g_Utility.TraceLine( pev.origin, pev.origin + pev.velocity * 10, dont_ignore_monsters, self.edict(), tr);

		NetworkMessage message1( MSG_PAS, NetworkMessages::SVC_TEMPENTITY, pev.origin );
			message1.WriteByte( TE_SPRITE_SPRAY );
			message1.WriteCoord( pev.origin.x + Math.RandomLong(-5, 5) );
			message1.WriteCoord( pev.origin.y + Math.RandomLong(-5, 5) );
			message1.WriteCoord( pev.origin.z + Math.RandomLong(-5, 5) );
			message1.WriteCoord( tr.vecPlaneNormal.Normalize().x );
			message1.WriteCoord( tr.vecPlaneNormal.Normalize().y );
			message1.WriteCoord( tr.vecPlaneNormal.Normalize().z );
			message1.WriteShort( g_sModelIndexTinySpit );
			message1.WriteByte( Math.RandomLong(3, 8) ); // count
			message1.WriteByte( Math.RandomLong(10, 15) ); // speed
			message1.WriteByte( Math.RandomLong(4, 9) * 100 );
		message1.End();

		NetworkMessage message2( MSG_BROADCAST, NetworkMessages::SVC_TEMPENTITY );
			message2.WriteByte( TE_DLIGHT);
			message2.WriteCoord( pev.origin.x );
			message2.WriteCoord( pev.origin.y );
			message2.WriteCoord( pev.origin.z );
			message2.WriteByte( 15 );     // radius
			message2.WriteByte( 0 );		// r
			message2.WriteByte( 180 );	// g
			message2.WriteByte( 0 );	// b
			message2.WriteByte( 1 );     // life * 10
			message2.WriteByte( 0 ); // decay
		message2.End();

		if( pev.movetype == MOVETYPE_BOUNCE )
		{
			if( pev.dmgtime <= g_Engine.time )
				Explode();
		}

		SetNextThink(0.001f);
	}

	void BounceThink( CBaseEntity@ pOther )
	{
		if( (pOther.pev.flags & FL_MONSTER) != 0 or pOther.IsPlayer() )
			Explode();

		if( g_EngineFuncs.PointContents(pev.origin) == CONTENTS_SKY )
		{
			if( m_pSprite !is null )
			{
				g_EntityFuncs.Remove( m_pSprite );
				@m_pSprite = null;
			}

			g_EntityFuncs.Remove( self );
			return;
		}

		// only do damage if we're moving fairly fast
		if( self.m_flNextAttack < g_Engine.time and pev.velocity.Length() > 100 )
		{
			entvars_t@ pevOwner = pev.owner.vars;

			if( pevOwner !is null )
			{
				TraceResult tr = g_Utility.GetGlobalTrace();
				g_WeaponFuncs.ClearMultiDamage();
				pOther.TraceAttack( pevOwner, 1, g_Engine.v_forward, tr, DMG_GENERIC );
				g_WeaponFuncs.ApplyMultiDamage( pev, pevOwner );
			}

			self.m_flNextAttack = g_Engine.time + 1.0f;
		}

		Vector vecTestVelocity;
		vecTestVelocity = pev.velocity;
		vecTestVelocity.z *= 0.45f;

		if( (pev.flags & FL_ONGROUND) != 0 )
		{
			pev.velocity = pev.velocity * 0.8f;
			pev.sequence = Math.RandomLong(1, 1);
		}
		else
		{
			// play bounce sound
			switch( Math.RandomLong(0, 2) )
			{
				case 0: g_SoundSystem.EmitSound( self.edict(), CHAN_VOICE, "weapons/spore_hit1.wav", 0.25f, ATTN_NORM ); break;
				case 1: g_SoundSystem.EmitSound( self.edict(), CHAN_VOICE, "weapons/spore_hit2.wav", 0.25f, ATTN_NORM ); break;
				case 2: g_SoundSystem.EmitSound( self.edict(), CHAN_VOICE, "weapons/spore_hit3.wav", 0.25f, ATTN_NORM ); break;
			}
		}

		pev.framerate = pev.velocity.Length() / 200.0f;
		if( pev.framerate > 1.0f )
			pev.framerate = 1;
		else if( pev.framerate < 0.5f )
			pev.framerate = 0;
	}

	void ExplodeThink( CBaseEntity@ pOther )
	{
		if( g_EngineFuncs.PointContents(pev.origin) == CONTENTS_SKY )
		{
			if( m_pSprite !is null )
			{
				g_EntityFuncs.Remove( m_pSprite );
				@m_pSprite = null;
			}

			g_EntityFuncs.Remove( self );
			return;
		}

		Explode();
	}

	void Explode()
	{
		SetTouch(null);
		SetThink(null);
		g_SoundSystem.EmitSound( self.edict(), CHAN_ITEM, "weapons/splauncher_impact.wav", 1.0f, ATTN_NORM );

		NetworkMessage message1( MSG_PAS, NetworkMessages::SVC_TEMPENTITY, pev.origin );
			message1.WriteByte( TE_SPRITE );		// This makes a dynamic light and the explosion sprites/sound
			message1.WriteCoord( pev.origin.x );	// Send to PAS because of the sound
			message1.WriteCoord( pev.origin.y );
			message1.WriteCoord( pev.origin.z );
			switch( Math.RandomLong(0, 1) )
			{
				case 0:
					message1.WriteShort(g_sModelIndexSpore1);
				break;
				case 1:
				default:
					message1.WriteShort(g_sModelIndexSpore3);
				break;
			}
			message1.WriteByte( 25 ); // scale * 10
			message1.WriteByte( 155 ); // framerate
		message1.End();

		NetworkMessage message2( MSG_PAS, NetworkMessages::SVC_TEMPENTITY, pev.origin );
			message2.WriteByte( TE_EXPLOSION );		// This makes a dynamic light and the explosion sprites/sound
			message2.WriteCoord( pev.origin.x );	// Send to PAS because of the sound
			message2.WriteCoord( pev.origin.y );
			message2.WriteCoord( pev.origin.z );
			message2.WriteShort( g_sModelIndexSpore1 );
			message2.WriteByte( int((pev.dmg - 50) * 0.60f) ); // scale * 10
			message2.WriteByte( 15 ); // framerate
			message2.WriteByte( TE_EXPLFLAG_NOSOUND );
		message2.End();

		TraceResult tr;
		g_Utility.TraceLine( pev.origin, pev.origin + pev.velocity * 15, dont_ignore_monsters, self.edict(), tr );

		NetworkMessage message3( MSG_PAS, NetworkMessages::SVC_TEMPENTITY, pev.origin );
			message3.WriteByte( TE_SPRITE_SPRAY );		// This makes a dynamic light and the explosion sprites/sound
			message3.WriteCoord( pev.origin.x );	// Send to PAS because of the sound
			message3.WriteCoord( pev.origin.y );
			message3.WriteCoord( pev.origin.z );
			message3.WriteCoord( tr.vecPlaneNormal.x );
			message3.WriteCoord( tr.vecPlaneNormal.y );
			message3.WriteCoord( tr.vecPlaneNormal.z );
			message3.WriteShort( g_sModelIndexTinySpit );
			message3.WriteByte( 50 ); // count
			message3.WriteByte( 30 ); // speed
			message3.WriteByte( 64 );
		message3.End();

		/*if( g_EngineFuncs.CVarGetFloat("r_particles") > 0 )
		{
			NetworkMessage message3( MSG_ALL, gmsgParticles );
				message3.WriteShort( 0 );
				message3.WriteByte( 0 );
				message3.WriteCoord( pev.origin.x );	// Send to PAS because of the sound
				message3.WriteCoord( pev.origin.y );
				message3.WriteCoord( pev.origin.z );
				message3.WriteCoord( 0 );
				message3.WriteCoord( 0 );
				message3.WriteCoord( 0 );
				message3.WriteShort( iDefaultSporeExplosion );
			message3.End();
		}*/

		NetworkMessage message4( MSG_PVS, NetworkMessages::SVC_TEMPENTITY, pev.origin );
			message4.WriteByte( TE_DLIGHT );
			message4.WriteCoord( pev.origin.x );	// X
			message4.WriteCoord( pev.origin.y );	// Y
			message4.WriteCoord( pev.origin.z );	// Z
			message4.WriteByte( 20 );		// radius * 0.1
			message4.WriteByte( 0 );		// r
			message4.WriteByte( 180 );		// g
			message4.WriteByte( 0 );		// b
			message4.WriteByte( 20 );		// time * 10
			message4.WriteByte( 20 );		// decay * 0.1
		message4.End();

		entvars_t@ pevOwner;
		if( pev.owner !is null )
			@pevOwner = pev.owner.vars;
		else
			@pevOwner = null;

		g_WeaponFuncs.RadiusDamage( pev.origin, self.pev, pevOwner, pev.dmg, 228, CLASS_PLAYER_BIOWEAPON, DMG_ENERGYBEAM | DMG_ACID | DMG_POISON );

		if( pev.movetype == MOVETYPE_FLY )
		{
			g_Utility.TraceLine( pev.origin, pev.origin + pev.velocity * 10, dont_ignore_monsters, self.edict(), tr );
			g_Utility.DecalTrace( tr, DECAL_SPORESPLAT1 + Math.RandomLong(0, 2) );
		}

		pev.velocity = g_vecZero;

		if( m_pSprite !is null )
		{
			g_EntityFuncs.Remove( m_pSprite );
			@m_pSprite = null;
		}

		SetNextThink(0.3f);
		g_EntityFuncs.Remove( self );
	}

	void SetNextThink( float flDelay )
	{
		pev.nextthink = g_Engine.time + flDelay;
	}
}

class sporegrenade2 : ScriptBaseMonsterEntity
{
	protected EHandle m_hSprite;
	protected CSprite@ m_pSprite
	{
		get const { return cast<CSprite@>(m_hSprite.GetEntity()); }
		set { m_hSprite = EHandle(@value); }
	}

	private int m_iBlow, m_iBlowSmall, m_iSpitSprite, m_iTrail;
	private float m_flIgniteTime;
	private float m_flSoundDelay;

	void Spawn()
	{
		Precache();

		pev.movetype = MOVETYPE_BOUNCE;
		pev.solid = SOLID_BBOX;
		pev.gravity = 0.5;
		pev.friction = 0.7;
		pev.dmg = 200;
		pev.angles.x -= ( Math.RandomLong(-5, 5) + 30 );

		g_EntityFuncs.SetModel( self, "models/spore.mdl" );
		g_EntityFuncs.SetSize( self.pev, g_vecZero, g_vecZero );
		g_EntityFuncs.SetOrigin( self, pev.origin );

		SetThink( ThinkFunction(this.FlyThink) );
		SetTouch( TouchFunction(this.SporeTouch) );

		m_flIgniteTime = g_Engine.time;

		pev.nextthink = g_Engine.time + 0.01;

		@m_pSprite = g_EntityFuncs.CreateSprite( "sprites/glow01.spr", pev.origin, false ); 
		m_pSprite.SetTransparency( kRenderTransAdd, 180, 180, 40, 100, kRenderFxDistort );
		m_pSprite.SetScale( 0.8f );
		m_pSprite.SetAttachment( self.edict(), 0 );

		m_flSoundDelay = g_Engine.time;
	}

	void Precache()
	{
		g_Game.PrecacheModel( "models/spore.mdl" );
		g_Game.PrecacheModel( "sprites/glow01.spr" );

		m_iBlow = g_Game.PrecacheModel( "sprites/spore_exp_01.spr" );
		m_iBlowSmall = g_Game.PrecacheModel( "sprites/spore_exp_c_01.spr" );
		m_iSpitSprite = m_iTrail = g_Game.PrecacheModel( "sprites/tinyspit.spr" );

		g_SoundSystem.PrecacheSound( "weapons/splauncher_impact.wav" );
		g_SoundSystem.PrecacheSound( "weapons/splauncher_bounce.wav" );
	}

	void IgniteThink()
	{
		SetThink( null );
		SetTouch( null );

		if( m_pSprite !is null )
			g_EntityFuncs.Remove( m_pSprite );

		g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_WEAPON, "weapons/splauncher_impact.wav", VOL_NORM, ATTN_NORM, 0, PITCH_NORM );

		const Vector vecDir = pev.velocity.Normalize();

		TraceResult tr;

		g_Utility.TraceLine( pev.origin, pev.origin + vecDir * 64, dont_ignore_monsters, self.edict(), tr );
		g_Utility.DecalTrace( tr, DECAL_SPORESPLAT1 + Math.RandomLong(0, 2) );

		NetworkMessage message1( MSG_PVS, NetworkMessages::SVC_TEMPENTITY, pev.origin );
			message1.WriteByte( TE_SPRITE_SPRAY );
			message1.WriteCoord( pev.origin.x );
			message1.WriteCoord( pev.origin.y );
			message1.WriteCoord( pev.origin.z );
			message1.WriteCoord( tr.vecPlaneNormal.x );
			message1.WriteCoord( tr.vecPlaneNormal.y );
			message1.WriteCoord( tr.vecPlaneNormal.z );
			message1.WriteShort( m_iSpitSprite );
			message1.WriteByte( 100 ); // count
			message1.WriteByte( 40 ); // speed
			message1.WriteByte( 180 );
		message1.End();

		NetworkMessage message2( MSG_PVS, NetworkMessages::SVC_TEMPENTITY, pev.origin );
			message2.WriteByte( TE_DLIGHT );
			message2.WriteCoord( pev.origin.x );
			message2.WriteCoord( pev.origin.y );
			message2.WriteCoord( pev.origin.z );
			message2.WriteByte( 10 );
			message2.WriteByte( 15 );
			message2.WriteByte( 220 );
			message2.WriteByte( 40 );
			message2.WriteByte( 5 );
			message2.WriteByte( 10 );
		message2.End();

		NetworkMessage message3( MSG_PVS, NetworkMessages::SVC_TEMPENTITY, pev.origin );
			message3.WriteByte( TE_SPRITE );
			message3.WriteCoord( pev.origin.x );
			message3.WriteCoord( pev.origin.y );
			message3.WriteCoord( pev.origin.z );
			message3.WriteShort( (Math.RandomLong(0, 1) == 1) ? m_iBlow : m_iBlowSmall );
			message3.WriteByte( 20 );
			message3.WriteByte( 128 );
		message3.End();

		NetworkMessage message4( MSG_PVS, NetworkMessages::SVC_TEMPENTITY, pev.origin );
			message4.WriteByte( TE_SPRITE_SPRAY );
			message4.WriteCoord( pev.origin.x );
			message4.WriteCoord( pev.origin.y );
			message4.WriteCoord( pev.origin.z );
			message4.WriteCoord( Math.RandomFloat(-1, 1) );
			message4.WriteCoord( 1 );
			message4.WriteCoord( Math.RandomFloat(-1, 1) );
			message4.WriteShort( m_iTrail );
			message4.WriteByte( 2 ); // count
			message4.WriteByte( 20 ); // speed
			message4.WriteByte( 80 );
		message4.End();

		g_WeaponFuncs.RadiusDamage( pev.origin, self.pev, pev.owner.vars, pev.dmg, 200, 0, DMG_POISON | DMG_ACID | DMG_ALWAYSGIB );

		SetThink( ThinkFunction(SUB_Remove) );

		pev.nextthink = g_Engine.time;
	}

	void FlyThink()
	{
		if( g_Engine.time <= (m_flIgniteTime + 4.0) )
		{
			Vector velocity = pev.velocity.Normalize();

			NetworkMessage message( MSG_PVS, NetworkMessages::SVC_TEMPENTITY, pev.origin );
				message.WriteByte( TE_SPRITE_SPRAY );
				message.WriteCoord( pev.origin.x );
				message.WriteCoord( pev.origin.y );
				message.WriteCoord( pev.origin.z );
				message.WriteCoord( velocity.x );
				message.WriteCoord( velocity.y );
				message.WriteCoord( velocity.z );
				message.WriteShort( m_iTrail );
				message.WriteByte( 2 ); // count
				message.WriteByte( 20 ); // speed
				message.WriteByte( 80 );
			message.End();
		}
		else
			SetThink( ThinkFunction(this.IgniteThink) );

		pev.nextthink = g_Engine.time + 0.03;
	}

	void SporeTouch( CBaseEntity@ pOther )
	{
		if( pOther.pev.takedamage == DAMAGE_NO )
		{
			if( pOther.edict() !is pev.owner )
			{
				if( g_Engine.time > m_flSoundDelay )
				{
					GetSoundEntInstance().InsertSound( bits_SOUND_DANGER, pev.origin, int(floor(pev.dmg / 0.4)), 0.3, self ); 

					m_flSoundDelay = g_Engine.time + 1.0;
				}

				if( (pev.flags & FL_ONGROUND) != 0 )
					pev.velocity = pev.velocity * 0.5;
				else
					g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, "weapons/splauncher_bounce.wav", 0.25, ATTN_NORM, 0, PITCH_NORM );
			}
		}
		else
		{
			pOther.TakeDamage( self.pev, pev.owner.vars, pev.dmg, DMG_GENERIC );

			IgniteThink();
		}
	}

	void SUB_Remove()
	{
		self.SUB_Remove();
	}
}

opfor_spore@ SpitTimed( entvars_t@ pevOwner, Vector vecStart, Vector vecVelocity, float time )
{
	CBaseEntity@ cbeSpore = g_EntityFuncs.CreateEntity( "sporegrenade2", null,  false );
	opfor_spore@ pSpore = cast<opfor_spore@>(CastToScriptClass(cbeSpore));
	g_EntityFuncs.SetOrigin( pSpore.self, vecStart );
	pSpore.pev.movetype = MOVETYPE_BOUNCE;
	@pSpore.pev.owner = pevOwner.pContainingEntity;
	pSpore.pev.velocity = vecVelocity;
	pSpore.pev.velocity = pSpore.pev.velocity + g_Engine.v_forward * 700;
	pSpore.pev.angles = Math.VecToAngles( pSpore.pev.velocity );
	pSpore.pev.dmgtime = (time <= 0 ? 2.5f : time);
	pSpore.pev.sequence = Math.RandomLong(3, 6);
	pSpore.pev.framerate = 1.0f;
	g_EntityFuncs.DispatchSpawn( pSpore.self.edict() );
	//pSpore.Spawn();

	return pSpore;
}

class weapon_strooper : CBaseDriveWeapon
{
	float m_flRechargeTime;
	float m_fNextIdleSound;
	float m_fNextRegenTick;
	float m_fNextHealthCheck;
	float m_fNextCombatSound;
	private bool m_bIsInCombat;
	int g_iShockTrooperQuestion;
	private int m_iVoicePitch;
	//private int m_iState;
	private int m_iShockMuzzleFlash;
	private int m_iBitchSlap;
	private float m_flStopHornetAttack;
	private float m_flLastBlinkInterval;
	private float m_flLastBlinkTime;
	private float m_flNextIdleSound;
	private float m_flNextAlertGrowl;
	private float m_flNextCharge;
	private float m_flHealthRegenTime = 0.0;
	private float m_flRemoveGlowTime = 0.0;
	private float m_flNextHealthCheck = 0.0;
	private float m_flNextAmmoRegen = 0.0;
	
	protected EHandle m_hEnemy;
	protected CBaseEntity@ m_pEnemy
	{
		get const { return m_hEnemy.GetEntity(); }
		set { m_hEnemy = EHandle(@value); }
	}

	void Spawn()
	{
		Precache();

		self.m_iDefaultAmmo = 9999;
		self.m_iDefaultSecAmmo = 0;

		g_EntityFuncs.SetModel( self, "models/strooper.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.pev.set_controller( 0,  127 );
		self.pev.set_controller( 1,  127 );
		//pev.targetname = "nosave";

		m_iState = STATE_IDLE;
		m_iBitchSlap = 0;
		m_flLastBlinkTime = m_flLastBlinkInterval = g_Engine.time;

		if( Math.RandomLong(0, 1) == 1 )
			m_iVoicePitch = 109 + Math.RandomLong( 0, 7 );
		else
			m_iVoicePitch = 100;

		self.FallInit();
	}

	void Precache()
	{
		BaseClass.Precache();
		self.PrecacheCustomModels();
		g_Game.PrecacheGeneric( "sprites/controlnpc/strooper/weapon_strooper.txt" );
		g_Game.PrecacheGeneric( "sprites/controlnpc/crosshairs.spr" );
		g_Game.PrecacheModel( "models/strooper.mdl" );
		g_Game.PrecacheGeneric( "models/strooper.mdl" );
		m_iShockMuzzleFlash = g_Game.PrecacheModel( "sprites/muzzle_shock.spr" );

		g_Game.PrecacheOther( "opfor_spore" );
	}

	bool GetItemInfo( ItemInfo& out info )
	{
		info.iMaxAmmo1	= 9999;
		info.iMaxAmmo2	= 5;
		info.iMaxClip		= WEAPON_NOCLIP;
		info.iSlot		= 0;
		info.iPosition		= 0;
		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;

		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 Holster( int skipLocal = 0 )
	{
		if( m_pDriveEnt !is null )
			@m_pDriveEnt.pev.owner = null;

		ResetPlayer();

		BaseClass.Holster( skipLocal );
	}

	void PrimaryAttack()
	{
		TraceResult tr;
		Math.MakeVectors( m_pPlayer.pev.v_angle );
		g_Utility.TraceHull( m_pPlayer.pev.origin, m_pPlayer.pev.origin + g_Engine.v_forward * 24, dont_ignore_monsters, head_hull, m_pPlayer.edict(), tr );

		if( m_iState == STATE_RUN )
		{
			self.m_flNextPrimaryAttack = g_Engine.time + 0.1;
		}

		if( m_pDriveEnt !is null )
		{
			if( m_iState != STATE_DEATH and tr.flFraction == 1.0 )
			{
				m_iState = STATE_ATTACK_SHOCK;

				m_pDriveEnt.pev.sequence = m_pDriveEnt.LookupSequence("standing_mp5");
				m_pDriveEnt.pev.frame = 0;
				m_pDriveEnt.ResetSequenceInfo();
				m_pDriveEnt.pev.framerate = 1.25;

				SetThink( ThinkFunction(this.HornetAttackThink) );
				pev.nextthink = g_Engine.time + 0.2;
				m_flStopHornetAttack = g_Engine.time + 0.225;
				
				/*if ( m_fNextCombatSound < g_Engine.time )
				{
					m_fNextCombatSound = g_Engine.time + 9999.0;

					g_SoundSystem.PlaySentenceGroup( m_pDriveEnt.edict(), "ST_ALERT", 0.75, ATTN_NORM, 0, m_iVoicePitch );
				}*/
			}
		}
		else
		{
			spawn_driveent();
			self.m_flNextPrimaryAttack = self.m_flNextSecondaryAttack = self.m_flTimeWeaponIdle = g_Engine.time + 0.5;
			m_flNextAlertGrowl = g_Engine.time + 0.0;
			m_flNextCharge = g_Engine.time + 0.0;

			return;
		}

		self.m_flNextPrimaryAttack = g_Engine.time + 0.25;
		self.m_flNextSecondaryAttack = g_Engine.time + 0.3;
		self.m_flTimeWeaponIdle = g_Engine.time + 0.4;
	}

	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_ATTACK_MELEE;
			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) );
		}

		self.m_flNextPrimaryAttack = g_Engine.time + 1.0;
		self.m_flNextSecondaryAttack = g_Engine.time + STROOPER_MELEE_CD;
		self.m_flTimeWeaponIdle = g_Engine.time + STROOPER_MELEE_CD;
	}

	void TertiaryAttack()
	{
		if( m_pPlayer.m_rgAmmo(self.m_iSecondaryAmmoType) != 0 )
		{
			self.m_flTimeWeaponIdle = g_Engine.time + 0.1;
			self.m_flNextTertiaryAttack = g_Engine.time + 0.1;
			return;
		}

		if( m_pDriveEnt !is null and m_iState != STATE_DEATH )
		{
			m_iState = STATE_THROW_SPORE;
			m_pPlayer.SetMaxSpeedOverride( 0 );

			m_pDriveEnt.pev.sequence = m_pDriveEnt.LookupSequence("throwgrenade");
			m_pDriveEnt.pev.frame = 0;
			m_pDriveEnt.ResetSequenceInfo();
			g_SoundSystem.PlaySentenceGroup( m_pDriveEnt.edict(), "ST_THROW", 0.75, ATTN_NORM, 0, m_iVoicePitch );

			SetThink( ThinkFunction(this.ThrowSporeThink) );
			pev.nextthink = g_Engine.time + 1.4;
			m_pPlayer.m_rgAmmo( self.m_iSecondaryAmmoType, m_pPlayer.m_rgAmmo( self.m_iSecondaryAmmoType ) + 5 );
		}

		m_flNextIdleSound = g_Engine.time + 3.0;
		m_fNextRegenTick = g_Engine.time + 6.0;
		self.m_flNextPrimaryAttack = g_Engine.time + 1.45;
		self.m_flNextSecondaryAttack = g_Engine.time + 1.45;
		self.m_flNextTertiaryAttack = g_Engine.time + 4.0;
		self.m_flTimeWeaponIdle = g_Engine.time + 1.45;
	}

	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;
			m_pPlayer.pev.punchangle.x = 0;
                        m_pPlayer.pev.punchangle.z = 0;
                        m_pPlayer.pev.punchangle.y = 0;
			
			DoIdleSound();
			CheckMaxHealth();
			Blink();

			if( m_flHealthRegenTime < g_Engine.time and m_pPlayer.pev.health < m_pPlayer.pev.max_health )
			{
				m_pDriveEnt.pev.rendermode = 0.0;
				m_pDriveEnt.pev.renderfx = kRenderFxGlowShell;
				m_pDriveEnt.pev.renderamt = 1.0;
				m_pDriveEnt.pev.rendercolor = Vector(0, 100, 0);

				m_pPlayer.pev.health += 20;
				if( m_pPlayer.pev.health > m_pPlayer.pev.max_health )
				{
					m_pPlayer.pev.health = m_pPlayer.pev.max_health;
				}
				m_flHealthRegenTime = g_Engine.time + 3.0;
				m_flRemoveGlowTime = g_Engine.time + 1.0;
				g_SoundSystem.EmitSoundDyn( m_pDriveEnt.edict(), CHAN_ITEM, "misc/regen.wav", 0.5, ATTN_NORM, 0, PITCH_NORM );
			}

			if( m_flRemoveGlowTime < g_Engine.time )
			{
				m_pDriveEnt.pev.rendercolor = Vector(0, 0, 0);
				m_flRemoveGlowTime = g_Engine.time + 99999;
			}
			
			if( m_flNextThink <= g_Engine.time )
			{
				LookForEnemies();
				m_flNextThink = g_Engine.time + 0.5;
			}

			if( m_flNextAmmoRegen <= g_Engine.time && m_pPlayer.m_rgAmmo(self.m_iSecondaryAmmoType) != 0 )
			{
				m_pPlayer.m_rgAmmo( self.m_iSecondaryAmmoType, m_pPlayer.m_rgAmmo( self.m_iSecondaryAmmoType ) - 1 );
				m_flNextAmmoRegen = g_Engine.time + 1.0;
			}

			if( m_pPlayer.pev.button & (IN_FORWARD|IN_BACK|IN_MOVELEFT|IN_MOVERIGHT) != 0 )
			{
				if( m_iState != STATE_THROW_SPORE )
				{
					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 and m_iState != STATE_ATTACK_SHOCK and m_iState != STATE_ATTACK_MELEE )
			{
				m_pPlayer.SetMaxSpeedOverride( 270 );
				DoMovementAnimation();
				self.m_flTimeWeaponIdle = g_Engine.time + 0.1;
			}

			if( m_pPlayer.pev.velocity.Length() <= 0 and m_iState != STATE_ATTACK_MELEE and m_iState != STATE_ATTACK_SHOCK and m_iState != STATE_THROW_SPORE )
				DoIdleAnimation();

			if( m_iState == STATE_ATTACK_SHOCK )
			{
				Vector angDir = m_pPlayer.pev.v_angle;

				if( angDir.x > 180 )
					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, 50 );
				m_pPlayer.SetViewMode( ViewMode_ThirdPerson );
			}

			if( m_iState == STATE_WALK )
			{
				m_pPlayer.pev.view_ofs = Vector( 0, 0, 50 );
				m_pPlayer.SetViewMode( ViewMode_ThirdPerson );
			}

			if( m_iState == STATE_RUN )
			{
				m_pPlayer.pev.view_ofs = Vector( 0, 0, 50 );
				m_pPlayer.SetViewMode( ViewMode_ThirdPerson );
			}
		}
	}

	void InactiveItemPostFrame()
	{
		m_pPlayer.SwitchWeapon(self);
	}
	
	CBaseEntity@ BestVisibleEnemy()
	{
		CBaseEntity@ pReturn = null;
		
		while( (@pReturn = g_EntityFuncs.FindEntityInSphere(pReturn, m_pPlayer.pev.origin, 8192, "*", "classname")) !is null )
		{
			if( pReturn.pev.FlagBitSet(FL_MONSTER | FL_CLIENT) and pReturn.IsAlive() and m_pPlayer.IRelationship(pReturn) > R_NO and pReturn.IsAlive() and pReturn.edict() !is m_pPlayer.edict() 
			and !pReturn.pev.FlagBitSet(FL_NOTARGET) and ( m_pPlayer.FInViewCone(pReturn) and m_pPlayer.FVisible(pReturn, true) ) and pReturn.pev.classname != "playerhornet" and pReturn.pev.sequence != 0 )
			{
				return pReturn;
			}
		}

		return null;
	}

	void LookForEnemies()
	{
		if( m_pEnemy is null )
		{
			m_pPlayer.Look( 8192 );
			CBaseEntity@ pEnemy = m_pPlayer.BestVisibleEnemy();
			if( pEnemy !is null and m_pPlayer.FVisible(pEnemy, true) and m_pPlayer.FInViewCone(pEnemy) )
			{
				m_bIsInCombat = true;
				@m_pEnemy = pEnemy;
				m_flNextIdleSound = g_Engine.time + 6.0;

				if( m_flNextAlertGrowl < g_Engine.time )
				{
					AlertGrowl();
					m_flNextAlertGrowl = g_Engine.time + 9999;
				}
			}
			/*else if( pEnemy !is null and !m_pPlayer.FVisible(pEnemy, true) and m_flNextIdleSound < g_Engine.time )
			{
					m_bIsInCombat = true;
					@m_pEnemy = pEnemy;
					m_flNextIdleSound = g_Engine.time + 3.0;
			}*/
		}
		else if( !m_pEnemy.IsAlive() or !m_pPlayer.FVisible(m_pEnemy, true) )
		{
			@m_pEnemy = null;
			m_bIsInCombat = false;
			m_flNextAlertGrowl = g_Engine.time + 0.0;
		}
	}

	void AlertGrowl()
	{
		if( m_pPlayer !is null or m_pDriveEnt !is null )
		{
			g_SoundSystem.PlaySentenceGroup( m_pDriveEnt.edict(), "ST_ALERT", 0.75, ATTN_NORM, 0, m_iVoicePitch );
		}
	}

	void DoIdleSound()
	{
		if( m_flNextIdleSound < g_Engine.time and m_bIsInCombat )
		{
			CombatSound();
		}
		else if( m_flNextIdleSound < g_Engine.time and !m_bIsInCombat )
		{
			if( Math.RandomLong(0, 99) == 0 )
				IdleSound();
		}
	}

	void PainSound()
	{
		if( m_pDriveEnt !is null )
		{
			switch( Math.RandomLong(0, 4) )
			{
				case 0: g_SoundSystem.EmitSound( m_pDriveEnt.edict(), CHAN_VOICE, "shocktrooper/shock_trooper_pain1.wav", VOL_NORM, ATTN_NORM ); break;
				case 1: g_SoundSystem.EmitSound( m_pDriveEnt.edict(), CHAN_VOICE, "shocktrooper/shock_trooper_pain2.wav", VOL_NORM, ATTN_NORM ); break;
				case 2: g_SoundSystem.EmitSound( m_pDriveEnt.edict(), CHAN_VOICE, "shocktrooper/shock_trooper_pain3.wav", VOL_NORM, ATTN_NORM ); break;
				case 3: g_SoundSystem.EmitSound( m_pDriveEnt.edict(), CHAN_VOICE, "shocktrooper/shock_trooper_pain4.wav", VOL_NORM, ATTN_NORM ); break;
				case 4: g_SoundSystem.EmitSound( m_pDriveEnt.edict(), CHAN_VOICE, "shocktrooper/shock_trooper_pain5.wav", VOL_NORM, ATTN_NORM ); break;
			}

			m_flNextIdleSound = g_Engine.time + 3.0;
		}
	}
	
	void CombatSound()
	{
		g_SoundSystem.PlaySentenceGroup( m_pDriveEnt.edict(), "ST_CHARGE", 0.75, ATTN_NORM, 0, m_iVoicePitch );
		m_flNextIdleSound = g_Engine.time + 3.0;
		g_iShockTrooperQuestion = 0;
	}

	void IdleSound()
	{
		m_fNextCombatSound = 0.0;

		if( g_iShockTrooperQuestion != 0 or Math.RandomLong(0, 1) == 1 )
		{
			if( g_iShockTrooperQuestion == 0 )
			{
				// ask question or make statement
				switch( Math.RandomLong(0, 2) )
				{
					case 0: // check in
					{
						g_SoundSystem.PlaySentenceGroup( m_pDriveEnt.edict(), "ST_CHECK", 0.75, ATTN_NORM, 0, m_iVoicePitch );
						g_iShockTrooperQuestion = 1;

						break;
					}

					case 1: // question
					{
						g_SoundSystem.PlaySentenceGroup( m_pDriveEnt.edict(), "ST_QUEST", 0.75, ATTN_NORM, 0, m_iVoicePitch );
						g_iShockTrooperQuestion = 2;

						break;
					}

					case 2: // statement
					{
						g_SoundSystem.PlaySentenceGroup( m_pDriveEnt.edict(), "ST_IDLE", 0.75, ATTN_NORM, 0, m_iVoicePitch );

						break;
					}
				}
			}
			else
			{
				switch( g_iShockTrooperQuestion )
				{
					case 1: // check in
					{
						g_SoundSystem.PlaySentenceGroup( m_pDriveEnt.edict(), "ST_CLEAR", 0.75, ATTN_NORM, 0, m_iVoicePitch );

						break;
					}

					case 2: // question
					{
						g_SoundSystem.PlaySentenceGroup( m_pDriveEnt.edict(), "ST_ANSWER", 0.75, ATTN_NORM, 0, m_iVoicePitch );

						break;
					}
				}

				g_iShockTrooperQuestion = 0;
			}

			m_flNextIdleSound = g_Engine.time + 5.0;
		}
	}

	void Blink()
	{
		float flLastBlink = g_Engine.time - m_flLastBlinkTime;

		if( flLastBlink >= 3.0 )
		{
			if( flLastBlink >= Math.RandomFloat(3.0, 7.0) )
			{
				m_pDriveEnt.pev.skin = 1;

				m_flLastBlinkInterval = g_Engine.time;
				m_flLastBlinkTime = g_Engine.time;
			}
		}

		if( m_pDriveEnt.pev.skin > 0 )
		{
			if( g_Engine.time - m_flLastBlinkInterval >= 0.1 )
			{
				if( m_pDriveEnt.pev.skin == 3 )
					m_pDriveEnt.pev.skin = 0;
				else
					++m_pDriveEnt.pev.skin;

				m_flLastBlinkInterval = g_Engine.time;
			}
		}
	}

	void RegenerateHealth ()
	{
		if( m_pDriveEnt !is null )
		{
			if ( m_fNextRegenTick < g_Engine.time and m_pPlayer.pev.health < 300 )
			{
				m_pPlayer.pev.health += 20;
				m_fNextRegenTick = g_Engine.time + 1.0;
			}
		}
	}

	void CheckMaxHealth ()
	{
		if( m_pDriveEnt !is null )
		{
			if( m_fNextHealthCheck < g_Engine.time and m_pPlayer.pev.health > 800 )
			{
				m_pPlayer.pev.health = 800;
				m_fNextHealthCheck = g_Engine.time + 0.1;

				if( m_pPlayer.pev.max_health > 800 || m_pPlayer.pev.max_health < 800)
				{
					m_pPlayer.pev.max_health = 800;
				}
			}
		}
	}

	void ThrowSporeThink()
	{
		if( m_pDriveEnt !is null and m_pPlayer.IsAlive() )
		{
			/*Vector vecSrc = m_pPlayer.GetGunPosition();
			Vector vecAiming = m_pPlayer.GetAutoaimVector( AUTOAIM_5DEGREES );
			Vector vecDir, vecVel;

			Math.MakeVectors( m_pPlayer.pev.v_angle );

			vecSrc = vecSrc + g_Engine.v_forward * 16;
			vecSrc = vecSrc + g_Engine.v_right * 8;
			vecSrc = vecSrc + g_Engine.v_up * -12;
			vecVel = g_Engine.v_forward * -200;

			opfor_spore@ pSpore = SpitTimed( m_pPlayer.pev, vecSrc, vecVel, 4 );*/

			Math.MakeVectors( m_pPlayer.pev.v_angle );
			Vector vecGrenadeOrigin = m_pDriveEnt.pev.origin + Vector(0, 0, 98);
			Vector vecGrenadeVelocity = m_pPlayer.pev.velocity + g_Engine.v_forward * 750.0;

			CreateSpore( vecGrenadeOrigin, vecGrenadeVelocity );
		}

		SetThink( null );
	}

	void CreateSpore( const Vector &in vecOrigin, const Vector &in vecAngles )
	{
		CBaseEntity@ cbeSpore = g_EntityFuncs.Create( "sporegrenade2", vecOrigin, vecAngles, true, m_pPlayer.edict() );
		sporegrenade2@ pSpore = cast<sporegrenade2@>(CastToScriptClass(cbeSpore));

		pSpore.pev.velocity = vecAngles;
		pSpore.pev.angles = Math.VecToAngles(vecAngles);

		g_EntityFuncs.DispatchSpawn( pSpore.self.edict() );
	}

	void HornetAttackThink()
	{
		if( m_pPlayer is null or m_pDriveEnt is null or m_iState != STATE_ATTACK_SHOCK or m_flStopHornetAttack < g_Engine.time )
		{
			SetThink( null );
			return;
		}

		m_pPlayer.SetMaxSpeedOverride( 0 );

		ShootHornet();
		pev.nextthink = g_Engine.time + SHOCK_REFIRE_TIME;
	}

	void ShootHornet()
	{
		if( m_pDriveEnt !is null and m_pPlayer.IsAlive() )
		{
			Vector vecAngle, vecOrigin, vecMuzzle;
			vecAngle = m_pPlayer.pev.v_angle;
			Vector vecDir = m_pPlayer.GetAutoaimVector( AUTOAIM_2DEGREES ) + g_Engine.v_up * 0.035 - g_Engine.v_right * 0.007;

			if( vecAngle.x < -84.5 ) vecAngle.x = -84.5;
			if( vecAngle.x > 62.0 ) vecAngle.x = 62.0;

			Math.MakeVectors( vecAngle );
			m_pDriveEnt.GetAttachment( 0, vecOrigin, void );

			vecMuzzle = vecOrigin + g_Engine.v_forward * 4 + g_Engine.v_right * -6;

			m_pDriveEnt.pev.effects |= EF_MUZZLEFLASH;

			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_iShockMuzzleFlash );
				m1.WriteByte( 6 ); // size * 10
				m1.WriteByte( 128 ); // brightness
			m1.End();

			CBaseEntity@ pShock = g_EntityFuncs.Create( "shock_beam", vecOrigin + g_Engine.v_forward * 4 + g_Engine.v_right * -10, vecDir, false, m_pPlayer.edict() );
			pShock.pev.velocity = vecDir * 1500; //g_Engine.v_up * 40 + g_Engine.v_forward * 1500;
			pShock.pev.angles = Math.VecToAngles( pShock.pev.velocity );
			pShock.pev.angles.x = -m_pPlayer.pev.v_angle.x; //to get the projectile to face the direction it's travelling in
			Math.MakeVectors ( pShock.pev.angles );

			g_SoundSystem.EmitSoundDyn( m_pDriveEnt.edict(), CHAN_WEAPON, "weapons/shock_fire.wav", 1, ATTN_NORM, 0, 100 );

			m_flNextIdleSound = g_Engine.time + 2.0;
			m_fNextRegenTick = g_Engine.time + 6.0;

			m_pPlayer.SetMaxSpeedOverride( 0 );
		}
	}

	void MeleeAttackThink()
	{
		m_flNextIdleSound = g_Engine.time + 5.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( STROOPER_MELEE_DMG_RANGE, STROOPER_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;
				}
			}

			m_iBitchSlap = 1;
			pev.nextthink = g_Engine.time + 0.4;
			return;
		}
		else if( m_iBitchSlap == 1 ) //right slap
		{
			if( m_iState == STATE_RUN or m_iState == STATE_WALK or m_iState == STATE_IDLE )
			{
				return; //no right slap when I start running or walking
			}

			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( STROOPER_MELEE_DMG_RANGE, STROOPER_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 DoMovementAnimation()
	{
		float flMinWalkVelocity = -150.0;
		float flMaxWalkVelocity = 150.0;

		m_pPlayer.SetMaxSpeedOverride( 270 );

		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("walk1") )
			{
				m_iState = STATE_WALK;
				m_pDriveEnt.pev.sequence = m_pDriveEnt.LookupSequence("walk1");
				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 DoIdleAnimation()
	{
		if( m_pDriveEnt is null ) return;
		if( m_pDriveEnt.pev.sequence == m_pDriveEnt.LookupSequence("throwgrenade") and !m_pDriveEnt.m_fSequenceFinished ) return;
		if( m_pDriveEnt.pev.sequence == m_pDriveEnt.LookupSequence("standing_mp5") and !m_pDriveEnt.m_fSequenceFinished ) return;
		if( m_pDriveEnt.pev.sequence == m_pDriveEnt.LookupSequence("frontkick") and !m_pDriveEnt.m_fSequenceFinished ) return;
		if( m_pDriveEnt.pev.sequence == m_pDriveEnt.LookupSequence("flank_signal") and !m_pDriveEnt.m_fSequenceFinished ) return;

		if( m_iState != STATE_IDLE and !m_bIsInCombat )
		{
			m_pPlayer.SetMaxSpeedOverride( 270 );
			m_iState = STATE_IDLE;
			m_pDriveEnt.pev.sequence = m_pDriveEnt.LookupSequence("idle1");
			m_pDriveEnt.pev.frame = 0;
			m_pDriveEnt.ResetSequenceInfo();
		}
		else if( m_iState != STATE_IDLE_COMBAT and m_bIsInCombat )
		{
			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 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_pPlayer.pev.velocity = g_vecZero;
		Vector vecOrigin = m_pPlayer.pev.origin;
		vecOrigin.z -= 36.0;
		@m_pDriveEnt = cast<CBaseAnimating@>( g_EntityFuncs.Create("cnpc_strooper", vecOrigin, Vector(0, m_pPlayer.pev.angles.y, 0), false, m_pPlayer.edict()) );
		m_pDriveEnt.pev.set_controller( 0,  127 );
		m_pDriveEnt.pev.set_controller( 1,  127 );
		m_pPlayer.m_flFieldOfView = 0.25;
		m_pPlayer.m_flDistLook = 8192.0;
		m_pPlayer.pev.effects |= EF_NODRAW;
		m_pPlayer.pev.iuser3 = 1;
		m_pPlayer.pev.fuser4 = 1; //disable jump
		m_pPlayer.m_bloodColor = BLOOD_COLOR_YELLOW;
		self.m_bExclusiveHold = true;
		m_pPlayer.pev.armorvalue = 10.0;
		m_pPlayer.pev.armortype = 10.0;
		if( m_pPlayer.pev.health < 100 )
		{
			m_pPlayer.pev.health = 800 * ( m_pPlayer.pev.health / m_pPlayer.pev.max_health );
			m_pPlayer.pev.max_health = 800;
		}
		else
		{
			m_pPlayer.pev.health = 800;
			m_pPlayer.pev.max_health = 800;
		}

		NetworkMessage m1( MSG_ONE_UNRELIABLE, NetworkMessages::SVC_STUFFTEXT, m_pPlayer.edict() );
			m1.WriteString( "cam_idealdist 128;cam_idealpitch 5;cl_movespeedkey 0.2;\n" );
		m1.End();

		CustomKeyvalues@ pCustom = m_pPlayer.GetCustomKeyvalues();
		pCustom.SetKeyvalue( CNPC::sCNPCKV, CNPC::CNPC_STROOPER );
		m_pPlayer.pev.view_ofs = Vector( 0, 0, 50 );
	}

	void ResetPlayer()
	{
		m_pPlayer.pev.iuser3 = 0;
		m_pPlayer.pev.fuser4 = 0; //enable jump
		m_pPlayer.SetMaxSpeedOverride( -1 );

		NetworkMessage m1( MSG_ONE_UNRELIABLE, NetworkMessages::SVC_STUFFTEXT, m_pPlayer.edict() );
			m1.WriteString( "cl_movespeedkey 0.3;\n" );
		m1.End();

		CustomKeyvalues@ pCustom = m_pPlayer.GetCustomKeyvalues();
		pCustom.SetKeyvalue( CNPC::sCNPCKV, 0 );
	}
}

class cnpc_strooper : ScriptBaseAnimating
{
	protected CBasePlayer@ m_pOwner
	{
		get { return cast<CBasePlayer@>( g_EntityFuncs.Instance(pev.owner) ); }
	}

	private float m_flNextOriginUpdate; //hopefully fixes hacky movement on other players
	private float m_flNextPainSound = 0.0;

	void Spawn()
	{
		g_EntityFuncs.SetModel( self, "models/strooper.mdl" );
		g_EntityFuncs.SetSize( self.pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX );
		g_EntityFuncs.SetOrigin( self, pev.origin );
		g_EngineFuncs.DropToFloor( self.edict() );

		pev.solid = SOLID_NOT;
		pev.movetype = MOVETYPE_NOCLIP;

		self.pev.sequence = self.LookupSequence("idle1");
		self.pev.frame = 0;
		self.ResetSequenceInfo();

		m_flNextOriginUpdate = g_Engine.time;

		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 -= 36.0;
			g_EntityFuncs.SetOrigin( self, vecOrigin );
			m_flNextOriginUpdate = g_Engine.time + 0.1;
		}

		pev.velocity = m_pOwner.pev.velocity;

		pev.angles.x = 0;

		//if( m_pOwner.pev.button & (IN_FORWARD|IN_BACK|IN_MOVELEFT|IN_MOVERIGHT) != 0 and pev.velocity.Length2D() > 0.0 and (pev.sequence == self.LookupSequence("walk1") or pev.sequence == self.LookupSequence("run")) )
		if( pev.velocity.Length2D() > 0.0 and (pev.sequence == self.LookupSequence("walk1") 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 = 
		{
			"dieforward",
			"dieback1",
			"diebackwards",
			"diegutshot",
			"diesimple",
			"dieheadshot"
		};

		pev.sequence = self.LookupSequence( arrsDeathAnims[Math.RandomLong(0, arrsDeathAnims.length()-1)] );
		pev.frame = 0;
		self.ResetSequenceInfo();

		DropShockroach();

		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 DropShockroach()
	{
		Vector vecAngles = pev.angles;
		vecAngles.x = vecAngles.z = 0;

		self.SetBodygroup( 1, 1 );

		CBaseEntity@ pRoach = g_EntityFuncs.Create( "weapon_shockrifle", pev.origin + Vector(0, 0, 48) , vecAngles, false, self.edict() );
		pRoach.pev.velocity = g_Engine.v_up * 300; //Vector( Math.RandomFloat(-20, 20), Math.RandomFloat(-20, 20), Math.RandomFloat(20, 30) );
		pRoach.pev.avelocity = Vector( 0, Math.RandomFloat(20, 140), 0 );
		pRoach.pev.spawnflags = 7424;
	}

	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_strooper::cnpc_strooper", "cnpc_strooper" );
	//g_CustomEntityFuncs.RegisterCustomEntity( "cnpc_strooper::opfor_spore", "opfor_spore" );
	g_CustomEntityFuncs.RegisterCustomEntity( "cnpc_strooper::sporegrenade2", "sporegrenade2" );
	g_Game.PrecacheOther( "cnpc_strooper" );
	g_Game.PrecacheOther( "sporegrenade2" );

	g_CustomEntityFuncs.RegisterCustomEntity( "cnpc_strooper::weapon_strooper", sWeaponName );
	g_ItemRegistry.RegisterWeapon( sWeaponName, "controlnpc/strooper", "strooper", "strspores" );
	g_Game.PrecacheOther( sWeaponName );
}

} //namespace cnpc_agrunt END

/* FIXME
Holding any button after attacking will cause the animation to freeze at the last frame until released
*/

/* TODO
Use turning animations 
*/
