aboutsummaryrefslogtreecommitdiff
path: root/sp/src/game/server/hl2/combine_mine.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /sp/src/game/server/hl2/combine_mine.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'sp/src/game/server/hl2/combine_mine.cpp')
-rw-r--r--sp/src/game/server/hl2/combine_mine.cpp1284
1 files changed, 1284 insertions, 0 deletions
diff --git a/sp/src/game/server/hl2/combine_mine.cpp b/sp/src/game/server/hl2/combine_mine.cpp
new file mode 100644
index 00000000..6d49f94e
--- /dev/null
+++ b/sp/src/game/server/hl2/combine_mine.cpp
@@ -0,0 +1,1284 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "soundenvelope.h"
+#include "Sprite.h"
+#include "entitylist.h"
+#include "ai_basenpc.h"
+#include "soundent.h"
+#include "explode.h"
+#include "physics.h"
+#include "physics_saverestore.h"
+#include "combine_mine.h"
+#include "movevars_shared.h"
+#include "vphysics/constraints.h"
+#include "ai_hint.h"
+
+enum
+{
+ MINE_STATE_DORMANT = 0,
+ MINE_STATE_DEPLOY, // Try to lock down and arm
+ MINE_STATE_CAPTIVE, // Held in the physgun
+ MINE_STATE_ARMED, // Locked down and looking for targets
+ MINE_STATE_TRIGGERED, // No turning back. I'm going to explode when I touch something.
+ MINE_STATE_LAUNCHED, // Similar. Thrown from physgun.
+};
+
+// for the Modification keyfield
+enum
+{
+ MINE_MODIFICATION_NORMAL = 0,
+ MINE_MODIFICATION_CAVERN,
+};
+
+// the citizen modified skins for the mine (inclusive):
+#define MINE_CITIZEN_SKIN_MIN 1
+#define MINE_CITIZEN_SKIN_MAX 2
+
+char *pszMineStateNames[] =
+{
+ "Dormant",
+ "Deploy",
+ "Captive",
+ "Armed",
+ "Triggered",
+ "Launched",
+};
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+// After this many flips, seriously cut the frequency with which you try.
+#define BOUNCEBOMB_MAX_FLIPS 5
+
+// Approximate radius of the bomb's model
+#define BOUNCEBOMB_RADIUS 24
+
+BEGIN_DATADESC( CBounceBomb )
+ DEFINE_THINKFUNC( ExplodeThink ),
+ DEFINE_ENTITYFUNC( ExplodeTouch ),
+ DEFINE_THINKFUNC( SearchThink ),
+ DEFINE_THINKFUNC( BounceThink ),
+ DEFINE_THINKFUNC( SettleThink ),
+ DEFINE_THINKFUNC( CaptiveThink ),
+ DEFINE_THINKFUNC( CavernBounceThink ),
+
+ DEFINE_SOUNDPATCH( m_pWarnSound ),
+
+ DEFINE_KEYFIELD( m_flExplosionDelay, FIELD_FLOAT, "ExplosionDelay" ),
+ DEFINE_KEYFIELD( m_bBounce, FIELD_BOOLEAN, "Bounce" ),
+
+ DEFINE_FIELD( m_bAwake, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_hNearestNPC, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hSprite, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_LastSpriteColor, FIELD_COLOR32 ),
+
+ DEFINE_FIELD( m_flHookPositions, FIELD_FLOAT ),
+ DEFINE_FIELD( m_iHookN, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iHookE, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iHookS, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iAllHooks, FIELD_INTEGER ),
+
+ DEFINE_KEYFIELD( m_bLockSilently, FIELD_BOOLEAN, "LockSilently" ),
+ DEFINE_FIELD( m_bFoeNearest, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flIgnoreWorldTime, FIELD_TIME ),
+ DEFINE_KEYFIELD( m_bDisarmed, FIELD_BOOLEAN, "StartDisarmed" ),
+ DEFINE_KEYFIELD( m_iModification, FIELD_INTEGER, "Modification" ),
+
+ DEFINE_FIELD( m_bPlacedByPlayer, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bHeldByPhysgun, FIELD_BOOLEAN ),
+
+ DEFINE_FIELD( m_iFlipAttempts, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_flTimeGrabbed, FIELD_TIME ),
+ DEFINE_FIELD( m_iMineState, FIELD_INTEGER ),
+
+ // Physics Influence
+ DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ),
+
+ DEFINE_PHYSPTR( m_pConstraint ),
+
+ DEFINE_OUTPUT( m_OnPulledUp, "OnPulledUp" ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Disarm", InputDisarm ),
+
+END_DATADESC()
+
+string_t CBounceBomb::gm_iszFloorTurretClassname;
+string_t CBounceBomb::gm_iszGroundTurretClassname;
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBounceBomb::Precache()
+{
+ PrecacheModel("models/props_combine/combine_mine01.mdl");
+
+ PrecacheScriptSound( "NPC_CombineMine.Hop" );
+ PrecacheScriptSound( "NPC_CombineMine.FlipOver" );
+ PrecacheScriptSound( "NPC_CombineMine.TurnOn" );
+ PrecacheScriptSound( "NPC_CombineMine.TurnOff" );
+ PrecacheScriptSound( "NPC_CombineMine.OpenHooks" );
+ PrecacheScriptSound( "NPC_CombineMine.CloseHooks" );
+
+ PrecacheScriptSound( "NPC_CombineMine.ActiveLoop" );
+
+ PrecacheModel( "sprites/glow01.vmt" );
+
+ gm_iszFloorTurretClassname = AllocPooledString( "npc_turret_floor" );
+ gm_iszGroundTurretClassname = AllocPooledString( "npc_turret_ground" );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBounceBomb::Spawn()
+{
+ Precache();
+
+ Wake( false );
+
+ SetModel("models/props_combine/combine_mine01.mdl");
+
+ SetSolid( SOLID_VPHYSICS );
+
+ m_hSprite.Set( NULL );
+ m_takedamage = DAMAGE_EVENTS_ONLY;
+
+ // Find my feet!
+ m_iHookN = LookupPoseParameter( "blendnorth" );
+ m_iHookE = LookupPoseParameter( "blendeast" );
+ m_iHookS = LookupPoseParameter( "blendsouth" );
+ m_iAllHooks = LookupPoseParameter( "blendstates" );
+ m_flHookPositions = 0;
+
+ SetHealth( 100 );
+
+ m_bBounce = true;
+
+ SetSequence( SelectWeightedSequence( ACT_IDLE ) );
+
+ OpenHooks( true );
+
+ m_bHeldByPhysgun = false;
+
+ m_iFlipAttempts = 0;
+
+ if( !GetParent() )
+ {
+ // Create vphysics now if I'm not being carried.
+ CreateVPhysics();
+ }
+
+ m_flTimeGrabbed = FLT_MAX;
+
+ if( m_bDisarmed )
+ {
+ SetMineState( MINE_STATE_DORMANT );
+ }
+ else
+ {
+ SetMineState( MINE_STATE_DEPLOY );
+ }
+
+ // default to a different skin for cavern turrets (unless explicitly overridden)
+ if ( m_iModification == MINE_MODIFICATION_CAVERN )
+ {
+ // look for this value in the first datamap
+ // loop through the data description list, restoring each data desc block
+ datamap_t *dmap = GetDataDescMap();
+
+ bool bFoundSkin = false;
+ // search through all the readable fields in the data description, looking for a match
+ for ( int i = 0; i < dmap->dataNumFields; ++i )
+ {
+ if ( dmap->dataDesc[i].flags & (FTYPEDESC_OUTPUT | FTYPEDESC_KEY) )
+ {
+ if ( !Q_stricmp(dmap->dataDesc[i].externalName, "Skin") )
+ {
+ bFoundSkin = true;
+ break;
+ }
+ }
+ }
+
+ if (!bFoundSkin)
+ {
+ // select a random skin for the mine. Actually, we'll cycle through the available skins
+ // using a static variable to provide better distribution. The static isn't saved but
+ // really it's only cosmetic.
+ static unsigned int nextSkin = MINE_CITIZEN_SKIN_MIN;
+ m_nSkin = nextSkin;
+ // increment the skin for next time
+ nextSkin = (nextSkin >= MINE_CITIZEN_SKIN_MAX) ? MINE_CITIZEN_SKIN_MIN : nextSkin + 1;
+ }
+
+ // pretend like the player set me down.
+ m_bPlacedByPlayer = true;
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBounceBomb::OnRestore()
+{
+ BaseClass::OnRestore();
+ if ( gpGlobals->eLoadType == MapLoad_Transition && !m_hSprite && m_LastSpriteColor.GetRawColor() != 0 )
+ {
+ UpdateLight( true, m_LastSpriteColor.r(), m_LastSpriteColor.g(), m_LastSpriteColor.b(), m_LastSpriteColor.a() );
+ }
+
+ if( VPhysicsGetObject() )
+ {
+ VPhysicsGetObject()->Wake();
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+int CBounceBomb::DrawDebugTextOverlays(void)
+{
+ int text_offset = BaseClass::DrawDebugTextOverlays();
+ if (m_debugOverlays & OVERLAY_TEXT_BIT)
+ {
+ char tempstr[512];
+ Q_snprintf(tempstr,sizeof(tempstr), pszMineStateNames[m_iMineState] );
+ EntityText(text_offset,tempstr,0);
+ text_offset++;
+ }
+ return text_offset;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBounceBomb::SetMineState( int iState )
+{
+ m_iMineState = iState;
+
+ switch( iState )
+ {
+ case MINE_STATE_DORMANT:
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundChangeVolume( m_pWarnSound, 0.0, 0.1 );
+ UpdateLight( false, 0, 0, 0, 0 );
+ SetThink( NULL );
+ }
+ break;
+
+ case MINE_STATE_CAPTIVE:
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundChangeVolume( m_pWarnSound, 0.0, 0.2 );
+
+ // Unhook
+ unsigned int flags = VPhysicsGetObject()->GetCallbackFlags();
+ VPhysicsGetObject()->SetCallbackFlags( flags | CALLBACK_GLOBAL_TOUCH_STATIC );
+ OpenHooks();
+ physenv->DestroyConstraint( m_pConstraint );
+ m_pConstraint = NULL;
+
+ UpdateLight( true, 0, 0, 255, 190 );
+ SetThink( &CBounceBomb::CaptiveThink );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ SetTouch( NULL );
+ }
+ break;
+
+ case MINE_STATE_DEPLOY:
+ OpenHooks( true );
+ UpdateLight( true, 0, 0, 255, 190 );
+ SetThink( &CBounceBomb::SettleThink );
+ SetTouch( NULL );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ break;
+
+ case MINE_STATE_ARMED:
+ UpdateLight( false, 0, 0, 0, 0 );
+ SetThink( &CBounceBomb::SearchThink );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ break;
+
+ case MINE_STATE_TRIGGERED:
+ {
+ OpenHooks();
+
+ if( m_pConstraint )
+ {
+ physenv->DestroyConstraint( m_pConstraint );
+ m_pConstraint = NULL;
+ }
+
+ // Scare NPC's
+ CSoundEnt::InsertSound( SOUND_DANGER, GetAbsOrigin(), 300, 1.0f, this );
+
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundChangeVolume( m_pWarnSound, 0.0, 0.2 );
+
+ SetTouch( &CBounceBomb::ExplodeTouch );
+ unsigned int flags = VPhysicsGetObject()->GetCallbackFlags();
+ VPhysicsGetObject()->SetCallbackFlags( flags | CALLBACK_GLOBAL_TOUCH_STATIC );
+
+ Vector vecNudge;
+
+ vecNudge.x = random->RandomFloat( -1, 1 );
+ vecNudge.y = random->RandomFloat( -1, 1 );
+ vecNudge.z = 1.5;
+ vecNudge *= 350;
+
+ VPhysicsGetObject()->Wake();
+ VPhysicsGetObject()->ApplyForceCenter( vecNudge );
+
+ float x, y;
+ x = 10 + random->RandomFloat( 0, 20 );
+ y = 10 + random->RandomFloat( 0, 20 );
+
+ VPhysicsGetObject()->ApplyTorqueCenter( AngularImpulse( x, y, 0 ) );
+
+ // Since we just nudged the mine, ignore collisions with the world until
+ // the mine is in the air. We only want to explode if the player tries to
+ // run over the mine before it jumps up.
+ m_flIgnoreWorldTime = gpGlobals->curtime + 1.0;
+ UpdateLight( true, 255, 0, 0, 190 );
+
+ // use the correct bounce behavior
+ if (m_iModification == MINE_MODIFICATION_CAVERN)
+ {
+ SetThink ( &CBounceBomb::CavernBounceThink );
+ SetNextThink( gpGlobals->curtime + 0.15 );
+ }
+ else
+ {
+ SetThink( &CBounceBomb::BounceThink );
+ SetNextThink( gpGlobals->curtime + 0.5 );
+ }
+ }
+ break;
+
+ case MINE_STATE_LAUNCHED:
+ {
+ UpdateLight( true, 255, 0, 0, 190 );
+ SetThink( NULL );
+ SetNextThink( gpGlobals->curtime + 0.5 );
+
+ SetTouch( &CBounceBomb::ExplodeTouch );
+ unsigned int flags = VPhysicsGetObject()->GetCallbackFlags();
+ VPhysicsGetObject()->SetCallbackFlags( flags | CALLBACK_GLOBAL_TOUCH_STATIC );
+ }
+ break;
+
+ default:
+ DevMsg("**Unknown Mine State: %d\n", iState );
+ break;
+ }
+}
+
+//---------------------------------------------------------
+// Bouncbomb flips to try to right itself, try to get off
+// of and object that it's not allowed to clamp to, or
+// to get away from a hint node that inhibits placement
+// of mines.
+//---------------------------------------------------------
+void CBounceBomb::Flip( const Vector &vecForce, const AngularImpulse &torque )
+{
+ if( m_iFlipAttempts > BOUNCEBOMB_MAX_FLIPS )
+ {
+ // Not allowed to try anymore.
+ SetThink(NULL);
+ return;
+ }
+
+ EmitSound( "NPC_CombineMine.FlipOver" );
+ VPhysicsGetObject()->ApplyForceCenter( vecForce );
+ VPhysicsGetObject()->ApplyTorqueCenter( torque );
+ m_iFlipAttempts++;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+#define MINE_MIN_PROXIMITY_SQR 676 // 27 inches
+bool CBounceBomb::IsValidLocation()
+{
+ CBaseEntity *pAvoidObject = NULL;
+ float flAvoidForce = 0.0f;
+ CAI_Hint *pHint;
+ CHintCriteria criteria;
+ criteria.SetHintType( HINT_WORLD_INHIBIT_COMBINE_MINES );
+ criteria.SetFlag( bits_HINT_NODE_NEAREST );
+ criteria.AddIncludePosition( GetAbsOrigin(), 12.0f * 15.0f );
+ pHint = CAI_HintManager::FindHint( GetAbsOrigin(), criteria );
+
+ if( pHint )
+ {
+ pAvoidObject = pHint;
+ flAvoidForce = 120.0f;
+ }
+ else
+ {
+ // Look for other mines that are too close to me.
+ CBaseEntity *pEntity = gEntList.FirstEnt();
+ Vector vecMyPosition = GetAbsOrigin();
+ while( pEntity )
+ {
+ if( pEntity->m_iClassname == m_iClassname && pEntity != this )
+ {
+ // Don't lock down if I'm near a mine that's already locked down.
+ if( vecMyPosition.DistToSqr(pEntity->GetAbsOrigin()) < MINE_MIN_PROXIMITY_SQR )
+ {
+ pAvoidObject = pEntity;
+ flAvoidForce = 60.0f;
+ break;
+ }
+ }
+
+ pEntity = gEntList.NextEnt( pEntity );
+ }
+ }
+
+ if( pAvoidObject )
+ {
+ // Build a force vector to push us away from the inhibitor.
+ // Start by pushing upwards.
+ Vector vecForce = Vector( 0, 0, VPhysicsGetObject()->GetMass() * 200.0f );
+
+ // Now add some force in the direction that takes us away from the inhibitor.
+ Vector vecDir = GetAbsOrigin() - pAvoidObject->GetAbsOrigin();
+ vecDir.z = 0.0f;
+ VectorNormalize( vecDir );
+ vecForce += vecDir * VPhysicsGetObject()->GetMass() * flAvoidForce;
+
+ Flip( vecForce, AngularImpulse( 100, 0, 0 ) );
+
+ // Tell the code that asked that this position isn't valid.
+ return false;
+ }
+
+ return true;
+}
+
+//---------------------------------------------------------
+// Release the spikes
+//---------------------------------------------------------
+void CBounceBomb::BounceThink()
+{
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ StudioFrameAdvance();
+
+ IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
+
+ if ( pPhysicsObject != NULL )
+ {
+ const float MINE_MAX_JUMP_HEIGHT = 200;
+
+ // Figure out how much headroom the mine has, and hop to within a few inches of that.
+ trace_t tr;
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, MINE_MAX_JUMP_HEIGHT ), MASK_SHOT, this, COLLISION_GROUP_INTERACTIVE, &tr );
+
+ float height;
+
+ if( tr.m_pEnt && tr.m_pEnt->VPhysicsGetObject() )
+ {
+ // Physics object resting on me. Jump as hard as allowed to try to knock it away.
+ height = MINE_MAX_JUMP_HEIGHT;
+ }
+ else
+ {
+ height = tr.endpos.z - GetAbsOrigin().z;
+ height -= BOUNCEBOMB_RADIUS;
+ if ( height < 0.1 )
+ height = 0.1;
+ }
+
+ float time = sqrt( height / (0.5 * GetCurrentGravity()) );
+ float velocity = GetCurrentGravity() * time;
+
+ // or you can just AddVelocity to the object instead of ApplyForce
+ float force = velocity * pPhysicsObject->GetMass();
+
+ Vector up;
+
+ GetVectors( NULL, NULL, &up );
+ pPhysicsObject->Wake();
+ pPhysicsObject->ApplyForceCenter( up * force );
+
+ pPhysicsObject->ApplyTorqueCenter( AngularImpulse( random->RandomFloat( 5, 25 ), random->RandomFloat( 5, 25 ), 0 ) );
+
+
+ if( m_hNearestNPC )
+ {
+ Vector vecPredict = m_hNearestNPC->GetSmoothedVelocity();
+
+ pPhysicsObject->ApplyForceCenter( vecPredict * 10 );
+ }
+
+ EmitSound( "NPC_CombineMine.Hop" );
+ SetThink( NULL );
+ }
+}
+
+
+//---------------------------------------------------------
+// A different bounce behavior for the citizen-modified mine. Detonates at the top of its apex,
+// and does not attempt to track enemies.
+//---------------------------------------------------------
+void CBounceBomb::CavernBounceThink()
+{
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ StudioFrameAdvance();
+
+ IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
+
+ if ( pPhysicsObject != NULL )
+ {
+ const float MINE_MAX_JUMP_HEIGHT = 78;
+
+ // Figure out how much headroom the mine has, and hop to within a few inches of that.
+ trace_t tr;
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, MINE_MAX_JUMP_HEIGHT ), MASK_SHOT, this, COLLISION_GROUP_INTERACTIVE, &tr );
+
+ float height;
+
+ if( tr.m_pEnt && tr.m_pEnt->VPhysicsGetObject() )
+ {
+ // Physics object resting on me. Jump as hard as allowed to try to knock it away.
+ height = MINE_MAX_JUMP_HEIGHT;
+ }
+ else
+ {
+ height = tr.endpos.z - GetAbsOrigin().z;
+ height -= BOUNCEBOMB_RADIUS;
+ if ( height < 0.1 )
+ height = 0.1;
+ }
+
+ float time = sqrt( height / (0.5 * GetCurrentGravity()) );
+ float velocity = GetCurrentGravity() * time;
+
+ // or you can just AddVelocity to the object instead of ApplyForce
+ float force = velocity * pPhysicsObject->GetMass();
+
+ Vector up;
+
+ GetVectors( NULL, NULL, &up );
+
+ pPhysicsObject->Wake();
+ pPhysicsObject->ApplyForceCenter( up * force );
+ if( m_hNearestNPC )
+ {
+ Vector vecPredict = m_hNearestNPC->GetSmoothedVelocity();
+
+ pPhysicsObject->ApplyForceCenter( vecPredict * (pPhysicsObject->GetMass() * 0.65f) );
+ }
+
+ pPhysicsObject->ApplyTorqueCenter( AngularImpulse( random->RandomFloat( 15, 40 ), random->RandomFloat( 15, 40 ), random->RandomFloat( 30, 60 ) ) );
+
+ EmitSound( "NPC_CombineMine.Hop" );
+
+ SetThink( &CBounceBomb::ExplodeThink );
+ SetNextThink( gpGlobals->curtime + 0.33f );
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBounceBomb::CaptiveThink()
+{
+ SetNextThink( gpGlobals->curtime + 0.05 );
+ StudioFrameAdvance();
+
+ float phase = fabs( sin( gpGlobals->curtime * 4.0f ) );
+ phase *= BOUNCEBOMB_HOOK_RANGE;
+ SetPoseParameter( m_iAllHooks, phase );
+ return;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBounceBomb::SettleThink()
+{
+ SetNextThink( gpGlobals->curtime + 0.05 );
+ StudioFrameAdvance();
+
+ if( GetParent() )
+ {
+ // A scanner or something is carrying me. Just keep checking back.
+ return;
+ }
+
+ // Not being carried.
+ if( !VPhysicsGetObject() )
+ {
+ // Probably was just dropped. Get physics going.
+ CreateVPhysics();
+
+ if( !VPhysicsGetObject() )
+ {
+ Msg("**** Can't create vphysics for combine_mine!\n" );
+ UTIL_Remove( this );
+ return;
+ }
+
+ VPhysicsGetObject()->Wake();
+ return;
+ }
+
+ if( !m_bDisarmed )
+ {
+ if( VPhysicsGetObject()->IsAsleep() && !(VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD) )
+ {
+ // If i'm not resting on the world, jump randomly.
+ trace_t tr;
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 1024 ), MASK_SHOT|CONTENTS_GRATE, this, COLLISION_GROUP_NONE, &tr );
+
+ bool bHop = false;
+ if( tr.m_pEnt )
+ {
+ IPhysicsObject *pPhysics = tr.m_pEnt->VPhysicsGetObject();
+
+ if( pPhysics && pPhysics->GetMass() <= 1000 )
+ {
+ // Light physics objects can be moved out from under the mine.
+ bHop = true;
+ }
+ else if( tr.m_pEnt->m_takedamage != DAMAGE_NO )
+ {
+ // Things that can be harmed can likely be broken.
+ bHop = true;
+ }
+
+ if( bHop )
+ {
+ Vector vecForce;
+ vecForce.x = random->RandomFloat( -1000, 1000 );
+ vecForce.y = random->RandomFloat( -1000, 1000 );
+ vecForce.z = 2500;
+
+ AngularImpulse torque( 160, 0, 160 );
+
+ Flip( vecForce, torque );
+ return;
+ }
+
+ // Check for upside-down
+ Vector vecUp;
+ GetVectors( NULL, NULL, &vecUp );
+ if( vecUp.z <= 0.8 )
+ {
+ // Landed upside down. Right self
+ Vector vecForce( 0, 0, 2500 );
+ Flip( vecForce, AngularImpulse( 60, 0, 0 ) );
+ return;
+ }
+ }
+
+ // Check to make sure I'm not in a forbidden location
+ if( !IsValidLocation() )
+ {
+ return;
+ }
+
+ // Lock to what I'm resting on
+ constraint_ballsocketparams_t ballsocket;
+ ballsocket.Defaults();
+ ballsocket.constraint.Defaults();
+ ballsocket.constraint.forceLimit = lbs2kg(1000);
+ ballsocket.constraint.torqueLimit = lbs2kg(1000);
+ ballsocket.InitWithCurrentObjectState( g_PhysWorldObject, VPhysicsGetObject(), GetAbsOrigin() );
+ m_pConstraint = physenv->CreateBallsocketConstraint( g_PhysWorldObject, VPhysicsGetObject(), NULL, ballsocket );
+ CloseHooks();
+
+ SetMineState( MINE_STATE_ARMED );
+ }
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+int CBounceBomb::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ if( m_pConstraint || !VPhysicsGetObject())
+ {
+ return false;
+ }
+
+ VPhysicsTakeDamage( info );
+ return true;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBounceBomb::UpdateLight( bool bTurnOn, unsigned int r, unsigned int g, unsigned int b, unsigned int a )
+{
+ if( bTurnOn )
+ {
+ Assert( a > 0 );
+
+ // Throw the old sprite away
+ if( m_hSprite )
+ {
+ UTIL_Remove( m_hSprite );
+ m_hSprite.Set( NULL );
+ }
+
+ if( !m_hSprite.Get() )
+ {
+ Vector up;
+ GetVectors( NULL, NULL, &up );
+
+ // Light isn't on.
+ m_hSprite = CSprite::SpriteCreate( "sprites/glow01.vmt", GetAbsOrigin() + up * 10.0f, false );
+ CSprite *pSprite = (CSprite *)m_hSprite.Get();
+
+ if( m_hSprite )
+ {
+ pSprite->SetParent( this );
+ pSprite->SetTransparency( kRenderTransAdd, r, g, b, a, kRenderFxNone );
+ pSprite->SetScale( 0.35, 0.0 );
+ }
+ }
+ else
+ {
+ // Update color
+ CSprite *pSprite = (CSprite *)m_hSprite.Get();
+ pSprite->SetTransparency( kRenderTransAdd, r, g, b, a, kRenderFxNone );
+ }
+ }
+
+ if( !bTurnOn )
+ {
+ if( m_hSprite )
+ {
+ UTIL_Remove( m_hSprite );
+ m_hSprite.Set( NULL );
+ }
+ }
+
+ if ( !m_hSprite )
+ {
+ m_LastSpriteColor.SetRawColor( 0 );
+ }
+ else
+ {
+ m_LastSpriteColor.SetColor( r, g, b, a );
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBounceBomb::Wake( bool bAwake )
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ CReliableBroadcastRecipientFilter filter;
+
+ if( !m_pWarnSound )
+ {
+ m_pWarnSound = controller.SoundCreate( filter, entindex(), "NPC_CombineMine.ActiveLoop" );
+ controller.Play( m_pWarnSound, 1.0, PITCH_NORM );
+ }
+
+ if( bAwake )
+ {
+ // Turning on
+ if( m_bFoeNearest )
+ {
+ EmitSound( "NPC_CombineMine.TurnOn" );
+ controller.SoundChangeVolume( m_pWarnSound, 1.0, 0.1 );
+ }
+
+ unsigned char r, g, b;
+ r = g = b = 0;
+
+ if( m_bFoeNearest )
+ {
+ r = 255;
+ }
+ else
+ {
+ g = 255;
+ }
+
+ UpdateLight( true, r, g, b, 190 );
+ }
+ else
+ {
+ // Turning off
+ if( m_bFoeNearest )
+ {
+ EmitSound( "NPC_CombineMine.TurnOff" );
+ }
+
+ SetNearestNPC( NULL );
+ controller.SoundChangeVolume( m_pWarnSound, 0.0, 0.1 );
+ UpdateLight( false, 0, 0, 0, 0 );
+ }
+
+ m_bAwake = bAwake;
+}
+
+//---------------------------------------------------------
+// Returns distance to the nearest BaseCombatCharacter.
+//---------------------------------------------------------
+float CBounceBomb::FindNearestNPC()
+{
+ float flNearest = (BOUNCEBOMB_WARN_RADIUS * BOUNCEBOMB_WARN_RADIUS) + 1.0;
+
+ // Assume this search won't find anyone.
+ SetNearestNPC( NULL );
+
+ CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
+ int nAIs = g_AI_Manager.NumAIs();
+
+ for ( int i = 0; i < nAIs; i++ )
+ {
+ CAI_BaseNPC *pNPC = ppAIs[ i ];
+
+ if( pNPC->IsAlive() )
+ {
+ // ignore hidden objects
+ if ( pNPC->IsEffectActive( EF_NODRAW ) )
+ continue;
+
+ // Don't bother with NPC's that are below me.
+ if( pNPC->EyePosition().z < GetAbsOrigin().z )
+ continue;
+
+ // Disregard things that want to be disregarded
+ if( pNPC->Classify() == CLASS_NONE )
+ continue;
+
+ // Disregard bullseyes
+ if( pNPC->Classify() == CLASS_BULLSEYE )
+ continue;
+
+ // Disregard turrets
+ if( pNPC->m_iClassname == gm_iszFloorTurretClassname || pNPC->m_iClassname == gm_iszGroundTurretClassname )
+ continue;
+
+
+ float flDist = (GetAbsOrigin() - pNPC->GetAbsOrigin()).LengthSqr();
+
+ if( flDist < flNearest )
+ {
+ // Now do a visibility test.
+ if( FVisible( pNPC, MASK_SOLID_BRUSHONLY ) )
+ {
+ flNearest = flDist;
+ SetNearestNPC( pNPC );
+ }
+ }
+ }
+ }
+
+ // finally, check the player.
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+
+ if( pPlayer && !(pPlayer->GetFlags() & FL_NOTARGET) )
+ {
+ float flDist = (pPlayer->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
+
+ if( flDist < flNearest && FVisible( pPlayer, MASK_SOLID_BRUSHONLY ) )
+ {
+ flNearest = flDist;
+ SetNearestNPC( pPlayer );
+ }
+ }
+
+ if( m_hNearestNPC.Get() )
+ {
+ // If sprite is active, update its color to reflect who is nearest.
+ if( IsFriend( m_hNearestNPC ) )
+ {
+ if( m_bFoeNearest )
+ {
+ // Changing state to where a friend is nearest.
+
+ if( IsFriend( m_hNearestNPC ) )
+ {
+ // Friend
+ UpdateLight( true, 0, 255, 0, 190 );
+ m_bFoeNearest = false;
+ }
+ }
+ }
+ else // it's a foe
+ {
+ if( !m_bFoeNearest )
+ {
+ // Changing state to where a foe is nearest.
+ UpdateLight( true, 255, 0, 0, 190 );
+ m_bFoeNearest = true;
+ }
+ }
+ }
+
+ return sqrt( flNearest );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+bool CBounceBomb::IsFriend( CBaseEntity *pEntity )
+{
+ int classify = pEntity->Classify();
+ bool bIsCombine = false;
+
+ // Unconditional enemies to combine and Player.
+ if( classify == CLASS_ZOMBIE || classify == CLASS_HEADCRAB || classify == CLASS_ANTLION )
+ {
+ return false;
+ }
+
+ if( classify == CLASS_METROPOLICE ||
+ classify == CLASS_COMBINE ||
+ classify == CLASS_MILITARY ||
+ classify == CLASS_COMBINE_HUNTER ||
+ classify == CLASS_SCANNER )
+ {
+ bIsCombine = true;
+ }
+
+ if( m_bPlacedByPlayer )
+ {
+ return !bIsCombine;
+ }
+ else
+ {
+ return bIsCombine;
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBounceBomb::SearchThink()
+{
+ if( !UTIL_FindClientInPVS(edict()) )
+ {
+ // Sleep!
+ SetNextThink( gpGlobals->curtime + 0.5 );
+ return;
+ }
+
+ if( (CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI) )
+ {
+ if( IsAwake() )
+ {
+ Wake(false);
+ }
+
+ SetNextThink( gpGlobals->curtime + 0.5 );
+ return;
+ }
+
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ StudioFrameAdvance();
+
+ if( m_pConstraint && gpGlobals->curtime - m_flTimeGrabbed >= 1.0f )
+ {
+ m_OnPulledUp.FireOutput( this, this );
+ SetMineState( MINE_STATE_CAPTIVE );
+ return;
+ }
+
+ float flNearestNPCDist = FindNearestNPC();
+
+ if( flNearestNPCDist <= BOUNCEBOMB_WARN_RADIUS )
+ {
+ if( !IsAwake() )
+ {
+ Wake( true );
+ }
+ }
+ else
+ {
+ if( IsAwake() )
+ {
+ Wake( false );
+ }
+
+ return;
+ }
+
+ if( flNearestNPCDist <= BOUNCEBOMB_DETONATE_RADIUS && !IsFriend( m_hNearestNPC ) )
+ {
+ if( m_bBounce )
+ {
+ SetMineState( MINE_STATE_TRIGGERED );
+ }
+ else
+ {
+ // Don't pop up in the air, just explode if the NPC gets closer than explode radius.
+ SetThink( &CBounceBomb::ExplodeThink );
+ SetNextThink( gpGlobals->curtime + m_flExplosionDelay );
+ }
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBounceBomb::ExplodeTouch( CBaseEntity *pOther )
+{
+ // Don't touch anything if held by physgun.
+ if( m_bHeldByPhysgun )
+ return;
+
+ // Don't touch triggers.
+ if( pOther->IsSolidFlagSet(FSOLID_TRIGGER) )
+ return;
+
+ // Don't touch gibs and other debris
+ if( pOther->GetCollisionGroup() == COLLISION_GROUP_DEBRIS )
+ {
+ if( hl2_episodic.GetBool() )
+ {
+ Vector vecVelocity;
+
+ VPhysicsGetObject()->GetVelocity( &vecVelocity, NULL );
+
+ if( vecVelocity == vec3_origin )
+ {
+ ExplodeThink();
+ }
+ }
+
+ return;
+ }
+
+ // Don't detonate against the world if not allowed. Actually, don't
+ // detonate against anything that's probably not an NPC (such as physics props)
+ if( m_flIgnoreWorldTime > gpGlobals->curtime && !pOther->MyCombatCharacterPointer() )
+ {
+ return;
+ }
+
+ ExplodeThink();
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBounceBomb::ExplodeThink()
+{
+ SetSolid( SOLID_NONE );
+
+ // Don't catch self in own explosion!
+ m_takedamage = DAMAGE_NO;
+
+ if( m_hSprite )
+ {
+ UpdateLight( false, 0, 0, 0, 0 );
+ }
+
+ if( m_pWarnSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundDestroy( m_pWarnSound );
+ }
+
+
+ CBaseEntity *pThrower = HasPhysicsAttacker( 0.5 );
+
+ if (m_iModification == MINE_MODIFICATION_CAVERN)
+ {
+ ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), (pThrower) ? pThrower : this, BOUNCEBOMB_EXPLODE_DAMAGE, BOUNCEBOMB_EXPLODE_RADIUS, true,
+ NULL, CLASS_PLAYER_ALLY );
+ }
+ else
+ {
+ ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), (pThrower) ? pThrower : this, BOUNCEBOMB_EXPLODE_DAMAGE, BOUNCEBOMB_EXPLODE_RADIUS, true);
+ }
+ UTIL_Remove( this );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBounceBomb::OpenHooks( bool bSilent )
+{
+ if( !bSilent )
+ {
+ EmitSound( "NPC_CombineMine.OpenHooks" );
+ }
+
+ if( VPhysicsGetObject() )
+ {
+ // It's possible to not have a valid physics object here, since this function doubles as an initialization function.
+ PhysClearGameFlags( VPhysicsGetObject(), FVPHYSICS_CONSTRAINT_STATIC );
+
+ VPhysicsGetObject()->EnableMotion( true );
+ }
+
+ SetPoseParameter( m_iAllHooks, BOUNCEBOMB_HOOK_RANGE );
+
+#ifdef _XBOX
+ RemoveEffects( EF_NOSHADOW );
+#endif
+
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBounceBomb::CloseHooks()
+{
+ if( !m_bLockSilently )
+ {
+ EmitSound( "NPC_CombineMine.CloseHooks" );
+ }
+
+ if( VPhysicsGetObject() )
+ {
+ // It's possible to not have a valid physics object here, since this function doubles as an initialization function.
+ PhysSetGameFlags( VPhysicsGetObject(), FVPHYSICS_CONSTRAINT_STATIC );
+ }
+
+ // Only lock silently the first time we call this.
+ m_bLockSilently = false;
+
+ SetPoseParameter( m_iAllHooks, 0 );
+
+ VPhysicsGetObject()->EnableMotion( false );
+
+ // Once I lock down, forget how many tries it took.
+ m_iFlipAttempts = 0;
+
+#ifdef _XBOX
+ AddEffects( EF_NOSHADOW );
+#endif
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBounceBomb::InputDisarm( inputdata_t &inputdata )
+{
+ // Only affect a mine that's armed and not placed by player.
+ if( !m_bPlacedByPlayer && m_iMineState == MINE_STATE_ARMED )
+ {
+ if( m_pConstraint )
+ {
+ physenv->DestroyConstraint( m_pConstraint );
+ m_pConstraint = NULL;
+ }
+
+ m_bDisarmed = true;
+ OpenHooks(false);
+
+ SetMineState(MINE_STATE_DORMANT);
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBounceBomb::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
+{
+ m_hPhysicsAttacker = pPhysGunUser;
+ m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
+
+ m_flTimeGrabbed = FLT_MAX;
+
+ m_bHeldByPhysgun = false;
+
+ if( m_iMineState == MINE_STATE_ARMED )
+ {
+ // Put the mine back to searching.
+ Wake( false );
+ return;
+ }
+
+ if( Reason == DROPPED_BY_CANNON )
+ {
+ // Set to lock down to ground again.
+ m_bPlacedByPlayer = true;
+ OpenHooks( true );
+ SetMineState( MINE_STATE_DEPLOY );
+ }
+ else if ( Reason == LAUNCHED_BY_CANNON )
+ {
+ SetMineState( MINE_STATE_LAUNCHED );
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+CBasePlayer *CBounceBomb::HasPhysicsAttacker( float dt )
+{
+ if (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime)
+ {
+ return m_hPhysicsAttacker;
+ }
+ return NULL;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBounceBomb::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
+{
+ m_hPhysicsAttacker = pPhysGunUser;
+ m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
+
+ m_iFlipAttempts = 0;
+
+ if( reason != PUNTED_BY_CANNON )
+ {
+ if( m_iMineState == MINE_STATE_ARMED )
+ {
+ // Yanking on a mine that is locked down, trying to rip it loose.
+ UpdateLight( true, 255, 255, 0, 190 );
+ m_flTimeGrabbed = gpGlobals->curtime;
+ m_bHeldByPhysgun = true;
+
+ VPhysicsGetObject()->EnableMotion( true );
+
+ // Try to scatter NPCs without panicking them. Make a move away sound up around their
+ // ear level.
+ CSoundEnt::InsertSound( SOUND_MOVE_AWAY, GetAbsOrigin() + Vector( 0, 0, 60), 32.0f, 0.2f );
+ return;
+ }
+ else
+ {
+ // Picked up a mine that was not locked down.
+ m_bHeldByPhysgun = true;
+
+ if( m_iMineState == MINE_STATE_TRIGGERED )
+ {
+ // This mine's already set to blow. Player can't place it.
+ return;
+ }
+ else
+ {
+ m_bDisarmed = false;
+ SetMineState( MINE_STATE_DEPLOY );
+ }
+ }
+ }
+ else
+ {
+ m_bHeldByPhysgun = false;
+ }
+
+ if( reason == PUNTED_BY_CANNON )
+ {
+ if( m_iMineState == MINE_STATE_TRIGGERED || m_iMineState == MINE_STATE_ARMED )
+ {
+ // Already set to blow
+ return;
+ }
+
+ m_bDisarmed = false;
+ m_bPlacedByPlayer = true;
+ SetTouch( NULL );
+ SetThink( &CBounceBomb::SettleThink );
+ SetNextThink( gpGlobals->curtime + 0.1);
+
+ // Since being punted causes the mine to flip, sometimes it 'catches an edge'
+ // and ends up touching the ground from whence it came, exploding instantly.
+ // This little stunt prevents that by ignoring world collisions for a very short time.
+ m_flIgnoreWorldTime = gpGlobals->curtime + 0.1;
+ }
+}
+
+
+LINK_ENTITY_TO_CLASS( bounce_bomb, CBounceBomb );
+LINK_ENTITY_TO_CLASS( combine_bouncemine, CBounceBomb );
+LINK_ENTITY_TO_CLASS( combine_mine, CBounceBomb );
+
+/*
+*/