//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Implements the headcrab, a tiny, jumpy alien parasite. // // TODO: make poison headcrab hop in response to nearby bullet impacts? // //=============================================================================// #include "cbase.h" #include "game.h" #include "antlion_dust.h" #include "ai_default.h" #include "ai_schedule.h" #include "ai_hint.h" #include "ai_hull.h" #include "ai_navigator.h" #include "ai_moveprobe.h" #include "ai_memory.h" #include "bitstring.h" #include "hl2_shareddefs.h" #include "npcevent.h" #include "soundent.h" #include "npc_headcrab.h" #include "gib.h" #include "ai_interactions.h" #include "ndebugoverlay.h" #include "vstdlib/random.h" #include "engine/IEngineSound.h" #include "movevars_shared.h" #include "world.h" #include "npc_bullseye.h" #include "physics_npc_solver.h" #include "hl2_gamerules.h" #include "decals.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define CRAB_ATTN_IDLE (float)1.5 #define HEADCRAB_GUTS_GIB_COUNT 1 #define HEADCRAB_LEGS_GIB_COUNT 3 #define HEADCRAB_ALL_GIB_COUNT 5 #define HEADCRAB_RUNMODE_ACCELERATE 1 #define HEADCRAB_RUNMODE_IDLE 2 #define HEADCRAB_RUNMODE_DECELERATE 3 #define HEADCRAB_RUNMODE_FULLSPEED 4 #define HEADCRAB_RUNMODE_PAUSE 5 #define HEADCRAB_RUN_MINSPEED 0.5 #define HEADCRAB_RUN_MAXSPEED 1.0 const float HEADCRAB_BURROWED_FOV = -1.0f; const float HEADCRAB_UNBURROWED_FOV = 0.5f; #define HEADCRAB_IGNORE_WORLD_COLLISION_TIME 0.5 const int HEADCRAB_MIN_JUMP_DIST = 48; const int HEADCRAB_MAX_JUMP_DIST = 256; #define HEADCRAB_BURROW_POINT_SEARCH_RADIUS 256.0 // Debugging #define HEADCRAB_DEBUG_HIDING 1 #define HEADCRAB_BURN_SOUND_FREQUENCY 10 ConVar g_debug_headcrab( "g_debug_headcrab", "0", FCVAR_CHEAT ); //------------------------------------ // Spawnflags //------------------------------------ #define SF_HEADCRAB_START_HIDDEN (1 << 16) #define SF_HEADCRAB_START_HANGING (1 << 17) //----------------------------------------------------------------------------- // Think contexts. //----------------------------------------------------------------------------- static const char *s_pPitchContext = "PitchContext"; //----------------------------------------------------------------------------- // Animation events. //----------------------------------------------------------------------------- int AE_HEADCRAB_JUMPATTACK; int AE_HEADCRAB_JUMP_TELEGRAPH; int AE_POISONHEADCRAB_FLINCH_HOP; int AE_POISONHEADCRAB_FOOTSTEP; int AE_POISONHEADCRAB_THREAT_SOUND; int AE_HEADCRAB_BURROW_IN; int AE_HEADCRAB_BURROW_IN_FINISH; int AE_HEADCRAB_BURROW_OUT; int AE_HEADCRAB_CEILING_DETACH; //----------------------------------------------------------------------------- // Custom schedules. //----------------------------------------------------------------------------- enum { SCHED_HEADCRAB_RANGE_ATTACK1 = LAST_SHARED_SCHEDULE, SCHED_HEADCRAB_WAKE_ANGRY, SCHED_HEADCRAB_WAKE_ANGRY_NO_DISPLAY, SCHED_HEADCRAB_DROWN, SCHED_HEADCRAB_FAIL_DROWN, SCHED_HEADCRAB_AMBUSH, SCHED_HEADCRAB_HOP_RANDOMLY, // get off something you're not supposed to be on. SCHED_HEADCRAB_BARNACLED, SCHED_HEADCRAB_UNHIDE, SCHED_HEADCRAB_HARASS_ENEMY, SCHED_HEADCRAB_FALL_TO_GROUND, SCHED_HEADCRAB_RUN_TO_BURROW_IN, SCHED_HEADCRAB_RUN_TO_SPECIFIC_BURROW, SCHED_HEADCRAB_BURROW_IN, SCHED_HEADCRAB_BURROW_WAIT, SCHED_HEADCRAB_BURROW_OUT, SCHED_HEADCRAB_WAIT_FOR_CLEAR_UNBURROW, SCHED_HEADCRAB_CRAWL_FROM_CANISTER, SCHED_FAST_HEADCRAB_RANGE_ATTACK1, SCHED_HEADCRAB_CEILING_WAIT, SCHED_HEADCRAB_CEILING_DROP, }; //========================================================= // tasks //========================================================= enum { TASK_HEADCRAB_HOP_ASIDE = LAST_SHARED_TASK, TASK_HEADCRAB_HOP_OFF_NPC, TASK_HEADCRAB_DROWN, TASK_HEADCRAB_WAIT_FOR_BARNACLE_KILL, TASK_HEADCRAB_UNHIDE, TASK_HEADCRAB_HARASS_HOP, TASK_HEADCRAB_FIND_BURROW_IN_POINT, TASK_HEADCRAB_BURROW, TASK_HEADCRAB_UNBURROW, TASK_HEADCRAB_BURROW_WAIT, TASK_HEADCRAB_CHECK_FOR_UNBURROW, TASK_HEADCRAB_JUMP_FROM_CANISTER, TASK_HEADCRAB_CLIMB_FROM_CANISTER, TASK_HEADCRAB_CEILING_WAIT, TASK_HEADCRAB_CEILING_POSITION, TASK_HEADCRAB_CEILING_DETACH, TASK_HEADCRAB_CEILING_FALL, TASK_HEADCRAB_CEILING_LAND, }; //========================================================= // conditions //========================================================= enum { COND_HEADCRAB_IN_WATER = LAST_SHARED_CONDITION, COND_HEADCRAB_ILLEGAL_GROUNDENT, COND_HEADCRAB_BARNACLED, COND_HEADCRAB_UNHIDE, }; //========================================================= // private activities //========================================================= int ACT_HEADCRAB_THREAT_DISPLAY; int ACT_HEADCRAB_HOP_LEFT; int ACT_HEADCRAB_HOP_RIGHT; int ACT_HEADCRAB_DROWN; int ACT_HEADCRAB_BURROW_IN; int ACT_HEADCRAB_BURROW_OUT; int ACT_HEADCRAB_BURROW_IDLE; int ACT_HEADCRAB_CRAWL_FROM_CANISTER_LEFT; int ACT_HEADCRAB_CRAWL_FROM_CANISTER_CENTER; int ACT_HEADCRAB_CRAWL_FROM_CANISTER_RIGHT; int ACT_HEADCRAB_CEILING_IDLE; int ACT_HEADCRAB_CEILING_DETACH; int ACT_HEADCRAB_CEILING_FALL; int ACT_HEADCRAB_CEILING_LAND; //----------------------------------------------------------------------------- // Skill settings. //----------------------------------------------------------------------------- ConVar sk_headcrab_health( "sk_headcrab_health","0"); ConVar sk_headcrab_fast_health( "sk_headcrab_fast_health","0"); ConVar sk_headcrab_poison_health( "sk_headcrab_poison_health","0"); ConVar sk_headcrab_melee_dmg( "sk_headcrab_melee_dmg","0"); ConVar sk_headcrab_poison_npc_damage( "sk_headcrab_poison_npc_damage", "0" ); BEGIN_DATADESC( CBaseHeadcrab ) // m_nGibCount - don't save DEFINE_FIELD( m_bHidden, FIELD_BOOLEAN ), DEFINE_FIELD( m_flTimeDrown, FIELD_TIME ), DEFINE_FIELD( m_bCommittedToJump, FIELD_BOOLEAN ), DEFINE_FIELD( m_vecCommittedJumpPos, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_flNextNPCThink, FIELD_TIME ), DEFINE_FIELD( m_flIgnoreWorldCollisionTime, FIELD_TIME ), DEFINE_KEYFIELD( m_bStartBurrowed, FIELD_BOOLEAN, "startburrowed" ), DEFINE_FIELD( m_bBurrowed, FIELD_BOOLEAN ), DEFINE_FIELD( m_flBurrowTime, FIELD_TIME ), DEFINE_FIELD( m_nContext, FIELD_INTEGER ), DEFINE_FIELD( m_bCrawlFromCanister, FIELD_BOOLEAN ), DEFINE_FIELD( m_bMidJump, FIELD_BOOLEAN ), DEFINE_FIELD( m_nJumpFromCanisterDir, FIELD_INTEGER ), DEFINE_FIELD( m_bHangingFromCeiling, FIELD_BOOLEAN ), DEFINE_FIELD( m_flIlluminatedTime, FIELD_TIME ), DEFINE_INPUTFUNC( FIELD_VOID, "Burrow", InputBurrow ), DEFINE_INPUTFUNC( FIELD_VOID, "BurrowImmediate", InputBurrowImmediate ), DEFINE_INPUTFUNC( FIELD_VOID, "Unburrow", InputUnburrow ), DEFINE_INPUTFUNC( FIELD_VOID, "StartHangingFromCeiling", InputStartHangingFromCeiling ), DEFINE_INPUTFUNC( FIELD_VOID, "DropFromCeiling", InputDropFromCeiling ), // Function Pointers DEFINE_THINKFUNC( EliminateRollAndPitch ), DEFINE_THINKFUNC( ThrowThink ), DEFINE_ENTITYFUNC( LeapTouch ), END_DATADESC() //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseHeadcrab::Spawn( void ) { //Precache(); //SetModel( "models/headcrab.mdl" ); //m_iHealth = sk_headcrab_health.GetFloat(); #ifdef _XBOX // Always fade the corpse AddSpawnFlags( SF_NPC_FADE_CORPSE ); #endif // _XBOX SetHullType(HULL_TINY); SetHullSizeNormal(); SetSolid( SOLID_BBOX ); AddSolidFlags( FSOLID_NOT_STANDABLE ); SetMoveType( MOVETYPE_STEP ); SetCollisionGroup( HL2COLLISION_GROUP_HEADCRAB ); SetViewOffset( Vector(6, 0, 11) ) ; // Position of the eyes relative to NPC's origin. SetBloodColor( BLOOD_COLOR_GREEN ); m_flFieldOfView = 0.5; m_NPCState = NPC_STATE_NONE; m_nGibCount = HEADCRAB_ALL_GIB_COUNT; // Are we starting hidden? if ( m_spawnflags & SF_HEADCRAB_START_HIDDEN ) { m_bHidden = true; AddSolidFlags( FSOLID_NOT_SOLID ); SetRenderColorA( 0 ); m_nRenderMode = kRenderTransTexture; AddEffects( EF_NODRAW ); } else { m_bHidden = false; } CapabilitiesClear(); CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 ); CapabilitiesAdd( bits_CAP_SQUAD ); // headcrabs get to cheat for 5 seconds (sjb) GetEnemies()->SetFreeKnowledgeDuration( 5.0 ); m_bHangingFromCeiling = false; m_flIlluminatedTime = -1; } //----------------------------------------------------------------------------- // Purpose: Stuff that must happen after NPCInit is called. //----------------------------------------------------------------------------- void CBaseHeadcrab::HeadcrabInit() { // See if we're supposed to start burrowed if ( m_bStartBurrowed ) { SetBurrowed( true ); SetSchedule( SCHED_HEADCRAB_BURROW_WAIT ); } if ( GetSpawnFlags() & SF_HEADCRAB_START_HANGING ) { SetSchedule( SCHED_HEADCRAB_CEILING_WAIT ); m_flIlluminatedTime = -1; } } //----------------------------------------------------------------------------- // Purpose: Precaches all resources this monster needs. //----------------------------------------------------------------------------- void CBaseHeadcrab::Precache( void ) { BaseClass::Precache(); } //----------------------------------------------------------------------------- // The headcrab will crawl from the cannister, then jump to a burrow point //----------------------------------------------------------------------------- void CBaseHeadcrab::CrawlFromCanister() { // This is necessary to prevent ground computations, etc. from happening // while the crawling animation is occuring AddFlag( FL_FLY ); m_bCrawlFromCanister = true; SetNextThink( gpGlobals->curtime ); } //----------------------------------------------------------------------------- // Purpose: // Input : NewActivity - //----------------------------------------------------------------------------- void CBaseHeadcrab::OnChangeActivity( Activity NewActivity ) { bool fRandomize = false; float flRandomRange = 0.0; // If this crab is starting to walk or idle, pick a random point within // the animation to begin. This prevents lots of crabs being in lockstep. if ( NewActivity == ACT_IDLE ) { flRandomRange = 0.75; fRandomize = true; } else if ( NewActivity == ACT_RUN ) { flRandomRange = 0.25; fRandomize = true; } BaseClass::OnChangeActivity( NewActivity ); if( fRandomize ) { SetCycle( random->RandomFloat( 0.0, flRandomRange ) ); } } //----------------------------------------------------------------------------- // Purpose: Indicates this monster's place in the relationship table. // Output : //----------------------------------------------------------------------------- Class_T CBaseHeadcrab::Classify( void ) { if( m_bHidden ) { // Effectively invisible to other AI's while hidden. return( CLASS_NONE ); } else { return( CLASS_HEADCRAB ); } } //----------------------------------------------------------------------------- // Purpose: // Input : &posSrc - // Output : Vector //----------------------------------------------------------------------------- Vector CBaseHeadcrab::BodyTarget( const Vector &posSrc, bool bNoisy ) { Vector vecResult; vecResult = GetAbsOrigin(); vecResult.z += 6; return vecResult; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- float CBaseHeadcrab::GetAutoAimRadius() { if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE ) { return 24.0f; } return 12.0f; } //----------------------------------------------------------------------------- // Purpose: Allows each sequence to have a different turn rate associated with it. // Output : float //----------------------------------------------------------------------------- float CBaseHeadcrab::MaxYawSpeed( void ) { return BaseClass::MaxYawSpeed(); } //----------------------------------------------------------------------------- // Because the AI code does a tracehull to find the ground under an NPC, headcrabs // can often be seen standing with one edge of their box perched on a ledge and // 80% or more of their body hanging out over nothing. This is often a case // where a headcrab will be unable to pathfind out of its location. This heuristic // very crudely tries to determine if this is the case by casting a simple ray // down from the center of the headcrab. //----------------------------------------------------------------------------- #define HEADCRAB_MAX_LEDGE_HEIGHT 12.0f bool CBaseHeadcrab::IsFirmlyOnGround() { if( !(GetFlags()&FL_ONGROUND) ) return false; trace_t tr; UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, HEADCRAB_MAX_LEDGE_HEIGHT ), MASK_NPCSOLID, this, GetCollisionGroup(), &tr ); return tr.fraction != 1.0; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CBaseHeadcrab::MoveOrigin( const Vector &vecDelta ) { UTIL_SetOrigin( this, GetLocalOrigin() + vecDelta ); } //----------------------------------------------------------------------------- // Purpose: // Input : vecPos - //----------------------------------------------------------------------------- void CBaseHeadcrab::ThrowAt( const Vector &vecPos ) { JumpAttack( false, vecPos, true ); } //----------------------------------------------------------------------------- // Purpose: // Input : vecPos - //----------------------------------------------------------------------------- void CBaseHeadcrab::JumpToBurrowHint( CAI_Hint *pHint ) { Vector vecVel = VecCheckToss( this, GetAbsOrigin(), pHint->GetAbsOrigin(), 0.5f, 1.0f, false, NULL, NULL ); // Undershoot by a little because it looks bad if we overshoot and turn around to burrow. vecVel *= 0.9f; Leap( vecVel ); GrabHintNode( pHint ); } //----------------------------------------------------------------------------- // Purpose: // Input : vecVel - //----------------------------------------------------------------------------- void CBaseHeadcrab::Leap( const Vector &vecVel ) { SetTouch( &CBaseHeadcrab::LeapTouch ); SetCondition( COND_FLOATING_OFF_GROUND ); SetGroundEntity( NULL ); m_flIgnoreWorldCollisionTime = gpGlobals->curtime + HEADCRAB_IGNORE_WORLD_COLLISION_TIME; if( HasHeadroom() ) { // Take him off ground so engine doesn't instantly reset FL_ONGROUND. MoveOrigin( Vector( 0, 0, 1 ) ); } SetAbsVelocity( vecVel ); // Think every frame so the player sees the headcrab where he actually is... m_bMidJump = true; SetThink( &CBaseHeadcrab::ThrowThink ); SetNextThink( gpGlobals->curtime ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseHeadcrab::ThrowThink( void ) { if (gpGlobals->curtime > m_flNextNPCThink) { NPCThink(); m_flNextNPCThink = gpGlobals->curtime + 0.1; } if( GetFlags() & FL_ONGROUND ) { SetThink( &CBaseHeadcrab::CallNPCThink ); SetNextThink( gpGlobals->curtime + 0.1 ); return; } SetNextThink( gpGlobals->curtime ); } //----------------------------------------------------------------------------- // Purpose: Does a jump attack at the given position. // Input : bRandomJump - Just hop in a random direction. // vecPos - Position to jump at, ignored if bRandom is set to true. // bThrown - //----------------------------------------------------------------------------- void CBaseHeadcrab::JumpAttack( bool bRandomJump, const Vector &vecPos, bool bThrown ) { Vector vecJumpVel; if ( !bRandomJump ) { float gravity = GetCurrentGravity(); if ( gravity <= 1 ) { gravity = 1; } // How fast does the headcrab need to travel to reach the position given gravity? float flActualHeight = vecPos.z - GetAbsOrigin().z; float height = flActualHeight; if ( height < 16 ) { height = 16; } else { float flMaxHeight = bThrown ? 400 : 120; if ( height > flMaxHeight ) { height = flMaxHeight; } } // overshoot the jump by an additional 8 inches // NOTE: This calculation jumps at a position INSIDE the box of the enemy (player) // so if you make the additional height too high, the crab can land on top of the // enemy's head. If we want to jump high, we'll need to move vecPos to the surface/outside // of the enemy's box. float additionalHeight = 0; if ( height < 32 ) { additionalHeight = 8; } height += additionalHeight; // NOTE: This equation here is from vf^2 = vi^2 + 2*a*d float speed = sqrt( 2 * gravity * height ); float time = speed / gravity; // add in the time it takes to fall the additional height // So the impact takes place on the downward slope at the original height time += sqrt( (2 * additionalHeight) / gravity ); // Scale the sideways velocity to get there at the right time VectorSubtract( vecPos, GetAbsOrigin(), vecJumpVel ); vecJumpVel /= time; // Speed to offset gravity at the desired height. vecJumpVel.z = speed; // Don't jump too far/fast. float flJumpSpeed = vecJumpVel.Length(); float flMaxSpeed = bThrown ? 1000.0f : 650.0f; if ( flJumpSpeed > flMaxSpeed ) { vecJumpVel *= flMaxSpeed / flJumpSpeed; } } else { // // Jump hop, don't care where. // Vector forward, up; AngleVectors( GetLocalAngles(), &forward, NULL, &up ); vecJumpVel = Vector( forward.x, forward.y, up.z ) * 350; } AttackSound(); Leap( vecJumpVel ); } //----------------------------------------------------------------------------- // Purpose: Catches the monster-specific messages that occur when tagged // animation frames are played. // Input : *pEvent - //----------------------------------------------------------------------------- void CBaseHeadcrab::HandleAnimEvent( animevent_t *pEvent ) { if ( pEvent->event == AE_HEADCRAB_JUMPATTACK ) { // Ignore if we're in mid air if ( m_bMidJump ) return; CBaseEntity *pEnemy = GetEnemy(); if ( pEnemy ) { if ( m_bCommittedToJump ) { JumpAttack( false, m_vecCommittedJumpPos ); } else { // Jump at my enemy's eyes. JumpAttack( false, pEnemy->EyePosition() ); } m_bCommittedToJump = false; } else { // Jump hop, don't care where. JumpAttack( true ); } return; } if ( pEvent->event == AE_HEADCRAB_CEILING_DETACH ) { SetMoveType( MOVETYPE_STEP ); RemoveFlag( FL_ONGROUND ); RemoveFlag( FL_FLY ); SetAbsVelocity( Vector ( 0, 0, -128 ) ); return; } if ( pEvent->event == AE_HEADCRAB_JUMP_TELEGRAPH ) { TelegraphSound(); CBaseEntity *pEnemy = GetEnemy(); if ( pEnemy ) { // Once we telegraph, we MUST jump. This is also when commit to what point // we jump at. Jump at our enemy's eyes. m_vecCommittedJumpPos = pEnemy->EyePosition(); m_bCommittedToJump = true; } return; } if ( pEvent->event == AE_HEADCRAB_BURROW_IN ) { EmitSound( "NPC_Headcrab.BurrowIn" ); CreateDust(); return; } if ( pEvent->event == AE_HEADCRAB_BURROW_IN_FINISH ) { SetBurrowed( true ); return; } if ( pEvent->event == AE_HEADCRAB_BURROW_OUT ) { Assert( m_bBurrowed ); if ( m_bBurrowed ) { EmitSound( "NPC_Headcrab.BurrowOut" ); CreateDust(); SetBurrowed( false ); // We're done with this burrow hint node. It might be NULL here // because we may have started burrowed (no hint node in that case). GrabHintNode( NULL ); } return; } CAI_BaseNPC::HandleAnimEvent( pEvent ); } //----------------------------------------------------------------------------- // Purpose: Does all the fixup for going to/from the burrowed state. //----------------------------------------------------------------------------- void CBaseHeadcrab::SetBurrowed( bool bBurrowed ) { if ( bBurrowed ) { AddEffects( EF_NODRAW ); AddFlag( FL_NOTARGET ); m_spawnflags |= SF_NPC_GAG; AddSolidFlags( FSOLID_NOT_SOLID ); m_takedamage = DAMAGE_NO; m_flFieldOfView = HEADCRAB_BURROWED_FOV; SetState( NPC_STATE_IDLE ); SetActivity( (Activity) ACT_HEADCRAB_BURROW_IDLE ); } else { RemoveEffects( EF_NODRAW ); RemoveFlag( FL_NOTARGET ); m_spawnflags &= ~SF_NPC_GAG; RemoveSolidFlags( FSOLID_NOT_SOLID ); m_takedamage = DAMAGE_YES; m_flFieldOfView = HEADCRAB_UNBURROWED_FOV; } m_bBurrowed = bBurrowed; } //----------------------------------------------------------------------------- // Purpose: // Input : *pTask - //----------------------------------------------------------------------------- void CBaseHeadcrab::RunTask( const Task_t *pTask ) { switch ( pTask->iTask ) { case TASK_HEADCRAB_CLIMB_FROM_CANISTER: AutoMovement( ); if ( IsActivityFinished() ) { TaskComplete(); } break; case TASK_HEADCRAB_JUMP_FROM_CANISTER: GetMotor()->UpdateYaw(); if ( FacingIdeal() ) { TaskComplete(); } break; case TASK_HEADCRAB_WAIT_FOR_BARNACLE_KILL: if ( m_flNextFlinchTime < gpGlobals->curtime ) { m_flNextFlinchTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 2.0f ); CTakeDamageInfo info; PainSound( info ); } break; case TASK_HEADCRAB_HOP_OFF_NPC: if( GetFlags() & FL_ONGROUND ) { TaskComplete(); } else { // Face the direction I've been forced to jump. GetMotor()->SetIdealYawToTargetAndUpdate( GetAbsOrigin() + GetAbsVelocity() ); } break; case TASK_HEADCRAB_DROWN: if( gpGlobals->curtime > m_flTimeDrown ) { OnTakeDamage( CTakeDamageInfo( this, this, m_iHealth * 2, DMG_DROWN ) ); } break; case TASK_RANGE_ATTACK1: case TASK_RANGE_ATTACK2: case TASK_HEADCRAB_HARASS_HOP: { if ( IsActivityFinished() ) { TaskComplete(); m_bMidJump = false; SetTouch( NULL ); SetThink( &CBaseHeadcrab::CallNPCThink ); SetIdealActivity( ACT_IDLE ); if ( m_bAttackFailed ) { // our attack failed because we just ran into something solid. // delay attacking for a while so we don't just repeatedly leap // at the enemy from a bad location. m_bAttackFailed = false; m_flNextAttack = gpGlobals->curtime + 1.2f; } } break; } case TASK_HEADCRAB_CHECK_FOR_UNBURROW: { // Must wait for our next check time if ( m_flBurrowTime > gpGlobals->curtime ) return; // See if we can pop up if ( ValidBurrowPoint( GetAbsOrigin() ) ) { m_spawnflags &= ~SF_NPC_GAG; RemoveSolidFlags( FSOLID_NOT_SOLID ); TaskComplete(); return; } // Try again in a couple of seconds m_flBurrowTime = gpGlobals->curtime + random->RandomFloat( 0.5f, 1.0f ); break; } case TASK_HEADCRAB_BURROW_WAIT: { if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) || HasCondition( COND_CAN_RANGE_ATTACK2 ) ) { TaskComplete(); } break; } case TASK_HEADCRAB_CEILING_WAIT: { #ifdef HL2_EPISODIC if ( DarknessLightSourceWithinRadius( this, DARKNESS_LIGHTSOURCE_SIZE ) ) { DropFromCeiling(); } #endif if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) || HasCondition( COND_CAN_RANGE_ATTACK2 ) ) { TaskComplete(); } break; } case TASK_HEADCRAB_CEILING_DETACH: { if ( IsActivityFinished() ) { ClearCondition( COND_CAN_RANGE_ATTACK1 ); RemoveFlag(FL_FLY); TaskComplete(); } } break; case TASK_HEADCRAB_CEILING_FALL: { Vector vecPrPos; trace_t tr; //Figure out where the headcrab is going to be in quarter of a second. vecPrPos = GetAbsOrigin() + ( GetAbsVelocity() * 0.25f ); UTIL_TraceHull( vecPrPos, vecPrPos, GetHullMins(), GetHullMaxs(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); if ( tr.startsolid == true || GetFlags() & FL_ONGROUND ) { RemoveSolidFlags( FSOLID_NOT_SOLID ); TaskComplete(); } } break; case TASK_HEADCRAB_CEILING_LAND: { if ( IsActivityFinished() ) { RemoveSolidFlags( FSOLID_NOT_SOLID ); //double-dog verify that we're solid. TaskComplete(); m_bHangingFromCeiling = false; } } break; default: { BaseClass::RunTask( pTask ); } } } //----------------------------------------------------------------------------- // Before jumping, headcrabs usually use SetOrigin() to lift themselves off the // ground. If the headcrab doesn't have the clearance to so, they'll be stuck // in the world. So this function makes sure there's headroom first. //----------------------------------------------------------------------------- bool CBaseHeadcrab::HasHeadroom() { trace_t tr; UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, 1 ), MASK_NPCSOLID, this, GetCollisionGroup(), &tr ); #if 0 if( tr.fraction == 1.0f ) { Msg("Headroom\n"); } else { Msg("NO Headroom\n"); } #endif return (tr.fraction == 1.0); } //----------------------------------------------------------------------------- // Purpose: LeapTouch - this is the headcrab's touch function when it is in the air. // Input : *pOther - //----------------------------------------------------------------------------- void CBaseHeadcrab::LeapTouch( CBaseEntity *pOther ) { m_bMidJump = false; if ( IRelationType( pOther ) == D_HT ) { // Don't hit if back on ground if ( !( GetFlags() & FL_ONGROUND ) ) { if ( pOther->m_takedamage != DAMAGE_NO ) { BiteSound(); TouchDamage( pOther ); // attack succeeded, so don't delay our next attack if we previously thought we failed m_bAttackFailed = false; } else { ImpactSound(); } } else { ImpactSound(); } } else if( !(GetFlags() & FL_ONGROUND) ) { // Still in the air... if( !pOther->IsSolid() ) { // Touching a trigger or something. return; } // just ran into something solid, so the attack probably failed. make a note of it // so that when the attack is done, we'll delay attacking for a while so we don't // just repeatedly leap at the enemy from a bad location. m_bAttackFailed = true; if( gpGlobals->curtime < m_flIgnoreWorldCollisionTime ) { // Headcrabs try to ignore the world, static props, and friends for a // fraction of a second after they jump. This is because they often brush // doorframes or props as they leap, and touching those objects turns off // this touch function, which can cause them to hit the player and not bite. // A timer probably isn't the best way to fix this, but it's one of our // safer options at this point (sjb). return; } } // Shut off the touch function. SetTouch( NULL ); SetThink ( &CBaseHeadcrab::CallNPCThink ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- int CBaseHeadcrab::CalcDamageInfo( CTakeDamageInfo *pInfo ) { pInfo->Set( this, this, sk_headcrab_melee_dmg.GetFloat(), DMG_SLASH ); CalculateMeleeDamageForce( pInfo, GetAbsVelocity(), GetAbsOrigin() ); return pInfo->GetDamage(); } //----------------------------------------------------------------------------- // Purpose: Deal the damage from the headcrab's touch attack. //----------------------------------------------------------------------------- void CBaseHeadcrab::TouchDamage( CBaseEntity *pOther ) { CTakeDamageInfo info; CalcDamageInfo( &info ); pOther->TakeDamage( info ); } //--------------------------------------------------------- //--------------------------------------------------------- void CBaseHeadcrab::GatherConditions( void ) { // If we're hidden, just check to see if we should unhide if ( m_bHidden ) { // See if there's enough room for our hull to fit here. If so, unhide. trace_t tr; AI_TraceHull( GetAbsOrigin(), GetAbsOrigin(),GetHullMins(), GetHullMaxs(), MASK_SHOT, this, GetCollisionGroup(), &tr ); if ( tr.fraction == 1.0 ) { SetCondition( COND_PROVOKED ); SetCondition( COND_HEADCRAB_UNHIDE ); if ( g_debug_headcrab.GetInt() == HEADCRAB_DEBUG_HIDING ) { NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0,255,0, true, 1.0 ); } } else if ( g_debug_headcrab.GetInt() == HEADCRAB_DEBUG_HIDING ) { NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 255,0,0, true, 0.1 ); } // Prevent baseclass thinking, so we don't respond to enemy fire, etc. return; } BaseClass::GatherConditions(); if( m_lifeState == LIFE_ALIVE && GetWaterLevel() > 1 ) { // Start Drowning! SetCondition( COND_HEADCRAB_IN_WATER ); } // See if I've landed on an NPC or player or something else illegal ClearCondition( COND_HEADCRAB_ILLEGAL_GROUNDENT ); CBaseEntity *ground = GetGroundEntity(); if( (GetFlags() & FL_ONGROUND) && ground && !ground->IsWorld() ) { if ( IsHangingFromCeiling() == false ) { if( ( ground->IsNPC() || ground->IsPlayer() ) ) { SetCondition( COND_HEADCRAB_ILLEGAL_GROUNDENT ); } else if( ground->VPhysicsGetObject() && (ground->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD) ) { SetCondition( COND_HEADCRAB_ILLEGAL_GROUNDENT ); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseHeadcrab::PrescheduleThink( void ) { BaseClass::PrescheduleThink(); // Are we fading in after being hidden? if ( !m_bHidden && (m_nRenderMode != kRenderNormal) ) { int iNewAlpha = MIN( 255, GetRenderColor().a + 120 ); if ( iNewAlpha >= 255 ) { m_nRenderMode = kRenderNormal; SetRenderColorA( 0 ); } else { SetRenderColorA( iNewAlpha ); } } // // Make the crab coo a little bit in combat state. // if (( m_NPCState == NPC_STATE_COMBAT ) && ( random->RandomFloat( 0, 5 ) < 0.1 )) { IdleSound(); } // Make sure we've turned off our burrow state if we're not in it Activity eActivity = GetActivity(); if ( m_bBurrowed && ( eActivity != ACT_HEADCRAB_BURROW_IDLE ) && ( eActivity != ACT_HEADCRAB_BURROW_OUT ) && ( eActivity != ACT_HEADCRAB_BURROW_IN) ) { DevMsg( "Headcrab failed to unburrow properly!\n" ); Assert( 0 ); SetBurrowed( false ); } } //----------------------------------------------------------------------------- // Eliminates roll + pitch from the headcrab //----------------------------------------------------------------------------- #define HEADCRAB_ROLL_ELIMINATION_TIME 0.3f #define HEADCRAB_PITCH_ELIMINATION_TIME 0.3f //----------------------------------------------------------------------------- // Eliminates roll + pitch potentially in the headcrab at canister jump time //----------------------------------------------------------------------------- void CBaseHeadcrab::EliminateRollAndPitch() { QAngle angles = GetAbsAngles(); angles.x = AngleNormalize( angles.x ); angles.z = AngleNormalize( angles.z ); if ( ( angles.x == 0.0f ) && ( angles.z == 0.0f ) ) return; float flPitchRate = 90.0f / HEADCRAB_PITCH_ELIMINATION_TIME; float flPitchDelta = flPitchRate * TICK_INTERVAL; if ( fabs( angles.x ) <= flPitchDelta ) { angles.x = 0.0f; } else { flPitchDelta *= (angles.x > 0.0f) ? -1.0f : 1.0f; angles.x += flPitchDelta; } float flRollRate = 180.0f / HEADCRAB_ROLL_ELIMINATION_TIME; float flRollDelta = flRollRate * TICK_INTERVAL; if ( fabs( angles.z ) <= flRollDelta ) { angles.z = 0.0f; } else { flRollDelta *= (angles.z > 0.0f) ? -1.0f : 1.0f; angles.z += flRollDelta; } SetAbsAngles( angles ); SetContextThink( &CBaseHeadcrab::EliminateRollAndPitch, gpGlobals->curtime + TICK_INTERVAL, s_pPitchContext ); } //----------------------------------------------------------------------------- // Begins the climb from the canister //----------------------------------------------------------------------------- void CBaseHeadcrab::BeginClimbFromCanister() { Assert( GetMoveParent() ); // Compute a desired position or hint Vector vecForward, vecActualForward; AngleVectors( GetMoveParent()->GetAbsAngles(), &vecActualForward ); vecForward = vecActualForward; vecForward.z = 0.0f; VectorNormalize( vecForward ); Vector vecSearchCenter = GetAbsOrigin(); CAI_Hint *pHint = CAI_HintManager::FindHint( this, HINT_HEADCRAB_BURROW_POINT, 0, HEADCRAB_BURROW_POINT_SEARCH_RADIUS, &vecSearchCenter ); if( !pHint && hl2_episodic.GetBool() ) { // Look for exit points within 10 feet. pHint = CAI_HintManager::FindHint( this, HINT_HEADCRAB_EXIT_POD_POINT, 0, 120.0f, &vecSearchCenter ); } if ( pHint && ( !pHint->IsLocked() ) ) { // Claim the hint node so other headcrabs don't try to take it! GrabHintNode( pHint ); // Compute relative yaw.. Vector vecDelta; VectorSubtract( pHint->GetAbsOrigin(), vecSearchCenter, vecDelta ); vecDelta.z = 0.0f; VectorNormalize( vecDelta ); float flAngle = DotProduct( vecDelta, vecForward ); if ( flAngle >= 0.707f ) { m_nJumpFromCanisterDir = 1; } else { // Check the cross product to see if it's on the left or right. // All we care about is the sign of the z component. If it's +, the hint is on the left. // If it's -, then the hint is on the right. float flCrossZ = vecForward.x * vecDelta.y - vecDelta.x * vecForward.y; m_nJumpFromCanisterDir = ( flCrossZ > 0 ) ? 0 : 2; } } else { // Choose a random direction (forward, left, or right) m_nJumpFromCanisterDir = random->RandomInt( 0, 2 ); } Activity act; switch( m_nJumpFromCanisterDir ) { case 0: act = (Activity)ACT_HEADCRAB_CRAWL_FROM_CANISTER_LEFT; break; default: case 1: act = (Activity)ACT_HEADCRAB_CRAWL_FROM_CANISTER_CENTER; break; case 2: act = (Activity)ACT_HEADCRAB_CRAWL_FROM_CANISTER_RIGHT; break; } SetIdealActivity( act ); } //----------------------------------------------------------------------------- // Jumps from the canister //----------------------------------------------------------------------------- #define HEADCRAB_ATTACK_PLAYER_FROM_CANISTER_DIST 250.0f #define HEADCRAB_ATTACK_PLAYER_FROM_CANISTER_COSANGLE 0.866f void CBaseHeadcrab::JumpFromCanister() { Assert( GetMoveParent() ); Vector vecForward, vecActualForward, vecActualRight; AngleVectors( GetMoveParent()->GetAbsAngles(), &vecActualForward, &vecActualRight, NULL ); switch( m_nJumpFromCanisterDir ) { case 0: VectorMultiply( vecActualRight, -1.0f, vecForward ); break; case 1: vecForward = vecActualForward; break; case 2: vecForward = vecActualRight; break; } vecForward.z = 0.0f; VectorNormalize( vecForward ); QAngle headCrabAngles; VectorAngles( vecForward, headCrabAngles ); SetActivity( ACT_RANGE_ATTACK1 ); StudioFrameAdvanceManual( 0.0 ); SetParent( NULL ); RemoveFlag( FL_FLY ); IncrementInterpolationFrame(); GetMotor()->SetIdealYaw( headCrabAngles.y ); // Check to see if the player is within jump range. If so, jump at him! bool bJumpedAtEnemy = false; // FIXME: Can't use GetEnemy() here because enemy only updates during // schedules which are interruptible by COND_NEW_ENEMY or COND_LOST_ENEMY CBaseEntity *pEnemy = BestEnemy(); if ( pEnemy ) { Vector vecDirToEnemy; VectorSubtract( pEnemy->GetAbsOrigin(), GetAbsOrigin(), vecDirToEnemy ); vecDirToEnemy.z = 0.0f; float flDist = VectorNormalize( vecDirToEnemy ); if ( ( flDist < HEADCRAB_ATTACK_PLAYER_FROM_CANISTER_DIST ) && ( DotProduct( vecDirToEnemy, vecForward ) >= HEADCRAB_ATTACK_PLAYER_FROM_CANISTER_COSANGLE ) ) { GrabHintNode( NULL ); JumpAttack( false, pEnemy->EyePosition(), false ); bJumpedAtEnemy = true; } } if ( !bJumpedAtEnemy ) { if ( GetHintNode() ) { JumpToBurrowHint( GetHintNode() ); } else { vecForward *= 100.0f; vecForward += GetAbsOrigin(); JumpAttack( false, vecForward, false ); } } EliminateRollAndPitch(); } #define HEADCRAB_ILLUMINATED_TIME 0.15f void CBaseHeadcrab::DropFromCeiling( void ) { #ifdef HL2_EPISODIC if ( HL2GameRules()->IsAlyxInDarknessMode() ) { if ( IsHangingFromCeiling() ) { if ( m_flIlluminatedTime == -1 ) { m_flIlluminatedTime = gpGlobals->curtime + HEADCRAB_ILLUMINATED_TIME; return; } if ( m_flIlluminatedTime <= gpGlobals->curtime ) { if ( IsCurSchedule( SCHED_HEADCRAB_CEILING_DROP ) == false ) { SetSchedule( SCHED_HEADCRAB_CEILING_DROP ); CBaseEntity *pPlayer = AI_GetSinglePlayer(); if ( pPlayer ) { SetEnemy( pPlayer ); //Is this a bad thing to do? UpdateEnemyMemory( pPlayer, pPlayer->GetAbsOrigin()); } } } } } #endif // HL2_EPISODIC } //----------------------------------------------------------------------------- // Purpose: Player has illuminated this NPC with the flashlight //----------------------------------------------------------------------------- void CBaseHeadcrab::PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot ) { if ( flDot < 0.97387f ) return; DropFromCeiling(); } bool CBaseHeadcrab::CanBeAnEnemyOf( CBaseEntity *pEnemy ) { #ifdef HL2_EPISODIC if ( IsHangingFromCeiling() ) return false; #endif return BaseClass::CanBeAnEnemyOf( pEnemy ); } //----------------------------------------------------------------------------- // Purpose: // Input : pTask - //----------------------------------------------------------------------------- void CBaseHeadcrab::StartTask( const Task_t *pTask ) { switch ( pTask->iTask ) { case TASK_HEADCRAB_WAIT_FOR_BARNACLE_KILL: break; case TASK_HEADCRAB_BURROW_WAIT: break; case TASK_HEADCRAB_CLIMB_FROM_CANISTER: BeginClimbFromCanister(); break; case TASK_HEADCRAB_JUMP_FROM_CANISTER: JumpFromCanister(); break; case TASK_HEADCRAB_CEILING_POSITION: { trace_t tr; UTIL_TraceHull( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, 512 ), NAI_Hull::Mins( GetHullType() ), NAI_Hull::Maxs( GetHullType() ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); // SetMoveType( MOVETYPE_NONE ); AddFlag(FL_FLY); m_bHangingFromCeiling = true; //Don't need this anymore RemoveSpawnFlags( SF_HEADCRAB_START_HANGING ); SetAbsOrigin( tr.endpos ); TaskComplete(); } break; case TASK_HEADCRAB_CEILING_WAIT: break; case TASK_HEADCRAB_CEILING_DETACH: { SetIdealActivity( (Activity)ACT_HEADCRAB_CEILING_DETACH ); } break; case TASK_HEADCRAB_CEILING_FALL: { SetIdealActivity( (Activity)ACT_HEADCRAB_CEILING_FALL ); } break; case TASK_HEADCRAB_CEILING_LAND: { SetIdealActivity( (Activity)ACT_HEADCRAB_CEILING_LAND ); } break; case TASK_HEADCRAB_HARASS_HOP: { // Just pop up into the air like you're trying to get at the // enemy, even though it's known you can't reach them. if ( GetEnemy() ) { Vector forward, up; GetVectors( &forward, NULL, &up ); m_vecCommittedJumpPos = GetAbsOrigin(); m_vecCommittedJumpPos += up * random->RandomFloat( 80, 150 ); m_vecCommittedJumpPos += forward * random->RandomFloat( 32, 80 ); m_bCommittedToJump = true; SetIdealActivity( ACT_RANGE_ATTACK1 ); } else { TaskFail( "No enemy" ); } } break; case TASK_HEADCRAB_HOP_OFF_NPC: { CBaseEntity *ground = GetGroundEntity(); if( ground ) { // If jumping off of a physics object that the player is holding, create a // solver to prevent the headcrab from colliding with that object for a // short time. if( ground && ground->VPhysicsGetObject() ) { if( ground->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) { NPCPhysics_CreateSolver( this, ground, true, 0.5 ); } } Vector vecJumpDir; // Jump in some random direction. This way if the person I'm standing on is // against a wall, I'll eventually get away. vecJumpDir.z = 0; vecJumpDir.x = 0; vecJumpDir.y = 0; while( vecJumpDir.x == 0 && vecJumpDir.y == 0 ) { vecJumpDir.x = random->RandomInt( -1, 1 ); vecJumpDir.y = random->RandomInt( -1, 1 ); } vecJumpDir.NormalizeInPlace(); SetGroundEntity( NULL ); if( HasHeadroom() ) { // Bump up MoveOrigin( Vector( 0, 0, 1 ) ); } SetAbsVelocity( vecJumpDir * 200 + Vector( 0, 0, 200 ) ); } else { // *shrug* I guess they're gone now. Or dead. TaskComplete(); } } break; case TASK_HEADCRAB_DROWN: { // Set the gravity really low here! Sink slowly SetGravity( UTIL_ScaleForGravity( 80 ) ); m_flTimeDrown = gpGlobals->curtime + 4; break; } case TASK_RANGE_ATTACK1: { #ifdef WEDGE_FIX_THIS CPASAttenuationFilter filter( this, ATTN_IDLE ); EmitSound( filter, entindex(), CHAN_WEAPON, pAttackSounds[0], GetSoundVolume(), ATTN_IDLE, 0, GetVoicePitch() ); #endif SetIdealActivity( ACT_RANGE_ATTACK1 ); break; } case TASK_HEADCRAB_UNHIDE: { m_bHidden = false; RemoveSolidFlags( FSOLID_NOT_SOLID ); RemoveEffects( EF_NODRAW ); TaskComplete(); break; } case TASK_HEADCRAB_CHECK_FOR_UNBURROW: { if ( ValidBurrowPoint( GetAbsOrigin() ) ) { m_spawnflags &= ~SF_NPC_GAG; RemoveSolidFlags( FSOLID_NOT_SOLID ); TaskComplete(); } break; } case TASK_HEADCRAB_FIND_BURROW_IN_POINT: { if ( FindBurrow( GetAbsOrigin(), pTask->flTaskData, true ) == false ) { TaskFail( "TASK_HEADCRAB_FIND_BURROW_IN_POINT: Unable to find burrow in position\n" ); } else { TaskComplete(); } break; } case TASK_HEADCRAB_BURROW: { Burrow(); TaskComplete(); break; } case TASK_HEADCRAB_UNBURROW: { Unburrow(); TaskComplete(); break; } default: { BaseClass::StartTask( pTask ); } } } //----------------------------------------------------------------------------- // Purpose: For innate melee attack // Input : // Output : //----------------------------------------------------------------------------- float CBaseHeadcrab::InnateRange1MinRange( void ) { return HEADCRAB_MIN_JUMP_DIST; } float CBaseHeadcrab::InnateRange1MaxRange( void ) { return HEADCRAB_MAX_JUMP_DIST; } int CBaseHeadcrab::RangeAttack1Conditions( float flDot, float flDist ) { if ( gpGlobals->curtime < m_flNextAttack ) return 0; if ( ( GetFlags() & FL_ONGROUND ) == false ) return 0; // When we're burrowed ignore facing, because when we unburrow we'll cheat and face our enemy. if ( !m_bBurrowed && ( flDot < 0.65 ) ) return COND_NOT_FACING_ATTACK; // This code stops lots of headcrabs swarming you and blocking you // whilst jumping up and down in your face over and over. It forces // them to back up a bit. If this causes problems, consider using it // for the fast headcrabs only, rather than just removing it.(sjb) if ( flDist < HEADCRAB_MIN_JUMP_DIST ) return COND_TOO_CLOSE_TO_ATTACK; if ( flDist > HEADCRAB_MAX_JUMP_DIST ) return COND_TOO_FAR_TO_ATTACK; // Make sure the way is clear! CBaseEntity *pEnemy = GetEnemy(); if( pEnemy ) { bool bEnemyIsBullseye = ( dynamic_cast(pEnemy) != NULL ); trace_t tr; AI_TraceLine( EyePosition(), pEnemy->EyePosition(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); if ( tr.m_pEnt != GetEnemy() ) { if ( !bEnemyIsBullseye || tr.m_pEnt != NULL ) return COND_NONE; } if( GetEnemy()->EyePosition().z - 36.0f > GetAbsOrigin().z ) { // Only run this test if trying to jump at a player who is higher up than me, else this // code will always prevent a headcrab from jumping down at an enemy, and sometimes prevent it // jumping just slightly up at an enemy. Vector vStartHullTrace = GetAbsOrigin(); vStartHullTrace.z += 1.0; Vector vEndHullTrace = GetEnemy()->EyePosition() - GetAbsOrigin(); vEndHullTrace.NormalizeInPlace(); vEndHullTrace *= 8.0; vEndHullTrace += GetAbsOrigin(); AI_TraceHull( vStartHullTrace, vEndHullTrace,GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, GetCollisionGroup(), &tr ); if ( tr.m_pEnt != NULL && tr.m_pEnt != GetEnemy() ) { return COND_TOO_CLOSE_TO_ATTACK; } } } return COND_CAN_RANGE_ATTACK1; } //------------------------------------------------------------------------------ // Purpose: Override to do headcrab specific gibs // Output : //------------------------------------------------------------------------------ bool CBaseHeadcrab::CorpseGib( const CTakeDamageInfo &info ) { EmitSound( "NPC_HeadCrab.Gib" ); return BaseClass::CorpseGib( info ); } //------------------------------------------------------------------------------ // Purpose: // Input : //------------------------------------------------------------------------------ void CBaseHeadcrab::Touch( CBaseEntity *pOther ) { // If someone has smacked me into a wall then gib! if (m_NPCState == NPC_STATE_DEAD) { if (GetAbsVelocity().Length() > 250) { trace_t tr; Vector vecDir = GetAbsVelocity(); VectorNormalize(vecDir); AI_TraceLine(GetAbsOrigin(), GetAbsOrigin() + vecDir * 100, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr); float dotPr = DotProduct(vecDir,tr.plane.normal); if ((tr.fraction != 1.0) && (dotPr < -0.8) ) { CTakeDamageInfo info( GetWorldEntity(), GetWorldEntity(), 100.0f, DMG_CRUSH ); info.SetDamagePosition( tr.endpos ); Event_Gibbed( info ); } } } BaseClass::Touch(pOther); } //----------------------------------------------------------------------------- // Purpose: // Input : pevInflictor - // pevAttacker - // flDamage - // bitsDamageType - // Output : //----------------------------------------------------------------------------- int CBaseHeadcrab::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo ) { CTakeDamageInfo info = inputInfo; // // Don't take any acid damage. // if ( info.GetDamageType() & DMG_ACID ) { info.SetDamage( 0 ); } // // Certain death from melee bludgeon weapons! // if ( info.GetDamageType() & DMG_CLUB ) { info.SetDamage( m_iHealth ); } if( info.GetDamageType() & DMG_BLAST ) { if( random->RandomInt( 0 , 1 ) == 0 ) { // Catch on fire randomly if damaged in a blast. Ignite( 30 ); } } if( info.GetDamageType() & DMG_BURN ) { // Slow down burn damage so that headcrabs live longer while on fire. info.ScaleDamage( 0.25 ); #define HEADCRAB_SCORCH_RATE 5 #define HEADCRAB_SCORCH_FLOOR 30 if( IsOnFire() ) { Scorch( HEADCRAB_SCORCH_RATE, HEADCRAB_SCORCH_FLOOR ); if( m_iHealth <= 1 && (entindex() % 2) ) { // Some headcrabs leap at you with their dying breath if( !IsCurSchedule( SCHED_HEADCRAB_RANGE_ATTACK1 ) && !IsRunningDynamicInteraction() ) { SetSchedule( SCHED_HEADCRAB_RANGE_ATTACK1 ); } } } Ignite( 30 ); } return CAI_BaseNPC::OnTakeDamage_Alive( info ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CBaseHeadcrab::ClampRagdollForce( const Vector &vecForceIn, Vector *vecForceOut ) { // Assumes the headcrab mass is 5kg (100 feet per second) float MAX_HEADCRAB_RAGDOLL_SPEED = 100.0f * 12.0f * 5.0f; Vector vecClampedForce; BaseClass::ClampRagdollForce( vecForceIn, &vecClampedForce ); // Copy the force to vecForceOut, in case we don't change it. *vecForceOut = vecClampedForce; float speed = VectorNormalize( vecClampedForce ); if( speed > MAX_HEADCRAB_RAGDOLL_SPEED ) { // Don't let the ragdoll go as fast as it was going to. vecClampedForce *= MAX_HEADCRAB_RAGDOLL_SPEED; *vecForceOut = vecClampedForce; } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CBaseHeadcrab::Event_Killed( const CTakeDamageInfo &info ) { // Create a little decal underneath the headcrab // This type of damage combination happens from dynamic scripted sequences if ( info.GetDamageType() & (DMG_GENERIC | DMG_PREVENT_PHYSICS_FORCE) ) { trace_t tr; AI_TraceLine( GetAbsOrigin()+Vector(0,0,1), GetAbsOrigin()-Vector(0,0,64), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); UTIL_DecalTrace( &tr, "YellowBlood" ); } BaseClass::Event_Killed( info ); } //----------------------------------------------------------------------------- // Purpose: // Input : Type - //----------------------------------------------------------------------------- int CBaseHeadcrab::TranslateSchedule( int scheduleType ) { switch( scheduleType ) { case SCHED_FALL_TO_GROUND: return SCHED_HEADCRAB_FALL_TO_GROUND; case SCHED_WAKE_ANGRY: { if ( HaveSequenceForActivity((Activity)ACT_HEADCRAB_THREAT_DISPLAY) ) return SCHED_HEADCRAB_WAKE_ANGRY; else return SCHED_HEADCRAB_WAKE_ANGRY_NO_DISPLAY; } case SCHED_RANGE_ATTACK1: return SCHED_HEADCRAB_RANGE_ATTACK1; case SCHED_FAIL_TAKE_COVER: return SCHED_ALERT_FACE; case SCHED_CHASE_ENEMY_FAILED: { if( !GetEnemy() ) break; if( !HasCondition( COND_SEE_ENEMY ) ) break; float flZDiff; flZDiff = GetEnemy()->GetAbsOrigin().z - GetAbsOrigin().z; // Make sure the enemy isn't so high above me that this would look silly. if( flZDiff < 128.0f || flZDiff > 512.0f ) return SCHED_COMBAT_PATROL; float flDist; flDist = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ).Length2D(); // Maybe a patrol will bring me closer. if( flDist > 384.0f ) { return SCHED_COMBAT_PATROL; } return SCHED_HEADCRAB_HARASS_ENEMY; } break; } return BaseClass::TranslateSchedule( scheduleType ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CBaseHeadcrab::SelectSchedule( void ) { if ( m_bCrawlFromCanister ) { m_bCrawlFromCanister = false; return SCHED_HEADCRAB_CRAWL_FROM_CANISTER; } // If we're hidden or waiting until seen, don't do much at all if ( m_bHidden || HasSpawnFlags(SF_NPC_WAIT_TILL_SEEN) ) { if( HasCondition( COND_HEADCRAB_UNHIDE ) ) { // We've decided to unhide return SCHED_HEADCRAB_UNHIDE; } return m_bBurrowed ? ( int )SCHED_HEADCRAB_BURROW_WAIT : ( int )SCHED_IDLE_STAND; } if ( GetSpawnFlags() & SF_HEADCRAB_START_HANGING && IsHangingFromCeiling() == false ) { return SCHED_HEADCRAB_CEILING_WAIT; } if ( IsHangingFromCeiling() ) { bool bIsAlyxInDarknessMode = false; #ifdef HL2_EPISODIC bIsAlyxInDarknessMode = HL2GameRules()->IsAlyxInDarknessMode(); #endif // HL2_EPISODIC if ( bIsAlyxInDarknessMode == false && ( HasCondition( COND_CAN_RANGE_ATTACK1 ) || HasCondition( COND_NEW_ENEMY ) ) ) return SCHED_HEADCRAB_CEILING_DROP; if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) ) return SCHED_HEADCRAB_CEILING_DROP; return SCHED_HEADCRAB_CEILING_WAIT; } if ( m_bBurrowed ) { if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) ) return SCHED_HEADCRAB_BURROW_OUT; return SCHED_HEADCRAB_BURROW_WAIT; } if( HasCondition( COND_HEADCRAB_IN_WATER ) ) { // No matter what, drown in water return SCHED_HEADCRAB_DROWN; } if( HasCondition( COND_HEADCRAB_ILLEGAL_GROUNDENT ) ) { // You're on an NPC's head. Get off. return SCHED_HEADCRAB_HOP_RANDOMLY; } if ( HasCondition( COND_HEADCRAB_BARNACLED ) ) { // Caught by a barnacle! return SCHED_HEADCRAB_BARNACLED; } switch ( m_NPCState ) { case NPC_STATE_ALERT: { if (HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE )) { if ( fabs( GetMotor()->DeltaIdealYaw() ) < ( 1.0 - m_flFieldOfView) * 60 ) // roughly in the correct direction { return SCHED_TAKE_COVER_FROM_ORIGIN; } else if ( SelectWeightedSequence( ACT_SMALL_FLINCH ) != -1 ) { m_flNextFlinchTime = gpGlobals->curtime + random->RandomFloat( 1, 3 ); return SCHED_SMALL_FLINCH; } } else if (HasCondition( COND_HEAR_DANGER ) || HasCondition( COND_HEAR_PLAYER ) || HasCondition( COND_HEAR_WORLD ) || HasCondition( COND_HEAR_COMBAT )) { return SCHED_ALERT_FACE_BESTSOUND; } else { return SCHED_PATROL_WALK; } break; } } if ( HasCondition( COND_FLOATING_OFF_GROUND ) ) { SetGravity( 1.0 ); SetGroundEntity( NULL ); return SCHED_FALL_TO_GROUND; } if ( GetHintNode() && GetHintNode()->HintType() == HINT_HEADCRAB_BURROW_POINT ) { // Only burrow if we're not within leap attack distance of our enemy. if ( ( GetEnemy() == NULL ) || ( ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ).Length() > HEADCRAB_MAX_JUMP_DIST ) ) { return SCHED_HEADCRAB_RUN_TO_SPECIFIC_BURROW; } else { // Forget about burrowing, we've got folks to leap at! GrabHintNode( NULL ); } } int nSchedule = BaseClass::SelectSchedule(); if ( nSchedule == SCHED_SMALL_FLINCH ) { m_flNextFlinchTime = gpGlobals->curtime + random->RandomFloat( 1, 3 ); } return nSchedule; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- int CBaseHeadcrab::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode ) { if ( failedSchedule == SCHED_BACK_AWAY_FROM_ENEMY && failedTask == TASK_FIND_BACKAWAY_FROM_SAVEPOSITION ) { if ( HasCondition( COND_SEE_ENEMY ) ) { return SCHED_RANGE_ATTACK1; } } if ( failedSchedule == SCHED_BACK_AWAY_FROM_ENEMY || failedSchedule == SCHED_PATROL_WALK || failedSchedule == SCHED_COMBAT_PATROL ) { if( !IsFirmlyOnGround() ) { return SCHED_HEADCRAB_HOP_RANDOMLY; } } return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode ); } //----------------------------------------------------------------------------- // Purpose: // Input : &info - // &vecDir - // *ptr - //----------------------------------------------------------------------------- void CBaseHeadcrab::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) { CTakeDamageInfo newInfo = info; // Ignore if we're in a dynamic scripted sequence if ( info.GetDamageType() & DMG_PHYSGUN && !IsRunningDynamicInteraction() ) { Vector puntDir = ( info.GetDamageForce() * 1000.0f ); newInfo.SetDamage( m_iMaxHealth / 3.0f ); if( info.GetDamage() >= GetHealth() ) { // This blow will be fatal, so scale the damage force // (it's a unit vector) so that the ragdoll will be // affected. newInfo.SetDamageForce( info.GetDamageForce() * 3000.0f ); } PainSound( newInfo ); SetGroundEntity( NULL ); ApplyAbsVelocityImpulse( puntDir ); } BaseClass::TraceAttack( newInfo, vecDir, ptr, pAccumulator ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CBaseHeadcrab::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner ) { // Can't start on fire if we're burrowed if ( m_bBurrowed ) return; bool bWasOnFire = IsOnFire(); #ifdef HL2_EPISODIC if( GetHealth() > flFlameLifetime ) { // Add some burn time to very healthy headcrabs to fix a bug where // black headcrabs would sometimes spontaneously extinguish (and survive) flFlameLifetime += 10.0f; } #endif// HL2_EPISODIC BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner ); if( !bWasOnFire ) { #ifdef HL2_EPISODIC if ( HL2GameRules()->IsAlyxInDarknessMode() == true ) { GetEffectEntity()->AddEffects( EF_DIMLIGHT ); } #endif // HL2_EPISODIC // For the poison headcrab, who runs around when ignited SetActivity( TranslateActivity(GetIdealActivity()) ); } } //----------------------------------------------------------------------------- // Purpose: This is a generic function (to be implemented by sub-classes) to // handle specific interactions between different types of characters // (For example the barnacle grabbing an NPC) // Input : Constant for the type of interaction // Output : true - if sub-class has a response for the interaction // false - if sub-class has no response //----------------------------------------------------------------------------- bool CBaseHeadcrab::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt) { if (interactionType == g_interactionBarnacleVictimDangle) { // Die instantly return false; } else if (interactionType == g_interactionVortigauntStomp) { SetIdealState( NPC_STATE_PRONE ); return true; } else if (interactionType == g_interactionVortigauntStompFail) { SetIdealState( NPC_STATE_COMBAT ); return true; } else if (interactionType == g_interactionVortigauntStompHit) { // Gib the existing guy, but only with legs and guts m_nGibCount = HEADCRAB_LEGS_GIB_COUNT; OnTakeDamage ( CTakeDamageInfo( sourceEnt, sourceEnt, m_iHealth, DMG_CRUSH|DMG_ALWAYSGIB ) ); // Create dead headcrab in its place CBaseHeadcrab *pEntity = (CBaseHeadcrab*) CreateEntityByName( "npc_headcrab" ); pEntity->Spawn(); pEntity->SetLocalOrigin( GetLocalOrigin() ); pEntity->SetLocalAngles( GetLocalAngles() ); pEntity->m_NPCState = NPC_STATE_DEAD; return true; } else if ( interactionType == g_interactionVortigauntKick /* || (interactionType == g_interactionBullsquidThrow) */ ) { SetIdealState( NPC_STATE_PRONE ); if( HasHeadroom() ) { MoveOrigin( Vector( 0, 0, 1 ) ); } Vector vHitDir = GetLocalOrigin() - sourceEnt->GetLocalOrigin(); VectorNormalize(vHitDir); CTakeDamageInfo info( sourceEnt, sourceEnt, m_iHealth+1, DMG_CLUB ); CalculateMeleeDamageForce( &info, vHitDir, GetAbsOrigin() ); TakeDamage( info ); return true; } return BaseClass::HandleInteraction( interactionType, data, sourceEnt ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CBaseHeadcrab::FValidateHintType( CAI_Hint *pHint ) { return true; } //----------------------------------------------------------------------------- // Purpose: // Input : &origin - //----------------------------------------------------------------------------- void CBaseHeadcrab::ClearBurrowPoint( const Vector &origin ) { CBaseEntity *pEntity = NULL; float flDist; Vector vecSpot, vecCenter, vecForce; //Cause a ruckus UTIL_ScreenShake( origin, 1.0f, 80.0f, 1.0f, 256.0f, SHAKE_START ); //Iterate on all entities in the vicinity. for ( CEntitySphereQuery sphere( origin, 128 ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() ) { if ( pEntity->m_takedamage != DAMAGE_NO && pEntity->Classify() != CLASS_PLAYER && pEntity->VPhysicsGetObject() ) { vecSpot = pEntity->BodyTarget( origin ); vecForce = ( vecSpot - origin ) + Vector( 0, 0, 16 ); // decrease damage for an ent that's farther from the bomb. flDist = VectorNormalize( vecForce ); //float mass = pEntity->VPhysicsGetObject()->GetMass(); CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1.0f, 1.0f, 1.0f ), &vecCenter ); if ( flDist <= 128.0f ) { pEntity->VPhysicsGetObject()->Wake(); pEntity->VPhysicsGetObject()->ApplyForceOffset( vecForce * 250.0f, vecCenter ); } } } } //----------------------------------------------------------------------------- // Purpose: Determine whether a point is valid or not for burrowing up into // Input : &point - point to test for validity // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CBaseHeadcrab::ValidBurrowPoint( const Vector &point ) { trace_t tr; AI_TraceHull( point, point+Vector(0,0,1), GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, GetCollisionGroup(), &tr ); // See if we were able to get there if ( ( tr.startsolid ) || ( tr.allsolid ) || ( tr.fraction < 1.0f ) ) { CBaseEntity *pEntity = tr.m_pEnt; //If it's a physics object, attempt to knock is away, unless it's a car if ( ( pEntity ) && ( pEntity->VPhysicsGetObject() ) && ( pEntity->GetServerVehicle() == NULL ) ) { ClearBurrowPoint( point ); } return false; } return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseHeadcrab::GrabHintNode( CAI_Hint *pHint ) { // Free up the node for use ClearHintNode(); if ( pHint ) { SetHintNode( pHint ); pHint->Lock( this ); } } //----------------------------------------------------------------------------- // Purpose: Finds a point where the headcrab can burrow underground. // Input : distance - radius to search for burrow spot in // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CBaseHeadcrab::FindBurrow( const Vector &origin, float distance, bool excludeNear ) { // Attempt to find a burrowing point CHintCriteria hintCriteria; hintCriteria.SetHintType( HINT_HEADCRAB_BURROW_POINT ); hintCriteria.SetFlag( bits_HINT_NODE_NEAREST ); hintCriteria.AddIncludePosition( origin, distance ); if ( excludeNear ) { hintCriteria.AddExcludePosition( origin, 128 ); } CAI_Hint *pHint = CAI_HintManager::FindHint( this, hintCriteria ); if ( pHint == NULL ) return false; GrabHintNode( pHint ); // Setup our path and attempt to run there Vector vHintPos; pHint->GetPosition( this, &vHintPos ); AI_NavGoal_t goal( vHintPos, ACT_RUN ); return GetNavigator()->SetGoal( goal ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseHeadcrab::Burrow( void ) { // Stop us from taking damage and being solid m_spawnflags |= SF_NPC_GAG; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseHeadcrab::Unburrow( void ) { // Become solid again and visible m_spawnflags &= ~SF_NPC_GAG; RemoveSolidFlags( FSOLID_NOT_SOLID ); m_takedamage = DAMAGE_YES; SetGroundEntity( NULL ); // If we have an enemy, come out facing them if ( GetEnemy() ) { Vector dir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin(); VectorNormalize(dir); GetMotor()->SetIdealYaw( dir ); QAngle angles = GetLocalAngles(); angles[YAW] = UTIL_VecToYaw( dir ); SetLocalAngles( angles ); } } //----------------------------------------------------------------------------- // Purpose: Tells the headcrab to unburrow as soon the space is clear. //----------------------------------------------------------------------------- void CBaseHeadcrab::InputUnburrow( inputdata_t &inputdata ) { if ( IsAlive() == false ) return; SetSchedule( SCHED_HEADCRAB_WAIT_FOR_CLEAR_UNBURROW ); } //----------------------------------------------------------------------------- // Purpose: Tells the headcrab to run to a nearby burrow point and burrow. //----------------------------------------------------------------------------- void CBaseHeadcrab::InputBurrow( inputdata_t &inputdata ) { if ( IsAlive() == false ) return; SetSchedule( SCHED_HEADCRAB_RUN_TO_BURROW_IN ); } //----------------------------------------------------------------------------- // Purpose: Tells the headcrab to burrow right where he is. //----------------------------------------------------------------------------- void CBaseHeadcrab::InputBurrowImmediate( inputdata_t &inputdata ) { if ( IsAlive() == false ) return; SetSchedule( SCHED_HEADCRAB_BURROW_IN ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseHeadcrab::InputStartHangingFromCeiling( inputdata_t &inputdata ) { if ( IsAlive() == false ) return; SetSchedule( SCHED_HEADCRAB_CEILING_WAIT ); m_flIlluminatedTime = -1; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseHeadcrab::InputDropFromCeiling( inputdata_t &inputdata ) { if ( IsAlive() == false ) return; if ( IsHangingFromCeiling() == false ) return; SetSchedule( SCHED_HEADCRAB_CEILING_DROP ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseHeadcrab::CreateDust( bool placeDecal ) { trace_t tr; AI_TraceLine( GetAbsOrigin()+Vector(0,0,1), GetAbsOrigin()-Vector(0,0,64), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); if ( tr.fraction < 1.0f ) { const surfacedata_t *pdata = physprops->GetSurfaceData( tr.surface.surfaceProps ); if ( ( (char) pdata->game.material == CHAR_TEX_CONCRETE ) || ( (char) pdata->game.material == CHAR_TEX_DIRT ) ) { UTIL_CreateAntlionDust( tr.endpos + Vector(0, 0, 24), GetLocalAngles() ); //CEffectData data; //data.m_vOrigin = GetAbsOrigin(); //data.m_vNormal = tr.plane.normal; //DispatchEffect( "headcrabdust", data ); if ( placeDecal ) { UTIL_DecalTrace( &tr, "Headcrab.Unburrow" ); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CHeadcrab::Precache( void ) { PrecacheModel( "models/headcrabclassic.mdl" ); PrecacheScriptSound( "NPC_HeadCrab.Gib" ); PrecacheScriptSound( "NPC_HeadCrab.Idle" ); PrecacheScriptSound( "NPC_HeadCrab.Alert" ); PrecacheScriptSound( "NPC_HeadCrab.Pain" ); PrecacheScriptSound( "NPC_HeadCrab.Die" ); PrecacheScriptSound( "NPC_HeadCrab.Attack" ); PrecacheScriptSound( "NPC_HeadCrab.Bite" ); PrecacheScriptSound( "NPC_Headcrab.BurrowIn" ); PrecacheScriptSound( "NPC_Headcrab.BurrowOut" ); BaseClass::Precache(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CHeadcrab::Spawn( void ) { Precache(); SetModel( "models/headcrabclassic.mdl" ); BaseClass::Spawn(); m_iHealth = sk_headcrab_health.GetFloat(); m_flBurrowTime = 0.0f; m_bCrawlFromCanister = false; m_bMidJump = false; NPCInit(); HeadcrabInit(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- Activity CHeadcrab::NPC_TranslateActivity( Activity eNewActivity ) { if ( eNewActivity == ACT_WALK ) return ACT_RUN; return BaseClass::NPC_TranslateActivity( eNewActivity ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CHeadcrab::IdleSound( void ) { EmitSound( "NPC_HeadCrab.Idle" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CHeadcrab::AlertSound( void ) { EmitSound( "NPC_HeadCrab.Alert" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CHeadcrab::PainSound( const CTakeDamageInfo &info ) { if( IsOnFire() && random->RandomInt( 0, HEADCRAB_BURN_SOUND_FREQUENCY ) > 0 ) { // Don't squeak every think when burning. return; } EmitSound( "NPC_HeadCrab.Pain" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CHeadcrab::DeathSound( const CTakeDamageInfo &info ) { EmitSound( "NPC_HeadCrab.Die" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CHeadcrab::TelegraphSound( void ) { //FIXME: Need a real one EmitSound( "NPC_HeadCrab.Alert" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CHeadcrab::AttackSound( void ) { EmitSound( "NPC_Headcrab.Attack" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CHeadcrab::BiteSound( void ) { EmitSound( "NPC_HeadCrab.Bite" ); } //--------------------------------------------------------- // Save/Restore //--------------------------------------------------------- BEGIN_DATADESC( CFastHeadcrab ) DEFINE_FIELD( m_iRunMode, FIELD_INTEGER ), DEFINE_FIELD( m_flRealGroundSpeed, FIELD_FLOAT ), DEFINE_FIELD( m_flSlowRunTime, FIELD_TIME ), DEFINE_FIELD( m_flPauseTime, FIELD_TIME ), DEFINE_FIELD( m_vecJumpVel, FIELD_VECTOR ), END_DATADESC() //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CFastHeadcrab::Precache( void ) { PrecacheModel( "models/headcrab.mdl" ); PrecacheScriptSound( "NPC_FastHeadcrab.Idle" ); PrecacheScriptSound( "NPC_FastHeadcrab.Alert" ); PrecacheScriptSound( "NPC_FastHeadcrab.Pain" ); PrecacheScriptSound( "NPC_FastHeadcrab.Die" ); PrecacheScriptSound( "NPC_FastHeadcrab.Bite" ); PrecacheScriptSound( "NPC_FastHeadcrab.Attack" ); BaseClass::Precache(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CFastHeadcrab::Spawn( void ) { Precache(); SetModel( "models/headcrab.mdl" ); BaseClass::Spawn(); m_iHealth = sk_headcrab_health.GetFloat(); m_iRunMode = HEADCRAB_RUNMODE_IDLE; m_flPauseTime = 999999; NPCInit(); HeadcrabInit(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CFastHeadcrab::IdleSound( void ) { EmitSound( "NPC_FastHeadcrab.Idle" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CFastHeadcrab::AlertSound( void ) { EmitSound( "NPC_FastHeadcrab.Alert" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CFastHeadcrab::PainSound( const CTakeDamageInfo &info ) { if( IsOnFire() && random->RandomInt( 0, HEADCRAB_BURN_SOUND_FREQUENCY ) > 0 ) { // Don't squeak every think when burning. return; } EmitSound( "NPC_FastHeadcrab.Pain" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CFastHeadcrab::DeathSound( const CTakeDamageInfo &info ) { EmitSound( "NPC_FastHeadcrab.Die" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CFastHeadcrab::PrescheduleThink( void ) { #if 1 // #IF 0 this to stop the accelrating/decelerating movement. #define HEADCRAB_ACCELERATION 0.1 if( IsAlive() && GetNavigator()->IsGoalActive() ) { switch( m_iRunMode ) { case HEADCRAB_RUNMODE_IDLE: if ( GetActivity() == ACT_RUN ) { m_flRealGroundSpeed = m_flGroundSpeed; m_iRunMode = HEADCRAB_RUNMODE_ACCELERATE; m_flPlaybackRate = HEADCRAB_RUN_MINSPEED; } break; case HEADCRAB_RUNMODE_FULLSPEED: if( gpGlobals->curtime > m_flSlowRunTime ) { m_iRunMode = HEADCRAB_RUNMODE_DECELERATE; } break; case HEADCRAB_RUNMODE_ACCELERATE: if( m_flPlaybackRate < HEADCRAB_RUN_MAXSPEED ) { m_flPlaybackRate += HEADCRAB_ACCELERATION; } if( m_flPlaybackRate >= HEADCRAB_RUN_MAXSPEED ) { m_flPlaybackRate = HEADCRAB_RUN_MAXSPEED; m_iRunMode = HEADCRAB_RUNMODE_FULLSPEED; m_flSlowRunTime = gpGlobals->curtime + random->RandomFloat( 0.1, 1.0 ); } break; case HEADCRAB_RUNMODE_DECELERATE: m_flPlaybackRate -= HEADCRAB_ACCELERATION; if( m_flPlaybackRate <= HEADCRAB_RUN_MINSPEED ) { m_flPlaybackRate = HEADCRAB_RUN_MINSPEED; // Now stop the crab. m_iRunMode = HEADCRAB_RUNMODE_PAUSE; SetActivity( ACT_IDLE ); GetNavigator()->SetMovementActivity(ACT_IDLE); m_flPauseTime = gpGlobals->curtime + random->RandomFloat( 0.2, 0.5 ); m_flRealGroundSpeed = 0.0; } break; case HEADCRAB_RUNMODE_PAUSE: { if( gpGlobals->curtime > m_flPauseTime ) { m_iRunMode = HEADCRAB_RUNMODE_IDLE; SetActivity( ACT_RUN ); GetNavigator()->SetMovementActivity(ACT_RUN); m_flPauseTime = gpGlobals->curtime - 1; m_flRealGroundSpeed = m_flGroundSpeed; } } break; default: Warning( "BIG TIME HEADCRAB ERROR\n" ); break; } m_flGroundSpeed = m_flRealGroundSpeed * m_flPlaybackRate; } else { m_flPauseTime = gpGlobals->curtime - 1; } #endif BaseClass::PrescheduleThink(); } //----------------------------------------------------------------------------- // Purpose: // Input : scheduleType - //----------------------------------------------------------------------------- int CFastHeadcrab::SelectSchedule( void ) { if ( HasSpawnFlags(SF_NPC_WAIT_TILL_SEEN) ) { return SCHED_IDLE_STAND; } if ( HasCondition(COND_CAN_RANGE_ATTACK1) && IsHangingFromCeiling() == false ) { if ( OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) ) return SCHED_RANGE_ATTACK1; ClearCondition(COND_CAN_RANGE_ATTACK1); } return BaseClass::SelectSchedule(); } //----------------------------------------------------------------------------- // Purpose: // Input : scheduleType - //----------------------------------------------------------------------------- int CFastHeadcrab::TranslateSchedule( int scheduleType ) { switch( scheduleType ) { case SCHED_IDLE_STAND: return SCHED_PATROL_WALK; break; case SCHED_RANGE_ATTACK1: return SCHED_FAST_HEADCRAB_RANGE_ATTACK1; break; case SCHED_CHASE_ENEMY: if ( !OccupyStrategySlotRange( SQUAD_SLOT_ENGAGE1, SQUAD_SLOT_ENGAGE4 ) ) return SCHED_PATROL_WALK; break; } return BaseClass::TranslateSchedule( scheduleType ); } //----------------------------------------------------------------------------- // Purpose: // Input : *pTask - //----------------------------------------------------------------------------- void CFastHeadcrab::RunTask( const Task_t *pTask ) { switch ( pTask->iTask ) { case TASK_RANGE_ATTACK1: case TASK_RANGE_ATTACK2: if ( GetEnemy() ) // Fast headcrab faces the target in flight. GetMotor()->SetIdealYawAndUpdate( GetEnemy()->GetAbsOrigin() - GetAbsOrigin(), AI_KEEP_YAW_SPEED ); // Call back up into base headcrab for collision. BaseClass::RunTask( pTask ); break; case TASK_HEADCRAB_HOP_ASIDE: if ( GetEnemy() ) GetMotor()->SetIdealYawAndUpdate( GetEnemy()->GetAbsOrigin() - GetAbsOrigin(), AI_KEEP_YAW_SPEED ); if( GetFlags() & FL_ONGROUND ) { SetGravity(1.0); SetMoveType( MOVETYPE_STEP ); if( GetEnemy() && ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ).Length() > HEADCRAB_MAX_JUMP_DIST ) { TaskFail( ""); } TaskComplete(); } break; default: BaseClass::RunTask( pTask ); break; } } //----------------------------------------------------------------------------- // Purpose: // Input : pTask - //----------------------------------------------------------------------------- void CFastHeadcrab::StartTask( const Task_t *pTask ) { switch ( pTask->iTask ) { case TASK_HEADCRAB_HOP_ASIDE: { Vector vecDir, vecForward, vecRight; bool fJumpIsLeft; trace_t tr; GetVectors( &vecForward, &vecRight, NULL ); fJumpIsLeft = false; if( random->RandomInt( 0, 100 ) < 50 ) { fJumpIsLeft = true; vecRight.Negate(); } vecDir = ( vecRight + ( vecForward * 2 ) ); VectorNormalize( vecDir ); vecDir *= 150.0; // This could be a problem. Since I'm adjusting the headcrab's gravity for flight, this check actually // checks farther ahead than the crab will actually jump. (sjb) AI_TraceHull( GetAbsOrigin(), GetAbsOrigin() + vecDir,GetHullMins(), GetHullMaxs(), MASK_SHOT, this, GetCollisionGroup(), &tr ); //NDebugOverlay::Line( tr.startpos, tr.endpos, 0, 255, 0, false, 1.0 ); if( tr.fraction == 1.0 ) { AIMoveTrace_t moveTrace; GetMoveProbe()->MoveLimit( NAV_JUMP, GetAbsOrigin(), tr.endpos, MASK_NPCSOLID, GetEnemy(), &moveTrace ); // FIXME: Where should this happen? m_vecJumpVel = moveTrace.vJumpVelocity; if( !IsMoveBlocked( moveTrace ) ) { SetAbsVelocity( m_vecJumpVel );// + 0.5f * Vector(0,0,GetCurrentGravity()) * flInterval; SetGravity( UTIL_ScaleForGravity( 1600 ) ); SetGroundEntity( NULL ); SetNavType( NAV_JUMP ); if( fJumpIsLeft ) { SetIdealActivity( (Activity)ACT_HEADCRAB_HOP_LEFT ); GetNavigator()->SetMovementActivity( (Activity) ACT_HEADCRAB_HOP_LEFT ); } else { SetIdealActivity( (Activity)ACT_HEADCRAB_HOP_RIGHT ); GetNavigator()->SetMovementActivity( (Activity) ACT_HEADCRAB_HOP_RIGHT ); } } else { // Can't jump, just fall through. TaskComplete(); } } else { // Can't jump, just fall through. TaskComplete(); } } break; default: { BaseClass::StartTask( pTask ); } } } LINK_ENTITY_TO_CLASS( npc_headcrab, CHeadcrab ); LINK_ENTITY_TO_CLASS( npc_headcrab_fast, CFastHeadcrab ); //----------------------------------------------------------------------------- // Purpose: Make the sound of this headcrab chomping a target. // Input : //----------------------------------------------------------------------------- void CFastHeadcrab::BiteSound( void ) { EmitSound( "NPC_FastHeadcrab.Bite" ); } void CFastHeadcrab::AttackSound( void ) { EmitSound( "NPC_FastHeadcrab.Attack" ); } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- float CHeadcrab::MaxYawSpeed ( void ) { switch ( GetActivity() ) { case ACT_IDLE: return 30; case ACT_RUN: case ACT_WALK: return 20; case ACT_TURN_LEFT: case ACT_TURN_RIGHT: return 15; case ACT_RANGE_ATTACK1: { const Task_t *pCurTask = GetTask(); if ( pCurTask && pCurTask->iTask == TASK_HEADCRAB_JUMP_FROM_CANISTER ) return 15; } return 30; default: return 30; } return BaseClass::MaxYawSpeed(); } //----------------------------------------------------------------------------- // Purpose: Allows for modification of the interrupt mask for the current schedule. // In the most cases the base implementation should be called first. //----------------------------------------------------------------------------- void CBaseHeadcrab::BuildScheduleTestBits( void ) { if ( !IsCurSchedule(SCHED_HEADCRAB_DROWN) ) { // Interrupt any schedule unless already drowning. SetCustomInterruptCondition( COND_HEADCRAB_IN_WATER ); } else { // Don't stop drowning just because you're in water! ClearCustomInterruptCondition( COND_HEADCRAB_IN_WATER ); } if( !IsCurSchedule(SCHED_HEADCRAB_HOP_RANDOMLY) ) { SetCustomInterruptCondition( COND_HEADCRAB_ILLEGAL_GROUNDENT ); } else { ClearCustomInterruptCondition( COND_HEADCRAB_ILLEGAL_GROUNDENT ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CFastHeadcrab::MaxYawSpeed( void ) { switch ( GetActivity() ) { case ACT_IDLE: { return( 120 ); } case ACT_RUN: case ACT_WALK: { return( 150 ); } case ACT_TURN_LEFT: case ACT_TURN_RIGHT: { return( 120 ); } case ACT_RANGE_ATTACK1: { return( 120 ); } default: { return( 120 ); } } } bool CFastHeadcrab::QuerySeeEntity(CBaseEntity *pSightEnt, bool bOnlyHateOrFearIfNPC ) { if ( IsHangingFromCeiling() == true ) return BaseClass::QuerySeeEntity(pSightEnt, bOnlyHateOrFearIfNPC); if( m_NPCState != NPC_STATE_COMBAT ) { if( fabs( pSightEnt->GetAbsOrigin().z - GetAbsOrigin().z ) >= 150 ) { // Don't see things much higher or lower than me unless // I'm already pissed. return false; } } return BaseClass::QuerySeeEntity(pSightEnt, bOnlyHateOrFearIfNPC); } //----------------------------------------------------------------------------- // Black headcrab stuff //----------------------------------------------------------------------------- int ACT_BLACKHEADCRAB_RUN_PANIC; BEGIN_DATADESC( CBlackHeadcrab ) DEFINE_FIELD( m_bPanicState, FIELD_BOOLEAN ), DEFINE_FIELD( m_flPanicStopTime, FIELD_TIME ), DEFINE_FIELD( m_flNextHopTime, FIELD_TIME ), DEFINE_ENTITYFUNC( EjectTouch ), END_DATADESC() LINK_ENTITY_TO_CLASS( npc_headcrab_black, CBlackHeadcrab ); LINK_ENTITY_TO_CLASS( npc_headcrab_poison, CBlackHeadcrab ); //----------------------------------------------------------------------------- // Purpose: Make the sound of this headcrab chomping a target. //----------------------------------------------------------------------------- void CBlackHeadcrab::BiteSound( void ) { EmitSound( "NPC_BlackHeadcrab.Bite" ); } //----------------------------------------------------------------------------- // Purpose: The sound we make when leaping at our enemy. //----------------------------------------------------------------------------- void CBlackHeadcrab::AttackSound( void ) { EmitSound( "NPC_BlackHeadcrab.Attack" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBlackHeadcrab::TelegraphSound( void ) { EmitSound( "NPC_BlackHeadcrab.Telegraph" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBlackHeadcrab::Spawn( void ) { Precache(); SetModel( "models/headcrabblack.mdl" ); BaseClass::Spawn(); m_bPanicState = false; m_iHealth = sk_headcrab_poison_health.GetFloat(); NPCInit(); HeadcrabInit(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBlackHeadcrab::Precache( void ) { PrecacheModel( "models/headcrabblack.mdl" ); PrecacheScriptSound( "NPC_BlackHeadcrab.Telegraph" ); PrecacheScriptSound( "NPC_BlackHeadcrab.Attack" ); PrecacheScriptSound( "NPC_BlackHeadcrab.Bite" ); PrecacheScriptSound( "NPC_BlackHeadcrab.Threat" ); PrecacheScriptSound( "NPC_BlackHeadcrab.Alert" ); PrecacheScriptSound( "NPC_BlackHeadcrab.Idle" ); PrecacheScriptSound( "NPC_BlackHeadcrab.Talk" ); PrecacheScriptSound( "NPC_BlackHeadcrab.AlertVoice" ); PrecacheScriptSound( "NPC_BlackHeadcrab.Pain" ); PrecacheScriptSound( "NPC_BlackHeadcrab.Die" ); PrecacheScriptSound( "NPC_BlackHeadcrab.Impact" ); PrecacheScriptSound( "NPC_BlackHeadcrab.ImpactAngry" ); PrecacheScriptSound( "NPC_BlackHeadcrab.FootstepWalk" ); PrecacheScriptSound( "NPC_BlackHeadcrab.Footstep" ); BaseClass::Precache(); } //----------------------------------------------------------------------------- // Purpose: Returns the max yaw speed for the current activity. //----------------------------------------------------------------------------- float CBlackHeadcrab::MaxYawSpeed( void ) { // Not a constant, can't be in a switch statement. if ( GetActivity() == ACT_BLACKHEADCRAB_RUN_PANIC ) { return 30; } switch ( GetActivity() ) { case ACT_WALK: case ACT_RUN: { return 10; } case ACT_TURN_LEFT: case ACT_TURN_RIGHT: { return( 30 ); } case ACT_RANGE_ATTACK1: { return( 30 ); } default: { return( 30 ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- Activity CBlackHeadcrab::NPC_TranslateActivity( Activity eNewActivity ) { if ( eNewActivity == ACT_RUN || eNewActivity == ACT_WALK ) { if( m_bPanicState || IsOnFire() ) { return ( Activity )ACT_BLACKHEADCRAB_RUN_PANIC; } } return BaseClass::NPC_TranslateActivity( eNewActivity ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBlackHeadcrab::PrescheduleThink( void ) { BaseClass::PrescheduleThink(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CBlackHeadcrab::TranslateSchedule( int scheduleType ) { switch ( scheduleType ) { // Keep trying to take cover for at least a few seconds. case SCHED_FAIL_TAKE_COVER: { if ( ( m_bPanicState ) && ( gpGlobals->curtime > m_flPanicStopTime ) ) { //DevMsg( "I'm sick of panicking\n" ); m_bPanicState = false; return SCHED_CHASE_ENEMY; } break; } } return BaseClass::TranslateSchedule( scheduleType ); } //----------------------------------------------------------------------------- // Purpose: Allows for modification of the interrupt mask for the current schedule. // In the most cases the base implementation should be called first. //----------------------------------------------------------------------------- void CBlackHeadcrab::BuildScheduleTestBits( void ) { // Ignore damage if we're attacking or are fleeing and recently flinched. if ( IsCurSchedule( SCHED_HEADCRAB_CRAWL_FROM_CANISTER ) || IsCurSchedule( SCHED_RANGE_ATTACK1 ) || ( IsCurSchedule( SCHED_TAKE_COVER_FROM_ENEMY ) && HasMemory( bits_MEMORY_FLINCHED ) ) ) { ClearCustomInterruptCondition( COND_LIGHT_DAMAGE ); ClearCustomInterruptCondition( COND_HEAVY_DAMAGE ); } else { SetCustomInterruptCondition( COND_LIGHT_DAMAGE ); SetCustomInterruptCondition( COND_HEAVY_DAMAGE ); } // If we're committed to jump, carry on even if our enemy hides behind a crate. Or a barrel. if ( IsCurSchedule( SCHED_RANGE_ATTACK1 ) && m_bCommittedToJump ) { ClearCustomInterruptCondition( COND_ENEMY_OCCLUDED ); } } //----------------------------------------------------------------------------- // Purpose: // Output : //----------------------------------------------------------------------------- int CBlackHeadcrab::SelectSchedule( void ) { // don't override inherited behavior when hanging from ceiling if ( !IsHangingFromCeiling() ) { if ( HasSpawnFlags(SF_NPC_WAIT_TILL_SEEN) ) { return SCHED_IDLE_STAND; } if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) ) { if ( ( gpGlobals->curtime >= m_flNextHopTime ) && SelectWeightedSequence( ACT_SMALL_FLINCH ) != -1 ) { m_flNextHopTime = gpGlobals->curtime + random->RandomFloat( 1, 3 ); return SCHED_SMALL_FLINCH; } } if ( m_bPanicState ) { // We're looking for a place to hide, and we've found one. Lurk! if ( HasMemory( bits_MEMORY_INCOVER ) ) { m_bPanicState = false; m_flPanicStopTime = gpGlobals->curtime; return SCHED_HEADCRAB_AMBUSH; } return SCHED_TAKE_COVER_FROM_ENEMY; } } return BaseClass::SelectSchedule(); } //----------------------------------------------------------------------------- // Purpose: Black headcrab's touch attack damage. Evil! //----------------------------------------------------------------------------- void CBlackHeadcrab::TouchDamage( CBaseEntity *pOther ) { if ( pOther->m_iHealth > 1 ) { CTakeDamageInfo info; if ( CalcDamageInfo( &info ) >= pOther->m_iHealth ) info.SetDamage( pOther->m_iHealth - 1 ); pOther->TakeDamage( info ); if ( pOther->IsAlive() && pOther->m_iHealth > 1) { // Episodic change to avoid NPCs dying too quickly from poison bites if ( hl2_episodic.GetBool() ) { if ( pOther->IsPlayer() ) { // That didn't finish them. Take them down to one point with poison damage. It'll heal. pOther->TakeDamage( CTakeDamageInfo( this, this, pOther->m_iHealth - 1, DMG_POISON ) ); } else { // Just take some amount of slash damage instead pOther->TakeDamage( CTakeDamageInfo( this, this, sk_headcrab_poison_npc_damage.GetFloat(), DMG_SLASH ) ); } } else { // That didn't finish them. Take them down to one point with poison damage. It'll heal. pOther->TakeDamage( CTakeDamageInfo( this, this, pOther->m_iHealth - 1, DMG_POISON ) ); } } } } //----------------------------------------------------------------------------- // Purpose: Bails out of our host zombie, either because he died or was blown // into two pieces by an explosion. // Input : vecAngles - The yaw direction we should face. // flVelocityScale - A multiplier for our ejection velocity. // pEnemy - Who we should acquire as our enemy. Usually our zombie host's enemy. //----------------------------------------------------------------------------- void CBlackHeadcrab::Eject( const QAngle &vecAngles, float flVelocityScale, CBaseEntity *pEnemy ) { SetGroundEntity( NULL ); m_spawnflags |= SF_NPC_FALL_TO_GROUND; SetIdealState( NPC_STATE_ALERT ); if ( pEnemy ) { SetEnemy( pEnemy ); UpdateEnemyMemory(pEnemy, pEnemy->GetAbsOrigin()); } SetActivity( ACT_RANGE_ATTACK1 ); SetNextThink( gpGlobals->curtime ); PhysicsSimulate(); GetMotor()->SetIdealYaw( vecAngles.y ); SetAbsVelocity( flVelocityScale * random->RandomInt( 20, 50 ) * Vector( random->RandomFloat( -1.0, 1.0 ), random->RandomFloat( -1.0, 1.0 ), random->RandomFloat( 0.5, 1.0 ) ) ); m_bMidJump = false; SetTouch( &CBlackHeadcrab::EjectTouch ); } //----------------------------------------------------------------------------- // Purpose: Touch function for when we are ejected from the poison zombie. // Panic when we hit the ground. //----------------------------------------------------------------------------- void CBlackHeadcrab::EjectTouch( CBaseEntity *pOther ) { LeapTouch( pOther ); if ( GetFlags() & FL_ONGROUND ) { // Keep trying to take cover for at least a few seconds. Panic( random->RandomFloat( 2, 8 ) ); } } //----------------------------------------------------------------------------- // Purpose: Puts us in a state in which we just want to hide. We'll stop // hiding after the given duration. //----------------------------------------------------------------------------- void CBlackHeadcrab::Panic( float flDuration ) { m_flPanicStopTime = gpGlobals->curtime + flDuration; m_bPanicState = true; } #if HL2_EPISODIC //----------------------------------------------------------------------------- // Purpose: Black headcrabs have 360-degree vision when they are in the ambush // schedule. This is because they ignore sounds when in ambush, and // you could walk up behind them without having them attack you. // This vision extends only 24 feet. //----------------------------------------------------------------------------- #define CRAB_360_VIEW_DIST_SQR (12 * 12 * 24 * 24) bool CBlackHeadcrab::FInViewCone( CBaseEntity *pEntity ) { if( IsCurSchedule( SCHED_HEADCRAB_AMBUSH ) && (( pEntity->IsNPC() || pEntity->IsPlayer() ) && pEntity->GetAbsOrigin().DistToSqr(GetAbsOrigin()) <= CRAB_360_VIEW_DIST_SQR ) ) { // Only see players and NPC's with 360 cone // For instance, DON'T tell the eyeball/head tracking code that you can see an object that is behind you! return true; } else { return BaseClass::FInViewCone( pEntity ); } } #endif //----------------------------------------------------------------------------- // Purpose: Does a spastic hop in a random or provided direction. // Input : pvecDir - 2D direction to hop, NULL picks a random direction. //----------------------------------------------------------------------------- void CBlackHeadcrab::JumpFlinch( const Vector *pvecDir ) { SetGroundEntity( NULL ); // // Take him off ground so engine doesn't instantly reset FL_ONGROUND. // if( HasHeadroom() ) { MoveOrigin( Vector( 0, 0, 1 ) ); } // // Jump in a random direction. // Vector up; AngleVectors( GetLocalAngles(), NULL, NULL, &up ); if (pvecDir) { SetAbsVelocity( Vector( pvecDir->x * 4, pvecDir->y * 4, up.z ) * random->RandomFloat( 40, 80 ) ); } else { SetAbsVelocity( Vector( random->RandomFloat( -4, 4 ), random->RandomFloat( -4, 4 ), up.z ) * random->RandomFloat( 40, 80 ) ); } } //----------------------------------------------------------------------------- // Purpose: Catches the monster-specific messages that occur when tagged // animation frames are played. // Input : pEvent - //----------------------------------------------------------------------------- void CBlackHeadcrab::HandleAnimEvent( animevent_t *pEvent ) { if ( pEvent->event == AE_POISONHEADCRAB_FOOTSTEP ) { bool walk = ( GetActivity() == ACT_WALK ); // ? 1.0 : 0.6; !!cgreen! old code had bug if ( walk ) { EmitSound( "NPC_BlackHeadcrab.FootstepWalk" ); } else { EmitSound( "NPC_BlackHeadcrab.Footstep" ); } return; } if ( pEvent->event == AE_HEADCRAB_JUMP_TELEGRAPH ) { EmitSound( "NPC_BlackHeadcrab.Telegraph" ); CBaseEntity *pEnemy = GetEnemy(); if ( pEnemy ) { // Once we telegraph, we MUST jump. This is also when commit to what point // we jump at. Jump at our enemy's eyes. m_vecCommittedJumpPos = pEnemy->EyePosition(); m_bCommittedToJump = true; } return; } if ( pEvent->event == AE_POISONHEADCRAB_THREAT_SOUND ) { EmitSound( "NPC_BlackHeadcrab.Threat" ); EmitSound( "NPC_BlackHeadcrab.Alert" ); return; } if ( pEvent->event == AE_POISONHEADCRAB_FLINCH_HOP ) { // // Hop in a random direction, then run and hide. If we're already running // to hide, jump forward -- hopefully that will take us closer to a hiding spot. // if (m_bPanicState) { Vector vecForward; AngleVectors( GetLocalAngles(), &vecForward ); JumpFlinch( &vecForward ); } else { JumpFlinch( NULL ); } Panic( random->RandomFloat( 2, 5 ) ); return; } BaseClass::HandleAnimEvent( pEvent ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CBlackHeadcrab::IsHeavyDamage( const CTakeDamageInfo &info ) { if ( !HasMemory(bits_MEMORY_FLINCHED) && info.GetDamage() > 1.0f ) { // If I haven't flinched lately, any amount of damage is interpreted as heavy. return true; } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBlackHeadcrab::IdleSound( void ) { // TODO: hook up "Marco" / "Polo" talking with nearby buddies if ( m_NPCState == NPC_STATE_IDLE ) { EmitSound( "NPC_BlackHeadcrab.Idle" ); } else if ( m_NPCState == NPC_STATE_ALERT ) { EmitSound( "NPC_BlackHeadcrab.Talk" ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBlackHeadcrab::AlertSound( void ) { EmitSound( "NPC_BlackHeadcrab.AlertVoice" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBlackHeadcrab::PainSound( const CTakeDamageInfo &info ) { if( IsOnFire() && random->RandomInt( 0, HEADCRAB_BURN_SOUND_FREQUENCY ) > 0 ) { // Don't squeak every think when burning. return; } EmitSound( "NPC_BlackHeadcrab.Pain" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBlackHeadcrab::DeathSound( const CTakeDamageInfo &info ) { EmitSound( "NPC_BlackHeadcrab.Die" ); } //----------------------------------------------------------------------------- // Purpose: Played when we jump and hit something that we can't bite. //----------------------------------------------------------------------------- void CBlackHeadcrab::ImpactSound( void ) { EmitSound( "NPC_BlackHeadcrab.Impact" ); if ( !( GetFlags() & FL_ONGROUND ) ) { // Hit a wall - make a pissed off sound. EmitSound( "NPC_BlackHeadcrab.ImpactAngry" ); } } //----------------------------------------------------------------------------- // // Schedules // //----------------------------------------------------------------------------- AI_BEGIN_CUSTOM_NPC( npc_headcrab, CBaseHeadcrab ) DECLARE_TASK( TASK_HEADCRAB_HOP_ASIDE ) DECLARE_TASK( TASK_HEADCRAB_DROWN ) DECLARE_TASK( TASK_HEADCRAB_HOP_OFF_NPC ) DECLARE_TASK( TASK_HEADCRAB_WAIT_FOR_BARNACLE_KILL ) DECLARE_TASK( TASK_HEADCRAB_UNHIDE ) DECLARE_TASK( TASK_HEADCRAB_HARASS_HOP ) DECLARE_TASK( TASK_HEADCRAB_BURROW ) DECLARE_TASK( TASK_HEADCRAB_UNBURROW ) DECLARE_TASK( TASK_HEADCRAB_FIND_BURROW_IN_POINT ) DECLARE_TASK( TASK_HEADCRAB_BURROW_WAIT ) DECLARE_TASK( TASK_HEADCRAB_CHECK_FOR_UNBURROW ) DECLARE_TASK( TASK_HEADCRAB_JUMP_FROM_CANISTER ) DECLARE_TASK( TASK_HEADCRAB_CLIMB_FROM_CANISTER ) DECLARE_TASK( TASK_HEADCRAB_CEILING_POSITION ) DECLARE_TASK( TASK_HEADCRAB_CEILING_WAIT ) DECLARE_TASK( TASK_HEADCRAB_CEILING_DETACH ) DECLARE_TASK( TASK_HEADCRAB_CEILING_FALL ) DECLARE_TASK( TASK_HEADCRAB_CEILING_LAND ) DECLARE_ACTIVITY( ACT_HEADCRAB_THREAT_DISPLAY ) DECLARE_ACTIVITY( ACT_HEADCRAB_HOP_LEFT ) DECLARE_ACTIVITY( ACT_HEADCRAB_HOP_RIGHT ) DECLARE_ACTIVITY( ACT_HEADCRAB_DROWN ) DECLARE_ACTIVITY( ACT_HEADCRAB_BURROW_IN ) DECLARE_ACTIVITY( ACT_HEADCRAB_BURROW_OUT ) DECLARE_ACTIVITY( ACT_HEADCRAB_BURROW_IDLE ) DECLARE_ACTIVITY( ACT_HEADCRAB_CRAWL_FROM_CANISTER_LEFT ) DECLARE_ACTIVITY( ACT_HEADCRAB_CRAWL_FROM_CANISTER_CENTER ) DECLARE_ACTIVITY( ACT_HEADCRAB_CRAWL_FROM_CANISTER_RIGHT ) DECLARE_ACTIVITY( ACT_HEADCRAB_CEILING_FALL ) DECLARE_ACTIVITY( ACT_HEADCRAB_CEILING_IDLE ) DECLARE_ACTIVITY( ACT_HEADCRAB_CEILING_DETACH ) DECLARE_ACTIVITY( ACT_HEADCRAB_CEILING_LAND ) DECLARE_CONDITION( COND_HEADCRAB_IN_WATER ) DECLARE_CONDITION( COND_HEADCRAB_ILLEGAL_GROUNDENT ) DECLARE_CONDITION( COND_HEADCRAB_BARNACLED ) DECLARE_CONDITION( COND_HEADCRAB_UNHIDE ) //Adrian: events go here DECLARE_ANIMEVENT( AE_HEADCRAB_JUMPATTACK ) DECLARE_ANIMEVENT( AE_HEADCRAB_JUMP_TELEGRAPH ) DECLARE_ANIMEVENT( AE_HEADCRAB_BURROW_IN ) DECLARE_ANIMEVENT( AE_HEADCRAB_BURROW_IN_FINISH ) DECLARE_ANIMEVENT( AE_HEADCRAB_BURROW_OUT ) DECLARE_ANIMEVENT( AE_HEADCRAB_CEILING_DETACH ) //========================================================= // > SCHED_HEADCRAB_RANGE_ATTACK1 //========================================================= DEFINE_SCHEDULE ( SCHED_HEADCRAB_RANGE_ATTACK1, " Tasks" " TASK_STOP_MOVING 0" " TASK_FACE_ENEMY 0" " TASK_RANGE_ATTACK1 0" " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" " TASK_FACE_IDEAL 0" " TASK_WAIT_RANDOM 0.5" "" " Interrupts" " COND_ENEMY_OCCLUDED" " COND_NO_PRIMARY_AMMO" ) //========================================================= // //========================================================= DEFINE_SCHEDULE ( SCHED_HEADCRAB_WAKE_ANGRY, " Tasks" " TASK_STOP_MOVING 0" " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE " " TASK_FACE_IDEAL 0" " TASK_SOUND_WAKE 0" " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_HEADCRAB_THREAT_DISPLAY" "" " Interrupts" ) //========================================================= // //========================================================= DEFINE_SCHEDULE ( SCHED_HEADCRAB_WAKE_ANGRY_NO_DISPLAY, " Tasks" " TASK_STOP_MOVING 0" " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE " " TASK_FACE_IDEAL 0" " TASK_SOUND_WAKE 0" " TASK_FACE_ENEMY 0" "" " Interrupts" ) //========================================================= // > SCHED_FAST_HEADCRAB_RANGE_ATTACK1 //========================================================= DEFINE_SCHEDULE ( SCHED_FAST_HEADCRAB_RANGE_ATTACK1, " Tasks" " TASK_STOP_MOVING 0" " TASK_FACE_ENEMY 0" " TASK_RANGE_ATTACK1 0" " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" " TASK_FACE_IDEAL 0" " TASK_WAIT_RANDOM 0.5" "" " Interrupts" ) //========================================================= // The irreversible process of drowning //========================================================= DEFINE_SCHEDULE ( SCHED_HEADCRAB_DROWN, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HEADCRAB_FAIL_DROWN" " TASK_SET_ACTIVITY ACTIVITY:ACT_HEADCRAB_DROWN" " TASK_HEADCRAB_DROWN 0" "" " Interrupts" ) DEFINE_SCHEDULE ( SCHED_HEADCRAB_FAIL_DROWN, " Tasks" " TASK_HEADCRAB_DROWN 0" "" " Interrupts" ) //========================================================= // Headcrab lurks in place and waits for a chance to jump on // some unfortunate soul. //========================================================= DEFINE_SCHEDULE ( SCHED_HEADCRAB_AMBUSH, " Tasks" " TASK_STOP_MOVING 0" " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" " TASK_WAIT_INDEFINITE 0" " Interrupts" " COND_SEE_ENEMY" " COND_SEE_HATE" " COND_CAN_RANGE_ATTACK1" " COND_LIGHT_DAMAGE" " COND_HEAVY_DAMAGE" " COND_PROVOKED" ) //========================================================= // Headcrab has landed atop another NPC or has landed on // a ledge. Get down! //========================================================= DEFINE_SCHEDULE ( SCHED_HEADCRAB_HOP_RANDOMLY, " Tasks" " TASK_STOP_MOVING 0" " TASK_HEADCRAB_HOP_OFF_NPC 0" " Interrupts" ) //========================================================= // Headcrab is in the clutches of a barnacle //========================================================= DEFINE_SCHEDULE ( SCHED_HEADCRAB_BARNACLED, " Tasks" " TASK_STOP_MOVING 0" " TASK_SET_ACTIVITY ACTIVITY:ACT_HEADCRAB_DROWN" " TASK_HEADCRAB_WAIT_FOR_BARNACLE_KILL 0" " Interrupts" ) //========================================================= // Headcrab is unhiding //========================================================= DEFINE_SCHEDULE ( SCHED_HEADCRAB_UNHIDE, " Tasks" " TASK_HEADCRAB_UNHIDE 0" " Interrupts" ) DEFINE_SCHEDULE ( SCHED_HEADCRAB_HARASS_ENEMY, " Tasks" " TASK_FACE_ENEMY 0" " TASK_HEADCRAB_HARASS_HOP 0" " TASK_WAIT_FACE_ENEMY 1" " TASK_SET_ROUTE_SEARCH_TIME 2" // Spend 2 seconds trying to build a path if stuck " TASK_GET_PATH_TO_RANDOM_NODE 300" " TASK_WALK_PATH 0" " TASK_WAIT_FOR_MOVEMENT 0" " Interrupts" " COND_NEW_ENEMY" ) DEFINE_SCHEDULE ( SCHED_HEADCRAB_FALL_TO_GROUND, " Tasks" " TASK_SET_ACTIVITY ACTIVITY:ACT_HEADCRAB_DROWN" " TASK_FALL_TO_GROUND 0" "" " Interrupts" ) DEFINE_SCHEDULE ( SCHED_HEADCRAB_CRAWL_FROM_CANISTER, " Tasks" " TASK_HEADCRAB_CLIMB_FROM_CANISTER 0" " TASK_HEADCRAB_JUMP_FROM_CANISTER 0" "" " Interrupts" ) //================================================== // Burrow In //================================================== DEFINE_SCHEDULE ( SCHED_HEADCRAB_BURROW_IN, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED" " TASK_HEADCRAB_BURROW 0" " TASK_PLAY_SEQUENCE ACTIVITY:ACT_HEADCRAB_BURROW_IN" " TASK_PLAY_SEQUENCE ACTIVITY:ACT_HEADCRAB_BURROW_IDLE" " TASK_SET_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_WAIT" "" " Interrupts" " COND_TASK_FAILED" ) //================================================== // Run to a nearby burrow hint and burrow there //================================================== DEFINE_SCHEDULE ( SCHED_HEADCRAB_RUN_TO_BURROW_IN, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED" " TASK_HEADCRAB_FIND_BURROW_IN_POINT 512" " TASK_SET_TOLERANCE_DISTANCE 8" " TASK_RUN_PATH 0" " TASK_WAIT_FOR_MOVEMENT 0" " TASK_SET_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_IN" "" " Interrupts" " COND_TASK_FAILED" " COND_GIVE_WAY" " COND_CAN_RANGE_ATTACK1" ) //================================================== // Run to m_pHintNode and burrow there //================================================== DEFINE_SCHEDULE ( SCHED_HEADCRAB_RUN_TO_SPECIFIC_BURROW, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED" " TASK_SET_TOLERANCE_DISTANCE 8" " TASK_GET_PATH_TO_HINTNODE 0" " TASK_RUN_PATH 0" " TASK_WAIT_FOR_MOVEMENT 0" " TASK_SET_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_IN" "" " Interrupts" " COND_TASK_FAILED" " COND_GIVE_WAY" ) //================================================== // Wait until we can unburrow and attack something //================================================== DEFINE_SCHEDULE ( SCHED_HEADCRAB_BURROW_WAIT, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_WAIT" " TASK_HEADCRAB_BURROW_WAIT 1" "" " Interrupts" " COND_TASK_FAILED" " COND_NEW_ENEMY" // HACK: We don't actually choose a new schedule on new enemy, but // we need this interrupt so that the headcrab actually acquires // new enemies while burrowed. (look in ai_basenpc.cpp for "DO NOT mess") " COND_CAN_RANGE_ATTACK1" ) //================================================== // Burrow Out //================================================== DEFINE_SCHEDULE ( SCHED_HEADCRAB_BURROW_OUT, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_WAIT" " TASK_HEADCRAB_UNBURROW 0" " TASK_PLAY_SEQUENCE ACTIVITY:ACT_HEADCRAB_BURROW_OUT" "" " Interrupts" " COND_TASK_FAILED" ) //================================================== // Wait for it to be clear for unburrowing //================================================== DEFINE_SCHEDULE ( SCHED_HEADCRAB_WAIT_FOR_CLEAR_UNBURROW, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_WAIT" " TASK_HEADCRAB_CHECK_FOR_UNBURROW 1" " TASK_SET_SCHEDULE SCHEDULE:SCHED_HEADCRAB_BURROW_OUT" "" " Interrupts" " COND_TASK_FAILED" ) //================================================== // Wait until we can drop. //================================================== DEFINE_SCHEDULE ( SCHED_HEADCRAB_CEILING_WAIT, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HEADCRAB_CEILING_DROP" " TASK_SET_ACTIVITY ACTIVITY:ACT_HEADCRAB_CEILING_IDLE" " TASK_HEADCRAB_CEILING_POSITION 0" " TASK_HEADCRAB_CEILING_WAIT 1" "" " Interrupts" " COND_TASK_FAILED" " COND_NEW_ENEMY" " COND_CAN_RANGE_ATTACK1" ) //================================================== // Deatch from ceiling. //================================================== DEFINE_SCHEDULE ( SCHED_HEADCRAB_CEILING_DROP, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HEADCRAB_CEILING_WAIT" " TASK_HEADCRAB_CEILING_DETACH 0" " TASK_HEADCRAB_CEILING_FALL 0" " TASK_HEADCRAB_CEILING_LAND 0" "" " Interrupts" " COND_TASK_FAILED" ) AI_END_CUSTOM_NPC() //----------------------------------------------------------------------------- AI_BEGIN_CUSTOM_NPC( npc_headcrab_poison, CBlackHeadcrab ) DECLARE_ACTIVITY( ACT_BLACKHEADCRAB_RUN_PANIC ) //Adrian: events go here DECLARE_ANIMEVENT( AE_POISONHEADCRAB_FLINCH_HOP ) DECLARE_ANIMEVENT( AE_POISONHEADCRAB_FOOTSTEP ) DECLARE_ANIMEVENT( AE_POISONHEADCRAB_THREAT_SOUND ) AI_END_CUSTOM_NPC() AI_BEGIN_CUSTOM_NPC( npc_headcrab_fast, CFastHeadcrab ) DECLARE_SQUADSLOT( SQUAD_SLOT_ENGAGE1 ) DECLARE_SQUADSLOT( SQUAD_SLOT_ENGAGE2 ) DECLARE_SQUADSLOT( SQUAD_SLOT_ENGAGE3 ) DECLARE_SQUADSLOT( SQUAD_SLOT_ENGAGE4 ) AI_END_CUSTOM_NPC()