Files
HL2Overcharged/game/server/episodic/vehicle_jeep_episodic.cpp
2025-05-21 21:20:08 +03:00

1842 lines
55 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"
#include "IEffects.h"
#include "weapon_ar2.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);
//LINK_ENTITY_TO_CLASS(prop_vehicle_jeep_episodic, CPropJeepEpisodic);
LINK_ENTITY_TO_CLASS(prop_vehicle_snowmobile, CPropJeepEpisodic); // BJ: Snowmobile class added
BEGIN_DATADESC(CPropJeepEpisodic)
DEFINE_FIELD(m_add, FIELD_EHANDLE),
DEFINE_FIELD(m_add2, FIELD_EHANDLE),
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),
DEFINE_FIELD(m_bHeadlightIsOn, FIELD_BOOLEAN),
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(true),
m_flNextAvoidBroadcastTime(0.0f)
{
m_bHasGun = false;
m_bAllowDetach = false;
m_bUnableToFire = false;
m_bRadarDetectsEnemies = false;
HasTakenGauss = 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);
PrecacheModel("models/combine_turrets/combine_cannon_gun.mdl");
PrecacheScriptSound("JNK_Radar_Ping_Friendly");
PrecacheScriptSound("Physics.WaterSplash");
PrecacheScriptSound("Jeep.GaussReCharge");
PrecacheScriptSound("Jeep.GaussDeCharge");
PrecacheParticleSystem("WheelDust");
PrecacheParticleSystem("WheelSplash");
BaseClass::Precache();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pPlayer -
//-----------------------------------------------------------------------------
void CPropJeepEpisodic::EnterVehicle(CBaseCombatCharacter *pPassenger)
{
BaseClass::EnterVehicle(pPassenger);
// Turn our hazards off!
DestroyHazardLights();
if (FClassnameIs(this, "prop_vehicle_snowmobile")) // BJ: If snowmobile, use real hands and legs
{
DevMsg("Enter snowmobile event \n");
static int iFindhands = FindBodygroupByName("hevhands");
this->SetBodygroup(iFindhands, 1); // hands
//SetBodygroup(1, 1); // legs
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPropJeepEpisodic::Spawn(void)
{
BaseClass::Spawn();
SetBlocksLOS(false);
bool isBuggy = Q_strstr(STRING(GetModelName()), "buggy.mdl") > 0;
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
if (pPlayer != NULL && !isBuggy)
{
pPlayer->m_Local.m_iHideHUD |= HIDEHUD_VEHICLE_CROSSHAIR;
}
if (!isBuggy)
{
m_bHasGun = false;
m_bAllowDetach = false;
}
SetBodygroup(JEEP_HOPPER_BODYGROUP, m_bBusterHopperVisible ? 1 : 0);
if (!isBuggy)
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)
{
CBasePlayer *pPlayer = ToBasePlayer(pActivator);
if (pPlayer == NULL)
return;
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();
int attachment = LookupAttachment("Muzzle");
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
if (attachment)
{
if (m_bEngineLocked)
{
m_bUnableToFire = true;
if (pPlayer != NULL)
{
pPlayer->m_Local.m_iHideHUD |= HIDEHUD_VEHICLE_CROSSHAIR;
}
}
else if (m_bHasGun)
{
// Start this as false and update it again each frame
m_bUnableToFire = false;
if (pPlayer != NULL)
{
if (attachment)
pPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_VEHICLE_CROSSHAIR;
else
pPlayer->m_Local.m_iHideHUD |= HIDEHUD_VEHICLE_CROSSHAIR;
}
}
}
// 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)
{
/*if (Q_strcmp(STRING(GetModelName()), "models/buggy.mdl") == 0)
{
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);
if (HeadlightIsOn())
HeadlightTurnOff();
if (Q_strcmp(STRING(GetModelName()), "models/buggy.mdl") != 0)
CreateHazardLights();
if (FClassnameIs(this, "prop_vehicle_snowmobile")) // BJ: If snowmobile, use real hands and legs
{
DevMsg("Exit snowmobile event \n");
static int iFindhands = FindBodygroupByName("hevhands");
this->SetBodygroup(iFindhands, 0); // hands
//SetBodygroup(1, 0); // legs
}
}
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());
}