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/episodic/grenade_hopwire.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/server/episodic/grenade_hopwire.cpp')
| -rw-r--r-- | game/server/episodic/grenade_hopwire.cpp | 581 |
1 files changed, 581 insertions, 0 deletions
diff --git a/game/server/episodic/grenade_hopwire.cpp b/game/server/episodic/grenade_hopwire.cpp new file mode 100644 index 0000000..7010c4b --- /dev/null +++ b/game/server/episodic/grenade_hopwire.cpp @@ -0,0 +1,581 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Gravity well device +// +//=====================================================================================// + +#include "cbase.h" +#include "grenade_hopwire.h" +#include "rope.h" +#include "rope_shared.h" +#include "beam_shared.h" +#include "physics.h" +#include "physics_saverestore.h" +#include "explode.h" +#include "physics_prop_ragdoll.h" +#include "movevars_shared.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +ConVar hopwire_vortex( "hopwire_vortex", "0" ); +ConVar hopwire_trap( "hopwire_trap", "1" ); +ConVar hopwire_strider_kill_dist_h( "hopwire_strider_kill_dist_h", "300" ); +ConVar hopwire_strider_kill_dist_v( "hopwire_strider_kill_dist_v", "256" ); +ConVar hopwire_strider_hits( "hopwire_strider_hits", "1" ); +ConVar hopwire_hopheight( "hopwire_hopheight", "400" ); + +ConVar g_debug_hopwire( "g_debug_hopwire", "0" ); + +#define DENSE_BALL_MODEL "models/props_junk/metal_paintcan001b.mdl" + +#define MAX_HOP_HEIGHT (hopwire_hopheight.GetFloat()) // Maximum amount the grenade will "hop" upwards when detonated + +class CGravityVortexController : public CBaseEntity +{ + DECLARE_CLASS( CGravityVortexController, CBaseEntity ); + DECLARE_DATADESC(); + +public: + + CGravityVortexController( void ) : m_flEndTime( 0.0f ), m_flRadius( 256 ), m_flStrength( 256 ), m_flMass( 0.0f ) {} + float GetConsumedMass( void ) const; + + static CGravityVortexController *Create( const Vector &origin, float radius, float strength, float duration ); + +private: + + void ConsumeEntity( CBaseEntity *pEnt ); + void PullPlayersInRange( void ); + bool KillNPCInRange( CBaseEntity *pVictim, IPhysicsObject **pPhysObj ); + void CreateDenseBall( void ); + void PullThink( void ); + void StartPull( const Vector &origin, float radius, float strength, float duration ); + + float m_flMass; // Mass consumed by the vortex + float m_flEndTime; // Time when the vortex will stop functioning + float m_flRadius; // Area of effect for the vortex + float m_flStrength; // Pulling strength of the vortex +}; + +//----------------------------------------------------------------------------- +// Purpose: Returns the amount of mass consumed by the vortex +//----------------------------------------------------------------------------- +float CGravityVortexController::GetConsumedMass( void ) const +{ + return m_flMass; +} + +//----------------------------------------------------------------------------- +// Purpose: Adds the entity's mass to the aggregate mass consumed +//----------------------------------------------------------------------------- +void CGravityVortexController::ConsumeEntity( CBaseEntity *pEnt ) +{ + // Get our base physics object + IPhysicsObject *pPhysObject = pEnt->VPhysicsGetObject(); + if ( pPhysObject == NULL ) + return; + + // Ragdolls need to report the sum of all their parts + CRagdollProp *pRagdoll = dynamic_cast< CRagdollProp* >( pEnt ); + if ( pRagdoll != NULL ) + { + // Find the aggregate mass of the whole ragdoll + ragdoll_t *pRagdollPhys = pRagdoll->GetRagdoll(); + for ( int j = 0; j < pRagdollPhys->listCount; ++j ) + { + m_flMass += pRagdollPhys->list[j].pObject->GetMass(); + } + } + else + { + // Otherwise we just take the normal mass + m_flMass += pPhysObject->GetMass(); + } + + // Destroy the entity + UTIL_Remove( pEnt ); +} + +//----------------------------------------------------------------------------- +// Purpose: Causes players within the radius to be sucked in +//----------------------------------------------------------------------------- +void CGravityVortexController::PullPlayersInRange( void ) +{ + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + + Vector vecForce = GetAbsOrigin() - pPlayer->WorldSpaceCenter(); + float dist = VectorNormalize( vecForce ); + + // FIXME: Need a more deterministic method here + if ( dist < 128.0f ) + { + // Kill the player (with falling death sound and effects) + CTakeDamageInfo deathInfo( this, this, GetAbsOrigin(), GetAbsOrigin(), 200, DMG_FALL ); + pPlayer->TakeDamage( deathInfo ); + + if ( pPlayer->IsAlive() == false ) + { + color32 black = { 0, 0, 0, 255 }; + UTIL_ScreenFade( pPlayer, black, 0.1f, 0.0f, (FFADE_OUT|FFADE_STAYOUT) ); + return; + } + } + + // Must be within the radius + if ( dist > m_flRadius ) + return; + + float mass = pPlayer->VPhysicsGetObject()->GetMass(); + float playerForce = m_flStrength * 0.05f; + + // Find the pull force + // NOTE: We might want to make this non-linear to give more of a "grace distance" + vecForce *= ( 1.0f - ( dist / m_flRadius ) ) * playerForce * mass; + vecForce[2] *= 0.025f; + + pPlayer->SetBaseVelocity( vecForce ); + pPlayer->AddFlag( FL_BASEVELOCITY ); + + // Make sure the player moves + if ( vecForce.z > 0 && ( pPlayer->GetFlags() & FL_ONGROUND) ) + { + pPlayer->SetGroundEntity( NULL ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Attempts to kill an NPC if it's within range and other criteria +// Input : *pVictim - NPC to assess +// **pPhysObj - pointer to the ragdoll created if the NPC is killed +// Output : bool - whether or not the NPC was killed and the returned pointer is valid +//----------------------------------------------------------------------------- +bool CGravityVortexController::KillNPCInRange( CBaseEntity *pVictim, IPhysicsObject **pPhysObj ) +{ + CBaseCombatCharacter *pBCC = pVictim->MyCombatCharacterPointer(); + + // See if we can ragdoll + if ( pBCC != NULL && pBCC->CanBecomeRagdoll() ) + { + // Don't bother with striders + if ( FClassnameIs( pBCC, "npc_strider" ) ) + return false; + + // TODO: Make this an interaction between the NPC and the vortex + + // Become ragdoll + CTakeDamageInfo info( this, this, 1.0f, DMG_GENERIC ); + CBaseEntity *pRagdoll = CreateServerRagdoll( pBCC, 0, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, true ); + pRagdoll->SetCollisionBounds( pVictim->CollisionProp()->OBBMins(), pVictim->CollisionProp()->OBBMaxs() ); + + // Necessary to cause it to do the appropriate death cleanup + CTakeDamageInfo ragdollInfo( this, this, 10000.0, DMG_GENERIC | DMG_REMOVENORAGDOLL ); + pVictim->TakeDamage( ragdollInfo ); + + // Return the pointer to the ragdoll + *pPhysObj = pRagdoll->VPhysicsGetObject(); + return true; + } + + // Wasn't able to ragdoll this target + *pPhysObj = NULL; + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Creates a dense ball with a mass equal to the aggregate mass consumed by the vortex +//----------------------------------------------------------------------------- +void CGravityVortexController::CreateDenseBall( void ) +{ + CBaseEntity *pBall = CreateEntityByName( "prop_physics" ); + + pBall->SetModel( DENSE_BALL_MODEL ); + pBall->SetAbsOrigin( GetAbsOrigin() ); + pBall->Spawn(); + + IPhysicsObject *pObj = pBall->VPhysicsGetObject(); + if ( pObj != NULL ) + { + pObj->SetMass( GetConsumedMass() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Pulls physical objects towards the vortex center, killing them if they come too near +//----------------------------------------------------------------------------- +void CGravityVortexController::PullThink( void ) +{ + // Pull any players close enough to us + PullPlayersInRange(); + + Vector mins, maxs; + mins = GetAbsOrigin() - Vector( m_flRadius, m_flRadius, m_flRadius ); + maxs = GetAbsOrigin() + Vector( m_flRadius, m_flRadius, m_flRadius ); + + // Draw debug information + if ( g_debug_hopwire.GetBool() ) + { + NDebugOverlay::Box( GetAbsOrigin(), mins - GetAbsOrigin(), maxs - GetAbsOrigin(), 0, 255, 0, 16, 4.0f ); + } + + CBaseEntity *pEnts[128]; + int numEnts = UTIL_EntitiesInBox( pEnts, 128, mins, maxs, 0 ); + + for ( int i = 0; i < numEnts; i++ ) + { + IPhysicsObject *pPhysObject = NULL; + + // Attempt to kill and ragdoll any victims in range + if ( KillNPCInRange( pEnts[i], &pPhysObject ) == false ) + { + // If we didn't have a valid victim, see if we can just get the vphysics object + pPhysObject = pEnts[i]->VPhysicsGetObject(); + if ( pPhysObject == NULL ) + continue; + } + + float mass; + + CRagdollProp *pRagdoll = dynamic_cast< CRagdollProp* >( pEnts[i] ); + if ( pRagdoll != NULL ) + { + ragdoll_t *pRagdollPhys = pRagdoll->GetRagdoll(); + mass = 0.0f; + + // Find the aggregate mass of the whole ragdoll + for ( int j = 0; j < pRagdollPhys->listCount; ++j ) + { + mass += pRagdollPhys->list[j].pObject->GetMass(); + } + } + else + { + mass = pPhysObject->GetMass(); + } + + Vector vecForce = GetAbsOrigin() - pEnts[i]->WorldSpaceCenter(); + Vector vecForce2D = vecForce; + vecForce2D[2] = 0.0f; + float dist2D = VectorNormalize( vecForce2D ); + float dist = VectorNormalize( vecForce ); + + // FIXME: Need a more deterministic method here + if ( dist < 48.0f ) + { + ConsumeEntity( pEnts[i] ); + continue; + } + + // Must be within the radius + if ( dist > m_flRadius ) + continue; + + // Find the pull force + vecForce *= ( 1.0f - ( dist2D / m_flRadius ) ) * m_flStrength * mass; + + if ( pEnts[i]->VPhysicsGetObject() ) + { + // Pull the object in + pEnts[i]->VPhysicsTakeDamage( CTakeDamageInfo( this, this, vecForce, GetAbsOrigin(), m_flStrength, DMG_BLAST ) ); + } + } + + // Keep going if need-be + if ( m_flEndTime > gpGlobals->curtime ) + { + SetThink( &CGravityVortexController::PullThink ); + SetNextThink( gpGlobals->curtime + 0.1f ); + } + else + { + //Msg( "Consumed %.2f kilograms\n", m_flMass ); + //CreateDenseBall(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Starts the vortex working +//----------------------------------------------------------------------------- +void CGravityVortexController::StartPull( const Vector &origin, float radius, float strength, float duration ) +{ + SetAbsOrigin( origin ); + m_flEndTime = gpGlobals->curtime + duration; + m_flRadius = radius; + m_flStrength= strength; + + SetThink( &CGravityVortexController::PullThink ); + SetNextThink( gpGlobals->curtime + 0.1f ); +} + +//----------------------------------------------------------------------------- +// Purpose: Creation utility +//----------------------------------------------------------------------------- +CGravityVortexController *CGravityVortexController::Create( const Vector &origin, float radius, float strength, float duration ) +{ + // Create an instance of the vortex + CGravityVortexController *pVortex = (CGravityVortexController *) CreateEntityByName( "vortex_controller" ); + if ( pVortex == NULL ) + return NULL; + + // Start the vortex working + pVortex->StartPull( origin, radius, strength, duration ); + + return pVortex; +} + +BEGIN_DATADESC( CGravityVortexController ) + DEFINE_FIELD( m_flMass, FIELD_FLOAT ), + DEFINE_FIELD( m_flEndTime, FIELD_TIME ), + DEFINE_FIELD( m_flRadius, FIELD_FLOAT ), + DEFINE_FIELD( m_flStrength, FIELD_FLOAT ), + + DEFINE_THINKFUNC( PullThink ), +END_DATADESC() + +LINK_ENTITY_TO_CLASS( vortex_controller, CGravityVortexController ); + +#define GRENADE_MODEL_CLOSED "models/roller.mdl" +#define GRENADE_MODEL_OPEN "models/roller_spikes.mdl" + +BEGIN_DATADESC( CGrenadeHopwire ) + DEFINE_FIELD( m_hVortexController, FIELD_EHANDLE ), + + DEFINE_THINKFUNC( EndThink ), + DEFINE_THINKFUNC( CombatThink ), +END_DATADESC() + +LINK_ENTITY_TO_CLASS( npc_grenade_hopwire, CGrenadeHopwire ); + +IMPLEMENT_SERVERCLASS_ST( CGrenadeHopwire, DT_GrenadeHopwire ) +END_SEND_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGrenadeHopwire::Spawn( void ) +{ + Precache(); + + SetModel( GRENADE_MODEL_CLOSED ); + SetCollisionGroup( COLLISION_GROUP_PROJECTILE ); + + CreateVPhysics(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CGrenadeHopwire::CreateVPhysics() +{ + // Create the object in the physics system + VPhysicsInitNormal( SOLID_BBOX, 0, false ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGrenadeHopwire::Precache( void ) +{ + // FIXME: Replace + //PrecacheSound("NPC_Strider.Shoot"); + //PrecacheSound("d3_citadel.weapon_zapper_beam_loop2"); + + PrecacheModel( GRENADE_MODEL_OPEN ); + PrecacheModel( GRENADE_MODEL_CLOSED ); + + PrecacheModel( DENSE_BALL_MODEL ); + + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : timer - +//----------------------------------------------------------------------------- +void CGrenadeHopwire::SetTimer( float timer ) +{ + SetThink( &CBaseGrenade::PreDetonate ); + SetNextThink( gpGlobals->curtime + timer ); +} + +#define MAX_STRIDER_KILL_DISTANCE_HORZ (hopwire_strider_kill_dist_h.GetFloat()) // Distance a Strider will be killed if within +#define MAX_STRIDER_KILL_DISTANCE_VERT (hopwire_strider_kill_dist_v.GetFloat()) // Distance a Strider will be killed if within + +#define MAX_STRIDER_STUN_DISTANCE_HORZ (MAX_STRIDER_KILL_DISTANCE_HORZ*2) // Distance a Strider will be stunned if within +#define MAX_STRIDER_STUN_DISTANCE_VERT (MAX_STRIDER_KILL_DISTANCE_VERT*2) // Distance a Strider will be stunned if within + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGrenadeHopwire::KillStriders( void ) +{ + CBaseEntity *pEnts[128]; + Vector mins, maxs; + + ClearBounds( mins, maxs ); + AddPointToBounds( -Vector( MAX_STRIDER_STUN_DISTANCE_HORZ, MAX_STRIDER_STUN_DISTANCE_HORZ, MAX_STRIDER_STUN_DISTANCE_HORZ ), mins, maxs ); + AddPointToBounds( Vector( MAX_STRIDER_STUN_DISTANCE_HORZ, MAX_STRIDER_STUN_DISTANCE_HORZ, MAX_STRIDER_STUN_DISTANCE_HORZ ), mins, maxs ); + AddPointToBounds( -Vector( MAX_STRIDER_STUN_DISTANCE_VERT, MAX_STRIDER_STUN_DISTANCE_VERT, MAX_STRIDER_STUN_DISTANCE_VERT ), mins, maxs ); + AddPointToBounds( Vector( MAX_STRIDER_STUN_DISTANCE_VERT, MAX_STRIDER_STUN_DISTANCE_VERT, MAX_STRIDER_STUN_DISTANCE_VERT ), mins, maxs ); + + // FIXME: It's probably much faster to simply iterate over the striders in the map, rather than any entity in the radius - jdw + + // Find any striders in range of us + int numTargets = UTIL_EntitiesInBox( pEnts, ARRAYSIZE( pEnts ), GetAbsOrigin()+mins, GetAbsOrigin()+maxs, FL_NPC ); + float targetDistHorz, targetDistVert; + + for ( int i = 0; i < numTargets; i++ ) + { + // Only affect striders + if ( FClassnameIs( pEnts[i], "npc_strider" ) == false ) + continue; + + // We categorize our spatial relation to the strider in horizontal and vertical terms, so that we can specify both parameters separately + targetDistHorz = UTIL_DistApprox2D( pEnts[i]->GetAbsOrigin(), GetAbsOrigin() ); + targetDistVert = fabs( pEnts[i]->GetAbsOrigin()[2] - GetAbsOrigin()[2] ); + + if ( targetDistHorz < MAX_STRIDER_KILL_DISTANCE_HORZ && targetDistHorz < MAX_STRIDER_KILL_DISTANCE_VERT ) + { + // Kill the strider + float fracDamage = ( pEnts[i]->GetMaxHealth() / hopwire_strider_hits.GetFloat() ) + 1.0f; + CTakeDamageInfo killInfo( this, this, fracDamage, DMG_GENERIC ); + Vector killDir = pEnts[i]->GetAbsOrigin() - GetAbsOrigin(); + VectorNormalize( killDir ); + + killInfo.SetDamageForce( killDir * -1000.0f ); + killInfo.SetDamagePosition( GetAbsOrigin() ); + + pEnts[i]->TakeDamage( killInfo ); + } + else if ( targetDistHorz < MAX_STRIDER_STUN_DISTANCE_HORZ && targetDistHorz < MAX_STRIDER_STUN_DISTANCE_VERT ) + { + // Stun the strider + CTakeDamageInfo killInfo( this, this, 200.0f, DMG_GENERIC ); + pEnts[i]->TakeDamage( killInfo ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGrenadeHopwire::EndThink( void ) +{ + if ( hopwire_vortex.GetBool() ) + { + EntityMessageBegin( this, true ); + WRITE_BYTE( 1 ); + MessageEnd(); + } + + SetThink( &CBaseEntity::SUB_Remove ); + SetNextThink( gpGlobals->curtime + 1.0f ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGrenadeHopwire::CombatThink( void ) +{ + // Stop the grenade from moving + AddEFlags( EF_NODRAW ); + AddFlag( FSOLID_NOT_SOLID ); + VPhysicsDestroyObject(); + SetAbsVelocity( vec3_origin ); + SetMoveType( MOVETYPE_NONE ); + + // Do special behaviors if there are any striders in the area + KillStriders(); + + // FIXME: Replace + //EmitSound("NPC_Strider.Shoot"); + //EmitSound("d3_citadel.weapon_zapper_beam_loop2"); + + // Quick screen flash + CBasePlayer *pPlayer = ToBasePlayer( GetThrower() ); + color32 white = { 255,255,255,255 }; + UTIL_ScreenFade( pPlayer, white, 0.2f, 0.0f, FFADE_IN ); + + // Create the vortex controller to pull entities towards us + if ( hopwire_vortex.GetBool() ) + { + m_hVortexController = CGravityVortexController::Create( GetAbsOrigin(), 512, 150, 3.0f ); + + // Start our client-side effect + EntityMessageBegin( this, true ); + WRITE_BYTE( 0 ); + MessageEnd(); + + // Begin to stop in two seconds + SetThink( &CGrenadeHopwire::EndThink ); + SetNextThink( gpGlobals->curtime + 2.0f ); + } + else + { + // Remove us immediately + SetThink( &CBaseEntity::SUB_Remove ); + SetNextThink( gpGlobals->curtime + 0.1f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGrenadeHopwire::SetVelocity( const Vector &velocity, const AngularImpulse &angVelocity ) +{ + IPhysicsObject *pPhysicsObject = VPhysicsGetObject(); + + if ( pPhysicsObject != NULL ) + { + pPhysicsObject->AddVelocity( &velocity, &angVelocity ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Hop off the ground to start deployment +//----------------------------------------------------------------------------- +void CGrenadeHopwire::Detonate( void ) +{ + SetModel( GRENADE_MODEL_OPEN ); + + AngularImpulse hopAngle = RandomAngularImpulse( -300, 300 ); + + //Find out how tall the ceiling is and always try to hop halfway + trace_t tr; + UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, MAX_HOP_HEIGHT*2 ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); + + // Jump half the height to the found ceiling + float hopHeight = MIN( MAX_HOP_HEIGHT, (MAX_HOP_HEIGHT*tr.fraction) ); + + //Add upwards velocity for the "hop" + Vector hopVel( 0.0f, 0.0f, hopHeight ); + SetVelocity( hopVel, hopAngle ); + + // Get the time until the apex of the hop + float apexTime = sqrt( hopHeight / GetCurrentGravity() ); + + // Explode at the apex + SetThink( &CGrenadeHopwire::CombatThink ); + SetNextThink( gpGlobals->curtime + apexTime); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseGrenade *HopWire_Create( const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, CBaseEntity *pOwner, float timer ) +{ + CGrenadeHopwire *pGrenade = (CGrenadeHopwire *) CBaseEntity::Create( "npc_grenade_hopwire", position, angles, pOwner ); + + // Only set ourselves to detonate on a timer if we're not a trap hopwire + if ( hopwire_trap.GetBool() == false ) + { + pGrenade->SetTimer( timer ); + } + + pGrenade->SetVelocity( velocity, angVelocity ); + pGrenade->SetThrower( ToBaseCombatCharacter( pOwner ) ); + + return pGrenade; +} |