namespace sc_sporelauncher
{

const float SCW_DAMAGE				= 100;
const float SCW_TIME_DELAY			= 0.5f;
const float SCW_TIME_DRAW			= 1.0f;
const int SCW_MAX_CARRY				= 20;
const int SCW_MAX_CLIP				= 8;
const int SCW_DEFAULT_GIVE			= 8;
const int SCW_WEIGHT				= 10;
const int SCSPORE_SLOT				= 6;
const int SCSPORE_POSITION			= 12;

const string SCW_ANIMEXT			= "rpg";

enum scw_e
{
	ANIM_IDLE1 = 0,
	ANIM_FIDGET,
	ANIM_RELOAD_REACH,
	ANIM_RELOAD_LOAD,
	ANIM_RELOAD_AIM,
	ANIM_FIRE,
	ANIM_HOLSTER,
	ANIM_DRAW,
	ANIM_IDLE2,
	ANIM_RELOAD_AIMFAST
};

enum ReloadState
{
	NOT_RELOADING = 0,
	DO_RELOAD_EFFECTS,
	RELOAD_ONE
};

enum SporeType
{
	ROCKET = 1,
	GRENADE = 2
};


class weapon_scsporelauncher : CBaseCustomWeapon
{
	int g_sModelIndexTinySpit, g_sModelIndexSpore1, g_sModelIndexSpore3;
	ReloadState m_ReloadState;
	float m_flNextReload;

	void Spawn()
	{
		Precache();
		g_EntityFuncs.SetModel( self, "models/w_spore_launcher.mdl" );
		self.FallInit();
		self.m_iDefaultAmmo = SCW_MAX_CARRY;

		pev.sequence = 0;
		pev.animtime = g_Engine.time;
		pev.framerate = 1;
	}

	void Precache()
	{
		BaseClass.Precache();
		self.PrecacheCustomModels();

		g_Game.PrecacheModel( "models/w_spore_launcher.mdl" );
		g_Game.PrecacheModel( "models/v_spore_launcher.mdl" );
		g_Game.PrecacheModel( "models/p_spore_launcher.mdl" );

		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_fire.wav" );
		g_SoundSystem.PrecacheSound( "weapons/splauncher_altfire.wav" );
		g_SoundSystem.PrecacheSound( "weapons/splauncher_bounce.wav" );
		g_SoundSystem.PrecacheSound( "weapons/splauncher_reload.wav" );
		g_SoundSystem.PrecacheSound( "weapons/splauncher_pet.wav" );

		g_Game.PrecacheOther( "sc_spore" );

		//Precache these for downloading
		g_Game.PrecacheGeneric( "sprites/custom_weapons/svencoop/weapon_scsporelauncher.txt" );
	}

	bool GetItemInfo( ItemInfo& out info )
	{
		info.iMaxAmmo1		= SCW_MAX_CARRY;
		info.iMaxAmmo2		= -1;
		info.iMaxClip		= SCW_MAX_CLIP;
		info.iSlot			= SCSPORE_SLOT - 1;
		info.iPosition		= SCSPORE_POSITION - 1;
		info.iFlags			= 0;
		info.iWeight		= SCW_WEIGHT;

		return true;
	}

	bool AddToPlayer( CBasePlayer@ pPlayer )
	{
		if( BaseClass.AddToPlayer(pPlayer) )
		{
			@m_pPlayer = pPlayer;

			NetworkMessage m( MSG_ONE, NetworkMessages::WeapPickup, pPlayer.edict() );
				m.WriteLong( self.m_iId );
			m.End();

			return true;
		}

		return false;
	}

	bool Deploy()
	{
		//return self.DefaultDeploy( self.GetV_Model("models/v_spore_launcher.mdl"), self.GetP_Model("models/p_spore_launcher.mdl"), ANIM_DRAW, SCW_ANIMEXT );

		bool bResult;
		{
			bResult = self.DefaultDeploy( self.GetV_Model("models/v_spore_launcher.mdl"), self.GetP_Model("models/p_spore_launcher.mdl"), ANIM_DRAW, SCW_ANIMEXT );
			self.m_flTimeWeaponIdle = self.m_flNextPrimaryAttack = self.m_flNextSecondaryAttack = g_Engine.time + SCW_TIME_DRAW;

			return bResult;
		}
	}

