Vector Pickup_PhysGunLaunchVelocity( CBaseEntity *pObject, const Vector &vecForward, PhysGunForce_t reason ) { // The object must be valid if ( pObject == NULL ) { Assert( 0 ); return vec3_origin; } // Shouldn't ever get here with a non-vphysics object. IPhysicsObject *pPhysicsObject = pObject->VPhysicsGetObject(); if ( pPhysicsObject == NULL ) { Assert( 0 ); return vec3_origin; } // Call the pickup entity's callback IPlayerPickupVPhysics *pPickup = dynamic_cast<IPlayerPickupVPhysics *>(pObject); if ( pPickup != NULL && pPickup->ShouldPuntUseLaunchForces( reason ) ) return pPickup->PhysGunLaunchVelocity( vecForward, pPhysicsObject->GetMass() ); // Do our default behavior return Pickup_DefaultPhysGunLaunchVelocity( vecForward, pPhysicsObject->GetMass() ); }
//--------------------------------------------------------- // 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 * sv_gravity.GetFloat()) ); float velocity = sv_gravity.GetFloat() * 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 ); } }
//----------------------------------------------------------------------------- // 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: // Input : // Output : //----------------------------------------------------------------------------- int CTDP_NPC_CombineS::TakeDamage( const CTakeDamageInfo &info ) { if( info.GetInflictor() && info.GetInflictor()->VPhysicsGetObject() ) { // Hit by a physics object! Was I blocking? if( m_fIsBlocking ) { IPhysicsObject *pPhysObject; pPhysObject = info.GetInflictor()->VPhysicsGetObject(); if( pPhysObject ) { // Only deflect objects of relatively low mass //DevMsg( "MASS: %f\n", pPhysObject->GetMass() ); if( pPhysObject->GetMass() <= 30.0 ) { // No damage from light objects (tuned for melons) return 0; } } } } BaseClass::TakeDamage( info ); return 0; }
void CPlayerPickupController::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { if ( ToBasePlayer(pActivator) == m_pPlayer ) { CBaseEntity *pAttached = m_grabController.GetAttached(); // UNDONE: Use vphysics stress to decide to drop objects // UNDONE: Must fix case of forcing objects into the ground you're standing on (causes stress) before that will work if ( !pAttached || useType == USE_OFF || (m_pPlayer->m_nButtons & IN_ATTACK2) || m_grabController.ComputeError() > 12 ) { Shutdown(); return; } //Adrian: Oops, our object became motion disabled, let go! IPhysicsObject *pPhys = pAttached->VPhysicsGetObject(); if ( pPhys && pPhys->IsMoveable() == false ) { Shutdown(); return; } #if STRESS_TEST vphysics_objectstress_t stress; CalculateObjectStress( pPhys, pAttached, &stress ); if ( stress.exertedStress > 250 ) { Shutdown(); return; } #endif #ifndef PLAYER_DISABLE_THROWING // +ATTACK will throw phys objects if ( m_pPlayer->m_nButtons & IN_ATTACK ) { Shutdown( true ); Vector vecLaunch; m_pPlayer->EyeVectors( &vecLaunch ); // JAY: Scale this with mass because some small objects really go flying float massFactor = clamp( pPhys->GetMass(), 0.5, 15 ); massFactor = RemapVal( massFactor, 0.5, 15, 0.5, 4 ); vecLaunch *= player_throwforce.GetFloat() * massFactor; pPhys->ApplyForceCenter( vecLaunch ); AngularImpulse aVel = RandomAngularImpulse( -10, 10 ) * massFactor; pPhys->ApplyTorqueCenter( aVel ); return; } #endif if ( useType == USE_SET ) { // update position m_grabController.UpdateObject( m_pPlayer, 12 ); } } }
//----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPhysMagnet::DoMagnetSuck( CBaseEntity *pOther ) { if ( !HasSpawnFlags( SF_MAGNET_SUCK ) ) return; if ( !m_bActive ) return; // Don't repeatedly suck if ( m_flNextSuckTime > gpGlobals->curtime ) return; // Look for physics objects underneath the magnet and suck them onto it Vector vecCheckPos, vecSuckPoint; VectorTransform( Vector(0,0,-96), EntityToWorldTransform(), vecCheckPos ); VectorTransform( Vector(0,0,-64), EntityToWorldTransform(), vecSuckPoint ); CBaseEntity *pEntities[20]; int iNumEntities = UTIL_EntitiesInSphere( pEntities, 20, vecCheckPos, 80.0, 0 ); for ( int i = 0; i < iNumEntities; i++ ) { CBaseEntity *pEntity = pEntities[i]; if ( !pEntity || pEntity == pOther ) continue; IPhysicsObject *pPhys = pEntity->VPhysicsGetObject(); if ( pPhys && pEntity->GetMoveType() == MOVETYPE_VPHYSICS && pPhys->GetMass() < 5000 ) { // Do we have line of sight to it? trace_t tr; UTIL_TraceLine( GetAbsOrigin(), pEntity->GetAbsOrigin(), MASK_SHOT, this, 0, &tr ); if ( tr.fraction == 1.0 || tr.m_pEnt == pEntity ) { // Pull it towards the magnet Vector vecVelocity = (vecSuckPoint - pEntity->GetAbsOrigin()); VectorNormalize(vecVelocity); vecVelocity *= 5 * pPhys->GetMass(); pPhys->AddVelocity( &vecVelocity, NULL ); } } } m_flNextSuckTime = gpGlobals->curtime + 2.0; }
void CWeaponGravityGun::SoundUpdate( void ) { int newState; if ( m_hObject ) newState = SS_LOCKEDON; else newState = SS_SCANNING; if ( newState != m_soundState ) { SoundStop(); m_soundState = newState; SoundStart(); } switch( m_soundState ) { case SS_SCANNING: break; case SS_LOCKEDON: { CPASAttenuationFilter filter( this ); float height = m_hObject->GetAbsOrigin().z - m_originalObjectPosition.z; // go from pitch 90 to 150 over a height of 500 int pitch = 90 + (int)UTIL_LineFraction( height, 0, 500, 60 ); assert(m_sndLockedOn!=NULL); if ( m_sndLockedOn != NULL ) { (CSoundEnvelopeController::GetController()).SoundChangePitch( m_sndLockedOn, pitch, 0.0f ); } // attenutate the movement sounds over 200 units of movement float distance = UTIL_LineFraction( m_movementLength, 0, 200, 1.0 ); // blend the "mass" sounds between 50 and 500 kg IPhysicsObject *pPhys = GetPhysObjFromPhysicsBone( m_hObject, m_physicsBone ); if ( pPhys == NULL ) { // we no longer exist! break; } float fade = UTIL_LineFraction( pPhys->GetMass(), 50, 500, 1.0 ); (CSoundEnvelopeController::GetController()).SoundChangeVolume( m_sndLightObject, fade * distance, 0.0f ); (CSoundEnvelopeController::GetController()).SoundChangeVolume( m_sndHeavyObject, (1.0 - fade) * distance, 0.0f ); } break; } }
static bool InContactWithHeavyObject( IPhysicsObject *pObject, float heavyMass ) { bool contact = false; IPhysicsFrictionSnapshot *pSnapshot = pObject->CreateFrictionSnapshot(); while ( pSnapshot->IsValid() ) { IPhysicsObject *pOther = pSnapshot->GetObject( 1 ); if ( !pOther->IsMoveable() || pOther->GetMass() > heavyMass ) { contact = true; break; } pSnapshot->NextFrictionData(); } pObject->DestroyFrictionSnapshot( pSnapshot ); return contact; }
bool CZombie::IsSquashed( const CTakeDamageInfo &info ) { if( GetHealth() > 0 ) return false; if( info.GetDamageType() & DMG_CRUSH ) { IPhysicsObject *pCrusher = info.GetInflictor()->VPhysicsGetObject(); if( pCrusher && pCrusher->GetMass() >= ZOMBIE_SQUASH_MASS && info.GetInflictor()->WorldSpaceCenter().z > EyePosition().z ) // This heuristic detects when a zombie has been squashed from above by a heavy // item. Done specifically so we can add gore effects to Ravenholm cartraps. // The zombie must take physics damage from a 300+kg object that is centered above its eyes (comes from above) return true; } return false; }
CBaseEntity *CreateServerRagdoll( CBaseAnimating *pAnimating, int forceBone, const CTakeDamageInfo &info, int collisionGroup ) { SyncAnimatingWithPhysics( pAnimating ); CRagdollProp *pRagdoll = (CRagdollProp *)CBaseEntity::CreateNoSpawn( "prop_ragdoll", pAnimating->GetAbsOrigin(), vec3_angle, NULL ); pRagdoll->CopyAnimationDataFrom( pAnimating ); pRagdoll->InitRagdollAnimation(); matrix3x4_t pBoneToWorld[MAXSTUDIOBONES]; pAnimating->SetupBones( pBoneToWorld, BONE_USED_BY_ANYTHING ); // Is this a vehicle / NPC collision? if ( (info.GetDamageType() & DMG_VEHICLE) && pAnimating->MyNPCPointer() ) { // init the ragdoll with no forces pRagdoll->InitRagdoll( vec3_origin, -1, vec3_origin, pBoneToWorld, pBoneToWorld, 0.1, collisionGroup, true ); // apply vehicle forces // Get a list of bones with hitboxes below the plane of impact int boxList[128]; Vector normal(0,0,-1); int count = pAnimating->GetHitboxesFrontside( boxList, ARRAYSIZE(boxList), normal, DotProduct( normal, info.GetDamagePosition() ) ); // distribute force over mass of entire character float massScale = Studio_GetMass(pAnimating->GetModelPtr()); massScale = clamp( massScale, 1, 1e4 ); massScale = 1 / massScale; // distribute the force // BUGBUG: This will hit the same bone twice if it has two hitboxes!!!! ragdoll_t *pRagInfo = pRagdoll->GetRagdoll(); for ( int i = 0; i < count; i++ ) { int physBone = pAnimating->GetPhysicsBone( pAnimating->GetHitboxBone( boxList[i] ) ); IPhysicsObject *pPhysics = pRagInfo->list[physBone].pObject; pPhysics->ApplyForceCenter( info.GetDamageForce() * pPhysics->GetMass() * massScale ); } } else { pRagdoll->InitRagdoll( info.GetDamageForce(), forceBone, info.GetDamagePosition(), pBoneToWorld, pBoneToWorld, 0.1, collisionGroup, true ); } return pRagdoll; }
//----------------------------------------------------------------------------- // Purpose: Push a physics object in our wash. Return false if it's now out of our wash //----------------------------------------------------------------------------- bool CBaseHelicopter::DoWashPush( washentity_t *pWash, Vector vecWashOrigin ) { if ( !pWash || !pWash->hEntity.Get() ) return false; // Make sure the entity is still within our wash's radius CBaseEntity *pEntity = pWash->hEntity; Vector vecSpot = pEntity->BodyTarget( vecWashOrigin ); Vector vecToSpot = ( vecSpot - vecWashOrigin ); vecToSpot.z = 0; float flDist = VectorNormalize( vecToSpot ); if ( flDist > BASECHOPPER_WASH_RADIUS ) return false; IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject(); if ( pPhysObject == NULL ) return false; // Push it away from the center of the wash float flMass = pPhysObject->GetMass(); float flPushTime = (gpGlobals->curtime - pWash->flWashStartTime); float flMinPush = BASECHOPPER_WASH_PUSH_MIN * flMass; float flMaxPush = BASECHOPPER_WASH_PUSH_MAX * flMass; float flWashAmount = min( flMaxPush, RemapVal( flPushTime, 0, BASECHOPPER_WASH_RAMP_TIME, flMinPush, flMaxPush ) ); Vector vecForce = flWashAmount * vecToSpot * phys_pushscale.GetFloat(); pEntity->VPhysicsTakeDamage( CTakeDamageInfo( this, this, vecForce, vecWashOrigin, flWashAmount, DMG_BLAST ) ); // Debug if ( g_debug_basehelicopter.GetInt() == BASECHOPPER_DEBUG_WASH ) { NDebugOverlay::Cross3D( pEntity->GetAbsOrigin(), -Vector(4,4,4), Vector(4,4,4), 255, 0, 0, true, 0.1f ); NDebugOverlay::Line( pEntity->GetAbsOrigin(), pEntity->GetAbsOrigin() + vecForce, 255, 255, 0, true, 0.1f ); IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject(); Msg("Pushed %s (mass %f) with force %f (min %.2f max %.2f) at time %.2f\n", pEntity->GetClassname(), pPhysObject->GetMass(), flWashAmount, flMinPush, flMaxPush, gpGlobals->curtime ); } // If we've pushed this thing for some time, remove it to give us a chance to find lighter things nearby if ( flPushTime > 2.0 ) return false; return true; }
//----------------------------------------------------------------------------- // Purpose: One of our magnet constraints broke //----------------------------------------------------------------------------- void CPhysMagnet::ConstraintBroken( IPhysicsConstraint *pConstraint ) { // Find the entity that was constrained and release it int iCount = m_MagnettedEntities.Count(); for ( int i = 0; i < iCount; i++ ) { if ( m_MagnettedEntities[i].pConstraint == pConstraint ) { IPhysicsObject *pPhysObject = m_MagnettedEntities[i].hEntity->VPhysicsGetObject(); m_flTotalMass -= pPhysObject->GetMass(); m_MagnettedEntities.Remove(i); break; } } m_OnMagnetDetach.FireOutput( this, this ); physenv->DestroyConstraint( pConstraint ); }
//----------------------------------------------------------------------------- // Purpose: Get the force that we should add to this NPC's ragdoll. // Input : *pNPC - // Output : Vector // // NOTE: This function assumes pNPC is within this magnet's radius. //----------------------------------------------------------------------------- Vector CRagdollMagnet::GetForceVector( CBaseEntity *pNPC ) { Vector vecForceToApply; if( IsBarMagnet() ) { CPlane axis; Vector vecForceDir; Vector vecClosest; CalcClosestPointOnLineSegment( pNPC->WorldSpaceCenter(), GetAbsOrigin(), m_axis, vecClosest, NULL ); vecForceDir = (vecClosest - pNPC->WorldSpaceCenter() ); VectorNormalize( vecForceDir ); vecForceToApply = vecForceDir * m_force; } else { Vector vecForce; vecForce = GetAbsOrigin() - pNPC->WorldSpaceCenter(); VectorNormalize( vecForce ); vecForceToApply = vecForce * m_force; } if( ai_debug_ragdoll_magnets.GetBool() ) { IPhysicsObject *pPhysObject; pPhysObject = pNPC->VPhysicsGetObject(); if( pPhysObject ) { Msg("Ragdoll magnet adding %f inches/sec to %s\n", m_force/pPhysObject->GetMass(), pNPC->GetClassname() ); } } return vecForceToApply; }
void CWeaponGravityGun::AttachObject( CBaseEntity *pObject, const Vector& start, const Vector &end, float distance ) { m_hObject = pObject; m_useDown = false; IPhysicsObject *pPhysics = pObject ? (pObject->VPhysicsGetObject()) : NULL; if ( pPhysics && pObject->GetMoveType() == MOVETYPE_VPHYSICS ) { m_distance = distance; m_gravCallback.AttachEntity( pObject, pPhysics, end ); float mass = pPhysics->GetMass(); Msg( "Object mass: %.2f lbs (%.2f kg)\n", kg2lbs(mass), mass ); float vel = phys_gunvel.GetFloat(); if ( mass > phys_gunmass.GetFloat() ) { vel = (vel*phys_gunmass.GetFloat())/mass; } m_gravCallback.SetMaxVelocity( vel ); // Msg( "Object mass: %.2f lbs (%.2f kg) %f %f %f\n", kg2lbs(mass), mass, pObject->GetAbsOrigin().x, pObject->GetAbsOrigin().y, pObject->GetAbsOrigin().z ); // Msg( "ANG: %f %f %f\n", pObject->GetAbsAngles().x, pObject->GetAbsAngles().y, pObject->GetAbsAngles().z ); m_originalObjectPosition = pObject->GetAbsOrigin(); m_pelletAttract = -1; m_pelletHeld = -1; pPhysics->Wake(); SortPelletsForObject( pObject ); CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if( pOwner ) { Pickup_OnPhysGunPickup( pObject, pOwner ); } } else { m_hObject = NULL; } }
//----------------------------------------------------------------------------- // Purpose: // Input : *pActivator - // *pCaller - // useType - // value - //----------------------------------------------------------------------------- void CPhysImpact::InputImpact( inputdata_t &inputdata ) { Vector dir; trace_t trace; AngleVectors( GetAbsAngles(), &dir ); //Setup our trace information float dist = HasSpawnFlags( bitsPHYSIMPACT_INFINITE_LENGTH ) ? MAX_TRACE_LENGTH : m_distance; Vector start = GetAbsOrigin(); Vector end = start + ( dir * dist ); //Trace out UTIL_TraceLine( start, end, MASK_SHOT, this, 0, &trace ); if ( trace.fraction != 1.0 ) { CBaseEntity *pEnt = trace.m_pEnt; IPhysicsObject *pPhysics = pEnt->VPhysicsGetObject(); //If the entity is valid, hit it if ( ( pEnt != NULL ) && ( pPhysics != NULL ) ) { //Damage falls off unless specified or the ray's length is infinite float damage = HasSpawnFlags( bitsPHYSIMPACT_NOFALLOFF | bitsPHYSIMPACT_INFINITE_LENGTH ) ? m_damage : (m_damage * (1.0f-trace.fraction)); if ( HasSpawnFlags( bitsPHYSIMPACT_IGNORE_MASS ) ) { damage *= pPhysics->GetMass(); } pPhysics->ApplyForceOffset( -damage * trace.plane.normal * phys_pushscale.GetFloat(), trace.endpos ); } } }
float CalculateObjectStress( IPhysicsObject *pObject, CBaseEntity *pInputOwnerEntity, vphysics_objectstress_t *pOutput ) { CUtlVector< CBaseEntity * > pObjectList; CUtlVector< Vector > objectForce; bool hasLargeObject = false; // add a slot for static objects pObjectList.AddToTail( NULL ); objectForce.AddToTail( vec3_origin ); // add a slot for friendly objects pObjectList.AddToTail( NULL ); objectForce.AddToTail( vec3_origin ); CBaseCombatCharacter *pBCC = pInputOwnerEntity->MyCombatCharacterPointer(); IPhysicsFrictionSnapshot *pSnapshot = pObject->CreateFrictionSnapshot(); float objMass = pObject->GetMass(); while ( pSnapshot->IsValid() ) { float force = pSnapshot->GetNormalForce(); if ( force > 0.0f ) { IPhysicsObject *pOther = pSnapshot->GetObject(1); CBaseEntity *pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData()); if ( !pOtherEntity ) { // object was just deleted, but we still have a contact point this frame... // just assume it came from the world. pOtherEntity = GetWorldEntity(); } CBaseEntity *pOtherOwner = pOtherEntity; if ( pOtherEntity->GetOwnerEntity() ) { pOtherOwner = pOtherEntity->GetOwnerEntity(); } int outIndex = 0; if ( !pOther->IsMoveable() ) { outIndex = 0; } // NavIgnored objects are often being pushed by a friendly else if ( pBCC && (pBCC->IRelationType( pOtherOwner ) == D_LI || pOtherEntity->IsNavIgnored()) ) { outIndex = 1; } // player held objects do no stress else if ( pOther->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) { outIndex = 1; } else { if ( pOther->GetMass() >= VPHYSICS_LARGE_OBJECT_MASS ) { if ( pInputOwnerEntity->GetGroundEntity() != pOtherEntity) { hasLargeObject = true; } } // moveable, non-friendly // aggregate contacts over each object to avoid greater stress in multiple contact cases // NOTE: Contacts should be in order, so this shouldn't ever search, but just in case outIndex = pObjectList.Count(); for ( int i = pObjectList.Count()-1; i >= 2; --i ) { if ( pObjectList[i] == pOtherOwner ) { outIndex = i; break; } } if ( outIndex == pObjectList.Count() ) { pObjectList.AddToTail( pOtherOwner ); objectForce.AddToTail( vec3_origin ); } } if ( outIndex != 0 && pInputOwnerEntity->GetMoveType() != MOVETYPE_VPHYSICS && !IsPhysicallyControlled(pOtherEntity, pOther) ) { // UNDONE: Test this! This is to remove any shadow/shadow stress. The game should handle this with blocked/damage force = 0.0f; } Vector normal; pSnapshot->GetSurfaceNormal( normal ); objectForce[outIndex] += normal * force; } pSnapshot->NextFrictionData(); } pObject->DestroyFrictionSnapshot( pSnapshot ); pSnapshot = NULL; // clear out all friendly force objectForce[1].Init(); float sum = 0; Vector negativeForce = vec3_origin; Vector positiveForce = vec3_origin; Assert( pObjectList.Count() == objectForce.Count() ); for ( int objectIndex = pObjectList.Count()-1; objectIndex >= 0; --objectIndex ) { sum += objectForce[objectIndex].Length(); for ( int i = 0; i < 3; i++ ) { if ( objectForce[objectIndex][i] < 0 ) { negativeForce[i] -= objectForce[objectIndex][i]; } else { positiveForce[i] += objectForce[objectIndex][i]; } } } // "external" stress is two way (something pushes on the object and something else pushes back) // so the set of minimum values per component are the projections of the two-way force // "internal" stress is one way (the object is pushing against something OR something pushing back) // the momentum must have come from inside the object (gravity, controller, etc) Vector internalForce = vec3_origin; Vector externalForce = vec3_origin; for ( int i = 0; i < 3; i++ ) { if ( negativeForce[i] < positiveForce[i] ) { internalForce[i] = positiveForce[i] - negativeForce[i]; externalForce[i] = negativeForce[i]; } else { internalForce[i] = negativeForce[i] - positiveForce[i]; externalForce[i] = positiveForce[i]; } } // sum is kg in / s Vector gravVector; physenv->GetGravity( &gravVector ); float gravity = gravVector.Length(); if ( pInputOwnerEntity->GetMoveType() != MOVETYPE_VPHYSICS && pObject->IsMoveable() ) { Vector lastVel; lastVel.Init(); if ( pObject->GetShadowController() ) { pObject->GetShadowController()->GetLastImpulse( &lastVel ); } else { if ( ( pObject->GetCallbackFlags() & CALLBACK_IS_PLAYER_CONTROLLER ) ) { CBasePlayer *pPlayer = ToBasePlayer( pInputOwnerEntity ); IPhysicsPlayerController *pController = pPlayer ? pPlayer->GetPhysicsController() : NULL; if ( pController ) { pController->GetLastImpulse( &lastVel ); } } } // Work in progress... // Peek into the controller for this object. Look at the input velocity and make sure it's all // accounted for in the computed stress. If not, redistribute external to internal as it's // probably being reflected in a way we can't measure here. float inputLen = lastVel.Length() * (1.0f / physenv->GetSimulationTimestep()) * objMass; if ( inputLen > 0.0f ) { float internalLen = internalForce.Length(); if ( internalLen < inputLen ) { float ratio = internalLen / inputLen; Vector delta = internalForce * (1.0f - ratio); internalForce += delta; float deltaLen = delta.Length(); sum -= deltaLen; float extLen = VectorNormalize(externalForce) - deltaLen; if ( extLen < 0 ) { extLen = 0; } externalForce *= extLen; } } } float invGravity = gravity; if ( invGravity <= 0 ) { invGravity = 1.0f; } else { invGravity = 1.0f / invGravity; } sum *= invGravity; internalForce *= invGravity; externalForce *= invGravity; if ( !pObject->IsMoveable() ) { // the above algorithm will see almost all force as internal if the object is not moveable // (it doesn't push on anything else, so nothing is reciprocated) // exceptions for friction of a single other object with multiple contact points on this object // But the game wants to see it all as external because obviously the object can't move, so it can't have // internal stress externalForce = internalForce; internalForce.Init(); if ( !pObject->IsStatic() ) { sum += objMass; } } else { // assume object is at rest if ( sum > objMass ) { sum = objMass + (sum-objMass) * 0.5; } } if ( pOutput ) { pOutput->exertedStress = internalForce.Length(); pOutput->receivedStress = externalForce.Length(); pOutput->hasNonStaticStress = pObjectList.Count() > 2 ? true : false; pOutput->hasLargeObjectContact = hasLargeObject; } // sum is now kg return sum; }
void CWeaponGravityGun::SoundUpdate( void ) { int newState; if ( m_hObject ) newState = SS_LOCKEDON; else newState = SS_SCANNING; if ( newState != m_soundState ) { SoundStop(); m_soundState = newState; SoundStart(); } switch( m_soundState ) { case SS_SCANNING: break; case SS_LOCKEDON: { CPASAttenuationFilter filter( GetOwner() ); filter.MakeReliable(); float height = m_hObject->GetAbsOrigin().z - m_originalObjectPosition.z; // go from pitch 90 to 150 over a height of 500 int pitch = 90 + (int)UTIL_LineFraction( height, 0, 500, 60 ); CSoundParameters params; if ( GetParametersForSound( "Weapon_Physgun.LockedOn", params, NULL ) ) { EmitSound_t ep( params ); ep.m_nFlags = SND_CHANGE_VOL | SND_CHANGE_PITCH; ep.m_nPitch = pitch; EmitSound( filter, GetOwner()->entindex(), ep ); } // attenutate the movement sounds over 200 units of movement float distance = UTIL_LineFraction( m_movementLength, 0, 200, 1.0 ); // blend the "mass" sounds between 50 and 500 kg IPhysicsObject *pPhys = m_hObject->VPhysicsGetObject(); float fade = UTIL_LineFraction( pPhys->GetMass(), 50, 500, 1.0 ); if ( GetParametersForSound( "Weapon_Physgun.LightObject", params, NULL ) ) { EmitSound_t ep( params ); ep.m_nFlags = SND_CHANGE_VOL; ep.m_flVolume = fade * distance; EmitSound( filter, GetOwner()->entindex(), ep ); } if ( GetParametersForSound( "Weapon_Physgun.HeavyObject", params, NULL ) ) { EmitSound_t ep( params ); ep.m_nFlags = SND_CHANGE_VOL; ep.m_flVolume = (1.0 - fade) * distance; EmitSound( filter, GetOwner()->entindex(), ep ); } } break; } }
//----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseHelicopter::DoRotorPhysicsPush( const Vector &vecRotorOrigin, float flAltitude ) { CBaseEntity *pEntity = NULL; trace_t tr; // First, trace down and find out where the was is hitting the ground UTIL_TraceLine( vecRotorOrigin, vecRotorOrigin+Vector(0,0,-flAltitude), (MASK_SOLID_BRUSHONLY|CONTENTS_WATER), NULL, COLLISION_GROUP_NONE, &tr ); // Always raise the physics origin a bit Vector vecPhysicsOrigin = tr.endpos + Vector(0,0,64); // Debug if ( g_debug_basehelicopter.GetInt() == BASECHOPPER_DEBUG_WASH ) { NDebugOverlay::Cross3D( vecPhysicsOrigin, -Vector(16,16,16), Vector(16,16,16), 0, 255, 255, true, 0.1f ); } // Push entities that we've pushed before, and are still within range // Walk backwards because they may be removed if they're now out of range int iCount = m_hEntitiesPushedByWash.Count(); bool bWasPushingObjects = (iCount > 0); for ( int i = (iCount-1); i >= 0; i-- ) { if ( !DoWashPush( &(m_hEntitiesPushedByWash[i]), vecPhysicsOrigin ) ) { // Out of range now, so remove m_hEntitiesPushedByWash.Remove(i); } } if ( m_flRotorWashEntitySearchTime > gpGlobals->curtime ) return; // Any spare slots? iCount = m_hEntitiesPushedByWash.Count(); if ( iCount >= BASECHOPPER_WASH_MAX_OBJECTS ) return; // Find the lightest physics entity below us and add it to our list to push around CBaseEntity *pLightestEntity = NULL; float flLightestMass = 9999; while ((pEntity = gEntList.FindEntityInSphere(pEntity, vecPhysicsOrigin, BASECHOPPER_WASH_RADIUS )) != NULL) { IRotorWashShooter *pShooter = GetRotorWashShooter( pEntity ); if ( pEntity->IsEFlagSet( EFL_NO_ROTORWASH_PUSH )) continue; if ( pShooter || pEntity->GetMoveType() == MOVETYPE_VPHYSICS || (pEntity->VPhysicsGetObject() && !pEntity->IsPlayer()) ) { // Make sure it's not already in our wash bool bAlreadyPushing = false; for ( int i = 0; i < iCount; i++ ) { if ( m_hEntitiesPushedByWash[i].hEntity == pEntity ) { bAlreadyPushing = true; break; } } if ( bAlreadyPushing ) continue; float flMass = FLT_MAX; if ( pShooter ) { flMass = 1.0f; } else { // Don't try to push anything too big IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject(); if ( pPhysObject ) { flMass = pPhysObject->GetMass(); if ( flMass > BASECHOPPER_WASH_MAX_MASS ) continue; } } // Ignore anything bigger than the one we've already found if ( flMass > flLightestMass ) continue; Vector vecSpot = pEntity->BodyTarget( vecPhysicsOrigin ); // Don't push things too far below our starting point (helps reduce through-roof cases w/o doing a trace) if ( fabs( vecSpot.z - vecPhysicsOrigin.z ) > 96 ) continue; Vector vecToSpot = ( vecSpot - vecPhysicsOrigin ); vecToSpot.z = 0; float flDist = VectorNormalize( vecToSpot ); if ( flDist > BASECHOPPER_WASH_RADIUS ) continue; // Try to cast to the helicopter; if we can't, then we can't be hit. if ( pEntity->GetServerVehicle() ) { UTIL_TraceLine( vecSpot, vecPhysicsOrigin, MASK_SOLID_BRUSHONLY, pEntity, COLLISION_GROUP_NONE, &tr ); if ( tr.fraction != 1.0f ) continue; } flLightestMass = flMass; pLightestEntity = pEntity; washentity_t Wash; Wash.hEntity = pLightestEntity; Wash.flWashStartTime = gpGlobals->curtime; m_hEntitiesPushedByWash.AddToTail( Wash ); // Can we fit more after adding this one? No? Then we are done. iCount = m_hEntitiesPushedByWash.Count(); if ( iCount >= BASECHOPPER_WASH_MAX_OBJECTS ) break; } } // Handle sound. // If we just started pushing objects, ramp the blast sound up. if ( !bWasPushingObjects && m_hEntitiesPushedByWash.Count() ) { if ( m_pRotorBlast ) { CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); controller.SoundChangeVolume( m_pRotorBlast, 1.0, 1.0 ); } } else if ( bWasPushingObjects && m_hEntitiesPushedByWash.Count() == 0 ) { if ( m_pRotorBlast ) { // We just stopped pushing objects, so fade the blast sound out. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); controller.SoundChangeVolume( m_pRotorBlast, 0, 1.0 ); } } }
//----------------------------------------------------------------------------- // Purpose: Push a physics object in our wash. Return false if it's now out of our wash //----------------------------------------------------------------------------- bool CBaseHelicopter::DoWashPush( washentity_t *pWash, const Vector &vecWashOrigin ) { if ( !pWash || !pWash->hEntity.Get() ) return false; // Make sure the entity is still within our wash's radius CBaseEntity *pEntity = pWash->hEntity; // This can happen because we can dynamically turn this flag on and off if ( pEntity->IsEFlagSet( EFL_NO_ROTORWASH_PUSH )) return false; Vector vecSpot = pEntity->BodyTarget( vecWashOrigin ); Vector vecToSpot = ( vecSpot - vecWashOrigin ); vecToSpot.z = 0; float flDist = VectorNormalize( vecToSpot ); if ( flDist > BASECHOPPER_WASH_RADIUS ) return false; IRotorWashShooter *pShooter = GetRotorWashShooter( pEntity ); IPhysicsObject *pPhysObject; float flPushTime = (gpGlobals->curtime - pWash->flWashStartTime); flPushTime = clamp( flPushTime, 0, BASECHOPPER_WASH_RAMP_TIME ); float flWashAmount = RemapVal( flPushTime, 0, BASECHOPPER_WASH_RAMP_TIME, BASECHOPPER_WASH_PUSH_MIN, BASECHOPPER_WASH_PUSH_MAX ); if ( pShooter ) { Vector vecForce = (0.015f / 0.1f) * flWashAmount * vecToSpot * phys_pushscale.GetFloat(); pEntity = pShooter->DoWashPush( pWash->flWashStartTime, vecForce ); if ( !pEntity ) return true; washentity_t Wash; Wash.hEntity = pEntity; Wash.flWashStartTime = pWash->flWashStartTime; int i = m_hEntitiesPushedByWash.AddToTail( Wash ); pWash = &m_hEntitiesPushedByWash[i]; pPhysObject = pEntity->VPhysicsGetObject(); if ( !pPhysObject ) return true; } else { // Airboat gets special treatment if ( FClassnameIs( pEntity, "prop_vehicle_airboat" ) ) { DoWashPushOnAirboat( pEntity, vecToSpot, flWashAmount ); return true; } pPhysObject = pEntity->VPhysicsGetObject(); if ( !pPhysObject ) return false; } // Push it away from the center of the wash float flMass = pPhysObject->GetMass(); // This used to be mass independent, which is a bad idea because it blows 200kg engine blocks // as much as it blows cardboard and soda cans. Make this force mass-independent, but clamp at // 30kg. flMass = MIN( flMass, 30.0f ); Vector vecForce = (0.015f / 0.1f) * flWashAmount * flMass * vecToSpot * phys_pushscale.GetFloat(); pEntity->VPhysicsTakeDamage( CTakeDamageInfo( this, this, vecForce, vecWashOrigin, flWashAmount, DMG_BLAST ) ); // Debug if ( g_debug_basehelicopter.GetInt() == BASECHOPPER_DEBUG_WASH ) { NDebugOverlay::Cross3D( pEntity->GetAbsOrigin(), -Vector(4,4,4), Vector(4,4,4), 255, 0, 0, true, 0.1f ); NDebugOverlay::Line( pEntity->GetAbsOrigin(), pEntity->GetAbsOrigin() + vecForce, 255, 255, 0, true, 0.1f ); IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject(); Msg("Pushed %s (index %d) (mass %f) with force %f (min %.2f max %.2f) at time %.2f\n", pEntity->GetClassname(), pEntity->entindex(), pPhysObject->GetMass(), flWashAmount, BASECHOPPER_WASH_PUSH_MIN * flMass, BASECHOPPER_WASH_PUSH_MAX * flMass, gpGlobals->curtime ); } // If we've pushed this thing for some time, remove it to give us a chance to find lighter things nearby if ( flPushTime > 2.0 ) return false; return true; }
void PhysComputeSlideDirection( IPhysicsObject *pPhysics, const Vector &inputVelocity, const AngularImpulse &inputAngularVelocity, Vector *pOutputVelocity, Vector *pOutputAngularVelocity, float minMass ) { Vector velocity = inputVelocity; AngularImpulse angVel = inputAngularVelocity; Vector pos; IPhysicsFrictionSnapshot *pSnapshot = pPhysics->CreateFrictionSnapshot(); while ( pSnapshot->IsValid() ) { IPhysicsObject *pOther = pSnapshot->GetObject( 1 ); if ( !pOther->IsMoveable() || pOther->GetMass() > minMass ) { Vector normal; pSnapshot->GetSurfaceNormal( normal ); // BUGBUG: Figure out the correct rotation clipping equation if ( pOutputAngularVelocity ) { angVel = normal * DotProduct( angVel, normal ); #if 0 pSnapshot->GetContactPoint( point ); Vector point, dummy; AngularImpulse angularClip, clip2; pPhysics->CalculateVelocityOffset( normal, point, dummy, angularClip ); VectorNormalize( angularClip ); float proj = DotProduct( angVel, angularClip ); if ( proj > 0 ) { angVel -= angularClip * proj; } CrossProduct( angularClip, normal, clip2 ); proj = DotProduct( angVel, clip2 ); if ( proj > 0 ) { angVel -= clip2 * proj; } //NDebugOverlay::Line( point, point - normal * 20, 255, 0, 0, true, 0.1 ); #endif } // Determine how far along plane to slide based on incoming direction. // NOTE: Normal points away from this object float proj = DotProduct( velocity, normal ); if ( proj > 0.0f ) { velocity -= normal * proj; } } pSnapshot->NextFrictionData(); } pPhysics->DestroyFrictionSnapshot( pSnapshot ); //NDebugOverlay::Line( pos, pos + unitVel * 20, 0, 0, 255, true, 0.1 ); if ( pOutputVelocity ) { *pOutputVelocity = velocity; } if ( pOutputAngularVelocity ) { *pOutputAngularVelocity = angVel; } }
CBaseEntity *CreateServerRagdoll( CBaseAnimating *pAnimating, int forceBone, const CTakeDamageInfo &info, int collisionGroup, bool bUseLRURetirement ) { SyncAnimatingWithPhysics( pAnimating ); CRagdollProp *pRagdoll = (CRagdollProp *)CBaseEntity::CreateNoSpawn( "prop_ragdoll", pAnimating->GetAbsOrigin(), vec3_angle, NULL ); pRagdoll->CopyAnimationDataFrom( pAnimating ); pRagdoll->SetOwnerEntity( pAnimating ); pRagdoll->InitRagdollAnimation(); matrix3x4_t pBoneToWorld[MAXSTUDIOBONES], pBoneToWorldNext[MAXSTUDIOBONES]; const float dt = 0.1f; // Copy over dissolve state... if ( pAnimating->IsEFlagSet( EFL_NO_DISSOLVE ) ) { pRagdoll->AddEFlags( EFL_NO_DISSOLVE ); } // NOTE: This currently is only necessary to prevent manhacks from // colliding with server ragdolls they kill pRagdoll->SetKiller( info.GetInflictor() ); pRagdoll->SetSourceClassName( pAnimating->GetClassname() ); // NPC_STATE_DEAD npc's will have their COND_IN_PVS cleared, so this needs to force SetupBones to happen unsigned short fPrevFlags = pAnimating->GetBoneCacheFlags(); pAnimating->SetBoneCacheFlags( BCF_NO_ANIMATION_SKIP ); // UNDONE: Extract velocity from bones via animation (like we do on the client) // UNDONE: For now, just move each bone by the total entity velocity if set. pAnimating->SetupBones( pBoneToWorld, BONE_USED_BY_ANYTHING ); // Reset previous bone flags pAnimating->ClearBoneCacheFlags( BCF_NO_ANIMATION_SKIP ); pAnimating->SetBoneCacheFlags( fPrevFlags ); memcpy( pBoneToWorldNext, pBoneToWorld, sizeof(pBoneToWorld) ); Vector vel = pAnimating->GetAbsVelocity(); if ( vel.LengthSqr() > 0 ) { int numbones = pAnimating->GetModelPtr()->numbones(); for ( int i = 0; i < numbones; i++ ) { Vector pos; MatrixGetColumn( pBoneToWorldNext[i], 3, pos ); pos += vel * dt; MatrixSetColumn( pos, 3, pBoneToWorldNext[i] ); } } // Is this a vehicle / NPC collision? if ( (info.GetDamageType() & DMG_VEHICLE) && pAnimating->MyNPCPointer() ) { // init the ragdoll with no forces pRagdoll->InitRagdoll( vec3_origin, -1, vec3_origin, pBoneToWorld, pBoneToWorldNext, dt, collisionGroup, true ); // apply vehicle forces // Get a list of bones with hitboxes below the plane of impact int boxList[128]; Vector normal(0,0,-1); int count = pAnimating->GetHitboxesFrontside( boxList, ARRAYSIZE(boxList), normal, DotProduct( normal, info.GetDamagePosition() ) ); // distribute force over mass of entire character float massScale = Studio_GetMass(pAnimating->GetModelPtr()); massScale = clamp( massScale, 1, 1e4 ); massScale = 1 / massScale; // distribute the force // BUGBUG: This will hit the same bone twice if it has two hitboxes!!!! ragdoll_t *pRagInfo = pRagdoll->GetRagdoll(); for ( int i = 0; i < count; i++ ) { int physBone = pAnimating->GetPhysicsBone( pAnimating->GetHitboxBone( boxList[i] ) ); IPhysicsObject *pPhysics = pRagInfo->list[physBone].pObject; pPhysics->ApplyForceCenter( info.GetDamageForce() * pPhysics->GetMass() * massScale ); } } else { pRagdoll->InitRagdoll( info.GetDamageForce(), forceBone, info.GetDamagePosition(), pBoneToWorld, pBoneToWorldNext, dt, collisionGroup, true ); } // Are we dissolving? if ( pAnimating->IsDissolving() ) { pRagdoll->TransferDissolveFrom( pAnimating ); } else if ( bUseLRURetirement ) { pRagdoll->AddSpawnFlags( SF_RAGDOLLPROP_USE_LRU_RETIREMENT ); s_RagdollLRU.MoveToTopOfLRU( pRagdoll ); } // Tracker 22598: If we don't set the OBB mins/maxs to something valid here, then the client will have a zero sized hull // for the ragdoll for one frame until Vphysics updates the real obb bounds after the first simulation frame. Having // a zero sized hull makes the ragdoll think it should be faded/alpha'd to zero for a frame, so you get a blink where // the ragdoll doesn't draw initially. Vector mins, maxs; mins = pAnimating->CollisionProp()->OBBMins(); maxs = pAnimating->CollisionProp()->OBBMaxs(); pRagdoll->CollisionProp()->SetCollisionBounds( mins, maxs ); return pRagdoll; }
Vector QUA_helicopter::CalcDamageForceVector( const CTakeDamageInfo &info ) { // Already have a damage force in the data, use that. if (info.GetDamageForce() != vec3_origin || (info.GetDamageType() & /*DMG_NO_PHYSICS_FORCE*/DMG_BLAST)) { //if( info.GetDamageType() & DMG_BLAST ) //{ // Fudge blast forces a little bit, so that each // victim gets a slightly different trajectory. // This simulates features that usually vary from // person-to-person variables such as bodyweight, // which are all indentical for characters using the same model. float scale = random->RandomFloat( 0.85, 1.15 ); Vector force = info.GetDamageForce(); force.x *= scale; force.y *= scale; // Try to always exaggerate the upward force because we've got pretty harsh gravity force.z *= (force.z > 0) ? 1.15 : scale; return force; //} return info.GetDamageForce(); } CBaseEntity *pForce = info.GetInflictor(); if ( !pForce ) { pForce = info.GetAttacker(); } if ( pForce ) { // Calculate an impulse large enough to push a 75kg man 4 in/sec per point of damage float forceScale = info.GetDamage() * 75 * 4; Vector forceVector; // If the damage is a blast, point the force vector higher than usual, this gives // the ragdolls a bodacious "really got blowed up" look. if( info.GetDamageType() & DMG_BLAST ) { // exaggerate the force from explosions a little (37.5%) forceVector = (GetLocalOrigin() + Vector(0, 0, WorldAlignSize().z) ) - pForce->GetLocalOrigin(); VectorNormalize(forceVector); forceVector *= 1.375f; } else { // taking damage from self? Take a little random force, but still try to collapse on the spot. if ( this == pForce ) { forceVector.x = random->RandomFloat( -1.0f, 1.0f ); forceVector.y = random->RandomFloat( -1.0f, 1.0f ); forceVector.z = 0.0; forceScale = random->RandomFloat( 1000.0f, 2000.0f ); } else { // UNDONE: Collision forces are baked in to CTakeDamageInfo now // UNDONE: Is this MOVETYPE_VPHYSICS code still necessary? if ( pForce->GetMoveType() == MOVETYPE_VPHYSICS ) { // killed by a physics object IPhysicsObject *pPhysics = VPhysicsGetObject(); if ( !pPhysics ) { pPhysics = pForce->VPhysicsGetObject(); } pPhysics->GetVelocity( &forceVector, NULL ); forceScale = pPhysics->GetMass(); } else { forceVector = GetLocalOrigin() - pForce->GetLocalOrigin(); VectorNormalize(forceVector); } } } return forceVector * forceScale; } return vec3_origin; }
//--------------------------------------------------------- //--------------------------------------------------------- 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 ); } } }
//----------------------------------------------------------------------------- // Purpose: // // // Output : //----------------------------------------------------------------------------- CBaseEntity *CNPC_RollerDozer::FindDebris( void ) { if( !m_pHintNode ) { // Detect rubbish near a hint node. CAI_Hint* pHintNode = CAI_Hint::FindHint( this, HINT_ROLLER_CLEANUP_POINT, 0, 1024 ); if( pHintNode) { // Search around the hint node for debris that should be cleared. Vector vecHintNodeOrigin; // Get hint node position vecHintNodeOrigin; pHintNode->GetPosition(this,&vecHintNodeOrigin); CBaseEntity *pList[ 16 ]; Vector vecDeltaUp( 200, 200, 64 ); Vector vecDeltaDown( 200, 200, 10 ); int i; IPhysicsObject *pPhysObj; int count = UTIL_EntitiesInBox( pList, 16, vecHintNodeOrigin - vecDeltaDown, vecHintNodeOrigin + vecDeltaUp, 0 ); float m_flHeaviestMass = 0; CBaseEntity *pHeaviest = NULL; for( i = 0 ; i < count ; i++ ) { pPhysObj = pList[ i ]->VPhysicsGetObject(); if( !pPhysObj || FClassnameIs( pList[ i ], "npc_rollerdozer" ) ) { // Only consider physics objects. Exclude rollers. continue; } if( pPhysObj->GetMass() <= 400 ) { if( pPhysObj->GetMass() > m_flHeaviestMass ) { m_flHeaviestMass = pPhysObj->GetMass(); pHeaviest = pList[ i ]; } /* // Report to the cleanup point and doze this piece of debris away. SetCondition( COND_ROLLERDOZER_FOUND_DEBRIS ); m_vecCleanupPoint = vecHintNodeOrigin; return pList[ i ]; */ } } if( pHeaviest ) { SetCondition( COND_ROLLERDOZER_FOUND_DEBRIS ); //NDebugOverlay::Line( GetLocalOrigin(), pHeaviest->GetLocalOrigin(), 255,255,0, true, 3 ); m_vecCleanupPoint = vecHintNodeOrigin; return pHeaviest; } } } return NULL; }
void CNPC_Roller::Unstick( void ) { CBaseEntity *pList[ 16 ]; IPhysicsObject *pPhysObj; int i; Vector vecDirToEnemy; Vector vecDirToObject; m_flWaitFinished = gpGlobals->curtime; int count = UTIL_EntitiesInBox( pList, 16, GetAbsOrigin() - vecDelta, GetAbsOrigin() + vecDelta, 0 ); m_vecUnstickDirection = vec3_origin; for( i = 0 ; i < count ; i++ ) { pPhysObj = pList[ i ]->VPhysicsGetObject(); if( !pPhysObj || pList[ i ]->m_iClassname == m_iClassname ) { // Only consider physics objects. Exclude rollers. continue; } if( pPhysObj->GetMass() <= ROLLER_MAX_PUSH_MASS ) { // Try to bash this physics object. trace_t tr; AI_TraceLine( GetAbsOrigin(), pList[ i ]->GetAbsOrigin(), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); if( tr.fraction == 1.0 || tr.m_pEnt == pList[ i ] ) { // Roll towards this item if the trace hits nothing, or // the trace hits the object. Vector vecBashDir; vecBashDir = pList[ i ]->GetAbsOrigin() - GetAbsOrigin(); VectorNormalize( vecBashDir ); vecBashDir.z = 0.0; //NDebugOverlay::Line( GetAbsOrigin(), pList[ i ]->GetAbsOrigin(), 0,255,0, true, 2 ); m_vecUnstickDirection = vecBashDir * 80; return; } } } // No physics objects. Just pick a direction with some clearance and go there. #define ROLLER_UNSTICK_DIST 80 Vector vecDirections[ 4 ] = { Vector( 0, ROLLER_UNSTICK_DIST, 0 ), Vector( ROLLER_UNSTICK_DIST, 0, 0 ), Vector( 0, -ROLLER_UNSTICK_DIST, 0 ), Vector( -ROLLER_UNSTICK_DIST, 0, 0 ) }; trace_t tr; for( i = 0 ; i < 4 ; i++ ) { AI_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vecDirections[ i ], MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); if( tr.fraction == 1.0 ) { m_vecUnstickDirection = vecDirections[ i ]; //NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin() + m_vecUnstickDirection, 255,255,0, true, 2 ); // Roll in this direction for a couple of seconds. Msg( "unsticking!\n" ); return; } } }
//========================================================= // SonicAttack //========================================================= void CNPC_Houndeye::SonicAttack ( void ) { EmitSound( "NPC_Houndeye.SonicAttack" ); float flAdjustedDamage; float flDist; CBroadcastRecipientFilter filter2; te->BeamRingPoint(filter2, 0.0, GetAbsOrigin(), //origin 16, //start radius HOUNDEYE_MAX_ATTACK_RADIUS,//end radius m_iSpriteTexture, //texture 0, //halo index 0, //start frame 0, //framerate 0.2, //life 24, //width 16, //spread 0, //amplitude 188, //r 220, //g 255, //b 192, //a 0 //speed ); CBroadcastRecipientFilter filter3; te->BeamRingPoint(filter3, 0.0, GetAbsOrigin(), //origin 16, //start radius HOUNDEYE_MAX_ATTACK_RADIUS / 2, //end radius m_iSpriteTexture, //texture 0, //halo index 0, //start frame 0, //framerate 0.2, //life 24, //width 16, //spread 0, //amplitude 188, //r 220, //g 255, //b 192, //a 0 //speed ); CBaseEntity *pEntity = NULL; // iterate on all entities in the vicinity. while ((pEntity = gEntList.FindEntityInSphere(pEntity, GetAbsOrigin(), HOUNDEYE_MAX_ATTACK_RADIUS)) != NULL) { if (pEntity->m_takedamage != DAMAGE_NO) { if (!FClassnameIs(pEntity, "npc_houndeye")) {// houndeyes don't hurt other houndeyes with their attack // houndeyes do FULL damage if the ent in question is visible. Half damage otherwise. // This means that you must get out of the houndeye's attack range entirely to avoid damage. // Calculate full damage first flAdjustedDamage = sk_Houndeye_dmg_blast.GetFloat(); flDist = (pEntity->WorldSpaceCenter() - GetAbsOrigin()).Length(); flAdjustedDamage -= (flDist / HOUNDEYE_MAX_ATTACK_RADIUS) * flAdjustedDamage; if (!FVisible(pEntity)) { if (pEntity->IsPlayer()) { // if this entity is a client, and is not in full view, inflict half damage. We do this so that players still // take the residual damage if they don't totally leave the houndeye's effective radius. We restrict it to clients // so that monsters in other parts of the level don't take the damage and get pissed. flAdjustedDamage *= 0.5; } else if (!FClassnameIs(pEntity, "func_breakable") && !FClassnameIs(pEntity, "func_pushable")) { // do not hurt nonclients through walls, but allow damage to be done to breakables flAdjustedDamage = 0; } } //ALERT ( at_aiconsole, "Damage: %f\n", flAdjustedDamage ); if (flAdjustedDamage > 0) { CTakeDamageInfo info(this, this, flAdjustedDamage, DMG_SONIC | DMG_ALWAYSGIB); CalculateExplosiveDamageForce(&info, (pEntity->GetAbsOrigin() - GetAbsOrigin()), pEntity->GetAbsOrigin()); pEntity->TakeDamage(info); if ((pEntity->GetAbsOrigin() - GetAbsOrigin()).Length2D() <= HOUNDEYE_MAX_ATTACK_RADIUS) { if (pEntity->GetMoveType() == MOVETYPE_VPHYSICS || (pEntity->VPhysicsGetObject() && !pEntity->IsPlayer())) { IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject(); if (pPhysObject) { float flMass = pPhysObject->GetMass(); if (flMass <= HOUNDEYE_TOP_MASS) { // Increase the vertical lift of the force Vector vecForce = info.GetDamageForce(); vecForce.z *= 2.0f; info.SetDamageForce(vecForce); pEntity->VPhysicsTakeDamage(info); } } } } } } } } }
//----------------------------------------------------------------------------- // 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(); } }
bool CNPC_Dog::FindPhysicsObject( const char *pPickupName, CBaseEntity *pIgnore ) { CBaseEntity *pEnt = NULL; CBaseEntity *pNearest = NULL; float flDist; IPhysicsObject *pPhysObj = NULL; float flNearestDist = 99999; if ( pPickupName != NULL && strlen( pPickupName ) > 0 ) { pEnt = gEntList.FindEntityByName( NULL, pPickupName ); if ( m_hUnreachableObjects.Find( pEnt ) == -1 ) { m_bHasObject = false; m_hPhysicsEnt = pEnt; return true; } } while ( ( pEnt = gEntList.FindEntityByClassname( pEnt, "prop_physics" ) ) != NULL ) { //We don't want this one. if ( pEnt == pIgnore ) continue; if ( m_hUnreachableObjects.Find( pEnt ) != -1 ) continue; pPhysObj = pEnt->VPhysicsGetObject(); if( pPhysObj == NULL ) continue; if ( pPhysObj->GetMass() > DOG_MAX_THROW_MASS ) continue; Vector center = pEnt->WorldSpaceCenter(); flDist = UTIL_DistApprox2D( GetAbsOrigin(), center ); vcollide_t *pCollide = modelinfo->GetVCollide( pEnt->GetModelIndex() ); if ( pCollide == NULL ) continue; if ( pPhysObj->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) continue; if ( pPhysObj->IsMoveable() == false ) continue; if ( pEnt->GetCollisionGroup() == COLLISION_GROUP_DEBRIS || pEnt->GetCollisionGroup() == COLLISION_GROUP_INTERACTIVE_DEBRIS ) continue; if ( center.z > EyePosition().z ) continue; if ( flDist >= flNearestDist ) continue; if ( FVisible( pEnt ) == false ) continue; pNearest = pEnt; flNearestDist = flDist; } m_bHasObject = false; m_hPhysicsEnt = pNearest; if ( dog_debug.GetBool() == true ) { if ( pNearest ) NDebugOverlay::Box( pNearest->WorldSpaceCenter(), pNearest->CollisionProp()->OBBMins(), pNearest->CollisionProp()->OBBMaxs(), 255, 0, 255, true, 3 ); } if( m_hPhysicsEnt == NULL ) { return false; } else { return true; } }
//Actual work code IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ) { C_BaseEntity *pEnt = ClientEntityList().GetBaseEntityFromHandle( pHandleEntity->GetRefEHandle() ); if ( pEnt == NULL ) return ITERATION_CONTINUE; C_BaseAnimating *pModel = static_cast< C_BaseAnimating * >( pEnt ); if ( pModel == NULL ) return ITERATION_CONTINUE; trace_t tr; enginetrace->ClipRayToEntity( m_rayShot, MASK_SHOT, pModel, &tr ); IPhysicsObject *pPhysicsObject = NULL; //Find the real object we hit. if( tr.physicsbone >= 0 ) { if ( pModel->m_pRagdoll ) { CRagdoll *pCRagdoll = dynamic_cast < CRagdoll * > ( pModel->m_pRagdoll ); if ( pCRagdoll ) { ragdoll_t *pRagdollT = pCRagdoll->GetRagdoll(); if ( tr.physicsbone < pRagdollT->listCount ) { pPhysicsObject = pRagdollT->list[tr.physicsbone].pObject; } } } } if ( pPhysicsObject == NULL ) return ITERATION_CONTINUE; if ( tr.fraction < 1.0 ) { IPhysicsObject *pReference = GetWorldPhysObject(); if ( pReference == NULL || pPhysicsObject == NULL ) return ITERATION_CONTINUE; float flMass = pPhysicsObject->GetMass(); pPhysicsObject->SetMass( flMass * 2 ); constraint_ballsocketparams_t ballsocket; ballsocket.Defaults(); pReference->WorldToLocal( &ballsocket.constraintPosition[0], m_vWorld ); pPhysicsObject->WorldToLocal( &ballsocket.constraintPosition[1], tr.endpos ); physenv->CreateBallsocketConstraint( pReference, pPhysicsObject, NULL, ballsocket ); //Play a sound CPASAttenuationFilter filter( pEnt ); EmitSound_t ep; ep.m_nChannel = CHAN_VOICE; ep.m_pSoundName = "Weapon_Crossbow.BoltSkewer"; ep.m_flVolume = 1.0f; ep.m_SoundLevel = SNDLVL_NORM; ep.m_pOrigin = &pEnt->GetAbsOrigin(); C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, ep ); return ITERATION_STOP; } return ITERATION_CONTINUE; }
void CNPC_Dog::ThrowObject( const char *pAttachmentName ) { if ( m_hPhysicsEnt ) { m_bHasObject = false; IPhysicsObject *pPhysObj = m_hPhysicsEnt->VPhysicsGetObject(); if ( pPhysObj ) { Vector vGunPos; QAngle angGunAngles; AngularImpulse angVelocity = RandomAngularImpulse( -250 , -250 ) / pPhysObj->GetMass(); InvalidateBoneCache(); int iAttachment = LookupAttachment( pAttachmentName ); if ( iAttachment == 0 ) iAttachment = m_iPhysGunAttachment; GetAttachment( iAttachment, vGunPos, angGunAngles ); pPhysObj->Wake(); if ( pPhysObj->GetShadowController() ) { m_hPhysicsEnt->SetParent( NULL ); m_hPhysicsEnt->SetMoveType( (MoveType_t)m_iContainerMoveType ); m_hPhysicsEnt->SetOwnerEntity( this ); pPhysObj->RemoveShadowController(); pPhysObj->SetPosition( m_hPhysicsEnt->GetLocalOrigin(), m_hPhysicsEnt->GetLocalAngles(), true ); pPhysObj->RecheckCollisionFilter(); pPhysObj->RecheckContactPoints(); } if ( m_hThrowTarget == NULL ) #ifdef SecobMod__Enable_Fixed_Multiplayer_AI m_hThrowTarget = UTIL_GetNearestVisiblePlayer(this); #else m_hThrowTarget = AI_GetSinglePlayer(); #endif //SecobMod__Enable_Fixed_Multiplayer_AI Vector vThrowDirection; if ( m_hThrowTarget ) { Vector vThrowOrigin = m_hThrowTarget->GetAbsOrigin(); if ( m_hThrowTarget->IsPlayer() ) vThrowOrigin = vThrowOrigin + Vector( random->RandomFloat( -128, 128 ), random->RandomFloat( -128, 128 ), 0 ); Vector vecToss = VecCheckToss( this, vGunPos, vThrowOrigin, m_flThrowArcModifier, 1.0f, true ); if( vecToss == vec3_origin ) { // Fix up an impossible throw so dog will at least toss the box in the target's general direction instead of dropping it. // Also toss it up in the air so it will fall down and break. (Just throw the box up at a 45 degree angle) Vector forward, up; GetVectors( &forward, NULL, &up ); vecToss = forward + up; VectorNormalize( vecToss ); vecToss *= pPhysObj->GetMass() * 30.0f; } vThrowDirection = vecToss + ( m_hThrowTarget->GetSmoothedVelocity() / 2 ); Vector vLinearDrag; Vector unitVel = vThrowDirection; VectorNormalize( unitVel ); float flTest = 1000 / vThrowDirection.Length(); float flDrag = pPhysObj->CalculateLinearDrag( vThrowDirection ); vThrowDirection = vThrowDirection + ( unitVel * ( flDrag * flDrag ) ) / flTest; pPhysObj->SetVelocity( &vThrowDirection, &angVelocity ); m_flTimeToCatch = gpGlobals->curtime + dog_max_wait_time.GetFloat(); //Don't start pulling until the object is away from me. //We base the time on the throw velocity. m_flTimeToPull = gpGlobals->curtime + ( 1000 / vThrowDirection.Length() ); } //Fire Output! m_OnThrow.FireOutput( this, this ); ClearBeams(); if ( m_bBeamEffects == true ) { EmitSound( "Weapon_PhysCannon.Launch" ); CBeam *pBeam = CBeam::BeamCreate( "sprites/orangelight1.vmt", 1.8 ); if ( pBeam != NULL ) { pBeam->PointEntInit( m_hPhysicsEnt->WorldSpaceCenter(), this ); pBeam->SetEndAttachment( m_iPhysGunAttachment ); pBeam->SetWidth( 6.4 ); pBeam->SetEndWidth( 12.8 ); pBeam->SetBrightness( 255 ); pBeam->SetColor( 255, 255, 255 ); pBeam->LiveForTime( 0.2f ); pBeam->RelinkBeam(); pBeam->SetNoise( 2 ); } Vector shotDir = ( m_hPhysicsEnt->WorldSpaceCenter() - vGunPos ); VectorNormalize( shotDir ); CPVSFilter filter( m_hPhysicsEnt->WorldSpaceCenter() ); te->GaussExplosion( filter, 0.0f, m_hPhysicsEnt->WorldSpaceCenter() - ( shotDir * 4.0f ), RandomVector(-1.0f, 1.0f), 0 ); } } } }