source-engine/game/server/hl2/npc_barnacle.cpp
FluorescentCIAAfricanAmerican 3bf9df6b27 1
2020-04-22 12:56:21 -04:00

2738 lines
80 KiB
C++

//========= Copyright 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"
#if HL2_EPISODIC
#include "npc_antlion.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
float GetCurrentGravity( void );
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;
int g_interactionBarnacleVictimBite = 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;
#if BARNACLE_USE_TONGUE_OFFSET
// Static variable that holds the difference between the player's
// eyepos and the tongue when he is seized -- used for offsetting
// the drawing of the tongue so that it doesn't appear to clip into
// the camera when we recenter the player.
const Vector CNPC_Barnacle::m_svPlayerHeldTipOffset(24,0,-8);
#endif
//-----------------------------------------------------------------------------
// 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;
}
}
/*
input LetGo(void) : "Let go of anything I am holding."
output OnGrab(string) : "When I attach my tongue to something"
output OnRelease(string) : "When I let go of something"
*/
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 ),
#ifdef HL2_EPISODIC
DEFINE_INPUTFUNC( FIELD_VOID, "LetGo", InputLetGo ),
DEFINE_OUTPUT( m_OnGrab, "OnGrab" ),
DEFINE_OUTPUT( m_OnRelease, "OnRelease" ),
#endif
// 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 ),
SendPropVector( SENDINFO( m_vecTipDrawOffset ), 0, SPROP_NOSCALE ),
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 );
#if HL2_EPISODIC // the episodic barnacle is solid, so it can be sawbladed.
SetMoveType( MOVETYPE_PUSH );
#else
SetMoveType( MOVETYPE_NONE );
#endif
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;
#if HL2_EPISODIC // the episodic barnacle is solid, so it can be sawbladed.
IPhysicsObject *pPhys = VPhysicsInitShadow( false, false );
if (pPhys)
{
pPhys->SetMass(500);
}
#endif
InitBoneControllers();
InitTonguePosition();
// set eye position
SetDefaultEyeOffset();
// Add some variation because we're often in large bunches
SetActivity( ACT_IDLE );
SetPlaybackRate( random->RandomFloat( 0.8f, 1.2f ) );
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:
// TODO: The LostPrey(true) at the top of if ( m_hRagdoll ) isnt' quite right:
// it will make the barnacle drop anything that's shot on the way up. This is a
// quick fix for the antlions which crashed otherwise (they have somewhat anomalous
// ragdoll behaivor) but should be revisted.
//-----------------------------------------------------------------------------
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 )
{
if ( GetEnemy() )
{
LiftPrey();
}
else
{
LostPrey(true);
}
}
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
#if HL2_EPISODIC
// have to save this off because LostPrey() resets it (and if we take damage before hitting that,
// then the dead thing will go flying)
bool poisoned = m_bSwallowingPoison;
LostPrey( true ); // Remove all evidence
m_flDigestFinish = 0;
if ( poisoned )
{ // hurt me
TakeDamage( CTakeDamageInfo( this, this, m_iHealth, DMG_ACID ) );
}
#else
LostPrey( true ); // Remove all evidence
m_flDigestFinish = 0;
#endif
}
}
}
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
#if HL2_EPISODIC
// have to save this off because LostPrey() resets it (and if we take damage before hitting that,
// then the dead thing will go flying)
bool poisoned = m_bSwallowingPoison;
LostPrey( true ); // Remove all evidence
m_flDigestFinish = 0;
if ( poisoned )
{ // hurt me
TakeDamage( CTakeDamageInfo( this, this, m_iHealth, DMG_ACID ) );
}
#else
LostPrey( true ); // Remove all evidence
m_flDigestFinish = 0;
#endif
}
}
}
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.
// Add some variation because we're often in large bunches
SetActivity( ACT_IDLE );
SetPlaybackRate( random->RandomFloat( 0.8f, 1.2f ) );
}
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;
}
else if ( pBCC->IsInAVehicle() )
{
// Don't pluck an NPC from a vehicle.
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: Make a horrific noise before we pull the prey 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 prey stuck to our tongue up towards our mouth
//-----------------------------------------------------------------------------
void CNPC_Barnacle::PullEnemyTorwardsMouth( bool bAdjustEnemyOrigin )
{
CBaseEntity *pEnemy = GetEnemy();
if ( pEnemy->IsPlayer() && pEnemy->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 ( (pEnemy->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 = pEnemy->GetAbsOrigin();
m_flLastPull = flPull;
Vector vecNewPos = m_vLastEnemyPos;
// vecNewPos.z += flPull;
#if 0
// this is an example of one somewhat crude attempt to realign objects so that they are directly underneath
// the barnacle. It introduces unacceptable oscillation.
const float MAX_CENTERING_VELOCITY = 24.0f;
float distToMove = MAX_CENTERING_VELOCITY * dt;
Vector2D vToCenter = GetAbsOrigin().AsVector2D() - GetEnemy()->GetAbsOrigin().AsVector2D();
float distFromCenter = vToCenter.NormalizeInPlace();
Msg("<%.3f,%.3f>\n",vToCenter.x,vToCenter.y);
if ( distFromCenter < distToMove )
{
vecNewPos.x = GetAbsOrigin().x;
vecNewPos.y = GetAbsOrigin().y;
}
else
{
vToCenter *= distToMove;
vecNewPos.x += vToCenter.x;
vecNewPos.y += vToCenter.y;
// GetEnemy()->Teleport( &vecNewPos, NULL, NULL );
}
#endif
// recentering the player under the barnacle was tried in the code
// below, but then disabled for Orange Box ship because the viewmodel
// jitter became unacceptably noisy after other changes to physics
// and client.
#if 0
// this technique is a little noisy and needs to be readdressed.
if (pEnemy->IsPlayer())
{
Vector playerOrigin = GetEnemy()->GetAbsOrigin();
Vector2D vToCenter = GetAbsOrigin().AsVector2D() - playerOrigin.AsVector2D();
float distFromCenter = vToCenter.NormalizeInPlace();
// if we're off by more than a few inches
if ( distFromCenter > 6.0f )
{
// get us there in a second
Vector desiredVelocity;
float distToMove = min(distFromCenter, 24.0f * dt);
desiredVelocity.x = vToCenter.x * distToMove;
desiredVelocity.y = vToCenter.y * distToMove;
desiredVelocity.z = 0;
#if 0 // here is a physical force-based way (too noisy!):
IPhysicsObject *pTonguePhys = m_hTongueTip->VPhysicsGetObject();
pTonguePhys->ApplyForceCenter(desiredVelocity);
#else
vecNewPos = playerOrigin + desiredVelocity;
// find how far we can actually transport the player
trace_t tr;
UTIL_TraceEntity( pEnemy, playerOrigin, vecNewPos, MASK_PLAYERSOLID, m_hTongueTip.Get(), pEnemy->GetCollisionGroup(), &tr );
pEnemy->Teleport(&tr.endpos, NULL, &desiredVelocity);
#endif
}
}
#endif
// GetEnemy()->Teleport( &vecNewPos, NULL, NULL );
if( pEnemy->GetFlags() & FL_ONGROUND )
{
// Try to fight OnGround
pEnemy->SetGravity( 0 );
pEnemy->RemoveFlag( FL_ONGROUND );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Barnacle::UpdatePlayerConstraint( 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;
// if player is on the ladder, disengage him
if ( pPlayer->GetMoveType() == MOVETYPE_LADDER )
{
pPlayer->ExitLadder();
}
// 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.
UpdatePlayerConstraint();
// 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 ) // then yes, let's chomp
{
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 );
}
#ifdef HL2_EPISODIC
// if the object is a combatclass, send it a chomp interaction in case it wants to respond to that
// in some nonstandard way.
CBaseCombatCharacter *pBCC = dynamic_cast<CBaseCombatCharacter *>(pVictim);
if( pBCC )
{
Vector tipPos = m_vecTip.Get();
pBCC->DispatchInteraction( g_interactionBarnacleVictimBite, &tipPos, this );
}
#endif
}
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( pVictim );
// 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 );
bool bEnemyIsNPC = IsEnemyAnNPC() && !IsEnemyARagdoll();
if ( ( bEnemyIsNPC && !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 ( bEnemyIsNPC )
{
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 )
{
#if HL2_EPISODIC
PhysEnableEntityCollisions( this, pAnimating );
PhysDisableEntityCollisions( this, pRagdoll );
#endif
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 )
{
#if HL2_EPISODIC
m_OnGrab.Set( pTouchEnt, this, this );
#endif
// Reset this valricue 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 HL2_EPISODIC
// Disable collision between myself and the obejct I've seized.
PhysDisableEntityCollisions( this, pTouchEnt );
#endif
// teleporting the player in this way is illegitimate -- try it in third person to see the problem
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 );
// pTouchEnt->Teleport( &placementTrace.endpos, NULL, NULL );
}
}
else
{
pTouchEnt->SetAbsOrigin( origin );
// pTouchEnt->Teleport( &origin, NULL, NULL );
}
}
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();
#if BARNACLE_USE_TONGUE_OFFSET
VectorRotate( m_svPlayerHeldTipOffset, pTouchEnt->EntityToWorldTransform(), m_vecTipDrawOffset.GetForModify() );
m_vecTipDrawOffset.GetForModify().z = m_svPlayerHeldTipOffset.z;
#endif
// pTonguePhys->GetPosition(&vecGrabPos,NULL);
}
else
{
VectorSubtract( m_vecTip, pTouchEnt->GetAbsOrigin(), vecGrabPos );
VectorNormalize( vecGrabPos );
vecGrabPos = physcollision->CollideGetExtent( pPlayerPhys->GetCollide(), pTouchEnt->GetAbsOrigin(), pTouchEnt->GetAbsAngles(), vecGrabPos );
#if BARNACLE_USE_TONGUE_OFFSET
m_vecTipDrawOffset.GetForModify().Zero();
#endif
}
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();
/*
You can use this stanza to try to counterplace the constraint on the player's head so he gets hauled sideways to the right place on the barnacle, but it is better to just move the tongue before attachment.
if ( IsEnemyAPlayer() )
{
Vector2D vToCenter = GetAbsOrigin().AsVector2D() - pTouchEnt->EyePosition().AsVector2D();
fixed.attachedRefXform[0][3] -= vToCenter.x ;
fixed.attachedRefXform[1][3] -= vToCenter.y ;
}
*/
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( &params );
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();
#ifdef HL2_EPISODIC
if ( pVictim == NULL )
{
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;
}
#endif
Assert( pVictim );
if ( !pVictim )
{
return;
}
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 );
}
#if HL2_EPISODIC
m_bSwallowingPoison = IsPoisonous(pVictim);
unsigned int enemyClass = GetEnemy()->Classify();
#endif
// 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
#ifdef HL2_EPISODIC
if ( enemyClass == 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;
}
// in episodic, where barnacles can eat antlions, vanish the ragdoll because the gibs will spray everywhere
// and hide it.
if ( enemyClass == CLASS_ANTLION )
{
#ifndef _XBOX
m_nBloodColor = pVictim->BloodColor();
#endif
m_flNextBloodTime = 0.0f;
SprayBlood();
m_flDigestFinish = gpGlobals->curtime + 10.0;
if (m_hRagdoll)
{
UTIL_Remove( m_hRagdoll );
}
if ( m_bSwallowingPoison )
{ // hurt me
TakeDamage( CTakeDamageInfo( this, this, m_iHealth, DMG_ACID ) );
}
return;
}
#endif
// 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 );
#if HL2_EPISODIC
// digest poisonous things for just a moment before being killed by them (it looks wierd if it's instant)
// Parentheses were probably intended around the ?: part of the expression, but putting them there now
// would change the behavior which is undesirable, so parentheses were placed around the '+' to suppress
// compiler warnings.
m_flDigestFinish = ( gpGlobals->curtime + m_bSwallowingPoison ) ? 0.48f : 10.0f;
#else
m_flDigestFinish = gpGlobals->curtime + 10.0;
#endif
}
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 HL2_EPISODIC
m_OnRelease.Set( GetEnemy(), this, this );
#endif
CBaseEntity * const pEnemy = GetEnemy();
if ( pEnemy )
{
#if HL2_EPISODIC
PhysEnableEntityCollisions( this, pEnemy );
#endif
//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() - pEnemy->WorldSpaceCenter();
Vector newOrigin = pEnemy->GetAbsOrigin() + centerDelta;
pEnemy->SetAbsOrigin( newOrigin );
pVictim->SetAbsAngles( newAngles );
}
pVictim->SetGroundEntity( NULL );
}
else if ( IsEnemyAPhysicsObject() )
{
// If we're a physics object, then we need to clear this flag
pEnemy->RemoveEFlags( EFL_IS_BEING_LIFTED_BY_BARNACLE );
}
}
RemoveRagdoll( bRemoveRagdoll );
m_bLiftingPrey = false;
m_bSwallowingPrey = false;
#if HL2_EPISODIC
m_bSwallowingPoison = false;
#endif
SetEnemy( NULL );
m_vecTipDrawOffset.GetForModify().Zero();
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 * GetCurrentGravity()) / 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 );
SendOnKilledGameEvent( info );
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 );
// we deliberately do not call BaseClass::EventKilled
}
//-----------------------------------------------------------------------------
// 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();
}
}
#if HL2_EPISODIC
//=========================================================
// Some creatures are poisonous to barnacles, and the barnacle
// will die after consuming them. This determines if a given
// entity is one of those things.
// todo: could be a bit faster
//=========================================================
bool CNPC_Barnacle::IsPoisonous( CBaseEntity *pVictim )
{
if (!pVictim)
return false;
if ( FClassnameIs(pVictim,"npc_headcrab_poison") )
return true;
if ( FClassnameIs(pVictim,"npc_headcrab_black") )
return true;
if ( FClassnameIs(pVictim,"npc_antlion") &&
static_cast<CNPC_Antlion *>(pVictim)->IsWorker()
)
return true;
return false;
}
//=========================================================
// script input to immediately abandon whatever I am lifting
//=========================================================
void CNPC_Barnacle::InputLetGo( inputdata_t &inputdata )
{
if ( GetEnemy() )
{
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 );
}
}
// Barnacle has custom impact damage tables, so it can take grave damage from sawblades.
static impactentry_t barnacleLinearTable[] =
{
{ 150*150, 5 },
{ 250*250, 10 },
{ 350*350, 50 },
{ 500*500, 100 },
{ 1000*1000, 500 },
};
static impactentry_t barnacleAngularTable[] =
{
{ 100*100, 35 }, // Sawblade always kills.
{ 200*200, 50 },
{ 250*250, 500 },
};
static impactdamagetable_t gBarnacleImpactDamageTable =
{
barnacleLinearTable,
barnacleAngularTable,
ARRAYSIZE(barnacleLinearTable),
ARRAYSIZE(barnacleAngularTable),
24*24, // minimum linear speed squared
360*360, // minimum angular speed squared (360 deg/s to cause spin/slice damage)
2, // can't take damage from anything under 2kg
5, // anything less than 5kg is "small"
5, // never take more than 5 pts of damage from anything under 5kg
36*36, // <5kg objects must go faster than 36 in/s to do damage
VPHYSICS_LARGE_OBJECT_MASS, // large mass in kg
4, // large mass scale (anything over 500kg does 4X as much energy to read from damage table)
5, // large mass falling scale (emphasize falling/crushing damage over sideways impacts since the stress will kill you anyway)
0.0f, // min vel
};
const impactdamagetable_t &CNPC_Barnacle::GetPhysicsImpactDamageTable( void )
{
return gBarnacleImpactDamageTable;
}
#endif
//=========================================================
// 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 12
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 )
DECLARE_INTERACTION( g_interactionBarnacleVictimBite )
// 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()