2378 lines
70 KiB
C++
2378 lines
70 KiB
C++
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: barnacle - stationary ceiling mounted 'fishing' monster
|
|
//
|
|
// $Workfile: $
|
|
// $Date: $
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "physics_prop_ragdoll.h"
|
|
#include "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 "vphysics/constraints.h"
|
|
#include "studio.h"
|
|
#include "bone_setup.h"
|
|
#include "iservervehicle.h"
|
|
#include "collisionutils.h"
|
|
#include "combine_mine.h"
|
|
#include "explode.h"
|
|
#include "npc_BaseZombie.h"
|
|
#include "modelentities.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
extern ConVar sv_gravity;
|
|
ConVar sk_barnacle_health( "sk_barnacle_health","0");
|
|
|
|
static ConVar npc_barnacle_swallow( "npc_barnacle_swallow", "0", 0, "Use prototype swallow code." );
|
|
|
|
const char *CNPC_Barnacle::m_szGibNames[NUM_BARNACLE_GIBS] =
|
|
{
|
|
"models/gibs/hgibs.mdl",
|
|
"models/gibs/hgibs_scapula.mdl",
|
|
"models/gibs/hgibs_rib.mdl",
|
|
"models/gibs/hgibs_spine.mdl"
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Private activities.
|
|
//-----------------------------------------------------------------------------
|
|
int ACT_BARNACLE_SLURP; // Pulling the tongue up with prey on the end
|
|
int ACT_BARNACLE_BITE_HUMAN; // Biting the head of a humanoid
|
|
int ACT_BARNACLE_BITE_PLAYER; // Biting the head of the player
|
|
int ACT_BARNACLE_CHEW_HUMAN; // Slowly swallowing the humanoid
|
|
int ACT_BARNACLE_BARF_HUMAN; // Spitting out human legs & gibs
|
|
int ACT_BARNACLE_TONGUE_WRAP; // Wrapping the tongue around a target
|
|
int ACT_BARNACLE_TASTE_SPIT; // Yuck! Me no like that!
|
|
int ACT_BARNACLE_BITE_SMALL_THINGS; // Eats small things
|
|
int ACT_BARNACLE_CHEW_SMALL_THINGS; // Chews small things
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Interactions
|
|
//-----------------------------------------------------------------------------
|
|
int g_interactionBarnacleVictimDangle = 0;
|
|
int g_interactionBarnacleVictimReleased = 0;
|
|
int g_interactionBarnacleVictimGrab = 0;
|
|
|
|
LINK_ENTITY_TO_CLASS( npc_barnacle, CNPC_Barnacle );
|
|
|
|
// Tongue Spring constants
|
|
#define BARNACLE_TONGUE_SPRING_CONSTANT_HANGING 10000
|
|
#define BARNACLE_TONGUE_SPRING_CONSTANT_LIFTING 10000
|
|
#define BARNACLE_TONGUE_SPRING_CONSTANT_LOWERING 7000
|
|
#define BARNACLE_TONGUE_SPRING_DAMPING 20
|
|
#define BARNACLE_TONGUE_TIP_MASS 100
|
|
#define BARNACLE_TONGUE_MAX_LIFT_MASS 70
|
|
|
|
#define BARNACLE_BITE_DAMAGE_TO_PLAYER 15
|
|
#define BARNACLE_DEAD_TONGUE_ALTITUDE 164
|
|
#define BARNACLE_MIN_DEAD_TONGUE_CLEARANCE 78
|
|
|
|
|
|
//=========================================================
|
|
// Monster's Anim Events Go Here
|
|
//=========================================================
|
|
#define BARNACLE_AE_PUKEGIB 2
|
|
#define BARNACLE_AE_BITE 3
|
|
#define BARNACLE_AE_SPIT 4
|
|
|
|
int AE_BARNACLE_PUKEGIB;
|
|
int AE_BARNACLE_BITE;
|
|
int AE_BARNACLE_SPIT;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Constructor
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
CNPC_Barnacle::CNPC_Barnacle(void)
|
|
{
|
|
m_flRestUnitsAboveGround = 16.0f;
|
|
m_flNextBloodTime = -1.0f;
|
|
#ifndef _XBOX
|
|
m_nBloodColor = BLOOD_COLOR_YELLOW;
|
|
#endif
|
|
m_bPlayerWasStanding = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CNPC_Barnacle::~CNPC_Barnacle( void )
|
|
{
|
|
// Destroy the ragdoll->tongue tip constraint
|
|
if ( m_pConstraint )
|
|
{
|
|
physenv->DestroyConstraint( m_pConstraint );
|
|
m_pConstraint = NULL;
|
|
}
|
|
}
|
|
|
|
BEGIN_DATADESC( CNPC_Barnacle )
|
|
|
|
DEFINE_FIELD( m_flAltitude, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_cGibs, FIELD_INTEGER ),// barnacle loads up on gibs each time it kills something.
|
|
DEFINE_FIELD( m_bLiftingPrey, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bSwallowingPrey, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_flDigestFinish, FIELD_TIME ),
|
|
DEFINE_FIELD( m_bPlayedPullSound, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bPlayerWasStanding, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_flVictimHeight, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_iGrabbedBoneIndex, FIELD_INTEGER ),
|
|
|
|
DEFINE_FIELD( m_vecRoot, FIELD_POSITION_VECTOR ),
|
|
DEFINE_FIELD( m_vecTip, FIELD_POSITION_VECTOR ),
|
|
DEFINE_FIELD( m_hTongueRoot, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_hTongueTip, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_hRagdoll, FIELD_EHANDLE ),
|
|
DEFINE_AUTO_ARRAY( m_pRagdollBones, FIELD_MATRIX3X4_WORLDSPACE ),
|
|
DEFINE_PHYSPTR( m_pConstraint ),
|
|
DEFINE_KEYFIELD( m_flRestUnitsAboveGround, FIELD_FLOAT, "RestDist" ),
|
|
DEFINE_FIELD( m_nSpitAttachment, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_hLastSpitEnemy, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_nShakeCount, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_flNextBloodTime, FIELD_TIME ),
|
|
#ifndef _XBOX
|
|
DEFINE_FIELD( m_nBloodColor, FIELD_INTEGER ),
|
|
#endif
|
|
DEFINE_FIELD( m_vecBloodPos, FIELD_POSITION_VECTOR ),
|
|
DEFINE_FIELD( m_flBarnaclePullSpeed, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_flLocalTimer, FIELD_TIME ),
|
|
DEFINE_FIELD( m_vLastEnemyPos, FIELD_POSITION_VECTOR ),
|
|
DEFINE_FIELD( m_flLastPull, FIELD_FLOAT ),
|
|
DEFINE_EMBEDDED( m_StuckTimer ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "DropTongue", InputDropTongue ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetDropTongueSpeed", InputSetDropTongueSpeed ),
|
|
|
|
// Function pointers
|
|
DEFINE_THINKFUNC( BarnacleThink ),
|
|
DEFINE_THINKFUNC( WaitTillDead ),
|
|
|
|
DEFINE_FIELD( m_bSwallowingBomb, FIELD_BOOLEAN ),
|
|
|
|
END_DATADESC()
|
|
|
|
IMPLEMENT_SERVERCLASS_ST( CNPC_Barnacle, DT_Barnacle )
|
|
SendPropFloat( SENDINFO( m_flAltitude ), 0, SPROP_NOSCALE),
|
|
SendPropVector( SENDINFO( m_vecRoot ), 0, SPROP_COORD ),
|
|
SendPropVector( SENDINFO( m_vecTip ), 0, SPROP_COORD ),
|
|
END_SEND_TABLE()
|
|
|
|
|
|
//=========================================================
|
|
// Classify - indicates this monster's place in the
|
|
// relationship table.
|
|
//=========================================================
|
|
Class_T CNPC_Barnacle::Classify ( void )
|
|
{
|
|
return CLASS_BARNACLE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Initialize absmin & absmax to the appropriate box
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::ComputeWorldSpaceSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs )
|
|
{
|
|
// Extend our bounding box downwards the length of the tongue
|
|
CollisionProp()->WorldSpaceAABB( pVecWorldMins, pVecWorldMaxs );
|
|
|
|
// We really care about the tongue tip. The altitude is not really relevant.
|
|
VectorMin( *pVecWorldMins, m_vecTip, *pVecWorldMins );
|
|
VectorMax( *pVecWorldMaxs, m_vecTip, *pVecWorldMaxs );
|
|
|
|
// pVecWorldMins->z -= m_flAltitude;
|
|
}
|
|
|
|
//=========================================================
|
|
// 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 )
|
|
{
|
|
if ( pEvent->event== AE_BARNACLE_PUKEGIB )
|
|
{
|
|
CGib::SpawnSpecificGibs( this, 1, 50, 1, "models/gibs/hgibs_rib.mdl");
|
|
return;
|
|
}
|
|
if ( pEvent->event == AE_BARNACLE_BITE )
|
|
{
|
|
BitePrey();
|
|
return;
|
|
}
|
|
if ( pEvent->event == AE_BARNACLE_SPIT )
|
|
{
|
|
SpitPrey();
|
|
return;
|
|
}
|
|
|
|
BaseClass::HandleAnimEvent( pEvent );
|
|
}
|
|
|
|
//=========================================================
|
|
// Spawn
|
|
//=========================================================
|
|
void CNPC_Barnacle::Spawn()
|
|
{
|
|
Precache( );
|
|
|
|
SetModel( "models/barnacle.mdl" );
|
|
UTIL_SetSize( this, Vector(-16, -16, -40), Vector(16, 16, 0) );
|
|
|
|
SetSolid( SOLID_BBOX );
|
|
AddSolidFlags( FSOLID_NOT_STANDABLE );
|
|
CollisionProp()->SetSurroundingBoundsType( USE_GAME_CODE );
|
|
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_cGibs = 0;
|
|
m_bLiftingPrey = false;
|
|
m_bSwallowingPrey = false;
|
|
m_bSwallowingBomb = false;
|
|
m_flDigestFinish = 0;
|
|
m_takedamage = DAMAGE_YES;
|
|
m_pConstraint = NULL;
|
|
m_nShakeCount = 0;
|
|
InitBoneControllers();
|
|
InitTonguePosition();
|
|
|
|
// set eye position
|
|
SetDefaultEyeOffset();
|
|
|
|
SetActivity( ACT_IDLE );
|
|
|
|
SetThink ( &CNPC_Barnacle::BarnacleThink );
|
|
SetNextThink( gpGlobals->curtime + 0.5f );
|
|
|
|
m_flBarnaclePullSpeed = BARNACLE_PULL_SPEED;
|
|
|
|
//Do not have a shadow
|
|
AddEffects( EF_NOSHADOW );
|
|
|
|
AddFlag( FL_AIMTARGET );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Sets the tongue's height
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::SetAltitude( float flAltitude )
|
|
{
|
|
if ( HasSpawnFlags( SF_BARNACLE_AMBUSH ) )
|
|
return;
|
|
|
|
m_flAltitude = flAltitude;
|
|
}
|
|
|
|
void CNPC_Barnacle::DropTongue( void )
|
|
{
|
|
if ( m_hTongueRoot )
|
|
return;
|
|
|
|
m_hTongueRoot = CBarnacleTongueTip::CreateTongueRoot( m_vecRoot, QAngle(90,0,0) );
|
|
m_hTongueTip = CBarnacleTongueTip::CreateTongueTip( this, m_hTongueRoot, m_vecTip, QAngle(0,0,0) );
|
|
m_nSpitAttachment = LookupAttachment( "StrikeHeadAttach" );
|
|
Assert( m_hTongueRoot && m_hTongueTip );
|
|
|
|
RemoveSpawnFlags( SF_BARNACLE_AMBUSH );
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::Activate( void )
|
|
{
|
|
BaseClass::Activate();
|
|
|
|
if ( HasSpawnFlags( SF_BARNACLE_AMBUSH ) )
|
|
return;
|
|
|
|
// Create our tongue tips
|
|
if ( !m_hTongueRoot )
|
|
{
|
|
DropTongue();
|
|
}
|
|
else if ( GetEnemy() && IsEnemyAPlayer() && !m_pConstraint )
|
|
{
|
|
IPhysicsObject *pPlayerPhys = GetEnemy()->VPhysicsGetObject();
|
|
IPhysicsObject *pTonguePhys = m_hTongueTip->VPhysicsGetObject();
|
|
|
|
constraint_fixedparams_t fixed;
|
|
fixed.Defaults();
|
|
fixed.InitWithCurrentObjectState( pTonguePhys, pPlayerPhys );
|
|
fixed.constraint.Defaults();
|
|
m_pConstraint = physenv->CreateFixedConstraint( pTonguePhys, pPlayerPhys, NULL, fixed );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
int CNPC_Barnacle::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
|
|
{
|
|
CTakeDamageInfo info = inputInfo;
|
|
if ( info.GetDamageType() & DMG_CLUB )
|
|
{
|
|
info.SetDamage( m_iHealth );
|
|
}
|
|
|
|
if ( GetActivity() == ACT_IDLE )
|
|
{
|
|
SetActivity( ACT_SMALL_FLINCH );
|
|
}
|
|
|
|
if( hl2_episodic.GetBool() && info.GetAttacker() && info.GetAttacker()->Classify() == CLASS_PLAYER_ALLY_VITAL )
|
|
{
|
|
if( FClassnameIs( info.GetAttacker(), "npc_alyx" ) )
|
|
{
|
|
// Alyx does double damage to barnacles, so that she can save the
|
|
// player's life in a more timely fashion. (sjb)
|
|
info.ScaleDamage( 2.0f );
|
|
}
|
|
}
|
|
|
|
DropTongue();
|
|
|
|
return BaseClass::OnTakeDamage_Alive( info );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Player has illuminated this NPC with the flashlight
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot )
|
|
{
|
|
// Create a sound to scare friendly allies away from the base on the barnacle
|
|
if( IsAlive() )
|
|
{
|
|
CSoundEnt::InsertSound( SOUND_MOVE_AWAY | SOUND_CONTEXT_ALLIES_ONLY, m_vecTip, 60.0f, FLASHLIGHT_NPC_CHECK_INTERVAL );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Initialize tongue position when first spawned
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::InitTonguePosition( void )
|
|
{
|
|
CBaseEntity *pTouchEnt;
|
|
float flLength;
|
|
|
|
pTouchEnt = TongueTouchEnt( &flLength );
|
|
SetAltitude( flLength );
|
|
|
|
Vector origin;
|
|
|
|
GetAttachment( "TongueEnd", origin );
|
|
|
|
float flTongueAdj = origin.z - GetAbsOrigin().z;
|
|
m_vecRoot = origin - Vector(0,0,flTongueAdj);
|
|
m_vecTip.Set( m_vecRoot.Get() - Vector(0,0,(float)m_flAltitude) );
|
|
CollisionProp()->MarkSurroundingBoundsDirty();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::BarnacleThink ( void )
|
|
{
|
|
CBaseEntity *pTouchEnt;
|
|
float flLength;
|
|
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
|
|
UpdateTongue();
|
|
|
|
// AI Disabled, don't do anything?
|
|
if ( CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI )
|
|
return;
|
|
|
|
// Do we have an enemy?
|
|
if ( m_hRagdoll )
|
|
{
|
|
if ( m_bLiftingPrey )
|
|
{
|
|
LiftPrey();
|
|
}
|
|
else if ( m_bSwallowingPrey )
|
|
{
|
|
// Slowly swallowing the ragdoll
|
|
SwallowPrey();
|
|
}
|
|
// Stay bloated as we digest
|
|
else if ( m_flDigestFinish )
|
|
{
|
|
// Still digesting him>
|
|
if ( m_flDigestFinish > gpGlobals->curtime )
|
|
{
|
|
if ( IsActivityFinished() )
|
|
{
|
|
SetActivity( ACT_IDLE );
|
|
}
|
|
|
|
// bite prey every once in a while
|
|
if ( random->RandomInt(0,25) == 0 )
|
|
{
|
|
EmitSound( "NPC_Barnacle.Digest" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Finished digesting
|
|
LostPrey( true ); // Remove all evidence
|
|
m_flDigestFinish = 0;
|
|
}
|
|
}
|
|
}
|
|
else if ( GetEnemy() )
|
|
{
|
|
if ( m_bLiftingPrey || m_bSwallowingBomb == true )
|
|
{
|
|
LiftPrey();
|
|
}
|
|
// Stay bloated as we digest
|
|
else if ( m_flDigestFinish )
|
|
{
|
|
// Still digesting him>
|
|
if ( m_flDigestFinish > gpGlobals->curtime )
|
|
{
|
|
if ( IsActivityFinished() )
|
|
{
|
|
SetActivity( ACT_IDLE );
|
|
}
|
|
|
|
// bite prey every once in a while
|
|
if ( random->RandomInt(0,25) == 0 )
|
|
{
|
|
EmitSound( "NPC_Barnacle.Digest" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Finished digesting
|
|
LostPrey( true ); // Remove all evidence
|
|
m_flDigestFinish = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Were we lifting prey?
|
|
if ( m_bSwallowingPrey || m_bLiftingPrey )
|
|
{
|
|
// Something removed our prey.
|
|
LostPrey( false );
|
|
}
|
|
|
|
// 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
|
|
// NOTE: Use the surrounding bounds so that we'll think often event if the tongue
|
|
// tip is in the PVS but the body isn't
|
|
Vector vecSurroundMins, vecSurroundMaxs;
|
|
CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs );
|
|
if ( !UTIL_FindClientInPVS( vecSurroundMins, vecSurroundMaxs ) )
|
|
{
|
|
SetNextThink( gpGlobals->curtime + random->RandomFloat(1,1.5) ); // Stagger a bit to keep barnacles from thinking on the same frame
|
|
}
|
|
|
|
if ( IsActivityFinished() && GetActivity() != ACT_IDLE )
|
|
{
|
|
// this is done so barnacle will fidget.
|
|
SetActivity( ACT_IDLE );
|
|
}
|
|
|
|
if ( m_cGibs && random->RandomInt(0,99) == 1 )
|
|
{
|
|
// cough up a gib.
|
|
CGib::SpawnSpecificGibs( this, 1, 50, 1, "models/gibs/hgibs_rib.mdl");
|
|
m_cGibs--;
|
|
|
|
EmitSound( "NPC_Barnacle.Digest" );
|
|
}
|
|
|
|
pTouchEnt = TongueTouchEnt( &flLength );
|
|
|
|
// If there's something under us, lower the tongue down so we can grab it
|
|
if ( m_flAltitude < flLength )
|
|
{
|
|
float dt = gpGlobals->curtime - GetLastThink();
|
|
SetAltitude( m_flAltitude + m_flBarnaclePullSpeed * dt );
|
|
}
|
|
|
|
// NOTE: SetAltitude above will change m_flAltitude, hence the second check
|
|
if ( m_flAltitude >= flLength )
|
|
{
|
|
// If we're already low enough, try to grab.
|
|
bool bGrabbedTarget = false;
|
|
if ( ( pTouchEnt != NULL ) && ( pTouchEnt != m_hLastSpitEnemy.Get() ) )
|
|
{
|
|
// tongue is fully extended, and is touching someone.
|
|
CBaseCombatCharacter *pBCC = dynamic_cast<CBaseCombatCharacter *>(pTouchEnt);
|
|
|
|
if( CanPickup( pBCC ) )
|
|
{
|
|
Vector vecGrabPos = pTouchEnt->EyePosition();
|
|
if( !pBCC || pBCC->DispatchInteraction( g_interactionBarnacleVictimGrab, &vecGrabPos, this ) )
|
|
{
|
|
EmitSound( "NPC_Barnacle.BreakNeck" );
|
|
AttachTongueToTarget( pTouchEnt, vecGrabPos );
|
|
|
|
// Set the local timer to 60 seconds, which starts the lifting phase on
|
|
// the upshot of the sine wave which right away makes it more obvious
|
|
// that the player is being lifted.
|
|
m_flLocalTimer = 60.0f;
|
|
m_vLastEnemyPos = pTouchEnt->GetAbsOrigin();
|
|
m_flLastPull = 0;
|
|
m_StuckTimer.Set(3.0);
|
|
bGrabbedTarget = true;
|
|
|
|
// Set our touch flag so no one else tries to grab us this frame
|
|
pTouchEnt->AddEFlags( EFL_IS_BEING_LIFTED_BY_BARNACLE );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !bGrabbedTarget )
|
|
{
|
|
// Restore the hanging spring constant
|
|
if ( m_hTongueTip )
|
|
{
|
|
m_hTongueTip->m_pSpring->SetSpringConstant( BARNACLE_TONGUE_SPRING_CONSTANT_HANGING );
|
|
}
|
|
SetAltitude( flLength );
|
|
}
|
|
}
|
|
}
|
|
|
|
// NDebugOverlay::Box( GetAbsOrigin() - Vector( 0, 0, m_flAltitude ), Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255,255,255, 0, 0.1 );
|
|
|
|
StudioFrameAdvance();
|
|
DispatchAnimEvents( this );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_Barnacle::CanPickup( CBaseCombatCharacter *pBCC )
|
|
{
|
|
// Barnacle can pick this item up because it has already passed the filters
|
|
// in TongueTouchEnt. It just isn't an NPC or player and doesn't need further inspection.
|
|
if( !pBCC )
|
|
return true;
|
|
|
|
// Don't pickup turrets
|
|
if( FClassnameIs( pBCC, "npc_turret_floor" ) )
|
|
return false;
|
|
|
|
// Don't pick up a dead player or NPC
|
|
if( !pBCC->IsAlive() )
|
|
return false;
|
|
|
|
if( pBCC->IsPlayer() )
|
|
{
|
|
CBasePlayer *pPlayer = dynamic_cast<CBasePlayer*>(pBCC);
|
|
|
|
Assert( pPlayer != NULL );
|
|
|
|
// Don't pick up a player held by another barnacle
|
|
if( pPlayer->HasPhysicsFlag(PFLAG_ONBARNACLE) )
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Allows the ragdoll to settle before biting it
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_Barnacle::WaitForRagdollToSettle( float flBiteZOffset )
|
|
{
|
|
Vector vecVictimPos = GetEnemy()->GetAbsOrigin();
|
|
|
|
Vector vecCheckPos;
|
|
QAngle vecBoneAngles;
|
|
m_hRagdoll->GetBonePosition( m_iGrabbedBoneIndex, vecCheckPos, vecBoneAngles );
|
|
|
|
// Stop sucking while we wait for the ragdoll to settle
|
|
SetActivity( ACT_IDLE );
|
|
|
|
Vector vecVelocity;
|
|
AngularImpulse angVel;
|
|
float flDelta = 4.0;
|
|
|
|
// Only bite if the target bone is in the right position.
|
|
Vector vecBitePoint = GetAbsOrigin();
|
|
vecBitePoint.z -= flBiteZOffset;
|
|
|
|
//NDebugOverlay::Box( vecBitePoint, -Vector(10,10,10), Vector(10,10,10), 0,255,0, 0, 0.1 );
|
|
//NDebugOverlay::Line( vecBitePoint, vecCheckPos, 0, 255, 0, true, 0.1 );
|
|
|
|
if ( (vecBitePoint.x - vecCheckPos.x) > flDelta || (vecBitePoint.y - vecCheckPos.y) > flDelta )
|
|
{
|
|
// I can't bite this critter because it's not lined up with me on the X/Y plane. If it is
|
|
// as close to my mouth as I can get it, I should drop it.
|
|
if( vecBitePoint.z - vecVictimPos.z < 72.0f )
|
|
{
|
|
// A man-sized target has been pulled up to my mouth, but
|
|
// is not aligned for biting. Drop it.
|
|
SpitPrey();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Right height?
|
|
if ( (vecBitePoint.z - vecCheckPos.z) > flDelta )
|
|
{
|
|
// Slowly raise / lower the target into the right position
|
|
if ( vecBitePoint.z > vecCheckPos.z )
|
|
{
|
|
// Pull the victim towards the mouth
|
|
SetAltitude( m_flAltitude - 1 );
|
|
vecVictimPos.z += 1;
|
|
}
|
|
else
|
|
{
|
|
// We pulled 'em up too far, so lower them a little
|
|
SetAltitude( m_flAltitude + 1 );
|
|
vecVictimPos.z -= 1;
|
|
}
|
|
UTIL_SetOrigin ( GetEnemy(), vecVictimPos );
|
|
return false;
|
|
}
|
|
|
|
// Get the velocity of the bone we've grabbed, and only bite when it's not moving much
|
|
CStudioHdr *pStudioHdr = m_hRagdoll->GetModelPtr();
|
|
mstudiobone_t *pBone = pStudioHdr->pBone( m_iGrabbedBoneIndex );
|
|
int iBoneIndex = pBone->physicsbone;
|
|
ragdoll_t *pRagdoll = m_hRagdoll->GetRagdoll();
|
|
IPhysicsObject *pRagdollPhys = pRagdoll->list[iBoneIndex].pObject;
|
|
pRagdollPhys->GetVelocity( &vecVelocity, &angVel );
|
|
return ( vecVelocity.LengthSqr() < 20 );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Allows the physics prop to settle before biting it
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_Barnacle::WaitForPhysicsObjectToSettle( float flBiteZOffset )
|
|
{
|
|
--m_nShakeCount;
|
|
if ( m_nShakeCount & 0x1 )
|
|
{
|
|
SetAltitude( flBiteZOffset + 15 );
|
|
}
|
|
else
|
|
{
|
|
SetAltitude( flBiteZOffset );
|
|
}
|
|
|
|
return ( m_nShakeCount <= 0 );
|
|
|
|
|
|
/*
|
|
IPhysicsObject *pPhysicsObject = GetEnemy()->VPhysicsGetObject();
|
|
|
|
Vector vecVelocity;
|
|
AngularImpulse angVel;
|
|
pPhysicsObject->GetVelocity( &vecVelocity, &angVel );
|
|
|
|
return ( vecVelocity.LengthSqr() < 25 );
|
|
*/
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Lift the prety stuck to our tongue up towards our mouth
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::PlayLiftingScream( float flBiteZOffset )
|
|
{
|
|
if ( !m_bPlayedPullSound && m_flAltitude < (flBiteZOffset + 100) )
|
|
{
|
|
EmitSound( "NPC_Barnacle.Scream" );
|
|
m_bPlayedPullSound = true;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Lift the prety stuck to our tongue up towards our mouth
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::PullEnemyTorwardsMouth( bool bAdjustEnemyOrigin )
|
|
{
|
|
if ( GetEnemy()->IsPlayer() && GetEnemy()->GetMoveType() == MOVETYPE_NOCLIP )
|
|
{
|
|
LostPrey( false );
|
|
return;
|
|
}
|
|
|
|
// Pull the victim towards the mouth
|
|
float dt = gpGlobals->curtime - GetLastThink();
|
|
|
|
// Assumes constant frame rate :|
|
|
m_flLocalTimer += dt;
|
|
|
|
float flPull = fabs(sin( m_flLocalTimer * 5 ));
|
|
|
|
flPull *= m_flBarnaclePullSpeed * dt;
|
|
|
|
SetAltitude( m_flAltitude - flPull );
|
|
|
|
if ( bAdjustEnemyOrigin )
|
|
{
|
|
if ( m_flLastPull > 1.0 )
|
|
{
|
|
if ( (GetEnemy()->GetAbsOrigin() - m_vLastEnemyPos).LengthSqr() < Square( m_flLastPull - 1.0 ) )
|
|
{
|
|
if ( m_StuckTimer.Expired() )
|
|
{
|
|
LostPrey( false );
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_StuckTimer.Set(3.0);
|
|
}
|
|
}
|
|
else
|
|
m_StuckTimer.Delay(dt);
|
|
|
|
m_vLastEnemyPos = GetEnemy()->GetAbsOrigin();
|
|
m_flLastPull = flPull;
|
|
|
|
Vector vecNewPos = m_vLastEnemyPos;
|
|
vecNewPos.z += flPull;
|
|
|
|
#if 0
|
|
const float MAX_CENTERING_VELOCITY = 2.0;
|
|
float distToMove = MAX_CENTERING_VELOCITY * dt;
|
|
Vector2D vToCenter = GetAbsOrigin().AsVector2D() - GetEnemy()->GetAbsOrigin().AsVector2D();
|
|
float distFromCenter = vToCenter.NormalizeInPlace();
|
|
|
|
if ( distFromCenter < distToMove )
|
|
{
|
|
vecNewPos.x = GetAbsOrigin().x;
|
|
vecNewPos.y = GetAbsOrigin().y;
|
|
}
|
|
else
|
|
{
|
|
vToCenter *= distToMove;
|
|
vecNewPos.x += vToCenter.x;
|
|
vecNewPos.y += vToCenter.y;
|
|
}
|
|
#endif
|
|
|
|
GetEnemy()->SetAbsOrigin( vecNewPos );
|
|
|
|
if( GetEnemy()->GetFlags() & FL_ONGROUND )
|
|
{
|
|
// Try to fight OnGround
|
|
GetEnemy()->SetGravity( 0 );
|
|
GetEnemy()->RemoveFlag( FL_ONGROUND );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::UpdatePlayerContraint( void )
|
|
{
|
|
// Check to see if the player's standing/ducking state has changed.
|
|
CBasePlayer *pPlayer = static_cast<CBasePlayer*>( GetEnemy() );
|
|
bool bStanding = ( ( pPlayer->GetFlags() & FL_DUCKING ) == 0 );
|
|
if ( bStanding == m_bPlayerWasStanding )
|
|
return;
|
|
|
|
// Destroy the current constraint.
|
|
physenv->DestroyConstraint( m_pConstraint );
|
|
m_pConstraint = NULL;
|
|
|
|
if ( m_hTongueTip )
|
|
{
|
|
// Create the new constraint for the standing/ducking player physics object.
|
|
IPhysicsObject *pPlayerPhys = pPlayer->VPhysicsGetObject();
|
|
IPhysicsObject *pTonguePhys = m_hTongueTip->VPhysicsGetObject();
|
|
|
|
constraint_fixedparams_t fixed;
|
|
fixed.Defaults();
|
|
fixed.InitWithCurrentObjectState( pTonguePhys, pPlayerPhys );
|
|
fixed.constraint.Defaults();
|
|
m_pConstraint = physenv->CreateFixedConstraint( pTonguePhys, pPlayerPhys, NULL, fixed );
|
|
}
|
|
|
|
// Save state for the next check.
|
|
m_bPlayerWasStanding = bStanding;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Lift the prey stuck to our tongue up towards our mouth
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::LiftPlayer( float flBiteZOffset )
|
|
{
|
|
// Add an additional height for the player to avoid view clipping
|
|
flBiteZOffset += 25.0;
|
|
|
|
// Play a scream when we're almost within bite range
|
|
PlayLiftingScream( flBiteZOffset );
|
|
|
|
// Update player constraint.
|
|
UpdatePlayerContraint();
|
|
|
|
// Figure out when the prey has reached our bite range use eye position to avoid
|
|
// clipping into the barnacle body
|
|
if ( GetAbsOrigin().z - GetEnemy()->EyePosition().z < flBiteZOffset)
|
|
{
|
|
m_bLiftingPrey = false;
|
|
|
|
// Start the bite animation. The anim event in it will finish the job.
|
|
SetActivity( (Activity)ACT_BARNACLE_BITE_PLAYER );
|
|
}
|
|
else
|
|
{
|
|
PullEnemyTorwardsMouth( true );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Lift the prey stuck to our tongue up towards our mouth
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::LiftNPC( float flBiteZOffset )
|
|
{
|
|
// Necessary to make the NPCs not do things like talk
|
|
GetEnemy()->AddEFlags( EFL_IS_BEING_LIFTED_BY_BARNACLE );
|
|
|
|
// Play a scream when we're almost within bite range
|
|
PlayLiftingScream( flBiteZOffset );
|
|
|
|
// Figure out when the prey has reached our bite range
|
|
if ( GetAbsOrigin().z - m_vecTip.Get().z < flBiteZOffset )
|
|
{
|
|
m_bLiftingPrey = false;
|
|
|
|
const Vector &vecSize = GetEnemy()->CollisionProp()->OBBSize();
|
|
if ( vecSize.z < 40 )
|
|
{
|
|
// Start the bite animation. The anim event in it will finish the job.
|
|
SetActivity( (Activity)ACT_BARNACLE_BITE_SMALL_THINGS );
|
|
}
|
|
else
|
|
{
|
|
// Start the bite animation. The anim event in it will finish the job.
|
|
SetActivity( (Activity)ACT_BARNACLE_BITE_HUMAN );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PullEnemyTorwardsMouth( true );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Lift the prey stuck to our tongue up towards our mouth
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::LiftRagdoll( float flBiteZOffset )
|
|
{
|
|
// Necessary to make the NPCs not do things like talk
|
|
GetEnemy()->AddEFlags( EFL_IS_BEING_LIFTED_BY_BARNACLE );
|
|
|
|
// Play a scream when we're almost within bite range
|
|
PlayLiftingScream( flBiteZOffset );
|
|
|
|
// Figure out when the prey has reached our bite range
|
|
if ( GetAbsOrigin().z - m_vecTip.Get().z < flBiteZOffset )
|
|
{
|
|
// If we've got a ragdoll, wait until the bone is down below the mouth.
|
|
if ( !WaitForRagdollToSettle( flBiteZOffset ) )
|
|
return;
|
|
|
|
if ( GetEnemy()->Classify() == CLASS_ZOMBIE )
|
|
{
|
|
// lifted the prey high enough to see it's a zombie. Spit it out.
|
|
if ( hl2_episodic.GetBool() )
|
|
{
|
|
m_bLiftingPrey = false;
|
|
SetActivity( (Activity)ACT_BARNACLE_BITE_SMALL_THINGS );
|
|
}
|
|
else
|
|
{
|
|
SpitPrey();
|
|
}
|
|
return;
|
|
}
|
|
|
|
m_bLiftingPrey = false;
|
|
|
|
const Vector &vecSize = GetEnemy()->CollisionProp()->OBBSize();
|
|
if ( vecSize.z < 40 )
|
|
{
|
|
// Start the bite animation. The anim event in it will finish the job.
|
|
SetActivity( (Activity)ACT_BARNACLE_BITE_SMALL_THINGS );
|
|
}
|
|
else
|
|
{
|
|
// Start the bite animation. The anim event in it will finish the job.
|
|
SetActivity( (Activity)ACT_BARNACLE_BITE_HUMAN );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Pull the victim towards the mouth
|
|
PullEnemyTorwardsMouth( false );
|
|
|
|
// Apply forces to the attached ragdoll based upon the animations of the enemy, if the enemy is still alive.
|
|
if ( GetEnemy()->IsAlive() )
|
|
{
|
|
CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating*>( GetEnemy() );
|
|
|
|
// Get the current bone matrix
|
|
/*
|
|
Vector pos[MAXSTUDIOBONES];
|
|
Quaternion q[MAXSTUDIOBONES];
|
|
matrix3x4_t pBoneToWorld[MAXSTUDIOBONES];
|
|
CalcPoseSingle( pStudioHdr, pos, q, pAnimating->GetSequence(), pAnimating->m_flCycle, pAnimating->GetPoseParameterArray(), BONE_USED_BY_ANYTHING );
|
|
Studio_BuildMatrices( pStudioHdr, vec3_angle, vec3_origin, pos, q, -1, pBoneToWorld, BONE_USED_BY_ANYTHING );
|
|
|
|
|
|
// Apply the forces to the ragdoll
|
|
RagdollApplyAnimationAsVelocity( *(m_hRagdoll->GetRagdoll()), pBoneToWorld );
|
|
*/
|
|
|
|
// Get the current bone matrix
|
|
matrix3x4_t pBoneToWorld[MAXSTUDIOBONES];
|
|
pAnimating->SetupBones( pBoneToWorld, BONE_USED_BY_ANYTHING );
|
|
|
|
// Apply the forces to the ragdoll
|
|
RagdollApplyAnimationAsVelocity( *(m_hRagdoll->GetRagdoll()), m_pRagdollBones, pBoneToWorld, 0.2 );
|
|
|
|
// Store off the current bone matrix for next time
|
|
pAnimating->SetupBones( m_pRagdollBones, BONE_USED_BY_ANYTHING );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Lift the prey stuck to our tongue up towards our mouth
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::LiftPhysicsObject( float flBiteZOffset )
|
|
{
|
|
CBaseEntity *pVictim = GetEnemy();
|
|
|
|
// Bite a little higher up, since the bits point is the tip of the tongue
|
|
flBiteZOffset -= 5.0f;
|
|
|
|
//NDebugOverlay::Box( vecCheckPos, -Vector(10,10,10), Vector(10,10,10), 255,255,255, 0, 0.1 );
|
|
|
|
// Play a scream when we're almost within bite range
|
|
PlayLiftingScream( flBiteZOffset );
|
|
|
|
// Figure out when the prey has reached our bite range
|
|
if ( GetAbsOrigin().z - m_vecTip.Get().z < flBiteZOffset )
|
|
{
|
|
if ( m_hTongueTip )
|
|
{
|
|
m_hTongueTip->m_pSpring->SetSpringConstant( BARNACLE_TONGUE_SPRING_CONSTANT_HANGING );
|
|
}
|
|
|
|
// Wait until the physics object stops flailing
|
|
if ( !WaitForPhysicsObjectToSettle( flBiteZOffset ) )
|
|
return;
|
|
|
|
// Necessary for good +use interactions
|
|
pVictim->RemoveEFlags( EFL_IS_BEING_LIFTED_BY_BARNACLE );
|
|
|
|
// If we got a physics prop, wait until the thing has settled down
|
|
m_bLiftingPrey = false;
|
|
|
|
if ( hl2_episodic.GetBool() )
|
|
{
|
|
CBounceBomb *pBounce = dynamic_cast<CBounceBomb *>( pVictim );
|
|
|
|
if ( pBounce )
|
|
{
|
|
if ( m_bSwallowingBomb == true )
|
|
{
|
|
pBounce->ExplodeThink();
|
|
return;
|
|
}
|
|
|
|
SetActivity( (Activity)ACT_BARNACLE_BITE_SMALL_THINGS );
|
|
}
|
|
else
|
|
{
|
|
// Start the bite animation. The anim event in it will finish the job.
|
|
SetActivity( (Activity)ACT_BARNACLE_TASTE_SPIT );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Start the bite animation. The anim event in it will finish the job.
|
|
SetActivity( (Activity)ACT_BARNACLE_TASTE_SPIT );
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// Necessary for good +use interactions
|
|
pVictim->AddEFlags( EFL_IS_BEING_LIFTED_BY_BARNACLE );
|
|
|
|
// Pull the victim towards the mouth
|
|
PullEnemyTorwardsMouth( false );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Lift the prey stuck to our tongue up towards our mouth
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::LiftPrey( void )
|
|
{
|
|
CBaseEntity *pVictim = GetEnemy();
|
|
Assert( GetEnemy() );
|
|
|
|
// Drop the prey if it's been obscured by something
|
|
trace_t tr;
|
|
AI_TraceLine( WorldSpaceCenter(), pVictim->WorldSpaceCenter(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
|
|
if ( !pVictim->IsAlive() || (tr.fraction < 1.0 && tr.m_pEnt != pVictim && tr.m_pEnt != m_hRagdoll) )
|
|
{
|
|
if ( !GetEnemy()->IsPlayer() )
|
|
{
|
|
// ignore the object so we don't get into a loop of trying to pick it up.
|
|
m_hLastSpitEnemy = GetEnemy();
|
|
}
|
|
LostPrey( false );
|
|
return;
|
|
}
|
|
|
|
// Height from the barnacle's origin to the point at which it bites
|
|
float flBiteZOffset = 60.0;
|
|
|
|
if ( IsEnemyAPlayer() )
|
|
{
|
|
LiftPlayer(flBiteZOffset);
|
|
}
|
|
else if ( IsEnemyARagdoll() )
|
|
{
|
|
LiftRagdoll(flBiteZOffset);
|
|
}
|
|
else if ( IsEnemyAnNPC() )
|
|
{
|
|
LiftNPC(flBiteZOffset);
|
|
}
|
|
else
|
|
{
|
|
LiftPhysicsObject(flBiteZOffset);
|
|
}
|
|
|
|
if ( m_hRagdoll )
|
|
{
|
|
QAngle newAngles( 0, m_hRagdoll->GetAbsAngles()[YAW], 0 );
|
|
|
|
Vector centerDelta = m_hRagdoll->WorldSpaceCenter() - GetEnemy()->WorldSpaceCenter();
|
|
Vector newOrigin = GetEnemy()->GetAbsOrigin() + centerDelta;
|
|
GetEnemy()->SetAbsOrigin( newOrigin );
|
|
GetEnemy()->SetAbsAngles( newAngles );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Attach a serverside ragdoll prop for the specified entity to our tongue
|
|
//-----------------------------------------------------------------------------
|
|
CRagdollProp *CNPC_Barnacle::AttachRagdollToTongue( CBaseAnimating *pAnimating )
|
|
{
|
|
// Find his head bone
|
|
m_iGrabbedBoneIndex = -1;
|
|
Vector vecNeckOffset;
|
|
|
|
if ( m_hTongueTip )
|
|
{
|
|
vecNeckOffset = (pAnimating->EyePosition() - m_hTongueTip->GetAbsOrigin());
|
|
}
|
|
|
|
CStudioHdr *pHdr = pAnimating->GetModelPtr();
|
|
if ( pHdr )
|
|
{
|
|
int set = pAnimating->GetHitboxSet();
|
|
for( int i = 0; i < pHdr->iHitboxCount(set); i++ )
|
|
{
|
|
mstudiobbox_t *pBox = pHdr->pHitbox( i, set );
|
|
if ( !pBox )
|
|
continue;
|
|
|
|
if ( pBox->group == HITGROUP_HEAD )
|
|
{
|
|
m_iGrabbedBoneIndex = pBox->bone;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// HACK: Until we have correctly assigned hitgroups on our models, lookup the bones
|
|
// for the models that we know are in the barnacle maps.
|
|
//m_iGrabbedBoneIndex = pAnimating->LookupBone( "Bip01 L Foot" );
|
|
if ( m_iGrabbedBoneIndex == -1 )
|
|
{
|
|
// Citizens, Conscripts
|
|
m_iGrabbedBoneIndex = pAnimating->LookupBone( "Bip01 Head" );
|
|
}
|
|
if ( m_iGrabbedBoneIndex == -1 )
|
|
{
|
|
// Metrocops, Combine soldiers
|
|
m_iGrabbedBoneIndex = pAnimating->LookupBone( "ValveBiped.Bip01_Head1" );
|
|
}
|
|
if ( m_iGrabbedBoneIndex == -1 )
|
|
{
|
|
// Vortigaunts
|
|
m_iGrabbedBoneIndex = pAnimating->LookupBone( "ValveBiped.head" );
|
|
}
|
|
if ( m_iGrabbedBoneIndex == -1 )
|
|
{
|
|
// Bullsquids
|
|
m_iGrabbedBoneIndex = pAnimating->LookupBone( "Bullsquid.Head_Bone1" );
|
|
}
|
|
|
|
if ( m_iGrabbedBoneIndex == -1 )
|
|
{
|
|
// Just use the first bone
|
|
m_iGrabbedBoneIndex = 0;
|
|
}
|
|
|
|
// Move the tip to the bone
|
|
Vector vecBonePos;
|
|
QAngle vecBoneAngles;
|
|
pAnimating->GetBonePosition( m_iGrabbedBoneIndex, vecBonePos, vecBoneAngles );
|
|
|
|
if ( m_hTongueTip )
|
|
{
|
|
m_hTongueTip->Teleport( &vecBonePos, NULL, NULL );
|
|
}
|
|
|
|
//NDebugOverlay::Box( vecBonePos, -Vector(5,5,5), Vector(5,5,5), 255,255,255, 0, 10.0 );
|
|
|
|
// Create the ragdoll attached to tongue
|
|
IPhysicsObject *pTonguePhysObject = m_hTongueTip->VPhysicsGetObject();
|
|
CRagdollProp *pRagdoll = CreateServerRagdollAttached( pAnimating, vec3_origin, -1, COLLISION_GROUP_NONE, pTonguePhysObject, m_hTongueTip, 0, vecBonePos, m_iGrabbedBoneIndex, vec3_origin );
|
|
if ( pRagdoll )
|
|
{
|
|
pRagdoll->DisableAutoFade();
|
|
pRagdoll->SetThink( NULL );
|
|
}
|
|
|
|
return pRagdoll;
|
|
}
|
|
|
|
void CNPC_Barnacle::InputSetDropTongueSpeed( inputdata_t &inputdata )
|
|
{
|
|
m_flBarnaclePullSpeed = inputdata.value.Int();
|
|
}
|
|
|
|
void CNPC_Barnacle::InputDropTongue( inputdata_t &inputdata )
|
|
{
|
|
DropTongue();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Grab the specified target with our tongue
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::AttachTongueToTarget( CBaseEntity *pTouchEnt, Vector vecGrabPos )
|
|
{
|
|
// Reset this value each time we attach prey. If it needs to be reduced, code below will do so.
|
|
m_flBarnaclePullSpeed = BARNACLE_PULL_SPEED;
|
|
|
|
if ( RandomFloat(0,1) > 0.5 )
|
|
{
|
|
EmitSound( "NPC_Barnacle.PullPant" );
|
|
}
|
|
else
|
|
{
|
|
EmitSound( "NPC_Barnacle.TongueStretch" );
|
|
}
|
|
|
|
SetActivity( (Activity)ACT_BARNACLE_SLURP );
|
|
|
|
// Get the player out of the vehicle he's in.
|
|
if ( pTouchEnt->IsPlayer() )
|
|
{
|
|
CBasePlayer *pPlayer = static_cast<CBasePlayer*>(pTouchEnt);
|
|
if ( pPlayer->IsInAVehicle() )
|
|
{
|
|
pPlayer->LeaveVehicle( pPlayer->GetAbsOrigin(), pPlayer->GetAbsAngles() );
|
|
|
|
// The player could have warped through the tongue while on a high-speed vehicle.
|
|
// Move him back under the barnacle.
|
|
Vector vecDelta;
|
|
VectorSubtract( pPlayer->GetAbsOrigin(), GetAbsOrigin(), vecDelta );
|
|
vecDelta.z = 0.0f;
|
|
float flDist = VectorNormalize( vecDelta );
|
|
if ( flDist > 20 )
|
|
{
|
|
Vector vecNewPos;
|
|
VectorMA( GetAbsOrigin(), 20, vecDelta, vecNewPos );
|
|
vecNewPos.z = pPlayer->GetAbsOrigin().z;
|
|
pPlayer->SetAbsOrigin( vecNewPos );
|
|
}
|
|
}
|
|
|
|
m_bPlayerWasStanding = ( ( pPlayer->GetFlags() & FL_DUCKING ) == 0 );
|
|
}
|
|
|
|
SetEnemy( pTouchEnt );
|
|
|
|
if ( pTouchEnt->IsPlayer() || pTouchEnt->MyNPCPointer() )
|
|
{
|
|
Vector origin = GetAbsOrigin();
|
|
origin.z = pTouchEnt->GetAbsOrigin().z;
|
|
|
|
CTraceFilterSkipTwoEntities traceFilter( this, pTouchEnt, COLLISION_GROUP_NONE );
|
|
trace_t placementTrace;
|
|
UTIL_TraceHull( origin, origin, pTouchEnt->WorldAlignMins(), pTouchEnt->WorldAlignMaxs(), MASK_NPCSOLID, &traceFilter, &placementTrace );
|
|
if ( placementTrace.startsolid )
|
|
{
|
|
UTIL_TraceHull( origin + Vector(0, 0, 24), origin, pTouchEnt->WorldAlignMins(), pTouchEnt->WorldAlignMaxs(), MASK_NPCSOLID, &traceFilter, &placementTrace );
|
|
if ( !placementTrace.startsolid )
|
|
pTouchEnt->SetAbsOrigin( placementTrace.endpos );
|
|
}
|
|
else
|
|
{
|
|
pTouchEnt->SetAbsOrigin( origin );
|
|
}
|
|
}
|
|
|
|
m_nShakeCount = 6;
|
|
m_bLiftingPrey = true;// indicate that we should be lifting prey.
|
|
SetAltitude( (GetAbsOrigin().z - vecGrabPos.z) );
|
|
m_bPlayedPullSound = false;
|
|
|
|
CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating*>(pTouchEnt);
|
|
|
|
if ( IsEnemyAPlayer() || IsEnemyAPhysicsObject() )
|
|
{
|
|
// The player (and phys objects) doesn't ragdoll, so just grab him and pull him up manually
|
|
IPhysicsObject *pPlayerPhys = pTouchEnt->VPhysicsGetObject();
|
|
IPhysicsObject *pTonguePhys = m_hTongueTip->VPhysicsGetObject();
|
|
|
|
Vector vecGrabPos;
|
|
if ( pTouchEnt->IsPlayer() )
|
|
{
|
|
vecGrabPos = pTouchEnt->EyePosition();
|
|
}
|
|
else
|
|
{
|
|
VectorSubtract( m_vecTip, pTouchEnt->GetAbsOrigin(), vecGrabPos );
|
|
VectorNormalize( vecGrabPos );
|
|
vecGrabPos = physcollision->CollideGetExtent( pPlayerPhys->GetCollide(), pTouchEnt->GetAbsOrigin(), pTouchEnt->GetAbsAngles(), vecGrabPos );
|
|
}
|
|
|
|
m_hTongueTip->Teleport( &vecGrabPos, NULL, NULL );
|
|
|
|
float flDist = (vecGrabPos - GetAbsOrigin() ).Length();
|
|
float flTime = flDist / m_flBarnaclePullSpeed;
|
|
|
|
// If this object would be pulled in too quickly, change the pull speed.
|
|
if( flTime < BARNACLE_MIN_PULL_TIME )
|
|
{
|
|
m_flBarnaclePullSpeed = flDist / BARNACLE_MIN_PULL_TIME;
|
|
}
|
|
|
|
constraint_fixedparams_t fixed;
|
|
fixed.Defaults();
|
|
fixed.InitWithCurrentObjectState( pTonguePhys, pPlayerPhys );
|
|
fixed.constraint.Defaults();
|
|
m_pConstraint = physenv->CreateFixedConstraint( pTonguePhys, pPlayerPhys, NULL, fixed );
|
|
|
|
// Increase the tongue's spring constant while lifting
|
|
m_hTongueTip->m_pSpring->SetSpringConstant( BARNACLE_TONGUE_SPRING_CONSTANT_LIFTING );
|
|
UpdateTongue();
|
|
|
|
return;
|
|
}
|
|
|
|
// NPC case...
|
|
pAnimating->InvalidateBoneCache();
|
|
|
|
// Make a ragdoll for the guy, and hide him.
|
|
pTouchEnt->AddSolidFlags( FSOLID_NOT_SOLID );
|
|
|
|
m_hRagdoll = AttachRagdollToTongue( pAnimating );
|
|
m_hRagdoll->SetDamageEntity( pAnimating );
|
|
|
|
// Make it try to blend out of ragdoll on the client on deletion
|
|
// NOTE: This isn't fully implemented, so disable
|
|
//m_hRagdoll->SetUnragdoll( pAnimating );
|
|
|
|
// Apply the target's current velocity to each of the ragdoll's bones
|
|
Vector vecVelocity = pAnimating->GetGroundSpeedVelocity() * 0.5;
|
|
ragdoll_t *pRagdoll = m_hRagdoll->GetRagdoll();
|
|
|
|
// barnacle might let go if ragdoll is separated - so increase the separation checking a bit
|
|
constraint_groupparams_t params;
|
|
pRagdoll->pGroup->GetErrorParams( ¶ms );
|
|
params.minErrorTicks = min( params.minErrorTicks, 5 );
|
|
pRagdoll->pGroup->SetErrorParams( params );
|
|
|
|
for ( int i = 0; i < pRagdoll->listCount; i++ )
|
|
{
|
|
pRagdoll->list[i].pObject->AddVelocity( &vecVelocity, NULL );
|
|
}
|
|
|
|
if ( npc_barnacle_swallow.GetBool() )
|
|
{
|
|
m_hRagdoll->SetOverlaySequence( ACT_GESTURE_BARNACLE_STRANGLE );
|
|
m_hRagdoll->SetBlendWeight( 1.0f );
|
|
}
|
|
|
|
// Now hide the actual enemy
|
|
pTouchEnt->AddEffects( EF_NODRAW );
|
|
|
|
// Increase the tongue's spring constant while lifting
|
|
m_hTongueTip->m_pSpring->SetSpringConstant( BARNACLE_TONGUE_SPRING_CONSTANT_LIFTING );
|
|
UpdateTongue();
|
|
|
|
// Store off the current bone matrix so we have it next frame
|
|
pAnimating->SetupBones( m_pRagdollBones, BONE_USED_BY_ANYTHING );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Spit out the prey; add physics force!
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::SpitPrey()
|
|
{
|
|
if ( GetEnemy() )
|
|
{
|
|
IPhysicsObject *pObject = GetEnemy()->VPhysicsGetObject();
|
|
if (pObject)
|
|
{
|
|
Vector vecPosition, force;
|
|
GetAttachment( m_nSpitAttachment, vecPosition, &force );
|
|
|
|
force *= pObject->GetMass() * 50.0f;
|
|
pObject->ApplyForceOffset( force, vec3_origin );
|
|
}
|
|
|
|
m_hLastSpitEnemy = GetEnemy();
|
|
}
|
|
|
|
LostPrey( false );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Prey is in position, bite them and start swallowing them
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::BitePrey( void )
|
|
{
|
|
Assert( GetEnemy() );
|
|
|
|
CBaseCombatCharacter *pVictim = GetEnemyCombatCharacterPointer();
|
|
|
|
if ( pVictim == NULL )
|
|
{
|
|
if ( hl2_episodic.GetBool() )
|
|
{
|
|
if ( GetEnemy() )
|
|
{
|
|
CBounceBomb *pBounce = dynamic_cast<CBounceBomb *>( GetEnemy() );
|
|
|
|
if ( pBounce )
|
|
{
|
|
// Stop the ragdoll moving and start to pull the sucker up into our mouth
|
|
m_bSwallowingPrey = true;
|
|
m_bSwallowingBomb = true;
|
|
IPhysicsObject *pTonguePhys = m_hTongueTip->VPhysicsGetObject();
|
|
|
|
// Stop the tongue's spring getting in the way of swallowing
|
|
m_hTongueTip->m_pSpring->SetSpringConstant( 0 );
|
|
|
|
// Switch the tongue tip to shadow and drag it up
|
|
pTonguePhys->SetShadow( 1e4, 1e4, false, false );
|
|
pTonguePhys->UpdateShadow( m_hTongueTip->GetAbsOrigin(), m_hTongueTip->GetAbsAngles(), false, 0 );
|
|
m_hTongueTip->SetMoveType( MOVETYPE_NOCLIP );
|
|
m_hTongueTip->SetAbsVelocity( Vector(0,0,32) );
|
|
|
|
|
|
SetAltitude( (GetAbsOrigin().z - m_hTongueTip->GetAbsOrigin().z) );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Assert( pVictim );
|
|
|
|
EmitSound( "NPC_Barnacle.FinalBite" );
|
|
|
|
m_flVictimHeight = GetEnemy()->WorldAlignSize().z;
|
|
|
|
// Kill the victim instantly
|
|
int iDamageType = DMG_SLASH | DMG_ALWAYSGIB;
|
|
int nDamage;
|
|
if ( !pVictim->IsPlayer() )
|
|
{
|
|
iDamageType |= DMG_ALWAYSGIB;
|
|
nDamage = pVictim->m_iHealth;
|
|
}
|
|
else
|
|
{
|
|
nDamage = BARNACLE_BITE_DAMAGE_TO_PLAYER;
|
|
}
|
|
|
|
if ( m_hRagdoll )
|
|
{
|
|
// We've got a ragdoll, so prevent this creating another one
|
|
iDamageType |= DMG_REMOVENORAGDOLL;
|
|
m_hRagdoll->SetDamageEntity( NULL );
|
|
}
|
|
|
|
// DMG_CRUSH because we don't wan't to impart physics forces
|
|
pVictim->TakeDamage( CTakeDamageInfo( this, this, nDamage, iDamageType | DMG_CRUSH ) );
|
|
m_cGibs = 3;
|
|
|
|
// In episodic, bite the zombie's headcrab off & drop the body
|
|
if ( hl2_episodic.GetBool() && GetEnemy()->Classify() == CLASS_ZOMBIE )
|
|
{
|
|
if ( m_hRagdoll )
|
|
{
|
|
m_hRagdoll->SetBodygroup( ZOMBIE_BODYGROUP_HEADCRAB, false );
|
|
DetachAttachedRagdoll( m_hRagdoll );
|
|
m_hLastSpitEnemy = m_hRagdoll.Get();
|
|
m_hRagdoll->EmitSound( "NPC_HeadCrab.Die" );
|
|
m_hRagdoll = NULL;
|
|
}
|
|
|
|
// Create some blood to hide the vanishing headcrab
|
|
Vector vecBloodPos;
|
|
CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 0.0f ), &vecBloodPos );
|
|
UTIL_BloodSpray( vecBloodPos, Vector(0,0,-1), GetEnemy()->BloodColor(), 8, FX_BLOODSPRAY_ALL );
|
|
|
|
m_flDigestFinish = gpGlobals->curtime + 10.0;
|
|
return;
|
|
}
|
|
|
|
// Players are never swallowed, nor is anything we don't have a ragdoll for
|
|
if ( !m_hRagdoll || pVictim->IsPlayer() )
|
|
{
|
|
if ( !pVictim->IsPlayer() || pVictim->GetHealth() <= 0 )
|
|
{
|
|
LostPrey( false );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Stop the ragdoll moving and start to pull the sucker up into our mouth
|
|
m_bSwallowingPrey = true;
|
|
IPhysicsObject *pTonguePhys = m_hTongueTip->VPhysicsGetObject();
|
|
|
|
// Make it nonsolid to the world so we can pull it through the roof
|
|
PhysDisableEntityCollisions( m_hRagdoll->VPhysicsGetObject(), g_PhysWorldObject );
|
|
|
|
// Stop the tongue's spring getting in the way of swallowing
|
|
m_hTongueTip->m_pSpring->SetSpringConstant( 0 );
|
|
|
|
// Switch the tongue tip to shadow and drag it up
|
|
pTonguePhys->SetShadow( 1e4, 1e4, false, false );
|
|
pTonguePhys->UpdateShadow( m_hTongueTip->GetAbsOrigin(), m_hTongueTip->GetAbsAngles(), false, 0 );
|
|
m_hTongueTip->SetMoveType( MOVETYPE_NOCLIP );
|
|
m_hTongueTip->SetAbsVelocity( Vector(0,0,32) );
|
|
|
|
SetAltitude( (GetAbsOrigin().z - m_hTongueTip->GetAbsOrigin().z) );
|
|
|
|
if ( !npc_barnacle_swallow.GetBool() )
|
|
return;
|
|
|
|
// Because the victim is dead, remember the blood color
|
|
m_flNextBloodTime = 0.0f;
|
|
|
|
// NOTE: This was too confusing to people with the more recognizable blood -- jdw
|
|
#ifndef _XBOX
|
|
m_nBloodColor = pVictim->BloodColor();
|
|
#endif
|
|
CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 0.0f ), &m_vecBloodPos );
|
|
|
|
// m_hRagdoll->SetOverlaySequence( ACT_DIE_BARNACLE_SWALLOW );
|
|
m_hRagdoll->SetBlendWeight( 0.0f );
|
|
|
|
SprayBlood();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::SprayBlood()
|
|
{
|
|
if ( gpGlobals->curtime < m_flNextBloodTime )
|
|
return;
|
|
|
|
m_flNextBloodTime = gpGlobals->curtime + 0.2f;
|
|
|
|
Vector bloodDir = RandomVector( -1.0f, 1.0f );
|
|
bloodDir.z = -fabs( bloodDir.z );
|
|
|
|
Vector jitterPos = RandomVector( -8, 8 );
|
|
jitterPos.z = 0.0f;
|
|
|
|
#ifndef _XBOX
|
|
UTIL_BloodSpray( m_vecBloodPos + jitterPos, Vector( 0,0,-1),
|
|
m_nBloodColor, RandomInt( 4, 8 ), RandomInt(0,2) == 0 ? FX_BLOODSPRAY_ALL : FX_BLOODSPRAY_CLOUD );
|
|
#else
|
|
UTIL_BloodSpray( m_vecBloodPos + jitterPos, Vector( 0,0,-1),
|
|
BLOOD_COLOR_YELLOW, RandomInt( 4, 8 ), RandomInt(0,2) == 0 ? FX_BLOODSPRAY_ALL : FX_BLOODSPRAY_CLOUD );
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Slowly swallow the prey whole. Only used on humanoids.
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::SwallowPrey( void )
|
|
{
|
|
if ( IsActivityFinished() )
|
|
{
|
|
if (GetActivity() == ACT_BARNACLE_BITE_HUMAN )
|
|
{
|
|
SetActivity( (Activity)ACT_BARNACLE_CHEW_HUMAN );
|
|
}
|
|
else
|
|
{
|
|
SetActivity( (Activity)ACT_BARNACLE_CHEW_SMALL_THINGS );
|
|
}
|
|
}
|
|
|
|
// Move the body up slowly
|
|
Vector vecSwallowPos = m_hTongueTip->GetAbsOrigin();
|
|
vecSwallowPos.z -= m_flVictimHeight;
|
|
//NDebugOverlay::Box( vecSwallowPos, -Vector(5,5,5), Vector(5,5,5), 255,255,255, 0, 0.1 );
|
|
|
|
// bite prey every once in a while
|
|
if ( random->RandomInt(0,25) == 0 )
|
|
{
|
|
EmitSound( "NPC_Barnacle.Digest" );
|
|
}
|
|
|
|
// Fully swallowed it?
|
|
float flDistanceToGo = GetAbsOrigin().z - vecSwallowPos.z;
|
|
if ( flDistanceToGo <= 0 )
|
|
{
|
|
// He's dead jim
|
|
m_bSwallowingPrey = false;
|
|
m_hTongueTip->SetAbsVelocity( vec3_origin );
|
|
m_flDigestFinish = gpGlobals->curtime + 10.0;
|
|
}
|
|
|
|
if ( npc_barnacle_swallow.GetBool() )
|
|
{
|
|
SprayBlood();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Remove the fake ragdoll and bring the actual enemy back in view
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::RemoveRagdoll( bool bDestroyRagdoll )
|
|
{
|
|
// Destroy the tongue tip constraint
|
|
if ( m_pConstraint )
|
|
{
|
|
physenv->DestroyConstraint( m_pConstraint );
|
|
m_pConstraint = NULL;
|
|
}
|
|
|
|
// Remove the ragdoll
|
|
if ( m_hRagdoll )
|
|
{
|
|
// Only destroy the ragdoll if told to. We might be just dropping
|
|
// the ragdoll because the target was killed on the way up.
|
|
m_hRagdoll->SetDamageEntity( NULL );
|
|
if ( npc_barnacle_swallow.GetBool() )
|
|
{
|
|
m_hRagdoll->SetThink( NULL );
|
|
m_hRagdoll->SetBlendWeight( 1.0f );
|
|
}
|
|
DetachAttachedRagdoll( m_hRagdoll );
|
|
if ( bDestroyRagdoll )
|
|
{
|
|
UTIL_Remove( m_hRagdoll );
|
|
}
|
|
m_hRagdoll = NULL;
|
|
|
|
// Reduce the spring constant while we lower
|
|
m_hTongueTip->m_pSpring->SetSpringConstant( BARNACLE_TONGUE_SPRING_CONSTANT_LOWERING );
|
|
|
|
// Unhide the enemy
|
|
if ( GetEnemy() )
|
|
{
|
|
GetEnemy()->RemoveEffects( EF_NODRAW );
|
|
GetEnemy()->RemoveSolidFlags( FSOLID_NOT_SOLID );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: For some reason (he was killed, etc) we lost the prey we were dragging towards our mouth.
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::LostPrey( bool bRemoveRagdoll )
|
|
{
|
|
if ( GetEnemy() )
|
|
{
|
|
//No one survives being snatched by a barnacle anymore, so leave
|
|
// this flag set so that their entity gets removed.
|
|
//GetEnemy()->RemoveEFlags( EFL_IS_BEING_LIFTED_BY_BARNACLE );
|
|
CBaseCombatCharacter *pVictim = GetEnemyCombatCharacterPointer();
|
|
if ( pVictim )
|
|
{
|
|
pVictim->DispatchInteraction( g_interactionBarnacleVictimReleased, NULL, this );
|
|
pVictim->RemoveEFlags( EFL_IS_BEING_LIFTED_BY_BARNACLE );
|
|
|
|
if ( m_hRagdoll )
|
|
{
|
|
QAngle newAngles( 0, m_hRagdoll->GetAbsAngles()[ YAW ], 0 );
|
|
|
|
Vector centerDelta = m_hRagdoll->WorldSpaceCenter() - GetEnemy()->WorldSpaceCenter();
|
|
Vector newOrigin = GetEnemy()->GetAbsOrigin() + centerDelta;
|
|
GetEnemy()->SetAbsOrigin( newOrigin );
|
|
|
|
pVictim->SetAbsAngles( newAngles );
|
|
}
|
|
pVictim->SetGroundEntity( NULL );
|
|
}
|
|
else if ( IsEnemyAPhysicsObject() )
|
|
{
|
|
// If we're a physics object, then we need to clear this flag
|
|
GetEnemy()->RemoveEFlags( EFL_IS_BEING_LIFTED_BY_BARNACLE );
|
|
}
|
|
}
|
|
|
|
|
|
RemoveRagdoll( bRemoveRagdoll );
|
|
m_bLiftingPrey = false;
|
|
m_bSwallowingPrey = false;
|
|
SetEnemy( NULL );
|
|
|
|
if ( m_hTongueTip )
|
|
{
|
|
// Remove our tongue's shadow object, in case we just finished swallowing something
|
|
IPhysicsObject *pPhysicsObject = m_hTongueTip->VPhysicsGetObject();
|
|
if ( pPhysicsObject && pPhysicsObject->GetShadowController() )
|
|
{
|
|
Vector vecCenter = WorldSpaceCenter();
|
|
m_hTongueTip->Teleport( &vecCenter, NULL, &vec3_origin );
|
|
|
|
// Reduce the spring constant while we lower
|
|
m_hTongueTip->m_pSpring->SetSpringConstant( BARNACLE_TONGUE_SPRING_CONSTANT_LOWERING );
|
|
|
|
// Start colliding with the world again
|
|
pPhysicsObject->RemoveShadowController();
|
|
m_hTongueTip->SetMoveType( MOVETYPE_VPHYSICS );
|
|
pPhysicsObject->EnableMotion( true );
|
|
pPhysicsObject->EnableGravity( true );
|
|
pPhysicsObject->RecheckCollisionFilter();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// The tongue's vphysics updated
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::OnTongueTipUpdated()
|
|
{
|
|
// Update the tip's position
|
|
const Vector &vecNewTip = m_hTongueTip->GetAbsOrigin();
|
|
if ( vecNewTip != m_vecTip )
|
|
{
|
|
m_vecTip = vecNewTip;
|
|
CollisionProp()->MarkSurroundingBoundsDirty();
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Update the positions of the tongue points
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::UpdateTongue( void )
|
|
{
|
|
if ( m_hTongueTip == NULL )
|
|
return;
|
|
|
|
// Set the spring's length to that of the tongue's extension
|
|
|
|
// Compute the rest length of the tongue based on the spring.
|
|
// This occurs when mg == kx or x = mg/k
|
|
float flRestStretch = (BARNACLE_TONGUE_TIP_MASS * sv_gravity.GetFloat()) / BARNACLE_TONGUE_SPRING_CONSTANT_HANGING;
|
|
|
|
// FIXME: HACK!!!! The code above doesn't quite make the tip end up in the right place.
|
|
// but it should. So, we're gonna hack it the rest of the way.
|
|
flRestStretch += 4;
|
|
|
|
m_hTongueTip->m_pSpring->SetSpringLength( m_flAltitude - flRestStretch );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::SpawnDeathGibs( void )
|
|
{
|
|
bool bDroppedAny = false;
|
|
|
|
// Drop a random number of gibs
|
|
for ( int i=0; i < ARRAYSIZE(m_szGibNames); i++ )
|
|
{
|
|
if ( random->RandomInt( 0, 1 ) )
|
|
{
|
|
CGib::SpawnSpecificGibs( this, 1, 32, 1, m_szGibNames[i] );
|
|
bDroppedAny = true;
|
|
}
|
|
}
|
|
|
|
// Make sure we at least drop something
|
|
if ( bDroppedAny == false )
|
|
{
|
|
CGib::SpawnSpecificGibs( this, 1, 32, 1, m_szGibNames[0] );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Barnacle::Event_Killed( const CTakeDamageInfo &info )
|
|
{
|
|
m_OnDeath.FireOutput( info.GetAttacker(), this );
|
|
|
|
AddSolidFlags( FSOLID_NOT_SOLID );
|
|
m_takedamage = DAMAGE_NO;
|
|
m_lifeState = LIFE_DYING;
|
|
|
|
// Are we lifting prey?
|
|
if ( GetEnemy() )
|
|
{
|
|
// Cleanup
|
|
LostPrey( false );
|
|
}
|
|
else if ( m_bSwallowingPrey && m_hRagdoll )
|
|
{
|
|
// We're swallowing a body. Make it stick inside us.
|
|
m_hTongueTip->SetAbsVelocity( vec3_origin );
|
|
|
|
m_hRagdoll->StopFollowingEntity();
|
|
m_hRagdoll->SetMoveType( MOVETYPE_VPHYSICS );
|
|
m_hRagdoll->SetAbsOrigin( m_hTongueTip->GetAbsOrigin() );
|
|
m_hRagdoll->RemoveSolidFlags( FSOLID_NOT_SOLID );
|
|
m_hRagdoll->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
|
|
m_hRagdoll->RecheckCollisionFilter();
|
|
if ( npc_barnacle_swallow.GetBool() )
|
|
{
|
|
m_hRagdoll->SetThink( NULL );
|
|
m_hRagdoll->SetBlendWeight( 1.0f );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Destroy the ragdoll->tongue tip constraint
|
|
if ( m_pConstraint )
|
|
{
|
|
physenv->DestroyConstraint( m_pConstraint );
|
|
m_pConstraint = NULL;
|
|
}
|
|
LostPrey( true );
|
|
}
|
|
|
|
// Puke gibs unless we're told to be cheap
|
|
bool spawnGibs = ( !HasSpawnFlags( SF_BARNACLE_CHEAP_DEATH ) || random->RandomInt( 0, 1 ) );
|
|
|
|
if ( spawnGibs )
|
|
{
|
|
SpawnDeathGibs();
|
|
}
|
|
|
|
// Puke blood
|
|
#ifdef _XBOX
|
|
UTIL_BloodSpray( GetAbsOrigin(), Vector(0,0,-1), BLOOD_COLOR_YELLOW, 8, FX_BLOODSPRAY_ALL );
|
|
#else
|
|
UTIL_BloodSpray( GetAbsOrigin(), Vector(0,0,-1), BLOOD_COLOR_RED, 8, FX_BLOODSPRAY_ALL );
|
|
#endif
|
|
|
|
// Put blood on the ground if near enough
|
|
trace_t bloodTrace;
|
|
AI_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 256 ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &bloodTrace);
|
|
|
|
if ( bloodTrace.fraction < 1.0f )
|
|
{
|
|
#ifdef _XBOX
|
|
UTIL_BloodDecalTrace( &bloodTrace, BLOOD_COLOR_YELLOW );
|
|
#else
|
|
UTIL_BloodDecalTrace( &bloodTrace, BLOOD_COLOR_RED );
|
|
#endif
|
|
}
|
|
|
|
EmitSound( "NPC_Barnacle.Die" );
|
|
|
|
SetActivity( ACT_DIESIMPLE );
|
|
|
|
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();
|
|
}
|
|
|
|
float goalAltitude = BARNACLE_DEAD_TONGUE_ALTITUDE;
|
|
|
|
trace_t tr;
|
|
AI_TraceLine( m_vecRoot.Get(), m_vecRoot.Get() - Vector( 0, 0, 256 ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
|
|
|
|
if ( tr.fraction < 1.0 )
|
|
{
|
|
float distToFloor = ( m_vecRoot.Get() - tr.endpos ).Length();
|
|
float clearance = distToFloor - goalAltitude;
|
|
|
|
if ( clearance < BARNACLE_MIN_DEAD_TONGUE_CLEARANCE )
|
|
{
|
|
if ( distToFloor - BARNACLE_MIN_DEAD_TONGUE_CLEARANCE > distToFloor * .5 )
|
|
{
|
|
goalAltitude = distToFloor - BARNACLE_MIN_DEAD_TONGUE_CLEARANCE;
|
|
}
|
|
else
|
|
{
|
|
goalAltitude = distToFloor * .5;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Keep moving the tongue to its dead position
|
|
// FIXME: This stupid algorithm is necessary because
|
|
// I can't seem to get reproduceable behavior from springs
|
|
bool bTongueInPosition = false;
|
|
float flDist = m_vecRoot.Get().z - m_vecTip.Get().z;
|
|
if ( fabs(flDist - goalAltitude) > 20.0f )
|
|
{
|
|
float flNewAltitude;
|
|
float dt = gpGlobals->curtime - GetLastThink();
|
|
if ( m_flAltitude >= goalAltitude )
|
|
{
|
|
flNewAltitude = max( goalAltitude, m_flAltitude - m_flBarnaclePullSpeed * dt );
|
|
}
|
|
else
|
|
{
|
|
flNewAltitude = min( goalAltitude, m_flAltitude + m_flBarnaclePullSpeed * dt );
|
|
}
|
|
SetAltitude( flNewAltitude );
|
|
}
|
|
else
|
|
{
|
|
// Wait for settling...
|
|
IPhysicsObject *pTipObject = m_hTongueTip->VPhysicsGetObject();
|
|
|
|
Vector vecVelocity;
|
|
AngularImpulse angVel;
|
|
pTipObject->GetVelocity( &vecVelocity, &angVel );
|
|
if ( vecVelocity.LengthSqr() < 1.0f )
|
|
{
|
|
// We may need to have a heavier spring constant until we settle
|
|
// to avoid strange looking rest conditions (when the tongue is really bent from
|
|
// picking up a barrel, it looks strange to switch to the hanging constant)
|
|
m_hTongueTip->m_pSpring->SetSpringConstant( BARNACLE_TONGUE_SPRING_CONSTANT_HANGING );
|
|
if ( fabs(flDist - goalAltitude) > 1.0f )
|
|
{
|
|
float flSign = ( flDist > goalAltitude ) ? -1.0f : 1.0f;
|
|
SetAltitude( m_flAltitude + flSign );
|
|
}
|
|
else if ( vecVelocity.LengthSqr() < 0.01f )
|
|
{
|
|
bTongueInPosition = ( fabs(flDist - goalAltitude) <= 1.0f );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( IsActivityFinished() && bTongueInPosition )
|
|
{
|
|
// Remove our tongue pieces
|
|
UTIL_Remove( m_hTongueTip );
|
|
UTIL_Remove( m_hTongueRoot );
|
|
m_hTongueTip = NULL;
|
|
m_hTongueRoot = NULL;
|
|
|
|
SetThink ( NULL );
|
|
m_lifeState = LIFE_DEAD;
|
|
}
|
|
else
|
|
{
|
|
UpdateTongue();
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
// Precache - precaches all resources this monster needs
|
|
//=========================================================
|
|
void CNPC_Barnacle::Precache()
|
|
{
|
|
PrecacheModel("models/barnacle.mdl");
|
|
|
|
// Precache all gibs
|
|
for ( int i=0; i < ARRAYSIZE(m_szGibNames); i++ )
|
|
{
|
|
PrecacheModel( m_szGibNames[i] );
|
|
}
|
|
|
|
PrecacheScriptSound( "NPC_Barnacle.Digest" );
|
|
PrecacheScriptSound( "NPC_Barnacle.Scream" );
|
|
PrecacheScriptSound( "NPC_Barnacle.PullPant" );
|
|
PrecacheScriptSound( "NPC_Barnacle.TongueStretch" );
|
|
PrecacheScriptSound( "NPC_Barnacle.FinalBite" );
|
|
PrecacheScriptSound( "NPC_Barnacle.Die" );
|
|
PrecacheScriptSound( "NPC_Barnacle.BreakNeck" );
|
|
|
|
PrecacheModel( "models/props_junk/rock001a.mdl" );
|
|
|
|
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.
|
|
//=========================================================
|
|
// enumerate entities that match a set of edict flags into a static array
|
|
class CTongueEntitiesEnum : public IPartitionEnumerator
|
|
{
|
|
public:
|
|
CTongueEntitiesEnum( CBaseEntity **pList, int listMax );
|
|
// This gets called by the enumeration methods with each element
|
|
// that passes the test.
|
|
virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity );
|
|
|
|
int GetCount() { return m_nCount; }
|
|
bool AddToList( CBaseEntity *pEntity );
|
|
|
|
private:
|
|
CBaseEntity **m_pList;
|
|
int m_nListMax;
|
|
int m_nCount;
|
|
};
|
|
|
|
CTongueEntitiesEnum::CTongueEntitiesEnum( CBaseEntity **pList, int listMax )
|
|
{
|
|
m_pList = pList;
|
|
m_nListMax = listMax;
|
|
m_nCount = 0;
|
|
}
|
|
|
|
bool CTongueEntitiesEnum::AddToList( CBaseEntity *pEntity )
|
|
{
|
|
m_pList[m_nCount] = pEntity;
|
|
++m_nCount;
|
|
return ( m_nCount < m_nListMax );
|
|
}
|
|
|
|
IterationRetval_t CTongueEntitiesEnum::EnumElement( IHandleEntity *pHandleEntity )
|
|
{
|
|
CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() );
|
|
if ( pEntity )
|
|
{
|
|
if ( !AddToList( pEntity ) )
|
|
return ITERATION_STOP;
|
|
}
|
|
return ITERATION_CONTINUE;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Barnacle must trace against only brushes and its last enemy
|
|
//-----------------------------------------------------------------------------
|
|
class CBarnacleTongueFilter : public CTraceFilterSimple
|
|
{
|
|
DECLARE_CLASS( CBarnacleTongueFilter, CTraceFilterSimple );
|
|
|
|
public:
|
|
CBarnacleTongueFilter( CBaseEntity *pLastEnemy, const IHandleEntity *passedict, int collisionGroup ) :
|
|
CTraceFilterSimple( passedict, collisionGroup )
|
|
{
|
|
m_pLastEnemy = pLastEnemy;
|
|
m_pBarnacle = const_cast<CBaseEntity*>( EntityFromEntityHandle( passedict ) );
|
|
}
|
|
|
|
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
|
|
{
|
|
if ( pServerEntity == m_pLastEnemy )
|
|
return true;
|
|
|
|
#ifdef HL2_EPISODIC
|
|
CBaseEntity *pEntity = EntityFromEntityHandle( pServerEntity );
|
|
|
|
if ( pEntity )
|
|
{
|
|
if ( FStrEq( STRING( pEntity->m_iClassname ), "func_brush" ) )
|
|
{
|
|
CFuncBrush *pFuncBrush = assert_cast<CFuncBrush *>(pEntity);
|
|
|
|
if ( pFuncBrush->m_bInvertExclusion )
|
|
{
|
|
if ( pFuncBrush->m_iszExcludedClass == m_pBarnacle->m_iClassname )
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if ( pFuncBrush->m_iszExcludedClass != m_pBarnacle->m_iClassname )
|
|
return false;
|
|
|
|
}
|
|
}
|
|
|
|
if ( pEntity->IsBSPModel() == false && pEntity->IsWorld() == false )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return BaseClass::ShouldHitEntity( pServerEntity, contentsMask );
|
|
}
|
|
|
|
private:
|
|
CBaseEntity *m_pLastEnemy;
|
|
CBaseEntity *m_pBarnacle;
|
|
};
|
|
|
|
|
|
#define BARNACLE_CHECK_SPACING 8
|
|
CBaseEntity *CNPC_Barnacle::TongueTouchEnt ( float *pflLength )
|
|
{
|
|
trace_t tr;
|
|
float length;
|
|
|
|
int iMask = MASK_SOLID_BRUSHONLY;
|
|
|
|
#ifdef HL2_EPISODIC
|
|
iMask = MASK_NPCSOLID;
|
|
#endif
|
|
|
|
// trace once to hit architecture and see if the tongue needs to change position.
|
|
CBarnacleTongueFilter tongueFilter( m_hLastSpitEnemy, this, COLLISION_GROUP_NONE );
|
|
AI_TraceLine ( GetAbsOrigin(), GetAbsOrigin() - Vector ( 0 , 0 , 2048 ),
|
|
iMask, &tongueFilter, &tr );
|
|
|
|
length = fabs( GetAbsOrigin().z - tr.endpos.z );
|
|
// Pull it up a tad
|
|
length = max(8, length - m_flRestUnitsAboveGround);
|
|
if ( pflLength )
|
|
{
|
|
*pflLength = length;
|
|
}
|
|
|
|
Vector delta = Vector( BARNACLE_CHECK_SPACING, BARNACLE_CHECK_SPACING, 0 );
|
|
Vector mins = GetAbsOrigin() - delta;
|
|
Vector maxs = GetAbsOrigin() + delta;
|
|
maxs.z = GetAbsOrigin().z;
|
|
mins.z -= length;
|
|
|
|
CBaseEntity *pList[10];
|
|
CTongueEntitiesEnum tongueEnum( pList, 10 );
|
|
partition->EnumerateElementsInBox( PARTITION_ENGINE_SOLID_EDICTS, mins, maxs, false, &tongueEnum );
|
|
int nCount = tongueEnum.GetCount();
|
|
if ( !nCount )
|
|
return NULL;
|
|
|
|
for ( int i = 0; i < nCount; i++ )
|
|
{
|
|
CBaseEntity *pTest = pList[i];
|
|
|
|
// Can't lift something that's in the process of being lifted...
|
|
// Necessary for good +use interactions
|
|
if ( pTest->IsEFlagSet( EFL_IS_BEING_LIFTED_BY_BARNACLE ) )
|
|
continue;
|
|
|
|
// Vehicles can drive so fast that players can warp through the barnacle tongue.
|
|
// Therefore, we have to do a check to ensure that doesn't happen.
|
|
if ( pTest->GetServerVehicle() )
|
|
{
|
|
CBaseEntity *pDriver = pTest->GetServerVehicle()->GetPassenger();
|
|
if ( pDriver )
|
|
{
|
|
Vector vecPrevDriverPos;
|
|
pTest->GetVelocity( &vecPrevDriverPos );
|
|
VectorMA( pDriver->GetAbsOrigin(), -0.1f, vecPrevDriverPos, vecPrevDriverPos );
|
|
|
|
Ray_t sweptDriver;
|
|
sweptDriver.Init( vecPrevDriverPos, pDriver->GetAbsOrigin(), pDriver->WorldAlignMins(), pDriver->WorldAlignMaxs() );
|
|
if ( IsBoxIntersectingRay( mins, maxs, sweptDriver ) )
|
|
{
|
|
pTest = pDriver;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Deal with physics objects
|
|
if ( pTest->GetMoveType() == MOVETYPE_VPHYSICS )
|
|
{
|
|
IPhysicsObject *pObject = pTest->VPhysicsGetObject();
|
|
if ( pObject && pObject->GetMass() <= BARNACLE_TONGUE_MAX_LIFT_MASS )
|
|
{
|
|
// If this is an item, make sure it's near the tongue before lifting it.
|
|
// Weapons and other items have very large bounding boxes.
|
|
if( pTest->GetSolidFlags() & FSOLID_TRIGGER )
|
|
{
|
|
if( UTIL_DistApprox2D( WorldSpaceCenter(), pTest->WorldSpaceCenter() ) > 16 )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Allow the barnacles to grab stuff while their tongue is lowering
|
|
#ifdef HL2_EPISODIC
|
|
length = fabs( GetAbsOrigin().z - pTest->WorldSpaceCenter().z );
|
|
// Pull it up a tad
|
|
length = max(8, length - m_flRestUnitsAboveGround);
|
|
if ( pflLength )
|
|
{
|
|
*pflLength = length;
|
|
}
|
|
#endif
|
|
|
|
return pTest;
|
|
}
|
|
}
|
|
|
|
// NPCs + players
|
|
CBaseCombatCharacter *pVictim = ToBaseCombatCharacter( pTest );
|
|
if ( !pVictim )
|
|
continue;
|
|
|
|
// only clients and monsters
|
|
if ( pTest != this &&
|
|
IRelationType( pTest ) == D_HT &&
|
|
pVictim->m_lifeState != LIFE_DEAD &&
|
|
pVictim->m_lifeState != LIFE_DYING &&
|
|
!( pVictim->GetFlags() & FL_NOTARGET ) )
|
|
{
|
|
|
|
// Allow the barnacles to grab stuff while their tongue is lowering
|
|
#ifdef HL2_EPISODIC
|
|
length = fabs( GetAbsOrigin().z - pTest->WorldSpaceCenter().z );
|
|
// Pull it up a tad
|
|
length = max(8, length - m_flRestUnitsAboveGround);
|
|
if ( pflLength )
|
|
{
|
|
*pflLength = length;
|
|
}
|
|
#endif
|
|
|
|
return pTest;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//===============================================================================================================================
|
|
// BARNACLE TONGUE TIP
|
|
//===============================================================================================================================
|
|
// Crane tip
|
|
LINK_ENTITY_TO_CLASS( npc_barnacle_tongue_tip, CBarnacleTongueTip );
|
|
|
|
BEGIN_DATADESC( CBarnacleTongueTip )
|
|
|
|
DEFINE_FIELD( m_hBarnacle, FIELD_EHANDLE ),
|
|
DEFINE_PHYSPTR( m_pSpring ),
|
|
|
|
END_DATADESC()
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: To by usable by vphysics, this needs to have a phys model.
|
|
//-----------------------------------------------------------------------------
|
|
void CBarnacleTongueTip::Spawn( void )
|
|
{
|
|
Precache();
|
|
SetModel( "models/props_junk/rock001a.mdl" );
|
|
AddEffects( EF_NODRAW );
|
|
|
|
// We don't want this to be solid, because we don't want it to collide with the barnacle.
|
|
SetSolid( SOLID_VPHYSICS );
|
|
AddSolidFlags( FSOLID_NOT_SOLID );
|
|
BaseClass::Spawn();
|
|
|
|
m_pSpring = NULL;
|
|
}
|
|
|
|
int CBarnacleTongueTip::UpdateTransmitState( void )
|
|
{
|
|
return SetTransmitState( FL_EDICT_PVSCHECK );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CBarnacleTongueTip::Precache( void )
|
|
{
|
|
BaseClass::Precache();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CBarnacleTongueTip::UpdateOnRemove( )
|
|
{
|
|
if ( m_pSpring )
|
|
{
|
|
physenv->DestroySpring( m_pSpring );
|
|
m_pSpring = NULL;
|
|
}
|
|
BaseClass::UpdateOnRemove();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// If the tip changes, we gotta update the barnacle's notion of his tongue
|
|
//-----------------------------------------------------------------------------
|
|
void CBarnacleTongueTip::VPhysicsUpdate( IPhysicsObject *pPhysics )
|
|
{
|
|
BaseClass::VPhysicsUpdate( pPhysics );
|
|
|
|
if ( m_hBarnacle.Get() )
|
|
{
|
|
m_hBarnacle->OnTongueTipUpdated();
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Activate/create the spring
|
|
//-----------------------------------------------------------------------------
|
|
bool CBarnacleTongueTip::CreateSpring( CBaseAnimating *pTongueRoot )
|
|
{
|
|
IPhysicsObject *pPhysObject = VPhysicsGetObject();
|
|
IPhysicsObject *pRootPhysObject = pTongueRoot->VPhysicsGetObject();
|
|
Assert( pRootPhysObject );
|
|
Assert( pPhysObject );
|
|
|
|
// Root has huge mass, tip has little
|
|
pRootPhysObject->SetMass( VPHYSICS_MAX_MASS );
|
|
pPhysObject->SetMass( BARNACLE_TONGUE_TIP_MASS );
|
|
float damping = 3;
|
|
pPhysObject->SetDamping( &damping, &damping );
|
|
|
|
springparams_t spring;
|
|
spring.constant = BARNACLE_TONGUE_SPRING_CONSTANT_HANGING;
|
|
spring.damping = BARNACLE_TONGUE_SPRING_DAMPING;
|
|
spring.naturalLength = (GetAbsOrigin() - pTongueRoot->GetAbsOrigin()).Length();
|
|
spring.relativeDamping = 10;
|
|
spring.startPosition = GetAbsOrigin();
|
|
spring.endPosition = pTongueRoot->GetAbsOrigin();
|
|
spring.useLocalPositions = false;
|
|
m_pSpring = physenv->CreateSpring( pPhysObject, pRootPhysObject, &spring );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Create a barnacle tongue tip at the bottom of the tongue
|
|
//-----------------------------------------------------------------------------
|
|
CBarnacleTongueTip *CBarnacleTongueTip::CreateTongueTip( CNPC_Barnacle *pBarnacle, CBaseAnimating *pTongueRoot, const Vector &vecOrigin, const QAngle &vecAngles )
|
|
{
|
|
CBarnacleTongueTip *pTip = (CBarnacleTongueTip *)CBaseEntity::Create( "npc_barnacle_tongue_tip", vecOrigin, vecAngles );
|
|
if ( !pTip )
|
|
return NULL;
|
|
|
|
pTip->VPhysicsInitNormal( pTip->GetSolid(), pTip->GetSolidFlags(), false );
|
|
if ( !pTip->CreateSpring( pTongueRoot ) )
|
|
return NULL;
|
|
|
|
// Set the backpointer to the barnacle
|
|
pTip->m_hBarnacle = pBarnacle;
|
|
|
|
// Don't collide with the world
|
|
IPhysicsObject *pTipPhys = pTip->VPhysicsGetObject();
|
|
|
|
// turn off all floating / fluid simulation
|
|
pTipPhys->SetCallbackFlags( pTipPhys->GetCallbackFlags() & (~CALLBACK_DO_FLUID_SIMULATION) );
|
|
|
|
return pTip;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Create a barnacle tongue tip at the root (i.e. inside the barnacle)
|
|
//-----------------------------------------------------------------------------
|
|
CBarnacleTongueTip *CBarnacleTongueTip::CreateTongueRoot( const Vector &vecOrigin, const QAngle &vecAngles )
|
|
{
|
|
CBarnacleTongueTip *pTip = (CBarnacleTongueTip *)CBaseEntity::Create( "npc_barnacle_tongue_tip", vecOrigin, vecAngles );
|
|
if ( !pTip )
|
|
return NULL;
|
|
|
|
pTip->AddSolidFlags( FSOLID_NOT_SOLID );
|
|
|
|
// Disable movement on the root, we'll move this thing manually.
|
|
pTip->VPhysicsInitShadow( false, false );
|
|
pTip->SetMoveType( MOVETYPE_NONE );
|
|
return pTip;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// Schedules
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
AI_BEGIN_CUSTOM_NPC( npc_barnacle, CNPC_Barnacle )
|
|
|
|
// Register our interactions
|
|
DECLARE_INTERACTION( g_interactionBarnacleVictimDangle )
|
|
DECLARE_INTERACTION( g_interactionBarnacleVictimReleased )
|
|
DECLARE_INTERACTION( g_interactionBarnacleVictimGrab )
|
|
|
|
// Conditions
|
|
|
|
// Tasks
|
|
|
|
// Activities
|
|
DECLARE_ACTIVITY( ACT_BARNACLE_SLURP ) // Pulling the tongue up with prey on the end
|
|
DECLARE_ACTIVITY( ACT_BARNACLE_BITE_HUMAN ) // Biting the head of a humanoid
|
|
DECLARE_ACTIVITY( ACT_BARNACLE_BITE_PLAYER ) // Biting the head of a humanoid
|
|
DECLARE_ACTIVITY( ACT_BARNACLE_CHEW_HUMAN ) // Slowly swallowing the humanoid
|
|
DECLARE_ACTIVITY( ACT_BARNACLE_BARF_HUMAN ) // Spitting out human legs & gibs
|
|
DECLARE_ACTIVITY( ACT_BARNACLE_TONGUE_WRAP ) // Wrapping the tongue around a target
|
|
DECLARE_ACTIVITY( ACT_BARNACLE_TASTE_SPIT ) // Yuck! Me no like that!
|
|
DECLARE_ACTIVITY( ACT_BARNACLE_BITE_SMALL_THINGS ) // Biting small things, like a headcrab
|
|
DECLARE_ACTIVITY( ACT_BARNACLE_CHEW_SMALL_THINGS ) // Chewing small things, like a headcrab
|
|
|
|
//Adrian: events go here
|
|
DECLARE_ANIMEVENT( AE_BARNACLE_PUKEGIB )
|
|
DECLARE_ANIMEVENT( AE_BARNACLE_BITE )
|
|
DECLARE_ANIMEVENT( AE_BARNACLE_SPIT )
|
|
// Schedules
|
|
|
|
AI_END_CUSTOM_NPC()
|