diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/hl2/env_headcrabcanister.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/server/hl2/env_headcrabcanister.cpp')
| -rw-r--r-- | game/server/hl2/env_headcrabcanister.cpp | 1114 |
1 files changed, 1114 insertions, 0 deletions
diff --git a/game/server/hl2/env_headcrabcanister.cpp b/game/server/hl2/env_headcrabcanister.cpp new file mode 100644 index 0000000..ed1883d --- /dev/null +++ b/game/server/hl2/env_headcrabcanister.cpp @@ -0,0 +1,1114 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "ai_hint.h" +#include "env_headcrabcanister_shared.h" +#include "explode.h" +#include "beam_shared.h" +#include "SpriteTrail.h" +#include "ar2_explosion.h" +#include "SkyCamera.h" +#include "smoke_trail.h" +#include "ai_basenpc.h" +#include "npc_headcrab.h" +#include "ai_motor.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Models! +//----------------------------------------------------------------------------- +#define ENV_HEADCRABCANISTER_MODEL "models/props_combine/headcrabcannister01a.mdl" +#define ENV_HEADCRABCANISTER_BROKEN_MODEL "models/props_combine/headcrabcannister01b.mdl" +#define ENV_HEADCRABCANISTER_SKYBOX_MODEL "models/props_combine/headcrabcannister01a_skybox.mdl" +#define ENV_HEADCRABCANISTER_INCOMING_SOUND_TIME 1.0f + +ConVar sk_env_headcrabcanister_shake_amplitude( "sk_env_headcrabcanister_shake_amplitude", "50" ); +ConVar sk_env_headcrabcanister_shake_radius( "sk_env_headcrabcanister_shake_radius", "1024" ); +ConVar sk_env_headcrabcanister_shake_radius_vehicle( "sk_env_headcrabcanister_shake_radius_vehicle", "2500" ); + +#define ENV_HEADCRABCANISTER_TRAIL_TIME 3.0f + +//----------------------------------------------------------------------------- +// Spawn flags +//----------------------------------------------------------------------------- +enum +{ + SF_NO_IMPACT_SOUND = 0x1, + SF_NO_LAUNCH_SOUND = 0x2, + SF_START_IMPACTED = 0x1000, + SF_LAND_AT_INITIAL_POSITION = 0x2000, + SF_WAIT_FOR_INPUT_TO_OPEN = 0x4000, + SF_WAIT_FOR_INPUT_TO_SPAWN_HEADCRABS = 0x8000, + SF_NO_SMOKE = 0x10000, + SF_NO_SHAKE = 0x20000, + SF_REMOVE_ON_IMPACT = 0x40000, + SF_NO_IMPACT_EFFECTS = 0x80000, +}; + + +//----------------------------------------------------------------------------- +// Headcrab types +//----------------------------------------------------------------------------- +static const char *s_pHeadcrabClass[] = +{ + "npc_headcrab", + "npc_headcrab_fast", + "npc_headcrab_poison", +}; + + +//----------------------------------------------------------------------------- +// Context think +//----------------------------------------------------------------------------- +static const char *s_pOpenThinkContext = "OpenThink"; +static const char *s_pHeadcrabThinkContext = "HeadcrabThink"; + + +//----------------------------------------------------------------------------- +// HeadcrabCanister Class +//----------------------------------------------------------------------------- +class CEnvHeadcrabCanister : public CBaseAnimating +{ + DECLARE_CLASS( CEnvHeadcrabCanister, CBaseAnimating ); + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + +public: + + // Initialization + CEnvHeadcrabCanister(); + + virtual void Precache( void ); + virtual void Spawn( void ); + virtual void UpdateOnRemove(); + + virtual void SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways ); + +private: + void InputFireCanister( inputdata_t &inputdata ); + void InputOpenCanister( inputdata_t &inputdata ); + void InputSpawnHeadcrabs( inputdata_t &inputdata ); + void InputStopSmoke( inputdata_t &inputdata ); + + // Think(s) + void HeadcrabCanisterSkyboxThink( void ); + void HeadcrabCanisterWorldThink( void ); + void HeadcrabCanisterSpawnHeadcrabThink(); + void HeadcrabCanisterSkyboxOnlyThink( void ); + void HeadcrabCanisterSkyboxRestartThink( void ); + void WaitForOpenSequenceThink(); + + // Place the canister in the world + CSkyCamera* PlaceCanisterInWorld(); + + // Check for impacts + void TestForCollisionsAgainstEntities( const Vector &vecEndPosition ); + void TestForCollisionsAgainstWorld( const Vector &vecEndPosition ); + + // Figure out where we enter the world + void ComputeWorldEntryPoint( Vector *pStartPosition, QAngle *pStartAngles, Vector *pStartDirection ); + + // Blows up! + void Detonate( void ); + + // Landed! + void SetLanded( void ); + void Landed( void ); + + // Open! + void OpenCanister( void ); + void CanisterFinishedOpening(); + + // Set up the world model + void SetupWorldModel(); + + // Start spawning headcrabs + void StartSpawningHeadcrabs( float flDelay ); + +private: + CNetworkVar( bool, m_bLanded ); + + CNetworkVarEmbedded( CEnvHeadcrabCanisterShared, m_Shared ); + CHandle<CSpriteTrail> m_hTrail; + CHandle<SmokeTrail> m_hSmokeTrail; + int m_nHeadcrabType; + int m_nHeadcrabCount; + Vector m_vecImpactPosition; + float m_flDamageRadius; + float m_flDamage; + bool m_bIncomingSoundStarted; + bool m_bHasDetonated; + bool m_bLaunched; + bool m_bOpened; + float m_flSmokeLifetime; + string_t m_iszLaunchPositionName; + + COutputEHANDLE m_OnLaunched; + COutputEvent m_OnImpacted; + COutputEvent m_OnOpened; + + // Only for skybox only cannisters. + float m_flMinRefireTime; + float m_flMaxRefireTime; + int m_nSkyboxCannisterCount; +}; + + +//============================================================================= +// +// HeadcrabCanister Functions +// + +LINK_ENTITY_TO_CLASS( env_headcrabcanister, CEnvHeadcrabCanister ); + +BEGIN_DATADESC( CEnvHeadcrabCanister ) + + DEFINE_FIELD( m_bLanded, FIELD_BOOLEAN ), + DEFINE_EMBEDDED( m_Shared ), + DEFINE_FIELD( m_hTrail, FIELD_EHANDLE ), + DEFINE_FIELD( m_hSmokeTrail, FIELD_EHANDLE ), + DEFINE_KEYFIELD( m_nHeadcrabType, FIELD_INTEGER, "HeadcrabType" ), + DEFINE_KEYFIELD( m_nHeadcrabCount, FIELD_INTEGER, "HeadcrabCount" ), + DEFINE_KEYFIELD( m_flSmokeLifetime, FIELD_FLOAT, "SmokeLifetime" ), + DEFINE_KEYFIELD( m_iszLaunchPositionName, FIELD_STRING, "LaunchPositionName" ), + DEFINE_FIELD( m_vecImpactPosition, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_bIncomingSoundStarted, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bHasDetonated, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bLaunched, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bOpened, FIELD_BOOLEAN ), + DEFINE_KEYFIELD( m_flMinRefireTime, FIELD_FLOAT, "MinSkyboxRefireTime" ), + DEFINE_KEYFIELD( m_flMaxRefireTime, FIELD_FLOAT, "MaxSkyboxRefireTime" ), + DEFINE_KEYFIELD( m_nSkyboxCannisterCount, FIELD_INTEGER, "SkyboxCannisterCount" ), + DEFINE_KEYFIELD( m_flDamageRadius, FIELD_FLOAT, "DamageRadius" ), + DEFINE_KEYFIELD( m_flDamage, FIELD_FLOAT, "Damage" ), + + // Function Pointers. + DEFINE_FUNCTION( HeadcrabCanisterSkyboxThink ), + DEFINE_FUNCTION( HeadcrabCanisterWorldThink ), + DEFINE_FUNCTION( HeadcrabCanisterSpawnHeadcrabThink ), + DEFINE_FUNCTION( WaitForOpenSequenceThink ), + DEFINE_FUNCTION( HeadcrabCanisterSkyboxOnlyThink ), + DEFINE_FUNCTION( HeadcrabCanisterSkyboxRestartThink ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "FireCanister", InputFireCanister ), + DEFINE_INPUTFUNC( FIELD_VOID, "OpenCanister", InputOpenCanister ), + DEFINE_INPUTFUNC( FIELD_VOID, "SpawnHeadcrabs", InputSpawnHeadcrabs ), + DEFINE_INPUTFUNC( FIELD_VOID, "StopSmoke", InputStopSmoke ), + + // Outputs + DEFINE_OUTPUT( m_OnLaunched, "OnLaunched" ), + DEFINE_OUTPUT( m_OnImpacted, "OnImpacted" ), + DEFINE_OUTPUT( m_OnOpened, "OnOpened" ), + +END_DATADESC() + + +EXTERN_SEND_TABLE(DT_EnvHeadcrabCanisterShared); + +IMPLEMENT_SERVERCLASS_ST( CEnvHeadcrabCanister, DT_EnvHeadcrabCanister ) + SendPropDataTable( SENDINFO_DT( m_Shared ), &REFERENCE_SEND_TABLE(DT_EnvHeadcrabCanisterShared) ), + SendPropBool( SENDINFO( m_bLanded ) ), +END_SEND_TABLE() + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CEnvHeadcrabCanister::CEnvHeadcrabCanister() +{ + m_flMinRefireTime = -1.0f; + m_flMaxRefireTime = -1.0f; +} + + +//----------------------------------------------------------------------------- +// Precache! +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::Precache( void ) +{ + BaseClass::Precache(); + PrecacheModel( ENV_HEADCRABCANISTER_MODEL ); + PrecacheModel( ENV_HEADCRABCANISTER_BROKEN_MODEL ); + PrecacheModel( ENV_HEADCRABCANISTER_SKYBOX_MODEL ); + PrecacheModel("sprites/smoke.vmt"); + + PrecacheScriptSound( "HeadcrabCanister.LaunchSound" ); + PrecacheScriptSound( "HeadcrabCanister.AfterLanding" ); + PrecacheScriptSound( "HeadcrabCanister.Explosion" ); + PrecacheScriptSound( "HeadcrabCanister.IncomingSound" ); + PrecacheScriptSound( "HeadcrabCanister.SkyboxExplosion" ); + PrecacheScriptSound( "HeadcrabCanister.Open" ); + + UTIL_PrecacheOther( s_pHeadcrabClass[m_nHeadcrabType] ); +} + + +//----------------------------------------------------------------------------- +// Spawn! +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::Spawn( void ) +{ + Precache(); + BaseClass::Spawn(); + + // Do we have a position to launch from? + if ( m_iszLaunchPositionName != NULL_STRING ) + { + // It doesn't have any real presence at first. + SetSolid( SOLID_NONE ); + + m_vecImpactPosition = GetAbsOrigin(); + m_bIncomingSoundStarted = false; + m_bLanded = false; + m_bHasDetonated = false; + m_bOpened = false; + } + else if ( !HasSpawnFlags( SF_START_IMPACTED ) ) + { + // It doesn't have any real presence at first. + SetSolid( SOLID_NONE ); + + if ( !HasSpawnFlags( SF_LAND_AT_INITIAL_POSITION ) ) + { + Vector vecForward; + GetVectors( &vecForward, NULL, NULL ); + vecForward *= -1.0f; + + trace_t trace; + UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vecForward * 10000, MASK_NPCWORLDSTATIC, + this, COLLISION_GROUP_NONE, &trace ); + + m_vecImpactPosition = trace.endpos; + } + else + { + m_vecImpactPosition = GetAbsOrigin(); + } + + m_bIncomingSoundStarted = false; + m_bLanded = false; + m_bHasDetonated = false; + m_bOpened = false; + } + else + { + m_bHasDetonated = true; + m_bIncomingSoundStarted = true; + m_bOpened = false; + m_vecImpactPosition = GetAbsOrigin(); + Landed(); + } +} + + +//----------------------------------------------------------------------------- +// On remove! +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::UpdateOnRemove() +{ + BaseClass::UpdateOnRemove(); + StopSound( "HeadcrabCanister.AfterLanding" ); + if ( m_hTrail ) + { + UTIL_Remove( m_hTrail ); + m_hTrail = NULL; + } + if ( m_hSmokeTrail ) + { + UTIL_Remove( m_hSmokeTrail ); + m_hSmokeTrail = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Set up the world model +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::SetupWorldModel() +{ + SetModel( ENV_HEADCRABCANISTER_MODEL ); + SetSolid( SOLID_BBOX ); + + float flRadius = CollisionProp()->BoundingRadius(); + Vector vecMins( -flRadius, -flRadius, -flRadius ); + Vector vecMaxs( flRadius, flRadius, flRadius ); + SetSize( vecMins, vecMaxs ); + +} + + +//----------------------------------------------------------------------------- +// Figure out where we enter the world +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::ComputeWorldEntryPoint( Vector *pStartPosition, QAngle *pStartAngles, Vector *pStartDirection ) +{ + SetupWorldModel(); + + Vector vecForward; + GetVectors( &vecForward, NULL, NULL ); + + // Raycast up to the place where we should start from (start raycast slightly off the ground, + // since it'll be buried in the ground oftentimes) + trace_t tr; + CTraceFilterWorldOnly filter; + UTIL_TraceLine( GetAbsOrigin() + vecForward * 100, GetAbsOrigin() + vecForward * 10000, + CONTENTS_SOLID, &filter, &tr ); + + *pStartPosition = tr.endpos; + *pStartAngles = GetAbsAngles(); + VectorMultiply( vecForward, -1.0f, *pStartDirection ); +} + + +//----------------------------------------------------------------------------- +// Place the canister in the world +//----------------------------------------------------------------------------- +CSkyCamera *CEnvHeadcrabCanister::PlaceCanisterInWorld() +{ + CSkyCamera *pCamera = NULL; + + // Are we launching from a point? If so, use that point. + if ( m_iszLaunchPositionName != NULL_STRING ) + { + // Get the launch position entity + CBaseEntity *pLaunchPos = gEntList.FindEntityByName( NULL, m_iszLaunchPositionName ); + if ( !pLaunchPos ) + { + Warning("%s (%s) could not find an entity matching LaunchPositionName of '%s'\n", GetEntityName().ToCStr(), GetDebugName(), STRING(m_iszLaunchPositionName) ); + SUB_Remove(); + } + else + { + SetupWorldModel(); + + Vector vecForward, vecImpactDirection; + GetVectors( &vecForward, NULL, NULL ); + VectorMultiply( vecForward, -1.0f, vecImpactDirection ); + + m_Shared.InitInWorld( gpGlobals->curtime, pLaunchPos->GetAbsOrigin(), GetAbsAngles(), + vecImpactDirection, m_vecImpactPosition, true ); + SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterWorldThink ); + SetNextThink( gpGlobals->curtime ); + } + } + else if ( DetectInSkybox() ) + { + pCamera = GetEntitySkybox(); + + SetModel( ENV_HEADCRABCANISTER_SKYBOX_MODEL ); + SetSolid( SOLID_NONE ); + + Vector vecForward; + GetVectors( &vecForward, NULL, NULL ); + vecForward *= -1.0f; + + m_Shared.InitInSkybox( gpGlobals->curtime, m_vecImpactPosition, GetAbsAngles(), vecForward, + m_vecImpactPosition, pCamera->m_skyboxData.origin, pCamera->m_skyboxData.scale ); + AddEFlags( EFL_IN_SKYBOX ); + SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterSkyboxOnlyThink ); + SetNextThink( gpGlobals->curtime + m_Shared.GetEnterWorldTime() + TICK_INTERVAL ); + } + else + { + Vector vecStartPosition, vecDirection; + QAngle vecStartAngles; + ComputeWorldEntryPoint( &vecStartPosition, &vecStartAngles, &vecDirection ); + + // Figure out which skybox to place the entity in. + pCamera = GetCurrentSkyCamera(); + if ( pCamera ) + { + m_Shared.InitInSkybox( gpGlobals->curtime, vecStartPosition, vecStartAngles, vecDirection, + m_vecImpactPosition, pCamera->m_skyboxData.origin, pCamera->m_skyboxData.scale ); + + if ( m_Shared.IsInSkybox() ) + { + SetModel( ENV_HEADCRABCANISTER_SKYBOX_MODEL ); + SetSolid( SOLID_NONE ); + AddEFlags( EFL_IN_SKYBOX ); + SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterSkyboxThink ); + SetNextThink( gpGlobals->curtime + m_Shared.GetEnterWorldTime() ); + } + else + { + SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterWorldThink ); + SetNextThink( gpGlobals->curtime ); + } + } + else + { + m_Shared.InitInWorld( gpGlobals->curtime, vecStartPosition, vecStartAngles, + vecDirection, m_vecImpactPosition ); + SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterWorldThink ); + SetNextThink( gpGlobals->curtime ); + } + } + + Vector vecEndPosition; + QAngle vecEndAngles; + m_Shared.GetPositionAtTime( gpGlobals->curtime, vecEndPosition, vecEndAngles ); + SetAbsOrigin( vecEndPosition ); + SetAbsAngles( vecEndAngles ); + + return pCamera; +} + + +//----------------------------------------------------------------------------- +// Fires the canister! +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::InputFireCanister( inputdata_t &inputdata ) +{ + if (m_bLaunched) + return; + + m_bLaunched = true; + + if ( HasSpawnFlags( SF_START_IMPACTED ) ) + { + StartSpawningHeadcrabs( 0.01f ); + return; + } + + // Play a firing sound + CPASAttenuationFilter filter( this, ATTN_NONE ); + + if ( !HasSpawnFlags( SF_NO_LAUNCH_SOUND ) ) + { + EmitSound( filter, entindex(), "HeadcrabCanister.LaunchSound" ); + } + + // Place the canister + CSkyCamera *pCamera = PlaceCanisterInWorld(); + + // Hook up a smoke trail + m_hTrail = CSpriteTrail::SpriteTrailCreate( "sprites/smoke.vmt", GetAbsOrigin(), true ); + m_hTrail->SetTransparency( kRenderTransAdd, 224, 224, 255, 255, kRenderFxNone ); + m_hTrail->SetAttachment( this, 0 ); + m_hTrail->SetStartWidth( 32.0 ); + m_hTrail->SetEndWidth( 200.0 ); + m_hTrail->SetStartWidthVariance( 15.0f ); + m_hTrail->SetTextureResolution( 0.002 ); + m_hTrail->SetLifeTime( ENV_HEADCRABCANISTER_TRAIL_TIME ); + m_hTrail->SetMinFadeLength( 1000.0f ); + + if ( pCamera && m_Shared.IsInSkybox() ) + { + m_hTrail->SetSkybox( pCamera->m_skyboxData.origin, pCamera->m_skyboxData.scale ); + } + + // Fire that output! + m_OnLaunched.Set( this, this, this ); +} + + +//----------------------------------------------------------------------------- +// Opens the canister! +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::InputOpenCanister( inputdata_t &inputdata ) +{ + if ( m_bLanded && !m_bOpened && HasSpawnFlags( SF_WAIT_FOR_INPUT_TO_OPEN ) ) + { + OpenCanister(); + } +} + + +//----------------------------------------------------------------------------- +// Spawns headcrabs +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::InputSpawnHeadcrabs( inputdata_t &inputdata ) +{ + if ( m_bLanded && m_bOpened && HasSpawnFlags( SF_WAIT_FOR_INPUT_TO_SPAWN_HEADCRABS ) ) + { + StartSpawningHeadcrabs( 0.01f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::InputStopSmoke( inputdata_t &inputdata ) +{ + if ( m_hSmokeTrail != NULL ) + { + UTIL_Remove( m_hSmokeTrail ); + m_hSmokeTrail = NULL; + } +} + +//============================================================================= +// +// Enumerator for swept bbox collision. +// +class CCollideList : public IEntityEnumerator +{ +public: + CCollideList( Ray_t *pRay, CBaseEntity* pIgnoreEntity, int nContentsMask ) : + m_Entities( 0, 32 ), m_pIgnoreEntity( pIgnoreEntity ), + m_nContentsMask( nContentsMask ), m_pRay(pRay) {} + + virtual bool EnumEntity( IHandleEntity *pHandleEntity ) + { + // Don't bother with the ignore entity. + if ( pHandleEntity == m_pIgnoreEntity ) + return true; + + Assert( pHandleEntity ); + + trace_t tr; + enginetrace->ClipRayToEntity( *m_pRay, m_nContentsMask, pHandleEntity, &tr ); + if (( tr.fraction < 1.0f ) || (tr.startsolid) || (tr.allsolid)) + { + CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() ); + m_Entities.AddToTail( pEntity ); + } + + return true; + } + + CUtlVector<CBaseEntity*> m_Entities; + +private: + CBaseEntity *m_pIgnoreEntity; + int m_nContentsMask; + Ray_t *m_pRay; +}; + + +//----------------------------------------------------------------------------- +// Test for impact! +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::TestForCollisionsAgainstEntities( const Vector &vecEndPosition ) +{ + // Debugging!! +// NDebugOverlay::Box( GetAbsOrigin(), m_vecMin * 0.5f, m_vecMax * 0.5f, 255, 255, 0, 0, 5 ); +// NDebugOverlay::Box( vecEndPosition, m_vecMin, m_vecMax, 255, 0, 0, 0, 5 ); + + float flRadius = CollisionProp()->BoundingRadius(); + Vector vecMins( -flRadius, -flRadius, -flRadius ); + Vector vecMaxs( flRadius, flRadius, flRadius ); + + Ray_t ray; + ray.Init( GetAbsOrigin(), vecEndPosition, vecMins, vecMaxs ); + + CCollideList collideList( &ray, this, MASK_SOLID ); + enginetrace->EnumerateEntities( ray, false, &collideList ); + + float flDamage = m_flDamage; + + // Now get each entity and react accordinly! + for( int iEntity = collideList.m_Entities.Count(); --iEntity >= 0; ) + { + CBaseEntity *pEntity = collideList.m_Entities[iEntity]; + Vector vecForceDir = m_Shared.m_vecDirection; + + // Check for a physics object and apply force! + IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject(); + if ( pPhysObject ) + { + float flMass = PhysGetEntityMass( pEntity ); + vecForceDir *= flMass * 750; + pPhysObject->ApplyForceCenter( vecForceDir ); + } + + if ( pEntity->m_takedamage && ( m_flDamage != 0.0f ) ) + { + CTakeDamageInfo info( this, this, flDamage, DMG_BLAST ); + CalculateExplosiveDamageForce( &info, vecForceDir, pEntity->GetAbsOrigin() ); + pEntity->TakeDamage( info ); + } + } +} + + +//----------------------------------------------------------------------------- +// Test for impact! +//----------------------------------------------------------------------------- +#define INNER_RADIUS_FRACTION 0.25f + +void CEnvHeadcrabCanister::TestForCollisionsAgainstWorld( const Vector &vecEndPosition ) +{ + // Splash damage! + // Iterate on all entities in the vicinity. + float flDamageRadius = m_flDamageRadius; + float flDamage = m_flDamage; + + CBaseEntity *pEntity; + for ( CEntitySphereQuery sphere( vecEndPosition, flDamageRadius ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() ) + { + if ( pEntity == this ) + continue; + + if ( !pEntity->IsSolid() ) + continue; + + // Get distance to object and use it as a scale value. + Vector vecSegment; + VectorSubtract( pEntity->GetAbsOrigin(), vecEndPosition, vecSegment ); + float flDistance = VectorNormalize( vecSegment ); + + float flFactor = 1.0f / ( flDamageRadius * (INNER_RADIUS_FRACTION - 1) ); + flFactor *= flFactor; + float flScale = flDistance - flDamageRadius; + flScale *= flScale * flFactor; + if ( flScale > 1.0f ) + { + flScale = 1.0f; + } + + // Check for a physics object and apply force! + Vector vecForceDir = vecSegment; + IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject(); + if ( pPhysObject ) + { + // Send it flying!!! + float flMass = PhysGetEntityMass( pEntity ); + vecForceDir *= flMass * 750 * flScale; + pPhysObject->ApplyForceCenter( vecForceDir ); + } + + if ( pEntity->m_takedamage && ( m_flDamage != 0.0f ) ) + { + CTakeDamageInfo info( this, this, flDamage * flScale, DMG_BLAST ); + CalculateExplosiveDamageForce( &info, vecSegment, pEntity->GetAbsOrigin() ); + pEntity->TakeDamage( info ); + } + + if ( pEntity->IsPlayer() && !(static_cast<CBasePlayer*>(pEntity)->IsInAVehicle()) ) + { + if (vecSegment.z < 0.1f) + { + vecSegment.z = 0.1f; + VectorNormalize( vecSegment ); + } + float flAmount = SimpleSplineRemapVal( flScale, 0.0f, 1.0f, 250.0f, 1000.0f ); + pEntity->ApplyAbsVelocityImpulse( vecSegment * flAmount ); + } + } +} + + +//----------------------------------------------------------------------------- +// Headcrab creation +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::HeadcrabCanisterSpawnHeadcrabThink() +{ + Vector vecSpawnPosition; + QAngle vecSpawnAngles; + + --m_nHeadcrabCount; + + int nHeadCrabAttachment = LookupAttachment( "headcrab" ); + if ( GetAttachment( nHeadCrabAttachment, vecSpawnPosition, vecSpawnAngles ) ) + { + CBaseEntity *pEnt = CreateEntityByName( s_pHeadcrabClass[m_nHeadcrabType] ); + CBaseHeadcrab *pHeadCrab = assert_cast<CBaseHeadcrab*>(pEnt); + + // Necessary to get it to eject properly (don't allow the NPC + // to override the spawn position specified). + pHeadCrab->AddSpawnFlags( SF_NPC_FALL_TO_GROUND ); + + // So we don't collide with the canister + // NOTE: Hierarchical attachment is necessary here to get the animations to work + pHeadCrab->SetOwnerEntity( this ); + DispatchSpawn( pHeadCrab ); + pHeadCrab->SetParent( this, nHeadCrabAttachment ); + pHeadCrab->SetLocalOrigin( vec3_origin ); + pHeadCrab->SetLocalAngles( vec3_angle ); + pHeadCrab->CrawlFromCanister(); + } + + if ( m_nHeadcrabCount != 0 ) + { + float flWaitTime = random->RandomFloat( 1.0f, 2.0f ); + SetContextThink( &CEnvHeadcrabCanister::HeadcrabCanisterSpawnHeadcrabThink, gpGlobals->curtime + flWaitTime, s_pHeadcrabThinkContext ); + } + else + { + SetContextThink( NULL, gpGlobals->curtime, s_pHeadcrabThinkContext ); + } +} + + +//----------------------------------------------------------------------------- +// Start spawning headcrabs +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::StartSpawningHeadcrabs( float flDelay ) +{ + if ( !m_bLanded || !m_bOpened || m_nHeadcrabCount == 0 ) + return; + + if ( m_nHeadcrabCount != 0 ) + { + SetContextThink( &CEnvHeadcrabCanister::HeadcrabCanisterSpawnHeadcrabThink, gpGlobals->curtime + flDelay, s_pHeadcrabThinkContext ); + } +} + + +//----------------------------------------------------------------------------- +// Canister finished opening +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::CanisterFinishedOpening( void ) +{ + ResetSequence( LookupSequence( "idle_open" ) ); + m_OnOpened.FireOutput( this, this, 0 ); + m_bOpened = true; + SetContextThink( NULL, gpGlobals->curtime, s_pOpenThinkContext ); + + if ( !HasSpawnFlags( SF_START_IMPACTED ) ) + { + if ( !HasSpawnFlags( SF_WAIT_FOR_INPUT_TO_SPAWN_HEADCRABS ) ) + { + StartSpawningHeadcrabs( 3.0f ); + } + } +} + + +//----------------------------------------------------------------------------- +// Finish the opening sequence +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::WaitForOpenSequenceThink() +{ + StudioFrameAdvance(); + if ( ( GetSequence() == LookupSequence( "open" ) ) && IsSequenceFinished() ) + { + CanisterFinishedOpening(); + } + else + { + SetContextThink( &CEnvHeadcrabCanister::WaitForOpenSequenceThink, gpGlobals->curtime + 0.01f, s_pOpenThinkContext ); + } +} + + +//----------------------------------------------------------------------------- +// Open the canister! +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::OpenCanister( void ) +{ + if ( m_bOpened ) + return; + + int nOpenSequence = LookupSequence( "open" ); + if ( nOpenSequence != ACT_INVALID ) + { + EmitSound( "HeadcrabCanister.Open" ); + + ResetSequence( nOpenSequence ); + SetContextThink( &CEnvHeadcrabCanister::WaitForOpenSequenceThink, gpGlobals->curtime + 0.01f, s_pOpenThinkContext ); + } + else + { + CanisterFinishedOpening(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::SetLanded( void ) +{ + SetAbsOrigin( m_vecImpactPosition ); + SetModel( ENV_HEADCRABCANISTER_BROKEN_MODEL ); + SetMoveType( MOVETYPE_NONE ); + SetSolid( SOLID_VPHYSICS ); + VPhysicsInitStatic(); + + IncrementInterpolationFrame(); + m_bLanded = true; +} + +//----------------------------------------------------------------------------- +// Landed! +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::Landed( void ) +{ + EmitSound( "HeadcrabCanister.AfterLanding" ); + + // Lock us now that we've stopped + SetLanded(); + + // Hook the follow trail to the lead of the canister (which should be buried) + // to hide problems with the edge of the follow trail + if (m_hTrail) + { + m_hTrail->SetAttachment( this, LookupAttachment("trail") ); + } + + // Start smoke, unless we don't want it + if ( !HasSpawnFlags( SF_NO_SMOKE ) ) + { + // Create the smoke trail to obscure the headcrabs + m_hSmokeTrail = SmokeTrail::CreateSmokeTrail(); + m_hSmokeTrail->FollowEntity( this, "smoke" ); + + m_hSmokeTrail->m_SpawnRate = 8; + m_hSmokeTrail->m_ParticleLifetime = 2.0f; + + m_hSmokeTrail->m_StartColor.Init( 0.7f, 0.7f, 0.7f ); + m_hSmokeTrail->m_EndColor.Init( 0.6, 0.6, 0.6 ); + + m_hSmokeTrail->m_StartSize = 32; + m_hSmokeTrail->m_EndSize = 64; + m_hSmokeTrail->m_SpawnRadius= 8; + m_hSmokeTrail->m_MinSpeed = 0; + m_hSmokeTrail->m_MaxSpeed = 8; + m_hSmokeTrail->m_MinDirectedSpeed = 32; + m_hSmokeTrail->m_MaxDirectedSpeed = 64; + m_hSmokeTrail->m_Opacity = 0.35f; + + m_hSmokeTrail->SetLifetime( m_flSmokeLifetime ); + } + + SetThink( NULL ); + + if ( !HasSpawnFlags( SF_WAIT_FOR_INPUT_TO_OPEN ) ) + { + if ( HasSpawnFlags( SF_START_IMPACTED ) ) + { + CanisterFinishedOpening( ); + } + else + { + OpenCanister(); + } + } +} + + +//----------------------------------------------------------------------------- +// Creates the explosion effect +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::Detonate( ) +{ + // Send the impact output + m_OnImpacted.FireOutput( this, this, 0 ); + + if ( !HasSpawnFlags( SF_NO_IMPACT_SOUND ) ) + { + StopSound( "HeadcrabCanister.IncomingSound" ); + EmitSound( "HeadcrabCanister.Explosion" ); + } + + // If we're supposed to be removed, do that now + if ( HasSpawnFlags( SF_REMOVE_ON_IMPACT ) ) + { + SetAbsOrigin( m_vecImpactPosition ); + SetModel( ENV_HEADCRABCANISTER_BROKEN_MODEL ); + SetMoveType( MOVETYPE_NONE ); + IncrementInterpolationFrame(); + m_bLanded = true; + + // Become invisible so our trail can finish up + AddEffects( EF_NODRAW ); + SetSolidFlags( FSOLID_NOT_SOLID ); + + SetThink( &CEnvHeadcrabCanister::SUB_Remove ); + SetNextThink( gpGlobals->curtime + ENV_HEADCRABCANISTER_TRAIL_TIME ); + + return; + } + + // Test for damaging things + TestForCollisionsAgainstWorld( m_vecImpactPosition ); + + // Shake the screen unless flagged otherwise + if ( !HasSpawnFlags( SF_NO_SHAKE ) ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 ); + + // If the player is on foot, then do a more limited shake + float shakeRadius = ( pPlayer && pPlayer->IsInAVehicle() ) ? sk_env_headcrabcanister_shake_radius_vehicle.GetFloat() : sk_env_headcrabcanister_shake_radius.GetFloat(); + + UTIL_ScreenShake( m_vecImpactPosition, sk_env_headcrabcanister_shake_amplitude.GetFloat(), 150.0, 1.0, shakeRadius, SHAKE_START ); + } + + // Do explosion effects + if ( !HasSpawnFlags( SF_NO_IMPACT_EFFECTS ) ) + { + // Normal explosion + ExplosionCreate( m_vecImpactPosition, GetAbsAngles(), this, 50.0f, 500.0f, + SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODAMAGE | SF_ENVEXPLOSION_NOSOUND, 1300.0f ); + + // Dust explosion + AR2Explosion *pExplosion = AR2Explosion::CreateAR2Explosion( m_vecImpactPosition ); + + if( pExplosion ) + { + pExplosion->SetLifetime(10); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: This think function simulates (moves/collides) the HeadcrabCanister while in +// the world. +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::HeadcrabCanisterWorldThink( void ) +{ + // Get the current time. + float flTime = gpGlobals->curtime; + + Vector vecStartPosition = GetAbsOrigin(); + + // Update HeadcrabCanister position for swept collision test. + Vector vecEndPosition; + QAngle vecEndAngles; + m_Shared.GetPositionAtTime( flTime, vecEndPosition, vecEndAngles ); + + if ( !m_bIncomingSoundStarted && !HasSpawnFlags( SF_NO_IMPACT_SOUND ) ) + { + float flDistSq = ENV_HEADCRABCANISTER_INCOMING_SOUND_TIME * m_Shared.m_flFlightSpeed; + flDistSq *= flDistSq; + if ( vecEndPosition.DistToSqr(m_vecImpactPosition) <= flDistSq ) + { + // Figure out if we're close enough to play the incoming sound + EmitSound( "HeadcrabCanister.IncomingSound" ); + m_bIncomingSoundStarted = true; + } + } + + TestForCollisionsAgainstEntities( vecEndPosition ); + if ( m_Shared.DidImpact( flTime ) ) + { + if ( !m_bHasDetonated ) + { + Detonate(); + m_bHasDetonated = true; + } + + if ( !HasSpawnFlags( SF_REMOVE_ON_IMPACT ) ) + { + Landed(); + } + + return; + } + + // Always move full movement. + SetAbsOrigin( vecEndPosition ); + + // Touch triggers along the way + PhysicsTouchTriggers( &vecStartPosition ); + + SetNextThink( gpGlobals->curtime + 0.2f ); + SetAbsAngles( vecEndAngles ); + + if ( !m_bHasDetonated ) + { + if ( vecEndPosition.DistToSqr( m_vecImpactPosition ) < BoundingRadius() * BoundingRadius() ) + { + Detonate(); + m_bHasDetonated = true; + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: This think function should be called at the time when the HeadcrabCanister +// will be leaving the skybox and entering the world. +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::HeadcrabCanisterSkyboxThink( void ) +{ + // Use different position computation + m_Shared.ConvertFromSkyboxToWorld(); + + Vector vecEndPosition; + QAngle vecEndAngles; + m_Shared.GetPositionAtTime( gpGlobals->curtime, vecEndPosition, vecEndAngles ); + UTIL_SetOrigin( this, vecEndPosition ); + SetAbsAngles( vecEndAngles ); + RemoveEFlags( EFL_IN_SKYBOX ); + + // Switch to the actual-scale model + SetupWorldModel(); + + // Futz with the smoke trail to get it working across the boundary + m_hTrail->SetSkybox( vec3_origin, 1.0f ); + + // Now we start looking for collisions + SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterWorldThink ); + SetNextThink( gpGlobals->curtime + 0.01f ); +} + + +//----------------------------------------------------------------------------- +// Purpose: This stops its motion in the skybox +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::HeadcrabCanisterSkyboxOnlyThink( void ) +{ + Vector vecEndPosition; + QAngle vecEndAngles; + m_Shared.GetPositionAtTime( gpGlobals->curtime, vecEndPosition, vecEndAngles ); + UTIL_SetOrigin( this, vecEndPosition ); + SetAbsAngles( vecEndAngles ); + + if ( !HasSpawnFlags( SF_NO_IMPACT_SOUND ) ) + { + CPASAttenuationFilter filter( this, ATTN_NONE ); + EmitSound( filter, entindex(), "HeadcrabCanister.SkyboxExplosion" ); + } + + if ( m_nSkyboxCannisterCount != 0 ) + { + if ( --m_nSkyboxCannisterCount <= 0 ) + { + SetThink( NULL ); + return; + } + } + + float flRefireTime = random->RandomFloat( m_flMinRefireTime, m_flMaxRefireTime ) + ENV_HEADCRABCANISTER_TRAIL_TIME; + SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterSkyboxRestartThink ); + SetNextThink( gpGlobals->curtime + flRefireTime ); +} + + +//----------------------------------------------------------------------------- +// This will re-fire the headcrab cannister +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::HeadcrabCanisterSkyboxRestartThink( void ) +{ + if ( m_hTrail ) + { + UTIL_Remove( m_hTrail ); + m_hTrail = NULL; + } + + m_bLaunched = false; + + inputdata_t data; + InputFireCanister( data ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pInfo - +// bAlways - +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways ) +{ + // Are we already marked for transmission? + if ( pInfo->m_pTransmitEdict->Get( entindex() ) ) + return; + + BaseClass::SetTransmit( pInfo, bAlways ); + + // Make our smoke trail always come with us + if ( m_hSmokeTrail ) + { + m_hSmokeTrail->SetTransmit( pInfo, bAlways ); + } +} |