	void Holster( int skipLocal = 0 )
	{
		m_ReloadState = NOT_RELOADING;
		self.SendWeaponAnim( ANIM_HOLSTER );
	}

	void PrimaryAttack()
	{
		if( self.m_iClip > 0 )
		{
			m_pPlayer.m_iWeaponVolume = LOUD_GUN_VOLUME;
			m_pPlayer.m_iWeaponFlash = BRIGHT_GUN_FLASH;

			m_pPlayer.SetAnimation( PLAYER_ATTACK1 );

			Vector vecAngles = m_pPlayer.pev.v_angle + m_pPlayer.pev.punchangle;

			Math.MakeVectors( vecAngles );

			const Vector vecSrc = 
				m_pPlayer.GetGunPosition() + 
				g_Engine.v_forward * 16 +
				g_Engine.v_right * 8 + 
				g_Engine.v_up * -8;

			//vecAngles = vecAngles + m_pPlayer.GetAutoaimVectorFromPoint( vecSrc, AUTOAIM_10DEGREES );
			vecAngles = vecAngles + m_pPlayer.GetAutoaimVector( AUTOAIM_10DEGREES );

			sc_spore@ pSpore = CreateSpore( vecSrc, vecAngles, m_pPlayer.pev, ROCKET, false, false );

			Math.MakeVectors( vecAngles );

			pSpore.pev.velocity = pSpore.pev.velocity.Length() * g_Engine.v_forward;

			g_SoundSystem.EmitSoundDyn( m_pPlayer.edict(), CHAN_WEAPON, "weapons/splauncher_fire.wav", 0.9f, ATTN_NORM, 0, PITCH_NORM );

			self.SendWeaponAnim( ANIM_FIRE );
			m_pPlayer.pev.punchangle.x = -3.0f;

			Vector vecStart;
			Vector forward;
			{
				Vector dummy;
				self.GetAttachment( 1, vecStart, dummy );
				g_EngineFuncs.AngleVectors( m_pPlayer.pev.v_angle, forward, dummy, dummy );
			}

			NetworkMessage spitmsg( MSG_BROADCAST, NetworkMessages::SVC_TEMPENTITY );
				spitmsg.WriteByte( TE_SPRITE_SPRAY );
				spitmsg.WriteCoord( vecStart.x );
				spitmsg.WriteCoord( vecStart.y );
				spitmsg.WriteCoord( vecStart.z );
				spitmsg.WriteCoord( forward.x );
				spitmsg.WriteCoord( forward.y );
				spitmsg.WriteCoord( forward.z );
				spitmsg.WriteShort( g_sModelIndexTinySpit );
				spitmsg.WriteByte( 10 );
				spitmsg.WriteByte( 10 );//speed
				spitmsg.WriteByte( 180 );//noise ( client will divide by 100 )
			spitmsg.End();

			--self.m_iClip;
		}

		self.m_flNextPrimaryAttack = self.m_flNextSecondaryAttack = self.m_flTimeWeaponIdle = g_Engine.time + SCW_TIME_DELAY;

		m_ReloadState = NOT_RELOADING;
	}

