// Code derived from Insecure by Nevermore2790.

namespace SVEN_ZOMBIE_HEV
{
	const int ZOMBIE_AE_ATTACK_RIGHT 	= 1;
	const int ZOMBIE_AE_ATTACK_LEFT 	= 2;
	const int ZOMBIE_AE_ATTACK_BOTH 	= 3;
	
	const int ZOMBIE_FLINCH_DELAY 		= 2;
	
	const int g_iHealth 				= int( g_EngineFuncs.CVarGetFloat( "sk_zombie_health" ) * 2.0 );
	const int g_iOneSlash 				= int( g_EngineFuncs.CVarGetFloat( "sk_zombie_soldier_dmg_one_slash" ) * 2.0 );
	const int g_iBothSlash 				= int( g_EngineFuncs.CVarGetFloat( "sk_zombie_soldier_dmg_both_slash" ) * 2.0 );
	
	const float YAW_SPEED 				= 120;
	const float ZOMBIE_MELEE_RANGE		= 70;
	
	const string ZOMBIE_MODEL_CLASSIC 	= "models/sc_insecure/hlclassic/zombie_hev.mdl";
	const string ZOMBIE_MODEL 			= "models/sc_insecure/zombie_hev.mdl";
	
	const int SF_MONSTER_GAG 			= 2;
	
	// Sounds
	void PrecacheSoundArray(const array<string>& in arr)
	{
		for ( uint i = 0; i < arr.length(); i++ )
			g_SoundSystem.PrecacheSound( arr[i] );
	}

	string RandomSoundFromArray(const array<string>& in arr)
	{
		return arr[Math.RandomLong( 0, arr.length() - 1 )];
	}
	
	const array<string> pAttackHitSounds =
	{
		"zombie/claw_strike1.wav",
		"zombie/claw_strike2.wav",
		"zombie/claw_strike3.wav"
	};
	
	const array<string> pAttackMissSounds =
	{
		"zombie/claw_miss1.wav",
		"zombie/claw_miss2.wav"
	};
	
	const array<string> pAttackSounds =
	{
		"zombie/zo_attack1.wav",
		"zombie/zo_attack2.wav"
	};
	
	const array<string> pIdleSounds =
	{
		"zombie/zo_idle1.wav",
		"zombie/zo_idle2.wav",
		"zombie/zo_idle3.wav",
		"zombie/zo_idle4.wav"
	};
	
	const array<string> pIdleHEVSounds =
	{
		"insecure/zombie/hev_zo_idle1.wav",
		"insecure/zombie/hev_zo_idle2.wav",
		"insecure/zombie/hev_zo_idle3.wav",
		"insecure/zombie/hev_zo_idle4.wav",
		"insecure/zombie/hev_zo_idle5.wav",
		"insecure/zombie/hev_zo_idle6.wav",
		"insecure/zombie/hev_zo_idle7.wav"
	};

	const array<string> pAlertSounds =
	{
		"zombie/zo_alert10.wav",
		"zombie/zo_alert20.wav",
		"zombie/zo_alert30.wav"
	};
	
	const array<string> pPainSounds =
	{
		"zombie/zo_pain1.wav",
		"zombie/zo_pain2.wav"
	};
	
	class CZombieHEV : ScriptBaseMonsterEntity
	{
		private float m_flNextFlinch, m_flNextTalk;

		CBaseEntity@ CheckTraceHullAttack( CBaseMonster@ pThis, float flDist, int iDamage, int iDmgType ) 
		{
			TraceResult tr;

			if ( pThis.IsPlayer() ) 
			{
				Math.MakeVectors( pThis.pev.angles );
			} 
			else 
			{
				Math.MakeAimVectors( pThis.pev.angles );
			}

			Vector vecStart = pThis.pev.origin;
			vecStart.z += pThis.pev.size.z * 0.5;
			Vector vecEnd = vecStart + (g_Engine.v_forward * flDist );

			g_Utility.TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, pThis.edict(), tr );
			
			if ( tr.pHit !is null ) 
			{
				CBaseEntity@ pEntity = g_EntityFuncs.Instance( tr.pHit );
				
				if ( iDamage > 0 ) 
				{
					pEntity.TakeDamage( pThis.pev, pThis.pev, iDamage, iDmgType );
				}
				
				return pEntity;
			}
			
			return null;
		}
		
		int ObjectCaps()
		{
			if ( self.IsPlayerAlly() )
				return FCAP_IMPULSE_USE;
			else
				return BaseClass.ObjectCaps();
		}
		
