Files
HL2Overcharged/game/server/overcharged/npc_snark.cpp
2025-05-21 21:20:08 +03:00

3039 lines
80 KiB
C++

//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Snark remake
//
//=============================================================================//
#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_snark.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 snark_GUTS_GIB_COUNT 1
#define snark_LEGS_GIB_COUNT 3
#define snark_ALL_GIB_COUNT 5
#define snark_RUNMODE_ACCELERATE 1
#define snark_RUNMODE_IDLE 2
#define snark_RUNMODE_DECELERATE 3
#define snark_RUNMODE_FULLSPEED 4
#define snark_RUNMODE_PAUSE 5
#define snark_RUN_MINSPEED 0.5
#define snark_RUN_MAXSPEED 1.0
const float snark_BURROWED_FOV = -1.0f;
const float snark_UNBURROWED_FOV = 0.5f;
#define snark_IGNORE_WORLD_COLLISION_TIME 0.5
const int snark_MIN_JUMP_DIST = 48;
const int snark_MAX_JUMP_DIST = 64; // was 256, cannot jump that far
#define snark_BURROW_POINT_SEARCH_RADIUS 256.0
// Debugging
#define snark_DEBUG_HIDING 1
#define snark_BURN_SOUND_FREQUENCY 10
ConVar g_debug_snark( "g_debug_snark", "0", FCVAR_CHEAT );
ConVar sk_snark_livetime("sk_snark_livetime", "15");
ConVar sk_snark_explode_damage("sk_snark_explode_damage", "15");
ConVar sk_snark_health("sk_snark_health", "0");
ConVar sk_snark_bite_damage("sk_snark_bite_damage", "0");
//------------------------------------
// Spawnflags
//------------------------------------
#define SF_snark_START_HIDDEN (1 << 16)
#define SF_snark_START_HANGING (1 << 17)
//-----------------------------------------------------------------------------
// Think contexts.
//-----------------------------------------------------------------------------
static const char *s_pPitchContext = "PitchContext";
//-----------------------------------------------------------------------------
// Animation events.
//-----------------------------------------------------------------------------
int AE_snark_JUMPATTACK;
int AE_snark_JUMP_TELEGRAPH;
int AE_POISONsnark_FLINCH_HOP;
int AE_POISONsnark_FOOTSTEP;
int AE_POISONsnark_THREAT_SOUND;
int AE_snark_BURROW_IN;
int AE_snark_BURROW_IN_FINISH;
int AE_snark_BURROW_OUT;
int AE_snark_CEILING_DETACH;
int AE_snark_ADDROLL;
int AE_snark_REMOVEROLL;
//-----------------------------------------------------------------------------
// Custom schedules.
//-----------------------------------------------------------------------------
enum
{
SCHED_snark_RANGE_ATTACK1 = LAST_SHARED_SCHEDULE,
SCHED_snark_WAKE_ANGRY,
SCHED_snark_WAKE_ANGRY_NO_DISPLAY,
SCHED_snark_DROWN,
SCHED_snark_FAIL_DROWN,
SCHED_snark_AMBUSH,
SCHED_snark_HOP_RANDOMLY, // get off something you're not supposed to be on.
SCHED_snark_BARNACLED,
SCHED_snark_UNHIDE,
SCHED_snark_HARASS_ENEMY,
SCHED_snark_FALL_TO_GROUND,
SCHED_snark_RUN_TO_BURROW_IN,
SCHED_snark_RUN_TO_SPECIFIC_BURROW,
SCHED_snark_BURROW_IN,
SCHED_snark_BURROW_WAIT,
SCHED_snark_BURROW_OUT,
SCHED_snark_WAIT_FOR_CLEAR_UNBURROW,
SCHED_snark_CRAWL_FROM_CANISTER,
SCHED_FAST_snark_RANGE_ATTACK1,
SCHED_snark_CEILING_WAIT,
SCHED_snark_CEILING_DROP,
};
//=========================================================
// tasks
//=========================================================
enum
{
TASK_snark_HOP_ASIDE = LAST_SHARED_TASK,
TASK_snark_HOP_OFF_NPC,
TASK_snark_DROWN,
TASK_snark_WAIT_FOR_BARNACLE_KILL,
TASK_snark_UNHIDE,
TASK_snark_HARASS_HOP,
TASK_snark_FIND_BURROW_IN_POINT,
TASK_snark_BURROW,
TASK_snark_UNBURROW,
TASK_snark_BURROW_WAIT,
TASK_snark_CHECK_FOR_UNBURROW,
TASK_snark_JUMP_FROM_CANISTER,
TASK_snark_CLIMB_FROM_CANISTER,
TASK_snark_CEILING_WAIT,
TASK_snark_CEILING_POSITION,
TASK_snark_CEILING_DETACH,
TASK_snark_CEILING_FALL,
TASK_snark_CEILING_LAND,
};
//=========================================================
// conditions
//=========================================================
enum
{
COND_snark_IN_WATER = LAST_SHARED_CONDITION,
COND_snark_ILLEGAL_GROUNDENT,
COND_snark_BARNACLED,
COND_snark_UNHIDE,
};
//=========================================================
// private activities
//=========================================================
int ACT_snark_THREAT_DISPLAY;
int ACT_snark_HOP_LEFT;
int ACT_snark_HOP_RIGHT;
int ACT_snark_DROWN;
int ACT_snark_BURROW_IN;
int ACT_snark_BURROW_OUT;
int ACT_snark_BURROW_IDLE;
int ACT_snark_CRAWL_FROM_CANISTER_LEFT;
int ACT_snark_CRAWL_FROM_CANISTER_CENTER;
int ACT_snark_CRAWL_FROM_CANISTER_RIGHT;
int ACT_snark_CEILING_IDLE;
int ACT_snark_CEILING_DETACH;
int ACT_snark_CEILING_FALL;
int ACT_snark_CEILING_LAND;
LINK_ENTITY_TO_CLASS( npc_snark, CNPC_snark );
BEGIN_DATADESC( CNPC_snark )
// m_nGibCount - don't save
DEFINE_FIELD( m_bHidden, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flTimeAlive, FIELD_TIME), // BriJee OVR: Overall live time.
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: Stuff that must happen after NPCInit is called.
//-----------------------------------------------------------------------------
void CNPC_snark::snarkInit()
{
// See if we're supposed to start burrowed
if ( m_bStartBurrowed )
{
SetBurrowed( true );
SetSchedule( SCHED_snark_BURROW_WAIT );
}
if ( GetSpawnFlags() & SF_snark_START_HANGING )
{
SetSchedule( SCHED_snark_CEILING_WAIT );
m_flIlluminatedTime = -1;
}
}
//-----------------------------------------------------------------------------
// The snark will crawl from the cannister, then jump to a burrow point
//-----------------------------------------------------------------------------
void CNPC_snark::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 CNPC_snark::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:
//-----------------------------------------------------------------------------
Activity CNPC_snark::NPC_TranslateActivity(Activity eNewActivity)
{
if (eNewActivity == ACT_WALK)
return ACT_RUN;
return BaseClass::NPC_TranslateActivity(eNewActivity);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_snark::IdleSound(void)
{
//EmitSound("NPC_HeadCrab.Idle");
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_snark::AlertSound(void)
{
EmitSound("Snark.Bounce");
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_snark::PainSound(const CTakeDamageInfo &info)
{
if (IsOnFire() && random->RandomInt(0, snark_BURN_SOUND_FREQUENCY) > 0)
{
// Don't squeak every think when burning.
return;
}
EmitSound("NPC_HeadCrab.Pain");
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_snark::DeathSound(const CTakeDamageInfo &info)
{
EmitSound("Snark.Die");
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_snark::TelegraphSound(void)
{
//FIXME: Need a real one
//EmitSound("NPC_HeadCrab.Alert");
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_snark::AttackSound(void)
{
EmitSound("Snark.Bounce");
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_snark::BiteSound(void)
{
//EmitSound("Snark.Deploy");
float flpitch;
flpitch = 155.0 - 60.0 * ((m_flTimeAlive - gpGlobals->curtime) / 15); //m_flDie
flpitch = Clamp(flpitch, 0.1f, 150.f);
// make bite sound
CPASAttenuationFilter filter(this);
CSoundParameters params;
if (GetParametersForSound("Snark.Deploy", params, NULL))
{
EmitSound_t ep(params);
ep.m_nPitch = (int)flpitch;
EmitSound(filter, entindex(), ep);
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
//void CNPC_snark::PreExplodeSound(const CTakeDamageInfo &info)
//{
//EmitSound("Snark.Squeak");
//}
//-----------------------------------------------------------------------------
// Purpose: Indicates this monster's place in the relationship table.
// Output :
//-----------------------------------------------------------------------------
Class_T CNPC_snark::Classify( void )
{
if( m_bHidden )
{
// Effectively invisible to other AI's while hidden.
return( CLASS_NONE );
}
else
{
return( CLASS_SNARK );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &posSrc -
// Output : Vector
//-----------------------------------------------------------------------------
Vector CNPC_snark::BodyTarget( const Vector &posSrc, bool bNoisy )
{
Vector vecResult;
vecResult = GetAbsOrigin();
vecResult.z += 6;
return vecResult;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
float CNPC_snark::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 CNPC_snark::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_snark_JUMP_FROM_CANISTER)
return 15;
}
return 30;
default:
return 30;
}
return BaseClass::MaxYawSpeed();
}
//-----------------------------------------------------------------------------
// Because the AI code does a tracehull to find the ground under an NPC, snarks
// 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 snark 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 snark.
//-----------------------------------------------------------------------------
#define snark_MAX_LEDGE_HEIGHT 12.0f
bool CNPC_snark::IsFirmlyOnGround()
{
if( !(GetFlags()&FL_ONGROUND) )
return false;
trace_t tr;
UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, snark_MAX_LEDGE_HEIGHT ), MASK_NPCSOLID, this, GetCollisionGroup(), &tr );
return tr.fraction != 1.0;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_snark::MoveOrigin( const Vector &vecDelta )
{
UTIL_SetOrigin( this, GetLocalOrigin() + vecDelta );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : vecPos -
//-----------------------------------------------------------------------------
void CNPC_snark::ThrowAt( const Vector &vecPos )
{
JumpAttack( false, vecPos, true );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : vecPos -
//-----------------------------------------------------------------------------
void CNPC_snark::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 CNPC_snark::Leap( const Vector &vecVel )
{
SetTouch( &CNPC_snark::LeapTouch );
SetCondition( COND_FLOATING_OFF_GROUND );
SetGroundEntity( NULL );
m_flIgnoreWorldCollisionTime = gpGlobals->curtime + snark_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 );
/* add a little angle to the roll when we leap
QAngle angles = GetAbsAngles();
angles.z += (RandomFloat(-1, 1) * 40);
SetAbsAngles( angles ); */
// Think every frame so the player sees the snark where he actually is...
m_bMidJump = true;
SetThink( &CNPC_snark::ThrowThink );
SetNextThink( gpGlobals->curtime );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_snark::ThrowThink( void )
{
if (gpGlobals->curtime > m_flNextNPCThink)
{
NPCThink();
m_flNextNPCThink = gpGlobals->curtime + 0.1;
}
if( GetFlags() & FL_ONGROUND )
{
SetThink( &CNPC_snark::CallNPCThink );
SetNextThink( gpGlobals->curtime + 0.1 );
/* set the roll back to normal
QAngle angles = GetAbsAngles();
angles.z = 0.0f;
SetAbsAngles( angles ); */
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 CNPC_snark::JumpAttack( bool bRandomJump, const Vector &vecPos, bool bThrown )
{
Vector vecJumpVel;
if ( !bRandomJump )
{
float gravity = sv_gravity.GetFloat();
if ( gravity <= 1 )
{
gravity = 1;
}
// How fast does the snark 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 CNPC_snark::HandleAnimEvent( animevent_t *pEvent )
{
if (pEvent->event == AE_snark_ADDROLL )
{
QAngle angles = GetAbsAngles();
angles.z += (RandomFloat(-1, 1) * 8);
SetAbsAngles( angles );
#if 1
return;
#endif
}
if ( pEvent->event == AE_snark_REMOVEROLL )
{
QAngle angles = GetAbsAngles();
angles.z = 0;
SetAbsAngles( angles );
#if 1
return;
#endif
}
if ( pEvent->event == AE_snark_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() );
JumpAttack( false, pEnemy->WorldSpaceCenter() );
}
m_bCommittedToJump = false;
}
else
{
// Jump hop, don't care where.
JumpAttack( true );
}
return;
}
if ( pEvent->event == AE_snark_CEILING_DETACH )
{
SetMoveType( MOVETYPE_STEP );
RemoveFlag( FL_ONGROUND );
RemoveFlag( FL_FLY );
SetAbsVelocity( Vector ( 0, 0, -128 ) );
return;
}
if ( pEvent->event == AE_snark_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_snark_BURROW_IN )
{
//EmitSound( "NPC_snark.BurrowIn" );
CreateDust();
return;
}
if ( pEvent->event == AE_snark_BURROW_IN_FINISH )
{
SetBurrowed( true );
return;
}
if ( pEvent->event == AE_snark_BURROW_OUT )
{
Assert( m_bBurrowed );
if ( m_bBurrowed )
{
//EmitSound( "NPC_snark.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 CNPC_snark::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 = snark_BURROWED_FOV;
SetState( NPC_STATE_IDLE );
SetActivity( (Activity) ACT_snark_BURROW_IDLE );
}
else
{
RemoveEffects( EF_NODRAW );
RemoveFlag( FL_NOTARGET );
m_spawnflags &= ~SF_NPC_GAG;
RemoveSolidFlags( FSOLID_NOT_SOLID );
m_takedamage = DAMAGE_YES;
m_flFieldOfView = snark_UNBURROWED_FOV;
}
m_bBurrowed = bBurrowed;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pTask -
//-----------------------------------------------------------------------------
void CNPC_snark::RunTask( const Task_t *pTask )
{
switch ( pTask->iTask )
{
case TASK_snark_CLIMB_FROM_CANISTER:
AutoMovement( );
if ( IsActivityFinished() )
{
TaskComplete();
}
break;
case TASK_snark_JUMP_FROM_CANISTER:
GetMotor()->UpdateYaw();
if ( FacingIdeal() )
{
TaskComplete();
}
break;
case TASK_snark_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_snark_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_snark_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_snark_HARASS_HOP:
{
if ( IsActivityFinished() )
{
TaskComplete();
m_bMidJump = false;
SetTouch( NULL );
SetThink( &CNPC_snark::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_snark_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_snark_BURROW_WAIT:
{
if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) || HasCondition( COND_CAN_RANGE_ATTACK2 ) )
{
TaskComplete();
}
break;
}
case TASK_snark_CEILING_WAIT:
{
if ( DarknessLightSourceWithinRadius( this, DARKNESS_LIGHTSOURCE_SIZE ) )
{
DropFromCeiling();
}
if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) || HasCondition( COND_CAN_RANGE_ATTACK2 ) )
{
TaskComplete();
}
break;
}
case TASK_snark_CEILING_DETACH:
{
if ( IsActivityFinished() )
{
ClearCondition( COND_CAN_RANGE_ATTACK1 );
RemoveFlag(FL_FLY);
TaskComplete();
}
}
break;
case TASK_snark_CEILING_FALL:
{
Vector vecPrPos;
trace_t tr;
//Figure out where the snark 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_snark_CEILING_LAND:
{
if ( IsActivityFinished() )
{
RemoveSolidFlags( FSOLID_NOT_SOLID ); //double-dog verify that we're solid.
TaskComplete();
m_bHangingFromCeiling = false;
}
}
break;
default:
{
if (gpGlobals->curtime > m_flTimeAlive) //=========== BriJee OVR: Live time
{
OnTakeDamage(CTakeDamageInfo(this, this, m_iHealth * 2, DMG_DROWN));
}
BaseClass::RunTask( pTask );
}
}
}
//-----------------------------------------------------------------------------
// Before jumping, snarks usually use SetOrigin() to lift themselves off the
// ground. If the snark doesn't have the clearance to so, they'll be stuck
// in the world. So this function makes sure there's headroom first.
//-----------------------------------------------------------------------------
bool CNPC_snark::HasHeadroom()
{
trace_t tr;
UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, 1 ), MASK_NPCSOLID, this, GetCollisionGroup(), &tr );
return (tr.fraction == 1.0);
}
//-----------------------------------------------------------------------------
// Purpose: LeapTouch - this is the snark's touch function when it is in the air.
// Input : *pOther -
//-----------------------------------------------------------------------------
void CNPC_snark::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 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 )
{
// snarks 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 ( &CNPC_snark::CallNPCThink );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_snark::CalcDamageInfo( CTakeDamageInfo *pInfo )
{
pInfo->Set(this, this, sk_snark_bite_damage.GetFloat(), DMG_SLASH);
CalculateMeleeDamageForce( pInfo, GetAbsVelocity(), GetAbsOrigin() );
return pInfo->GetDamage();
}
//-----------------------------------------------------------------------------
// Purpose: Deal the damage from the snark's touch attack.
//-----------------------------------------------------------------------------
void CNPC_snark::TouchDamage( CBaseEntity *pOther )
{
CTakeDamageInfo info;
CalcDamageInfo( &info );
pOther->TakeDamage( info );
}
//---------------------------------------------------------
//---------------------------------------------------------
void CNPC_snark::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_snark_UNHIDE );
if ( g_debug_snark.GetInt() == snark_DEBUG_HIDING )
{
NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0,255,0, true, 1.0 );
}
}
else if ( g_debug_snark.GetInt() == snark_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_snark_IN_WATER );
}
// See if I've landed on an NPC or player or something else illegal
ClearCondition( COND_snark_ILLEGAL_GROUNDENT );
CBaseEntity *ground = GetGroundEntity();
if( (GetFlags() & FL_ONGROUND) && ground && !ground->IsWorld() )
{
if ( IsHangingFromCeiling() == false )
{
if( ( ground->IsNPC() || ground->IsPlayer() ) )
{
SetCondition( COND_snark_ILLEGAL_GROUNDENT );
}
else if( ground->VPhysicsGetObject() && (ground->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD) )
{
SetCondition( COND_snark_ILLEGAL_GROUNDENT );
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_snark::PrescheduleThink( void )
{
BaseClass::PrescheduleThink();
//DevMsg("Snark prethink \n");
if (gpGlobals->curtime > m_flTimeToForgetOwner)
{
if (GetOwnerEntity())
SetOwnerEntity(NULL);
}
//if (m_flTimeAlive == m_flTimeAlive - 3)
if (gpGlobals->curtime > m_flTimeAlive - 0.4)
{
DevMsg("Snark last sec to explode? \n");
if (!bDoPreExplodeOnce)
{
bDoPreExplodeOnce = true;
//CTakeDamageInfo infoExp;
//PreExplodeSound(infoExp);
JumpAttack(true);
CPASAttenuationFilter filter(this);
EmitSound(filter, entindex(), "Snark.Squeak");
DevMsg("Snark do jump explode \n");
}
}
// 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_snark_BURROW_IDLE ) &&
( eActivity != ACT_snark_BURROW_OUT ) &&
( eActivity != ACT_snark_BURROW_IN) )
{
DevMsg( "snark failed to unburrow properly!\n" );
Assert( 0 );
SetBurrowed( false );
}
}
//-----------------------------------------------------------------------------
// Eliminates roll + pitch from the snark
//-----------------------------------------------------------------------------
#define snark_ROLL_ELIMINATION_TIME 0.3f
#define snark_PITCH_ELIMINATION_TIME 0.3f
//-----------------------------------------------------------------------------
// Eliminates roll + pitch potentially in the snark at canister jump time
//-----------------------------------------------------------------------------
void CNPC_snark::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 / snark_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 / snark_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( &CNPC_snark::EliminateRollAndPitch, gpGlobals->curtime + TICK_INTERVAL, s_pPitchContext );
}
//-----------------------------------------------------------------------------
// Begins the climb from the canister
//-----------------------------------------------------------------------------
void CNPC_snark::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, snark_BURROW_POINT_SEARCH_RADIUS, &vecSearchCenter );
if( !pHint )
{
// 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 snarks 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_snark_CRAWL_FROM_CANISTER_LEFT;
break;
default:
case 1:
act = (Activity)ACT_snark_CRAWL_FROM_CANISTER_CENTER;
break;
case 2:
act = (Activity)ACT_snark_CRAWL_FROM_CANISTER_RIGHT;
break;
}
SetIdealActivity( act );
}
//-----------------------------------------------------------------------------
// Jumps from the canister
//-----------------------------------------------------------------------------
#define snark_ATTACK_PLAYER_FROM_CANISTER_DIST 250.0f
#define snark_ATTACK_PLAYER_FROM_CANISTER_COSANGLE 0.866f
void CNPC_snark::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 );
AddEffects( EF_NOINTERP );
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 < snark_ATTACK_PLAYER_FROM_CANISTER_DIST ) &&
( DotProduct( vecDirToEnemy, vecForward ) >= snark_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 snark_ILLUMINATED_TIME 0.15f
void CNPC_snark::DropFromCeiling( void )
{
if ( HL2GameRules()->IsAlyxInDarknessMode() )
{
if ( IsHangingFromCeiling() )
{
if ( m_flIlluminatedTime == -1 )
{
m_flIlluminatedTime = gpGlobals->curtime + snark_ILLUMINATED_TIME;
return;
}
if ( m_flIlluminatedTime <= gpGlobals->curtime )
{
if ( IsCurSchedule( SCHED_snark_CEILING_DROP ) == false )
{
SetSchedule( SCHED_snark_CEILING_DROP );
CBaseEntity *pPlayer = AI_GetSinglePlayer();
if ( pPlayer )
{
SetEnemy( pPlayer ); //Is this a bad thing to do?
UpdateEnemyMemory( pPlayer, pPlayer->GetAbsOrigin());
}
}
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Player has illuminated this NPC with the flashlight
//-----------------------------------------------------------------------------
void CNPC_snark::PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot )
{
if ( flDot < 0.97387f )
return;
DropFromCeiling();
}
bool CNPC_snark::CanBeAnEnemyOf( CBaseEntity *pEnemy )
{
if ( IsHangingFromCeiling() )
return false;
return BaseClass::CanBeAnEnemyOf( pEnemy );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : pTask -
//-----------------------------------------------------------------------------
void CNPC_snark::StartTask( const Task_t *pTask )
{
switch ( pTask->iTask )
{
case TASK_snark_WAIT_FOR_BARNACLE_KILL:
break;
case TASK_snark_BURROW_WAIT:
break;
case TASK_snark_CLIMB_FROM_CANISTER:
BeginClimbFromCanister();
break;
case TASK_snark_JUMP_FROM_CANISTER:
JumpFromCanister();
break;
case TASK_snark_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_snark_START_HANGING );
SetAbsOrigin( tr.endpos );
TaskComplete();
}
break;
case TASK_snark_CEILING_WAIT:
break;
case TASK_snark_CEILING_DETACH:
{
SetIdealActivity( (Activity)ACT_snark_CEILING_DETACH );
}
break;
case TASK_snark_CEILING_FALL:
{
SetIdealActivity( (Activity)ACT_snark_CEILING_FALL );
}
break;
case TASK_snark_CEILING_LAND:
{
SetIdealActivity( (Activity)ACT_snark_CEILING_LAND );
}
break;
case TASK_snark_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_snark_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 snark 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_snark_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_snark_UNHIDE:
{
m_bHidden = false;
RemoveSolidFlags( FSOLID_NOT_SOLID );
RemoveEffects( EF_NODRAW );
TaskComplete();
break;
}
case TASK_snark_CHECK_FOR_UNBURROW:
{
if ( ValidBurrowPoint( GetAbsOrigin() ) )
{
m_spawnflags &= ~SF_NPC_GAG;
RemoveSolidFlags( FSOLID_NOT_SOLID );
TaskComplete();
}
break;
}
case TASK_snark_FIND_BURROW_IN_POINT:
{
if ( FindBurrow( GetAbsOrigin(), pTask->flTaskData, true ) == false )
{
TaskFail( "TASK_snark_FIND_BURROW_IN_POINT: Unable to find burrow in position\n" );
}
else
{
TaskComplete();
}
break;
}
case TASK_snark_BURROW:
{
Burrow();
TaskComplete();
break;
}
case TASK_snark_UNBURROW:
{
Unburrow();
TaskComplete();
break;
}
default:
{
BaseClass::StartTask( pTask );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: For innate melee attack
// Input :
// Output :
//-----------------------------------------------------------------------------
float CNPC_snark::InnateRange1MinRange( void )
{
return snark_MIN_JUMP_DIST;
}
float CNPC_snark::InnateRange1MaxRange( void )
{
return snark_MAX_JUMP_DIST;
}
int CNPC_snark::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 snarks 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 snarks only, rather than just removing it.(sjb)
if ( flDist < snark_MIN_JUMP_DIST )
return COND_TOO_CLOSE_TO_ATTACK;
if ( flDist > snark_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 snark 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 snark specific gibs
// Output :
//------------------------------------------------------------------------------
bool CNPC_snark::CorpseGib( const CTakeDamageInfo &info )
{
EmitSound( "NPC_HeadCrab.Gib" );
return false;
}
//------------------------------------------------------------------------------
// Purpose:
// Input :
//------------------------------------------------------------------------------
void CNPC_snark::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 CNPC_snark::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 snarks live longer while on fire.
info.ScaleDamage( 0.25 );
#define snark_SCORCH_RATE 5
#define snark_SCORCH_FLOOR 30
if( IsOnFire() )
{
Scorch( snark_SCORCH_RATE, snark_SCORCH_FLOOR );
if( m_iHealth <= 1 && (entindex() % 2) )
{
// Some snarks leap at you with their dying breath
if( !IsCurSchedule( SCHED_snark_RANGE_ATTACK1 ) && !IsRunningDynamicInteraction() )
{
SetSchedule( SCHED_snark_RANGE_ATTACK1 );
}
}
}
Ignite( 30 );
}
return CAI_BaseNPC::OnTakeDamage_Alive( info );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_snark::ClampRagdollForce( const Vector &vecForceIn, Vector *vecForceOut )
{
// Assumes the snark mass is 5kg (100 feet per second)
float MAX_snark_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_snark_RAGDOLL_SPEED )
{
// Don't let the ragdoll go as fast as it was going to.
vecClampedForce *= MAX_snark_RAGDOLL_SPEED;
*vecForceOut = vecClampedForce;
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_snark::Event_Killed( const CTakeDamageInfo &info )
{
// Create a little decal underneath the snark
/* 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" );
}*/
// BJ: ADD BOOM HERE
DispatchParticleEffect("GrubSquashBlood", GetAbsOrigin(), QAngle(0, 0, 0));
if( info.GetDamageType() & ( DMG_BULLET | DMG_BLAST | DMG_BUCKSHOT ) )
{
//SetModel( "models/gibs/snarkgib01.mdl" );
trace_t tr;
UTIL_TraceHull(GetAbsOrigin() + Vector(0, 0, 1), GetAbsOrigin() - Vector(0, 0, 64), Vector(-5, -5, -5), Vector(5, 5, 5), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
//UTIL_TraceLine( GetAbsOrigin()+Vector(0,0,1), GetAbsOrigin()-Vector(0,0,64), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
UTIL_DecalTrace( &tr, "YellowBlood" );
}
CTakeDamageInfo infoX;
DeathSound(infoX);
//MikeD: Warning, potencial recursive call (don't forget about ignore this entity or this classify)
RadiusDamage(CTakeDamageInfo(this, NULL, sk_snark_explode_damage.GetFloat(), DMG_BLAST), GetAbsOrigin(), 64, CLASS_SNARK, this); // BJ: HEADCRAB class change later
UTIL_Remove(this);
//BaseClass::Event_Killed( info );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : Type -
//-----------------------------------------------------------------------------
int CNPC_snark::TranslateSchedule( int scheduleType )
{
switch( scheduleType )
{
case SCHED_FALL_TO_GROUND:
return SCHED_snark_FALL_TO_GROUND;
case SCHED_WAKE_ANGRY:
{
if ( HaveSequenceForActivity((Activity)ACT_snark_THREAT_DISPLAY) )
return SCHED_snark_WAKE_ANGRY;
else
return SCHED_snark_WAKE_ANGRY_NO_DISPLAY;
}
case SCHED_RANGE_ATTACK1:
return SCHED_snark_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_snark_HARASS_ENEMY;
}
break;
}
return BaseClass::TranslateSchedule( scheduleType );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CNPC_snark::SelectSchedule( void )
{
if ( m_bCrawlFromCanister )
{
m_bCrawlFromCanister = false;
return SCHED_snark_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_snark_UNHIDE ) )
{
// We've decided to unhide
return SCHED_snark_UNHIDE;
}
return m_bBurrowed ? SCHED_snark_BURROW_WAIT : SCHED_IDLE_STAND;
}
if ( GetSpawnFlags() & SF_snark_START_HANGING && IsHangingFromCeiling() == false )
{
return SCHED_snark_CEILING_WAIT;
}
if ( IsHangingFromCeiling() )
{
if ( HL2GameRules()->IsAlyxInDarknessMode() == false && ( HasCondition( COND_CAN_RANGE_ATTACK1 ) || HasCondition( COND_NEW_ENEMY ) ) )
return SCHED_snark_CEILING_DROP;
if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
return SCHED_snark_CEILING_DROP;
return SCHED_snark_CEILING_WAIT;
}
if ( m_bBurrowed )
{
if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
return SCHED_snark_BURROW_OUT;
return SCHED_snark_BURROW_WAIT;
}
if( HasCondition( COND_snark_IN_WATER ) )
{
// No matter what, drown in water
return SCHED_snark_DROWN;
}
if( HasCondition( COND_snark_ILLEGAL_GROUNDENT ) )
{
// You're on an NPC's head. Get off.
return SCHED_snark_HOP_RANDOMLY;
}
if ( HasCondition( COND_snark_BARNACLED ) )
{
// Caught by a barnacle!
return SCHED_snark_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() > snark_MAX_JUMP_DIST ) )
{
return SCHED_snark_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 CNPC_snark::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_snark_HOP_RANDOMLY;
}
}
return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &info -
// &vecDir -
// *ptr -
//-----------------------------------------------------------------------------
void CNPC_snark::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 CNPC_snark::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();
if( GetHealth() > flFlameLifetime )
{
// Add some burn time to very healthy snarks to fix a bug where
// black snarks would sometimes spontaneously extinguish (and survive)
flFlameLifetime += 10.0f;
}
BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner );
if( !bWasOnFire )
{
if ( HL2GameRules()->IsAlyxInDarknessMode() == true )
{
GetEffectEntity()->AddEffects( EF_DIMLIGHT );
}
// For the poison snark, 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 CNPC_snark::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 = snark_LEGS_GIB_COUNT;
OnTakeDamage ( CTakeDamageInfo( sourceEnt, sourceEnt, m_iHealth, DMG_CRUSH|DMG_ALWAYSGIB ) );
// Create dead snark in its place
CNPC_snark *pEntity = (CNPC_snark*) CreateEntityByName( "npc_snark" );
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 CNPC_snark::FValidateHintType( CAI_Hint *pHint )
{
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &origin -
//-----------------------------------------------------------------------------
void CNPC_snark::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 CNPC_snark::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 CNPC_snark::GrabHintNode( CAI_Hint *pHint )
{
// Free up the node for use
ClearHintNode();
if ( pHint )
{
SetHintNode( pHint );
pHint->Lock( this );
}
}
//-----------------------------------------------------------------------------
// Purpose: Finds a point where the snark can burrow underground.
// Input : distance - radius to search for burrow spot in
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_snark::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 CNPC_snark::Burrow( void )
{
// Stop us from taking damage and being solid
m_spawnflags |= SF_NPC_GAG;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_snark::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 snark to unburrow as soon the space is clear.
//-----------------------------------------------------------------------------
void CNPC_snark::InputUnburrow( inputdata_t &inputdata )
{
if ( IsAlive() == false )
return;
SetSchedule( SCHED_snark_WAIT_FOR_CLEAR_UNBURROW );
}
//-----------------------------------------------------------------------------
// Purpose: Tells the snark to run to a nearby burrow point and burrow.
//-----------------------------------------------------------------------------
void CNPC_snark::InputBurrow( inputdata_t &inputdata )
{
if ( IsAlive() == false )
return;
SetSchedule( SCHED_snark_RUN_TO_BURROW_IN );
}
//-----------------------------------------------------------------------------
// Purpose: Tells the snark to burrow right where he is.
//-----------------------------------------------------------------------------
void CNPC_snark::InputBurrowImmediate( inputdata_t &inputdata )
{
if ( IsAlive() == false )
return;
SetSchedule( SCHED_snark_BURROW_IN );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_snark::InputStartHangingFromCeiling( inputdata_t &inputdata )
{
if ( IsAlive() == false )
return;
SetSchedule( SCHED_snark_CEILING_WAIT );
m_flIlluminatedTime = -1;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_snark::InputDropFromCeiling( inputdata_t &inputdata )
{
if ( IsAlive() == false )
return;
if ( IsHangingFromCeiling() == false )
return;
SetSchedule( SCHED_snark_CEILING_DROP );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_snark::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() );
if ( placeDecal )
{
UTIL_DecalTrace( &tr, "snark.Unburrow" );
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_snark::Precache(void)
{
PrecacheModel( "models/snark_xen.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_snark.BurrowIn" );
//PrecacheScriptSound( "NPC_snark.BurrowOut" );
PrecacheScriptSound("Snark.Die");
PrecacheScriptSound("Snark.Squeak");
PrecacheScriptSound("Snark.Deploy");
PrecacheScriptSound("Snark.Bounce");
PrecacheParticleSystem("GrubSquashBlood");
BaseClass::Precache();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_snark::Spawn(void)
{
Precache();
SetModel( "models/snark_xen.mdl" );
m_flTimeAlive = gpGlobals->curtime + sk_snark_livetime.GetFloat();
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 = snark_ALL_GIB_COUNT;
// Are we starting hidden?
if ( m_spawnflags & SF_snark_START_HIDDEN )
{
m_bHidden = true;
AddSolidFlags( FSOLID_NOT_SOLID );
SetRenderColorA( 0 );
m_nRenderMode = kRenderTransTexture;
AddEffects( EF_NODRAW );
}
else
{
m_bHidden = false;
}
m_bSpawnNoRagdoll = true;
CapabilitiesClear();
CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 );
CapabilitiesAdd( bits_CAP_SQUAD );
// snarks get to cheat for 5 seconds (sjb)
GetEnemies()->SetFreeKnowledgeDuration( 5.0 );
m_bHangingFromCeiling = false;
m_flIlluminatedTime = -1;
m_iHealth = sk_snark_health.GetFloat();
m_flBurrowTime = 0.0f;
m_flTimeToForgetOwner = gpGlobals->curtime + 3.f;
m_bCrawlFromCanister = false;
m_bMidJump = false;
NPCInit();
snarkInit();
}
//-----------------------------------------------------------------------------
// Purpose: Allows for modification of the interrupt mask for the current schedule.
// In the most cases the base implementation should be called first.
//-----------------------------------------------------------------------------
void CNPC_snark::BuildScheduleTestBits( void )
{
if ( !IsCurSchedule(SCHED_snark_DROWN) )
{
// Interrupt any schedule unless already drowning.
SetCustomInterruptCondition( COND_snark_IN_WATER );
}
else
{
// Don't stop drowning just because you're in water!
ClearCustomInterruptCondition( COND_snark_IN_WATER );
}
if( !IsCurSchedule(SCHED_snark_HOP_RANDOMLY) )
{
SetCustomInterruptCondition( COND_snark_ILLEGAL_GROUNDENT );
}
else
{
ClearCustomInterruptCondition( COND_snark_ILLEGAL_GROUNDENT );
}
}
//-----------------------------------------------------------------------------
//
// Schedules
//
//-----------------------------------------------------------------------------
AI_BEGIN_CUSTOM_NPC( npc_snark, CNPC_snark )
DECLARE_TASK( TASK_snark_HOP_ASIDE )
DECLARE_TASK( TASK_snark_DROWN )
DECLARE_TASK( TASK_snark_HOP_OFF_NPC )
DECLARE_TASK( TASK_snark_WAIT_FOR_BARNACLE_KILL )
DECLARE_TASK( TASK_snark_UNHIDE )
DECLARE_TASK( TASK_snark_HARASS_HOP )
DECLARE_TASK( TASK_snark_BURROW )
DECLARE_TASK( TASK_snark_UNBURROW )
DECLARE_TASK( TASK_snark_FIND_BURROW_IN_POINT )
DECLARE_TASK( TASK_snark_BURROW_WAIT )
DECLARE_TASK( TASK_snark_CHECK_FOR_UNBURROW )
DECLARE_TASK( TASK_snark_JUMP_FROM_CANISTER )
DECLARE_TASK( TASK_snark_CLIMB_FROM_CANISTER )
DECLARE_TASK( TASK_snark_CEILING_POSITION )
DECLARE_TASK( TASK_snark_CEILING_WAIT )
DECLARE_TASK( TASK_snark_CEILING_DETACH )
DECLARE_TASK( TASK_snark_CEILING_FALL )
DECLARE_TASK( TASK_snark_CEILING_LAND )
DECLARE_ACTIVITY( ACT_snark_THREAT_DISPLAY )
DECLARE_ACTIVITY( ACT_snark_HOP_LEFT )
DECLARE_ACTIVITY( ACT_snark_HOP_RIGHT )
DECLARE_ACTIVITY( ACT_snark_DROWN )
DECLARE_ACTIVITY( ACT_snark_BURROW_IN )
DECLARE_ACTIVITY( ACT_snark_BURROW_OUT )
DECLARE_ACTIVITY( ACT_snark_BURROW_IDLE )
DECLARE_ACTIVITY( ACT_snark_CRAWL_FROM_CANISTER_LEFT )
DECLARE_ACTIVITY( ACT_snark_CRAWL_FROM_CANISTER_CENTER )
DECLARE_ACTIVITY( ACT_snark_CRAWL_FROM_CANISTER_RIGHT )
DECLARE_ACTIVITY( ACT_snark_CEILING_FALL )
DECLARE_ACTIVITY( ACT_snark_CEILING_IDLE )
DECLARE_ACTIVITY( ACT_snark_CEILING_DETACH )
DECLARE_ACTIVITY( ACT_snark_CEILING_LAND )
DECLARE_CONDITION( COND_snark_IN_WATER )
DECLARE_CONDITION( COND_snark_ILLEGAL_GROUNDENT )
DECLARE_CONDITION( COND_snark_BARNACLED )
DECLARE_CONDITION( COND_snark_UNHIDE )
//Adrian: events go here
DECLARE_ANIMEVENT( AE_snark_JUMPATTACK )
DECLARE_ANIMEVENT( AE_snark_JUMP_TELEGRAPH )
DECLARE_ANIMEVENT( AE_snark_BURROW_IN )
DECLARE_ANIMEVENT( AE_snark_BURROW_IN_FINISH )
DECLARE_ANIMEVENT( AE_snark_BURROW_OUT )
DECLARE_ANIMEVENT( AE_snark_CEILING_DETACH )
DECLARE_ANIMEVENT( AE_snark_ADDROLL )
DECLARE_ANIMEVENT( AE_snark_REMOVEROLL )
//=========================================================
// > SCHED_snark_RANGE_ATTACK1
//=========================================================
DEFINE_SCHEDULE
(
SCHED_snark_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_snark_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_snark_THREAT_DISPLAY"
""
" Interrupts"
)
//=========================================================
//
//=========================================================
DEFINE_SCHEDULE
(
SCHED_snark_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_snark_RANGE_ATTACK1
//=========================================================
DEFINE_SCHEDULE
(
SCHED_FAST_snark_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_snark_DROWN,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_snark_FAIL_DROWN"
" TASK_SET_ACTIVITY ACTIVITY:ACT_snark_DROWN"
" TASK_snark_DROWN 0"
""
" Interrupts"
)
DEFINE_SCHEDULE
(
SCHED_snark_FAIL_DROWN,
" Tasks"
" TASK_snark_DROWN 0"
""
" Interrupts"
)
//=========================================================
// snark lurks in place and waits for a chance to jump on
// some unfortunate soul.
//=========================================================
DEFINE_SCHEDULE
(
SCHED_snark_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"
)
//=========================================================
// snark has landed atop another NPC or has landed on
// a ledge. Get down!
//=========================================================
DEFINE_SCHEDULE
(
SCHED_snark_HOP_RANDOMLY,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_snark_HOP_OFF_NPC 0"
" Interrupts"
)
//=========================================================
// snark is in the clutches of a barnacle
//=========================================================
DEFINE_SCHEDULE
(
SCHED_snark_BARNACLED,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_SET_ACTIVITY ACTIVITY:ACT_snark_DROWN"
" TASK_snark_WAIT_FOR_BARNACLE_KILL 0"
" Interrupts"
)
//=========================================================
// snark is unhiding
//=========================================================
DEFINE_SCHEDULE
(
SCHED_snark_UNHIDE,
" Tasks"
" TASK_snark_UNHIDE 0"
" Interrupts"
)
DEFINE_SCHEDULE
(
SCHED_snark_HARASS_ENEMY,
" Tasks"
" TASK_FACE_ENEMY 0"
" TASK_snark_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_snark_FALL_TO_GROUND,
" Tasks"
" TASK_SET_ACTIVITY ACTIVITY:ACT_snark_DROWN"
" TASK_FALL_TO_GROUND 0"
""
" Interrupts"
)
DEFINE_SCHEDULE
(
SCHED_snark_CRAWL_FROM_CANISTER,
" Tasks"
" TASK_snark_CLIMB_FROM_CANISTER 0"
" TASK_snark_JUMP_FROM_CANISTER 0"
""
" Interrupts"
)
//==================================================
// Burrow In
//==================================================
DEFINE_SCHEDULE
(
SCHED_snark_BURROW_IN,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
" TASK_snark_BURROW 0"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_snark_BURROW_IN"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_snark_BURROW_IDLE"
" TASK_SET_SCHEDULE SCHEDULE:SCHED_snark_BURROW_WAIT"
""
" Interrupts"
" COND_TASK_FAILED"
)
//==================================================
// Run to a nearby burrow hint and burrow there
//==================================================
DEFINE_SCHEDULE
(
SCHED_snark_RUN_TO_BURROW_IN,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
" TASK_snark_FIND_BURROW_IN_POINT 512"
" TASK_SET_TOLERANCE_DISTANCE 8"
" TASK_RUN_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
" TASK_SET_SCHEDULE SCHEDULE:SCHED_snark_BURROW_IN"
""
" Interrupts"
" COND_TASK_FAILED"
" COND_GIVE_WAY"
" COND_CAN_RANGE_ATTACK1"
)
//==================================================
// Run to m_pHintNode and burrow there
//==================================================
DEFINE_SCHEDULE
(
SCHED_snark_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_snark_BURROW_IN"
""
" Interrupts"
" COND_TASK_FAILED"
" COND_GIVE_WAY"
)
//==================================================
// Wait until we can unburrow and attack something
//==================================================
DEFINE_SCHEDULE
(
SCHED_snark_BURROW_WAIT,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_snark_BURROW_WAIT"
" TASK_snark_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 snark actually acquires
// new enemies while burrowed. (look in ai_basenpc.cpp for "DO NOT mess")
" COND_CAN_RANGE_ATTACK1"
)
//==================================================
// Burrow Out
//==================================================
DEFINE_SCHEDULE
(
SCHED_snark_BURROW_OUT,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_snark_BURROW_WAIT"
" TASK_snark_UNBURROW 0"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_snark_BURROW_OUT"
""
" Interrupts"
" COND_TASK_FAILED"
)
//==================================================
// Wait for it to be clear for unburrowing
//==================================================
DEFINE_SCHEDULE
(
SCHED_snark_WAIT_FOR_CLEAR_UNBURROW,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_snark_BURROW_WAIT"
" TASK_snark_CHECK_FOR_UNBURROW 1"
" TASK_SET_SCHEDULE SCHEDULE:SCHED_snark_BURROW_OUT"
""
" Interrupts"
" COND_TASK_FAILED"
)
//==================================================
// Wait until we can drop.
//==================================================
DEFINE_SCHEDULE
(
SCHED_snark_CEILING_WAIT,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_snark_CEILING_DROP"
" TASK_SET_ACTIVITY ACTIVITY:ACT_snark_CEILING_IDLE"
" TASK_snark_CEILING_POSITION 0"
" TASK_snark_CEILING_WAIT 1"
""
" Interrupts"
" COND_TASK_FAILED"
" COND_NEW_ENEMY"
" COND_CAN_RANGE_ATTACK1"
)
//==================================================
// Deatch from ceiling.
//==================================================
DEFINE_SCHEDULE
(
SCHED_snark_CEILING_DROP,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_snark_CEILING_WAIT"
" TASK_snark_CEILING_DETACH 0"
" TASK_snark_CEILING_FALL 0"
" TASK_snark_CEILING_LAND 0"
""
" Interrupts"
" COND_TASK_FAILED"
)
AI_END_CUSTOM_NPC()
//-----------------------------------------------------------------------------