You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
516 lines
15 KiB
516 lines
15 KiB
//========= 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; |
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// ALERT( at_console, "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; |
|
|
|
// trace once to hit architecture and see if the tongue needs to change position. |
|
UTIL_TraceLine ( GetAbsOrigin(), GetAbsOrigin() - 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; |
|
}
|
|
|