/************************************************************
*															*
*			Sniper, par Julien								*
*															*
************************************************************/


#include	"extdll.h"
#include	"util.h"
#include	"cbase.h"
#include	"monsters.h"
#include	"schedule.h"
#include    "weapons.h"
#include	"soundent.h"
#include	"animation.h"
#include	"effects.h"



#define SNIPER_MAX_CLIP			5

// d

#define HEAD_GROUP		1
#define ARM_L_GROUP		3
#define ARM_R_GROUP		4
#define LEG_L_GROUP		5
#define LEG_R_GROUP		6

#define NO_MEMBRE		1

#define HEAD1			0
#define HEAD2			1
#define HEAD3			2
#define NO_HEAD			3

//=====================================================
// Monster's anim events
// Constantes associ
//=====================================================

#define SNIPER_AE_RELOAD				( 1 )
#define SNIPER_AE_MELEE_ATTACK1			( 2 )
#define SNIPER_AE_SHOOT					( 3 )
#define SNIPER_AE_DROPGUN				( 4 )

//=====================================================
//  Schedule types :
//	Attitudes 
//=====================================================
enum
{
	SCHED_SNIPER_RECHARGEMENT = LAST_COMMON_SCHEDULE + 1,
	SCHED_SNIPER_COVER,
};

//=====================================================
//D
//=====================================================


class CSniper : public CBaseMonster
{
public:
	void	Spawn			( void );						// initialisation
	void	Precache		( void );						// on pr
	void	SetYawSpeed		( void );						//vitesse de rotation
	int		Classify		( void );						// "camp" du monstre : alien ou humain
	int		IRelationship	( CBaseEntity *pTarget );

	void	 Shoot			(void);							// le tir !	
	CBaseEntity	*Kick		( void );						// trace un vecteur et voit s'il touche qqn

	BOOL CheckRangeAttack1 ( float flDot, float flDist );
	BOOL CheckMeleeAttack1 ( float flDot, float flDist );

	void	CheckAmmo		( void );						// v

	void HandleAnimEvent( MonsterEvent_t *pEvent );
	void RunTask ( Task_t *pTask );

	void GibMonster			( void );

	Schedule_t	*GetSchedule( void );						// analyse des bit_COND_ ...
	Schedule_t  *GetScheduleOfType ( int Type );			// ... et en retourne un comportement

	int m_cAmmoLoaded;										// nbre de balles dans le chargeur du sniper
	int m_iShell;

	CUSTOM_SCHEDULES;										// d
	
	static TYPEDESCRIPTION m_SaveData[];
	int	Save( CSave &save ); 
	int Restore( CRestore &restore );

	void MakeGib ( int body, entvars_t *pevAttacker );
	void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType);

	virtual Activity GetDeathActivity ( void );
};
LINK_ENTITY_TO_CLASS( monster_sniper, CSniper );


TYPEDESCRIPTION	CSniper::m_SaveData[] = 
{
	DEFINE_FIELD( CSniper, m_cAmmoLoaded, FIELD_INTEGER ),
};
IMPLEMENT_SAVERESTORE( CSniper, CBaseMonster );


int	CSniper :: Classify ( void )
{
	return	CLASS_HUMAN_MILITARY;	// copain avec les grunts
}

//====================

void CSniper :: SetYawSpeed ( void )
{
	/*int ys;

	switch ( m_Activity )
	{
	case ACT_IDLE:
	default:
		ys = 50;
		break;
	}

	pev->yaw_speed = ys;*/
	pev->yaw_speed = 360;	// rotation en degr
}

//-----------------------------------------
//
// Spawn / Precache
//
//------------------------------------------