	void SecondaryAttack()
	{
		if( self.m_iClip > 0 )
		{
			m_pPlayer.m_iWeaponVolume = LOUD_GUN_VOLUME;
			m_pPlayer.m_iWeaponFlash = BRIGHT_GUN_FLASH;

			m_pPlayer.SetAnimation( PLAYER_ATTACK1 );

			Vector vecAngles = m_pPlayer.pev.v_angle + m_pPlayer.pev.punchangle;

			Math.MakeVectors( vecAngles );

			const Vector vecSrc =
				m_pPlayer.GetGunPosition() +
				g_Engine.v_forward * 16 +
				g_Engine.v_right * 8 +
				g_Engine.v_up * -8;

			//vecAngles = vecAngles + m_pPlayer.GetAutoaimVectorFromPoint( vecSrc, AUTOAIM_10DEGREES );
			vecAngles = vecAngles + m_pPlayer.GetAutoaimVector( AUTOAIM_10DEGREES );

			sc_spore@ pSpore = CreateSpore( vecSrc, vecAngles, m_pPlayer.pev, GRENADE, false, false );

			Math.MakeVectors( vecAngles );

			pSpore.pev.velocity = m_pPlayer.pev.velocity + 800 * g_Engine.v_forward;

			g_SoundSystem.EmitSoundDyn( m_pPlayer.edict(), CHAN_WEAPON, "weapons/splauncher_altfire.wav", 0.9f, ATTN_NORM, 0, PITCH_NORM );

			self.SendWeaponAnim( ANIM_FIRE );
			m_pPlayer.pev.punchangle.x = -3.0f;

			Vector vecStart;
			Vector forward;
			{
				Vector dummy;
				self.GetAttachment( 1, vecStart, dummy );
				g_EngineFuncs.AngleVectors( m_pPlayer.pev.v_angle, forward, dummy, dummy );
			}

			NetworkMessage spitmsg( MSG_BROADCAST, NetworkMessages::SVC_TEMPENTITY );
				spitmsg.WriteByte( TE_SPRITE_SPRAY );
				spitmsg.WriteCoord( vecStart.x );
				spitmsg.WriteCoord( vecStart.y );
				spitmsg.WriteCoord( vecStart.z );
				spitmsg.WriteCoord( forward.x );
				spitmsg.WriteCoord( forward.y );
				spitmsg.WriteCoord( forward.z );
				spitmsg.WriteShort( g_sModelIndexTinySpit );
				spitmsg.WriteByte( 10 );
				spitmsg.WriteByte( 10 );//speed
				spitmsg.WriteByte( 180 );//noise ( client will divide by 100 )
			spitmsg.End();

			--self.m_iClip;
		}

		self.m_flNextPrimaryAttack = self.m_flNextSecondaryAttack = self.m_flTimeWeaponIdle = g_Engine.time + SCW_TIME_DELAY;

		m_ReloadState = NOT_RELOADING;
	}

	void Reload()
	{
		int ammo = m_pPlayer.m_rgAmmo(self.m_iPrimaryAmmoType);

		if( ammo <= 0 or self.m_iClip >= SCW_MAX_CLIP )
			return;

		// don't reload until recoil is done
		if( self.m_flNextPrimaryAttack > g_Engine.time )
			return;

		// check to see if we're ready to reload
		if( m_ReloadState == NOT_RELOADING )
		{
			self.SendWeaponAnim( ANIM_RELOAD_REACH );
			m_ReloadState = DO_RELOAD_EFFECTS;
			self.m_flTimeWeaponIdle = g_Engine.time + 0.66f;
			self.m_flNextPrimaryAttack = g_Engine.time + 0.66f;
			self.m_flNextSecondaryAttack = g_Engine.time + 0.66f;

			return;
		}
		else if( m_ReloadState == DO_RELOAD_EFFECTS )
		{
			if( self.m_flTimeWeaponIdle > g_Engine.time )
				return;
			// was waiting for gun to move to side
			m_ReloadState = RELOAD_ONE;

			g_SoundSystem.EmitSoundDyn( m_pPlayer.edict(), CHAN_ITEM, "weapons/splauncher_reload.wav", 0.7f, ATTN_NORM, 0, 100 );

			self.SendWeaponAnim( ANIM_RELOAD_LOAD );

			m_flNextReload = g_Engine.time + 1;
			self.m_flTimeWeaponIdle = g_Engine.time + 1;
		}
		else
		{
			self.m_iClip += 1;
			ammo -= 1;
			m_pPlayer.m_rgAmmo( self.m_iPrimaryAmmoType, ammo );
			m_ReloadState = DO_RELOAD_EFFECTS;
		}
	}

	bool ShouldWeaponIdle()
	{
		return true;
	}

