hl2_src-leak-2017/src/game/server/episodic/vehicle_jeep_episodic.cpp

1765 lines
54 KiB
C++

//========= 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<CPhysicsProp *>(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 <CBaseEntity> 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<CRadarTarget*>(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<CNPC_Strider*>(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<CPhysicsProp *>(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<CBaseEntity *> 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<CBaseEntity *>(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<CAI_BaseNPC *> npcList;
CUtlVector<Vector> forceList;
CUtlVector<Vector> contactList;
IPhysicsFrictionSnapshot *pSnapshot = pVehiclePhysics->CreateFrictionSnapshot();
while ( pSnapshot->IsValid() )
{
IPhysicsObject *pOther = pSnapshot->GetObject(1);
float otherMass = pOther->GetMass();
CBaseEntity *pOtherEntity = static_cast<CBaseEntity *>(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<vphysicsupdateai_t *>(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<vphysicsupdateai_t *>(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<CAI_RadialLinkController*>(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<CAI_RadialLinkController*>(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() );
}