mirror of
https://github.com/Gigaslav/HL2Overcharged.git
synced 2026-01-06 10:10:03 +03:00
1842 lines
55 KiB
C++
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());
|
|
}
|
|
|