	void WeaponIdle()
	{
		self.ResetEmptySound();

		m_pPlayer.GetAutoaimVector( AUTOAIM_10DEGREES );

		if( self.m_flTimeWeaponIdle < g_Engine.time )
		{
			if( self.m_iClip == 0 and m_ReloadState == NOT_RELOADING and m_pPlayer.m_rgAmmo(self.m_iPrimaryAmmoType) > 0 )
				Reload();
			else if( m_ReloadState != NOT_RELOADING )
			{
				if( self.m_iClip != SCW_MAX_CLIP and m_pPlayer.m_rgAmmo(self.m_iPrimaryAmmoType) > 0 )
					Reload();
				else
				{
					// reload debounce has timed out
					self.SendWeaponAnim( ANIM_RELOAD_AIM );

					m_ReloadState = NOT_RELOADING;
					self.m_flTimeWeaponIdle = g_Engine.time + 0.83f;
				}
			}
			else
			{
				int iAnim;
				float flRand = g_PlayerFuncs.SharedRandomFloat( m_pPlayer.random_seed, 0, 1 );
				if( flRand <= 0.75f )
				{
					iAnim = ANIM_IDLE1;
					self.m_flTimeWeaponIdle = g_Engine.time + 2;
				}
				else if( flRand <= 0.95f )
				{
					iAnim = ANIM_IDLE2;
					self.m_flTimeWeaponIdle = g_Engine.time + 4;
				}
				else
				{
					iAnim = ANIM_FIDGET;
					self.m_flTimeWeaponIdle = g_Engine.time + 4;

					g_SoundSystem.EmitSoundDyn( m_pPlayer.edict(), CHAN_ITEM, "weapons/splauncher_pet.wav", 0.7f, ATTN_NORM, 0, PITCH_NORM );
				}

				self.SendWeaponAnim( iAnim );
			}
		}
	}
}

class sc_spore : ScriptBaseMonsterEntity
{
	private CSprite@ m_pSprite;
	private int m_iBlow, m_iBlowSmall, m_iSpitSprite, m_iTrail;
	int m_SporeType;
	private float m_flIgniteTime;
	private float m_flSoundDelay;
	bool m_bPuked;
	bool m_bIsAI;

	void Spawn()
	{
		Precache();

		if( m_SporeType == GRENADE )
			pev.movetype = MOVETYPE_BOUNCE;
		else
			pev.movetype = MOVETYPE_FLY;

		pev.solid = SOLID_BBOX;

		g_EntityFuncs.SetModel( self, "models/spore.mdl" );

		g_EntityFuncs.SetSize( self.pev, g_vecZero, g_vecZero );

		pev.origin = self.GetOrigin();

		SetThink( ThinkFunction(this.FlyThink) );

		if( m_SporeType == GRENADE )
		{
			SetTouch( TouchFunction(this.MyBounceTouch) );

			if( !m_bPuked )
			{
				Vector vecAngles = pev.angles;
				vecAngles.x -= ( Math.RandomLong( -5, 5 ) + 30 );
			}
		}
		else
			SetTouch( TouchFunction(this.RocketTouch) );

		Math.MakeVectors( pev.angles );

		if( !m_bIsAI )
		{
			if( m_SporeType != GRENADE )
				pev.velocity = g_Engine.v_forward * 1200;
		}
		else
		{
			pev.gravity = 0.5f;
			pev.friction = 0.7f;
		}

		pev.dmg = SCW_DAMAGE;

		m_flIgniteTime = g_Engine.time;

		SetNextThink( g_Engine.time + 0.01f );

		@m_pSprite = g_EntityFuncs.CreateSprite( "sprites/glow01.spr", self.GetOrigin(), 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 BounceSound()
	{
		//Nothing
	}

	void IgniteThink()
	{
		SetThink( null );
		SetTouch( null );

		if( m_pSprite !is null )
		{
			g_EntityFuncs.Remove( m_pSprite );
			@m_pSprite = null;
		}

		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( self.GetOrigin(), self.GetOrigin() + vecDir * (m_SporeType == GRENADE ? 64 : 32), dont_ignore_monsters, self.edict(), tr );
		g_Utility.DecalTrace( tr, DECAL_SPORESPLAT1 + Math.RandomLong(0, 2) );

		NetworkMessage message1( MSG_PVS, NetworkMessages::SVC_TEMPENTITY, self.GetOrigin() );
			message1.WriteByte( TE_SPRITE_SPRAY );
			message1.WriteCoord( self.GetOrigin().x );
			message1.WriteCoord( self.GetOrigin().y );
			message1.WriteCoord( self.GetOrigin().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, self.GetOrigin() );
			message2.WriteByte( TE_DLIGHT );
			message2.WriteCoord( self.GetOrigin().x );
			message2.WriteCoord( self.GetOrigin().y );
			message2.WriteCoord( self.GetOrigin().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, self.GetOrigin() );
			message3.WriteByte( TE_SPRITE );
			message3.WriteCoord( self.GetOrigin().x );
			message3.WriteCoord( self.GetOrigin().y );
			message3.WriteCoord( self.GetOrigin().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, self.GetOrigin() );
			message4.WriteByte( TE_SPRITE_SPRAY );
			message4.WriteCoord( self.GetOrigin().x );
			message4.WriteCoord( self.GetOrigin().y );
			message4.WriteCoord( self.GetOrigin().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();

		entvars_t@ pevOwner;
		if( pev.owner !is null )
			@pevOwner = pev.owner.vars;
		else
			@pevOwner = null;

		g_WeaponFuncs.RadiusDamage( self.GetOrigin(), self.pev, pevOwner, pev.dmg, 200, 0, DMG_ALWAYSGIB|DMG_BLAST );

		SetThink( ThinkFunction(SUB_Remove) );

		SetNextThink( g_Engine.time );
	}

	void FlyThink()
	{
		const float flDelay = m_bIsAI ? 4.0f : 2.0f;

		if( m_SporeType != GRENADE or (g_Engine.time <= m_flIgniteTime + flDelay) )
		{
			Vector velocity = pev.velocity.Normalize();

			NetworkMessage message( MSG_PVS, NetworkMessages::SVC_TEMPENTITY, self.GetOrigin() );
				message.WriteByte( TE_SPRITE_SPRAY );
				message.WriteCoord( self.GetOrigin().x );
				message.WriteCoord( self.GetOrigin().y );
				message.WriteCoord( self.GetOrigin().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) );

		SetNextThink( g_Engine.time + 0.03f );
	}

	void GibThink()
	{
		//Nothing
	}

	void RocketTouch( CBaseEntity@ pOther )
	{
		if( pOther.pev.takedamage != DAMAGE_NO )
		{
			entvars_t@ pevOwner;
			if( pev.owner !is null )
				@pevOwner = pev.owner.vars;
			else
				@pevOwner = null;

			pOther.TakeDamage( pevOwner, pevOwner, SCW_DAMAGE, DMG_GENERIC );
		}

		IgniteThink();
	}

	void MyBounceTouch( 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, self.GetOrigin(), int(floor(SCW_DAMAGE / 0.4f)), 0.3f, self ); 

					m_flSoundDelay = g_Engine.time + 1.0f;
				}

				if( (pev.flags & FL_SWIM) != 0 )
				{
					pev.velocity = pev.velocity * 0.5f;

					pev.sequence = 0;
				}
				else
					g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, "weapons/splauncher_bounce.wav", 0.25f, ATTN_NORM, 0, PITCH_NORM );
			}
		}
		else
		{
			entvars_t@ pevOwner;
			if( pev.owner !is null )
				@pevOwner = pev.owner.vars;
			else
				@pevOwner = null;

			pOther.TakeDamage( pevOwner, pevOwner, SCW_DAMAGE, DMG_GENERIC );

			IgniteThink();
		}
	}

	void SetNextThink( float flDelay )
	{
		pev.nextthink = flDelay;
	}

	void SUB_Remove()
	{
		self.SUB_Remove();
	}
}

