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.
465 lines
14 KiB
465 lines
14 KiB
/*** |
|
* |
|
* Copyright (c) 1996-2002, 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. |
|
* |
|
****/ |
|
//========================================================= |
|
// cockroach |
|
//========================================================= |
|
|
|
#include "extdll.h" |
|
#include "util.h" |
|
#include "cbase.h" |
|
#include "monsters.h" |
|
#include "schedule.h" |
|
#include "soundent.h" |
|
#include "decals.h" |
|
|
|
#define ROACH_IDLE 0 |
|
#define ROACH_BORED 1 |
|
#define ROACH_SCARED_BY_ENT 2 |
|
#define ROACH_SCARED_BY_LIGHT 3 |
|
#define ROACH_SMELL_FOOD 4 |
|
#define ROACH_EAT 5 |
|
|
|
//========================================================= |
|
// Monster's Anim Events Go Here |
|
//========================================================= |
|
class CRoach : public CBaseMonster |
|
{ |
|
public: |
|
void Spawn( void ); |
|
void Precache( void ); |
|
void SetYawSpeed( void ); |
|
void EXPORT MonsterThink ( void ); |
|
void Move( float flInterval ); |
|
void PickNewDest( int iCondition ); |
|
void EXPORT Touch( CBaseEntity *pOther ); |
|
void Killed( entvars_t *pevAttacker, int iGib ); |
|
|
|
float m_flLastLightLevel; |
|
float m_flNextSmellTime; |
|
int Classify( void ); |
|
void Look( int iDistance ); |
|
int ISoundMask( void ); |
|
|
|
// UNDONE: These don't necessarily need to be save/restored, but if we add more data, it may |
|
BOOL m_fLightHacked; |
|
int m_iMode; |
|
// ----------------------------- |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( monster_cockroach, CRoach ) |
|
|
|
//========================================================= |
|
// ISoundMask - returns a bit mask indicating which types |
|
// of sounds this monster regards. In the base class implementation, |
|
// monsters care about all sounds, but no scents. |
|
//========================================================= |
|
int CRoach::ISoundMask( void ) |
|
{ |
|
return bits_SOUND_CARCASS | bits_SOUND_MEAT; |
|
} |
|
|
|
//========================================================= |
|
// Classify - indicates this monster's place in the |
|
// relationship table. |
|
//========================================================= |
|
int CRoach::Classify( void ) |
|
{ |
|
return m_iClass?m_iClass:CLASS_INSECT; |
|
} |
|
|
|
//========================================================= |
|
// Touch |
|
//========================================================= |
|
void CRoach::Touch( CBaseEntity *pOther ) |
|
{ |
|
Vector vecSpot; |
|
TraceResult tr; |
|
|
|
if( pOther->pev->velocity == g_vecZero || !pOther->IsPlayer() ) |
|
{ |
|
return; |
|
} |
|
|
|
vecSpot = pev->origin + Vector( 0, 0, 8 );//move up a bit, and trace down. |
|
UTIL_TraceLine( vecSpot, vecSpot + Vector( 0, 0, -24 ), ignore_monsters, ENT( pev ), &tr ); |
|
|
|
// This isn't really blood. So you don't have to screen it out based on violence levels (UTIL_ShouldShowBlood()) |
|
UTIL_DecalTrace( &tr, DECAL_YBLOOD1 + RANDOM_LONG( 0, 5 ) ); |
|
|
|
TakeDamage( pOther->pev, pOther->pev, pev->health, DMG_CRUSH ); |
|
} |
|
|
|
//========================================================= |
|
// SetYawSpeed - allows each sequence to have a different |
|
// turn rate associated with it. |
|
//========================================================= |
|
void CRoach::SetYawSpeed( void ) |
|
{ |
|
int ys; |
|
|
|
ys = 120; |
|
|
|
pev->yaw_speed = ys; |
|
} |
|
|
|
//========================================================= |
|
// Spawn |
|
//========================================================= |
|
void CRoach::Spawn() |
|
{ |
|
Precache(); |
|
|
|
if (pev->model) |
|
SET_MODEL(ENT(pev), STRING(pev->model)); //LRC |
|
else |
|
SET_MODEL( ENT( pev ), "models/roach.mdl" ); |
|
UTIL_SetSize( pev, Vector( -1, -1, 0 ), Vector( 1, 1, 2 ) ); |
|
|
|
pev->solid = SOLID_SLIDEBOX; |
|
pev->movetype = MOVETYPE_STEP; |
|
m_bloodColor = BLOOD_COLOR_YELLOW; |
|
pev->effects = 0; |
|
pev->health = 1; |
|
m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) |
|
m_MonsterState = MONSTERSTATE_NONE; |
|
|
|
MonsterInit(); |
|
SetActivity( ACT_IDLE ); |
|
|
|
pev->view_ofs = Vector( 0, 0, 1 );// position of the eyes relative to monster's origin. |
|
pev->takedamage = DAMAGE_YES; |
|
m_fLightHacked = FALSE; |
|
m_flLastLightLevel = -1; |
|
m_iMode = ROACH_IDLE; |
|
m_flNextSmellTime = gpGlobals->time; |
|
} |
|
|
|
//========================================================= |
|
// Precache - precaches all resources this monster needs |
|
//========================================================= |
|
void CRoach::Precache() |
|
{ |
|
if (pev->model) |
|
PRECACHE_MODEL(STRING(pev->model)); //LRC |
|
else |
|
PRECACHE_MODEL( "models/roach.mdl" ); |
|
|
|
PRECACHE_SOUND( "roach/rch_die.wav" ); |
|
PRECACHE_SOUND( "roach/rch_walk.wav" ); |
|
PRECACHE_SOUND( "roach/rch_smash.wav" ); |
|
} |
|
|
|
//========================================================= |
|
// Killed. |
|
//========================================================= |
|
void CRoach::Killed( entvars_t *pevAttacker, int iGib ) |
|
{ |
|
pev->solid = SOLID_NOT; |
|
|
|
//random sound |
|
if( RANDOM_LONG( 0, 4 ) == 1 ) |
|
{ |
|
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "roach/rch_die.wav", 0.8, ATTN_NORM, 0, 80 + RANDOM_LONG( 0, 39 ) ); |
|
} |
|
else |
|
{ |
|
EMIT_SOUND_DYN( ENT( pev ), CHAN_BODY, "roach/rch_smash.wav", 0.7, ATTN_NORM, 0, 80 + RANDOM_LONG( 0, 39 ) ); |
|
} |
|
|
|
CSoundEnt::InsertSound( bits_SOUND_WORLD, pev->origin, 128, 1 ); |
|
|
|
CBaseEntity *pOwner = CBaseEntity::Instance( pev->owner ); |
|
if( pOwner ) |
|
{ |
|
pOwner->DeathNotice( pev ); |
|
} |
|
UTIL_Remove( this ); |
|
} |
|
|
|
//========================================================= |
|
// MonsterThink, overridden for roaches. |
|
//========================================================= |
|
void CRoach::MonsterThink( void ) |
|
{ |
|
if( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) |
|
SetNextThink( RANDOM_FLOAT( 1.0f, 1.5f ) ); |
|
else |
|
SetNextThink( 0.1f );// keep monster thinking |
|
|
|
float flInterval = StudioFrameAdvance(); // animate |
|
|
|
if( !m_fLightHacked ) |
|
{ |
|
// if light value hasn't been collection for the first time yet, |
|
// suspend the creature for a second so the world finishes spawning, then we'll collect the light level. |
|
SetNextThink( 1 ); |
|
m_fLightHacked = TRUE; |
|
return; |
|
} |
|
else if( m_flLastLightLevel < 0 ) |
|
{ |
|
// collect light level for the first time, now that all of the lightmaps in the roach's area have been calculated. |
|
m_flLastLightLevel = GETENTITYILLUM( ENT( pev ) ); |
|
} |
|
|
|
switch( m_iMode ) |
|
{ |
|
case ROACH_IDLE: |
|
case ROACH_EAT: |
|
{ |
|
// if not moving, sample environment to see if anything scary is around. Do a radius search 'look' at random. |
|
if( RANDOM_LONG( 0, 3 ) == 1 ) |
|
{ |
|
Look( 150 ); |
|
if( HasConditions( bits_COND_SEE_FEAR ) ) |
|
{ |
|
// if see something scary |
|
//ALERT( at_aiconsole, "Scared\n" ); |
|
Eat( 30 + ( RANDOM_LONG( 0, 14 ) ) );// roach will ignore food for 30 to 45 seconds |
|
PickNewDest( ROACH_SCARED_BY_ENT ); |
|
SetActivity( ACT_WALK ); |
|
} |
|
else if( RANDOM_LONG( 0, 149 ) == 1 ) |
|
{ |
|
// if roach doesn't see anything, there's still a chance that it will move. (boredom) |
|
//ALERT( at_aiconsole, "Bored\n" ); |
|
PickNewDest( ROACH_BORED ); |
|
SetActivity( ACT_WALK ); |
|
|
|
if( m_iMode == ROACH_EAT ) |
|
{ |
|
// roach will ignore food for 30 to 45 seconds if it got bored while eating. |
|
Eat( 30 + ( RANDOM_LONG( 0, 14 ) ) ); |
|
} |
|
} |
|
} |
|
|
|
// don't do this stuff if eating! |
|
if( m_iMode == ROACH_IDLE ) |
|
{ |
|
if( FShouldEat() ) |
|
{ |
|
Listen(); |
|
} |
|
|
|
if( GETENTITYILLUM( ENT( pev ) ) > m_flLastLightLevel ) |
|
{ |
|
// someone turned on lights! |
|
//ALERT( at_console, "Lights!\n" ); |
|
PickNewDest( ROACH_SCARED_BY_LIGHT ); |
|
SetActivity( ACT_WALK ); |
|
} |
|
else if( HasConditions( bits_COND_SMELL_FOOD ) ) |
|
{ |
|
CSound *pSound; |
|
|
|
pSound = CSoundEnt::SoundPointerForIndex( m_iAudibleList ); |
|
|
|
// roach smells food and is just standing around. Go to food unless food isn't on same z-plane. |
|
if( pSound && fabs( pSound->m_vecOrigin.z - pev->origin.z ) <= 3.0f ) |
|
{ |
|
PickNewDest( ROACH_SMELL_FOOD ); |
|
SetActivity( ACT_WALK ); |
|
} |
|
} |
|
} |
|
|
|
break; |
|
} |
|
case ROACH_SCARED_BY_LIGHT: |
|
{ |
|
// if roach was scared by light, then stop if we're over a spot at least as dark as where we started! |
|
if( GETENTITYILLUM( ENT( pev ) ) <= m_flLastLightLevel ) |
|
{ |
|
SetActivity( ACT_IDLE ); |
|
m_flLastLightLevel = GETENTITYILLUM( ENT( pev ) );// make this our new light level. |
|
} |
|
break; |
|
} |
|
} |
|
|
|
if( m_flGroundSpeed != 0 ) |
|
{ |
|
Move( flInterval ); |
|
} |
|
} |
|
|
|
//========================================================= |
|
// Picks a new spot for roach to run to.( |
|
//========================================================= |
|
void CRoach::PickNewDest( int iCondition ) |
|
{ |
|
Vector vecNewDir; |
|
Vector vecDest; |
|
float flDist; |
|
|
|
m_iMode = iCondition; |
|
|
|
if( m_iMode == ROACH_SMELL_FOOD ) |
|
{ |
|
// find the food and go there. |
|
CSound *pSound; |
|
|
|
pSound = CSoundEnt::SoundPointerForIndex( m_iAudibleList ); |
|
|
|
if( pSound ) |
|
{ |
|
m_Route[0].vecLocation.x = pSound->m_vecOrigin.x + ( 3 - RANDOM_LONG( 0, 5 ) ); |
|
m_Route[0].vecLocation.y = pSound->m_vecOrigin.y + ( 3 - RANDOM_LONG( 0, 5 ) ); |
|
m_Route[0].vecLocation.z = pSound->m_vecOrigin.z; |
|
m_Route[0].iType = bits_MF_TO_LOCATION; |
|
m_movementGoal = RouteClassify( m_Route[0].iType ); |
|
return; |
|
} |
|
} |
|
|
|
do |
|
{ |
|
// picks a random spot, requiring that it be at least 128 units away |
|
// else, the roach will pick a spot too close to itself and run in |
|
// circles. this is a hack but buys me time to work on the real monsters. |
|
vecNewDir.x = RANDOM_FLOAT( -1, 1 ); |
|
vecNewDir.y = RANDOM_FLOAT( -1, 1 ); |
|
flDist = 256 + ( RANDOM_LONG( 0, 255 ) ); |
|
vecDest = pev->origin + vecNewDir * flDist; |
|
|
|
} while( ( vecDest - pev->origin ).Length2D() < 128 ); |
|
|
|
m_Route[0].vecLocation.x = vecDest.x; |
|
m_Route[0].vecLocation.y = vecDest.y; |
|
m_Route[0].vecLocation.z = pev->origin.z; |
|
m_Route[0].iType = bits_MF_TO_LOCATION; |
|
m_movementGoal = RouteClassify( m_Route[0].iType ); |
|
|
|
if( RANDOM_LONG( 0, 9 ) == 1 ) |
|
{ |
|
// every once in a while, a roach will play a skitter sound when they decide to run |
|
EMIT_SOUND_DYN( ENT( pev ), CHAN_BODY, "roach/rch_walk.wav", 1, ATTN_NORM, 0, 80 + RANDOM_LONG( 0, 39 ) ); |
|
} |
|
} |
|
|
|
//========================================================= |
|
// roach's move function |
|
//========================================================= |
|
void CRoach::Move( float flInterval ) |
|
{ |
|
float flWaypointDist; |
|
Vector vecApex; |
|
|
|
// local move to waypoint. |
|
flWaypointDist = ( m_Route[m_iRouteIndex].vecLocation - pev->origin ).Length2D(); |
|
MakeIdealYaw( m_Route[m_iRouteIndex].vecLocation ); |
|
|
|
ChangeYaw( pev->yaw_speed ); |
|
UTIL_MakeVectors( pev->angles ); |
|
|
|
if( RANDOM_LONG( 0, 7 ) == 1 ) |
|
{ |
|
// randomly check for blocked path.(more random load balancing) |
|
if( !WALK_MOVE( ENT( pev ), pev->ideal_yaw, 4, WALKMOVE_NORMAL ) ) |
|
{ |
|
// stuck, so just pick a new spot to run off to |
|
PickNewDest( m_iMode ); |
|
} |
|
} |
|
|
|
WALK_MOVE( ENT( pev ), pev->ideal_yaw, m_flGroundSpeed * flInterval, WALKMOVE_NORMAL ); |
|
|
|
// if the waypoint is closer than step size, then stop after next step (ok for roach to overshoot) |
|
if( flWaypointDist <= m_flGroundSpeed * flInterval ) |
|
{ |
|
// take truncated step and stop |
|
|
|
SetActivity( ACT_IDLE ); |
|
m_flLastLightLevel = GETENTITYILLUM( ENT( pev ) );// this is roach's new comfortable light level |
|
|
|
if( m_iMode == ROACH_SMELL_FOOD ) |
|
{ |
|
m_iMode = ROACH_EAT; |
|
} |
|
else |
|
{ |
|
m_iMode = ROACH_IDLE; |
|
} |
|
} |
|
|
|
if( RANDOM_LONG( 0, 149 ) == 1 && m_iMode != ROACH_SCARED_BY_LIGHT && m_iMode != ROACH_SMELL_FOOD ) |
|
{ |
|
// random skitter while moving as long as not on a b-line to get out of light or going to food |
|
PickNewDest( FALSE ); |
|
} |
|
} |
|
|
|
//========================================================= |
|
// Look - overriden for the roach, which can virtually see |
|
// 360 degrees. |
|
//========================================================= |
|
void CRoach::Look( int iDistance ) |
|
{ |
|
CBaseEntity *pSightEnt = NULL;// the current visible entity that we're dealing with |
|
CBaseEntity *pPreviousEnt;// the last entity added to the link list |
|
int iSighted = 0; |
|
|
|
// DON'T let visibility information from last frame sit around! |
|
ClearConditions( bits_COND_SEE_HATE | bits_COND_SEE_DISLIKE | bits_COND_SEE_ENEMY | bits_COND_SEE_FEAR ); |
|
|
|
// don't let monsters outside of the player's PVS act up, or most of the interesting |
|
// things will happen before the player gets there! |
|
if( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) |
|
{ |
|
return; |
|
} |
|
|
|
m_pLink = NULL; |
|
pPreviousEnt = this; |
|
|
|
// Does sphere also limit itself to PVS? |
|
// Examine all entities within a reasonable radius |
|
// !!!PERFORMANCE - let's trivially reject the ent list before radius searching! |
|
while( ( pSightEnt = UTIL_FindEntityInSphere( pSightEnt, pev->origin, iDistance ) ) != NULL ) |
|
{ |
|
// only consider ents that can be damaged. !!!temporarily only considering other monsters and clients |
|
if( pSightEnt->IsPlayer() || FBitSet( pSightEnt->pev->flags, FL_MONSTER ) ) |
|
{ |
|
if( /*FVisible( pSightEnt ) &&*/ !FBitSet( pSightEnt->pev->flags, FL_NOTARGET ) && pSightEnt->pev->health > 0 ) |
|
{ |
|
// NULL the Link pointer for each ent added to the link list. If other ents follow, the will overwrite |
|
// this value. If this ent happens to be the last, the list will be properly terminated. |
|
pPreviousEnt->m_pLink = pSightEnt; |
|
pSightEnt->m_pLink = NULL; |
|
pPreviousEnt = pSightEnt; |
|
|
|
// don't add the Enemy's relationship to the conditions. We only want to worry about conditions when |
|
// we see monsters other than the Enemy. |
|
switch( IRelationship( pSightEnt ) ) |
|
{ |
|
case R_FR: |
|
iSighted |= bits_COND_SEE_FEAR; |
|
break; |
|
case R_NO: |
|
break; |
|
default: |
|
ALERT( at_console, "%s can't asses %s\n", STRING( pev->classname ), STRING( pSightEnt->pev->classname ) ); |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
SetConditions( iSighted ); |
|
} |
|
|
|
//========================================================= |
|
// AI Schedules Specific to this monster |
|
//=========================================================
|
|
|