//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Physics cannon // //=============================================================================// #include "cbase.h" #ifdef CLIENT_DLL #include "c_hl2mp_player.h" #include "vcollide_parse.h" #include "engine/ivdebugoverlay.h" #include "iviewrender_beams.h" #include "beamdraw.h" #include "c_te_effect_dispatch.h" #include "model_types.h" #include "clienteffectprecachesystem.h" #include "fx_interpvalue.h" #else #include "hl2mp_player.h" #include "soundent.h" #include "ndebugoverlay.h" #include "ai_basenpc.h" #include "player_pickup.h" #include "physics_prop_ragdoll.h" #include "globalstate.h" #include "props.h" #include "te_effect_dispatch.h" #include "util.h" #endif #include "gamerules.h" #include "soundenvelope.h" #include "engine/IEngineSound.h" #include "physics.h" #include "in_buttons.h" #include "IEffects.h" #include "shake.h" #include "beam_shared.h" #include "Sprite.h" #include "weapon_physcannon.h" #include "physics_saverestore.h" #include "movevars_shared.h" #include "weapon_hl2mpbasehlmpcombatweapon.h" #include "vphysics/friction.h" #include "debugoverlay_shared.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define SPRITE_SCALE 128.0f static const char *s_pWaitForUpgradeContext = "WaitForUpgrade"; ConVar g_debug_physcannon( "g_debug_physcannon", "0", FCVAR_REPLICATED | FCVAR_CHEAT ); ConVar physcannon_minforce( "physcannon_minforce", "700", FCVAR_REPLICATED | FCVAR_CHEAT ); ConVar physcannon_maxforce( "physcannon_maxforce", "1500", FCVAR_REPLICATED | FCVAR_CHEAT ); ConVar physcannon_maxmass( "physcannon_maxmass", "250", FCVAR_REPLICATED | FCVAR_CHEAT ); ConVar physcannon_tracelength( "physcannon_tracelength", "250", FCVAR_REPLICATED | FCVAR_CHEAT ); ConVar physcannon_chargetime("physcannon_chargetime", "2", FCVAR_REPLICATED | FCVAR_CHEAT ); ConVar physcannon_pullforce( "physcannon_pullforce", "4000", FCVAR_REPLICATED | FCVAR_CHEAT ); ConVar physcannon_cone( "physcannon_cone", "0.97", FCVAR_REPLICATED | FCVAR_CHEAT ); ConVar physcannon_ball_cone( "physcannon_ball_cone", "0.997", FCVAR_REPLICATED | FCVAR_CHEAT ); ConVar player_throwforce( "player_throwforce", "1000", FCVAR_REPLICATED | FCVAR_CHEAT ); #ifndef CLIENT_DLL extern ConVar hl2_normspeed; extern ConVar hl2_walkspeed; #endif #define PHYSCANNON_BEAM_SPRITE "sprites/orangelight1.vmt" #define PHYSCANNON_BEAM_SPRITE_NOZ "sprites/orangelight1_noz.vmt" #define PHYSCANNON_GLOW_SPRITE "sprites/glow04_noz" #define PHYSCANNON_ENDCAP_SPRITE "sprites/orangeflare1" #define PHYSCANNON_CENTER_GLOW "sprites/orangecore1" #define PHYSCANNON_BLAST_SPRITE "sprites/orangecore2" #ifdef CLIENT_DLL //Precahce the effects CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectPhysCannon ) CLIENTEFFECT_MATERIAL( "sprites/orangelight1" ) CLIENTEFFECT_MATERIAL( "sprites/orangelight1_noz" ) CLIENTEFFECT_MATERIAL( PHYSCANNON_GLOW_SPRITE ) CLIENTEFFECT_MATERIAL( PHYSCANNON_ENDCAP_SPRITE ) CLIENTEFFECT_MATERIAL( PHYSCANNON_CENTER_GLOW ) CLIENTEFFECT_MATERIAL( PHYSCANNON_BLAST_SPRITE ) CLIENTEFFECT_REGISTER_END() #endif // CLIENT_DLL #ifndef CLIENT_DLL void PhysCannonBeginUpgrade( CBaseAnimating *pAnim ) { } bool PlayerHasMegaPhysCannon( void ) { return false; } bool PhysCannonAccountableForObject( CBaseCombatWeapon *pPhysCannon, CBaseEntity *pObject ) { // BRJ: FIXME! This can't be implemented trivially, so I'm leaving it to Steve or Adrian Assert( 0 ); return false; } #endif //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // this will hit skip the pass entity, but not anything it owns // (lets player grab own grenades) class CTraceFilterNoOwnerTest : public CTraceFilterSimple { public: DECLARE_CLASS( CTraceFilterNoOwnerTest, CTraceFilterSimple ); CTraceFilterNoOwnerTest( const IHandleEntity *passentity, int collisionGroup ) : CTraceFilterSimple( NULL, collisionGroup ), m_pPassNotOwner(passentity) { } virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) { if ( pHandleEntity != m_pPassNotOwner ) return BaseClass::ShouldHitEntity( pHandleEntity, contentsMask ); return false; } protected: const IHandleEntity *m_pPassNotOwner; }; static void MatrixOrthogonalize( matrix3x4_t &matrix, int column ) { Vector columns[3]; int i; for ( i = 0; i < 3; i++ ) { MatrixGetColumn( matrix, i, columns[i] ); } int index0 = column; int index1 = (column+1)%3; int index2 = (column+2)%3; columns[index2] = CrossProduct( columns[index0], columns[index1] ); columns[index1] = CrossProduct( columns[index2], columns[index0] ); VectorNormalize( columns[index2] ); VectorNormalize( columns[index1] ); MatrixSetColumn( columns[index1], index1, matrix ); MatrixSetColumn( columns[index2], index2, matrix ); } #define SIGN(x) ( (x) < 0 ? -1 : 1 ) static QAngle AlignAngles( const QAngle &angles, float cosineAlignAngle ) { matrix3x4_t alignMatrix; AngleMatrix( angles, alignMatrix ); // NOTE: Must align z first for ( int j = 3; --j >= 0; ) { Vector vec; MatrixGetColumn( alignMatrix, j, vec ); for ( int i = 0; i < 3; i++ ) { if ( fabs(vec[i]) > cosineAlignAngle ) { vec[i] = SIGN(vec[i]); vec[(i+1)%3] = 0; vec[(i+2)%3] = 0; MatrixSetColumn( vec, j, alignMatrix ); MatrixOrthogonalize( alignMatrix, j ); break; } } } QAngle out; MatrixAngles( alignMatrix, out ); return out; } static void TraceCollideAgainstBBox( const CPhysCollide *pCollide, const Vector &start, const Vector &end, const QAngle &angles, const Vector &boxOrigin, const Vector &mins, const Vector &maxs, trace_t *ptr ) { physcollision->TraceBox( boxOrigin, boxOrigin + (start-end), mins, maxs, pCollide, start, angles, ptr ); if ( ptr->DidHit() ) { ptr->endpos = start * (1-ptr->fraction) + end * ptr->fraction; ptr->startpos = start; ptr->plane.dist = -ptr->plane.dist; ptr->plane.normal *= -1; } } //----------------------------------------------------------------------------- // Purpose: Computes a local matrix for the player clamped to valid carry ranges //----------------------------------------------------------------------------- // when looking level, hold bottom of object 8 inches below eye level #define PLAYER_HOLD_LEVEL_EYES -8 // when looking down, hold bottom of object 0 inches from feet #define PLAYER_HOLD_DOWN_FEET 2 // when looking up, hold bottom of object 24 inches above eye level #define PLAYER_HOLD_UP_EYES 24 // use a +/-30 degree range for the entire range of motion of pitch #define PLAYER_LOOK_PITCH_RANGE 30 // player can reach down 2ft below his feet (otherwise he'll hold the object above the bottom) #define PLAYER_REACH_DOWN_DISTANCE 24 static void ComputePlayerMatrix( CBasePlayer *pPlayer, matrix3x4_t &out ) { if ( !pPlayer ) return; QAngle angles = pPlayer->EyeAngles(); Vector origin = pPlayer->EyePosition(); // 0-360 / -180-180 //angles.x = init ? 0 : AngleDistance( angles.x, 0 ); //angles.x = clamp( angles.x, -PLAYER_LOOK_PITCH_RANGE, PLAYER_LOOK_PITCH_RANGE ); angles.x = 0; float feet = pPlayer->GetAbsOrigin().z + pPlayer->WorldAlignMins().z; float eyes = origin.z; float zoffset = 0; // moving up (negative pitch is up) if ( angles.x < 0 ) { zoffset = RemapVal( angles.x, 0, -PLAYER_LOOK_PITCH_RANGE, PLAYER_HOLD_LEVEL_EYES, PLAYER_HOLD_UP_EYES ); } else { zoffset = RemapVal( angles.x, 0, PLAYER_LOOK_PITCH_RANGE, PLAYER_HOLD_LEVEL_EYES, PLAYER_HOLD_DOWN_FEET + (feet - eyes) ); } origin.z += zoffset; angles.x = 0; AngleMatrix( angles, origin, out ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- // derive from this so we can add save/load data to it struct game_shadowcontrol_params_t : public hlshadowcontrol_params_t { DECLARE_SIMPLE_DATADESC(); }; BEGIN_SIMPLE_DATADESC( game_shadowcontrol_params_t ) DEFINE_FIELD( targetPosition, FIELD_POSITION_VECTOR ), DEFINE_FIELD( targetRotation, FIELD_VECTOR ), DEFINE_FIELD( maxAngular, FIELD_FLOAT ), DEFINE_FIELD( maxDampAngular, FIELD_FLOAT ), DEFINE_FIELD( maxSpeed, FIELD_FLOAT ), DEFINE_FIELD( maxDampSpeed, FIELD_FLOAT ), DEFINE_FIELD( dampFactor, FIELD_FLOAT ), DEFINE_FIELD( teleportDistance, FIELD_FLOAT ), END_DATADESC() //----------------------------------------------------------------------------- class CGrabController : public IMotionEvent { public: CGrabController( void ); ~CGrabController( void ); void AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, IPhysicsObject *pPhys, bool bIsMegaPhysCannon, const Vector &vGrabPosition, bool bUseGrabPosition ); void DetachEntity( bool bClearVelocity ); void OnRestore(); bool UpdateObject( CBasePlayer *pPlayer, float flError ); void SetTargetPosition( const Vector &target, const QAngle &targetOrientation ); float ComputeError(); float GetLoadWeight( void ) const { return m_flLoadWeight; } void SetAngleAlignment( float alignAngleCosine ) { m_angleAlignment = alignAngleCosine; } void SetIgnorePitch( bool bIgnore ) { m_bIgnoreRelativePitch = bIgnore; } QAngle TransformAnglesToPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ); QAngle TransformAnglesFromPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ); CBaseEntity *GetAttached() { return (CBaseEntity *)m_attachedEntity; } IMotionEvent::simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular ); float GetSavedMass( IPhysicsObject *pObject ); QAngle m_attachedAnglesPlayerSpace; Vector m_attachedPositionObjectSpace; private: // Compute the max speed for an attached object void ComputeMaxSpeed( CBaseEntity *pEntity, IPhysicsObject *pPhysics ); game_shadowcontrol_params_t m_shadow; float m_timeToArrive; float m_errorTime; float m_error; float m_contactAmount; float m_angleAlignment; bool m_bCarriedEntityBlocksLOS; bool m_bIgnoreRelativePitch; float m_flLoadWeight; float m_savedRotDamping[VPHYSICS_MAX_OBJECT_LIST_COUNT]; float m_savedMass[VPHYSICS_MAX_OBJECT_LIST_COUNT]; EHANDLE m_attachedEntity; QAngle m_vecPreferredCarryAngles; bool m_bHasPreferredCarryAngles; IPhysicsMotionController *m_controller; int m_frameCount; friend class CWeaponPhysCannon; }; const float DEFAULT_MAX_ANGULAR = 360.0f * 10.0f; const float REDUCED_CARRY_MASS = 1.0f; CGrabController::CGrabController( void ) { m_shadow.dampFactor = 1.0; m_shadow.teleportDistance = 0; m_errorTime = 0; m_error = 0; // make this controller really stiff! m_shadow.maxSpeed = 1000; m_shadow.maxAngular = DEFAULT_MAX_ANGULAR; m_shadow.maxDampSpeed = m_shadow.maxSpeed*2; m_shadow.maxDampAngular = m_shadow.maxAngular; m_attachedEntity = NULL; m_vecPreferredCarryAngles = vec3_angle; m_bHasPreferredCarryAngles = false; } CGrabController::~CGrabController( void ) { DetachEntity( false ); } void CGrabController::OnRestore() { if ( m_controller ) { m_controller->SetEventHandler( this ); } } void CGrabController::SetTargetPosition( const Vector &target, const QAngle &targetOrientation ) { m_shadow.targetPosition = target; m_shadow.targetRotation = targetOrientation; m_timeToArrive = gpGlobals->frametime; CBaseEntity *pAttached = GetAttached(); if ( pAttached ) { IPhysicsObject *pObj = pAttached->VPhysicsGetObject(); if ( pObj != NULL ) { pObj->Wake(); } else { DetachEntity( false ); } } } //----------------------------------------------------------------------------- // Purpose: // Output : float //----------------------------------------------------------------------------- float CGrabController::ComputeError() { if ( m_errorTime <= 0 ) return 0; CBaseEntity *pAttached = GetAttached(); if ( pAttached ) { Vector pos; IPhysicsObject *pObj = pAttached->VPhysicsGetObject(); if ( pObj ) { pObj->GetShadowPosition( &pos, NULL ); float error = (m_shadow.targetPosition - pos).Length(); if ( m_errorTime > 0 ) { if ( m_errorTime > 1 ) { m_errorTime = 1; } float speed = error / m_errorTime; if ( speed > m_shadow.maxSpeed ) { error *= 0.5; } m_error = (1-m_errorTime) * m_error + error * m_errorTime; } } else { DevMsg( "Object attached to Physcannon has no physics object\n" ); DetachEntity( false ); return 9999; // force detach } } if ( pAttached->IsEFlagSet( EFL_IS_BEING_LIFTED_BY_BARNACLE ) ) { m_error *= 3.0f; } m_errorTime = 0; return m_error; } #define MASS_SPEED_SCALE 60 #define MAX_MASS 40 void CGrabController::ComputeMaxSpeed( CBaseEntity *pEntity, IPhysicsObject *pPhysics ) { #ifndef CLIENT_DLL m_shadow.maxSpeed = 1000; m_shadow.maxAngular = DEFAULT_MAX_ANGULAR; // Compute total mass... float flMass = PhysGetEntityMass( pEntity ); float flMaxMass = physcannon_maxmass.GetFloat(); if ( flMass <= flMaxMass ) return; float flLerpFactor = clamp( flMass, flMaxMass, 500.0f ); flLerpFactor = SimpleSplineRemapVal( flLerpFactor, flMaxMass, 500.0f, 0.0f, 1.0f ); float invMass = pPhysics->GetInvMass(); float invInertia = pPhysics->GetInvInertia().Length(); float invMaxMass = 1.0f / MAX_MASS; float ratio = invMaxMass / invMass; invMass = invMaxMass; invInertia *= ratio; float maxSpeed = invMass * MASS_SPEED_SCALE * 200; float maxAngular = invInertia * MASS_SPEED_SCALE * 360; m_shadow.maxSpeed = Lerp( flLerpFactor, m_shadow.maxSpeed, maxSpeed ); m_shadow.maxAngular = Lerp( flLerpFactor, m_shadow.maxAngular, maxAngular ); #endif } QAngle CGrabController::TransformAnglesToPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ) { if ( m_bIgnoreRelativePitch ) { matrix3x4_t test; QAngle angleTest = pPlayer->EyeAngles(); angleTest.x = 0; AngleMatrix( angleTest, test ); return TransformAnglesToLocalSpace( anglesIn, test ); } return TransformAnglesToLocalSpace( anglesIn, pPlayer->EntityToWorldTransform() ); } QAngle CGrabController::TransformAnglesFromPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ) { if ( m_bIgnoreRelativePitch ) { matrix3x4_t test; QAngle angleTest = pPlayer->EyeAngles(); angleTest.x = 0; AngleMatrix( angleTest, test ); return TransformAnglesToWorldSpace( anglesIn, test ); } return TransformAnglesToWorldSpace( anglesIn, pPlayer->EntityToWorldTransform() ); } void CGrabController::AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, IPhysicsObject *pPhys, bool bIsMegaPhysCannon, const Vector &vGrabPosition, bool bUseGrabPosition ) { // play the impact sound of the object hitting the player // used as feedback to let the player know he picked up the object #ifndef CLIENT_DLL PhysicsImpactSound( pPlayer, pPhys, CHAN_STATIC, pPhys->GetMaterialIndex(), pPlayer->VPhysicsGetObject()->GetMaterialIndex(), 1.0, 64 ); #endif Vector position; QAngle angles; pPhys->GetPosition( &position, &angles ); // If it has a preferred orientation, use that instead. #ifndef CLIENT_DLL Pickup_GetPreferredCarryAngles( pEntity, pPlayer, pPlayer->EntityToWorldTransform(), angles ); #endif // ComputeMaxSpeed( pEntity, pPhys ); // Carried entities can never block LOS m_bCarriedEntityBlocksLOS = pEntity->BlocksLOS(); pEntity->SetBlocksLOS( false ); m_controller = physenv->CreateMotionController( this ); m_controller->AttachObject( pPhys, true ); // Don't do this, it's causing trouble with constraint solvers. //m_controller->SetPriority( IPhysicsMotionController::HIGH_PRIORITY ); pPhys->Wake(); PhysSetGameFlags( pPhys, FVPHYSICS_PLAYER_HELD ); SetTargetPosition( position, angles ); m_attachedEntity = pEntity; IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; int count = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); m_flLoadWeight = 0; float damping = 10; float flFactor = count / 7.5f; if ( flFactor < 1.0f ) { flFactor = 1.0f; } for ( int i = 0; i < count; i++ ) { float mass = pList[i]->GetMass(); pList[i]->GetDamping( NULL, &m_savedRotDamping[i] ); m_flLoadWeight += mass; m_savedMass[i] = mass; // reduce the mass to prevent the player from adding crazy amounts of energy to the system pList[i]->SetMass( REDUCED_CARRY_MASS / flFactor ); pList[i]->SetDamping( NULL, &damping ); } // Give extra mass to the phys object we're actually picking up pPhys->SetMass( REDUCED_CARRY_MASS ); pPhys->EnableDrag( false ); m_errorTime = -1.0f; // 1 seconds until error starts accumulating m_error = 0; m_contactAmount = 0; m_attachedAnglesPlayerSpace = TransformAnglesToPlayerSpace( angles, pPlayer ); if ( m_angleAlignment != 0 ) { m_attachedAnglesPlayerSpace = AlignAngles( m_attachedAnglesPlayerSpace, m_angleAlignment ); } VectorITransform( pEntity->WorldSpaceCenter(), pEntity->EntityToWorldTransform(), m_attachedPositionObjectSpace ); #ifndef CLIENT_DLL // If it's a prop, see if it has desired carry angles CPhysicsProp *pProp = dynamic_cast(pEntity); if ( pProp ) { m_bHasPreferredCarryAngles = pProp->GetPropDataAngles( "preferred_carryangles", m_vecPreferredCarryAngles ); } else { m_bHasPreferredCarryAngles = false; } #else m_bHasPreferredCarryAngles = false; #endif } static void ClampPhysicsVelocity( IPhysicsObject *pPhys, float linearLimit, float angularLimit ) { Vector vel; AngularImpulse angVel; pPhys->GetVelocity( &vel, &angVel ); float speed = VectorNormalize(vel) - linearLimit; float angSpeed = VectorNormalize(angVel) - angularLimit; speed = speed < 0 ? 0 : -speed; angSpeed = angSpeed < 0 ? 0 : -angSpeed; vel *= speed; angVel *= angSpeed; pPhys->AddVelocity( &vel, &angVel ); } void CGrabController::DetachEntity( bool bClearVelocity ) { CBaseEntity *pEntity = GetAttached(); if ( pEntity ) { // Restore the LS blocking state pEntity->SetBlocksLOS( m_bCarriedEntityBlocksLOS ); IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; int count = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); for ( int i = 0; i < count; i++ ) { IPhysicsObject *pPhys = pList[i]; if ( !pPhys ) continue; // on the odd chance that it's gone to sleep while under anti-gravity pPhys->EnableDrag( true ); pPhys->Wake(); pPhys->SetMass( m_savedMass[i] ); pPhys->SetDamping( NULL, &m_savedRotDamping[i] ); PhysClearGameFlags( pPhys, FVPHYSICS_PLAYER_HELD ); if ( bClearVelocity ) { PhysForceClearVelocity( pPhys ); } else { #ifndef CLIENT_DLL ClampPhysicsVelocity( pPhys, hl2_normspeed.GetFloat() * 1.5f, 2.0f * 360.0f ); #endif } } } m_attachedEntity = NULL; if ( physenv ) { physenv->DestroyMotionController( m_controller ); } m_controller = NULL; } 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; } IMotionEvent::simresult_e CGrabController::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular ) { game_shadowcontrol_params_t shadowParams = m_shadow; if ( InContactWithHeavyObject( pObject, GetLoadWeight() ) ) { m_contactAmount = Approach( 0.1f, m_contactAmount, deltaTime*2.0f ); } else { m_contactAmount = Approach( 1.0f, m_contactAmount, deltaTime*2.0f ); } shadowParams.maxAngular = m_shadow.maxAngular * m_contactAmount * m_contactAmount * m_contactAmount; #ifndef CLIENT_DLL m_timeToArrive = pObject->ComputeShadowControl( shadowParams, m_timeToArrive, deltaTime ); #else m_timeToArrive = pObject->ComputeShadowControl( shadowParams, (TICK_INTERVAL*2), deltaTime ); #endif // Slide along the current contact points to fix bouncing problems Vector velocity; AngularImpulse angVel; pObject->GetVelocity( &velocity, &angVel ); PhysComputeSlideDirection( pObject, velocity, angVel, &velocity, &angVel, GetLoadWeight() ); pObject->SetVelocityInstantaneous( &velocity, NULL ); linear.Init(); angular.Init(); m_errorTime += deltaTime; return SIM_LOCAL_ACCELERATION; } float CGrabController::GetSavedMass( IPhysicsObject *pObject ) { CBaseEntity *pHeld = m_attachedEntity; if ( pHeld ) { if ( pObject->GetGameData() == (void*)pHeld ) { IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; int count = pHeld->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); for ( int i = 0; i < count; i++ ) { if ( pList[i] == pObject ) return m_savedMass[i]; } } } return 0.0f; } //----------------------------------------------------------------------------- // Player pickup controller //----------------------------------------------------------------------------- class CPlayerPickupController : public CBaseEntity { DECLARE_CLASS( CPlayerPickupController, CBaseEntity ); public: void Init( CBasePlayer *pPlayer, CBaseEntity *pObject ); void Shutdown( bool bThrown = false ); bool OnControls( CBaseEntity *pControls ) { return true; } void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); void OnRestore() { m_grabController.OnRestore(); } void VPhysicsUpdate( IPhysicsObject *pPhysics ){} void VPhysicsShadowUpdate( IPhysicsObject *pPhysics ) {} bool IsHoldingEntity( CBaseEntity *pEnt ); CGrabController &GetGrabController() { return m_grabController; } private: CGrabController m_grabController; CBasePlayer *m_pPlayer; }; LINK_ENTITY_TO_CLASS( player_pickup, CPlayerPickupController ); //----------------------------------------------------------------------------- // Purpose: // Input : *pPlayer - // *pObject - //----------------------------------------------------------------------------- void CPlayerPickupController::Init( CBasePlayer *pPlayer, CBaseEntity *pObject ) { #ifndef CLIENT_DLL // Holster player's weapon if ( pPlayer->GetActiveWeapon() ) { if ( !pPlayer->GetActiveWeapon()->Holster() ) { Shutdown(); return; } } CHL2MP_Player *pOwner = (CHL2MP_Player *)ToBasePlayer( pPlayer ); if ( pOwner ) { pOwner->EnableSprint( false ); } // If the target is debris, convert it to non-debris if ( pObject->GetCollisionGroup() == COLLISION_GROUP_DEBRIS ) { // Interactive debris converts back to debris when it comes to rest pObject->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS ); } // done so I'll go across level transitions with the player SetParent( pPlayer ); m_grabController.SetIgnorePitch( true ); m_grabController.SetAngleAlignment( DOT_30DEGREE ); m_pPlayer = pPlayer; IPhysicsObject *pPhysics = pObject->VPhysicsGetObject(); Pickup_OnPhysGunPickup( pObject, m_pPlayer ); m_grabController.AttachEntity( pPlayer, pObject, pPhysics, false, vec3_origin, false ); m_pPlayer->m_Local.m_iHideHUD |= HIDEHUD_WEAPONSELECTION; m_pPlayer->SetUseEntity( this ); #endif } //----------------------------------------------------------------------------- // Purpose: // Input : bool - //----------------------------------------------------------------------------- void CPlayerPickupController::Shutdown( bool bThrown ) { #ifndef CLIENT_DLL CBaseEntity *pObject = m_grabController.GetAttached(); bool bClearVelocity = false; if ( !bThrown && pObject && pObject->VPhysicsGetObject() && pObject->VPhysicsGetObject()->GetContactPoint(NULL,NULL) ) { bClearVelocity = true; } m_grabController.DetachEntity( bClearVelocity ); if ( pObject != NULL ) { Pickup_OnPhysGunDrop( pObject, m_pPlayer, bThrown ? THROWN_BY_PLAYER : DROPPED_BY_PLAYER ); } if ( m_pPlayer ) { CHL2MP_Player *pOwner = (CHL2MP_Player *)ToBasePlayer( m_pPlayer ); if ( pOwner ) { pOwner->EnableSprint( true ); } m_pPlayer->SetUseEntity( NULL ); if ( m_pPlayer->GetActiveWeapon() ) { if ( !m_pPlayer->GetActiveWeapon()->Deploy() ) { // We tried to restore the player's weapon, but we couldn't. // This usually happens when they're holding an empty weapon that doesn't // autoswitch away when out of ammo. Switch to next best weapon. m_pPlayer->SwitchToNextBestWeapon( NULL ); } } m_pPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION; } Remove(); #endif } 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 // +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; } if ( useType == USE_SET ) { // update position m_grabController.UpdateObject( m_pPlayer, 12 ); } } } //----------------------------------------------------------------------------- // Purpose: // Input : *pEnt - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CPlayerPickupController::IsHoldingEntity( CBaseEntity *pEnt ) { return ( m_grabController.GetAttached() == pEnt ); } void PlayerPickupObject( CBasePlayer *pPlayer, CBaseEntity *pObject ) { #ifndef CLIENT_DLL //Don't pick up if we don't have a phys object. if ( pObject->VPhysicsGetObject() == NULL ) return; CPlayerPickupController *pController = (CPlayerPickupController *)CBaseEntity::Create( "player_pickup", pObject->GetAbsOrigin(), vec3_angle, pPlayer ); if ( !pController ) return; pController->Init( pPlayer, pObject ); #endif } //---------------------------------------------------------------------------------------------------------------------------------------------------------- // CInterpolatedValue class //---------------------------------------------------------------------------------------------------------------------------------------------------------- #ifdef CLIENT_DLL //---------------------------------------------------------------------------------------------------------------------------------------------------------- // CPhysCannonEffect class //---------------------------------------------------------------------------------------------------------------------------------------------------------- class CPhysCannonEffect { public: CPhysCannonEffect( void ) : m_vecColor( 255, 255, 255 ), m_bVisible( true ), m_nAttachment( -1 ) {}; void SetAttachment( int attachment ) { m_nAttachment = attachment; } int GetAttachment( void ) const { return m_nAttachment; } void SetVisible( bool visible = true ) { m_bVisible = visible; } int IsVisible( void ) const { return m_bVisible; } void SetColor( const Vector &color ) { m_vecColor = color; } const Vector &GetColor( void ) const { return m_vecColor; } bool SetMaterial( const char *materialName ) { m_hMaterial.Init( materialName, TEXTURE_GROUP_CLIENT_EFFECTS ); return ( m_hMaterial != NULL ); } CMaterialReference &GetMaterial( void ) { return m_hMaterial; } CInterpolatedValue &GetAlpha( void ) { return m_Alpha; } CInterpolatedValue &GetScale( void ) { return m_Scale; } private: CInterpolatedValue m_Alpha; CInterpolatedValue m_Scale; Vector m_vecColor; bool m_bVisible; int m_nAttachment; CMaterialReference m_hMaterial; }; //---------------------------------------------------------------------------------------------------------------------------------------------------------- // CPhysCannonEffectBeam class //---------------------------------------------------------------------------------------------------------------------------------------------------------- class CPhysCannonEffectBeam { public: CPhysCannonEffectBeam( void ) : m_pBeam( NULL ) {}; ~CPhysCannonEffectBeam( void ) { Release(); } void Release( void ) { if ( m_pBeam != NULL ) { m_pBeam->flags = 0; m_pBeam->die = gpGlobals->curtime - 1; m_pBeam = NULL; } } void Init( int startAttachment, int endAttachment, CBaseEntity *pEntity, bool firstPerson ) { if ( m_pBeam != NULL ) return; BeamInfo_t beamInfo; beamInfo.m_pStartEnt = pEntity; beamInfo.m_nStartAttachment = startAttachment; beamInfo.m_pEndEnt = pEntity; beamInfo.m_nEndAttachment = endAttachment; beamInfo.m_nType = TE_BEAMPOINTS; beamInfo.m_vecStart = vec3_origin; beamInfo.m_vecEnd = vec3_origin; beamInfo.m_pszModelName = ( firstPerson ) ? PHYSCANNON_BEAM_SPRITE_NOZ : PHYSCANNON_BEAM_SPRITE; beamInfo.m_flHaloScale = 0.0f; beamInfo.m_flLife = 0.0f; if ( firstPerson ) { beamInfo.m_flWidth = 0.0f; beamInfo.m_flEndWidth = 4.0f; } else { beamInfo.m_flWidth = 0.5f; beamInfo.m_flEndWidth = 2.0f; } beamInfo.m_flFadeLength = 0.0f; beamInfo.m_flAmplitude = 16; beamInfo.m_flBrightness = 255.0; beamInfo.m_flSpeed = 150.0f; beamInfo.m_nStartFrame = 0.0; beamInfo.m_flFrameRate = 30.0; beamInfo.m_flRed = 255.0; beamInfo.m_flGreen = 255.0; beamInfo.m_flBlue = 255.0; beamInfo.m_nSegments = 8; beamInfo.m_bRenderable = true; beamInfo.m_nFlags = FBEAM_FOREVER; m_pBeam = beams->CreateBeamEntPoint( beamInfo ); } void SetVisible( bool state = true ) { if ( m_pBeam == NULL ) return; m_pBeam->brightness = ( state ) ? 255.0f : 0.0f; } private: Beam_t *m_pBeam; }; #endif //---------------------------------------------------------------------------------------------------------------------------------------------------------- // CWeaponPhysCannon class //---------------------------------------------------------------------------------------------------------------------------------------------------------- #ifdef CLIENT_DLL #define CWeaponPhysCannon C_WeaponPhysCannon #endif class CWeaponPhysCannon : public CBaseHL2MPCombatWeapon { public: DECLARE_CLASS( CWeaponPhysCannon, CBaseHL2MPCombatWeapon ); DECLARE_NETWORKCLASS(); DECLARE_PREDICTABLE(); CWeaponPhysCannon( void ); void Drop( const Vector &vecVelocity ); void Precache(); virtual void OnRestore(); virtual void StopLoopingSounds(); virtual void UpdateOnRemove(void); void PrimaryAttack(); void SecondaryAttack(); void WeaponIdle(); void ItemPreFrame(); void ItemPostFrame(); void ForceDrop( void ); bool DropIfEntityHeld( CBaseEntity *pTarget ); // Drops its held entity if it matches the entity passed in CGrabController &GetGrabController() { return m_grabController; } bool CanHolster( void ); bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); bool Deploy( void ); bool HasAnyAmmo( void ) { return true; } virtual void SetViewModel( void ); virtual const char *GetShootSound( int iIndex ) const; #ifndef CLIENT_DLL CNetworkQAngle ( m_attachedAnglesPlayerSpace ); #else QAngle m_attachedAnglesPlayerSpace; #endif CNetworkVector ( m_attachedPositionObjectSpace ); CNetworkHandle( CBaseEntity, m_hAttachedObject ); EHANDLE m_hOldAttachedObject; protected: enum FindObjectResult_t { OBJECT_FOUND = 0, OBJECT_NOT_FOUND, OBJECT_BEING_DETACHED, }; void DoEffect( int effectType, Vector *pos = NULL ); void OpenElements( void ); void CloseElements( void ); // Pickup and throw objects. bool CanPickupObject( CBaseEntity *pTarget ); void CheckForTarget( void ); #ifndef CLIENT_DLL bool AttachObject( CBaseEntity *pObject, const Vector &vPosition ); FindObjectResult_t FindObject( void ); CBaseEntity *FindObjectInCone( const Vector &vecOrigin, const Vector &vecDir, float flCone ); #endif // !CLIENT_DLL void UpdateObject( void ); void DetachObject( bool playSound = true, bool wasLaunched = false ); void LaunchObject( const Vector &vecDir, float flForce ); void StartEffects( void ); // Initialize all sprites and beams void StopEffects( bool stopSound = true ); // Hide all effects temporarily void DestroyEffects( void ); // Destroy all sprites and beams // Punt objects - this is pointing at an object in the world and applying a force to it. void PuntNonVPhysics( CBaseEntity *pEntity, const Vector &forward, trace_t &tr ); void PuntVPhysics( CBaseEntity *pEntity, const Vector &forward, trace_t &tr ); // Velocity-based throw common to punt and launch code. void ApplyVelocityBasedForce( CBaseEntity *pEntity, const Vector &forward ); // Physgun effects void DoEffectClosed( void ); void DoEffectReady( void ); void DoEffectHolding( void ); void DoEffectLaunch( Vector *pos ); void DoEffectNone( void ); void DoEffectIdle( void ); // Trace length float TraceLength(); // Sprite scale factor float SpriteScaleFactor(); float GetLoadPercentage(); CSoundPatch *GetMotorSound( void ); void DryFire( void ); void PrimaryFireEffect( void ); #ifndef CLIENT_DLL // What happens when the physgun picks up something void Physgun_OnPhysGunPickup( CBaseEntity *pEntity, CBasePlayer *pOwner, PhysGunPickup_t reason ); #endif // !CLIENT_DLL #ifdef CLIENT_DLL enum EffectType_t { PHYSCANNON_CORE = 0, PHYSCANNON_BLAST, PHYSCANNON_GLOW1, // Must be in order! PHYSCANNON_GLOW2, PHYSCANNON_GLOW3, PHYSCANNON_GLOW4, PHYSCANNON_GLOW5, PHYSCANNON_GLOW6, PHYSCANNON_ENDCAP1, // Must be in order! PHYSCANNON_ENDCAP2, PHYSCANNON_ENDCAP3, // Only used in third-person! NUM_PHYSCANNON_PARAMETERS // Must be last! }; #define NUM_GLOW_SPRITES ((CWeaponPhysCannon::PHYSCANNON_GLOW6-CWeaponPhysCannon::PHYSCANNON_GLOW1)+1) #define NUM_ENDCAP_SPRITES ((CWeaponPhysCannon::PHYSCANNON_ENDCAP3-CWeaponPhysCannon::PHYSCANNON_ENDCAP1)+1) #define NUM_PHYSCANNON_BEAMS 3 virtual int DrawModel( int flags ); virtual void ViewModelDrawn( C_BaseViewModel *pBaseViewModel ); virtual bool IsTransparent( void ); virtual void OnDataChanged( DataUpdateType_t type ); virtual void ClientThink( void ); void ManagePredictedObject( void ); void DrawEffects( void ); void GetEffectParameters( EffectType_t effectID, color32 &color, float &scale, IMaterial **pMaterial, Vector &vecAttachment ); void DrawEffectSprite( EffectType_t effectID ); inline bool IsEffectVisible( EffectType_t effectID ); void UpdateElementPosition( void ); // We need to render opaque and translucent pieces RenderGroup_t GetRenderGroup( void ) { return RENDER_GROUP_TWOPASS; } CInterpolatedValue m_ElementParameter; // Used to interpolate the position of the articulated elements CPhysCannonEffect m_Parameters[NUM_PHYSCANNON_PARAMETERS]; // Interpolated parameters for the effects CPhysCannonEffectBeam m_Beams[NUM_PHYSCANNON_BEAMS]; // Beams int m_nOldEffectState; // Used for parity checks bool m_bOldOpen; // Used for parity checks void NotifyShouldTransmit( ShouldTransmitState_t state ); #endif // CLIENT_DLL int m_nChangeState; // For delayed state change of elements float m_flCheckSuppressTime; // Amount of time to suppress the checking for targets bool m_flLastDenySoundPlayed; // Debounce for deny sound int m_nAttack2Debounce; CNetworkVar( bool, m_bActive ); CNetworkVar( int, m_EffectState ); // Current state of the effects on the gun CNetworkVar( bool, m_bOpen ); bool m_bResetOwnerEntity; float m_flElementDebounce; CSoundPatch *m_sndMotor; // Whirring sound for the gun CGrabController m_grabController; float m_flRepuntObjectTime; EHANDLE m_hLastPuntedObject; private: CWeaponPhysCannon( const CWeaponPhysCannon & ); #ifndef CLIENT_DLL DECLARE_ACTTABLE(); #endif }; IMPLEMENT_NETWORKCLASS_ALIASED( WeaponPhysCannon, DT_WeaponPhysCannon ) BEGIN_NETWORK_TABLE( CWeaponPhysCannon, DT_WeaponPhysCannon ) #ifdef CLIENT_DLL RecvPropBool( RECVINFO( m_bActive ) ), RecvPropEHandle( RECVINFO( m_hAttachedObject ) ), RecvPropVector( RECVINFO( m_attachedPositionObjectSpace ) ), RecvPropFloat( RECVINFO( m_attachedAnglesPlayerSpace[0] ) ), RecvPropFloat( RECVINFO( m_attachedAnglesPlayerSpace[1] ) ), RecvPropFloat( RECVINFO( m_attachedAnglesPlayerSpace[2] ) ), RecvPropInt( RECVINFO( m_EffectState ) ), RecvPropBool( RECVINFO( m_bOpen ) ), #else SendPropBool( SENDINFO( m_bActive ) ), SendPropEHandle( SENDINFO( m_hAttachedObject ) ), SendPropVector(SENDINFO( m_attachedPositionObjectSpace ), -1, SPROP_COORD), SendPropAngle( SENDINFO_VECTORELEM(m_attachedAnglesPlayerSpace, 0 ), 11 ), SendPropAngle( SENDINFO_VECTORELEM(m_attachedAnglesPlayerSpace, 1 ), 11 ), SendPropAngle( SENDINFO_VECTORELEM(m_attachedAnglesPlayerSpace, 2 ), 11 ), SendPropInt( SENDINFO( m_EffectState ) ), SendPropBool( SENDINFO( m_bOpen ) ), #endif END_NETWORK_TABLE() #ifdef CLIENT_DLL BEGIN_PREDICTION_DATA( CWeaponPhysCannon ) DEFINE_PRED_FIELD( m_EffectState, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_bOpen, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), END_PREDICTION_DATA() #endif LINK_ENTITY_TO_CLASS( weapon_physcannon, CWeaponPhysCannon ); PRECACHE_WEAPON_REGISTER( weapon_physcannon ); #ifndef CLIENT_DLL acttable_t CWeaponPhysCannon::m_acttable[] = { { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_PHYSGUN, false }, { ACT_HL2MP_RUN, ACT_HL2MP_RUN_PHYSGUN, false }, { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_PHYSGUN, false }, { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_PHYSGUN, false }, { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_PHYSGUN, false }, { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_PHYSGUN, false }, { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_PHYSGUN, false }, }; IMPLEMENT_ACTTABLE(CWeaponPhysCannon); #endif enum { ELEMENT_STATE_NONE = -1, ELEMENT_STATE_OPEN, ELEMENT_STATE_CLOSED, }; enum { EFFECT_NONE, EFFECT_CLOSED, EFFECT_READY, EFFECT_HOLDING, EFFECT_LAUNCH, }; //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CWeaponPhysCannon::CWeaponPhysCannon( void ) { m_bOpen = false; m_nChangeState = ELEMENT_STATE_NONE; m_flCheckSuppressTime = 0.0f; m_EffectState = (int)EFFECT_NONE; m_flLastDenySoundPlayed = false; #ifdef CLIENT_DLL m_nOldEffectState = EFFECT_NONE; m_bOldOpen = false; #endif } //----------------------------------------------------------------------------- // Purpose: Precache //----------------------------------------------------------------------------- void CWeaponPhysCannon::Precache( void ) { PrecacheModel( PHYSCANNON_BEAM_SPRITE ); PrecacheModel( PHYSCANNON_BEAM_SPRITE_NOZ ); PrecacheScriptSound( "Weapon_PhysCannon.HoldSound" ); BaseClass::Precache(); } //----------------------------------------------------------------------------- // Purpose: Restore //----------------------------------------------------------------------------- void CWeaponPhysCannon::OnRestore() { BaseClass::OnRestore(); m_grabController.OnRestore(); // Tracker 8106: Physcannon effects disappear through level transition, so // just recreate any effects here if ( m_EffectState != EFFECT_NONE ) { DoEffect( m_EffectState, NULL ); } } //----------------------------------------------------------------------------- // On Remove //----------------------------------------------------------------------------- void CWeaponPhysCannon::UpdateOnRemove(void) { DestroyEffects( ); BaseClass::UpdateOnRemove(); } #ifdef CLIENT_DLL void CWeaponPhysCannon::OnDataChanged( DataUpdateType_t type ) { BaseClass::OnDataChanged( type ); if ( type == DATA_UPDATE_CREATED ) { SetNextClientThink( CLIENT_THINK_ALWAYS ); C_BaseAnimating::AutoAllowBoneAccess boneaccess( true, false ); StartEffects(); } if ( GetOwner() == NULL ) { if ( m_hAttachedObject ) { m_hAttachedObject->VPhysicsDestroyObject(); } if ( m_hOldAttachedObject ) { m_hOldAttachedObject->VPhysicsDestroyObject(); } } // Update effect state when out of parity with the server if ( m_nOldEffectState != m_EffectState ) { DoEffect( m_EffectState ); m_nOldEffectState = m_EffectState; } // Update element state when out of parity if ( m_bOldOpen != m_bOpen ) { if ( m_bOpen ) { m_ElementParameter.InitFromCurrent( 1.0f, 0.2f, INTERP_SPLINE ); } else { m_ElementParameter.InitFromCurrent( 0.0f, 0.5f, INTERP_SPLINE ); } m_bOldOpen = (bool) m_bOpen; } } #endif //----------------------------------------------------------------------------- // Sprite scale factor //----------------------------------------------------------------------------- inline float CWeaponPhysCannon::SpriteScaleFactor() { return 1.0f; } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CWeaponPhysCannon::Deploy( void ) { CloseElements(); DoEffect( EFFECT_READY ); bool bReturn = BaseClass::Deploy(); m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->curtime; CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( pOwner ) { pOwner->SetNextAttack( gpGlobals->curtime ); } return bReturn; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponPhysCannon::SetViewModel( void ) { BaseClass::SetViewModel(); } //----------------------------------------------------------------------------- // Purpose: Force the cannon to drop anything it's carrying //----------------------------------------------------------------------------- void CWeaponPhysCannon::ForceDrop( void ) { CloseElements(); DetachObject(); StopEffects(); } //----------------------------------------------------------------------------- // Purpose: Drops its held entity if it matches the entity passed in // Input : *pTarget - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CWeaponPhysCannon::DropIfEntityHeld( CBaseEntity *pTarget ) { if ( pTarget == NULL ) return false; CBaseEntity *pHeld = m_grabController.GetAttached(); if ( pHeld == NULL ) return false; if ( pHeld == pTarget ) { ForceDrop(); return true; } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponPhysCannon::Drop( const Vector &vecVelocity ) { ForceDrop(); #ifndef CLIENT_DLL UTIL_Remove( this ); #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CWeaponPhysCannon::CanHolster( void ) { //Don't holster this weapon if we're holding onto something if ( m_bActive ) return false; return BaseClass::CanHolster(); }; //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CWeaponPhysCannon::Holster( CBaseCombatWeapon *pSwitchingTo ) { //Don't holster this weapon if we're holding onto something if ( m_bActive ) return false; ForceDrop(); DestroyEffects(); return BaseClass::Holster( pSwitchingTo ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponPhysCannon::DryFire( void ) { SendWeaponAnim( ACT_VM_PRIMARYATTACK ); WeaponSound( EMPTY ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponPhysCannon::PrimaryFireEffect( void ) { CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( pOwner == NULL ) return; pOwner->ViewPunch( QAngle(-6, SharedRandomInt( "physcannonfire", -2,2) ,0) ); #ifndef CLIENT_DLL color32 white = { 245, 245, 255, 32 }; UTIL_ScreenFade( pOwner, white, 0.1f, 0.0f, FFADE_IN ); #endif WeaponSound( SINGLE ); } #define MAX_KNOCKBACK_FORCE 128 //----------------------------------------------------------------------------- // Punt non-physics //----------------------------------------------------------------------------- void CWeaponPhysCannon::PuntNonVPhysics( CBaseEntity *pEntity, const Vector &forward, trace_t &tr ) { if ( m_hLastPuntedObject == pEntity && gpGlobals->curtime < m_flRepuntObjectTime ) return; #ifndef CLIENT_DLL CTakeDamageInfo info; info.SetAttacker( GetOwner() ); info.SetInflictor( this ); info.SetDamage( 1.0f ); info.SetDamageType( DMG_CRUSH | DMG_PHYSGUN ); info.SetDamageForce( forward ); // Scale? info.SetDamagePosition( tr.endpos ); m_hLastPuntedObject = pEntity; m_flRepuntObjectTime = gpGlobals->curtime + 0.5f; pEntity->DispatchTraceAttack( info, forward, &tr ); ApplyMultiDamage(); //Explosion effect DoEffect( EFFECT_LAUNCH, &tr.endpos ); #endif PrimaryFireEffect(); SendWeaponAnim( ACT_VM_SECONDARYATTACK ); m_nChangeState = ELEMENT_STATE_CLOSED; m_flElementDebounce = gpGlobals->curtime + 0.5f; m_flCheckSuppressTime = gpGlobals->curtime + 0.25f; } #ifndef CLIENT_DLL //----------------------------------------------------------------------------- // What happens when the physgun picks up something //----------------------------------------------------------------------------- void CWeaponPhysCannon::Physgun_OnPhysGunPickup( CBaseEntity *pEntity, CBasePlayer *pOwner, PhysGunPickup_t reason ) { // If the target is debris, convert it to non-debris if ( pEntity->GetCollisionGroup() == COLLISION_GROUP_DEBRIS ) { // Interactive debris converts back to debris when it comes to rest pEntity->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS ); } Pickup_OnPhysGunPickup( pEntity, pOwner, reason ); } #endif //----------------------------------------------------------------------------- // Punt vphysics //----------------------------------------------------------------------------- void CWeaponPhysCannon::PuntVPhysics( CBaseEntity *pEntity, const Vector &vecForward, trace_t &tr ) { CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( m_hLastPuntedObject == pEntity && gpGlobals->curtime < m_flRepuntObjectTime ) return; m_hLastPuntedObject = pEntity; m_flRepuntObjectTime = gpGlobals->curtime + 0.5f; #ifndef CLIENT_DLL CTakeDamageInfo info; Vector forward = vecForward; info.SetAttacker( GetOwner() ); info.SetInflictor( this ); info.SetDamage( 0.0f ); info.SetDamageType( DMG_PHYSGUN ); pEntity->DispatchTraceAttack( info, forward, &tr ); ApplyMultiDamage(); if ( Pickup_OnAttemptPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON ) ) { IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; int listCount = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); if ( !listCount ) { //FIXME: Do we want to do this if there's no physics object? Physgun_OnPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON ); DryFire(); return; } if( forward.z < 0 ) { //reflect, but flatten the trajectory out a bit so it's easier to hit standing targets forward.z *= -0.65f; } // NOTE: Do this first to enable motion (if disabled) - so forces will work // Tell the object it's been punted Physgun_OnPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON ); // don't push vehicles that are attached to the world via fixed constraints // they will just wiggle... if ( (pList[0]->GetGameFlags() & FVPHYSICS_CONSTRAINT_STATIC) && pEntity->GetServerVehicle() ) { forward.Init(); } if ( !Pickup_ShouldPuntUseLaunchForces( pEntity, PHYSGUN_FORCE_PUNTED ) ) { int i; // limit mass to avoid punting REALLY huge things float totalMass = 0; for ( i = 0; i < listCount; i++ ) { totalMass += pList[i]->GetMass(); } float maxMass = 250; IServerVehicle *pVehicle = pEntity->GetServerVehicle(); if ( pVehicle ) { maxMass *= 2.5; // 625 for vehicles } float mass = MIN(totalMass, maxMass); // max 250kg of additional force // Put some spin on the object for ( i = 0; i < listCount; i++ ) { const float hitObjectFactor = 0.5f; const float otherObjectFactor = 1.0f - hitObjectFactor; // Must be light enough float ratio = pList[i]->GetMass() / totalMass; if ( pList[i] == pEntity->VPhysicsGetObject() ) { ratio += hitObjectFactor; ratio = MIN(ratio,1.0f); } else { ratio *= otherObjectFactor; } pList[i]->ApplyForceCenter( forward * 15000.0f * ratio ); pList[i]->ApplyForceOffset( forward * mass * 600.0f * ratio, tr.endpos ); } } else { ApplyVelocityBasedForce( pEntity, vecForward ); } } #endif // Add recoil QAngle recoil = QAngle( random->RandomFloat( 1.0f, 2.0f ), random->RandomFloat( -1.0f, 1.0f ), 0 ); pOwner->ViewPunch( recoil ); //Explosion effect DoEffect( EFFECT_LAUNCH, &tr.endpos ); PrimaryFireEffect(); SendWeaponAnim( ACT_VM_SECONDARYATTACK ); m_nChangeState = ELEMENT_STATE_CLOSED; m_flElementDebounce = gpGlobals->curtime + 0.5f; m_flCheckSuppressTime = gpGlobals->curtime + 0.25f; // Don't allow the gun to regrab a thrown object!! m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f; } //----------------------------------------------------------------------------- // Purpose: Applies velocity-based forces to throw the entity. This code is // called from both punt and launch carried code. // ASSUMES: that pEntity is a vphysics entity. // Input : - //----------------------------------------------------------------------------- void CWeaponPhysCannon::ApplyVelocityBasedForce( CBaseEntity *pEntity, const Vector &forward ) { #ifndef CLIENT_DLL IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject(); Assert(pPhysicsObject); // Shouldn't ever get here with a non-vphysics object. if (!pPhysicsObject) return; float flForceMax = physcannon_maxforce.GetFloat(); float flForce = flForceMax; float mass = pPhysicsObject->GetMass(); if (mass > 100) { mass = MIN(mass, 1000); float flForceMin = physcannon_minforce.GetFloat(); flForce = SimpleSplineRemapVal(mass, 100, 600, flForceMax, flForceMin); } Vector vVel = forward * flForce; // FIXME: Josh needs to put a real value in for PHYSGUN_FORCE_PUNTED AngularImpulse aVel = Pickup_PhysGunLaunchAngularImpulse( pEntity, PHYSGUN_FORCE_PUNTED ); pPhysicsObject->AddVelocity( &vVel, &aVel ); #endif } //----------------------------------------------------------------------------- // Trace length //----------------------------------------------------------------------------- float CWeaponPhysCannon::TraceLength() { return physcannon_tracelength.GetFloat(); } //----------------------------------------------------------------------------- // Purpose: // // This mode is a toggle. Primary fire one time to pick up a physics object. // With an object held, click primary fire again to drop object. //----------------------------------------------------------------------------- void CWeaponPhysCannon::PrimaryAttack( void ) { if( m_flNextPrimaryAttack > gpGlobals->curtime ) return; CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( pOwner == NULL ) return; if( m_bActive ) { // Punch the object being held!! Vector forward; pOwner->EyeVectors( &forward ); // Validate the item is within punt range CBaseEntity *pHeld = m_grabController.GetAttached(); Assert( pHeld != NULL ); if ( pHeld != NULL ) { float heldDist = ( pHeld->WorldSpaceCenter() - pOwner->WorldSpaceCenter() ).Length(); if ( heldDist > physcannon_tracelength.GetFloat() ) { // We can't punt this yet DryFire(); return; } } LaunchObject( forward, physcannon_maxforce.GetFloat() ); PrimaryFireEffect(); SendWeaponAnim( ACT_VM_SECONDARYATTACK ); return; } // If not active, just issue a physics punch in the world. m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f; Vector forward; pOwner->EyeVectors( &forward ); // NOTE: Notice we're *not* using the mega tracelength here // when you have the mega cannon. Punting has shorter range. Vector start, end; start = pOwner->Weapon_ShootPosition(); float flPuntDistance = physcannon_tracelength.GetFloat(); VectorMA( start, flPuntDistance, forward, end ); CTraceFilterNoOwnerTest filter( pOwner, COLLISION_GROUP_NONE ); trace_t tr; UTIL_TraceHull( start, end, -Vector(8,8,8), Vector(8,8,8), MASK_SHOT|CONTENTS_GRATE, &filter, &tr ); bool bValid = true; CBaseEntity *pEntity = tr.m_pEnt; if ( tr.fraction == 1 || !tr.m_pEnt || tr.m_pEnt->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) ) { bValid = false; } else if ( (pEntity->GetMoveType() != MOVETYPE_VPHYSICS) && ( pEntity->m_takedamage == DAMAGE_NO ) ) { bValid = false; } // If the entity we've hit is invalid, try a traceline instead if ( !bValid ) { UTIL_TraceLine( start, end, MASK_SHOT|CONTENTS_GRATE, &filter, &tr ); if ( tr.fraction == 1 || !tr.m_pEnt || tr.m_pEnt->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) ) { // Play dry-fire sequence DryFire(); return; } pEntity = tr.m_pEnt; } // See if we hit something if ( pEntity->GetMoveType() != MOVETYPE_VPHYSICS ) { if ( pEntity->m_takedamage == DAMAGE_NO ) { DryFire(); return; } if( GetOwner()->IsPlayer() ) { // Don't let the player zap any NPC's except regular antlions and headcrabs. if( pEntity->IsPlayer() ) { DryFire(); return; } } PuntNonVPhysics( pEntity, forward, tr ); } else { if ( pEntity->VPhysicsIsFlesh( ) ) { DryFire(); return; } PuntVPhysics( pEntity, forward, tr ); } } //----------------------------------------------------------------------------- // Purpose: Click secondary attack whilst holding an object to hurl it. //----------------------------------------------------------------------------- void CWeaponPhysCannon::SecondaryAttack( void ) { #ifndef CLIENT_DLL if ( m_flNextSecondaryAttack > gpGlobals->curtime ) return; CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( pOwner == NULL ) return; // See if we should drop a held item if ( ( m_bActive ) && ( pOwner->m_afButtonPressed & IN_ATTACK2 ) ) { // Drop the held object m_flNextPrimaryAttack = gpGlobals->curtime + 0.5; m_flNextSecondaryAttack = gpGlobals->curtime + 0.5; DetachObject(); DoEffect( EFFECT_READY ); SendWeaponAnim( ACT_VM_PRIMARYATTACK ); } else { // Otherwise pick it up FindObjectResult_t result = FindObject(); switch ( result ) { case OBJECT_FOUND: WeaponSound( SPECIAL1 ); SendWeaponAnim( ACT_VM_PRIMARYATTACK ); m_flNextSecondaryAttack = gpGlobals->curtime + 0.5f; // We found an object. Debounce the button m_nAttack2Debounce |= pOwner->m_nButtons; break; case OBJECT_NOT_FOUND: m_flNextSecondaryAttack = gpGlobals->curtime + 0.1f; CloseElements(); break; case OBJECT_BEING_DETACHED: m_flNextSecondaryAttack = gpGlobals->curtime + 0.01f; break; } DoEffect( EFFECT_HOLDING ); } #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponPhysCannon::WeaponIdle( void ) { if ( HasWeaponIdleTimeElapsed() ) { if ( m_bActive ) { //Shake when holding an item SendWeaponAnim( ACT_VM_RELOAD ); } else { //Otherwise idle simply SendWeaponAnim( ACT_VM_IDLE ); } } } #ifndef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: // Input : *pObject - //----------------------------------------------------------------------------- bool CWeaponPhysCannon::AttachObject( CBaseEntity *pObject, const Vector &vPosition ) { if ( m_bActive ) return false; if ( CanPickupObject( pObject ) == false ) return false; m_grabController.SetIgnorePitch( false ); m_grabController.SetAngleAlignment( 0 ); IPhysicsObject *pPhysics = pObject->VPhysicsGetObject(); // Must be valid if ( !pPhysics ) return false; CHL2MP_Player *pOwner = (CHL2MP_Player *)ToBasePlayer( GetOwner() ); m_bActive = true; if( pOwner ) { // NOTE: This can change the mass; so it must be done before max speed setting Physgun_OnPhysGunPickup( pObject, pOwner, PICKED_UP_BY_CANNON ); } // NOTE :This must happen after OnPhysGunPickup because that can change the mass m_grabController.AttachEntity( pOwner, pObject, pPhysics, false, vPosition, false ); m_hAttachedObject = pObject; m_attachedPositionObjectSpace = m_grabController.m_attachedPositionObjectSpace; m_attachedAnglesPlayerSpace = m_grabController.m_attachedAnglesPlayerSpace; m_bResetOwnerEntity = false; if ( m_hAttachedObject->GetOwnerEntity() == NULL ) { m_hAttachedObject->SetOwnerEntity( pOwner ); m_bResetOwnerEntity = true; } /* if( pOwner ) { pOwner->EnableSprint( false ); float loadWeight = ( 1.0f - GetLoadPercentage() ); float maxSpeed = hl2_walkspeed.GetFloat() + ( ( hl2_normspeed.GetFloat() - hl2_walkspeed.GetFloat() ) * loadWeight ); //Msg( "Load perc: %f -- Movement speed: %f/%f\n", loadWeight, maxSpeed, hl2_normspeed.GetFloat() ); pOwner->SetMaxSpeed( maxSpeed ); }*/ // Don't drop again for a slight delay, in case they were pulling objects near them m_flNextSecondaryAttack = gpGlobals->curtime + 0.4f; DoEffect( EFFECT_HOLDING ); OpenElements(); if ( GetMotorSound() ) { (CSoundEnvelopeController::GetController()).Play( GetMotorSound(), 0.0f, 50 ); (CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 100, 0.5f ); (CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.8f, 0.5f ); } return true; } CWeaponPhysCannon::FindObjectResult_t CWeaponPhysCannon::FindObject( void ) { CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); Assert( pPlayer ); if ( pPlayer == NULL ) return OBJECT_NOT_FOUND; Vector forward; pPlayer->EyeVectors( &forward ); // Setup our positions Vector start = pPlayer->Weapon_ShootPosition(); float testLength = TraceLength() * 4.0f; Vector end = start + forward * testLength; // Try to find an object by looking straight ahead trace_t tr; CTraceFilterNoOwnerTest filter( pPlayer, COLLISION_GROUP_NONE ); UTIL_TraceLine( start, end, MASK_SHOT|CONTENTS_GRATE, &filter, &tr ); // Try again with a hull trace if ( ( tr.fraction == 1.0 ) || ( tr.m_pEnt == NULL ) || ( tr.m_pEnt->IsWorld() ) ) { UTIL_TraceHull( start, end, -Vector(4,4,4), Vector(4,4,4), MASK_SHOT|CONTENTS_GRATE, &filter, &tr ); } CBaseEntity *pEntity = tr.m_pEnt ? tr.m_pEnt->GetRootMoveParent() : NULL; bool bAttach = false; bool bPull = false; // If we hit something, pick it up or pull it if ( ( tr.fraction != 1.0f ) && ( tr.m_pEnt ) && ( tr.m_pEnt->IsWorld() == false ) ) { // Attempt to attach if within range if ( tr.fraction <= 0.25f ) { bAttach = true; } else if ( tr.fraction > 0.25f ) { bPull = true; } } // Find anything within a general cone in front CBaseEntity *pConeEntity = NULL; if (!bAttach && !bPull) { pConeEntity = FindObjectInCone( start, forward, physcannon_cone.GetFloat() ); } if ( pConeEntity ) { pEntity = pConeEntity; // If the object is near, grab it. Else, pull it a bit. if ( pEntity->WorldSpaceCenter().DistToSqr( start ) <= (testLength * testLength) ) { bAttach = true; } else { bPull = true; } } if ( CanPickupObject( pEntity ) == false ) { // Make a noise to signify we can't pick this up if ( !m_flLastDenySoundPlayed ) { m_flLastDenySoundPlayed = true; WeaponSound( SPECIAL3 ); } return OBJECT_NOT_FOUND; } // Check to see if the object is constrained + needs to be ripped off... CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( !Pickup_OnAttemptPhysGunPickup( pEntity, pOwner, PICKED_UP_BY_CANNON ) ) return OBJECT_BEING_DETACHED; if ( bAttach ) { return AttachObject( pEntity, tr.endpos ) ? OBJECT_FOUND : OBJECT_NOT_FOUND; } if ( !bPull ) return OBJECT_NOT_FOUND; // FIXME: This needs to be run through the CanPickupObject logic IPhysicsObject *pObj = pEntity->VPhysicsGetObject(); if ( !pObj ) return OBJECT_NOT_FOUND; // If we're too far, simply start to pull the object towards us Vector pullDir = start - pEntity->WorldSpaceCenter(); VectorNormalize( pullDir ); pullDir *= physcannon_pullforce.GetFloat(); float mass = PhysGetEntityMass( pEntity ); if ( mass < 50.0f ) { pullDir *= (mass + 0.5) * (1/50.0f); } // Nudge it towards us pObj->ApplyForceCenter( pullDir ); return OBJECT_NOT_FOUND; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- CBaseEntity *CWeaponPhysCannon::FindObjectInCone( const Vector &vecOrigin, const Vector &vecDir, float flCone ) { // Find the nearest physics-based item in a cone in front of me. CBaseEntity *list[256]; float flNearestDist = TraceLength() + 1.0; Vector mins = vecOrigin - Vector( flNearestDist, flNearestDist, flNearestDist ); Vector maxs = vecOrigin + Vector( flNearestDist, flNearestDist, flNearestDist ); CBaseEntity *pNearest = NULL; int count = UTIL_EntitiesInBox( list, 256, mins, maxs, 0 ); for( int i = 0 ; i < count ; i++ ) { if ( !list[ i ]->VPhysicsGetObject() ) continue; // Closer than other objects Vector los = ( list[ i ]->WorldSpaceCenter() - vecOrigin ); float flDist = VectorNormalize( los ); if( flDist >= flNearestDist ) continue; // Cull to the cone if ( DotProduct( los, vecDir ) <= flCone ) continue; // Make sure it isn't occluded! trace_t tr; CTraceFilterNoOwnerTest filter( GetOwner(), COLLISION_GROUP_NONE ); UTIL_TraceLine( vecOrigin, list[ i ]->WorldSpaceCenter(), MASK_SHOT|CONTENTS_GRATE, &filter, &tr ); if( tr.m_pEnt == list[ i ] ) { flNearestDist = flDist; pNearest = list[ i ]; } } return pNearest; } #endif //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CGrabController::UpdateObject( CBasePlayer *pPlayer, float flError ) { CBaseEntity *pEntity = GetAttached(); if ( !pEntity ) return false; if ( ComputeError() > flError ) return false; if ( pPlayer->GetGroundEntity() == pEntity ) return false; if (!pEntity->VPhysicsGetObject() ) return false; //Adrian: Oops, our object became motion disabled, let go! IPhysicsObject *pPhys = pEntity->VPhysicsGetObject(); if ( pPhys && pPhys->IsMoveable() == false ) { return false; } if ( m_frameCount == gpGlobals->framecount ) { return true; } m_frameCount = gpGlobals->framecount; Vector forward, right, up; QAngle playerAngles = pPlayer->EyeAngles(); float pitch = AngleDistance(playerAngles.x,0); playerAngles.x = clamp( pitch, -75, 75 ); AngleVectors( playerAngles, &forward, &right, &up ); // Now clamp a sphere of object radius at end to the player's bbox Vector radial = physcollision->CollideGetExtent( pPhys->GetCollide(), vec3_origin, pEntity->GetAbsAngles(), -forward ); Vector player2d = pPlayer->CollisionProp()->OBBMaxs(); float playerRadius = player2d.Length2D(); float flDot = DotProduct( forward, radial ); float radius = playerRadius + fabs( flDot ); float distance = 24 + ( radius * 2.0f ); Vector start = pPlayer->Weapon_ShootPosition(); Vector end = start + ( forward * distance ); trace_t tr; CTraceFilterSkipTwoEntities traceFilter( pPlayer, pEntity, COLLISION_GROUP_NONE ); Ray_t ray; ray.Init( start, end ); enginetrace->TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilter, &tr ); if ( tr.fraction < 0.5 ) { end = start + forward * (radius*0.5f); } else if ( tr.fraction <= 1.0f ) { end = start + forward * ( distance - radius ); } Vector playerMins, playerMaxs, nearest; pPlayer->CollisionProp()->WorldSpaceAABB( &playerMins, &playerMaxs ); Vector playerLine = pPlayer->CollisionProp()->WorldSpaceCenter(); CalcClosestPointOnLine( end, playerLine+Vector(0,0,playerMins.z), playerLine+Vector(0,0,playerMaxs.z), nearest, NULL ); Vector delta = end - nearest; float len = VectorNormalize(delta); if ( len < radius ) { end = nearest + radius * delta; } QAngle angles = TransformAnglesFromPlayerSpace( m_attachedAnglesPlayerSpace, pPlayer ); //Show overlays of radius if ( g_debug_physcannon.GetBool() ) { #ifdef CLIENT_DLL debugoverlay->AddBoxOverlay( end, -Vector( 2,2,2 ), Vector(2,2,2), angles, 0, 255, 255, true, 0 ); debugoverlay->AddBoxOverlay( GetAttached()->WorldSpaceCenter(), -Vector( radius, radius, radius), Vector( radius, radius, radius ), angles, 255, 255, 0, true, 0.0f ); #else NDebugOverlay::Box( end, -Vector( 2,2,2 ), Vector(2,2,2), 0, 255, 0, true, 0 ); NDebugOverlay::Box( GetAttached()->WorldSpaceCenter(), -Vector( radius+5, radius+5, radius+5), Vector( radius+5, radius+5, radius+5 ), 255, 0, 0, true, 0.0f ); #endif } #ifndef CLIENT_DLL // If it has a preferred orientation, update to ensure we're still oriented correctly. Pickup_GetPreferredCarryAngles( pEntity, pPlayer, pPlayer->EntityToWorldTransform(), angles ); // We may be holding a prop that has preferred carry angles if ( m_bHasPreferredCarryAngles ) { matrix3x4_t tmp; ComputePlayerMatrix( pPlayer, tmp ); angles = TransformAnglesToWorldSpace( m_vecPreferredCarryAngles, tmp ); } #endif matrix3x4_t attachedToWorld; Vector offset; AngleMatrix( angles, attachedToWorld ); VectorRotate( m_attachedPositionObjectSpace, attachedToWorld, offset ); SetTargetPosition( end - offset, angles ); return true; } void CWeaponPhysCannon::UpdateObject( void ) { CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); Assert( pPlayer ); float flError = 12; if ( !m_grabController.UpdateObject( pPlayer, flError ) ) { DetachObject(); return; } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CWeaponPhysCannon::DetachObject( bool playSound, bool wasLaunched ) { #ifndef CLIENT_DLL if ( m_bActive == false ) return; CHL2MP_Player *pOwner = (CHL2MP_Player *)ToBasePlayer( GetOwner() ); if( pOwner != NULL ) { pOwner->EnableSprint( true ); pOwner->SetMaxSpeed( hl2_normspeed.GetFloat() ); } CBaseEntity *pObject = m_grabController.GetAttached(); m_grabController.DetachEntity( wasLaunched ); if ( pObject != NULL ) { Pickup_OnPhysGunDrop( pObject, pOwner, wasLaunched ? LAUNCHED_BY_CANNON : DROPPED_BY_CANNON ); } // Stop our looping sound if ( GetMotorSound() ) { (CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.0f, 1.0f ); (CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 50, 1.0f ); } if ( pObject && m_bResetOwnerEntity == true ) { pObject->SetOwnerEntity( NULL ); } m_bActive = false; m_hAttachedObject = NULL; if ( playSound ) { //Play the detach sound WeaponSound( MELEE_MISS ); } #else m_grabController.DetachEntity( wasLaunched ); if ( m_hAttachedObject ) { m_hAttachedObject->VPhysicsDestroyObject(); } #endif } #ifdef CLIENT_DLL void CWeaponPhysCannon::ManagePredictedObject( void ) { CBaseEntity *pAttachedObject = m_hAttachedObject.Get(); if ( m_hAttachedObject ) { // NOTE :This must happen after OnPhysGunPickup because that can change the mass if ( pAttachedObject != GetGrabController().GetAttached() ) { IPhysicsObject *pPhysics = pAttachedObject->VPhysicsGetObject(); if ( pPhysics == NULL ) { solid_t tmpSolid; PhysModelParseSolid( tmpSolid, m_hAttachedObject, pAttachedObject->GetModelIndex() ); pAttachedObject->VPhysicsInitNormal( SOLID_VPHYSICS, 0, false, &tmpSolid ); } pPhysics = pAttachedObject->VPhysicsGetObject(); if ( pPhysics ) { m_grabController.SetIgnorePitch( false ); m_grabController.SetAngleAlignment( 0 ); GetGrabController().AttachEntity( ToBasePlayer( GetOwner() ), pAttachedObject, pPhysics, false, vec3_origin, false ); GetGrabController().m_attachedPositionObjectSpace = m_attachedPositionObjectSpace; GetGrabController().m_attachedAnglesPlayerSpace = m_attachedAnglesPlayerSpace; } } } else { if ( m_hOldAttachedObject && m_hOldAttachedObject->VPhysicsGetObject() ) { GetGrabController().DetachEntity( false ); m_hOldAttachedObject->VPhysicsDestroyObject(); } } m_hOldAttachedObject = m_hAttachedObject; } #endif #ifdef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: Update the pose parameter for the gun //----------------------------------------------------------------------------- void CWeaponPhysCannon::UpdateElementPosition( void ) { CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); float flElementPosition = m_ElementParameter.Interp( gpGlobals->curtime ); if ( ShouldDrawUsingViewModel() ) { if ( pOwner != NULL ) { CBaseViewModel *vm = pOwner->GetViewModel(); if ( vm != NULL ) { vm->SetPoseParameter( "active", flElementPosition ); } } } else { SetPoseParameter( "active", flElementPosition ); } } //----------------------------------------------------------------------------- // Purpose: Think function for the client //----------------------------------------------------------------------------- void CWeaponPhysCannon::ClientThink( void ) { // Update our elements visually UpdateElementPosition(); // Update our effects DoEffectIdle(); } #endif // CLIENT_DLL //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CWeaponPhysCannon::ItemPreFrame() { BaseClass::ItemPreFrame(); #ifdef CLIENT_DLL C_BasePlayer *localplayer = C_BasePlayer::GetLocalPlayer(); if ( localplayer && !localplayer->IsObserver() ) ManagePredictedObject(); #endif // Update the object if the weapon is switched on. if( m_bActive ) { UpdateObject(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponPhysCannon::CheckForTarget( void ) { #ifndef CLIENT_DLL //See if we're suppressing this if ( m_flCheckSuppressTime > gpGlobals->curtime ) return; // holstered if ( IsEffectActive( EF_NODRAW ) ) return; CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( pOwner == NULL ) return; if ( m_bActive ) return; Vector aimDir; pOwner->EyeVectors( &aimDir ); Vector startPos = pOwner->Weapon_ShootPosition(); Vector endPos; VectorMA( startPos, TraceLength(), aimDir, endPos ); trace_t tr; UTIL_TraceHull( startPos, endPos, -Vector(4,4,4), Vector(4,4,4), MASK_SHOT|CONTENTS_GRATE, pOwner, COLLISION_GROUP_NONE, &tr ); if ( ( tr.fraction != 1.0f ) && ( tr.m_pEnt != NULL ) ) { // FIXME: Try just having the elements always open when pointed at a physics object if ( CanPickupObject( tr.m_pEnt ) || Pickup_ForcePhysGunOpen( tr.m_pEnt, pOwner ) ) // if ( ( tr.m_pEnt->VPhysicsGetObject() != NULL ) && ( tr.m_pEnt->GetMoveType() == MOVETYPE_VPHYSICS ) ) { m_nChangeState = ELEMENT_STATE_NONE; OpenElements(); return; } } // Close the elements after a delay to prevent overact state switching if ( ( m_flElementDebounce < gpGlobals->curtime ) && ( m_nChangeState == ELEMENT_STATE_NONE ) ) { m_nChangeState = ELEMENT_STATE_CLOSED; m_flElementDebounce = gpGlobals->curtime + 0.5f; } #endif } //----------------------------------------------------------------------------- // Purpose: Idle effect (pulsing) //----------------------------------------------------------------------------- void CWeaponPhysCannon::DoEffectIdle( void ) { #ifdef CLIENT_DLL StartEffects(); //if ( ShouldDrawUsingViewModel() ) { // Turn on the glow sprites for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ ) { m_Parameters[i].GetScale().SetAbsolute( random->RandomFloat( 0.075f, 0.05f ) * SPRITE_SCALE ); m_Parameters[i].GetAlpha().SetAbsolute( random->RandomInt( 24, 32 ) ); } // Turn on the glow sprites for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ ) { m_Parameters[i].GetScale().SetAbsolute( random->RandomFloat( 3, 5 ) ); m_Parameters[i].GetAlpha().SetAbsolute( random->RandomInt( 200, 255 ) ); } if ( m_EffectState != EFFECT_HOLDING ) { // Turn beams off m_Beams[0].SetVisible( false ); m_Beams[1].SetVisible( false ); m_Beams[2].SetVisible( false ); } } /* else { // Turn on the glow sprites for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ ) { m_Parameters[i].GetScale().SetAbsolute( random->RandomFloat( 0.075f, 0.05f ) * SPRITE_SCALE ); m_Parameters[i].GetAlpha().SetAbsolute( random->RandomInt( 24, 32 ) ); } // Turn on the glow sprites for ( i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ ) { m_Parameters[i].GetScale().SetAbsolute( random->RandomFloat( 3, 5 ) ); m_Parameters[i].GetAlpha().SetAbsolute( random->RandomInt( 200, 255 ) ); } if ( m_EffectState != EFFECT_HOLDING ) { // Turn beams off m_Beams[0].SetVisible( false ); m_Beams[1].SetVisible( false ); m_Beams[2].SetVisible( false ); } } */ #endif } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CWeaponPhysCannon::ItemPostFrame() { CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( pOwner == NULL ) { // We found an object. Debounce the button m_nAttack2Debounce = 0; return; } //Check for object in pickup range if ( m_bActive == false ) { CheckForTarget(); if ( ( m_flElementDebounce < gpGlobals->curtime ) && ( m_nChangeState != ELEMENT_STATE_NONE ) ) { if ( m_nChangeState == ELEMENT_STATE_OPEN ) { OpenElements(); } else if ( m_nChangeState == ELEMENT_STATE_CLOSED ) { CloseElements(); } m_nChangeState = ELEMENT_STATE_NONE; } } // NOTE: Attack2 will be considered to be pressed until the first item is picked up. int nAttack2Mask = pOwner->m_nButtons & (~m_nAttack2Debounce); if ( nAttack2Mask & IN_ATTACK2 ) { SecondaryAttack(); } else { // Reset our debouncer m_flLastDenySoundPlayed = false; if ( m_bActive == false ) { DoEffect( EFFECT_READY ); } } if (( pOwner->m_nButtons & IN_ATTACK2 ) == 0 ) { m_nAttack2Debounce = 0; } if ( pOwner->m_nButtons & IN_ATTACK ) { PrimaryAttack(); } else { WeaponIdle(); } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- #define PHYSCANNON_DANGER_SOUND_RADIUS 128 void CWeaponPhysCannon::LaunchObject( const Vector &vecDir, float flForce ) { CBaseEntity *pObject = m_grabController.GetAttached(); if ( !(m_hLastPuntedObject == pObject && gpGlobals->curtime < m_flRepuntObjectTime) ) { // FIRE!!! if( pObject != NULL ) { DetachObject( false, true ); m_hLastPuntedObject = pObject; m_flRepuntObjectTime = gpGlobals->curtime + 0.5f; // Launch ApplyVelocityBasedForce( pObject, vecDir ); // Don't allow the gun to regrab a thrown object!! m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->curtime + 0.5; Vector center = pObject->WorldSpaceCenter(); //Do repulse effect DoEffect( EFFECT_LAUNCH, ¢er ); m_hAttachedObject = NULL; m_bActive = false; } } // Stop our looping sound if ( GetMotorSound() ) { (CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.0f, 1.0f ); (CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 50, 1.0f ); } //Close the elements and suppress checking for a bit m_nChangeState = ELEMENT_STATE_CLOSED; m_flElementDebounce = gpGlobals->curtime + 0.1f; m_flCheckSuppressTime = gpGlobals->curtime + 0.25f; } bool UTIL_IsCombineBall( CBaseEntity *pEntity ); //----------------------------------------------------------------------------- // Purpose: // Input : *pTarget - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CWeaponPhysCannon::CanPickupObject( CBaseEntity *pTarget ) { #ifndef CLIENT_DLL if ( pTarget == NULL ) return false; if ( pTarget->GetBaseAnimating() && pTarget->GetBaseAnimating()->IsDissolving() ) return false; if ( pTarget->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) ) return false; CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( pOwner && pOwner->GetGroundEntity() == pTarget ) return false; if ( pTarget->VPhysicsIsFlesh( ) ) return false; IPhysicsObject *pObj = pTarget->VPhysicsGetObject(); if ( pObj && pObj->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) return false; if ( UTIL_IsCombineBall( pTarget ) ) { return CBasePlayer::CanPickupObject( pTarget, 0, 0 ); } return CBasePlayer::CanPickupObject( pTarget, physcannon_maxmass.GetFloat(), 0 ); #else return false; #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponPhysCannon::OpenElements( void ) { if ( m_bOpen ) return; WeaponSound( SPECIAL2 ); CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( pOwner == NULL ) return; SendWeaponAnim( ACT_VM_IDLE ); m_bOpen = true; DoEffect( EFFECT_READY ); #ifdef CLIENT // Element prediction m_ElementParameter.InitFromCurrent( 1.0f, 0.2f, INTERP_SPLINE ); m_bOldOpen = true; #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponPhysCannon::CloseElements( void ) { if ( m_bOpen == false ) return; WeaponSound( MELEE_HIT ); CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( pOwner == NULL ) return; SendWeaponAnim( ACT_VM_IDLE ); m_bOpen = false; if ( GetMotorSound() ) { (CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.0f, 1.0f ); (CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 50, 1.0f ); } DoEffect( EFFECT_CLOSED ); #ifdef CLIENT // Element prediction m_ElementParameter.InitFromCurrent( 0.0f, 0.5f, INTERP_SPLINE ); m_bOldOpen = false; #endif } #define PHYSCANNON_MAX_MASS 500 //----------------------------------------------------------------------------- // Purpose: // Output : float //----------------------------------------------------------------------------- float CWeaponPhysCannon::GetLoadPercentage( void ) { float loadWeight = m_grabController.GetLoadWeight(); loadWeight /= physcannon_maxmass.GetFloat(); loadWeight = clamp( loadWeight, 0.0f, 1.0f ); return loadWeight; } //----------------------------------------------------------------------------- // Purpose: // Output : CSoundPatch //----------------------------------------------------------------------------- CSoundPatch *CWeaponPhysCannon::GetMotorSound( void ) { if ( m_sndMotor == NULL ) { CPASAttenuationFilter filter( this ); m_sndMotor = (CSoundEnvelopeController::GetController()).SoundCreate( filter, entindex(), CHAN_STATIC, "Weapon_PhysCannon.HoldSound", ATTN_NORM ); } return m_sndMotor; } //----------------------------------------------------------------------------- // Shuts down sounds //----------------------------------------------------------------------------- void CWeaponPhysCannon::StopLoopingSounds() { if ( m_sndMotor != NULL ) { (CSoundEnvelopeController::GetController()).SoundDestroy( m_sndMotor ); m_sndMotor = NULL; } #ifndef CLIENT_DLL BaseClass::StopLoopingSounds(); #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponPhysCannon::DestroyEffects( void ) { #ifdef CLIENT_DLL // Free our beams m_Beams[0].Release(); m_Beams[1].Release(); m_Beams[2].Release(); #endif // Stop everything StopEffects(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponPhysCannon::StopEffects( bool stopSound ) { // Turn off our effect state DoEffect( EFFECT_NONE ); #ifndef CLIENT_DLL //Shut off sounds if ( stopSound && GetMotorSound() != NULL ) { (CSoundEnvelopeController::GetController()).SoundFadeOut( GetMotorSound(), 0.1f ); } #endif // !CLIENT_DLL } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponPhysCannon::StartEffects( void ) { #ifdef CLIENT_DLL // ------------------------------------------ // Core // ------------------------------------------ if ( m_Parameters[PHYSCANNON_CORE].GetMaterial() == NULL ) { m_Parameters[PHYSCANNON_CORE].GetScale().Init( 0.0f, 1.0f, 0.1f ); m_Parameters[PHYSCANNON_CORE].GetAlpha().Init( 255.0f, 255.0f, 0.1f ); m_Parameters[PHYSCANNON_CORE].SetAttachment( 1 ); if ( m_Parameters[PHYSCANNON_CORE].SetMaterial( PHYSCANNON_CENTER_GLOW ) == false ) { // This means the texture was not found Assert( 0 ); } } // ------------------------------------------ // Blast // ------------------------------------------ if ( m_Parameters[PHYSCANNON_BLAST].GetMaterial() == NULL ) { m_Parameters[PHYSCANNON_BLAST].GetScale().Init( 0.0f, 1.0f, 0.1f ); m_Parameters[PHYSCANNON_BLAST].GetAlpha().Init( 255.0f, 255.0f, 0.1f ); m_Parameters[PHYSCANNON_BLAST].SetAttachment( 1 ); m_Parameters[PHYSCANNON_BLAST].SetVisible( false ); if ( m_Parameters[PHYSCANNON_BLAST].SetMaterial( PHYSCANNON_BLAST_SPRITE ) == false ) { // This means the texture was not found Assert( 0 ); } } // ------------------------------------------ // Glows // ------------------------------------------ const char *attachNamesGlowThirdPerson[NUM_GLOW_SPRITES] = { "fork1m", "fork1t", "fork2m", "fork2t", "fork3m", "fork3t", }; const char *attachNamesGlow[NUM_GLOW_SPRITES] = { "fork1b", "fork1m", "fork1t", "fork2b", "fork2m", "fork2t" }; //Create the glow sprites for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ ) { if ( m_Parameters[i].GetMaterial() != NULL ) continue; m_Parameters[i].GetScale().SetAbsolute( 0.05f * SPRITE_SCALE ); m_Parameters[i].GetAlpha().SetAbsolute( 64.0f ); // Different for different views if ( ShouldDrawUsingViewModel() ) { m_Parameters[i].SetAttachment( LookupAttachment( attachNamesGlow[i-PHYSCANNON_GLOW1] ) ); } else { m_Parameters[i].SetAttachment( LookupAttachment( attachNamesGlowThirdPerson[i-PHYSCANNON_GLOW1] ) ); } m_Parameters[i].SetColor( Vector( 255, 128, 0 ) ); if ( m_Parameters[i].SetMaterial( PHYSCANNON_GLOW_SPRITE ) == false ) { // This means the texture was not found Assert( 0 ); } } // ------------------------------------------ // End caps // ------------------------------------------ const char *attachNamesEndCap[NUM_ENDCAP_SPRITES] = { "fork1t", "fork2t", "fork3t" }; //Create the glow sprites for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ ) { if ( m_Parameters[i].GetMaterial() != NULL ) continue; m_Parameters[i].GetScale().SetAbsolute( 0.05f * SPRITE_SCALE ); m_Parameters[i].GetAlpha().SetAbsolute( 255.0f ); m_Parameters[i].SetAttachment( LookupAttachment( attachNamesEndCap[i-PHYSCANNON_ENDCAP1] ) ); m_Parameters[i].SetVisible( false ); if ( m_Parameters[i].SetMaterial( PHYSCANNON_ENDCAP_SPRITE ) == false ) { // This means the texture was not found Assert( 0 ); } } #endif } //----------------------------------------------------------------------------- // Purpose: Closing effects //----------------------------------------------------------------------------- void CWeaponPhysCannon::DoEffectClosed( void ) { #ifdef CLIENT_DLL // Turn off the end-caps for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ ) { m_Parameters[i].SetVisible( false ); } #endif } //----------------------------------------------------------------------------- // Purpose: Ready effects //----------------------------------------------------------------------------- void CWeaponPhysCannon::DoEffectReady( void ) { #ifdef CLIENT_DLL // Special POV case if ( ShouldDrawUsingViewModel() ) { //Turn on the center sprite m_Parameters[PHYSCANNON_CORE].GetScale().InitFromCurrent( 14.0f, 0.2f ); m_Parameters[PHYSCANNON_CORE].GetAlpha().InitFromCurrent( 128.0f, 0.2f ); m_Parameters[PHYSCANNON_CORE].SetVisible(); } else { //Turn off the center sprite m_Parameters[PHYSCANNON_CORE].GetScale().InitFromCurrent( 8.0f, 0.2f ); m_Parameters[PHYSCANNON_CORE].GetAlpha().InitFromCurrent( 0.0f, 0.2f ); m_Parameters[PHYSCANNON_CORE].SetVisible(); } // Turn on the glow sprites for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ ) { m_Parameters[i].GetScale().InitFromCurrent( 0.4f * SPRITE_SCALE, 0.2f ); m_Parameters[i].GetAlpha().InitFromCurrent( 64.0f, 0.2f ); m_Parameters[i].SetVisible(); } // Turn on the glow sprites for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ ) { m_Parameters[i].SetVisible( false ); } #endif } //----------------------------------------------------------------------------- // Holding effects //----------------------------------------------------------------------------- void CWeaponPhysCannon::DoEffectHolding( void ) { #ifdef CLIENT_DLL if ( ShouldDrawUsingViewModel() ) { // Scale up the center sprite m_Parameters[PHYSCANNON_CORE].GetScale().InitFromCurrent( 16.0f, 0.2f ); m_Parameters[PHYSCANNON_CORE].GetAlpha().InitFromCurrent( 255.0f, 0.1f ); m_Parameters[PHYSCANNON_CORE].SetVisible(); // Prepare for scale up m_Parameters[PHYSCANNON_BLAST].SetVisible( false ); // Turn on the glow sprites for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ ) { m_Parameters[i].GetScale().InitFromCurrent( 0.5f * SPRITE_SCALE, 0.2f ); m_Parameters[i].GetAlpha().InitFromCurrent( 64.0f, 0.2f ); m_Parameters[i].SetVisible(); } // Turn on the glow sprites // NOTE: The last glow is left off for first-person for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES-1); i++ ) { m_Parameters[i].SetVisible(); } // Create our beams CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); CBaseEntity *pBeamEnt = pOwner->GetViewModel(); // Setup the beams m_Beams[0].Init( LookupAttachment( "fork1t" ), 1, pBeamEnt, true ); m_Beams[1].Init( LookupAttachment( "fork2t" ), 1, pBeamEnt, true ); // Set them visible m_Beams[0].SetVisible(); m_Beams[1].SetVisible(); } else { // Scale up the center sprite m_Parameters[PHYSCANNON_CORE].GetScale().InitFromCurrent( 14.0f, 0.2f ); m_Parameters[PHYSCANNON_CORE].GetAlpha().InitFromCurrent( 255.0f, 0.1f ); m_Parameters[PHYSCANNON_CORE].SetVisible(); // Prepare for scale up m_Parameters[PHYSCANNON_BLAST].SetVisible( false ); // Turn on the glow sprites for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ ) { m_Parameters[i].GetScale().InitFromCurrent( 0.5f * SPRITE_SCALE, 0.2f ); m_Parameters[i].GetAlpha().InitFromCurrent( 64.0f, 0.2f ); m_Parameters[i].SetVisible(); } // Turn on the glow sprites for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ ) { m_Parameters[i].SetVisible(); } // Setup the beams m_Beams[0].Init( LookupAttachment( "fork1t" ), 1, this, false ); m_Beams[1].Init( LookupAttachment( "fork2t" ), 1, this, false ); m_Beams[2].Init( LookupAttachment( "fork3t" ), 1, this, false ); // Set them visible m_Beams[0].SetVisible(); m_Beams[1].SetVisible(); m_Beams[2].SetVisible(); } #endif } //----------------------------------------------------------------------------- // Launch effects //----------------------------------------------------------------------------- void CWeaponPhysCannon::DoEffectLaunch( Vector *pos ) { CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( pOwner == NULL ) return; Vector endPos; Vector shotDir; // See if we need to predict this position if ( pos == NULL ) { // Hit an entity if we're holding one if ( m_hAttachedObject ) { endPos = m_hAttachedObject->WorldSpaceCenter(); shotDir = endPos - pOwner->Weapon_ShootPosition(); VectorNormalize( shotDir ); } else { // Otherwise try and find the right spot endPos = pOwner->Weapon_ShootPosition(); pOwner->EyeVectors( &shotDir ); trace_t tr; UTIL_TraceLine( endPos, endPos + ( shotDir * MAX_TRACE_LENGTH ), MASK_SHOT, pOwner, COLLISION_GROUP_NONE, &tr ); endPos = tr.endpos; shotDir = endPos - pOwner->Weapon_ShootPosition(); VectorNormalize( shotDir ); } } else { // Use what is supplied endPos = *pos; shotDir = ( endPos - pOwner->Weapon_ShootPosition() ); VectorNormalize( shotDir ); } // End hit CPVSFilter filter( endPos ); // Don't send this to the owning player, they already had it predicted if ( IsPredicted() ) { filter.UsePredictionRules(); } // Do an impact hit CEffectData data; data.m_vOrigin = endPos; #ifdef CLIENT_DLL data.m_hEntity = GetRefEHandle(); #else data.m_nEntIndex = entindex(); #endif te->DispatchEffect( filter, 0.0, data.m_vOrigin, "PhyscannonImpact", data ); #ifdef CLIENT_DLL //Turn on the blast sprite and scale m_Parameters[PHYSCANNON_BLAST].GetScale().Init( 8.0f, 64.0f, 0.1f ); m_Parameters[PHYSCANNON_BLAST].GetAlpha().Init( 255.0f, 0.0f, 0.2f ); m_Parameters[PHYSCANNON_BLAST].SetVisible(); #endif } //----------------------------------------------------------------------------- // Purpose: Shutdown for the weapon when it's holstered //----------------------------------------------------------------------------- void CWeaponPhysCannon::DoEffectNone( void ) { #ifdef CLIENT_DLL //Turn off main glows m_Parameters[PHYSCANNON_CORE].SetVisible( false ); m_Parameters[PHYSCANNON_BLAST].SetVisible( false ); for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ ) { m_Parameters[i].SetVisible( false ); } // Turn on the glow sprites for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ ) { m_Parameters[i].SetVisible( false ); } m_Beams[0].SetVisible( false ); m_Beams[1].SetVisible( false ); m_Beams[2].SetVisible( false ); #endif } //----------------------------------------------------------------------------- // Purpose: // Input : effectType - //----------------------------------------------------------------------------- void CWeaponPhysCannon::DoEffect( int effectType, Vector *pos ) { m_EffectState = effectType; #ifdef CLIENT_DLL // Save predicted state m_nOldEffectState = m_EffectState; #endif switch( effectType ) { case EFFECT_CLOSED: DoEffectClosed( ); break; case EFFECT_READY: DoEffectReady( ); break; case EFFECT_HOLDING: DoEffectHolding(); break; case EFFECT_LAUNCH: DoEffectLaunch( pos ); break; default: case EFFECT_NONE: DoEffectNone(); break; } } //----------------------------------------------------------------------------- // Purpose: // Input : iIndex - // Output : const char //----------------------------------------------------------------------------- const char *CWeaponPhysCannon::GetShootSound( int iIndex ) const { return BaseClass::GetShootSound( iIndex ); } #ifdef CLIENT_DLL extern void FormatViewModelAttachment( Vector &vOrigin, bool bInverse ); //----------------------------------------------------------------------------- // Purpose: Gets the complete list of values needed to render an effect from an // effect parameter //----------------------------------------------------------------------------- void CWeaponPhysCannon::GetEffectParameters( EffectType_t effectID, color32 &color, float &scale, IMaterial **pMaterial, Vector &vecAttachment ) { const float dt = gpGlobals->curtime; // Get alpha float alpha = m_Parameters[effectID].GetAlpha().Interp( dt ); // Get scale scale = m_Parameters[effectID].GetScale().Interp( dt ); // Get material *pMaterial = (IMaterial *) m_Parameters[effectID].GetMaterial(); // Setup the color color.r = (int) m_Parameters[effectID].GetColor().x; color.g = (int) m_Parameters[effectID].GetColor().y; color.b = (int) m_Parameters[effectID].GetColor().z; color.a = (int) alpha; // Setup the attachment int attachment = m_Parameters[effectID].GetAttachment(); QAngle angles; // Format for first-person if ( ShouldDrawUsingViewModel() ) { CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( pOwner != NULL ) { pOwner->GetViewModel()->GetAttachment( attachment, vecAttachment, angles ); ::FormatViewModelAttachment( vecAttachment, true ); } } else { GetAttachment( attachment, vecAttachment, angles ); } } //----------------------------------------------------------------------------- // Purpose: Whether or not an effect is set to display //----------------------------------------------------------------------------- bool CWeaponPhysCannon::IsEffectVisible( EffectType_t effectID ) { return m_Parameters[effectID].IsVisible(); } //----------------------------------------------------------------------------- // Purpose: Draws the effect sprite, given an effect parameter ID //----------------------------------------------------------------------------- void CWeaponPhysCannon::DrawEffectSprite( EffectType_t effectID ) { color32 color; float scale; IMaterial *pMaterial; Vector vecAttachment; // Don't draw invisible effects if ( IsEffectVisible( effectID ) == false ) return; // Get all of our parameters GetEffectParameters( effectID, color, scale, &pMaterial, vecAttachment ); // Msg( "Scale: %.2f\tAlpha: %.2f\n", scale, alpha ); // Don't render fully translucent objects if ( color.a <= 0.0f ) return; // Draw the sprite CMatRenderContextPtr pRenderContext( materials ); pRenderContext->Bind( pMaterial, this ); DrawSprite( vecAttachment, scale, scale, color ); } //----------------------------------------------------------------------------- // Purpose: Render our third-person effects //----------------------------------------------------------------------------- void CWeaponPhysCannon::DrawEffects( void ) { // Draw the core effects DrawEffectSprite( PHYSCANNON_CORE ); DrawEffectSprite( PHYSCANNON_BLAST ); // Draw the glows for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ ) { DrawEffectSprite( (EffectType_t) i ); } // Draw the endcaps for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ ) { DrawEffectSprite( (EffectType_t) i ); } } //----------------------------------------------------------------------------- // Purpose: Third-person function call to render world model //----------------------------------------------------------------------------- int CWeaponPhysCannon::DrawModel( int flags ) { // Only render these on the transparent pass if ( flags & STUDIO_TRANSPARENCY ) { DrawEffects(); return 1; } // Only do this on the opaque pass return BaseClass::DrawModel( flags ); } //----------------------------------------------------------------------------- // Purpose: First-person function call after viewmodel has been drawn //----------------------------------------------------------------------------- void CWeaponPhysCannon::ViewModelDrawn( C_BaseViewModel *pBaseViewModel ) { // Render our effects DrawEffects(); // Pass this back up BaseClass::ViewModelDrawn( pBaseViewModel ); } //----------------------------------------------------------------------------- // Purpose: We are always considered transparent //----------------------------------------------------------------------------- bool CWeaponPhysCannon::IsTransparent( void ) { return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponPhysCannon::NotifyShouldTransmit( ShouldTransmitState_t state ) { BaseClass::NotifyShouldTransmit(state); if ( state == SHOULDTRANSMIT_END ) { DoEffect( EFFECT_NONE ); } } #endif //----------------------------------------------------------------------------- // EXTERNAL API //----------------------------------------------------------------------------- void PhysCannonForceDrop( CBaseCombatWeapon *pActiveWeapon, CBaseEntity *pOnlyIfHoldingThis ) { CWeaponPhysCannon *pCannon = dynamic_cast(pActiveWeapon); if ( pCannon ) { if ( pOnlyIfHoldingThis ) { pCannon->DropIfEntityHeld( pOnlyIfHoldingThis ); } else { pCannon->ForceDrop(); } } } bool PlayerPickupControllerIsHoldingEntity( CBaseEntity *pPickupControllerEntity, CBaseEntity *pHeldEntity ) { CPlayerPickupController *pController = dynamic_cast(pPickupControllerEntity); return pController ? pController->IsHoldingEntity( pHeldEntity ) : false; } float PhysCannonGetHeldObjectMass( CBaseCombatWeapon *pActiveWeapon, IPhysicsObject *pHeldObject ) { float mass = 0.0f; CWeaponPhysCannon *pCannon = dynamic_cast(pActiveWeapon); if ( pCannon ) { CGrabController &grab = pCannon->GetGrabController(); mass = grab.GetSavedMass( pHeldObject ); } return mass; } CBaseEntity *PhysCannonGetHeldEntity( CBaseCombatWeapon *pActiveWeapon ) { CWeaponPhysCannon *pCannon = dynamic_cast(pActiveWeapon); if ( pCannon ) { CGrabController &grab = pCannon->GetGrabController(); return grab.GetAttached(); } return NULL; } float PlayerPickupGetHeldObjectMass( CBaseEntity *pPickupControllerEntity, IPhysicsObject *pHeldObject ) { float mass = 0.0f; CPlayerPickupController *pController = dynamic_cast(pPickupControllerEntity); if ( pController ) { CGrabController &grab = pController->GetGrabController(); mass = grab.GetSavedMass( pHeldObject ); } return mass; } #ifdef CLIENT_DLL extern void FX_GaussExplosion( const Vector &pos, const Vector &dir, int type ); void CallbackPhyscannonImpact( const CEffectData &data ) { C_BaseEntity *pEnt = data.GetEntity(); if ( pEnt == NULL ) return; Vector vecAttachment; QAngle vecAngles; C_BaseCombatWeapon *pWeapon = dynamic_cast(pEnt); if ( pWeapon == NULL ) return; pWeapon->GetAttachment( 1, vecAttachment, vecAngles ); Vector dir = ( data.m_vOrigin - vecAttachment ); VectorNormalize( dir ); // Do special first-person fix-up if ( pWeapon->GetOwner() == CBasePlayer::GetLocalPlayer() ) { // Translate the attachment entity to the viewmodel C_BasePlayer *pPlayer = dynamic_cast(pWeapon->GetOwner()); if ( pPlayer ) { pEnt = pPlayer->GetViewModel(); } // Format attachment for first-person view! ::FormatViewModelAttachment( vecAttachment, true ); // Explosions at the impact point FX_GaussExplosion( data.m_vOrigin, -dir, 0 ); // Draw a beam BeamInfo_t beamInfo; beamInfo.m_pStartEnt = pEnt; beamInfo.m_nStartAttachment = 1; beamInfo.m_pEndEnt = NULL; beamInfo.m_nEndAttachment = -1; beamInfo.m_vecStart = vec3_origin; beamInfo.m_vecEnd = data.m_vOrigin; beamInfo.m_pszModelName = PHYSCANNON_BEAM_SPRITE; beamInfo.m_flHaloScale = 0.0f; beamInfo.m_flLife = 0.1f; beamInfo.m_flWidth = 12.0f; beamInfo.m_flEndWidth = 4.0f; beamInfo.m_flFadeLength = 0.0f; beamInfo.m_flAmplitude = 0; beamInfo.m_flBrightness = 255.0; beamInfo.m_flSpeed = 0.0f; beamInfo.m_nStartFrame = 0.0; beamInfo.m_flFrameRate = 30.0; beamInfo.m_flRed = 255.0; beamInfo.m_flGreen = 255.0; beamInfo.m_flBlue = 255.0; beamInfo.m_nSegments = 16; beamInfo.m_bRenderable = true; beamInfo.m_nFlags = FBEAM_ONLYNOISEONCE; beams->CreateBeamEntPoint( beamInfo ); } else { // Explosion at the starting point FX_GaussExplosion( vecAttachment, dir, 0 ); } } DECLARE_CLIENT_EFFECT( "PhyscannonImpact", CallbackPhyscannonImpact ); #endif