Portable Half-Life SDK. GoldSource and Xash3D. Crossplatform.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

716 lines
15 KiB

/************************************************************
* *
* 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 );
}
}
}