void CSniper :: Spawn()
{
	Precache( );

	SET_MODEL(ENT(pev), "models/sniper.mdl");				// model
	UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX);  // taille de la "boite" : constante pour taille standart

	pev->solid			= SOLID_SLIDEBOX;		// la "boite" est solide
	pev->movetype		= MOVETYPE_STEP;		// il ne vole pas
	m_bloodColor		= BLOOD_COLOR_RED;		// couleur du sang
	pev->health			= 75;					// vie (plus tard avec le skill.cfg)
	pev->view_ofs		= Vector ( 0, 0, 73 );	// position of the eyes relative to monster's origin.
	m_flFieldOfView		= VIEW_FIELD_FULL;		// c
	m_MonsterState		= MONSTERSTATE_NONE;	// ?

	m_HackedGunPos		= Vector( 0, 24, 48 );	// position du gun
	m_cAmmoLoaded		= SNIPER_MAX_CLIP;		// nbre de balles dans le chargeur (provisoire)
	ClearConditions(bits_COND_NO_AMMO_LOADED);
	
	m_afCapability		= bits_CAP_HEAR |
						  bits_CAP_RANGE_ATTACK1 |	// capable d'entendre, et deux attaques
						  bits_CAP_MELEE_ATTACK1 ;
	pev->effects		= 0;					// pour le muzzleflash ?

	MonsterInit();								// ?

	m_flDistTooFar		= 4096.0;
	m_flDistLook		= 4096.0;

	SetBodygroup ( HEAD_GROUP, RANDOM_LONG(0,2) );	// t

}


void CSniper :: Precache()
{
	PRECACHE_MODEL("models/sniper.mdl");
	PRECACHE_MODEL("models/sniper_gibs.mdl");

	PRECACHE_SOUND("weapons/fsniper_shoot1.wav");

	m_iShell = PRECACHE_MODEL ("models/sniper_shell.mdl");

}	




//-----------------------------------------
//
// Shoot / Kick
//
//------------------------------------------



void CSniper :: Shoot ( void )
{
	if (m_hEnemy == 0)
	{
		return;
	}

	Vector vecShootOrigin = GetGunPosition();
	Vector vecShootDir = ShootAtEnemy( vecShootOrigin );

	float m_flDiviation = 0;		// perfect shot 
	UTIL_MakeVectors ( pev->angles );

	Vector	vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40);
	EjectBrass ( pev->origin + gpGlobals->v_up * 32 + gpGlobals->v_forward * 12, vecShellVelocity, pev->angles.y, m_iShell, TE_BOUNCE_SHELL);

	FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_3DEGREES, 4096, 0, 0, 45 );

	EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/fsniper_shoot1.wav", RANDOM_FLOAT(0.6, 0.8), ATTN_NORM);

	m_cAmmoLoaded -- ;
}



CBaseEntity *CSniper :: Kick( void )
{
	TraceResult tr;

	UTIL_MakeVectors( pev->angles );		// == d
	Vector vecStart = pev->origin;							// le vecteur part de l'origine
	vecStart.z += pev->size.z * 0.5;						// du monstre et de la moiti
															// de la hauteur de la "boite"
	Vector vecEnd = vecStart + (gpGlobals->v_forward * 70); // jusqu'

	UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr );  // on trace ce vecteur
	
	if ( tr.pHit )		// 
	{
		CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit );
		return pEntity;
	}

	return NULL;		// si c'est un coup 
}


//-----------------------------------------
// IRelationship
//
//-----------------------------------------

int CSniper::IRelationship ( CBaseEntity *pTarget )
{
	if ( FClassnameIs( pTarget->pev,  "monster_gargantua" ) )
	{
		return R_NM;
	}

	if ( FClassnameIs( pTarget->pev, "vehicle_tank" ) )
	{
			return R_NO;
	}

	return CBaseMonster::IRelationship( pTarget );
}


//-----------------------------------------
//
// CheckAttacks - 
//------------------------------------------.

void CSniper :: GibMonster ( void )
{
	if ( GetBodygroup( 2 ) != 0 )
		return;

	Vector	vecGunPos = GetGunPosition();
	Vector	vecGunAngles;

	Vector zeroVector(0,0,0);
	GetAttachment( 0, zeroVector, vecGunAngles );
	DropItem( "weapon_fsniper", vecGunPos, vecGunAngles );

	CBaseMonster :: GibMonster();
}