		//=========================================================
		// Classify - indicates this monster's place in the 
		// relationship table.
		//=========================================================
		int Classify()
		{
			// player ally override
			if ( self.IsPlayerAlly() )
				return CLASS_PLAYER_ALLY;
				
			if ( self.m_fOverrideClass )
				return self.m_iClassSelection;
			
			return CLASS_ALIEN_MONSTER;
		}
		
		//=========================================================
		// SetYawSpeed - allows each sequence to have a different
		// turn rate associated with it.
		//=========================================================
		void SetYawSpeed()
		{
			int ys;
			
			ys = YAW_SPEED;
			
			self.pev.yaw_speed = ys * 2.5;
		}

		void TraceAttack( entvars_t@ pevAttacker, float flDamage, const Vector& in vecDir, TraceResult& in ptr, int bitsDamageType)
		{
			if ( ptr.iHitgroup == HITGROUP_HEAD || ptr.iHitgroup == HITGROUP_RIGHTARM || ptr.iHitgroup == HITGROUP_LEFTARM /*|| ptr.iHitgroup == HITGROUP_CHEST*/ )
			{
				self.m_bloodColor = BLOOD_COLOR_GREEN;
			}
			else
			{
				if ( ( bitsDamageType & ( DMG_BULLET | DMG_SLASH | DMG_CLUB ) ) != 0 )
				{
					flDamage -= 20;
							
					if ( flDamage <= 0 )
					{
						g_Utility.Sparks( ptr.vecEndPos );
						g_Utility.Ricochet( ptr.vecEndPos, 1.0 );
						self.m_bloodColor = DONT_BLEED;
						flDamage = 0.01;
					}
				}
			}

			BaseClass.TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType );
		}
		
		int TakeDamage( entvars_t@ pevInflictor, entvars_t@ pevAttacker, float flDamage, int bitsDamageType )
		{
			if ( pevAttacker is null )
				return 0;

			CBaseEntity@ pAttacker = g_EntityFuncs.Instance( pevAttacker );

			if ( self.CheckAttacker( pAttacker ) )
				return 0;
			
			// Take 50% damage from bullets, and melee.
			if ( ( bitsDamageType & DMG_BULLET ) != 0 || ( bitsDamageType & DMG_CLUB ) != 0 || ( bitsDamageType & DMG_SLASH ) != 0 )
			{
				Vector vecDir = self.pev.origin - ( pevInflictor.absmin + pevInflictor.absmax) * 0.5;
				vecDir = vecDir.Normalize();
				float flForce = flDamage * 0.5;
				self.pev.velocity = self.pev.velocity + vecDir * flForce;
				flDamage *= 0.5;
			}
			
			// HACK HACK -- until we fix this.
			if ( self.IsAlive() )
			{
				PainSound();
			}
			
			return BaseClass.TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType );
		}

		void PainSound()
		{
			int iPitch = 95 + Math.RandomLong(0, 9);

			if ( Math.RandomLong( 0, 5 ) < 2 )
				g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_STATIC, RandomSoundFromArray( pPainSounds ), 1.0, ATTN_NORM, 0, iPitch );
		}

		void AlertSound()
		{
			int iPitch = 95 + Math.RandomLong(0, 9);

			g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_STATIC, RandomSoundFromArray( pAlertSounds ), 1.0, ATTN_NORM, 0, iPitch );
		}

		void IdleSound()
		{
			int iPitch = 100 + Math.RandomLong(-5, 5);

			// Play a random idle sound
			g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_STATIC, RandomSoundFromArray( pIdleSounds ), 1.0, ATTN_NORM, 0, iPitch );
		}

		void AttackSound()
		{
			int iPitch = 100 + Math.RandomLong(-5, 5);

			// Play a random attack sound
			g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_STATIC, RandomSoundFromArray( pAttackSounds ), 1.0, ATTN_NORM, 0, iPitch );
		}

		void AttackHitSound()
		{
			int iPitch = 100 + Math.RandomLong( -5, 5 );
			
			g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_WEAPON, RandomSoundFromArray( pAttackHitSounds ), 1.0, ATTN_NORM, 0, iPitch );
		}

		void AttackMissSound()
		{
			int iPitch = 100 + Math.RandomLong( -5, 5 );
			
			g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_WEAPON, RandomSoundFromArray( pAttackMissSounds ), 1.0, ATTN_NORM, 0, iPitch );
		}
		
		void DeathSound()
		{
			int iPitch = 100 + Math.RandomLong( -5, 5 );
			
			if ( Math.RandomLong( 0, 5 ) < 2 )
				g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_STATIC, RandomSoundFromArray( pPainSounds ), 1.0, ATTN_NORM, 0, iPitch );

			// hev suit dead user
			g_SoundSystem.EmitGroupNameSuit( self.edict(), "HEV_DEAD" );
		}

		void IdleHEVSounds()
		{
			if ( ( self.pev.spawnflags & SF_MONSTER_GAG ) == 0 )
			{
				//g_SoundSystem.PlaySentenceGroup( self.edict(), "HEV_ZOMBIE", 0.85, ATTN_NORM, 0, PITCH_NORM );	-- Not available in default sentences. Gonna use EmitSoundDyn instead.
				g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_BODY, RandomSoundFromArray( pIdleHEVSounds ), 0.85, ATTN_NORM, 0, PITCH_NORM );
			}
		}

		//=========================================================
		// HandleAnimEvent - catches the monster-specific messages
		// that occur when tagged animation frames are played.
		//=========================================================
		void HandleAnimEvent( MonsterEvent@ pEvent )
		{
			switch ( pEvent.event )
			{
				case ZOMBIE_AE_ATTACK_RIGHT:
				{
					CBaseEntity@ pHurt = CheckTraceHullAttack( self, ZOMBIE_MELEE_RANGE, g_iOneSlash, DMG_SLASH );
					
					if ( pHurt !is null )
					{
						if ( pHurt.pev.flags & ( FL_MONSTER | FL_CLIENT ) != 0 )
						{
							pHurt.pev.punchangle.z = -18;
							pHurt.pev.punchangle.x = 5;
							pHurt.pev.velocity = pHurt.pev.velocity - g_Engine.v_right * 100;
						}
						
						// Play a random attack hit sound
						AttackHitSound();
					}
					else
					{
						AttackMissSound();
					}
					
					if ( Math.RandomLong( 0, 1 ) > 0 )
					{
						AttackSound();
					}
					
					break;
				}
				case ZOMBIE_AE_ATTACK_LEFT:
				{
					CBaseEntity@ pHurt = CheckTraceHullAttack(self, ZOMBIE_MELEE_RANGE, g_iOneSlash, DMG_SLASH );
					
					if ( pHurt !is null )
					{
					
						if ( pHurt.pev.flags & ( FL_MONSTER | FL_CLIENT ) != 0 )
						{
							pHurt.pev.punchangle.z = 18;
							pHurt.pev.punchangle.x = 5;
							pHurt.pev.velocity = pHurt.pev.velocity - g_Engine.v_right * 100;
						}
						
						// Play a random attack hit sound
						AttackHitSound();
					}
					else
					{
						AttackMissSound();
					}
					
					if ( Math.RandomLong( 0, 1 ) > 0 )
					{
						AttackSound();
					}
					
					break;
				}
				case ZOMBIE_AE_ATTACK_BOTH:
				{
					CBaseEntity@ pHurt = CheckTraceHullAttack(self, ZOMBIE_MELEE_RANGE, g_iBothSlash, DMG_SLASH );
					
					if ( pHurt !is null )
					{
					
						if ( pHurt.pev.flags & ( FL_MONSTER | FL_CLIENT ) != 0 )
						{
							pHurt.pev.punchangle.x = 5;
							pHurt.pev.velocity = pHurt.pev.velocity - g_Engine.v_right * 100;
						}
						
						// Play a random attack hit sound
						AttackHitSound();
					}
					else
					{
						AttackMissSound();
					}
					
					if ( Math.RandomLong( 0, 1 ) > 0 )
					{
						AttackSound();
					}
					
					break;
				}
				default:
					BaseClass.HandleAnimEvent( pEvent );
					break;
			}
		}
		
		//=========================================================
		// Spawn
		//=========================================================
		void Spawn()
		{
			Precache();
			
			// Added Classic Mode Support.
			if ( !self.SetupModel() )
			{
				if ( string( self.pev.model ).IsEmpty() )
				{
					if ( g_ClassicMode.IsEnabled() )
						g_EntityFuncs.SetModel( self, ZOMBIE_MODEL_CLASSIC );
					else
						g_EntityFuncs.SetModel( self, ZOMBIE_MODEL );
				}
			}
			
			g_EntityFuncs.SetSize(self.pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX);
			
			self.pev.solid				= SOLID_SLIDEBOX;
			self.pev.movetype			= MOVETYPE_STEP;
			
			if ( self.m_bloodColor == 0 )
				self.m_bloodColor = BLOOD_COLOR_GREEN;
			
			if ( self.pev.health == 0.0f )
			{
				self.pev.health = g_iHealth;
			}
			
			self.pev.view_ofs			= VEC_VIEW;
			self.m_flFieldOfView		= 0.5;
			self.m_MonsterState			= MONSTERSTATE_NONE;
			self.m_afCapability			= bits_CAP_DOORS_GROUP;
			
			if ( string( self.m_FormattedName ).IsEmpty() )
				self.m_FormattedName = GetFormattedName();

			if ( self.IsPlayerAlly() )
			{ 
				SetUse( UseFunction( this.FollowerUse ) );
				self.CanPlayerFollow();
			}
			
			m_flNextTalk = g_Engine.time + Math.RandomLong( 5, 10 );
			
			self.MonsterInit();
		}
		
		//=========================================================
		// Precache - precaches all resources this monster needs
		//=========================================================
		void Precache()
		{
			// Should precache both Classic and Default model.
			if ( string( self.pev.model ).IsEmpty() )
			{
				g_Game.PrecacheModel( ZOMBIE_MODEL_CLASSIC );
				g_Game.PrecacheModel( ZOMBIE_MODEL );
			}
			
			PrecacheSoundArray( pAttackSounds );
			PrecacheSoundArray( pAttackHitSounds );
			PrecacheSoundArray( pAttackMissSounds );
			PrecacheSoundArray( pPainSounds );
			PrecacheSoundArray( pIdleSounds );
			PrecacheSoundArray( pAlertSounds );
			PrecacheSoundArray( pIdleHEVSounds );
		}
		
		//=========================================================
		// AI Schedules Specific to this monster
		//=========================================================
		int IgnoreConditions()
		{
			int iIgnore = 0;
			
			if ( ( self.m_Activity == ACT_MELEE_ATTACK1 ) || ( self.m_Activity == ACT_MELEE_ATTACK1 ) )
			{
				if ( self.pev.health < g_iHealth * 0.2 )
				{
					iIgnore |= ( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE );
				}
				else
				{
					if ( m_flNextFlinch >= g_Engine.time )
						iIgnore |= ( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE );
				}
			}

			if ( ( self.m_Activity == ACT_SMALL_FLINCH ) || ( self.m_Activity == ACT_BIG_FLINCH ) )
			{
				if ( m_flNextFlinch < g_Engine.time )
					m_flNextFlinch = g_Engine.time + ZOMBIE_FLINCH_DELAY;
			}

			return iIgnore;
		}
		
		float GetPointsForDamage( float flDamage )
		{
			return ( flDamage / self.pev.max_health ) * ( 3 );
		}
		
		bool CheckRangeAttack1( float flDot, float flDist )
		{ 
			return false;
		}

		bool CheckRangeAttack2( float flDot, float flDist )
		{ 
			return false;
		}
		
		//=========================================================
		// FollowerUse
		//=========================================================
		void FollowerUse( CBaseEntity@ pActivator, CBaseEntity@ pCaller, USE_TYPE useType, float flValue )
		{
			self.FollowerPlayerUse( pActivator, pCaller, useType, flValue );
			
			CBaseEntity@ pTarget = self.m_hTargetEnt;
			
			if ( pTarget is pActivator )
			{
				AlertSound();
			}
			else
			{
				IdleSound();
			}
		}
		
		//=========================================================
		// GetSchedule
		//=========================================================
		Schedule@ GetSchedule()
		{
			switch ( self.m_MonsterState )
			{
				case MONSTERSTATE_IDLE:
				case MONSTERSTATE_ALERT:
				case MONSTERSTATE_COMBAT:
				{
					if ( m_flNextTalk < g_Engine.time )
					{
						IdleHEVSounds();
						m_flNextTalk = g_Engine.time + Math.RandomLong( 5, 10 );
					}
				}
				
				break;
			}

			return BaseClass.GetSchedule();
		}

		//=========================================================
		// GetScheduleOfType
		//=========================================================
		Schedule@ GetScheduleOfType( int Type )
		{
			return BaseClass.GetScheduleOfType( Type );
		}
		
		//=========================================================
		// RunAI
		//=========================================================
		void RunAI()
		{
			if ( ( self.m_MonsterState == MONSTERSTATE_IDLE ) && Math.RandomLong( 0, 30 ) == 0 )
				IdleSound();
			else if ( ( self.m_MonsterState == MONSTERSTATE_ALERT ) && Math.RandomLong( 0, 40 ) == 0 )
				IdleSound();

			BaseClass.RunAI();
		}
	}
	
	string GetFormattedName()
	{
		return "Zombie H.E.V.";
	}
	
	string GetMonsterName()
	{
		return "monster_zombie_hev";
	}
	
	void Register()
	{
		g_CustomEntityFuncs.RegisterCustomEntity( "SVEN_ZOMBIE_HEV::CZombieHEV", GetMonsterName() );
		g_Game.PrecacheOther( GetMonsterName() );
	}
}	// End of Namespace