//========= Copyright Valve Corporation, All rights reserved. ============// // // // //============================================================================= #include "cbase.h" #include "vehicle_jeep_episodic.h" #include "collisionutils.h" #include "npc_alyx_episodic.h" #include "particle_parse.h" #include "particle_system.h" #include "hl2_player.h" #include "in_buttons.h" #include "vphysics/friction.h" #include "vphysicsupdateai.h" #include "physics_npc_solver.h" #include "Sprite.h" #include "weapon_striderbuster.h" #include "npc_strider.h" #include "vguiscreen.h" #include "hl2_vehicle_radar.h" #include "props.h" #include "ai_dynamiclink.h" extern ConVar phys_upimpactforcescale; ConVar jalopy_blocked_exit_max_speed( "jalopy_blocked_exit_max_speed", "50" ); #define JEEP_AMMOCRATE_HITGROUP 5 #define JEEP_AMMO_CRATE_CLOSE_DELAY 2.0f // Bodygroups #define JEEP_RADAR_BODYGROUP 1 #define JEEP_HOPPER_BODYGROUP 2 #define JEEP_CARBAR_BODYGROUP 3 #define RADAR_PANEL_MATERIAL "vgui/screens/radar" #define RADAR_PANEL_WRITEZ "engine/writez" static const char *s_szHazardSprite = "sprites/light_glow01.vmt"; enum { RADAR_MODE_NORMAL = 0, RADAR_MODE_STICKY, }; //========================================================= //========================================================= class CRadarTarget : public CPointEntity { DECLARE_CLASS( CRadarTarget, CPointEntity ); public: void Spawn(); bool IsDisabled() { return m_bDisabled; } int GetType() { return m_iType; } int GetMode() { return m_iMode; } void InputEnable( inputdata_t &inputdata ); void InputDisable( inputdata_t &inputdata ); int ObjectCaps(); private: bool m_bDisabled; int m_iType; int m_iMode; public: float m_flRadius; DECLARE_DATADESC(); }; LINK_ENTITY_TO_CLASS( info_radar_target, CRadarTarget ); BEGIN_DATADESC( CRadarTarget ) DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ), DEFINE_KEYFIELD( m_iType, FIELD_INTEGER, "type" ), DEFINE_KEYFIELD( m_iMode, FIELD_INTEGER, "mode" ), DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable",InputDisable ), END_DATADESC(); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CRadarTarget::Spawn() { BaseClass::Spawn(); AddEffects( EF_NODRAW ); SetMoveType( MOVETYPE_NONE ); SetSolid( SOLID_NONE ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CRadarTarget::InputEnable( inputdata_t &inputdata ) { m_bDisabled = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CRadarTarget::InputDisable( inputdata_t &inputdata ) { m_bDisabled = true; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- int CRadarTarget::ObjectCaps() { return BaseClass::ObjectCaps() | FCAP_ACROSS_TRANSITION; } // // Trigger which detects entities placed in the cargo hold of the jalopy // class CVehicleCargoTrigger : public CBaseEntity { DECLARE_CLASS( CVehicleCargoTrigger, CBaseEntity ); public: // // Creates a trigger with the specified bounds static CVehicleCargoTrigger *Create( const Vector &vecOrigin, const Vector &vecMins, const Vector &vecMaxs, CBaseEntity *pOwner ) { CVehicleCargoTrigger *pTrigger = (CVehicleCargoTrigger *) CreateEntityByName( "trigger_vehicle_cargo" ); if ( pTrigger == NULL ) return NULL; UTIL_SetOrigin( pTrigger, vecOrigin ); UTIL_SetSize( pTrigger, vecMins, vecMaxs ); pTrigger->SetOwnerEntity( pOwner ); pTrigger->SetParent( pOwner ); pTrigger->Spawn(); return pTrigger; } // // Handles the trigger touching its intended quarry void CargoTouch( CBaseEntity *pOther ) { // Cannot be ignoring touches if ( ( m_hIgnoreEntity == pOther ) || ( m_flIgnoreDuration >= gpGlobals->curtime ) ) return; // Make sure this object is being held by the player if ( pOther->VPhysicsGetObject() == NULL || (pOther->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD) == false ) return; if ( StriderBuster_NumFlechettesAttached( pOther ) > 0 ) return; AddCargo( pOther ); } bool AddCargo( CBaseEntity *pOther ) { // For now, only bother with strider busters if ( (FClassnameIs( pOther, "weapon_striderbuster" ) == false) && (FClassnameIs( pOther, "npc_grenade_magna" ) == false) ) return false; // Must be a physics prop CPhysicsProp *pProp = dynamic_cast(pOther); if ( pOther == NULL ) return false; CPropJeepEpisodic *pJeep = dynamic_cast< CPropJeepEpisodic * >( GetOwnerEntity() ); if ( pJeep == NULL ) return false; // Make the player release the item Pickup_ForcePlayerToDropThisObject( pOther ); // Stop colliding with things pOther->VPhysicsDestroyObject(); pOther->SetSolidFlags( FSOLID_NOT_SOLID ); pOther->SetMoveType( MOVETYPE_NONE ); // Parent the object to our owner pOther->SetParent( GetOwnerEntity() ); // The car now owns the entity pJeep->AddPropToCargoHold( pProp ); // Notify the buster that it's been added to the cargo hold. StriderBuster_OnAddToCargoHold( pProp ); // Stop touching this item Disable(); return true; } // // Setup the entity void Spawn( void ) { BaseClass::Spawn(); SetSolid( SOLID_BBOX ); SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID ); SetTouch( &CVehicleCargoTrigger::CargoTouch ); } void Activate() { BaseClass::Activate(); SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID ); // Fixes up old savegames } // // When we've stopped touching this entity, we ignore it void EndTouch( CBaseEntity *pOther ) { if ( pOther == m_hIgnoreEntity ) { m_hIgnoreEntity = NULL; } BaseClass::EndTouch( pOther ); } // // Disables the trigger for a set duration void IgnoreTouches( CBaseEntity *pIgnoreEntity ) { m_hIgnoreEntity = pIgnoreEntity; m_flIgnoreDuration = gpGlobals->curtime + 0.5f; } void Disable( void ) { SetTouch( NULL ); } void Enable( void ) { SetTouch( &CVehicleCargoTrigger::CargoTouch ); } protected: float m_flIgnoreDuration; CHandle m_hIgnoreEntity; DECLARE_DATADESC(); }; LINK_ENTITY_TO_CLASS( trigger_vehicle_cargo, CVehicleCargoTrigger ); BEGIN_DATADESC( CVehicleCargoTrigger ) DEFINE_FIELD( m_flIgnoreDuration, FIELD_TIME ), DEFINE_FIELD( m_hIgnoreEntity, FIELD_EHANDLE ), DEFINE_ENTITYFUNC( CargoTouch ), END_DATADESC(); // // Transition reference point for the vehicle // class CInfoTargetVehicleTransition : public CPointEntity { public: DECLARE_CLASS( CInfoTargetVehicleTransition, CPointEntity ); void Enable( void ) { m_bDisabled = false; } void Disable( void ) { m_bDisabled = true; } bool IsDisabled( void ) const { return m_bDisabled; } private: void InputEnable( inputdata_t &data ) { Enable(); } void InputDisable( inputdata_t &data ) { Disable(); } bool m_bDisabled; DECLARE_DATADESC(); }; BEGIN_DATADESC( CInfoTargetVehicleTransition ) DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable",InputDisable ), END_DATADESC(); LINK_ENTITY_TO_CLASS( info_target_vehicle_transition, CInfoTargetVehicleTransition ); // // CPropJeepEpisodic // LINK_ENTITY_TO_CLASS( prop_vehicle_jeep, CPropJeepEpisodic ); BEGIN_DATADESC( CPropJeepEpisodic ) DEFINE_FIELD( m_bEntranceLocked, FIELD_BOOLEAN ), DEFINE_FIELD( m_bExitLocked, FIELD_BOOLEAN ), DEFINE_FIELD( m_hCargoProp, FIELD_EHANDLE ), DEFINE_FIELD( m_hCargoTrigger, FIELD_EHANDLE ), DEFINE_FIELD( m_bAddingCargo, FIELD_BOOLEAN ), DEFINE_ARRAY( m_hWheelDust, FIELD_EHANDLE, NUM_WHEEL_EFFECTS ), DEFINE_ARRAY( m_hWheelWater, FIELD_EHANDLE, NUM_WHEEL_EFFECTS ), DEFINE_ARRAY( m_hHazardLights, FIELD_EHANDLE, NUM_HAZARD_LIGHTS ), DEFINE_FIELD( m_flCargoStartTime, FIELD_TIME ), DEFINE_FIELD( m_bBlink, FIELD_BOOLEAN ), DEFINE_FIELD( m_bRadarEnabled, FIELD_BOOLEAN ), DEFINE_FIELD( m_bRadarDetectsEnemies, FIELD_BOOLEAN ), DEFINE_FIELD( m_hRadarScreen, FIELD_EHANDLE ), DEFINE_FIELD( m_hLinkControllerFront, FIELD_EHANDLE ), DEFINE_FIELD( m_hLinkControllerRear, FIELD_EHANDLE ), DEFINE_KEYFIELD( m_bBusterHopperVisible, FIELD_BOOLEAN, "CargoVisible" ), // m_flNextAvoidBroadcastTime DEFINE_FIELD( m_flNextWaterSound, FIELD_TIME ), DEFINE_FIELD( m_flNextRadarUpdateTime, FIELD_TIME ), DEFINE_FIELD( m_iNumRadarContacts, FIELD_INTEGER ), DEFINE_ARRAY( m_vecRadarContactPos, FIELD_POSITION_VECTOR, RADAR_MAX_CONTACTS ), DEFINE_ARRAY( m_iRadarContactType, FIELD_INTEGER, RADAR_MAX_CONTACTS ), DEFINE_THINKFUNC( HazardBlinkThink ), DEFINE_OUTPUT( m_OnCompanionEnteredVehicle, "OnCompanionEnteredVehicle" ), DEFINE_OUTPUT( m_OnCompanionExitedVehicle, "OnCompanionExitedVehicle" ), DEFINE_OUTPUT( m_OnHostileEnteredVehicle, "OnHostileEnteredVehicle" ), DEFINE_OUTPUT( m_OnHostileExitedVehicle, "OnHostileExitedVehicle" ), DEFINE_INPUTFUNC( FIELD_VOID, "LockEntrance", InputLockEntrance ), DEFINE_INPUTFUNC( FIELD_VOID, "UnlockEntrance", InputUnlockEntrance ), DEFINE_INPUTFUNC( FIELD_VOID, "LockExit", InputLockExit ), DEFINE_INPUTFUNC( FIELD_VOID, "UnlockExit", InputUnlockExit ), DEFINE_INPUTFUNC( FIELD_VOID, "EnableRadar", InputEnableRadar ), DEFINE_INPUTFUNC( FIELD_VOID, "DisableRadar", InputDisableRadar ), DEFINE_INPUTFUNC( FIELD_VOID, "EnableRadarDetectEnemies", InputEnableRadarDetectEnemies ), DEFINE_INPUTFUNC( FIELD_VOID, "AddBusterToCargo", InputAddBusterToCargo ), DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ), DEFINE_INPUTFUNC( FIELD_VOID, "DisablePhysGun", InputDisablePhysGun ), DEFINE_INPUTFUNC( FIELD_VOID, "EnablePhysGun", InputEnablePhysGun ), DEFINE_INPUTFUNC( FIELD_VOID, "CreateLinkController", InputCreateLinkController ), DEFINE_INPUTFUNC( FIELD_VOID, "DestroyLinkController", InputDestroyLinkController ), DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetCargoHopperVisibility", InputSetCargoVisibility ), END_DATADESC(); IMPLEMENT_SERVERCLASS_ST(CPropJeepEpisodic, DT_CPropJeepEpisodic) //CNetworkVar( int, m_iNumRadarContacts ); SendPropInt( SENDINFO(m_iNumRadarContacts), 8 ), //CNetworkArray( Vector, m_vecRadarContactPos, RADAR_MAX_CONTACTS ); SendPropArray( SendPropVector( SENDINFO_ARRAY(m_vecRadarContactPos), -1, SPROP_COORD), m_vecRadarContactPos ), //CNetworkArray( int, m_iRadarContactType, RADAR_MAX_CONTACTS ); SendPropArray( SendPropInt(SENDINFO_ARRAY(m_iRadarContactType), RADAR_CONTACT_TYPE_BITS ), m_iRadarContactType ), END_SEND_TABLE() //============================================================================= // Episodic jeep CPropJeepEpisodic::CPropJeepEpisodic( void ) : m_bEntranceLocked( false ), m_bExitLocked( false ), m_bAddingCargo( false ), m_flNextAvoidBroadcastTime( 0.0f ) { m_bHasGun = false; m_bUnableToFire = true; m_bRadarDetectsEnemies = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropJeepEpisodic::UpdateOnRemove( void ) { BaseClass::UpdateOnRemove(); // Kill our wheel dust for ( int i = 0; i < NUM_WHEEL_EFFECTS; i++ ) { if ( m_hWheelDust[i] != NULL ) { UTIL_Remove( m_hWheelDust[i] ); } if ( m_hWheelWater[i] != NULL ) { UTIL_Remove( m_hWheelWater[i] ); } } DestroyHazardLights(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropJeepEpisodic::Precache( void ) { PrecacheMaterial( RADAR_PANEL_MATERIAL ); PrecacheMaterial( RADAR_PANEL_WRITEZ ); PrecacheModel( s_szHazardSprite ); PrecacheScriptSound( "JNK_Radar_Ping_Friendly" ); PrecacheScriptSound( "Physics.WaterSplash" ); PrecacheParticleSystem( "WheelDust" ); PrecacheParticleSystem( "WheelSplash" ); BaseClass::Precache(); } //----------------------------------------------------------------------------- // Purpose: // Input : *pPlayer - //----------------------------------------------------------------------------- void CPropJeepEpisodic::EnterVehicle( CBaseCombatCharacter *pPassenger ) { BaseClass::EnterVehicle( pPassenger ); // Turn our hazards off! DestroyHazardLights(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropJeepEpisodic::Spawn( void ) { BaseClass::Spawn(); SetBlocksLOS( false ); CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); if ( pPlayer != NULL ) { pPlayer->m_Local.m_iHideHUD |= HIDEHUD_VEHICLE_CROSSHAIR; } SetBodygroup( JEEP_HOPPER_BODYGROUP, m_bBusterHopperVisible ? 1 : 0); CreateCargoTrigger(); // carbar bodygroup is always on SetBodygroup( JEEP_CARBAR_BODYGROUP, 1 ); m_bRadarDetectsEnemies = false; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CPropJeepEpisodic::Activate() { m_iNumRadarContacts = 0; // Force first contact tone BaseClass::Activate(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropJeepEpisodic::NPC_FinishedEnterVehicle( CAI_BaseNPC *pPassenger, bool bCompanion ) { // FIXME: This will be moved to the NPCs entering and exiting // Fire our outputs if ( bCompanion ) { m_OnCompanionEnteredVehicle.FireOutput( this, pPassenger ); } else { m_OnHostileEnteredVehicle.FireOutput( this, pPassenger ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropJeepEpisodic::NPC_FinishedExitVehicle( CAI_BaseNPC *pPassenger, bool bCompanion ) { // FIXME: This will be moved to the NPCs entering and exiting // Fire our outputs if ( bCompanion ) { m_OnCompanionExitedVehicle.FireOutput( this, pPassenger ); } else { m_OnHostileExitedVehicle.FireOutput( this, pPassenger ); } } //----------------------------------------------------------------------------- // Purpose: // Input : *pPassenger - // bCompanion - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CPropJeepEpisodic::NPC_CanEnterVehicle( CAI_BaseNPC *pPassenger, bool bCompanion ) { // Must be unlocked if ( bCompanion && m_bEntranceLocked ) return false; return BaseClass::NPC_CanEnterVehicle( pPassenger, bCompanion ); } //----------------------------------------------------------------------------- // Purpose: // Input : *pPassenger - // bCompanion - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CPropJeepEpisodic::NPC_CanExitVehicle( CAI_BaseNPC *pPassenger, bool bCompanion ) { // Must be unlocked if ( bCompanion && m_bExitLocked ) return false; return BaseClass::NPC_CanExitVehicle( pPassenger, bCompanion ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropJeepEpisodic::InputLockEntrance( inputdata_t &data ) { m_bEntranceLocked = true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropJeepEpisodic::InputUnlockEntrance( inputdata_t &data ) { m_bEntranceLocked = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropJeepEpisodic::InputLockExit( inputdata_t &data ) { m_bExitLocked = true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropJeepEpisodic::InputUnlockExit( inputdata_t &data ) { m_bExitLocked = false; } //----------------------------------------------------------------------------- // Purpose: Turn on the Jalopy radar device //----------------------------------------------------------------------------- void CPropJeepEpisodic::InputEnableRadar( inputdata_t &data ) { if( m_bRadarEnabled ) return; // Already enabled SetBodygroup( JEEP_RADAR_BODYGROUP, 1 ); SpawnRadarPanel(); } //----------------------------------------------------------------------------- // Purpose: Turn off the Jalopy radar device //----------------------------------------------------------------------------- void CPropJeepEpisodic::InputDisableRadar( inputdata_t &data ) { if( !m_bRadarEnabled ) return; // Already disabled SetBodygroup( JEEP_RADAR_BODYGROUP, 0 ); DestroyRadarPanel(); } //----------------------------------------------------------------------------- // Purpose: Allow the Jalopy radar to detect Hunters and Striders //----------------------------------------------------------------------------- void CPropJeepEpisodic::InputEnableRadarDetectEnemies( inputdata_t &data ) { m_bRadarDetectsEnemies = true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropJeepEpisodic::InputAddBusterToCargo( inputdata_t &data ) { if ( m_hCargoProp != NULL) { ReleasePropFromCargoHold(); m_hCargoProp = NULL; } CBaseEntity *pNewBomb = CreateEntityByName( "weapon_striderbuster" ); if ( pNewBomb ) { DispatchSpawn( pNewBomb ); pNewBomb->Teleport( &m_hCargoTrigger->GetAbsOrigin(), NULL, NULL ); m_hCargoTrigger->AddCargo( pNewBomb ); } } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CPropJeepEpisodic::PassengerInTransition( void ) { // FIXME: Big hack - we need a way to bridge this data better // TODO: Get a list of passengers we can traverse instead CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx(); if ( pAlyx ) { if ( pAlyx->GetPassengerState() == PASSENGER_STATE_ENTERING || pAlyx->GetPassengerState() == PASSENGER_STATE_EXITING ) return true; } return false; } //----------------------------------------------------------------------------- // Purpose: Override velocity if our passenger is transitioning or we're upside-down //----------------------------------------------------------------------------- Vector CPropJeepEpisodic::PhysGunLaunchVelocity( const Vector &forward, float flMass ) { // Disallow if ( PassengerInTransition() ) return vec3_origin; Vector vecPuntDir = BaseClass::PhysGunLaunchVelocity( forward, flMass ); vecPuntDir.z = 150.0f; vecPuntDir *= 600.0f; return vecPuntDir; } //----------------------------------------------------------------------------- // Purpose: Rolls the vehicle when its trying to upright itself from a punt //----------------------------------------------------------------------------- AngularImpulse CPropJeepEpisodic::PhysGunLaunchAngularImpulse( void ) { if ( IsOverturned() ) return AngularImpulse( 0, 300, 0 ); // Don't spin randomly, always spin reliably return AngularImpulse( 0, 0, 0 ); } //----------------------------------------------------------------------------- // Purpose: Get the upright strength based on what state we're in //----------------------------------------------------------------------------- float CPropJeepEpisodic::GetUprightStrength( void ) { // Lesser if overturned if ( IsOverturned() ) return 2.0f; return 0.0f; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropJeepEpisodic::CreateCargoTrigger( void ) { if ( m_hCargoTrigger != NULL ) return; int nAttachment = LookupAttachment( "cargo" ); if ( nAttachment ) { Vector vecAttachOrigin; Vector vecForward, vecRight, vecUp; GetAttachment( nAttachment, vecAttachOrigin, &vecForward, &vecRight, &vecUp ); // Approx size of the hold Vector vecMins( -8.0, -6.0, 0 ); Vector vecMaxs( 8.0, 6.0, 4.0 ); // NDebugOverlay::BoxDirection( vecAttachOrigin, vecMins, vecMaxs, vecForward, 255, 0, 0, 64, 4.0f ); // Create a trigger that lives for a small amount of time m_hCargoTrigger = CVehicleCargoTrigger::Create( vecAttachOrigin, vecMins, vecMaxs, this ); } } //----------------------------------------------------------------------------- // Purpose: If the player uses the jeep while at the back, he gets ammo from the crate instead //----------------------------------------------------------------------------- void CPropJeepEpisodic::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { // Fall back and get in the vehicle instead, skip giving ammo BaseClass::BaseClass::Use( pActivator, pCaller, useType, value ); } #define MIN_WHEEL_DUST_SPEED 5 //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropJeepEpisodic::UpdateWheelDust( void ) { // See if this wheel should emit dust const vehicleparams_t *vehicleData = m_pServerVehicle->GetVehicleParams(); const vehicle_operatingparams_t *carState = m_pServerVehicle->GetVehicleOperatingParams(); bool bAllowDust = vehicleData->steering.dustCloud; // Car must be active bool bCarOn = m_VehiclePhysics.IsOn(); // Must be moving quickly enough or skidding along the ground bool bCreateDust = ( bCarOn && bAllowDust && ( m_VehiclePhysics.GetSpeed() >= MIN_WHEEL_DUST_SPEED || carState->skidSpeed > DEFAULT_SKID_THRESHOLD ) ); // Update our wheel dust Vector vecPos; for ( int i = 0; i < NUM_WHEEL_EFFECTS; i++ ) { m_pServerVehicle->GetWheelContactPoint( i, vecPos ); // Make sure the effect is created if ( m_hWheelDust[i] == NULL ) { // Create the dust effect in place m_hWheelDust[i] = (CParticleSystem *) CreateEntityByName( "info_particle_system" ); if ( m_hWheelDust[i] == NULL ) continue; // Setup our basic parameters m_hWheelDust[i]->KeyValue( "start_active", "0" ); m_hWheelDust[i]->KeyValue( "effect_name", "WheelDust" ); m_hWheelDust[i]->SetParent( this ); m_hWheelDust[i]->SetLocalOrigin( vec3_origin ); DispatchSpawn( m_hWheelDust[i] ); if ( gpGlobals->curtime > 0.5f ) m_hWheelDust[i]->Activate(); } // Make sure the effect is created if ( m_hWheelWater[i] == NULL ) { // Create the dust effect in place m_hWheelWater[i] = (CParticleSystem *) CreateEntityByName( "info_particle_system" ); if ( m_hWheelWater[i] == NULL ) continue; // Setup our basic parameters m_hWheelWater[i]->KeyValue( "start_active", "0" ); m_hWheelWater[i]->KeyValue( "effect_name", "WheelSplash" ); m_hWheelWater[i]->SetParent( this ); m_hWheelWater[i]->SetLocalOrigin( vec3_origin ); DispatchSpawn( m_hWheelWater[i] ); if ( gpGlobals->curtime > 0.5f ) m_hWheelWater[i]->Activate(); } // Turn the dust on or off if ( bCreateDust ) { // Angle the dust out away from the wheels Vector vecForward, vecRight, vecUp; GetVectors( &vecForward, &vecRight, &vecUp ); const vehicle_controlparams_t *vehicleControls = m_pServerVehicle->GetVehicleControlParams(); float flWheelDir = ( i & 1 ) ? 1.0f : -1.0f; QAngle vecAngles; vecForward += vecRight * flWheelDir; vecForward += vecRight * (vehicleControls->steering*0.5f) * flWheelDir; vecForward += vecUp; VectorAngles( vecForward, vecAngles ); // NDebugOverlay::Axis( vecPos, vecAngles, 8.0f, true, 0.1f ); if ( m_WaterData.m_bWheelInWater[i] ) { m_hWheelDust[i]->StopParticleSystem(); // Set us up in the right position m_hWheelWater[i]->StartParticleSystem(); m_hWheelWater[i]->SetAbsAngles( vecAngles ); m_hWheelWater[i]->SetAbsOrigin( vecPos + Vector( 0, 0, 8 ) ); if ( m_flNextWaterSound < gpGlobals->curtime ) { m_flNextWaterSound = gpGlobals->curtime + random->RandomFloat( 0.25f, 1.0f ); EmitSound( "Physics.WaterSplash" ); } } else { m_hWheelWater[i]->StopParticleSystem(); // Set us up in the right position m_hWheelDust[i]->StartParticleSystem(); m_hWheelDust[i]->SetAbsAngles( vecAngles ); m_hWheelDust[i]->SetAbsOrigin( vecPos + Vector( 0, 0, 8 ) ); } } else { // Stop emitting m_hWheelDust[i]->StopParticleSystem(); m_hWheelWater[i]->StopParticleSystem(); } } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- ConVar jalopy_radar_test_ent( "jalopy_radar_test_ent", "none" ); //----------------------------------------------------------------------------- // Purpose: Search for things that the radar detects, and stick them in the // UTILVector that gets sent to the client for radar display. //----------------------------------------------------------------------------- void CPropJeepEpisodic::UpdateRadar( bool forceUpdate ) { bool bDetectedDog = false; if( !m_bRadarEnabled ) return; if( !forceUpdate && gpGlobals->curtime < m_flNextRadarUpdateTime ) return; // Count the targets on radar. If any more targets come on the radar, we beep. int m_iNumOldRadarContacts = m_iNumRadarContacts; m_flNextRadarUpdateTime = gpGlobals->curtime + RADAR_UPDATE_FREQUENCY; m_iNumRadarContacts = 0; CBaseEntity *pEnt = gEntList.FirstEnt(); string_t iszRadarTarget = FindPooledString( "info_radar_target" ); string_t iszStriderName = FindPooledString( "npc_strider" ); string_t iszHunterName = FindPooledString( "npc_hunter" ); string_t iszTestName = FindPooledString( jalopy_radar_test_ent.GetString() ); Vector vecJalopyOrigin = WorldSpaceCenter(); while( pEnt != NULL ) { int type = RADAR_CONTACT_NONE; if( pEnt->m_iClassname == iszRadarTarget ) { CRadarTarget *pTarget = dynamic_cast(pEnt); if( pTarget != NULL && !pTarget->IsDisabled() ) { if( pTarget->m_flRadius < 0 || vecJalopyOrigin.DistToSqr(pTarget->GetAbsOrigin()) <= Square(pTarget->m_flRadius) ) { // This item has been detected. type = pTarget->GetType(); if( type == RADAR_CONTACT_DOG ) bDetectedDog = true;// used to prevent Alyx talking about the radar (see below) if( pTarget->GetMode() == RADAR_MODE_STICKY ) { // This beacon was just detected. Now change the radius to infinite // so that it will never go off the radar due to distance. pTarget->m_flRadius = -1; } } } } else if ( m_bRadarDetectsEnemies ) { if ( pEnt->m_iClassname == iszStriderName ) { CNPC_Strider *pStrider = dynamic_cast(pEnt); if( !pStrider || !pStrider->CarriedByDropship() ) { // Ignore striders which are carried by dropships. type = RADAR_CONTACT_LARGE_ENEMY; } } if ( pEnt->m_iClassname == iszHunterName ) { type = RADAR_CONTACT_ENEMY; } } if( type != RADAR_CONTACT_NONE ) { Vector vecPos = pEnt->WorldSpaceCenter(); m_vecRadarContactPos.Set( m_iNumRadarContacts, vecPos ); m_iRadarContactType.Set( m_iNumRadarContacts, type ); m_iNumRadarContacts++; if( m_iNumRadarContacts == RADAR_MAX_CONTACTS ) break; } pEnt = gEntList.NextEnt(pEnt); } if( m_iNumRadarContacts > m_iNumOldRadarContacts ) { // Play a bleepy sound if( !bDetectedDog ) { EmitSound( "JNK_Radar_Ping_Friendly" ); } //Notify Alyx so she can talk about the radar contact CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx(); if( !bDetectedDog && pAlyx != NULL && pAlyx->GetVehicle() ) { pAlyx->SpeakIfAllowed( TLK_PASSENGER_NEW_RADAR_CONTACT ); } } if( bDetectedDog ) { // Update the radar much more frequently when dog is around. m_flNextRadarUpdateTime = gpGlobals->curtime + RADAR_UPDATE_FREQUENCY_FAST; } //Msg("Server detected %d objects\n", m_iNumRadarContacts ); CBasePlayer *pPlayer = AI_GetSinglePlayer(); CSingleUserRecipientFilter filter(pPlayer); UserMessageBegin( filter, "UpdateJalopyRadar" ); WRITE_BYTE( 0 ); // end marker MessageEnd(); // send message } ConVar jalopy_cargo_anim_time( "jalopy_cargo_anim_time", "1.0" ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropJeepEpisodic::UpdateCargoEntry( void ) { // Don't bother if we have no prop to move if ( m_hCargoProp == NULL ) return; // If we're past our animation point, then we're already done if ( m_flCargoStartTime + jalopy_cargo_anim_time.GetFloat() < gpGlobals->curtime ) { // Close the hold immediately if we're finished if ( m_bAddingCargo ) { m_flAmmoCrateCloseTime = gpGlobals->curtime; m_bAddingCargo = false; } return; } // Get our target point int nAttachment = LookupAttachment( "cargo" ); Vector vecTarget, vecOut; QAngle vecAngles; GetAttachmentLocal( nAttachment, vecTarget, vecAngles ); // Find where we are in the blend and bias it for a fast entry and slow ease-out float flPerc = (jalopy_cargo_anim_time.GetFloat()) ? (( gpGlobals->curtime - m_flCargoStartTime ) / jalopy_cargo_anim_time.GetFloat()) : 1.0f; flPerc = Bias( flPerc, 0.75f ); VectorLerp( m_hCargoProp->GetLocalOrigin(), vecTarget, flPerc, vecOut ); // Get our target orientation CPhysicsProp *pProp = dynamic_cast(m_hCargoProp.Get()); if ( pProp == NULL ) return; // Slerp our quaternions to find where we are this frame Quaternion qtTarget; QAngle qa( 0, 90, 0 ); qa += pProp->PreferredCarryAngles(); AngleQuaternion( qa, qtTarget ); // FIXME: Find the real offset to make this sit properly Quaternion qtCurrent; AngleQuaternion( pProp->GetLocalAngles(), qtCurrent ); Quaternion qtOut; QuaternionSlerp( qtCurrent, qtTarget, flPerc, qtOut ); // Put it back to angles QuaternionAngles( qtOut, vecAngles ); // Finally, take these new position m_hCargoProp->SetLocalOrigin( vecOut ); m_hCargoProp->SetLocalAngles( vecAngles ); // Push the closing out into the future to make sure we don't try and close at the same time m_flAmmoCrateCloseTime += gpGlobals->frametime; } #define VEHICLE_AVOID_BROADCAST_RATE 0.5f //----------------------------------------------------------------------------- // Purpose: This function isn't really what we want //----------------------------------------------------------------------------- void CPropJeepEpisodic::CreateAvoidanceZone( void ) { if ( m_flNextAvoidBroadcastTime > gpGlobals->curtime ) return; // Only do this when we're stopped if ( m_VehiclePhysics.GetSpeed() > 5.0f ) return; float flHullRadius = CollisionProp()->BoundingRadius2D(); Vector vecPos; CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.33f, 0.25f ), &vecPos ); CSoundEnt::InsertSound( SOUND_MOVE_AWAY, vecPos, (flHullRadius*0.4f), VEHICLE_AVOID_BROADCAST_RATE, this ); // NDebugOverlay::Sphere( vecPos, vec3_angle, flHullRadius*0.4f, 255, 0, 0, 0, true, VEHICLE_AVOID_BROADCAST_RATE ); CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.66f, 0.25f ), &vecPos ); CSoundEnt::InsertSound( SOUND_MOVE_AWAY, vecPos, (flHullRadius*0.4f), VEHICLE_AVOID_BROADCAST_RATE, this ); // NDebugOverlay::Sphere( vecPos, vec3_angle, flHullRadius*0.4f, 255, 0, 0, 0, true, VEHICLE_AVOID_BROADCAST_RATE ); // Don't broadcast again until these are done m_flNextAvoidBroadcastTime = gpGlobals->curtime + VEHICLE_AVOID_BROADCAST_RATE; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropJeepEpisodic::Think( void ) { BaseClass::Think(); // If our passenger is transitioning, then don't let the player drive off CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx(); if ( pAlyx && pAlyx->GetPassengerState() == PASSENGER_STATE_EXITING ) { m_throttleDisableTime = gpGlobals->curtime + 0.25f; } // Update our cargo entering our hold UpdateCargoEntry(); // See if the wheel dust should be on or off UpdateWheelDust(); // Update the radar, of course. UpdateRadar(); if ( m_hCargoTrigger && !m_hCargoProp && !m_hCargoTrigger->m_pfnTouch ) { m_hCargoTrigger->Enable(); } CreateAvoidanceZone(); } //----------------------------------------------------------------------------- // Purpose: // Input : *pEntity - //----------------------------------------------------------------------------- void CPropJeepEpisodic::AddPropToCargoHold( CPhysicsProp *pProp ) { // The hold must be empty to add something to it if ( m_hCargoProp != NULL ) { Assert( 0 ); return; } // Take the prop as our cargo m_hCargoProp = pProp; m_flCargoStartTime = gpGlobals->curtime; m_bAddingCargo = true; } //----------------------------------------------------------------------------- // Purpose: Drops the cargo from the hold //----------------------------------------------------------------------------- void CPropJeepEpisodic::ReleasePropFromCargoHold( void ) { // Pull the object free! m_hCargoProp->SetParent( NULL ); m_hCargoProp->CreateVPhysics(); if ( m_hCargoTrigger ) { m_hCargoTrigger->Enable(); m_hCargoTrigger->IgnoreTouches( m_hCargoProp ); } } //----------------------------------------------------------------------------- // Purpose: If the player is trying to pull the cargo out of the hold using the physcannon, let him // Output : Returns the cargo to pick up, if all the conditions are met //----------------------------------------------------------------------------- CBaseEntity *CPropJeepEpisodic::OnFailedPhysGunPickup( Vector vPhysgunPos ) { // Make sure we're available to open if ( m_hCargoProp != NULL ) { // Player's forward direction Vector vecPlayerForward; CBasePlayer *pPlayer = AI_GetSinglePlayer(); if ( pPlayer == NULL ) return NULL; pPlayer->EyeVectors( &vecPlayerForward ); // Origin and facing of the cargo hold Vector vecCargoOrigin; Vector vecCargoForward; GetAttachment( "cargo", vecCargoOrigin, &vecCargoForward ); // Direction from the cargo to the player's position Vector vecPickupDir = ( vecCargoOrigin - vPhysgunPos ); float flDist = VectorNormalize( vecPickupDir ); // We need to make sure the player's position is within a cone near the opening and that they're also facing the right way bool bInCargoRange = ( (flDist < (15.0f * 12.0f)) && DotProduct( vecCargoForward, vecPickupDir ) < 0.1f ); bool bFacingCargo = DotProduct( vecPlayerForward, vecPickupDir ) > 0.975f; // If we're roughly pulling at the item, pick that up if ( bInCargoRange && bFacingCargo ) { // Save this for later CBaseEntity *pCargo = m_hCargoProp; // Drop the cargo ReleasePropFromCargoHold(); // Forget the item but pass it back as the object to pick up m_hCargoProp = NULL; return pCargo; } } return BaseClass::OnFailedPhysGunPickup( vPhysgunPos ); } // adds a collision solver for any small props that are stuck under the vehicle static void SolveBlockingProps( CPropJeepEpisodic *pVehicleEntity, IPhysicsObject *pVehiclePhysics ) { CUtlVector solveList; float vehicleMass = pVehiclePhysics->GetMass(); Vector vehicleUp; pVehicleEntity->GetVectors( NULL, NULL, &vehicleUp ); IPhysicsFrictionSnapshot *pSnapshot = pVehiclePhysics->CreateFrictionSnapshot(); while ( pSnapshot->IsValid() ) { IPhysicsObject *pOther = pSnapshot->GetObject(1); float otherMass = pOther->GetMass(); CBaseEntity *pOtherEntity = static_cast(pOther->GetGameData()); Assert(pOtherEntity); if ( pOtherEntity && pOtherEntity->GetMoveType() == MOVETYPE_VPHYSICS && pOther->IsMoveable() && (otherMass*4.0f) < vehicleMass ) { Vector normal; pSnapshot->GetSurfaceNormal(normal); // this points down in the car's reference frame, then it's probably trapped under the car if ( DotProduct(normal, vehicleUp) < -0.9f ) { Vector point, pointLocal; pSnapshot->GetContactPoint(point); VectorITransform( point, pVehicleEntity->EntityToWorldTransform(), pointLocal ); Vector bottomPoint = physcollision->CollideGetExtent( pVehiclePhysics->GetCollide(), vec3_origin, vec3_angle, Vector(0,0,-1) ); // make sure it's under the bottom of the car float bottomPlane = DotProduct(bottomPoint,vehicleUp)+8; // 8 inches above bottom if ( DotProduct( pointLocal, vehicleUp ) <= bottomPlane ) { //Msg("Solved %s\n", pOtherEntity->GetClassname()); if ( solveList.Find(pOtherEntity) < 0 ) { solveList.AddToTail(pOtherEntity); } } } } pSnapshot->NextFrictionData(); } pVehiclePhysics->DestroyFrictionSnapshot( pSnapshot ); if ( solveList.Count() ) { for ( int i = 0; i < solveList.Count(); i++ ) { EntityPhysics_CreateSolver( pVehicleEntity, solveList[i], true, 4.0f ); } pVehiclePhysics->RecheckContactPoints(); } } static void SimpleCollisionResponse( Vector velocityIn, const Vector &normal, float coefficientOfRestitution, Vector *pVelocityOut ) { Vector Vn = DotProduct(velocityIn,normal) * normal; Vector Vt = velocityIn - Vn; *pVelocityOut = Vt - coefficientOfRestitution * Vn; } static void KillBlockingEnemyNPCs( CBasePlayer *pPlayer, CBaseEntity *pVehicleEntity, IPhysicsObject *pVehiclePhysics ) { Vector velocity; pVehiclePhysics->GetVelocity( &velocity, NULL ); float vehicleMass = pVehiclePhysics->GetMass(); // loop through the contacts and look for enemy NPCs that we're pushing on CUtlVector npcList; CUtlVector forceList; CUtlVector contactList; IPhysicsFrictionSnapshot *pSnapshot = pVehiclePhysics->CreateFrictionSnapshot(); while ( pSnapshot->IsValid() ) { IPhysicsObject *pOther = pSnapshot->GetObject(1); float otherMass = pOther->GetMass(); CBaseEntity *pOtherEntity = static_cast(pOther->GetGameData()); CAI_BaseNPC *pNPC = pOtherEntity ? pOtherEntity->MyNPCPointer() : NULL; // Is this an enemy NPC with a small enough mass? if ( pNPC && pPlayer->IRelationType(pNPC) != D_LI && ((otherMass*2.0f) < vehicleMass) ) { // accumulate the stress force for this NPC in the lsit float force = pSnapshot->GetNormalForce(); Vector normal; pSnapshot->GetSurfaceNormal(normal); normal *= force; int index = npcList.Find(pNPC); if ( index < 0 ) { vphysicsupdateai_t *pUpdate = NULL; if ( pNPC->VPhysicsGetObject() && pNPC->VPhysicsGetObject()->GetShadowController() && pNPC->GetMoveType() == MOVETYPE_STEP ) { if ( pNPC->HasDataObjectType(VPHYSICSUPDATEAI) ) { pUpdate = static_cast(pNPC->GetDataObject(VPHYSICSUPDATEAI)); // kill this guy if I've been pushing him for more than half a second and I'm // still pushing in his direction if ( (gpGlobals->curtime - pUpdate->startUpdateTime) > 0.5f && DotProduct(velocity,normal) > 0) { index = npcList.AddToTail(pNPC); forceList.AddToTail( normal ); Vector pos; pSnapshot->GetContactPoint(pos); contactList.AddToTail(pos); } } else { pUpdate = static_cast(pNPC->CreateDataObject( VPHYSICSUPDATEAI )); pUpdate->startUpdateTime = gpGlobals->curtime; } // update based on vphysics for the next second // this allows the car to push the NPC pUpdate->stopUpdateTime = gpGlobals->curtime + 1.0f; float maxAngular; pNPC->VPhysicsGetObject()->GetShadowController()->GetMaxSpeed( &pUpdate->savedShadowControllerMaxSpeed, &maxAngular ); pNPC->VPhysicsGetObject()->GetShadowController()->MaxSpeed( 1.0f, maxAngular ); } } else { forceList[index] += normal; } } pSnapshot->NextFrictionData(); } pVehiclePhysics->DestroyFrictionSnapshot( pSnapshot ); // now iterate the list and check each cumulative force against the threshold if ( npcList.Count() ) { for ( int i = npcList.Count(); --i >= 0; ) { Vector damageForce; npcList[i]->VPhysicsGetObject()->GetVelocity( &damageForce, NULL ); Vector vel; pVehiclePhysics->GetVelocityAtPoint( contactList[i], &vel ); damageForce -= vel; Vector normal = forceList[i]; VectorNormalize(normal); SimpleCollisionResponse( damageForce, normal, 1.0, &damageForce ); damageForce += (normal * 300.0f); damageForce *= npcList[i]->VPhysicsGetObject()->GetMass(); float len = damageForce.Length(); damageForce.z += len*phys_upimpactforcescale.GetFloat(); Vector vehicleForce = -damageForce; CTakeDamageInfo dmgInfo( pVehicleEntity, pVehicleEntity, damageForce, contactList[i], 200.0f, DMG_CRUSH|DMG_VEHICLE ); npcList[i]->TakeDamage( dmgInfo ); pVehiclePhysics->ApplyForceOffset( vehicleForce, contactList[i] ); PhysCollisionSound( pVehicleEntity, npcList[i]->VPhysicsGetObject(), CHAN_BODY, pVehiclePhysics->GetMaterialIndex(), npcList[i]->VPhysicsGetObject()->GetMaterialIndex(), gpGlobals->frametime, 200.0f ); } } } void CPropJeepEpisodic::DriveVehicle( float flFrameTime, CUserCmd *ucmd, int iButtonsDown, int iButtonsReleased ) { /* The car headlight hurts perf, there's no timer to turn it off automatically, and we haven't built any gameplay around it. Furthermore, I don't think I've ever seen a playtester turn it on. if ( ucmd->impulse == 100 ) { if (HeadlightIsOn()) { HeadlightTurnOff(); } else { HeadlightTurnOn(); } }*/ if ( ucmd->forwardmove != 0.0f ) { //Msg("Push V: %.2f, %.2f, %.2f\n", ucmd->forwardmove, carState->engineRPM, carState->speed ); CBasePlayer *pPlayer = ToBasePlayer(GetDriver()); if ( pPlayer && VPhysicsGetObject() ) { KillBlockingEnemyNPCs( pPlayer, this, VPhysicsGetObject() ); SolveBlockingProps( this, VPhysicsGetObject() ); } } BaseClass::DriveVehicle(flFrameTime, ucmd, iButtonsDown, iButtonsReleased); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropJeepEpisodic::CreateHazardLights( void ) { static const char *s_szAttach[NUM_HAZARD_LIGHTS] = { "rearlight_r", "rearlight_l", "headlight_r", "headlight_l", }; // Turn on the hazards! for ( int i = 0; i < NUM_HAZARD_LIGHTS; i++ ) { if ( m_hHazardLights[i] == NULL ) { m_hHazardLights[i] = CSprite::SpriteCreate( s_szHazardSprite, GetLocalOrigin(), false ); if ( m_hHazardLights[i] ) { m_hHazardLights[i]->SetTransparency( kRenderWorldGlow, 255, 220, 40, 255, kRenderFxNoDissipation ); m_hHazardLights[i]->SetAttachment( this, LookupAttachment( s_szAttach[i] ) ); m_hHazardLights[i]->SetGlowProxySize( 2.0f ); m_hHazardLights[i]->TurnOff(); if ( i < 2 ) { // Rear lights are red m_hHazardLights[i]->SetColor( 255, 0, 0 ); m_hHazardLights[i]->SetScale( 1.0f ); } else { // Font lights are white m_hHazardLights[i]->SetScale( 1.0f ); } } } } // We start off m_bBlink = false; // Setup our blink SetContextThink( &CPropJeepEpisodic::HazardBlinkThink, gpGlobals->curtime + 0.1f, "HazardBlink" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropJeepEpisodic::DestroyHazardLights( void ) { for ( int i = 0; i < NUM_HAZARD_LIGHTS; i++ ) { if ( m_hHazardLights[i] != NULL ) { UTIL_Remove( m_hHazardLights[i] ); } } SetContextThink( NULL, gpGlobals->curtime, "HazardBlink" ); } //----------------------------------------------------------------------------- // Purpose: // Input : nRole - //----------------------------------------------------------------------------- void CPropJeepEpisodic::ExitVehicle( int nRole ) { BaseClass::ExitVehicle( nRole ); CreateHazardLights(); } void CPropJeepEpisodic::SetBusterHopperVisibility(bool visible) { // if we're there already do nothing if (visible == m_bBusterHopperVisible) return; SetBodygroup( JEEP_HOPPER_BODYGROUP, visible ? 1 : 0); m_bBusterHopperVisible = visible; } void CPropJeepEpisodic::InputSetCargoVisibility( inputdata_t &data ) { bool visible = data.value.Bool(); SetBusterHopperVisibility( visible ); } //----------------------------------------------------------------------------- // THIS CODE LIFTED RIGHT OUT OF TF2, to defer the pain of making vgui-on-an-entity // code available to all CBaseAnimating. //----------------------------------------------------------------------------- void CPropJeepEpisodic::SpawnRadarPanel() { // FIXME: Deal with dynamically resizing control panels? // If we're attached to an entity, spawn control panels on it instead of use CBaseAnimating *pEntityToSpawnOn = this; char *pOrgLL = "controlpanel0_ll"; char *pOrgUR = "controlpanel0_ur"; Assert( pEntityToSpawnOn ); // Lookup the attachment point... int nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(pOrgLL); if (nLLAttachmentIndex <= 0) { return; } int nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(pOrgUR); if (nURAttachmentIndex <= 0) { return; } const char *pScreenName = "jalopy_radar_panel"; const char *pScreenClassname = "vgui_screen"; // Compute the screen size from the attachment points... matrix3x4_t panelToWorld; pEntityToSpawnOn->GetAttachment( nLLAttachmentIndex, panelToWorld ); matrix3x4_t worldToPanel; MatrixInvert( panelToWorld, worldToPanel ); // Now get the lower right position + transform into panel space Vector lr, lrlocal; pEntityToSpawnOn->GetAttachment( nURAttachmentIndex, panelToWorld ); MatrixGetColumn( panelToWorld, 3, lr ); VectorTransform( lr, worldToPanel, lrlocal ); float flWidth = lrlocal.x; float flHeight = lrlocal.y; CVGuiScreen *pScreen = CreateVGuiScreen( pScreenClassname, pScreenName, pEntityToSpawnOn, this, nLLAttachmentIndex ); pScreen->SetActualSize( flWidth, flHeight ); pScreen->SetActive( true ); pScreen->SetOverlayMaterial( RADAR_PANEL_WRITEZ ); pScreen->SetTransparency( true ); m_hRadarScreen.Set( pScreen ); m_bRadarEnabled = true; m_iNumRadarContacts = 0; m_flNextRadarUpdateTime = gpGlobals->curtime - 1.0f; } //----------------------------------------------------------------------------- void CPropJeepEpisodic::DestroyRadarPanel() { Assert( m_hRadarScreen != NULL ); m_hRadarScreen->SUB_Remove(); m_bRadarEnabled = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropJeepEpisodic::HazardBlinkThink( void ) { if ( m_bBlink ) { for ( int i = 0; i < NUM_HAZARD_LIGHTS; i++ ) { if ( m_hHazardLights[i] ) { m_hHazardLights[i]->SetBrightness( 0, 0.1f ); } } SetContextThink( &CPropJeepEpisodic::HazardBlinkThink, gpGlobals->curtime + 0.25f, "HazardBlink" ); } else { for ( int i = 0; i < NUM_HAZARD_LIGHTS; i++ ) { if ( m_hHazardLights[i] ) { m_hHazardLights[i]->SetBrightness( 255, 0.1f ); m_hHazardLights[i]->TurnOn(); } } SetContextThink( &CPropJeepEpisodic::HazardBlinkThink, gpGlobals->curtime + 0.5f, "HazardBlink" ); } m_bBlink = !m_bBlink; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropJeepEpisodic::HandleWater( void ) { // Only check the wheels and engine in water if we have a driver (player). if ( !GetDriver() ) return; // Update our internal state CheckWater(); // Save of data from last think. for ( int iWheel = 0; iWheel < JEEP_WHEEL_COUNT; ++iWheel ) { m_WaterData.m_bWheelWasInWater[iWheel] = m_WaterData.m_bWheelInWater[iWheel]; } } //----------------------------------------------------------------------------- // Purpose: Report our lock state //----------------------------------------------------------------------------- int CPropJeepEpisodic::DrawDebugTextOverlays( void ) { int text_offset = BaseClass::DrawDebugTextOverlays(); if ( m_debugOverlays & OVERLAY_TEXT_BIT ) { EntityText( text_offset, CFmtStr("Entrance: %s", m_bEntranceLocked ? "Locked" : "Unlocked" ), 0 ); text_offset++; EntityText( text_offset, CFmtStr("Exit: %s", m_bExitLocked ? "Locked" : "Unlocked" ), 0 ); text_offset++; } return text_offset; } #define TRANSITION_SEARCH_RADIUS (100*12) //----------------------------------------------------------------------------- // Purpose: Teleport the car to a destination that will cause it to transition if it's not going to otherwise //----------------------------------------------------------------------------- void CPropJeepEpisodic::InputOutsideTransition( inputdata_t &inputdata ) { // Teleport into the new map CBasePlayer *pPlayer = AI_GetSinglePlayer(); Vector vecTeleportPos; QAngle vecTeleportAngles; // Get our bounds Vector vecSurroundMins, vecSurroundMaxs; CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs ); vecSurroundMins -= WorldSpaceCenter(); vecSurroundMaxs -= WorldSpaceCenter(); Vector vecBestPos; QAngle vecBestAngles; CInfoTargetVehicleTransition *pEntity = NULL; bool bSucceeded = false; // Find all entities of the correct name and try and sit where they're at while ( ( pEntity = (CInfoTargetVehicleTransition *) gEntList.FindEntityByClassname( pEntity, "info_target_vehicle_transition" ) ) != NULL ) { // Must be enabled if ( pEntity->IsDisabled() ) continue; // Must be within range if ( ( pEntity->GetAbsOrigin() - pPlayer->GetAbsOrigin() ).LengthSqr() > Square( TRANSITION_SEARCH_RADIUS ) ) continue; vecTeleportPos = pEntity->GetAbsOrigin(); vecTeleportAngles = pEntity->GetAbsAngles() + QAngle( 0, -90, 0 ); // Vehicle is always off by 90 degrees // Rotate to face the destination angles Vector vecMins; Vector vecMaxs; VectorRotate( vecSurroundMins, vecTeleportAngles, vecMins ); VectorRotate( vecSurroundMaxs, vecTeleportAngles, vecMaxs ); if ( vecMaxs.x < vecMins.x ) V_swap( vecMins.x, vecMaxs.x ); if ( vecMaxs.y < vecMins.y ) V_swap( vecMins.y, vecMaxs.y ); if ( vecMaxs.z < vecMins.z ) V_swap( vecMins.z, vecMaxs.z ); // Move up vecTeleportPos.z += ( vecMaxs.z - vecMins.z ); trace_t tr; UTIL_TraceHull( vecTeleportPos, vecTeleportPos - Vector( 0, 0, 128 ), vecMins, vecMaxs, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); if ( tr.startsolid == false && tr.allsolid == false && tr.fraction < 1.0f ) { // Store this off vecBestPos = tr.endpos; vecBestAngles = vecTeleportAngles; bSucceeded = true; // If this point isn't visible, then stop looking and use it if ( pPlayer->FInViewCone( tr.endpos ) == false ) break; } } // See if we're finished if ( bSucceeded ) { Teleport( &vecTeleportPos, &vecTeleportAngles, NULL ); return; } // TODO: We found no valid teleport points, so try to find them dynamically Warning("No valid vehicle teleport points!\n"); } //----------------------------------------------------------------------------- // Purpose: Stop players punting the car around. //----------------------------------------------------------------------------- void CPropJeepEpisodic::InputDisablePhysGun( inputdata_t &data ) { AddEFlags( EFL_NO_PHYSCANNON_INTERACTION ); } //----------------------------------------------------------------------------- // Purpose: Return to normal //----------------------------------------------------------------------------- void CPropJeepEpisodic::InputEnablePhysGun( inputdata_t &data ) { RemoveEFlags( EFL_NO_PHYSCANNON_INTERACTION ); } //----------------------------------------------------------------------------- // Create and parent two radial node link controllers. //----------------------------------------------------------------------------- void CPropJeepEpisodic::InputCreateLinkController( inputdata_t &data ) { Vector vecFront, vecRear; Vector vecWFL, vecWFR; // Front wheels Vector vecWRL, vecWRR; // Back wheels GetAttachment( "wheel_fr", vecWFR ); GetAttachment( "wheel_fl", vecWFL ); GetAttachment( "wheel_rr", vecWRR ); GetAttachment( "wheel_rl", vecWRL ); vecFront = (vecWFL + vecWFR) * 0.5f; vecRear = (vecWRL + vecWRR) * 0.5f; float flRadius = ( (vecFront - vecRear).Length() ) * 0.6f; CAI_RadialLinkController *pLinkController = (CAI_RadialLinkController *)CreateEntityByName( "info_radial_link_controller" ); if( pLinkController != NULL && m_hLinkControllerFront.Get() == NULL ) { pLinkController->m_flRadius = flRadius; pLinkController->Spawn(); pLinkController->SetAbsOrigin( vecFront ); pLinkController->SetOwnerEntity( this ); pLinkController->SetParent( this ); pLinkController->Activate(); m_hLinkControllerFront.Set( pLinkController ); //NDebugOverlay::Circle( vecFront, Vector(0,1,0), Vector(1,0,0), flRadius, 255, 255, 255, 128, false, 100 ); } pLinkController = (CAI_RadialLinkController *)CreateEntityByName( "info_radial_link_controller" ); if( pLinkController != NULL && m_hLinkControllerRear.Get() == NULL ) { pLinkController->m_flRadius = flRadius; pLinkController->Spawn(); pLinkController->SetAbsOrigin( vecRear ); pLinkController->SetOwnerEntity( this ); pLinkController->SetParent( this ); pLinkController->Activate(); m_hLinkControllerRear.Set( pLinkController ); //NDebugOverlay::Circle( vecRear, Vector(0,1,0), Vector(1,0,0), flRadius, 255, 255, 255, 128, false, 100 ); } } void CPropJeepEpisodic::InputDestroyLinkController( inputdata_t &data ) { if( m_hLinkControllerFront.Get() != NULL ) { CAI_RadialLinkController *pLinkController = dynamic_cast(m_hLinkControllerFront.Get()); if( pLinkController != NULL ) { pLinkController->ModifyNodeLinks(false); UTIL_Remove( pLinkController ); m_hLinkControllerFront.Set(NULL); } } if( m_hLinkControllerRear.Get() != NULL ) { CAI_RadialLinkController *pLinkController = dynamic_cast(m_hLinkControllerRear.Get()); if( pLinkController != NULL ) { pLinkController->ModifyNodeLinks(false); UTIL_Remove( pLinkController ); m_hLinkControllerRear.Set(NULL); } } } bool CPropJeepEpisodic::AllowBlockedExit( CBaseCombatCharacter *pPassenger, int nRole ) { // Wait until we've settled down before we resort to blocked exits. // This keeps us from doing blocked exits in mid-jump, which can cause mayhem like // sticking the player through player clips or into geometry. return GetSmoothedVelocity().IsLengthLessThan( jalopy_blocked_exit_max_speed.GetFloat() ); }