//========= 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(pOther); if (pOther == NULL) return false; CPropJeepEpisodic *pJeep = dynamic_cast< CPropJeepEpisodic * >(GetOwnerEntity()); if (pJeep == NULL) return false; // Make the player release the item Pickup_ForcePlayerToDropThisObject(pOther); // Stop colliding with things pOther->VPhysicsDestroyObject(); pOther->SetSolidFlags(FSOLID_NOT_SOLID); pOther->SetMoveType(MOVETYPE_NONE); // Parent the object to our owner pOther->SetParent(GetOwnerEntity()); // The car now owns the entity pJeep->AddPropToCargoHold(pProp); // Notify the buster that it's been added to the cargo hold. StriderBuster_OnAddToCargoHold(pProp); // Stop touching this item Disable(); return true; } // // Setup the entity void Spawn(void) { BaseClass::Spawn(); SetSolid(SOLID_BBOX); SetSolidFlags(FSOLID_TRIGGER | FSOLID_NOT_SOLID); SetTouch(&CVehicleCargoTrigger::CargoTouch); } void Activate() { BaseClass::Activate(); SetSolidFlags(FSOLID_TRIGGER | FSOLID_NOT_SOLID); // Fixes up old savegames } // // When we've stopped touching this entity, we ignore it void EndTouch(CBaseEntity *pOther) { if (pOther == m_hIgnoreEntity) { m_hIgnoreEntity = NULL; } BaseClass::EndTouch(pOther); } // // Disables the trigger for a set duration void IgnoreTouches(CBaseEntity *pIgnoreEntity) { m_hIgnoreEntity = pIgnoreEntity; m_flIgnoreDuration = gpGlobals->curtime + 0.5f; } void Disable(void) { SetTouch(NULL); } void Enable(void) { SetTouch(&CVehicleCargoTrigger::CargoTouch); } protected: float m_flIgnoreDuration; CHandle m_hIgnoreEntity; DECLARE_DATADESC(); }; LINK_ENTITY_TO_CLASS(trigger_vehicle_cargo, CVehicleCargoTrigger); BEGIN_DATADESC(CVehicleCargoTrigger) DEFINE_FIELD(m_flIgnoreDuration, FIELD_TIME), DEFINE_FIELD(m_hIgnoreEntity, FIELD_EHANDLE), DEFINE_ENTITYFUNC(CargoTouch), END_DATADESC(); // // Transition reference point for the vehicle // class CInfoTargetVehicleTransition : public CPointEntity { public: DECLARE_CLASS(CInfoTargetVehicleTransition, CPointEntity); void Enable(void) { m_bDisabled = false; } void Disable(void) { m_bDisabled = true; } bool IsDisabled(void) const { return m_bDisabled; } private: void InputEnable(inputdata_t &data) { Enable(); } void InputDisable(inputdata_t &data) { Disable(); } bool m_bDisabled; DECLARE_DATADESC(); }; BEGIN_DATADESC(CInfoTargetVehicleTransition) DEFINE_KEYFIELD(m_bDisabled, FIELD_BOOLEAN, "StartDisabled"), DEFINE_INPUTFUNC(FIELD_VOID, "Enable", InputEnable), DEFINE_INPUTFUNC(FIELD_VOID, "Disable", InputDisable), END_DATADESC(); LINK_ENTITY_TO_CLASS(info_target_vehicle_transition, CInfoTargetVehicleTransition); // // CPropJeepEpisodic // LINK_ENTITY_TO_CLASS(prop_vehicle_jeep, CPropJeepEpisodic); //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(pEnt); if (pTarget != NULL && !pTarget->IsDisabled()) { if (pTarget->m_flRadius < 0 || vecJalopyOrigin.DistToSqr(pTarget->GetAbsOrigin()) <= Square(pTarget->m_flRadius)) { // This item has been detected. type = pTarget->GetType(); if (type == RADAR_CONTACT_DOG) bDetectedDog = true;// used to prevent Alyx talking about the radar (see below) if (pTarget->GetMode() == RADAR_MODE_STICKY) { // This beacon was just detected. Now change the radius to infinite // so that it will never go off the radar due to distance. pTarget->m_flRadius = -1; } } } } else if (m_bRadarDetectsEnemies) { if (pEnt->m_iClassname == iszStriderName) { CNPC_Strider *pStrider = dynamic_cast(pEnt); if (!pStrider || !pStrider->CarriedByDropship()) { // Ignore striders which are carried by dropships. type = RADAR_CONTACT_LARGE_ENEMY; } } if (pEnt->m_iClassname == iszHunterName) { type = RADAR_CONTACT_ENEMY; } } if (type != RADAR_CONTACT_NONE) { Vector vecPos = pEnt->WorldSpaceCenter(); m_vecRadarContactPos.Set(m_iNumRadarContacts, vecPos); m_iRadarContactType.Set(m_iNumRadarContacts, type); m_iNumRadarContacts++; if (m_iNumRadarContacts == RADAR_MAX_CONTACTS) break; } pEnt = gEntList.NextEnt(pEnt); } if (m_iNumRadarContacts > m_iNumOldRadarContacts) { // Play a bleepy sound if (!bDetectedDog) { EmitSound("JNK_Radar_Ping_Friendly"); } //Notify Alyx so she can talk about the radar contact CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx(); if (!bDetectedDog && pAlyx != NULL && pAlyx->GetVehicle()) { pAlyx->SpeakIfAllowed(TLK_PASSENGER_NEW_RADAR_CONTACT); } } if (bDetectedDog) { // Update the radar much more frequently when dog is around. m_flNextRadarUpdateTime = gpGlobals->curtime + RADAR_UPDATE_FREQUENCY_FAST; } //Msg("Server detected %d objects\n", m_iNumRadarContacts ); CBasePlayer *pPlayer = AI_GetSinglePlayer(); CSingleUserRecipientFilter filter(pPlayer); UserMessageBegin(filter, "UpdateJalopyRadar"); WRITE_BYTE(0); // end marker MessageEnd(); // send message } ConVar jalopy_cargo_anim_time("jalopy_cargo_anim_time", "1.0"); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropJeepEpisodic::UpdateCargoEntry(void) { // Don't bother if we have no prop to move if (m_hCargoProp == NULL) return; // If we're past our animation point, then we're already done if (m_flCargoStartTime + jalopy_cargo_anim_time.GetFloat() < gpGlobals->curtime) { // Close the hold immediately if we're finished if (m_bAddingCargo) { m_flAmmoCrateCloseTime = gpGlobals->curtime; m_bAddingCargo = false; } return; } // Get our target point int nAttachment = LookupAttachment("cargo"); Vector vecTarget, vecOut; QAngle vecAngles; GetAttachmentLocal(nAttachment, vecTarget, vecAngles); // Find where we are in the blend and bias it for a fast entry and slow ease-out float flPerc = (jalopy_cargo_anim_time.GetFloat()) ? ((gpGlobals->curtime - m_flCargoStartTime) / jalopy_cargo_anim_time.GetFloat()) : 1.0f; flPerc = Bias(flPerc, 0.75f); VectorLerp(m_hCargoProp->GetLocalOrigin(), vecTarget, flPerc, vecOut); // Get our target orientation CPhysicsProp *pProp = dynamic_cast(m_hCargoProp.Get()); if (pProp == NULL) return; // Slerp our quaternions to find where we are this frame Quaternion qtTarget; QAngle qa(0, 90, 0); qa += pProp->PreferredCarryAngles(); AngleQuaternion(qa, qtTarget); // FIXME: Find the real offset to make this sit properly Quaternion qtCurrent; AngleQuaternion(pProp->GetLocalAngles(), qtCurrent); Quaternion qtOut; QuaternionSlerp(qtCurrent, qtTarget, flPerc, qtOut); // Put it back to angles QuaternionAngles(qtOut, vecAngles); // Finally, take these new position m_hCargoProp->SetLocalOrigin(vecOut); m_hCargoProp->SetLocalAngles(vecAngles); // Push the closing out into the future to make sure we don't try and close at the same time m_flAmmoCrateCloseTime += gpGlobals->frametime; } #define VEHICLE_AVOID_BROADCAST_RATE 0.5f //----------------------------------------------------------------------------- // Purpose: This function isn't really what we want //----------------------------------------------------------------------------- void CPropJeepEpisodic::CreateAvoidanceZone(void) { if (m_flNextAvoidBroadcastTime > gpGlobals->curtime) return; // Only do this when we're stopped if (m_VehiclePhysics.GetSpeed() > 5.0f) return; float flHullRadius = CollisionProp()->BoundingRadius2D(); Vector vecPos; CollisionProp()->NormalizedToWorldSpace(Vector(0.5f, 0.33f, 0.25f), &vecPos); CSoundEnt::InsertSound(SOUND_MOVE_AWAY, vecPos, (flHullRadius*0.4f), VEHICLE_AVOID_BROADCAST_RATE, this); // NDebugOverlay::Sphere( vecPos, vec3_angle, flHullRadius*0.4f, 255, 0, 0, 0, true, VEHICLE_AVOID_BROADCAST_RATE ); CollisionProp()->NormalizedToWorldSpace(Vector(0.5f, 0.66f, 0.25f), &vecPos); CSoundEnt::InsertSound(SOUND_MOVE_AWAY, vecPos, (flHullRadius*0.4f), VEHICLE_AVOID_BROADCAST_RATE, this); // NDebugOverlay::Sphere( vecPos, vec3_angle, flHullRadius*0.4f, 255, 0, 0, 0, true, VEHICLE_AVOID_BROADCAST_RATE ); // Don't broadcast again until these are done m_flNextAvoidBroadcastTime = gpGlobals->curtime + VEHICLE_AVOID_BROADCAST_RATE; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropJeepEpisodic::Think(void) { BaseClass::Think(); 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 solveList; float vehicleMass = pVehiclePhysics->GetMass(); Vector vehicleUp; pVehicleEntity->GetVectors(NULL, NULL, &vehicleUp); IPhysicsFrictionSnapshot *pSnapshot = pVehiclePhysics->CreateFrictionSnapshot(); while (pSnapshot->IsValid()) { IPhysicsObject *pOther = pSnapshot->GetObject(1); float otherMass = pOther->GetMass(); CBaseEntity *pOtherEntity = static_cast(pOther->GetGameData()); Assert(pOtherEntity); if (pOtherEntity && pOtherEntity->GetMoveType() == MOVETYPE_VPHYSICS && pOther->IsMoveable() && (otherMass*4.0f) < vehicleMass) { Vector normal; pSnapshot->GetSurfaceNormal(normal); // this points down in the car's reference frame, then it's probably trapped under the car if (DotProduct(normal, vehicleUp) < -0.9f) { Vector point, pointLocal; pSnapshot->GetContactPoint(point); VectorITransform(point, pVehicleEntity->EntityToWorldTransform(), pointLocal); Vector bottomPoint = physcollision->CollideGetExtent(pVehiclePhysics->GetCollide(), vec3_origin, vec3_angle, Vector(0, 0, -1)); // make sure it's under the bottom of the car float bottomPlane = DotProduct(bottomPoint, vehicleUp) + 8; // 8 inches above bottom if (DotProduct(pointLocal, vehicleUp) <= bottomPlane) { //Msg("Solved %s\n", pOtherEntity->GetClassname()); if (solveList.Find(pOtherEntity) < 0) { solveList.AddToTail(pOtherEntity); } } } } pSnapshot->NextFrictionData(); } pVehiclePhysics->DestroyFrictionSnapshot(pSnapshot); if (solveList.Count()) { for (int i = 0; i < solveList.Count(); i++) { EntityPhysics_CreateSolver(pVehicleEntity, solveList[i], true, 4.0f); } pVehiclePhysics->RecheckContactPoints(); } } static void SimpleCollisionResponse(Vector velocityIn, const Vector &normal, float coefficientOfRestitution, Vector *pVelocityOut) { Vector Vn = DotProduct(velocityIn, normal) * normal; Vector Vt = velocityIn - Vn; *pVelocityOut = Vt - coefficientOfRestitution * Vn; } static void KillBlockingEnemyNPCs(CBasePlayer *pPlayer, CBaseEntity *pVehicleEntity, IPhysicsObject *pVehiclePhysics) { Vector velocity; pVehiclePhysics->GetVelocity(&velocity, NULL); float vehicleMass = pVehiclePhysics->GetMass(); // loop through the contacts and look for enemy NPCs that we're pushing on CUtlVector npcList; CUtlVector forceList; CUtlVector contactList; IPhysicsFrictionSnapshot *pSnapshot = pVehiclePhysics->CreateFrictionSnapshot(); while (pSnapshot->IsValid()) { IPhysicsObject *pOther = pSnapshot->GetObject(1); float otherMass = pOther->GetMass(); CBaseEntity *pOtherEntity = static_cast(pOther->GetGameData()); CAI_BaseNPC *pNPC = pOtherEntity ? pOtherEntity->MyNPCPointer() : NULL; // Is this an enemy NPC with a small enough mass? if (pNPC && pPlayer->IRelationType(pNPC) != D_LI && ((otherMass*2.0f) < vehicleMass)) { // accumulate the stress force for this NPC in the lsit float force = pSnapshot->GetNormalForce(); Vector normal; pSnapshot->GetSurfaceNormal(normal); normal *= force; int index = npcList.Find(pNPC); if (index < 0) { vphysicsupdateai_t *pUpdate = NULL; if (pNPC->VPhysicsGetObject() && pNPC->VPhysicsGetObject()->GetShadowController() && pNPC->GetMoveType() == MOVETYPE_STEP) { if (pNPC->HasDataObjectType(VPHYSICSUPDATEAI)) { pUpdate = static_cast(pNPC->GetDataObject(VPHYSICSUPDATEAI)); // kill this guy if I've been pushing him for more than half a second and I'm // still pushing in his direction if ((gpGlobals->curtime - pUpdate->startUpdateTime) > 0.5f && DotProduct(velocity, normal) > 0) { index = npcList.AddToTail(pNPC); forceList.AddToTail(normal); Vector pos; pSnapshot->GetContactPoint(pos); contactList.AddToTail(pos); } } else { pUpdate = static_cast(pNPC->CreateDataObject(VPHYSICSUPDATEAI)); pUpdate->startUpdateTime = gpGlobals->curtime; } // update based on vphysics for the next second // this allows the car to push the NPC pUpdate->stopUpdateTime = gpGlobals->curtime + 1.0f; float maxAngular; pNPC->VPhysicsGetObject()->GetShadowController()->GetMaxSpeed(&pUpdate->savedShadowControllerMaxSpeed, &maxAngular); pNPC->VPhysicsGetObject()->GetShadowController()->MaxSpeed(1.0f, maxAngular); } } else { forceList[index] += normal; } } pSnapshot->NextFrictionData(); } pVehiclePhysics->DestroyFrictionSnapshot(pSnapshot); // now iterate the list and check each cumulative force against the threshold if (npcList.Count()) { for (int i = npcList.Count(); --i >= 0;) { Vector damageForce; npcList[i]->VPhysicsGetObject()->GetVelocity(&damageForce, NULL); Vector vel; pVehiclePhysics->GetVelocityAtPoint(contactList[i], &vel); damageForce -= vel; Vector normal = forceList[i]; VectorNormalize(normal); SimpleCollisionResponse(damageForce, normal, 1.0, &damageForce); damageForce += (normal * 300.0f); damageForce *= npcList[i]->VPhysicsGetObject()->GetMass(); float len = damageForce.Length(); damageForce.z += len*phys_upimpactforcescale.GetFloat(); Vector vehicleForce = -damageForce; CTakeDamageInfo dmgInfo(pVehicleEntity, pVehicleEntity, damageForce, contactList[i], 200.0f, DMG_CRUSH | DMG_VEHICLE); npcList[i]->TakeDamage(dmgInfo); pVehiclePhysics->ApplyForceOffset(vehicleForce, contactList[i]); PhysCollisionSound(pVehicleEntity, npcList[i]->VPhysicsGetObject(), CHAN_BODY, pVehiclePhysics->GetMaterialIndex(), npcList[i]->VPhysicsGetObject()->GetMaterialIndex(), gpGlobals->frametime, 200.0f); } } } void CPropJeepEpisodic::DriveVehicle(float flFrameTime, CUserCmd *ucmd, int iButtonsDown, int iButtonsReleased) { /*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(m_hLinkControllerFront.Get()); if (pLinkController != NULL) { pLinkController->ModifyNodeLinks(false); UTIL_Remove(pLinkController); m_hLinkControllerFront.Set(NULL); } } if (m_hLinkControllerRear.Get() != NULL) { CAI_RadialLinkController *pLinkController = dynamic_cast(m_hLinkControllerRear.Get()); if (pLinkController != NULL) { pLinkController->ModifyNodeLinks(false); UTIL_Remove(pLinkController); m_hLinkControllerRear.Set(NULL); } } } bool CPropJeepEpisodic::AllowBlockedExit(CBaseCombatCharacter *pPassenger, int nRole) { // Wait until we've settled down before we resort to blocked exits. // This keeps us from doing blocked exits in mid-jump, which can cause mayhem like // sticking the player through player clips or into geometry. return GetSmoothedVelocity().IsLengthLessThan(jalopy_blocked_exit_max_speed.GetFloat()); }