//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: barnacle - stationary ceiling mounted 'fishing' monster
//
// $Workfile: $
// $Date: $
// $NoKeywords: $
//=============================================================================//
# include "cbase.h"
# include "hl1_npc_barnacle.h"
# include "npcevent.h"
# include "gib.h"
# include "ai_default.h"
# include "activitylist.h"
# include "hl2_player.h"
# include "vstdlib/random.h"
# include "physics_saverestore.h"
# include "vcollide_parse.h"
# include "engine/IEngineSound.h"
ConVar sk_barnacle_health ( " sk_barnacle_health " , " 25 " ) ;
//-----------------------------------------------------------------------------
// Private activities.
//-----------------------------------------------------------------------------
static int ACT_EAT = 0 ;
//-----------------------------------------------------------------------------
// Interactions
//-----------------------------------------------------------------------------
int g_interactionBarnacleVictimDangle = 0 ;
int g_interactionBarnacleVictimReleased = 0 ;
int g_interactionBarnacleVictimGrab = 0 ;
LINK_ENTITY_TO_CLASS ( monster_barnacle , CNPC_Barnacle ) ;
IMPLEMENT_CUSTOM_AI ( monster_barnacle , CNPC_Barnacle ) ;
//-----------------------------------------------------------------------------
// Purpose: Initialize the custom schedules
// Input :
// Output :
//-----------------------------------------------------------------------------
void CNPC_Barnacle : : InitCustomSchedules ( void )
{
INIT_CUSTOM_AI ( CNPC_Barnacle ) ;
ADD_CUSTOM_ACTIVITY ( CNPC_Barnacle , ACT_EAT ) ;
g_interactionBarnacleVictimDangle = CBaseCombatCharacter : : GetInteractionID ( ) ;
g_interactionBarnacleVictimReleased = CBaseCombatCharacter : : GetInteractionID ( ) ;
g_interactionBarnacleVictimGrab = CBaseCombatCharacter : : GetInteractionID ( ) ;
}
BEGIN_DATADESC ( CNPC_Barnacle )
DEFINE_FIELD ( m_flAltitude , FIELD_FLOAT ) ,
DEFINE_FIELD ( m_flKillVictimTime , FIELD_TIME ) ,
DEFINE_FIELD ( m_cGibs , FIELD_INTEGER ) , // barnacle loads up on gibs each time it kills something.
DEFINE_FIELD ( m_fLiftingPrey , FIELD_BOOLEAN ) ,
DEFINE_FIELD ( m_flTongueAdj , FIELD_FLOAT ) ,
DEFINE_FIELD ( m_flIgnoreTouchesUntil , FIELD_TIME ) ,
// Function pointers
DEFINE_THINKFUNC ( BarnacleThink ) ,
DEFINE_THINKFUNC ( WaitTillDead ) ,
END_DATADESC ( )
//=========================================================
// Classify - indicates this monster's place in the
// relationship table.
//=========================================================
Class_T CNPC_Barnacle : : Classify ( void )
{
return CLASS_ALIEN_MONSTER ;
}
//=========================================================
// HandleAnimEvent - catches the monster-specific messages
// that occur when tagged animation frames are played.
//
// Returns number of events handled, 0 if none.
//=========================================================
void CNPC_Barnacle : : HandleAnimEvent ( animevent_t * pEvent )
{
switch ( pEvent - > event )
{
case BARNACLE_AE_PUKEGIB :
CGib : : SpawnRandomGibs ( this , 1 , GIB_HUMAN ) ;
break ;
default :
BaseClass : : HandleAnimEvent ( pEvent ) ;
break ;
}
}
//=========================================================
// Spawn
//=========================================================
void CNPC_Barnacle : : Spawn ( )
{
Precache ( ) ;
SetModel ( " models/barnacle.mdl " ) ;
UTIL_SetSize ( this , Vector ( - 16 , - 16 , - 32 ) , Vector ( 16 , 16 , 0 ) ) ;
SetSolid ( SOLID_BBOX ) ;
AddSolidFlags ( FSOLID_NOT_STANDABLE ) ;
SetMoveType ( MOVETYPE_NONE ) ;
SetBloodColor ( BLOOD_COLOR_GREEN ) ;
m_iHealth = sk_barnacle_health . GetFloat ( ) ;
m_flFieldOfView = 0.5 ; // indicates the width of this monster's forward view cone ( as a dotproduct result )
m_NPCState = NPC_STATE_NONE ;
m_flKillVictimTime = 0 ;
m_cGibs = 0 ;
m_fLiftingPrey = FALSE ;
m_takedamage = DAMAGE_YES ;
InitBoneControllers ( ) ;
InitTonguePosition ( ) ;
// set eye position
SetDefaultEyeOffset ( ) ;
SetActivity ( ACT_IDLE ) ;
SetThink ( & CNPC_Barnacle : : BarnacleThink ) ;
SetNextThink ( gpGlobals - > curtime + 0.5f ) ;
//Do not have a shadow
AddEffects ( EF_NOSHADOW ) ;
m_flIgnoreTouchesUntil = gpGlobals - > curtime ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
int CNPC_Barnacle : : OnTakeDamage_Alive ( const CTakeDamageInfo & inputInfo )
{
CTakeDamageInfo info = inputInfo ;
if ( info . GetDamageType ( ) & DMG_CLUB )
{
info . SetDamage ( m_iHealth ) ;
}
return BaseClass : : OnTakeDamage_Alive ( info ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Initialize tongue position when first spawned
// Input :
// Output :
//-----------------------------------------------------------------------------
void CNPC_Barnacle : : InitTonguePosition ( void )
{
CBaseEntity * pTouchEnt ;
float flLength ;
pTouchEnt = TongueTouchEnt ( & flLength ) ;
m_flAltitude = flLength ;
Vector origin ;
QAngle angle ;
GetAttachment ( " TongueEnd " , origin , angle ) ;
m_flTongueAdj = origin . z - GetAbsOrigin ( ) . z ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Barnacle : : BarnacleThink ( void )
{
CBaseEntity * pTouchEnt ;
float flLength ;
SetNextThink ( gpGlobals - > curtime + 0.1f ) ;
if ( CAI_BaseNPC : : m_nDebugBits & bits_debugDisableAI )
{
// AI Disabled, don't do anything
}
else if ( GetEnemy ( ) ! = NULL )
{
// barnacle has prey.
if ( ! GetEnemy ( ) - > IsAlive ( ) )
{
// someone (maybe even the barnacle) killed the prey. Reset barnacle.
m_fLiftingPrey = FALSE ; // indicate that we're not lifting prey.
SetEnemy ( NULL ) ;
return ;
}
CBaseCombatCharacter * pVictim = GetEnemyCombatCharacterPointer ( ) ;
Assert ( pVictim ) ;
if ( m_fLiftingPrey )
{
if ( GetEnemy ( ) ! = NULL & & pVictim - > m_lifeState = = LIFE_DEAD )
{
// crap, someone killed the prey on the way up.
SetEnemy ( NULL ) ;
m_fLiftingPrey = FALSE ;
return ;
}
// still pulling prey.
Vector vecNewEnemyOrigin = GetEnemy ( ) - > GetLocalOrigin ( ) ;
vecNewEnemyOrigin . x = GetLocalOrigin ( ) . x ;
vecNewEnemyOrigin . y = GetLocalOrigin ( ) . y ;
// guess as to where their neck is
// FIXME: remove, ask victim where their neck is
vecNewEnemyOrigin . x - = 6 * cos ( GetEnemy ( ) - > GetLocalAngles ( ) . y * M_PI / 180.0 ) ;
vecNewEnemyOrigin . y - = 6 * sin ( GetEnemy ( ) - > GetLocalAngles ( ) . y * M_PI / 180.0 ) ;
m_flAltitude - = BARNACLE_PULL_SPEED ;
vecNewEnemyOrigin . z + = BARNACLE_PULL_SPEED ;
if ( fabs ( GetLocalOrigin ( ) . z - ( vecNewEnemyOrigin . z + GetEnemy ( ) - > GetViewOffset ( ) . z ) ) < BARNACLE_BODY_HEIGHT )
{
// prey has just been lifted into position ( if the victim origin + eye height + 8 is higher than the bottom of the barnacle, it is assumed that the head is within barnacle's body )
m_fLiftingPrey = FALSE ;
CPASAttenuationFilter filter ( this ) ;
EmitSound ( filter , entindex ( ) , " Barnacle.Bite " ) ;
// Take a while to kill the player
m_flKillVictimTime = gpGlobals - > curtime + 10 ;
if ( pVictim )
{
pVictim - > DispatchInteraction ( g_interactionBarnacleVictimDangle , NULL , this ) ;
SetActivity ( ( Activity ) ACT_EAT ) ;
}
}
CBaseEntity * pEnemy = GetEnemy ( ) ;
trace_t trace ;
UTIL_TraceEntity ( pEnemy , pEnemy - > GetAbsOrigin ( ) , vecNewEnemyOrigin , MASK_SOLID_BRUSHONLY , pEnemy , COLLISION_GROUP_NONE , & trace ) ;
if ( trace . fraction ! = 1.0 )
{
// The victim cannot be moved from their current origin to this new origin. So drop them.
SetEnemy ( NULL ) ;
m_fLiftingPrey = FALSE ;
if ( pEnemy - > MyCombatCharacterPointer ( ) )
{
pEnemy - > MyCombatCharacterPointer ( ) - > DispatchInteraction ( g_interactionBarnacleVictimReleased , NULL , this ) ;
}
// Ignore touches long enough to let the victim move away.
m_flIgnoreTouchesUntil = gpGlobals - > curtime + 1.5 ;
SetActivity ( ACT_IDLE ) ;
return ;
}
UTIL_SetOrigin ( GetEnemy ( ) , vecNewEnemyOrigin ) ;
}
else
{
// prey is lifted fully into feeding position and is dangling there.
if ( m_flKillVictimTime ! = - 1 & & gpGlobals - > curtime > m_flKillVictimTime )
{
// kill!
if ( pVictim )
{
// DMG_CRUSH added so no physics force is generated
pVictim - > TakeDamage ( CTakeDamageInfo ( this , this , pVictim - > m_iHealth , DMG_SLASH | DMG_ALWAYSGIB | DMG_CRUSH ) ) ;
m_cGibs = 3 ;
}
return ;
}
// bite prey every once in a while
if ( pVictim & & ( random - > RandomInt ( 0 , 49 ) = = 0 ) )
{
CPASAttenuationFilter filter ( this ) ;
EmitSound ( filter , entindex ( ) , " Barnacle.Chew " ) ;
if ( pVictim )
{
pVictim - > DispatchInteraction ( g_interactionBarnacleVictimDangle , NULL , this ) ;
}
}
}
}
else
{
// barnacle has no prey right now, so just idle and check to see if anything is touching the tongue.
// If idle and no nearby client, don't think so often. Client should be out of PVS and not within 50 feet.
if ( ! UTIL_FindClientInPVS ( edict ( ) ) )
{
CBasePlayer * pPlayer = UTIL_PlayerByIndex ( 1 ) ;
if ( pPlayer )
{
Vector vecDist = pPlayer - > GetAbsOrigin ( ) - GetAbsOrigin ( ) ;
if ( vecDist . Length2DSqr ( ) > = Square ( 600.0f ) )
{
SetNextThink ( gpGlobals - > curtime + 1.5f ) ;
}
}
}
if ( IsActivityFinished ( ) )
{ // this is done so barnacle will fidget.
SetActivity ( ACT_IDLE ) ;
}
if ( m_cGibs & & random - > RandomInt ( 0 , 99 ) = = 1 )
{
// cough up a gib.
CGib : : SpawnRandomGibs ( this , 1 , GIB_HUMAN ) ;
m_cGibs - - ;
CPASAttenuationFilter filter ( this ) ;
EmitSound ( filter , entindex ( ) , " Barnacle.Chew " ) ;
}
pTouchEnt = TongueTouchEnt ( & flLength ) ;
//NDebugOverlay::Box( GetAbsOrigin() - Vector( 0, 0, flLength ), Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255,0,0, 0, 0.1 );
if ( pTouchEnt ! = NULL )
{
// tongue is fully extended, and is touching someone.
CBaseCombatCharacter * pBCC = ( CBaseCombatCharacter * ) pTouchEnt ;
// FIXME: humans should return neck position
Vector vecGrabPos = pTouchEnt - > GetAbsOrigin ( ) ;
if ( pBCC & & pBCC - > DispatchInteraction ( g_interactionBarnacleVictimGrab , & vecGrabPos , this ) )
{
CPASAttenuationFilter filter ( this ) ;
EmitSound ( filter , entindex ( ) , " Barnacle.Alert " ) ;
SetSequenceByName ( " attack1 " ) ;
SetEnemy ( pTouchEnt ) ;
pTouchEnt - > SetMoveType ( MOVETYPE_FLY ) ;
pTouchEnt - > SetAbsVelocity ( vec3_origin ) ;
pTouchEnt - > SetBaseVelocity ( vec3_origin ) ;
Vector origin = GetAbsOrigin ( ) ;
origin . z = pTouchEnt - > GetAbsOrigin ( ) . z ;
pTouchEnt - > SetLocalOrigin ( origin ) ;
m_fLiftingPrey = TRUE ; // indicate that we should be lifting prey.
m_flKillVictimTime = - 1 ; // set this to a bogus time while the victim is lifted.
m_flAltitude = ( GetAbsOrigin ( ) . z - vecGrabPos . z ) ;
}
}
else
{
// calculate a new length for the tongue to be clear of anything else that moves under it.
if ( m_flAltitude < flLength )
{
// if tongue is higher than is should be, lower it kind of slowly.
m_flAltitude + = BARNACLE_PULL_SPEED ;
}
else
{
m_flAltitude = flLength ;
}
}
}
//Msg("tounge %f\n", m_flAltitude + m_flTongueAdj );
//NDebugOverlay::Box( GetAbsOrigin() - Vector( 0, 0, m_flAltitude ), Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255,255,255, 0, 0.1 );
SetBoneController ( 0 , - ( m_flAltitude + m_flTongueAdj ) ) ;
StudioFrameAdvance ( ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Barnacle : : Event_Killed ( const CTakeDamageInfo & info )
{
AddSolidFlags ( FSOLID_NOT_SOLID ) ;
m_takedamage = DAMAGE_NO ;
m_lifeState = LIFE_DEAD ;
if ( GetEnemy ( ) ! = NULL )
{
CBaseCombatCharacter * pVictim = GetEnemyCombatCharacterPointer ( ) ;
if ( pVictim )
{
pVictim - > DispatchInteraction ( g_interactionBarnacleVictimReleased , NULL , this ) ;
}
}
CGib : : SpawnRandomGibs ( this , 4 , GIB_HUMAN ) ;
CPASAttenuationFilter filter ( this ) ;
EmitSound ( filter , entindex ( ) , " Barnacle.Die " ) ;
SetActivity ( ACT_DIESIMPLE ) ;
SetBoneController ( 0 , 0 ) ;
StudioFrameAdvance ( ) ;
SetNextThink ( gpGlobals - > curtime + 0.1f ) ;
SetThink ( & CNPC_Barnacle : : WaitTillDead ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Barnacle : : WaitTillDead ( void )
{
SetNextThink ( gpGlobals - > curtime + 0.1f ) ;
StudioFrameAdvance ( ) ;
DispatchAnimEvents ( this ) ;
if ( IsActivityFinished ( ) )
{
// death anim finished.
StopAnimation ( ) ;
SetThink ( NULL ) ;
}
}
//=========================================================
// Precache - precaches all resources this monster needs
//=========================================================
void CNPC_Barnacle : : Precache ( )
{
PrecacheModel ( " models/barnacle.mdl " ) ;
PrecacheScriptSound ( " Barnacle.Bite " ) ;
PrecacheScriptSound ( " Barnacle.Chew " ) ;
PrecacheScriptSound ( " Barnacle.Alert " ) ;
PrecacheScriptSound ( " Barnacle.Die " ) ;
BaseClass : : Precache ( ) ;
}
//=========================================================
// TongueTouchEnt - does a trace along the barnacle's tongue
// to see if any entity is touching it. Also stores the length
// of the trace in the int pointer provided.
//=========================================================
# define BARNACLE_CHECK_SPACING 8
CBaseEntity * CNPC_Barnacle : : TongueTouchEnt ( float * pflLength )
{
trace_t tr ;
float length ;
Vector origin ( GetAbsOrigin ( ) ) ;
origin . z - = 1.f ;
// trace once to hit architecture and see if the tongue needs to change position.
UTIL_TraceLine ( origin , origin - Vector ( 0 , 0 , 2048 ) ,
MASK_SOLID_BRUSHONLY , this , COLLISION_GROUP_NONE , & tr ) ;
length = fabs ( GetAbsOrigin ( ) . z - tr . endpos . z ) ;
// Pull it up a tad
length - = 16 ;
if ( pflLength )
{
* pflLength = length ;
}
// Don't try to touch any prey.
if ( m_flIgnoreTouchesUntil > gpGlobals - > curtime )
return NULL ;
Vector delta = Vector ( BARNACLE_CHECK_SPACING , BARNACLE_CHECK_SPACING , 0 ) ;
Vector mins = GetAbsOrigin ( ) - delta ;
Vector maxs = GetAbsOrigin ( ) + delta ;
maxs . z = GetAbsOrigin ( ) . z ;
// Take our current tongue's length or a point higher if we hit a wall
// NOTENOTE: (this relieves the need to know if the tongue is currently moving)
mins . z - = MIN ( m_flAltitude , length ) ;
CBaseEntity * pList [ 10 ] ;
int count = UTIL_EntitiesInBox ( pList , 10 , mins , maxs , ( FL_CLIENT | FL_NPC ) ) ;
if ( count )
{
for ( int i = 0 ; i < count ; i + + )
{
CBaseCombatCharacter * pVictim = ToBaseCombatCharacter ( pList [ i ] ) ;
bool bCanHurt = false ;
if ( IRelationType ( pList [ i ] ) = = D_HT | | IRelationType ( pList [ i ] ) = = D_FR )
bCanHurt = true ;
if ( pList [ i ] ! = this & & bCanHurt = = true & & pVictim - > m_lifeState = = LIFE_ALIVE )
{
return pList [ i ] ;
}
}
}
return NULL ;
}