diff options
Diffstat (limited to 'game/server/hl2/npc_blob.cpp')
| -rw-r--r-- | game/server/hl2/npc_blob.cpp | 1379 |
1 files changed, 1379 insertions, 0 deletions
diff --git a/game/server/hl2/npc_blob.cpp b/game/server/hl2/npc_blob.cpp new file mode 100644 index 0000000..568ec5c --- /dev/null +++ b/game/server/hl2/npc_blob.cpp @@ -0,0 +1,1379 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// npc_blob - experimental, cpu-intensive monster made of lots of smaller elements +// +//=============================================================================// +#include "cbase.h" +#include "ai_default.h" +#include "ai_task.h" +#include "ai_schedule.h" +#include "ai_hull.h" +#include "soundent.h" +#include "game.h" +#include "npcevent.h" +#include "entitylist.h" +#include "activitylist.h" +#include "ai_basenpc.h" +#include "engine/IEngineSound.h" +#include "vstdlib/jobthread.h" +#include "saverestore_utlvector.h" +#include "eventqueue.h" +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern float MOVE_HEIGHT_EPSILON; + +#define BLOB_MAX_AVOID_ORIGINS 3 + +ConVar blob_mindist( "blob_mindist", "120.0" ); +ConVar blob_element_speed( "blob_element_speed", "187" ); +ConVar npc_blob_idle_speed_factor( "npc_blob_idle_speed_factor", "0.5" ); + +ConVar blob_numelements( "blob_numelements", "20" ); +ConVar blob_batchpercent( "blob_batchpercent", "100" ); + +ConVar blob_radius( "blob_radius", "160" ); + +//ConVar blob_min_element_speed( "blob_min_element_speed", "50" ); +//ConVar blob_max_element_speed( "blob_max_element_speed", "250" ); + +ConVar npc_blob_use_threading( "npc_blob_use_threading", "1" ); + +ConVar npc_blob_sin_amplitude( "npc_blob_sin_amplitude", "60.0f" ); + +ConVar npc_blob_show_centroid( "npc_blob_show_centroid", "0" ); + +ConVar npc_blob_straggler_dist( "npc_blob_straggler_dist", "240" ); + +ConVar npc_blob_use_orientation( "npc_blob_use_orientation", "1" ); +ConVar npc_blob_use_model( "npc_blob_use_model", "2" ); + +ConVar npc_blob_think_interval( "npc_blob_think_interval", "0.025" ); + + +#define NPC_BLOB_MODEL "models/headcrab.mdl" + +//========================================================= +// Blob movement rules +//========================================================= +enum +{ + BLOB_MOVE_SWARM = 0, // Just swarm with the rest of the group + BLOB_MOVE_TO_TARGET_LOCATION, // Move to a designated location + BLOB_MOVE_TO_TARGET_ENTITY, // Chase the designated entity + BLOB_MOVE_DONT_MOVE, // Sit still!!!! +}; + +//========================================================= +//========================================================= +class CBlobElement : public CBaseAnimating +{ +public: + void Precache(); + void Spawn(); + int DrawDebugTextOverlays(void); + + void SetElementVelocity( Vector vecVelocity, bool bPlanarOnly ); + void AddElementVelocity( Vector vecVelocityAdd, bool bPlanarOnly ); + void ModifyVelocityForSurface( float flInterval, float flSpeed ); + + void SetSinePhase( float flPhase ) { m_flSinePhase = flPhase; } + float GetSinePhase() { return m_flSinePhase; } + + float GetSineAmplitude() { return m_flSineAmplitude; } + float GetSineFrequency() { return m_flSineFrequency; } + + void SetActiveMovementRule( int moveRule ) { m_iMovementRule = moveRule; } + int GetActiveMovementRule() { return m_iMovementRule; } + + void MoveTowardsTargetEntity( float speed ); + void SetTargetEntity( CBaseEntity *pEntity ) { m_hTargetEntity = pEntity; } + CBaseEntity *GetTargetEntity() { return m_hTargetEntity.Get(); } + + void MoveTowardsTargetLocation( float speed ); + void SetTargetLocation( const Vector &vecLocation ) { m_vecTargetLocation = vecLocation; } + + void ReconfigureRandomParams(); + void EnforceSpeedLimits( float flMinSpeed, float flMaxSpeed ); + + DECLARE_DATADESC(); + +public: + Vector m_vecPrevOrigin; // Only exists for debugging (isolating stuck elements) + int m_iStuckCount; + bool m_bOnWall; + float m_flDistFromCentroidSqr; + int m_iElementNumber; + Vector m_vecTargetLocation; + float m_flRandomEightyPercent; + +private: + EHANDLE m_hTargetEntity; + float m_flSinePhase; + float m_flSineAmplitude; + float m_flSineFrequency; + int m_iMovementRule; +}; +LINK_ENTITY_TO_CLASS( blob_element, CBlobElement ); + +//--------------------------------------------------------- +// Save/Restore +//--------------------------------------------------------- +BEGIN_DATADESC( CBlobElement ) + +DEFINE_FIELD( m_vecPrevOrigin, FIELD_POSITION_VECTOR ), +DEFINE_FIELD( m_iStuckCount, FIELD_INTEGER ), +DEFINE_FIELD( m_bOnWall, FIELD_BOOLEAN ), +DEFINE_FIELD( m_flDistFromCentroidSqr, FIELD_FLOAT ), +DEFINE_FIELD( m_iElementNumber, FIELD_INTEGER ), +DEFINE_FIELD( m_vecTargetLocation, FIELD_POSITION_VECTOR ), +DEFINE_FIELD( m_hTargetEntity, FIELD_EHANDLE ), +DEFINE_FIELD( m_flSinePhase, FIELD_FLOAT ), +DEFINE_FIELD( m_flSineAmplitude, FIELD_FLOAT ), +DEFINE_FIELD( m_flSineFrequency, FIELD_FLOAT ), +DEFINE_FIELD( m_iMovementRule, FIELD_INTEGER ), + +END_DATADESC() + + +const char *pszBlobModels[] = +{ + "models/gibs/agibs.mdl", + "models/props_junk/watermelon01.mdl", + "models/w_squeak.mdl", + "models/baby_headcrab.mdl" +}; + +const char *GetBlobModelName() +{ + int index = npc_blob_use_model.GetInt(); + + return pszBlobModels[ index ]; +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CBlobElement::Precache() +{ + PrecacheModel( GetBlobModelName() ); + + m_flRandomEightyPercent = random->RandomFloat( 0.8f, 1.0f ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CBlobElement::Spawn() +{ + Precache(); + + SetSolid( SOLID_NONE ); + SetMoveType( MOVETYPE_FLY ); + AddSolidFlags( FSOLID_NOT_STANDABLE | FSOLID_NOT_SOLID ); + + SetModel( GetBlobModelName() ); + UTIL_SetSize( this, vec3_origin, vec3_origin ); + + QAngle angles(0,0,0); + angles.y = random->RandomFloat( 0, 180 ); + SetAbsAngles( angles ); + + AddEffects( EF_NOSHADOW ); + + ReconfigureRandomParams(); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +int CBlobElement::DrawDebugTextOverlays(void) +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + char tempstr[512]; + Q_snprintf(tempstr,sizeof(tempstr), "Element #:%d", m_iElementNumber ); + EntityText(text_offset,tempstr,0); + text_offset++; + } + return text_offset; +} + + +//--------------------------------------------------------- +// This is the official way to set velocity for an element +// Do not call SetAbsVelocity() directly, since we also +// need to record the last velocity we intended to give the +// element, so that we can detect changes after game physics +// runs. +//--------------------------------------------------------- +void CBlobElement::SetElementVelocity( Vector vecVelocity, bool bPlanarOnly ) +{ + SetAbsVelocity( vecVelocity ); +} + +//--------------------------------------------------------- +// This is the official way to add velocity to an element. +// See SetElementVelocity() for explanation. +//--------------------------------------------------------- +void CBlobElement::AddElementVelocity( Vector vecVelocityAdd, bool bPlanarOnly ) +{ + Vector vecSum = GetAbsVelocity() + vecVelocityAdd; + SetAbsVelocity( vecSum ); +} + +//--------------------------------------------------------- +// This function seeks to keep the blob element moving along +// multiple different types of surfaces (climbing walls, etc) +//--------------------------------------------------------- +#define BLOB_TRACE_HEIGHT 8.0f +void CBlobElement::ModifyVelocityForSurface( float flInterval, float flSpeed ) +{ + trace_t tr; + Vector vecStart = GetAbsOrigin(); + Vector up = Vector( 0, 0, BLOB_TRACE_HEIGHT ); + + Vector vecWishedGoal = vecStart + (GetAbsVelocity() * flInterval); + + UTIL_TraceLine( vecStart + up, vecWishedGoal + up, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + + //NDebugOverlay::Line( tr.startpos, tr.endpos, 255, 0, 0, false, 0.1f ); + + m_bOnWall = false; + + if( tr.fraction == 1.0f ) + { + UTIL_TraceLine( vecWishedGoal + up, vecWishedGoal - (up * 2.0f), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + //NDebugOverlay::Line( tr.startpos, tr.endpos, 255, 255, 0, false, 0.1f ); + tr.endpos.z += MOVE_HEIGHT_EPSILON; + } + else + { + //NDebugOverlay::Cross3D( GetAbsOrigin(), 16, 255, 255, 0, false, 0.025f ); + + m_bOnWall = true; + + if( tr.m_pEnt != NULL && !tr.m_pEnt->IsWorld() ) + { + IPhysicsObject *pPhysics = tr.m_pEnt->VPhysicsGetObject(); + + if( pPhysics != NULL ) + { + Vector vecMassCenter; + Vector vecMassCenterWorld; + + vecMassCenter = pPhysics->GetMassCenterLocalSpace(); + pPhysics->LocalToWorld( &vecMassCenterWorld, vecMassCenter ); + + if( tr.endpos.z > vecMassCenterWorld.z ) + { + pPhysics->ApplyForceOffset( (-150.0f * m_flRandomEightyPercent) * tr.plane.normal, tr.endpos ); + } + } + } + } + + Vector vecDir = tr.endpos - vecStart; + VectorNormalize( vecDir ); + SetElementVelocity( vecDir * flSpeed, false ); +} + +//--------------------------------------------------------- +// Set velocity that will carry me towards a specified entity +// Most often used to move along with the npc_blob that +// is directing me. +//--------------------------------------------------------- +void CBlobElement::MoveTowardsTargetEntity( float speed ) +{ + CBaseEntity *pTarget = m_hTargetEntity.Get(); + + if( pTarget != NULL ) + { + // Try to attack my target's enemy directly if I can. + CBaseEntity *pTargetEnemy = pTarget->GetEnemy(); + + if( pTargetEnemy != NULL ) + { + pTarget = pTargetEnemy; + } + + Vector vecDir = pTarget->WorldSpaceCenter() - GetAbsOrigin(); + vecDir.NormalizeInPlace(); + SetElementVelocity( vecDir * speed, true ); + } + else + { + SetElementVelocity( vec3_origin, true ); + } +} + +//--------------------------------------------------------- +// Set velocity that will take me towards a specified location. +// This is often used to send all blob elements to specific +// locations, causing the blob to appear as though it has +// formed a specific shape. +//--------------------------------------------------------- +void CBlobElement::MoveTowardsTargetLocation( float speed ) +{ + Vector vecDir = m_vecTargetLocation - GetAbsOrigin(); + float dist = VectorNormalize( vecDir ); + + //!!!HACKHACK - how about a real way to tell if we've reached our goal? + if( dist <= 8.0f ) + { + SetActiveMovementRule( BLOB_MOVE_DONT_MOVE ); + } + + speed = MIN( dist, speed ); + + SetElementVelocity( vecDir * speed, true ); +} + +//--------------------------------------------------------- +// Pick new random numbers for the parameters that create +// variations in movement. +//--------------------------------------------------------- +void CBlobElement::ReconfigureRandomParams() +{ + m_flSinePhase = random->RandomFloat( 0.01f, 0.9f ); + m_flSineFrequency = random->RandomFloat( 10.0f, 20.0f ); + m_flSineAmplitude = random->RandomFloat( 0.5f, 1.5f ); +} + +//--------------------------------------------------------- +// Adjust velocity if this element is moving faster than +// flMaxSpeed or slower than flMinSpeed +//--------------------------------------------------------- +void CBlobElement::EnforceSpeedLimits( float flMinSpeed, float flMaxSpeed ) +{ + Vector vecVelocity = GetAbsVelocity(); + float flSpeed = VectorNormalize( vecVelocity ); + + if( flSpeed > flMaxSpeed ) + { + SetElementVelocity( vecVelocity * flMaxSpeed, true ); + } + else if( flSpeed < flMinSpeed ) + { + SetElementVelocity( vecVelocity * flMinSpeed, true ); + } +} + +//========================================================= +// Custom schedules +//========================================================= +enum +{ + SCHED_MYCUSTOMSCHEDULE = LAST_SHARED_SCHEDULE, +}; + +//========================================================= +// Custom tasks +//========================================================= +enum +{ + TASK_MYCUSTOMTASK = LAST_SHARED_TASK, +}; + + +//========================================================= +// Custom Conditions +//========================================================= +enum +{ + COND_MYCUSTOMCONDITION = LAST_SHARED_CONDITION, +}; + + +//========================================================= +//========================================================= +class CNPC_Blob : public CAI_BaseNPC +{ + DECLARE_CLASS( CNPC_Blob, CAI_BaseNPC ); + +public: + CNPC_Blob(); + void Precache( void ); + void Spawn( void ); + Class_T Classify( void ); + void RunAI(); + void GatherConditions( void ); + int SelectSchedule( void ); + int GetSoundInterests( void ) { return (SOUND_BUGBAIT); } + + + void ComputeCentroid(); + + void DoBlobBatchedAI( int iStart, int iEnd ); + + int ComputeBatchSize(); + void AdvanceBatch(); + int GetBatchStart(); + int GetBatchEnd(); + + CBlobElement *CreateNewElement(); + void InitializeElements(); + void RecomputeIdealElementDist(); + + void RemoveAllElementsExcept( int iExempt ); + + void RemoveExcessElements( int iNumElements ); + void AddNewElements( int iNumElements ); + + void FormShapeFromPath( string_t iszPathName ); + void SetRadius( float flRadius ); + + DECLARE_DATADESC(); + + int m_iNumElements; + bool m_bInitialized; + int m_iBatchStart; + Vector m_vecCentroid; + float m_flMinElementDist; + + CUtlVector<CHandle< CBlobElement > >m_Elements; + + DEFINE_CUSTOM_AI; + +public: + void InputFormPathShape( inputdata_t &inputdata ); + void InputSetRadius( inputdata_t &inputdata ); + void InputChaseEntity( inputdata_t &inputdata ); + void InputIsolateElement( inputdata_t &inputdata ); + void InputFormHemisphere( inputdata_t &inputdata ); + void InputFormTwoSpheres( inputdata_t &inputdata ); + +public: + Vector m_vecAvoidOrigin[ BLOB_MAX_AVOID_ORIGINS ]; + float m_flAvoidRadiusSqr; + +private: + int m_iReconfigureElement; + int m_iNumAvoidOrigins; + + bool m_bEatCombineHack; +}; + +LINK_ENTITY_TO_CLASS( npc_blob, CNPC_Blob ); +IMPLEMENT_CUSTOM_AI( npc_blob,CNPC_Blob ); + + +//--------------------------------------------------------- +// Save/Restore +//--------------------------------------------------------- +BEGIN_DATADESC( CNPC_Blob ) + +DEFINE_FIELD( m_iNumElements, FIELD_INTEGER ), +DEFINE_FIELD( m_bInitialized, FIELD_BOOLEAN ), +DEFINE_FIELD( m_iBatchStart, FIELD_INTEGER ), +DEFINE_FIELD( m_vecCentroid, FIELD_POSITION_VECTOR ), +DEFINE_FIELD( m_flMinElementDist, FIELD_FLOAT ), +DEFINE_FIELD( m_iReconfigureElement, FIELD_INTEGER ), +DEFINE_UTLVECTOR( m_Elements, FIELD_EHANDLE ), + +DEFINE_INPUTFUNC( FIELD_STRING, "FormPathShape", InputFormPathShape ), +DEFINE_INPUTFUNC( FIELD_FLOAT, "SetRadius", InputSetRadius ), +DEFINE_INPUTFUNC( FIELD_STRING, "ChaseEntity", InputChaseEntity ), +DEFINE_INPUTFUNC( FIELD_INTEGER, "IsolateElement", InputIsolateElement ), +DEFINE_INPUTFUNC( FIELD_VOID, "FormHemisphere", InputFormHemisphere ), +DEFINE_INPUTFUNC( FIELD_VOID, "FormTwoSpheres", InputFormTwoSpheres ), + +END_DATADESC() + +//--------------------------------------------------------- +//--------------------------------------------------------- +CNPC_Blob::CNPC_Blob() +{ + m_iNumElements = 0; + m_bInitialized = false; + m_iBatchStart = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Initialize the custom schedules +// Input : +// Output : +//----------------------------------------------------------------------------- +void CNPC_Blob::InitCustomSchedules(void) +{ + INIT_CUSTOM_AI(CNPC_Blob); + + ADD_CUSTOM_TASK(CNPC_Blob, TASK_MYCUSTOMTASK); + + ADD_CUSTOM_SCHEDULE(CNPC_Blob, SCHED_MYCUSTOMSCHEDULE); + + ADD_CUSTOM_CONDITION(CNPC_Blob, COND_MYCUSTOMCONDITION); +} + +//----------------------------------------------------------------------------- +// Purpose: +// +// +//----------------------------------------------------------------------------- +void CNPC_Blob::Precache( void ) +{ + PrecacheModel( NPC_BLOB_MODEL ); + UTIL_PrecacheOther( "blob_element" ); + + BaseClass::Precache(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// +// +//----------------------------------------------------------------------------- +void CNPC_Blob::Spawn( void ) +{ + Precache(); + + SetModel( NPC_BLOB_MODEL ); + + SetHullType(HULL_TINY); + SetHullSizeNormal(); + + SetSolid( SOLID_NONE ); + AddSolidFlags( FSOLID_NOT_STANDABLE ); + SetMoveType( MOVETYPE_STEP ); + SetBloodColor( BLOOD_COLOR_RED ); + m_iHealth = INT_MAX; + m_flFieldOfView = -1.0f; + m_NPCState = NPC_STATE_NONE; + + CapabilitiesClear(); + CapabilitiesAdd( bits_CAP_MOVE_GROUND ); + + m_Elements.RemoveAll(); + + NPCInit(); + + AddEffects( EF_NODRAW ); + + m_flMinElementDist = blob_mindist.GetFloat(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// +// +// Output : +//----------------------------------------------------------------------------- +Class_T CNPC_Blob::Classify( void ) +{ + return CLASS_PLAYER_ALLY; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Blob::RunAI() +{ + BaseClass::RunAI(); + + if( !m_bInitialized ) + { + // m_bInitialized is set to false in the constructor. So this bit of + // code runs one time, the first time I think. + Msg("I need to initialize\n"); + InitializeElements(); + m_bInitialized = true; + return; + } + + int iIdealNumElements = blob_numelements.GetInt(); + if( iIdealNumElements != m_iNumElements ) + { + int delta = iIdealNumElements - m_iNumElements; + + if( delta < 0 ) + { + delta = -delta; + delta = MIN(delta, 5 ); + RemoveExcessElements( delta ); + + if( m_iReconfigureElement > m_iNumElements ) + { + // Start this index over at zero, if it is past the new end of the utlvector. + m_iReconfigureElement = 0; + } + } + else + { + delta = MIN(delta, 5 ); + AddNewElements( delta ); + } + + RecomputeIdealElementDist(); + } + + ComputeCentroid(); + + if( npc_blob_show_centroid.GetBool() ) + { + NDebugOverlay::Cross3D( m_vecCentroid + Vector( 0, 0, 12 ), 32, 0, 255, 0, false, 0.025f ); + } + + if( npc_blob_use_threading.GetBool() ) + { + IterRangeParallel( this, &CNPC_Blob::DoBlobBatchedAI, 0, m_Elements.Count() ); + } + else + { + DoBlobBatchedAI( 0, m_Elements.Count() ); + } + + if( GetEnemy() != NULL ) + { + float flEnemyDistSqr = m_vecCentroid.DistToSqr( GetEnemy()->GetAbsOrigin() ); + + if( flEnemyDistSqr <= Square( 32.0f ) ) + { + if( GetEnemy()->Classify() == CLASS_COMBINE ) + { + if( !m_bEatCombineHack ) + { + variant_t var; + + var.SetFloat( 0 ); + g_EventQueue.AddEvent( GetEnemy(), "HitByBugBait", 0.0f, this, this ); + g_EventQueue.AddEvent( GetEnemy(), "SetHealth", var, 3.0f, this, this ); + m_bEatCombineHack = true; + + blob_radius.SetValue( 48.0f ); + RecomputeIdealElementDist(); + } + } + else + { + CTakeDamageInfo info; + + info.SetAttacker( this ); + info.SetInflictor( this ); + info.SetDamage( 5 ); + info.SetDamageType( DMG_SLASH ); + info.SetDamageForce( Vector( 0, 0, 1 ) ); + + GetEnemy()->TakeDamage( info ); + } + } + } + + SetNextThink( gpGlobals->curtime + npc_blob_think_interval.GetFloat() ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Blob::GatherConditions( void ) +{ + if( m_bEatCombineHack ) + { + // We just ate someone. + if( !GetEnemy() || !GetEnemy()->IsAlive() ) + { + m_bEatCombineHack = false; + blob_radius.SetValue( 160.0f ); + RecomputeIdealElementDist(); + } + } + + BaseClass::GatherConditions(); + +} + +//----------------------------------------------------------------------------- +// Either stand still or chase the enemy, for now. +//----------------------------------------------------------------------------- +int CNPC_Blob::SelectSchedule( void ) +{ + if( GetEnemy() == NULL ) + return SCHED_IDLE_STAND; + + return SCHED_CHASE_ENEMY; +} + +//----------------------------------------------------------------------------- +// Average the origin of all elements to get the centroid for the group +//----------------------------------------------------------------------------- +void CNPC_Blob::ComputeCentroid() +{ + m_vecCentroid = vec3_origin; + + for( int i = 0 ; i < m_Elements.Count() ; i++ ) + { + m_vecCentroid += m_Elements[ i ]->GetAbsOrigin(); + } + + m_vecCentroid /= m_Elements.Count(); +} + +//----------------------------------------------------------------------------- +// Run all of the AI for elements within the range iStart to iEnd +//----------------------------------------------------------------------------- +void CNPC_Blob::DoBlobBatchedAI( int iStart, int iEnd ) +{ + float flInterval = gpGlobals->curtime - GetLastThink(); + + // Local fields for sin-wave movement variance + float flMySine; + float flAmplitude = npc_blob_sin_amplitude.GetFloat(); + float flMyAmplitude; + Vector vecRight; + Vector vecForward; + + // Local fields for attract/repel + float minDistSqr = Square( m_flMinElementDist ); + float flBlobSpeed = blob_element_speed.GetFloat(); + float flSpeed; + + // Local fields for speed limiting + float flMinSpeed = blob_element_speed.GetFloat() * 0.5f; + float flMaxSpeed = blob_element_speed.GetFloat() * 1.5f; + bool bEnforceSpeedLimit; + bool bEnforceRelativePositions; + bool bDoMovementVariation; + bool bDoOrientation = npc_blob_use_orientation.GetBool(); + float flIdleSpeedFactor = npc_blob_idle_speed_factor.GetFloat(); + + // Group cohesion + float flBlobRadiusSqr = Square( blob_radius.GetFloat() + 48.0f ); // Four feet of fudge + + // Build a right-hand vector along which we'll add some sine wave data to give each + // element a unique insect-like undulation along an axis perpendicular to their path, + // which makes the entire group look far less orderly + if( GetEnemy() != NULL ) + { + // If I have an enemy, the right-hand vector is perpendicular to a straight line + // from the group's centroid to the enemy's origin. + vecForward = GetEnemy()->GetAbsOrigin() - m_vecCentroid; + VectorNormalize( vecForward ); + vecRight.x = vecForward.y; + vecRight.y = -vecForward.x; + } + else + { + // If there is no enemy, wobble along the axis from the centroid to me. + vecForward = GetAbsOrigin() - m_vecCentroid; + VectorNormalize( vecForward ); + vecRight.x = vecForward.y; + vecRight.y = -vecForward.x; + } + + //-- + // MAIN LOOP - Run all of the elements in the set iStart to iEnd + //-- + for( int i = iStart ; i < iEnd ; i++ ) + { + CBlobElement *pThisElement = m_Elements[ i ]; + + //-- + // Initial movement + //-- + // Start out with bEnforceSpeedLimit set to false. This is because an element + // can't overspeed if it's moving undisturbed towards its target entity or + // target location. An element can only under or overspeed when it is repelled + // by multiple other elements in the group. See "Relative Positions" below. + // + // Initialize some 'defaults' that may be changed for each iteration of this loop + bEnforceSpeedLimit = false; + bEnforceRelativePositions = true; + bDoMovementVariation = true; + flSpeed = flBlobSpeed; + + switch( pThisElement->GetActiveMovementRule() ) + { + case BLOB_MOVE_DONT_MOVE: + { + pThisElement->SetElementVelocity( vec3_origin, true ); + + trace_t tr; + Vector vecOrigin = pThisElement->GetAbsOrigin(); + + UTIL_TraceLine( vecOrigin, vecOrigin - Vector( 0, 0, 16), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + + if( tr.fraction < 1.0f ) + { + QAngle angles; + + VectorAngles( tr.plane.normal, angles ); + + float flSwap = angles.x; + + angles.x = -angles.y; + angles.y = flSwap; + + pThisElement->SetAbsAngles( angles ); + } + } + continue; + break; + + case BLOB_MOVE_TO_TARGET_LOCATION: + { + Vector vecDiff = pThisElement->GetAbsOrigin() - pThisElement->m_vecTargetLocation; + + if( vecDiff.Length2DSqr() <= Square(80.0f) ) + { + // Don't shove this guy around any more, let him get to his goal position. + flSpeed *= 0.5f; + bEnforceRelativePositions = false; + bDoMovementVariation = false; + } + + pThisElement->MoveTowardsTargetLocation( flSpeed ); + } + break; + + case BLOB_MOVE_TO_TARGET_ENTITY: + { + if( !IsMoving() && GetEnemy() == NULL ) + { + if( pThisElement->GetAbsOrigin().DistToSqr( GetAbsOrigin() ) <= flBlobRadiusSqr ) + { + flSpeed = (flSpeed * flIdleSpeedFactor) * pThisElement->m_flRandomEightyPercent; + } + } + pThisElement->MoveTowardsTargetEntity( flSpeed ); + } + break; + + default: + Msg("ERROR: Blob Element with unspecified Movement Rule\n"); + break; + } + + //--- + // Relative positions + //-- + // Check this element against ALL other elements. If the two elements are closer + // than the allowed minimum distance, repel this element away. (The other element + // will repel when its AI runs). A single element can be repelled by many other + // elements. This is why bEnforceSpeedLimit is set to true if any of the repelling + // code runs for this element. Multiple attempts to repel an element in the same + // direction will cause overspeed. Conflicting attempts to repel an element in opposite + // directions will cause underspeed. + Vector vecDir = Vector( 0, 0, 0 ); + Vector vecThisElementOrigin = pThisElement->GetAbsOrigin(); + + if( bEnforceRelativePositions ) + { + for( int j = 0 ; j < m_Elements.Count() ; j++ ) + { + // This is the innermost loop! We should optimize here, if anywhere. + + // If this element is on the wall, then don't be repelled by anyone. Repelling + // elements that are trying to climb a wall usually make them look like they + // fall off the wall a few times while climbing. + if( pThisElement->m_bOnWall ) + continue; + + CBlobElement *pThatElement = m_Elements[ j ]; + if( i != j ) + { + Vector vecThatElementOrigin = pThatElement->GetAbsOrigin(); + float distSqr = vecThisElementOrigin.DistToSqr( vecThatElementOrigin ); + + if( distSqr < minDistSqr ) + { + // Too close to the other element. Move away. + float flRepelSpeed; + Vector vecRepelDir = ( vecThisElementOrigin - vecThatElementOrigin ); + + vecRepelDir.NormalizeInPlace(); + flRepelSpeed = (flSpeed * ( 1.0f - ( distSqr / minDistSqr ) ) ) * pThatElement->GetSinePhase(); + pThisElement->AddElementVelocity( vecRepelDir * flRepelSpeed, true ); + + // Since we altered this element's velocity after it was initially set, there's a chance + // that the sums of multiple vectors will cause the element to over or underspeed, so + // mark it for speed limit enforcement + bEnforceSpeedLimit = true; + } + } + } + } + + //-- + // Movement variation + //-- + if( bDoMovementVariation ) + { + flMySine = sin( gpGlobals->curtime * pThisElement->GetSineFrequency() ); + flMyAmplitude = flAmplitude * pThisElement->GetSineAmplitude(); + pThisElement->AddElementVelocity( vecRight * (flMySine * flMyAmplitude), true ); + } + + // Avoidance + for( int a = 0 ; a < m_iNumAvoidOrigins ; a++ ) + { + Vector vecAvoidDir = pThisElement->GetAbsOrigin() - m_vecAvoidOrigin[ a ]; + + if( vecAvoidDir.LengthSqr() <= (m_flAvoidRadiusSqr * pThisElement->m_flRandomEightyPercent) ) + { + VectorNormalize( vecAvoidDir ); + pThisElement->AddElementVelocity( vecAvoidDir * (flSpeed * 2.0f), true ); + break; + } + } + + //-- + // Speed limits + //--- + if( bEnforceSpeedLimit == true ) + { + pThisElement->EnforceSpeedLimits( flMinSpeed, flMaxSpeed ); + } + + //-- + // Wall crawling + //-- + pThisElement->ModifyVelocityForSurface( flInterval, flSpeed ); + + // For identifying stuck elements. + pThisElement->m_vecPrevOrigin = pThisElement->GetAbsOrigin(); + + pThisElement->m_flDistFromCentroidSqr = pThisElement->m_vecPrevOrigin.DistToSqr( m_vecCentroid ); + + // Orientation + if( bDoOrientation ) + { + QAngle angles; + VectorAngles( pThisElement->GetAbsVelocity(), angles ); + pThisElement->SetAbsAngles( angles ); + } + +/* + //-- + // Stragglers/Group integrity + // + if( pThisElement->m_flDistFromCentroidSqr > flStragglerDistSqr ) + { + NDebugOverlay::Line( pThisElement->GetAbsOrigin(), m_vecCentroid, 255, 0, 0, false, 0.025f ); + } +*/ + } +} + +//----------------------------------------------------------------------------- +// Throw out all elements and their entities except for the the specified +// index into the UTILVector. This is useful for isolating elements that +// get into a bad state. +//----------------------------------------------------------------------------- +void CNPC_Blob::RemoveAllElementsExcept( int iExempt ) +{ + if( m_Elements.Count() == 1 ) + return; + + m_Elements[ 0 ].Set( m_Elements[ iExempt ].Get() ); + + for( int i = 1 ; i < m_Elements.Count() ; i++ ) + { + if( i != iExempt ) + { + m_Elements[ i ]->SUB_Remove(); + } + } + + m_Elements.RemoveMultiple( 1, m_Elements.Count() - 1 ); + + m_iNumElements = 1; +} + +//----------------------------------------------------------------------------- +// Purpose: The blob has too many elements. Locate good candidates and remove +// this many elements. +//----------------------------------------------------------------------------- +void CNPC_Blob::RemoveExcessElements( int iNumElements ) +{ + // For now we're not assessing candidates, just blindly removing. + int i; + for( i = 0 ; i < iNumElements ; i++ ) + { + int iLastElement = m_iNumElements - 1; + + // Nuke the associated entity + m_Elements[ iLastElement ]->SUB_Remove(); + + m_Elements.Remove( iLastElement ); + m_iNumElements--; + } +} + +//----------------------------------------------------------------------------- +// Purpose: This blob has too few elements. Add this many elements by stacking +// them on top of existing elements and allowing them to disperse themselves +// into the blob. +//----------------------------------------------------------------------------- +void CNPC_Blob::AddNewElements( int iNumElements ) +{ + int i; + + // Keep track of how many elements we had when we came into this function. + // Since the new elements copy their origins from existing elements, we only want + // to copy origins from elements that existed before we came into this function. + // Otherwise, the more elements we create while in this function, the more likely it + // becomes that several of them will stack on the same origin. + int iInitialElements = m_iNumElements; + + for( i = 0 ; i < iNumElements ; i++ ) + { + CBlobElement *pElement = CreateNewElement(); + + if( pElement != NULL ) + { + // Copy the origin of some element that is not me. This will make the expansion + // of the group easier on the eye, since this element will spawn inside of some + // other element, and then be pushed out by the blob's repel rules. + int iCopyElement = random->RandomInt( 0, iInitialElements - 1 ); + pElement->SetAbsOrigin( m_Elements[iCopyElement]->GetAbsOrigin() ); + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +#define BLOB_MAX_VERTS 128 +void CNPC_Blob::FormShapeFromPath( string_t iszPathName ) +{ + Vector vertex[ BLOB_MAX_VERTS ]; + + int i; + int iNumVerts = 0; + + for ( i = 0 ; i < BLOB_MAX_VERTS ; i++ ) + { + if( iszPathName == NULL_STRING ) + { + //Msg("Terminal path\n"); + break; + } + + CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, iszPathName ); + + if( pEntity != NULL ) + { + bool bClosedPath = false; + + for( int j = 0 ; j < i ; j++ ) + { + // Stop if we reach a vertex that's already in the array (closed path) + if( vertex[ j ] == pEntity->GetAbsOrigin() ) + { + //Msg("Closed path!\n"); + bClosedPath = true; + break; + } + } + + vertex[ i ] = pEntity->GetAbsOrigin(); + iszPathName = pEntity->m_target; + iNumVerts++; + + if( bClosedPath ) + break; + } + } + + //Msg("%d verts found in path!\n", iNumVerts); + + float flPathLength = 0.0f; + float flDistribution; + + for( i = 0 ; i < iNumVerts - 1 ; i++ ) + { + Vector vecDiff = vertex[ i ] - vertex[ i + 1 ]; + + flPathLength += vecDiff.Length(); + } + + flDistribution = flPathLength / m_iNumElements; + Msg("Path length is %f, distribution is %f\n", flPathLength, flDistribution ); + + int element = 0; + for( i = 0 ; i < iNumVerts - 1 ; i++ ) + { + //NDebugOverlay::Line( vertex[ i ], vertex[ i + 1 ], 0, 255, 0, false, 10.0f ); + Vector vecDiff = vertex[ i + 1 ] - vertex[ i ]; + Vector vecStart = vertex[ i ]; + + float flSegmentLength = VectorNormalize( vecDiff ); + + float flStep; + + for( flStep = 0.0f ; flStep < flSegmentLength ; flStep += flDistribution ) + { + //NDebugOverlay::Cross3D( vecStart + vecDiff * flStep, 16, 255, 255, 255, false, 10.0f ); + m_Elements[ element ]->SetTargetLocation( vecStart + vecDiff * flStep ); + m_Elements[ element ]->SetActiveMovementRule( BLOB_MOVE_TO_TARGET_LOCATION ); + element++; + + if( element == m_iNumElements ) + return; + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Blob::SetRadius( float flRadius ) +{ + blob_radius.SetValue( flRadius ); + RecomputeIdealElementDist(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Blob::InputFormPathShape( inputdata_t &inputdata ) +{ + string_t shape = inputdata.value.StringID(); + + if( shape == NULL_STRING ) + return; + + //Msg("I'm supposed to form some shape called:%s\n", shape ); + + FormShapeFromPath( shape ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Blob::InputSetRadius( inputdata_t &inputdata ) +{ + float flNewRadius = inputdata.value.Float(); + + SetRadius( flNewRadius ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Blob::InputChaseEntity( inputdata_t &inputdata ) +{ + CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.StringID(), NULL, inputdata.pActivator, inputdata.pCaller ); + + if ( pEntity ) + { + for( int i = 0 ; i < m_Elements.Count() ; i++ ) + { + CBlobElement *pElement = m_Elements[ i ]; + + pElement->SetTargetEntity( pEntity ); + pElement->SetActiveMovementRule( BLOB_MOVE_TO_TARGET_ENTITY ); + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Blob::InputIsolateElement( inputdata_t &inputdata ) +{ + int iElement = inputdata.value.Int(); + + RemoveAllElementsExcept( iElement ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Blob::InputFormHemisphere( inputdata_t &inputdata ) +{ + Vector center = GetAbsOrigin(); + const float flRadius = 240.0f; + + Vector vecDir; + + for( int i = 0 ; i < m_Elements.Count() ; i++ ) + { + CBlobElement *pElement = m_Elements[ i ]; + + // Compute a point around my center + vecDir.x = random->RandomFloat( -1, 1 ); + vecDir.y = random->RandomFloat( -1, 1 ); + vecDir.z = random->RandomFloat( 0, 1 ); + + VectorNormalize( vecDir ); + + pElement->SetTargetLocation( center + vecDir * flRadius ); + pElement->SetActiveMovementRule( BLOB_MOVE_TO_TARGET_LOCATION ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Blob::InputFormTwoSpheres( inputdata_t &inputdata ) +{ + Vector center = GetAbsOrigin(); + Vector sphere1 = GetAbsOrigin() + Vector( 120.0f, 0, 120.0f ); + Vector sphere2 = GetAbsOrigin() + Vector( -120.0f, 0, 120.0f ); + const float flRadius = 100.0f; + + Vector vecDir; + + int batchSize = m_Elements.Count() / 2; + + for( int i = 0 ; i < batchSize ; i++ ) + { + CBlobElement *pElement = m_Elements[ i ]; + + // Compute a point around my center + vecDir.x = random->RandomFloat( -1, 1 ); + vecDir.y = random->RandomFloat( -1, 1 ); + vecDir.z = random->RandomFloat( -1, 1 ); + + VectorNormalize( vecDir ); + + pElement->SetTargetLocation( sphere1 + vecDir * flRadius ); + pElement->SetActiveMovementRule( BLOB_MOVE_TO_TARGET_LOCATION ); + } + + for( int i = batchSize ; i < m_Elements.Count() ; i++ ) + { + CBlobElement *pElement = m_Elements[ i ]; + + // Compute a point around my center + vecDir.x = random->RandomFloat( -1, 1 ); + vecDir.y = random->RandomFloat( -1, 1 ); + vecDir.z = random->RandomFloat( -1, 1 ); + + VectorNormalize( vecDir ); + + pElement->SetTargetLocation( sphere2 + vecDir * flRadius ); + pElement->SetActiveMovementRule( BLOB_MOVE_TO_TARGET_LOCATION ); + } + +} + +//----------------------------------------------------------------------------- +// Get the index of the element to start processing with for this batch. +//----------------------------------------------------------------------------- +int CNPC_Blob::GetBatchStart() +{ + return m_iBatchStart; +} + +//----------------------------------------------------------------------------- +// Get the index of the element to stop processing with for this batch. +//----------------------------------------------------------------------------- +int CNPC_Blob::GetBatchEnd() +{ + int batchDone = m_iBatchStart + ComputeBatchSize(); + batchDone = MIN( batchDone, m_Elements.Count() ); + + return batchDone; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CNPC_Blob::ComputeBatchSize() +{ + int batchSize = m_Elements.Count() / ( 100 / blob_batchpercent.GetInt() ); + return batchSize; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +void CNPC_Blob::AdvanceBatch() +{ + m_iBatchStart += ComputeBatchSize(); + + if( m_iBatchStart >= m_Elements.Count() ) + m_iBatchStart = 0; +} + +//----------------------------------------------------------------------------- +// Creates a new blob element from scratch and adds it to the blob +//----------------------------------------------------------------------------- +CBlobElement *CNPC_Blob::CreateNewElement() +{ + CBlobElement *pElement = static_cast<CBlobElement*>(CreateEntityByName( "blob_element" )); + + if( pElement != NULL ) + { + pElement->SetOwnerEntity( this ); + pElement->SetSinePhase( fabs( sin(((float)m_iNumElements)/10.0f) ) ); + pElement->SetActiveMovementRule( BLOB_MOVE_TO_TARGET_ENTITY ); + pElement->SetTargetEntity( this ); + + pElement->m_iElementNumber = m_iNumElements; + m_iNumElements++; + pElement->Spawn(); + m_Elements.AddToTail( pElement ); + return pElement; + } + + Warning("Blob could not spawn new element!\n"); + return NULL; +} + +//----------------------------------------------------------------------------- +// Create, initialize, and distribute all blob elements +//----------------------------------------------------------------------------- +void CNPC_Blob::InitializeElements() +{ + // Squirt all of the elements out into a circle + int i; + QAngle angDistributor( 0, 0, 0 ); + + int iNumElements = blob_numelements.GetInt(); + + float step = 360.0f / ((float)iNumElements); + for( i = 0 ; i < iNumElements ; i++ ) + { + Vector vecDir; + Vector vecDest; + AngleVectors( angDistributor, &vecDir, NULL, NULL ); + vecDest = WorldSpaceCenter() + vecDir * 64.0f; + + CBlobElement *pElement = CreateNewElement(); + + if( !pElement ) + { + Msg("Blob could not create all elements!!\n"); + return; + } + + trace_t tr; + UTIL_TraceLine( vecDest, vecDest + Vector (0, 0, MIN_COORD_FLOAT), MASK_SHOT, pElement, COLLISION_GROUP_NONE, &tr ); + + pElement->SetAbsOrigin( tr.endpos + Vector( 0, 0, 1 ) ); + + angDistributor.y += step; + } + + CBaseEntity *pEntity = gEntList.FindEntityByClassname( NULL, "info_target" ); + for( i = 0 ; i < BLOB_MAX_AVOID_ORIGINS ; i++ ) + { + if( pEntity ) + { + if( pEntity->NameMatches("avoid") ) + { + m_vecAvoidOrigin[ i ] = pEntity->GetAbsOrigin(); + m_flAvoidRadiusSqr = Square( 120.0f ); + m_iNumAvoidOrigins++; + } + + pEntity = gEntList.FindEntityByClassname( pEntity, "info_target" ); + } + else + { + break; + } + } + + Msg("%d avoid origins\n", m_iNumAvoidOrigins ); + + RecomputeIdealElementDist(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Blob::RecomputeIdealElementDist() +{ + float radius = blob_radius.GetFloat(); + float area = M_PI * Square(radius); + + //Msg("Area of blob is: %f\n", area ); + + //m_flMinElementDist = 2.75f * sqrt( area / m_iNumElements ); + m_flMinElementDist = M_PI * sqrt( area / m_iNumElements ); + + //Msg("New element dist: %f\n", m_flMinElementDist ); +} + |