//-----------------------------------------
//
// D
//------------------------------------------.


void CSniper :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType)
{
	CBaseMonster :: TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType);

	if ( gMultiDamage.pEntity != this )
		return;
	
	if ( ( pev->health - ( gMultiDamage.amount ) <= 0 )  && IsAlive() && m_iHasGibbed == 0 )
	{
		if ( ptr->iHitgroup == HITGROUP_CHEST )
		{
			ptr->iHitgroup = RANDOM_LONG ( 1,3 );
			ptr->iHitgroup = ptr->iHitgroup == 2 ? HITGROUP_LEFTARM : ptr->iHitgroup;
			ptr->iHitgroup = ptr->iHitgroup == 3 ? HITGROUP_RIGHTARM : ptr->iHitgroup;
		}

		switch ( ptr->iHitgroup )
		{
		case HITGROUP_RIGHTARM:
			SetBodygroup( ARM_R_GROUP, NO_MEMBRE);
			MakeGib ( 2, pevAttacker );
			break;
		case HITGROUP_LEFTARM:
			SetBodygroup( ARM_L_GROUP, NO_MEMBRE);
			MakeGib ( 2, pevAttacker );
			break;
		case HITGROUP_RIGHTLEG:
			SetBodygroup( LEG_R_GROUP, NO_MEMBRE);
			MakeGib ( 1, pevAttacker );
			break;
		case HITGROUP_LEFTLEG:
			SetBodygroup( LEG_L_GROUP, NO_MEMBRE);
			MakeGib ( 1, pevAttacker );
			break;
		case HITGROUP_HEAD:
			SetBodygroup( HEAD_GROUP, NO_HEAD);
			MakeGib ( 0, pevAttacker );
			break;
		}
	}

}

void CSniper :: MakeGib ( int body, entvars_t *pevAttacker )
{

	if ( m_iHasGibbed == 1 )
		return;
	m_iHasGibbed = 1;

	CGib *pGib = GetClassPtr( (CGib *)NULL );
	pGib->Spawn( "models/sniper_gibs.mdl" );
	pGib->m_bloodColor = BLOOD_COLOR_RED;
	pGib->pev->body = body;

	pGib->pev->origin = pev->origin + Vector ( 0, 0, 40 );
	pGib->pev->velocity = ( Center() - pevAttacker->origin).Normalize() * 300;
	
	pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 );
	pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 );

}

Activity CSniper :: GetDeathActivity ( void )
{

	Activity	deathActivity = ACT_DIESIMPLE;

	if ( pev->deadflag != DEAD_NO )
		return m_IdealActivity;


	switch ( m_LastHitGroup )
	{
	case HITGROUP_HEAD:
		deathActivity = ACT_DIE_HEADSHOT;
		break;

	case HITGROUP_STOMACH:
	case HITGROUP_CHEST:
	case HITGROUP_LEFTLEG:
	case HITGROUP_RIGHTLEG:
		deathActivity = ACT_DIE_GUTSHOT;
		break;

	case HITGROUP_LEFTARM:
	case HITGROUP_RIGHTARM:
		deathActivity = ACT_DIE_BACKSHOT ;
		break;
	}

	if ( LookupActivity ( deathActivity ) == ACTIVITY_NOT_AVAILABLE )
		deathActivity = ACT_DIESIMPLE;

	return deathActivity;
}

//-----------------------------------------
//
// CheckAttacks
//------------------------------------------


