summaryrefslogtreecommitdiff
path: root/game/server/hl1/hl1_npc_roach.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/hl1/hl1_npc_roach.cpp')
-rw-r--r--game/server/hl1/hl1_npc_roach.cpp471
1 files changed, 471 insertions, 0 deletions
diff --git a/game/server/hl1/hl1_npc_roach.cpp b/game/server/hl1/hl1_npc_roach.cpp
new file mode 100644
index 0000000..d5a4be7
--- /dev/null
+++ b/game/server/hl1/hl1_npc_roach.cpp
@@ -0,0 +1,471 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include "cbase.h"
+#include "hl1_ai_basenpc.h"
+#include "ai_default.h"
+#include "ai_task.h"
+#include "ai_schedule.h"
+#include "ai_node.h"
+#include "ai_hull.h"
+#include "ai_hint.h"
+#include "ai_memory.h"
+#include "ai_route.h"
+#include "ai_motor.h"
+#include "ai_senses.h"
+#include "soundent.h"
+#include "game.h"
+#include "npcevent.h"
+#include "entitylist.h"
+#include "activitylist.h"
+#include "animation.h"
+#include "basecombatweapon.h"
+#include "IEffects.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "ammodef.h"
+#include "ai_behavior_follow.h"
+#include "ai_navigator.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 CNPC_Roach : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CNPC_Roach, CHL1BaseNPC );
+
+public:
+
+ void Spawn( void );
+ void Precache( void );
+ float MaxYawSpeed( void );
+
+// DECLARE_DATADESC();
+
+ void NPCThink ( void );
+ void PickNewDest ( int iCondition );
+ void Look ( int iDistance );
+ void Move ( float flInterval );
+
+ Class_T Classify( void ) { return CLASS_INSECT; }
+
+ void Touch ( CBaseEntity *pOther );
+
+ void Event_Killed( const CTakeDamageInfo &info );
+ int GetSoundInterests ( void );
+
+ void Eat( float flFullDuration );
+ bool ShouldEat( void );
+
+ bool ShouldGib( const CTakeDamageInfo &info ) { return false; }
+
+ float m_flLastLightLevel;
+ float m_flNextSmellTime;
+
+ // UNDONE: These don't necessarily need to be save/restored, but if we add more data, it may
+ bool m_fLightHacked;
+ int m_iMode;
+
+ float m_flHungryTime;
+ // -----------------------------
+};
+
+LINK_ENTITY_TO_CLASS( monster_cockroach, CNPC_Roach );
+
+//BEGIN_DATADESC( CNPC_Roach )
+
+// DEFINE_FUNCTION( RoachTouch ),
+
+//END_DATADESC()
+
+
+//=========================================================
+// Spawn
+//=========================================================
+void CNPC_Roach::Spawn()
+{
+ Precache( );
+
+ SetModel( "models/roach.mdl" );
+ UTIL_SetSize( this, Vector( -1, -1, 0 ), Vector( 1, 1, 2 ) );
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ m_bloodColor = BLOOD_COLOR_YELLOW;
+ ClearEffects();
+ m_iHealth = 1;
+ m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result )
+ m_NPCState = NPC_STATE_NONE;
+
+ SetRenderColor( 255, 255, 255, 255 );
+
+ NPCInit();
+ SetActivity ( ACT_IDLE );
+
+ SetViewOffset ( Vector ( 0, 0, 1 ) );// position of the eyes relative to monster's origin.
+ m_takedamage = DAMAGE_YES;
+ m_fLightHacked = FALSE;
+ m_flLastLightLevel = -1;
+ m_iMode = ROACH_IDLE;
+ m_flNextSmellTime = gpGlobals->curtime;
+
+ AddEffects( EF_NOSHADOW );
+}
+
+//=========================================================
+// Precache - precaches all resources this monster needs
+//=========================================================
+void CNPC_Roach::Precache()
+{
+ PrecacheModel("models/roach.mdl");
+
+ PrecacheScriptSound( "Roach.Walk" );
+ PrecacheScriptSound( "Roach.Die" );
+ PrecacheScriptSound( "Roach.Smash" );
+}
+
+float CNPC_Roach::MaxYawSpeed( void )
+{
+ return 120.0f;
+}
+
+void CNPC_Roach::Eat( float flFullDuration )
+{
+ m_flHungryTime = gpGlobals->curtime + flFullDuration;
+}
+
+bool CNPC_Roach::ShouldEat( void )
+{
+ if ( m_flHungryTime > gpGlobals->curtime )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+//=========================================================
+// MonsterThink, overridden for roaches.
+//=========================================================
+void CNPC_Roach::NPCThink( void )
+{
+ if ( FNullEnt( UTIL_FindClientInPVS( edict() ) ) )
+ SetNextThink( gpGlobals->curtime + random->RandomFloat( 1.0f , 1.5f ) );
+ else
+ SetNextThink( gpGlobals->curtime + 0.1f );// keep monster thinking
+
+ float flInterval = gpGlobals->curtime - GetLastThink();
+
+ 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( gpGlobals->curtime + 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 = 0;
+ }
+
+ 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->RandomInt( 0, 3 ) == 1 )
+ {
+ Look( 150 );
+
+ if ( HasCondition( COND_SEE_FEAR ) )
+ {
+ // if see something scary
+ //ALERT ( at_aiconsole, "Scared\n" );
+ Eat( 30 + ( random->RandomInt( 0, 14 ) ) );// roach will ignore food for 30 to 45 seconds
+ PickNewDest( ROACH_SCARED_BY_ENT );
+ SetActivity ( ACT_WALK );
+ }
+ else if ( random->RandomInt( 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->RandomInt(0,14) ) );
+ }
+ }
+ }
+
+ // don't do this stuff if eating!
+ if ( m_iMode == ROACH_IDLE )
+ {
+ if ( ShouldEat() )
+ {
+ GetSenses()->Listen();
+ }
+
+ if ( 0 > m_flLastLightLevel )
+ {
+ // someone turned on lights!
+ //ALERT ( at_console, "Lights!\n" );
+ PickNewDest( ROACH_SCARED_BY_LIGHT );
+ SetActivity ( ACT_WALK );
+ }
+ else if ( HasCondition( COND_SMELL ) )
+ {
+ CSound *pSound = GetLoudestSoundOfType( ALL_SOUNDS );
+
+ // roach smells food and is just standing around. Go to food unless food isn't on same z-plane.
+ if ( pSound && abs( pSound->GetSoundOrigin().z - GetAbsOrigin().z ) <= 3 )
+ {
+ 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 ( 0 <= m_flLastLightLevel )
+ {
+ SetActivity ( ACT_IDLE );
+ m_flLastLightLevel = 0;// make this our new light level.
+ }
+ break;
+ }
+ }
+
+ if ( GetActivity() != ACT_IDLE )
+ {
+ Move( flInterval );
+ }
+}
+
+void CNPC_Roach::PickNewDest ( int iCondition )
+{
+ Vector vecNewDir;
+ Vector vecDest;
+ float flDist;
+
+ m_iMode = iCondition;
+
+ GetNavigator()->ClearGoal();
+
+ if ( m_iMode == ROACH_SMELL_FOOD )
+ {
+ // find the food and go there.
+ CSound *pSound = GetLoudestSoundOfType( ALL_SOUNDS );
+
+ if ( pSound )
+ {
+ GetNavigator()->SetRandomGoal( 3 - random->RandomInt( 0,5 ) );
+ 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->RandomInt( -1, 1 );
+ vecNewDir.y = random->RandomInt( -1, 1 );
+ flDist = 256 + ( random->RandomInt(0,255) );
+ vecDest = GetAbsOrigin() + vecNewDir * flDist;
+
+ } while ( ( vecDest - GetAbsOrigin() ).Length2D() < 128 );
+
+ Vector vecLocation;
+
+ vecLocation.x = vecDest.x;
+ vecLocation.y = vecDest.y;
+ vecLocation.z = GetAbsOrigin().z;
+
+ AI_NavGoal_t goal( GOALTYPE_LOCATION, vecLocation, ACT_WALK );
+
+ GetNavigator()->SetGoal( goal );
+
+ if ( random->RandomInt( 0, 9 ) == 1 )
+ {
+ // every once in a while, a roach will play a skitter sound when they decide to run
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Roach.Walk" );
+ }
+}
+
+//=========================================================
+// Look - overriden for the roach, which can virtually see
+// 360 degrees.
+//=========================================================
+void CNPC_Roach::Look ( int iDistance )
+{
+ CBaseEntity *pSightEnt = NULL;// the current visible entity that we're dealing with
+
+ // DON'T let visibility information from last frame sit around!
+ ClearCondition( COND_SEE_HATE | COND_SEE_DISLIKE | COND_SEE_ENEMY | 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( UTIL_FindClientInPVS( edict() ) ) )
+ {
+ return;
+ }
+
+ // 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!
+
+ for ( CEntitySphereQuery sphere( GetAbsOrigin(), iDistance ); ( pSightEnt = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
+ {
+ // only consider ents that can be damaged. !!!temporarily only considering other monsters and clients
+ if ( pSightEnt->IsPlayer() || FBitSet ( pSightEnt->GetFlags(), FL_NPC ) )
+ {
+ if ( /*FVisible( pSightEnt ) &&*/ !FBitSet( pSightEnt->GetFlags(), FL_NOTARGET ) && pSightEnt->m_iHealth > 0 )
+ {
+ // 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 ( IRelationType ( pSightEnt ) )
+ {
+ case D_FR:
+ SetCondition( COND_SEE_FEAR );
+ break;
+ case D_NU:
+ break;
+ default:
+ Msg ( "%s can't asses %s\n", GetClassname(), pSightEnt->GetClassname() );
+ break;
+ }
+ }
+ }
+ }
+}
+
+//=========================================================
+// roach's move function
+//=========================================================
+void CNPC_Roach::Move ( float flInterval )
+{
+ float flWaypointDist;
+ Vector vecApex;
+
+ // local move to waypoint.
+ flWaypointDist = ( GetNavigator()->GetGoalPos() - GetAbsOrigin() ).Length2D();
+
+ GetMotor()->SetIdealYawToTargetAndUpdate( GetNavigator()->GetGoalPos() );
+
+ float speed = 150 * flInterval;
+
+ Vector vToTarget = GetNavigator()->GetGoalPos() - GetAbsOrigin();
+ vToTarget.NormalizeInPlace();
+ Vector vMovePos = vToTarget * speed;
+
+ if ( random->RandomInt( 0,7 ) == 1 )
+ {
+ // randomly change direction
+ PickNewDest( m_iMode );
+ }
+
+ if( !WalkMove( vMovePos, MASK_NPCSOLID ) )
+ {
+ PickNewDest( m_iMode );
+ }
+
+ // 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 = 0;// 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->RandomInt( 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 );
+ }
+}
+
+void CNPC_Roach::Touch ( CBaseEntity *pOther )
+{
+ Vector vecSpot;
+ trace_t tr;
+
+ if ( pOther->GetAbsVelocity() == vec3_origin || !pOther->IsPlayer() )
+ {
+ return;
+ }
+
+ vecSpot = GetAbsOrigin() + Vector ( 0 , 0 , 8 );//move up a bit, and trace down.
+ //UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -24 ), ignore_monsters, ENT(pev), & tr);
+
+ UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -24 ), MASK_ALL, this, COLLISION_GROUP_NONE, &tr);
+
+ // This isn't really blood. So you don't have to screen it out based on violence levels (UTIL_ShouldShowBlood())
+ UTIL_DecalTrace( &tr, "YellowBlood" );
+
+ // DMG_GENERIC because we don't want any physics force generated
+ TakeDamage( CTakeDamageInfo( pOther, pOther, m_iHealth, DMG_GENERIC ) );
+}
+
+void CNPC_Roach::Event_Killed( const CTakeDamageInfo &info )
+{
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+
+ CPASAttenuationFilter filter( this );
+
+ //random sound
+ if ( random->RandomInt( 0,4 ) == 1 )
+ {
+ EmitSound( filter, entindex(), "Roach.Die" );
+ }
+ else
+ {
+ EmitSound( filter, entindex(), "Roach.Smash" );
+ }
+
+ CSoundEnt::InsertSound ( SOUND_WORLD, GetAbsOrigin(), 128, 1 );
+
+ UTIL_Remove( this );
+}
+
+int CNPC_Roach::GetSoundInterests ( void)
+{
+ return SOUND_CARCASS |
+ SOUND_MEAT;
+}