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.
 
 
 
 
 
 

666 lines
20 KiB

/***
*
* Copyright (c) 1996-2001, Valve LLC. All rights reserved.
*
* This product contains software technology licensed from Id
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
* All Rights Reserved.
*
* This source code contains proprietary and confidential information of
* Valve LLC and its suppliers. Access to this code is restricted to
* persons who have executed a written SDK license with Valve. Any access,
* use or distribution of this code by or to any unlicensed person is illegal.
*
****/
//=========================================================
// SPForce
//=========================================================
//=========================================================
// Hit groups!
//=========================================================
/*
1 - Head
2 - Stomach
3 - Gun
*/
#include "extdll.h"
#include "plane.h"
#include "util.h"
#include "cbase.h"
#include "monsters.h"
#include "schedule.h"
#include "animation.h"
#include "squadmonster.h"
#include "weapons.h"
#include "talkmonster.h"
#include "soundent.h"
#include "effects.h"
#include "customentity.h"
#include "hgrunt.h"
int g_fSPForceQuestion; // true if an idle SPForce asked a question. Cleared when someone answers.
extern DLL_GLOBAL int g_iSkillLevel;
//=========================================================
// monster-specific DEFINE's
//=========================================================
#define SPFORCE_CLIP_SIZE 36 // how many bullets in a clip? - NOTE: 3 round burst sound, so keep as 3 * x!
#define SPFORCE_VOL 0.35 // volume of SPForce sounds
#define SPFORCE_ATTN ATTN_NORM // attenutation of SPForce sentences
#define SPFORCE_LIMP_HEALTH 20
#define SPFORCE_DMG_HEADSHOT ( DMG_BULLET | DMG_CLUB ) // damage types that can kill a SPForce with a single headshot.
#define SPFORCE_NUM_HEADS 2 // how many SPForce heads are there?
#define SPFORCE_MINIMUM_HEADSHOT_DAMAGE 15 // must do at least this much damage in one shot to head to score a headshot kill
#define SPFORCE_SENTENCE_VOLUME (float)0.35 // volume of SPForce sentences
#define SPFORCE_M41A ( 1 << 0)
#define SPFORCE_HANDGRENADE ( 1 << 1)
#define SPFORCE_GRENADELAUNCHER ( 1 << 2)
#define SPFORCE_BERETTA ( 1 << 3)
#define HEAD_GROUP 1
#define HEAD_SPForce 0
#define HEAD_COMMANDER 1
#define HEAD_BERETTA 2
#define HEAD_M203 3
#define GUN_GROUP 2
#define GUN_M41A 0
#define GUN_BERETTA 1
#define GUN_NONE 2
//=========================================================
// Monster's Anim Events Go Here
//=========================================================
#define SPFORCE_AE_RELOAD ( 2 )
#define SPFORCE_AE_KICK ( 3 )
#define SPFORCE_AE_BURST1 ( 4 )
#define SPFORCE_AE_BURST2 ( 5 )
#define SPFORCE_AE_BURST3 ( 6 )
#define SPFORCE_AE_GREN_TOSS ( 7 )
#define SPFORCE_AE_GREN_LAUNCH ( 8 )
#define SPFORCE_AE_GREN_DROP ( 9 )
#define SPFORCE_AE_CAUGHT_ENEMY ( 10) // SPForce established sight with an enemy (player only) that had previously eluded the squad.
#define SPFORCE_AE_DROP_GUN ( 11) // SPForce (probably dead) is dropping his mp5.
//=========================================================
// monster-specific schedule types
//=========================================================
enum
{
SCHED_SPFORCE_SUPPRESS = LAST_COMMON_SCHEDULE + 1,
SCHED_SPFORCE_ESTABLISH_LINE_OF_FIRE,// move to a location to set up an attack against the enemy. (usually when a friendly is in the way).
SCHED_SPFORCE_COVER_AND_RELOAD,
SCHED_SPFORCE_SWEEP,
SCHED_SPFORCE_FOUND_ENEMY,
SCHED_SPFORCE_REPEL,
SCHED_SPFORCE_REPEL_ATTACK,
SCHED_SPFORCE_REPEL_LAND,
SCHED_SPFORCE_WAIT_FACE_ENEMY,
SCHED_SPFORCE_TAKECOVER_FAILED,// special schedule type that forces analysis of conditions and picks the best possible schedule to recover from this type of failure.
SCHED_SPFORCE_ELOF_FAIL,
};
//=========================================================
// monster-specific tasks
//=========================================================
enum
{
TASK_SPFORCE_FACE_TOSS_DIR = LAST_COMMON_TASK + 1,
TASK_SPFORCE_SPEAK_SENTENCE,
TASK_SPFORCE_CHECK_FIRE,
};
//=========================================================
// monster-specific conditions
//=========================================================
#define bits_COND_SPFORCE_NOFIRE ( bits_COND_SPECIAL1 )
class CSPForce : public CHGrunt
{
public:
void Spawn( void );
void Precache( void );
void HandleAnimEvent( MonsterEvent_t *pEvent );
void Shoot ( void );
void Beretta ( void );
void GibMonster( void );
int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType );
int IRelationship ( CBaseEntity *pTarget );
};
LINK_ENTITY_TO_CLASS( monster_human_spforce, CSPForce );
//=========================================================
// IRelationship - overridden because Alien Grunts are
// Human grunt's nemesis.
//=========================================================
int CSPForce::IRelationship ( CBaseEntity *pTarget )
{
if ( FClassnameIs( pTarget->pev, "monster_alien_grunt" ) || ( FClassnameIs( pTarget->pev, "monster_gargantua" ) ) )
{
return R_NM;
}
else if( FClassnameIs( pTarget->pev, "monster_human_grunt" ) )
{
return R_HT;
}
return CSquadMonster::IRelationship( pTarget );
}
//=========================================================
// GibMonster - make gun fly through the air.
//=========================================================
void CSPForce :: GibMonster ( void )
{
Vector vecGunPos;
Vector vecGunAngles;
if ( GetBodygroup( 2 ) != 2 )
{// throw a gun if the SPForce has one
GetAttachment( 0, vecGunPos, vecGunAngles );
CBaseEntity *pGun;
//Alex994 begin - for beretta spawning
if (FBitSet( pev->weapons, SPFORCE_BERETTA ))
{
pGun = DropItem( "weapon_beretta", vecGunPos, vecGunAngles );
}
//Alex994 end
else
{
pGun = DropItem( "weapon_9mmm41a", vecGunPos, vecGunAngles );
}
if ( pGun )
{
pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300));
pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 );
}
if (FBitSet( pev->weapons, SPFORCE_GRENADELAUNCHER ))
{
pGun = DropItem( "ammo_ARgrenades", vecGunPos, vecGunAngles );
if ( pGun )
{
pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300));
pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 );
}
}
}
CBaseMonster :: GibMonster();
}
//=========================================================
// TakeDamage - overridden for the SPForce because the SPForce
// needs to forget that he is in cover if he's hurt. (Obviously
// not in a safe place anymore).
//=========================================================
int CSPForce :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType )
{
Forget( bits_MEMORY_INCOVER );
return CSquadMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType );
}
//=========================================================
// Shoot
//=========================================================
void CSPForce :: Shoot ( void )
{
if (m_hEnemy == 0)
{
return;
}
Vector vecShootOrigin = GetGunPosition();
Vector vecShootDir = ShootAtEnemy( vecShootOrigin );
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 ( vecShootOrigin - vecShootDir * 24, vecShellVelocity, pev->angles.y, m_iBrassShell, TE_BOUNCE_SHELL);
FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_10DEGREES, 2048, BULLET_MONSTER_MP5); // shoot +-5 degrees
pev->effects |= EF_MUZZLEFLASH;
m_cAmmoLoaded--;// take away a bullet!
Vector angDir = UTIL_VecToAngles( vecShootDir );
SetBlending( 0, angDir.x );
}
//=========================================================
// Shoot
//=========================================================
void CSPForce :: Beretta ( void )
{
if (m_hEnemy == 0)
{
return;
}
Vector vecShootOrigin = GetGunPosition();
Vector vecShootDir = ShootAtEnemy( vecShootOrigin );
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 ( vecShootOrigin - vecShootDir * 24, vecShellVelocity, pev->angles.y, m_iBrassShell, TE_BOUNCE_SHELL);
FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_4DEGREES, 2048, BULLET_MONSTER_9MM, 0 ); // shoot +-7.5 degrees
pev->effects |= EF_MUZZLEFLASH;
m_cAmmoLoaded--;// take away a bullet!
Vector angDir = UTIL_VecToAngles( vecShootDir );
SetBlending( 0, angDir.x );
}
//=========================================================
// HandleAnimEvent - catches the monster-specific messages
// that occur when tagged animation frames are played.
//=========================================================
void CSPForce :: HandleAnimEvent( MonsterEvent_t *pEvent )
{
Vector vecShootDir;
Vector vecShootOrigin;
switch( pEvent->event )
{
case SPFORCE_AE_DROP_GUN:
{
Vector vecGunPos;
Vector vecGunAngles;
GetAttachment( 0, vecGunPos, vecGunAngles );
// switch to body group with no gun.
SetBodygroup( GUN_GROUP, GUN_NONE );
//Alex begin
if (FBitSet( pev->weapons, SPFORCE_BERETTA ))
{
DropItem( "weapon_beretta", vecGunPos, vecGunAngles );
}
//Alex end
else
{
DropItem( "weapon_9mmm41a", vecGunPos, vecGunAngles );
}
if (FBitSet( pev->weapons, SPFORCE_GRENADELAUNCHER ))
{
DropItem( "ammo_ARgrenades", BodyTarget( pev->origin ), vecGunAngles );
}
}
break;
case SPFORCE_AE_RELOAD:
// the first round of the three round burst plays the sound and puts a sound in the world sound list.
if ( FBitSet( pev->weapons, SPFORCE_BERETTA ) )
{
EMIT_SOUND( ENT(pev), CHAN_WEAPON, "spforce/spf_reload2.wav", 1, ATTN_NORM );
}
else
{
EMIT_SOUND( ENT(pev), CHAN_WEAPON, "spforce/spf_reload1.wav", 1, ATTN_NORM );
}
m_cAmmoLoaded = m_cClipSize;
ClearConditions(bits_COND_NO_AMMO_LOADED);
break;
case SPFORCE_AE_GREN_TOSS:
{
UTIL_MakeVectors( pev->angles );
// CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 34 + Vector (0, 0, 32), m_vecTossVelocity, 3.5 );
CGrenade::ShootTimed( pev, GetGunPosition(), m_vecTossVelocity, 3.5 );
m_fThrowGrenade = FALSE;
m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown.
// !!!LATER - when in a group, only try to throw grenade if ordered.
}
break;
case SPFORCE_AE_GREN_LAUNCH:
{
//EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/m41aglauncher.wav", 0.8, ATTN_NORM);
if ( RANDOM_LONG(0,1) )
{
EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/m41aglauncher.wav", 0.8, ATTN_NORM );
}
else
{
EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/m41aglauncher2.wav", 0.8, ATTN_NORM );
}
CGrenade::ShootContact( pev, GetGunPosition(), m_vecTossVelocity, gSkillData.plrDmgM41AGrenade );
m_fThrowGrenade = FALSE;
if (g_iSkillLevel == SKILL_HARD)
m_flNextGrenadeCheck = gpGlobals->time + RANDOM_FLOAT( 2, 5 );// wait a random amount of time before shooting again
else
m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown.
}
break;
case SPFORCE_AE_GREN_DROP:
{
UTIL_MakeVectors( pev->angles );
CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 17 - gpGlobals->v_right * 27 + gpGlobals->v_up * 6, g_vecZero, 3 );
}
break;
case SPFORCE_AE_BURST1:
{
if ( FBitSet( pev->weapons, SPFORCE_M41A ))
{
Shoot();
// the first round of the three round burst plays the sound and puts a sound in the world sound list.
if ( RANDOM_LONG(0,1) )
{
EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/m41ahks1.wav", 1, ATTN_NORM );
}
else
{
EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/m41ahks2.wav", 1, ATTN_NORM );
}
}
//Alex begin
else
{
Beretta();
// the first round of the three round burst plays the sound and puts a sound in the world sound list.
EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/beretta_fire1.wav", 1, ATTN_NORM );
}
CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 384, 0.3 );
}
break;
case SPFORCE_AE_BURST2:
case SPFORCE_AE_BURST3:
Shoot();
break;
case SPFORCE_AE_KICK:
{
CBaseEntity *pHurt = Kick();
if ( pHurt )
{
// SOUND HERE!
EMIT_SOUND( ENT(pev), CHAN_WEAPON, "common/kick.wav", 1, ATTN_NORM );
UTIL_MakeVectors( pev->angles );
pHurt->pev->punchangle.x = 15;
pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100 + gpGlobals->v_up * 50;
pHurt->TakeDamage( pev, pev, gSkillData.hgruntDmgKick, DMG_CLUB );
}
}
break;
case SPFORCE_AE_CAUGHT_ENEMY:
{
if ( FOkToSpeak() )
{
SENTENCEG_PlayRndSz(ENT(pev), "HG_ALERT", SPFORCE_SENTENCE_VOLUME, SPFORCE_ATTN, 0, m_voicePitch);
JustSpoke();
}
}
break;
default:
CSquadMonster::HandleAnimEvent( pEvent );
break;
}
}
//=========================================================
// Spawn
//=========================================================
void CSPForce :: Spawn()
{
Precache( );
SET_MODEL(ENT(pev), "models/spforce.mdl");
UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX);
pev->solid = SOLID_SLIDEBOX;
pev->movetype = MOVETYPE_STEP;
m_bloodColor = BLOOD_COLOR_RED;
pev->effects = 0;
pev->health = gSkillData.hgruntHealth;
m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result )
m_MonsterState = MONSTERSTATE_NONE;
m_flNextGrenadeCheck = gpGlobals->time + 1;
m_flNextPainTime = gpGlobals->time;
m_iSentence = HGRUNT_SENT_NONE;
m_afCapability = bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP;
m_fEnemyEluded = FALSE;
m_fFirstEncounter = TRUE;// this is true when the SPForce spawns, because he hasn't encountered an enemy yet.
m_HackedGunPos = Vector ( 0, 0, 55 );
if (pev->weapons == 0)
{
// initialize to original values
pev->weapons = SPFORCE_M41A | SPFORCE_HANDGRENADE;
// pev->weapons = SPFORCE_SHOTGUN;
// pev->weapons = SPFORCE_9MMAR | SPFORCE_GRENADELAUNCHER;
}
if (FBitSet( pev->weapons, SPFORCE_BERETTA ))
{
SetBodygroup( GUN_GROUP, GUN_BERETTA );
m_cClipSize = 15;
}
else
{
m_cClipSize = 45;
}
m_cAmmoLoaded = m_cClipSize;
if (FBitSet( pev->weapons, SPFORCE_BERETTA ))
{
SetBodygroup( HEAD_GROUP, HEAD_BERETTA);
}
else if (FBitSet( pev->weapons, SPFORCE_GRENADELAUNCHER ))
{
SetBodygroup( HEAD_GROUP, HEAD_M203 );
}
CTalkMonster::g_talkWaitTime = 0;
MonsterInit();
}
//=========================================================
// Precache - precaches all resources this monster needs
//=========================================================
void CSPForce :: Precache()
{
PRECACHE_MODEL( "models/spforce.mdl" );
PRECACHE_SOUND( "weapons/m41ahks1.wav" );
PRECACHE_SOUND( "weapons/m41ahks2.wav" );
PRECACHE_SOUND( "hgrunt/gr_die1.wav" );
PRECACHE_SOUND( "hgrunt/gr_die2.wav" );
PRECACHE_SOUND( "hgrunt/gr_die3.wav" );
PRECACHE_SOUND( "hgrunt/gr_pain1.wav" );
PRECACHE_SOUND( "hgrunt/gr_pain2.wav" );
PRECACHE_SOUND( "hgrunt/gr_pain3.wav" );
PRECACHE_SOUND( "hgrunt/gr_pain4.wav" );
PRECACHE_SOUND( "hgrunt/gr_pain5.wav" );
PRECACHE_SOUND( "spforce/spf_reload1.wav" );
PRECACHE_SOUND( "spforce/spf_reload2.wav" );
PRECACHE_SOUND( "weapons/m41aglauncher.wav" );
PRECACHE_SOUND( "weapons/m41aglauncher2.wav" );
PRECACHE_SOUND( "weapons/beretta_fire1.wav" );
PRECACHE_SOUND( "zombie/claw_miss2.wav" );// because we use the basemonster SWIPE animation event
PRECACHE_SOUND( "common/kick.wav" );
// get voice pitch
m_voicePitch = 92 + RANDOM_LONG( 0, 6 );
m_iBrassShell = PRECACHE_MODEL( "models/shell.mdl" );// brass shell
}
//=========================================================
// CSPForceRepel - when triggered, spawns a monster_human_spforce
// repelling down a line.
//=========================================================
class CSPForceRepel : public CBaseMonster
{
public:
void Spawn( void );
void Precache( void );
void EXPORT RepelUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
int m_iSpriteTexture; // Don't save, precache
};
LINK_ENTITY_TO_CLASS( monster_spforce_repel, CSPForceRepel );
void CSPForceRepel::Spawn( void )
{
Precache( );
pev->solid = SOLID_NOT;
SetUse( &CSPForceRepel::RepelUse );
}
void CSPForceRepel::Precache( void )
{
UTIL_PrecacheOther( "monster_human_spforce" );
m_iSpriteTexture = PRECACHE_MODEL( "sprites/rope.spr" );
}
void CSPForceRepel::RepelUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
TraceResult tr;
UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, -4096.0), dont_ignore_monsters, ENT(pev), &tr);
/*
if ( tr.pHit && Instance( tr.pHit )->pev->solid != SOLID_BSP)
return NULL;
*/
CBaseEntity *pEntity = Create( "monster_human_spforce", pev->origin, pev->angles );
CBaseMonster *pSPForce = pEntity->MyMonsterPointer( );
pSPForce->pev->movetype = MOVETYPE_FLY;
pSPForce->pev->velocity = Vector( 0, 0, RANDOM_FLOAT( -196, -128 ) );
pSPForce->SetActivity( ACT_GLIDE );
// UNDONE: position?
pSPForce->m_vecLastPosition = tr.vecEndPos;
CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.spr", 10 );
pBeam->PointEntInit( pev->origin + Vector(0,0,112), pSPForce->entindex() );
pBeam->SetFlags( BEAM_FSOLID );
pBeam->SetColor( 255, 255, 255 );
pBeam->SetThink( &CBeam::SUB_Remove );
pBeam->pev->nextthink = gpGlobals->time + -4096.0f * tr.flFraction / pSPForce->pev->velocity.z + 0.5f;
UTIL_Remove( this );
}
//=========================================================
// DEAD SPFORCE PROP
//=========================================================
class CDeadSPForce : public CBaseMonster
{
public:
void Spawn( void );
int Classify ( void ) { return CLASS_HUMAN_MILITARY; }
void KeyValue( KeyValueData *pkvd );
int m_iPose;// which sequence to display -- temporary, don't need to save
static const char *m_szPoses[3];
};
const char *CDeadSPForce::m_szPoses[] = { "deadstomach", "deadside", "deadsitting" };
void CDeadSPForce::KeyValue( KeyValueData *pkvd )
{
if (FStrEq(pkvd->szKeyName, "pose"))
{
m_iPose = atoi(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else
CBaseMonster::KeyValue( pkvd );
}
LINK_ENTITY_TO_CLASS( monster_spforce_dead, CDeadSPForce );
//=========================================================
// ********** DeadSPForce SPAWN **********
//=========================================================
void CDeadSPForce :: Spawn( void )
{
PRECACHE_MODEL("models/spforce.mdl");
SET_MODEL(ENT(pev), "models/spforce.mdl");
pev->effects = 0;
pev->yaw_speed = 8;
pev->sequence = 0;
m_bloodColor = BLOOD_COLOR_RED;
pev->sequence = LookupSequence( m_szPoses[m_iPose] );
if (pev->sequence == -1)
{
ALERT ( at_console, "Dead SPForce with bad pose\n" );
}
// Corpses have less health
pev->health = 8;
// map old bodies onto new bodies
switch( pev->body )
{
case 0: // SPForce with Gun
pev->body = 0;
pev->skin = 0;
SetBodygroup( HEAD_GROUP, HEAD_SPForce );
SetBodygroup( GUN_GROUP, GUN_M41A );
break;
case 1: // Commander with Gun
pev->body = 0;
pev->skin = 0;
SetBodygroup( HEAD_GROUP, HEAD_COMMANDER );
SetBodygroup( GUN_GROUP, GUN_M41A );
break;
case 2: // SPForce no Gun
pev->body = 0;
pev->skin = 0;
SetBodygroup( HEAD_GROUP, HEAD_SPForce );
SetBodygroup( GUN_GROUP, GUN_NONE );
break;
case 3: // Commander no Gun
pev->body = 0;
pev->skin = 0;
SetBodygroup( HEAD_GROUP, HEAD_COMMANDER );
SetBodygroup( GUN_GROUP, GUN_NONE );
break;
}
MonsterInitDead();
}