source-engine/game/server/hl2/env_headcrabcanister.cpp

1115 lines
33 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= 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 );
}
}