//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// #include "cbase.h" #include "basecombatcharacter.h" #include "entityoutput.h" #include "physics.h" #include "explode.h" #include "vphysics_interface.h" #include "collisionutils.h" #include "steamjet.h" #include "eventqueue.h" #include "soundflags.h" #include "engine/IEngineSound.h" #include "props.h" #include "physics_cannister.h" #include "globals.h" #include "physics_saverestore.h" #include "shareddefs.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define SF_CANNISTER_ASLEEP 0x0001 #define SF_CANNISTER_EXPLODE 0x0002 BEGIN_SIMPLE_DATADESC( CThrustController ) DEFINE_FIELD( m_thrustVector, FIELD_VECTOR ), DEFINE_FIELD( m_torqueVector, FIELD_VECTOR ), DEFINE_KEYFIELD( m_thrust, FIELD_FLOAT, "thrust" ), END_DATADESC() LINK_ENTITY_TO_CLASS( physics_cannister, CPhysicsCannister ); BEGIN_DATADESC( CPhysicsCannister ) DEFINE_OUTPUT( m_onActivate, "OnActivate" ), DEFINE_OUTPUT( m_OnAwakened, "OnAwakened" ), DEFINE_FIELD( m_thrustOrigin, FIELD_VECTOR ), // this is a position, but in local space DEFINE_EMBEDDED( m_thruster ), DEFINE_PHYSPTR( m_pController ), DEFINE_FIELD( m_pJet, FIELD_CLASSPTR ), DEFINE_FIELD( m_active, FIELD_BOOLEAN ), DEFINE_KEYFIELD( m_thrustTime, FIELD_FLOAT, "fuel" ), DEFINE_KEYFIELD( m_damage, FIELD_FLOAT, "expdamage" ), DEFINE_KEYFIELD( m_damageRadius, FIELD_FLOAT, "expradius" ), DEFINE_FIELD( m_activateTime, FIELD_TIME ), DEFINE_KEYFIELD( m_gasSound, FIELD_SOUNDNAME, "gassound" ), DEFINE_FIELD( m_bFired, FIELD_BOOLEAN ), // Physics Influence DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ), DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ), DEFINE_FIELD( m_hLauncher, FIELD_EHANDLE ), DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ), DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ), DEFINE_INPUTFUNC( FIELD_VOID, "Explode", InputExplode ), DEFINE_INPUTFUNC( FIELD_VOID, "Wake", InputWake ), DEFINE_THINKFUNC( BeginShutdownThink ), DEFINE_ENTITYFUNC( ExplodeTouch ), END_DATADESC() void CPhysicsCannister::Spawn( void ) { Precache(); SetModel( STRING(GetModelName()) ); SetBloodColor( DONT_BLEED ); AddSolidFlags( FSOLID_CUSTOMRAYTEST ); m_takedamage = DAMAGE_YES; SetNextThink( TICK_NEVER_THINK ); if ( m_iHealth <= 0 ) m_iHealth = 25; m_flAnimTime = gpGlobals->curtime; m_flPlaybackRate = 0.0; SetCycle( 0 ); m_bFired = false; // not thrusting m_active = false; CreateVPhysics(); if ( !VPhysicsGetObject() ) { // must have a physics object or code will crash later UTIL_Remove(this); } } void CPhysicsCannister::OnRestore() { BaseClass::OnRestore(); if ( m_pController ) { m_pController->SetEventHandler( &m_thruster ); } } bool CPhysicsCannister::CreateVPhysics() { bool asleep = HasSpawnFlags(SF_CANNISTER_ASLEEP); VPhysicsInitNormal( SOLID_VPHYSICS, 0, asleep ); return true; } bool CPhysicsCannister::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace ) { Vector vecAbsMins, vecAbsMaxs; CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs ); if ( !IsBoxIntersectingRay( vecAbsMins, vecAbsMaxs, ray.m_Start, ray.m_Delta ) ) return false; return BaseClass::TestCollision( ray, mask, trace ); } Vector CPhysicsCannister::CalcLocalThrust( const Vector &offset ) { matrix3x4_t nozzleMatrix; Vector thrustDirection; GetAttachment( LookupAttachment("nozzle"), nozzleMatrix ); MatrixGetColumn( nozzleMatrix, 2, thrustDirection ); MatrixGetColumn( nozzleMatrix, 3, m_thrustOrigin ); thrustDirection = -5*thrustDirection + offset; VectorNormalize( thrustDirection ); return thrustDirection; } CPhysicsCannister::~CPhysicsCannister( void ) { } void CPhysicsCannister::Precache( void ) { PropBreakablePrecacheAll( GetModelName() ); if ( m_gasSound != NULL_STRING ) { PrecacheScriptSound( STRING(m_gasSound) ); } BaseClass::Precache(); } int CPhysicsCannister::OnTakeDamage( const CTakeDamageInfo &info ) { // HACKHACK: Shouldn't g_vecAttackDir be a parameter to this function? if ( !m_takedamage ) return 0; if ( !m_active ) { m_iHealth -= info.GetDamage(); if ( m_iHealth < 0 ) { Explode( info.GetAttacker() ); } else { // explosions that don't destroy will activate // 50% of the time blunt damage will activate as well if ( (info.GetDamageType() & DMG_BLAST) || ( (info.GetDamageType() & (DMG_CLUB|DMG_SLASH|DMG_CRUSH) ) && random->RandomInt(1,100) < 50 ) ) { CannisterActivate( info.GetAttacker(), g_vecAttackDir ); } } return 1; } if ( (gpGlobals->curtime - m_activateTime) <= 0.1 ) return 0; if ( info.GetDamageType() & (DMG_BULLET|DMG_BUCKSHOT|DMG_BURN|DMG_BLAST) ) { Explode( info.GetAttacker() ); } return 0; } void CPhysicsCannister::TraceAttack( const CTakeDamageInfo &info, const Vector &dir, trace_t *ptr, CDmgAccumulator *pAccumulator ) { if ( !m_active && ptr->hitgroup != 0 ) { Vector direction = -dir; direction.z -= 5; VectorNormalize( direction ); CannisterActivate( info.GetAttacker(), direction ); } BaseClass::TraceAttack( info, dir, ptr, pAccumulator ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPhysicsCannister::CannisterActivate( CBaseEntity *pActivator, const Vector &thrustOffset ) { // already active or spent if ( m_active || !m_thrustTime ) { return; } m_hLauncher = pActivator; Vector thrustDirection = CalcLocalThrust( thrustOffset ); m_onActivate.FireOutput( pActivator, this, 0 ); m_thruster.CalcThrust( m_thrustOrigin, thrustDirection, VPhysicsGetObject() ); m_pController = physenv->CreateMotionController( &m_thruster ); IPhysicsObject *pPhys = VPhysicsGetObject(); m_pController->AttachObject( pPhys, true ); // Make sure the object is simulated pPhys->Wake(); m_active = true; m_activateTime = gpGlobals->curtime; SetNextThink( gpGlobals->curtime + m_thrustTime ); SetThink( &CPhysicsCannister::BeginShutdownThink ); QAngle angles; VectorAngles( -thrustDirection, angles ); m_pJet = dynamic_cast( CBaseEntity::Create( "env_steam", m_thrustOrigin, angles, this ) ); m_pJet->SetParent( this ); float extra = m_thruster.m_thrust * (1/5000.f); extra = clamp( extra, 0.f, 1.f ); m_pJet->m_SpreadSpeed = 15 * m_thruster.m_thrust * 0.001; m_pJet->m_Speed = 128 + 100 * extra; m_pJet->m_StartSize = 10; m_pJet->m_EndSize = 25; m_pJet->m_Rate = 52 + (int)extra*20; m_pJet->m_JetLength = 64; m_pJet->m_clrRender = m_clrRender; m_pJet->Use( this, this, USE_ON, 1 ); if ( m_gasSound != NULL_STRING ) { CPASAttenuationFilter filter( this ); EmitSound_t ep; ep.m_nChannel = CHAN_ITEM; ep.m_pSoundName = STRING(m_gasSound); ep.m_flVolume = 1.0f; ep.m_SoundLevel = SNDLVL_NORM; EmitSound( filter, entindex(), ep ); } } //----------------------------------------------------------------------------- // Purpose: The cannister's been fired by a weapon, so it should stay pretty accurate //----------------------------------------------------------------------------- void CPhysicsCannister::CannisterFire( CBaseEntity *pActivator ) { m_bFired = true; // Increase thrust m_thruster.m_thrust *= 4; // Only last a short time m_thrustTime = 10.0; // Explode on contact SetTouch( &CPhysicsCannister::ExplodeTouch ); CannisterActivate( pActivator, vec3_origin ); } //----------------------------------------------------------------------------- // Purpose: Input handler for activating the cannister. //----------------------------------------------------------------------------- void CPhysicsCannister::InputActivate( inputdata_t &data ) { CannisterActivate( data.pActivator, Vector(0,0.1,-0.25) ); } //----------------------------------------------------------------------------- // Purpose: Input handler for deactivating the cannister. //----------------------------------------------------------------------------- void CPhysicsCannister::InputDeactivate(inputdata_t &data) { Deactivate(); } //----------------------------------------------------------------------------- // Purpose: Input handler for making the cannister go boom. //----------------------------------------------------------------------------- void CPhysicsCannister::InputExplode(inputdata_t &data) { Explode( data.pActivator ); } //----------------------------------------------------------------------------- // Purpose: Input handler for waking up the cannister if it is sleeping. //----------------------------------------------------------------------------- void CPhysicsCannister::InputWake( inputdata_t &data ) { IPhysicsObject *pPhys = VPhysicsGetObject(); if ( pPhys != NULL ) { pPhys->Wake(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPhysicsCannister::Deactivate(void) { if ( !m_pController ) return; m_pController->DetachObject( VPhysicsGetObject() ); physenv->DestroyMotionController( m_pController ); m_pController = NULL; SetNextThink( TICK_NEVER_THINK ); m_thrustTime = 0; m_active = false; if ( m_pJet ) { ShutdownJet(); } if ( m_gasSound != NULL_STRING ) { StopSound( entindex(), CHAN_ITEM, STRING(m_gasSound) ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPhysicsCannister::Explode( CBaseEntity *pAttacker ) { // don't recurse m_takedamage = 0; Deactivate(); Vector velocity; AngularImpulse angVelocity; IPhysicsObject *pPhysics = VPhysicsGetObject(); pPhysics->GetVelocity( &velocity, &angVelocity ); PropBreakableCreateAll( GetModelIndex(), pPhysics, GetAbsOrigin(), GetAbsAngles(), velocity, angVelocity, 1.0, 20, COLLISION_GROUP_DEBRIS ); ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), pAttacker, m_damage, 0, true ); UTIL_Remove( this ); } //----------------------------------------------------------------------------- // Purpose: Explode when I next hit a damageable entity //----------------------------------------------------------------------------- void CPhysicsCannister::ExplodeTouch( CBaseEntity *pOther ) { if ( !pOther->m_takedamage ) return; Explode( m_hLauncher ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPhysicsCannister::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) { if ( m_bFired && m_active ) { int otherIndex = !index; CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex]; if ( pEvent->deltaCollisionTime < 0.5 && (pHitEntity == this) ) return; // If we hit hard enough. explode if ( pEvent->collisionSpeed > 1000 ) { Explode( m_hLauncher ); return; } } BaseClass::VPhysicsCollision( index, pEvent ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPhysicsCannister::ShutdownJet( void ) { g_EventQueue.AddEvent( m_pJet, "kill", 5, NULL, NULL ); m_pJet->m_bEmit = false; m_pJet->m_Rate = 0; m_pJet = NULL; SetNextThink( TICK_NEVER_THINK ); } //----------------------------------------------------------------------------- // Purpose: The think just shuts the cannister down //----------------------------------------------------------------------------- void CPhysicsCannister::BeginShutdownThink( void ) { Deactivate(); } //----------------------------------------------------------------------------- // Physics Attacker //----------------------------------------------------------------------------- void CPhysicsCannister::SetPhysicsAttacker( CBasePlayer *pEntity, float flTime ) { m_hPhysicsAttacker = pEntity; m_flLastPhysicsInfluenceTime = flTime; } //----------------------------------------------------------------------------- // Purpose: Keep track of physgun influence //----------------------------------------------------------------------------- void CPhysicsCannister::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason ) { SetPhysicsAttacker( pPhysGunUser, gpGlobals->curtime ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPhysicsCannister::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason ) { SetPhysicsAttacker( pPhysGunUser, gpGlobals->curtime ); if ( Reason == LAUNCHED_BY_CANNON ) { CannisterActivate( pPhysGunUser, vec3_origin ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CBasePlayer *CPhysicsCannister::HasPhysicsAttacker( float dt ) { if (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime) { return m_hPhysicsAttacker; } return NULL; } //----------------------------------------------------------------------------- // Purpose: Update the visible representation of the physic system's representation of this object //----------------------------------------------------------------------------- void CPhysicsCannister::VPhysicsUpdate( IPhysicsObject *pPhysics ) { BaseClass::VPhysicsUpdate( pPhysics ); // if this is the first time we have moved, fire our target if ( HasSpawnFlags( SF_CANNISTER_ASLEEP ) ) { if ( !pPhysics->IsAsleep() ) { m_OnAwakened.FireOutput(this, this); RemoveSpawnFlags( SF_CANNISTER_ASLEEP ); } } }