//========= 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()