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/combine_mine.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/combine_mine.cpp')
| -rw-r--r-- | game/server/hl2/combine_mine.cpp | 1284 |
1 files changed, 1284 insertions, 0 deletions
diff --git a/game/server/hl2/combine_mine.cpp b/game/server/hl2/combine_mine.cpp new file mode 100644 index 0000000..4eb7283 --- /dev/null +++ b/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), "%s", 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 ); + +/* +*/ |