//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: A gib is a chunk of a body, or a piece of wood/metal/rocks/etc. // // $Workfile: $ // $Date: $ // $NoKeywords: $ //===========================================================================// #include "cbase.h" #include "gib.h" #include "soundent.h" #include "func_break.h" // For materials #include "player.h" #include "vstdlib/random.h" #include "ai_utils.h" #include "EntityFlame.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" extern Vector g_vecAttackDir; // In globals.cpp BEGIN_DATADESC( CGib ) // gibs are not saved/restored // DEFINE_FIELD( m_bloodColor, FIELD_INTEGER ), // DEFINE_FIELD( m_hSprite, FIELD_EHANDLE ), // DEFINE_FIELD( m_cBloodDecals, FIELD_INTEGER ), // DEFINE_FIELD( m_material, FIELD_INTEGER ), // DEFINE_FIELD( m_lifeTime, FIELD_TIME ), // DEFINE_FIELD( m_pSprite, CSprite ), // DEFINE_FIELD( m_hFlame, FIELD_EHANDLE ), // DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ), // DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ), // DEFINE_FIELD( m_bForceRemove, FIELD_BOOLEAN ), // Function pointers DEFINE_ENTITYFUNC( BounceGibTouch ), DEFINE_ENTITYFUNC( StickyGibTouch ), DEFINE_THINKFUNC( WaitTillLand ), DEFINE_THINKFUNC( DieThink ), END_DATADESC() // HACKHACK -- The gib velocity equations don't work void CGib::LimitVelocity( void ) { Vector vecNewVelocity = GetAbsVelocity(); float length = VectorNormalize( vecNewVelocity ); // ceiling at 1500. The gib velocity equation is not bounded properly. Rather than tune it // in 3 separate places again, I'll just limit it here. if ( length > 1500.0 ) { vecNewVelocity *= 1500; // This should really be sv_maxvelocity * 0.75 or something SetAbsVelocity( vecNewVelocity ); } } void CGib::SpawnStickyGibs( CBaseEntity *pVictim, Vector vecOrigin, int cGibs ) { int i; if ( g_Language.GetInt() == LANGUAGE_GERMAN ) { // no sticky gibs in germany right now! return; } for ( i = 0 ; i < cGibs ; i++ ) { CGib *pGib = (CGib *)CreateEntityByName( "gib" ); pGib->Spawn( "models/stickygib.mdl" ); pGib->m_nBody = random->RandomInt(0,2); if ( pVictim ) { pGib->SetLocalOrigin( Vector( vecOrigin.x + random->RandomFloat( -3, 3 ), vecOrigin.y + random->RandomFloat( -3, 3 ), vecOrigin.z + random->RandomFloat( -3, 3 ) ) ); // make the gib fly away from the attack vector Vector vecNewVelocity = g_vecAttackDir * -1; // mix in some noise vecNewVelocity.x += random->RandomFloat ( -0.15, 0.15 ); vecNewVelocity.y += random->RandomFloat ( -0.15, 0.15 ); vecNewVelocity.z += random->RandomFloat ( -0.15, 0.15 ); vecNewVelocity *= 900; QAngle vecAngVelocity( random->RandomFloat ( 250, 400 ), random->RandomFloat ( 250, 400 ), 0 ); pGib->SetLocalAngularVelocity( vecAngVelocity ); // copy owner's blood color pGib->SetBloodColor( pVictim->BloodColor() ); pGib->AdjustVelocityBasedOnHealth( pVictim->m_iHealth, vecNewVelocity ); pGib->SetAbsVelocity( vecNewVelocity ); pGib->SetMoveType( MOVETYPE_FLYGRAVITY ); pGib->RemoveSolidFlags( FSOLID_NOT_SOLID ); pGib->SetCollisionBounds( vec3_origin, vec3_origin ); pGib->SetTouch ( &CGib::StickyGibTouch ); pGib->SetThink (NULL); } pGib->LimitVelocity(); } } void CGib::SpawnHeadGib( CBaseEntity *pVictim ) { CGib *pGib = CREATE_ENTITY( CGib, "gib" ); if ( g_Language.GetInt() == LANGUAGE_GERMAN ) { pGib->Spawn( "models/germangibs.mdl" );// throw one head pGib->m_nBody = 0; } else { pGib->Spawn( "models/gibs/hgibs.mdl" );// throw one head pGib->m_nBody = 0; } if ( pVictim ) { Vector vecNewVelocity = pGib->GetAbsVelocity(); pGib->SetLocalOrigin( pVictim->EyePosition() ); edict_t *pentPlayer = UTIL_FindClientInPVS( pGib->edict() ); if ( random->RandomInt ( 0, 100 ) <= 5 && pentPlayer ) { // 5% chance head will be thrown at player's face. CBasePlayer *player = (CBasePlayer *)CBaseEntity::Instance( pentPlayer ); if ( player ) { vecNewVelocity = ( player->EyePosition() ) - pGib->GetAbsOrigin(); VectorNormalize(vecNewVelocity); vecNewVelocity *= 300; vecNewVelocity.z += 100; } } else { vecNewVelocity = Vector (random->RandomFloat(-100,100), random->RandomFloat(-100,100), random->RandomFloat(200,300)); } QAngle vecNewAngularVelocity = pGib->GetLocalAngularVelocity(); vecNewAngularVelocity.x = random->RandomFloat ( 100, 200 ); vecNewAngularVelocity.y = random->RandomFloat ( 100, 300 ); pGib->SetLocalAngularVelocity( vecNewAngularVelocity ); // copy owner's blood color pGib->SetBloodColor( pVictim->BloodColor() ); pGib->AdjustVelocityBasedOnHealth( pVictim->m_iHealth, vecNewVelocity ); pGib->SetAbsVelocity( vecNewVelocity ); } pGib->LimitVelocity(); } //----------------------------------------------------------------------------- // Blood color (see BLOOD_COLOR_* macros in baseentity.h) //----------------------------------------------------------------------------- void CGib::SetBloodColor( int nBloodColor ) { m_bloodColor = nBloodColor; } //------------------------------------------------------------------------------ // A little piece of duplicated code //------------------------------------------------------------------------------ void CGib::AdjustVelocityBasedOnHealth( int nHealth, Vector &vecVelocity ) { if ( nHealth > -50) { vecVelocity *= 0.7; } else if ( nHealth > -200) { vecVelocity *= 2; } else { vecVelocity *= 4; } } //------------------------------------------------------------------------------ // Purpose : Initialize a gibs position and velocity // Input : // Output : //------------------------------------------------------------------------------ void CGib::InitGib( CBaseEntity *pVictim, float fMinVelocity, float fMaxVelocity ) { // ------------------------------------------------------------------------ // If have a pVictim spawn the gib somewhere in the pVictim's bounding volume // ------------------------------------------------------------------------ if ( pVictim ) { // Find a random position within the bounding box (add 1 to Z to get it out of the ground) Vector vecOrigin; pVictim->CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &vecOrigin ); vecOrigin.z += 1.0f; SetAbsOrigin( vecOrigin ); // make the gib fly away from the attack vector Vector vecNewVelocity = g_vecAttackDir * -1; // mix in some noise vecNewVelocity.x += random->RandomFloat ( -0.25, 0.25 ); vecNewVelocity.y += random->RandomFloat ( -0.25, 0.25 ); vecNewVelocity.z += random->RandomFloat ( -0.25, 0.25 ); vecNewVelocity *= random->RandomFloat ( fMaxVelocity, fMinVelocity ); QAngle vecNewAngularVelocity = GetLocalAngularVelocity(); vecNewAngularVelocity.x = random->RandomFloat ( 100, 200 ); vecNewAngularVelocity.y = random->RandomFloat ( 100, 300 ); SetLocalAngularVelocity( vecNewAngularVelocity ); // copy owner's blood color SetBloodColor( pVictim->BloodColor() ); AdjustVelocityBasedOnHealth( pVictim->m_iHealth, vecNewVelocity ); // Attempt to be physical if we can if ( VPhysicsInitNormal( SOLID_BBOX, 0, false ) ) { IPhysicsObject *pObj = VPhysicsGetObject(); if ( pObj != NULL ) { AngularImpulse angImpulse = RandomAngularImpulse( -500, 500 ); pObj->AddVelocity( &vecNewVelocity, &angImpulse ); } } else { SetSolid( SOLID_BBOX ); SetCollisionBounds( vec3_origin, vec3_origin ); SetAbsVelocity( vecNewVelocity ); } SetCollisionGroup( COLLISION_GROUP_DEBRIS ); } LimitVelocity(); } //------------------------------------------------------------------------------ // Purpose : Given an .mdl file with gibs and the number of gibs in the file // spawns them in pVictim's bounding box // Input : // Output : //------------------------------------------------------------------------------ void CGib::SpawnSpecificGibs( CBaseEntity* pVictim, int nNumGibs, float vMinVelocity, float vMaxVelocity, const char* cModelName, float flLifetime) { for (int i=0;iSpawn( cModelName ); pGib->m_nBody = i; pGib->InitGib( pVictim, vMinVelocity, vMaxVelocity ); pGib->m_lifeTime = flLifetime; if ( pVictim != NULL ) { pGib->SetOwnerEntity( pVictim ); } } } //------------------------------------------------------------------------------ // Purpose : Spawn random gibs of the given gib type // Input : // Output : //------------------------------------------------------------------------------ void CGib::SpawnRandomGibs( CBaseEntity *pVictim, int cGibs, GibType_e eGibType ) { int cSplat; for ( cSplat = 0 ; cSplat < cGibs ; cSplat++ ) { CGib *pGib = CREATE_ENTITY( CGib, "gib" ); if ( g_Language.GetInt() == LANGUAGE_GERMAN ) { pGib->Spawn( "models/germangibs.mdl" ); pGib->m_nBody = random->RandomInt(0,GERMAN_GIB_COUNT-1); } else { switch (eGibType) { case GIB_HUMAN: // human pieces pGib->Spawn( "models/gibs/hgibs.mdl" ); pGib->m_nBody = random->RandomInt(1,HUMAN_GIB_COUNT-1);// start at one to avoid throwing random amounts of skulls (0th gib) break; case GIB_ALIEN: // alien pieces pGib->Spawn( "models/gibs/agibs.mdl" ); pGib->m_nBody = random->RandomInt(0,ALIEN_GIB_COUNT-1); break; } } pGib->InitGib( pVictim, 300, 400); } } //========================================================= // WaitTillLand - in order to emit their meaty scent from // the proper location, gibs should wait until they stop // bouncing to emit their scent. That's what this function // does. //========================================================= void CGib::WaitTillLand ( void ) { if (!IsInWorld()) { UTIL_Remove( this ); return; } if ( GetAbsVelocity() == vec3_origin ) { SetRenderAlpha( 255 ); m_nRenderMode = kRenderTransTexture; if ( GetMoveType() != MOVETYPE_VPHYSICS ) { AddSolidFlags( FSOLID_NOT_SOLID ); } SetLocalAngularVelocity( vec3_angle ); SetNextThink( gpGlobals->curtime + m_lifeTime ); SetThink ( &CGib::SUB_FadeOut ); if ( GetSprite() ) { CSprite *pSprite = dynamic_cast( GetSprite() ); if ( pSprite ) { //Adrian - Why am I doing this? Check InitPointGib for the answer! if ( m_lifeTime == 0 ) m_lifeTime = random->RandomFloat( 1, 3 ); pSprite->FadeAndDie( m_lifeTime ); } } if ( GetFlame() ) { CEntityFlame *pFlame = dynamic_cast< CEntityFlame*>( GetFlame() ); if ( pFlame ) { pFlame->SetLifetime( 1.0f ); } } // If you bleed, you stink! if ( m_bloodColor != DONT_BLEED ) { // ok, start stinkin! // FIXME: It's too easy to fill up the sound queue with all these meat sounds // CSoundEnt::InsertSound ( SOUND_MEAT, GetAbsOrigin(), 384, 25 ); } } else { // wait and check again in another half second. SetNextThink( gpGlobals->curtime + 0.5f ); } } bool CGib::SUB_AllowedToFade( void ) { if( VPhysicsGetObject() ) { if( VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD || GetEFlags() & EFL_IS_BEING_LIFTED_BY_BARNACLE ) return false; } CBasePlayer *pPlayer = ( AI_IsSinglePlayer() ) ? UTIL_GetLocalPlayer() : NULL; if ( pPlayer && pPlayer->FInViewCone( this ) && m_bForceRemove == false ) { return false; } return true; } void CGib::DieThink ( void ) { if ( GetSprite() ) { CSprite *pSprite = dynamic_cast( GetSprite() ); if ( pSprite ) { pSprite->FadeAndDie( 0.0 ); } } if ( GetFlame() ) { CEntityFlame *pFlame = dynamic_cast< CEntityFlame*>( GetFlame() ); if ( pFlame ) { pFlame->SetLifetime( 1.0f ); } } if ( g_pGameRules->IsMultiplayer() ) { UTIL_Remove( this ); } else { SetThink ( &CGib::SUB_FadeOut ); SetNextThink( gpGlobals->curtime ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CGib::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { CBasePlayer *pPlayer = ToBasePlayer( pActivator ); if ( pPlayer ) { pPlayer->PickupObject( this ); } } //----------------------------------------------------------------------------- // Physics Attacker //----------------------------------------------------------------------------- void CGib::SetPhysicsAttacker( CBasePlayer *pEntity, float flTime ) { m_hPhysicsAttacker = pEntity; m_flLastPhysicsInfluenceTime = flTime; } //----------------------------------------------------------------------------- // Purpose: Keep track of physgun influence //----------------------------------------------------------------------------- void CGib::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason ) { SetPhysicsAttacker( pPhysGunUser, gpGlobals->curtime ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CGib::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason ) { SetPhysicsAttacker( pPhysGunUser, gpGlobals->curtime ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- CBasePlayer *CGib::HasPhysicsAttacker( float dt ) { if (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime) { return m_hPhysicsAttacker; } return NULL; } // // Gib bounces on the ground or wall, sponges some blood down, too! // void CGib::BounceGibTouch ( CBaseEntity *pOther ) { Vector vecSpot; trace_t tr; IPhysicsObject *pPhysics = VPhysicsGetObject(); if ( pPhysics ) return; //if ( random->RandomInt(0,1) ) // return;// don't bleed everytime if (GetFlags() & FL_ONGROUND) { SetAbsVelocity( GetAbsVelocity() * 0.9 ); QAngle angles = GetLocalAngles(); angles.x = 0; angles.z = 0; SetLocalAngles( angles ); QAngle angVel = GetLocalAngularVelocity(); angVel.x = 0; angVel.z = 0; SetLocalAngularVelocity( vec3_angle ); } else { if ( g_Language.GetInt() != LANGUAGE_GERMAN && m_cBloodDecals > 0 && m_bloodColor != DONT_BLEED ) { vecSpot = GetAbsOrigin() + Vector ( 0 , 0 , 8 );//move up a bit, and trace down. UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -24 ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr); UTIL_BloodDecalTrace( &tr, m_bloodColor ); m_cBloodDecals--; } if ( m_material != matNone && random->RandomInt(0,2) == 0 ) { float volume; float zvel = fabs(GetAbsVelocity().z); volume = 0.8f * MIN(1.0, ((float)zvel) / 450.0f); CBreakable::MaterialSoundRandom( entindex(), (Materials)m_material, volume ); } } } // // Sticky gib puts blood on the wall and stays put. // void CGib::StickyGibTouch ( CBaseEntity *pOther ) { Vector vecSpot; trace_t tr; SetThink ( &CGib::SUB_Remove ); SetNextThink( gpGlobals->curtime + 10 ); if ( !FClassnameIs( pOther, "worldspawn" ) ) { SetNextThink( gpGlobals->curtime ); return; } UTIL_TraceLine ( GetAbsOrigin(), GetAbsOrigin() + GetAbsVelocity() * 32, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr); UTIL_BloodDecalTrace( &tr, m_bloodColor ); Vector vecForward = tr.plane.normal * -1; QAngle angles; VectorAngles( vecForward, angles ); SetLocalAngles( angles ); SetAbsVelocity( vec3_origin ); SetLocalAngularVelocity( vec3_angle ); SetMoveType( MOVETYPE_NONE ); } // // Throw a chunk // void CGib::Spawn( const char *szGibModel ) { SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE ); SetFriction(0.55); // deading the bounce a bit // sometimes an entity inherits the edict from a former piece of glass, // and will spawn using the same render FX or m_nRenderMode! bad! SetRenderAlpha( 255 ); m_nRenderMode = kRenderNormal; m_nRenderFX = kRenderFxNone; // hopefully this will fix the VELOCITY TOO LOW crap m_takedamage = DAMAGE_EVENTS_ONLY; SetSolid( SOLID_BBOX ); AddSolidFlags( FSOLID_NOT_STANDABLE ); SetCollisionGroup( COLLISION_GROUP_DEBRIS ); SetModel( szGibModel ); SetNextThink( gpGlobals->curtime + 4 ); m_lifeTime = 25; SetTouch ( &CGib::BounceGibTouch ); m_bForceRemove = false; m_material = matNone; m_cBloodDecals = 5;// how many blood decals this gib can place (1 per bounce until none remain). } //----------------------------------------------------------------------------- // Spawn a gib with a finite lifetime, after which it will fade out. //----------------------------------------------------------------------------- void CGib::Spawn( const char *szGibModel, float flLifetime ) { Spawn( szGibModel ); m_lifeTime = flLifetime; SetThink ( &CGib::SUB_FadeOut ); SetNextThink( gpGlobals->curtime + m_lifeTime ); } LINK_ENTITY_TO_CLASS( gib, CGib ); CBaseEntity *CreateRagGib( const char *szModel, const Vector &vecOrigin, const QAngle &vecAngles, const Vector &vecForce, float flFadeTime, bool bShouldIgnite ) { CRagGib *pGib; pGib = (CRagGib*)CreateEntityByName( "raggib" ); pGib->SetLocalAngles( vecAngles ); if ( !pGib ) { Msg( "**Can't create ragdoll gib!\n" ); return NULL; } if ( bShouldIgnite ) { CBaseAnimating *pAnimating = pGib->GetBaseAnimating(); if (pAnimating != NULL ) { pAnimating->Ignite( random->RandomFloat( 8.0, 12.0 ), false ); } } pGib->Spawn( szModel, vecOrigin, vecForce, flFadeTime ); return pGib; } void CRagGib::Spawn( const char *szModel, const Vector &vecOrigin, const Vector &vecForce, float flFadeTime = 0.0 ) { SetSolid( SOLID_BBOX ); AddSolidFlags( FSOLID_NOT_SOLID ); SetModel( szModel ); UTIL_SetSize(this, vec3_origin, vec3_origin); UTIL_SetOrigin( this, vecOrigin ); if ( !BecomeRagdollOnClient( vecForce ) ) { AddSolidFlags( FSOLID_NOT_STANDABLE ); RemoveSolidFlags( FSOLID_NOT_SOLID ); if( flFadeTime > 0.0 ) { SUB_StartFadeOut( flFadeTime ); } } } LINK_ENTITY_TO_CLASS( raggib, CRagGib );