diff options
Diffstat (limited to 'game/server/hl2/npc_headcrab.cpp')
| -rw-r--r-- | game/server/hl2/npc_headcrab.cpp | 3991 |
1 files changed, 3991 insertions, 0 deletions
diff --git a/game/server/hl2/npc_headcrab.cpp b/game/server/hl2/npc_headcrab.cpp new file mode 100644 index 0000000..6e7d3e7 --- /dev/null +++ b/game/server/hl2/npc_headcrab.cpp @@ -0,0 +1,3991 @@ +//========= 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<CNPC_Bullseye *>(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() |