BOOL CSniper :: CheckRangeAttack1 ( float flDot, float flDist )
{
	if ( flDist <= 4096 )
	{
		if ( flDist <= 64 )
			return FALSE;

		TraceResult	tr;
		Vector vecSrc = GetGunPosition();
		Vector vecTarget = m_hEnemy->BodyTarget(vecSrc);

		UTIL_TraceLine( vecSrc, vecTarget, ignore_monsters, ignore_glass, ENT(pev), &tr);

		if ( tr.flFraction == 1.0 )
			return TRUE;

		else if ( !FNullEnt ( tr.pHit ) && VARS( tr.pHit )->takedamage == DAMAGE_YES )
		{
			return TRUE;
		}

	  }

	return FALSE;
}

BOOL CSniper :: CheckMeleeAttack1 ( float flDot, float flDist )
{
	CBaseMonster *pEnemy;

	if ( m_hEnemy != 0 )
	{
		pEnemy = m_hEnemy->MyMonsterPointer();

		if ( !pEnemy )
		{
			return FALSE;
		}
	}

	if ( flDist <= 64 )
		return TRUE;

	return FALSE;
}



void CSniper :: CheckAmmo ( void )
{
	if ( m_cAmmoLoaded <= 0 )
	{
		SetConditions(bits_COND_NO_AMMO_LOADED);
	}
}

//===============


void CSniper :: HandleAnimEvent( MonsterEvent_t *pEvent )
{
	switch (pEvent->event)
	{
	case SNIPER_AE_SHOOT:

		Shoot (); break;

	case SNIPER_AE_RELOAD:

		m_cAmmoLoaded = SNIPER_MAX_CLIP;
		ClearConditions(bits_COND_NO_AMMO_LOADED);
		break;

	case SNIPER_AE_MELEE_ATTACK1:
		{
			CBaseEntity *pHurt = Kick();
			if ( pHurt )
			{
				UTIL_MakeVectors( pev->angles );
				pHurt->pev->punchangle.z = 25;
				pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 200 + gpGlobals->v_up * 100;
				pHurt->TakeDamage( pev, pev,25, DMG_CLUB );
			}
			break;
		}
	case SNIPER_AE_DROPGUN:
		{
			SetBodygroup( 2, 1);

			Vector	vecGunPos = GetGunPosition();
			Vector	vecGunAngles;

			Vector zeroVector(0,0,0);
			GetAttachment( 0, zeroVector, vecGunAngles );
			DropItem( "weapon_fsniper", vecGunPos, vecGunAngles );
			break;
		}
	}
}

void CSniper :: RunTask ( Task_t *pTask )
{
	switch ( pTask->iTask )
	{
		case TASK_RANGE_ATTACK1:
		{
			if (m_hEnemy != 0)
			{
				Vector vecShootDir = m_hEnemy->Center() - Center();
				Vector angDir = UTIL_VecToAngles( vecShootDir );
				SetBlending( 0, angDir.x );
			}

			MakeIdealYaw ( m_vecEnemyLKP );
			ChangeYaw ( pev->yaw_speed );

			CBaseMonster :: RunTask( pTask );
			break;
		}

	default:
		{
			CBaseMonster :: RunTask( pTask );
			break;
		}
	}

}





//----------------------------------------------------
//
// AI Schedules
//----------------------------------------------------


//-------------------
// Rechargement

Task_t	tlSniperRechargement[] =
{
	{ TASK_PLAY_SEQUENCE,			(float)ACT_RELOAD			},
};

Schedule_t slSniperRechargement[] = 
{
	{
		tlSniperRechargement,
		ARRAYSIZE ( tlSniperRechargement ),
		bits_COND_HEAVY_DAMAGE	|
		bits_COND_HEAR_SOUND,

		0,
		"SniperRechargement"
	}
};

//-------------------
// abri des grenades

Task_t	tlSniperCover[] =
{
	{ TASK_PLAY_SEQUENCE,			(float)ACT_COWER			},
};

Schedule_t slSniperCover[] = 
{
	{
		tlSniperCover,
		ARRAYSIZE ( tlSniperRechargement ),
		bits_COND_HEAVY_DAMAGE,

		0,
		"SniperCover"
	}
};


