//========= 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(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(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( 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( 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( 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(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(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(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( ¶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(); #ifdef HL2_EPISODIC if ( pVictim == NULL ) { if ( GetEnemy() ) { CBounceBomb *pBounce = dynamic_cast( 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 || m_hTongueTip->m_pSpring == 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(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( 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(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()