sc_spore@ CreateSpore( const Vector &in vecOrigin, const Vector &in vecAngles, entvars_t@ pevOwner, int sporeType, bool bIsAI, bool bPuked )
{
	CBaseEntity@ cbeSpore = g_EntityFuncs.CreateEntity( "sc_spore", null,  false );
	sc_spore@ pSpore = cast<sc_spore@>(CastToScriptClass(cbeSpore));

	g_EntityFuncs.SetOrigin( pSpore.self, vecOrigin );

	pSpore.m_SporeType = sporeType;

	if( bIsAI )
	{
		pSpore.pev.velocity = vecAngles;

		pSpore.pev.angles = Math.VecToAngles(vecAngles);
	}
	else
		pSpore.pev.angles = vecAngles;

	pSpore.m_bIsAI = bIsAI;
	pSpore.m_bPuked = bPuked;
	g_EntityFuncs.DispatchSpawn( pSpore.self.edict() );
	@pSpore.pev.owner = pevOwner.pContainingEntity;

	return pSpore;
}

void Register()
{
	g_CustomEntityFuncs.RegisterCustomEntity( "sc_sporelauncher::weapon_scsporelauncher", "weapon_scsporelauncher" );
	g_CustomEntityFuncs.RegisterCustomEntity( "sc_sporelauncher::sc_spore", "sc_spore" );
	g_ItemRegistry.RegisterWeapon( "weapon_scsporelauncher", "custom_weapons/svencoop", "sporeclip" ); //sc_sporeclip
}

} //namespace sc_sporelauncher END