//-------------------
// tir
// red
// ne tire pas sans voir l'ennemi



Task_t	tlSniperShoot[] =
{
	{ TASK_STOP_MOVING,			0				},
	{ TASK_FACE_ENEMY,			(float)0		},
	{ TASK_RANGE_ATTACK1,		(float)0		},
};

Schedule_t slSniperShoot[] = 
{
	{
		tlSniperShoot,
		ARRAYSIZE ( tlSniperShoot ),
		bits_COND_NEW_ENEMY			|
		bits_COND_ENEMY_DEAD		|
		bits_COND_LIGHT_DAMAGE		|
		bits_COND_HEAVY_DAMAGE		|
		bits_COND_NO_AMMO_LOADED	|
		bits_COND_HEAR_SOUND,

		bits_SOUND_DANGER,
		"SniperShoot"
	}
};


//-------------------
// tableau des AI

DEFINE_CUSTOM_SCHEDULES( CSniper )
{
	slSniperRechargement,
	slSniperCover,
	slSniperShoot,
};

IMPLEMENT_CUSTOM_SCHEDULES( CSniper, CBaseMonster );



//----------------------------------------------------
//
//	Gestion des comportements
//----------------------------------------------------

#define NEER_GONNA_HAPPEN 677 //Just some obnoxiously high value, see below, modif de Roy
Schedule_t *CSniper :: GetSchedule( void )
{
	switch	( m_MonsterState )
	{
	
	case NEER_GONNA_HAPPEN: //Since MONSTERSTATE is an enum, this will never be executed (and so it wasn't in the original Julien's code), but we will satisfy the compiler, and it will hopefully not be angry at us, modif de Roy
	if ( HasConditions(bits_COND_HEAR_SOUND) )
	{
		CSound *pSound = PBestSound();

		ASSERT( pSound != NULL );
		if ( pSound)
		{
			if (pSound->m_iType & bits_SOUND_DANGER)
			{
				return GetScheduleOfType( SCHED_SNIPER_COVER );
			}
		}
	}


	if ( HasConditions( bits_COND_LIGHT_DAMAGE ) )
	{
		if ( RANDOM_LONG(0,99) <= 75 )
		{
			ClearConditions( bits_COND_LIGHT_DAMAGE );
			return GetScheduleOfType( SCHED_SMALL_FLINCH );
		}
	}

	case MONSTERSTATE_COMBAT:
		{
			if ( HasConditions ( bits_COND_NO_AMMO_LOADED ))
			{
				return GetScheduleOfType ( SCHED_SNIPER_RECHARGEMENT );
			}

			if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) )
			{
				return GetScheduleOfType ( SCHED_MELEE_ATTACK1 );
			}


			UTIL_MakeVectors ( pev->angles );
			Vector2D vec2LOS = ( m_hEnemy->pev->origin - pev->origin ).Make2D();
			vec2LOS = vec2LOS.Normalize();
			float flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() );

			if ( CheckRangeAttack1 ( DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ), (m_hEnemy->pev->origin - pev->origin).Length() ) )
			{
				return GetScheduleOfType ( SCHED_RANGE_ATTACK1 );
			}

		}	// fin du MS_COMBAT
	}		// fin du switch

	return CBaseMonster :: GetSchedule();
}

//----------------------------------------------------
//
//	Direction des comportements vers un tableau d'AI
//----------------------------------------------------


Schedule_t* CSniper :: GetScheduleOfType ( int Type ) 
{
	switch	( Type )
	{
	case SCHED_SNIPER_RECHARGEMENT:
		{
			return &slSniperRechargement[ 0 ];
		}
	case SCHED_SNIPER_COVER:
		{
			return &slSniperCover[ 0 ];	
		}
	case SCHED_RANGE_ATTACK1:
		{
			return &slSniperShoot[ 0 ];	
		}

	default:
		{
			return CBaseMonster :: GetScheduleOfType ( Type );
		}
	}
}