diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/npc_vehicledriver.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/server/npc_vehicledriver.cpp')
| -rw-r--r-- | game/server/npc_vehicledriver.cpp | 1190 |
1 files changed, 1190 insertions, 0 deletions
diff --git a/game/server/npc_vehicledriver.cpp b/game/server/npc_vehicledriver.cpp new file mode 100644 index 0000000..45da78d --- /dev/null +++ b/game/server/npc_vehicledriver.cpp @@ -0,0 +1,1190 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "ai_network.h" +#include "ai_default.h" +#include "ai_schedule.h" +#include "ai_hull.h" +#include "ai_node.h" +#include "ai_task.h" +#include "ai_senses.h" +#include "ai_navigator.h" +#include "ai_route.h" +#include "entitylist.h" +#include "soundenvelope.h" +#include "gamerules.h" +#include "ndebugoverlay.h" +#include "soundflags.h" +#include "trains.h" +#include "globalstate.h" +#include "vehicle_base.h" +#include "npc_vehicledriver.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define DRIVER_DEBUG_PATH 1 +#define DRIVER_DEBUG_PATH_SPLINE 2 + +//------------------------------------ +// +//------------------------------------ +ConVar g_debug_vehicledriver( "g_debug_vehicledriver", "0", FCVAR_CHEAT ); + +BEGIN_DATADESC( CNPC_VehicleDriver ) + DEFINE_KEYFIELD( m_iszVehicleName, FIELD_STRING, "vehicle" ), +// DEFINE_FIELD( m_hVehicle, FIELD_EHANDLE ), + // DEFINE_FIELD( m_pVehicleInterface, FIELD_POINTER ), + DEFINE_FIELD( m_hVehicleEntity, FIELD_EHANDLE ), + // DEFINE_FIELD( m_Waypoints, FIELD_???? ), + // DEFINE_FIELD( m_pCurrentWaypoint, FIELD_POINTER ), + // DEFINE_FIELD( m_pNextWaypoint, FIELD_POINTER ), + DEFINE_FIELD( m_vecDesiredVelocity, FIELD_VECTOR ), + DEFINE_FIELD( m_vecDesiredPosition, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_vecPrevPoint, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_vecPrevPrevPoint, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_vecPostPoint, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_vecPostPostPoint, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_flDistanceAlongSpline, FIELD_FLOAT ), + DEFINE_KEYFIELD( m_flDriversMaxSpeed, FIELD_FLOAT, "drivermaxspeed" ), + DEFINE_KEYFIELD( m_flDriversMinSpeed, FIELD_FLOAT, "driverminspeed" ), + DEFINE_FIELD( m_flMaxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( m_flGoalSpeed, FIELD_FLOAT ), + //DEFINE_KEYFIELD( m_flInitialSpeed, FIELD_FLOAT ), + DEFINE_FIELD( m_flSteering, FIELD_FLOAT ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDriversMaxSpeed", InputSetDriversMaxSpeed ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDriversMinSpeed", InputSetDriversMinSpeed ), + + DEFINE_INPUTFUNC( FIELD_VOID, "StartForward", InputStartForward ), + DEFINE_INPUTFUNC( FIELD_VOID, "Stop", InputStop ), + DEFINE_INPUTFUNC( FIELD_VOID, "StartFiring", InputStartFiring ), + DEFINE_INPUTFUNC( FIELD_VOID, "StopFiring", InputStopFiring ), + DEFINE_INPUTFUNC( FIELD_STRING, "GotoPathCorner", InputGotoPathCorner ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( npc_vehicledriver, CNPC_VehicleDriver ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CNPC_VehicleDriver::CNPC_VehicleDriver( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CNPC_VehicleDriver::~CNPC_VehicleDriver( void ) +{ + ClearWaypoints(); +} + +//------------------------------------------------------------------------------ +// Purpose : +//------------------------------------------------------------------------------ +void CNPC_VehicleDriver::Spawn( void ) +{ + Precache( ); + + BaseClass::Spawn(); + + CapabilitiesClear(); + CapabilitiesAdd( bits_CAP_MOVE_GROUND ); + CapabilitiesAdd( bits_CAP_MOVE_SHOOT ); + + SetModel( "models/roller_vehicledriver.mdl" ); + SetHullType(HULL_LARGE); + SetHullSizeNormal(); + m_iMaxHealth = m_iHealth = 1; + m_flFieldOfView = VIEW_FIELD_FULL; + + SetSolid( SOLID_BBOX ); + AddSolidFlags( FSOLID_NOT_SOLID ); + SetMoveType( MOVETYPE_NONE ); + AddEffects( EF_NODRAW ); + + m_lifeState = LIFE_ALIVE; + SetCycle( 0 ); + ResetSequenceInfo(); + + AddFlag( FL_NPC ); + + m_flMaxSpeed = 0; + m_flGoalSpeed = m_flInitialSpeed; + + m_vecDesiredVelocity = vec3_origin; + m_vecPrevPoint = vec3_origin; + m_vecPrevPrevPoint = vec3_origin; + m_vecPostPoint = vec3_origin; + m_vecPostPostPoint = vec3_origin; + m_vecDesiredPosition = vec3_origin; + m_flSteering = 45; + m_flDistanceAlongSpline = 0.2; + m_pCurrentWaypoint = m_pNextWaypoint = NULL; + + GetNavigator()->SetPathcornerPathfinding( false ); + + NPCInit(); + + m_takedamage = DAMAGE_NO; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_VehicleDriver::Precache( void ) +{ + PrecacheModel( "models/roller_vehicledriver.mdl" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_VehicleDriver::Activate( void ) +{ + BaseClass::Activate(); + + // Restore doesn't need to do this + if ( m_hVehicleEntity ) + return; + + // Make sure we've got a vehicle + if ( m_iszVehicleName == NULL_STRING ) + { + Warning( "npc_vehicledriver %s has no vehicle to drive.\n", STRING(GetEntityName()) ); + UTIL_Remove( this ); + return; + } + + m_hVehicleEntity = (gEntList.FindEntityByName( NULL, STRING(m_iszVehicleName) )); + if ( !m_hVehicleEntity ) + { + Warning( "npc_vehicledriver %s couldn't find his vehicle named %s.\n", STRING(GetEntityName()), STRING(m_iszVehicleName) ); + UTIL_Remove( this ); + return; + } + + m_pVehicleInterface = m_hVehicleEntity->GetServerVehicle(); + Assert( m_pVehicleInterface ); + if ( !m_pVehicleInterface->NPC_CanDrive() ) + { + Warning( "npc_vehicledriver %s doesn't know how to drive vehicle %s.\n", STRING(GetEntityName()), STRING(m_hVehicleEntity->GetEntityName()) ); + UTIL_Remove( this ); + return; + } + + // We've found our vehicle. Move to it and start following it. + SetAbsOrigin( m_hVehicleEntity->WorldSpaceCenter() ); + m_pVehicleInterface->NPC_SetDriver( this ); + + RecalculateSpeeds(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_VehicleDriver::OnRestore( void ) +{ + BaseClass::OnRestore(); + if ( m_hVehicleEntity ) + { + m_pVehicleInterface = m_hVehicleEntity->GetServerVehicle(); + Assert( m_pVehicleInterface ); + } +} +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_VehicleDriver::UpdateOnRemove( void ) +{ + // Leave our vehicle + if ( m_pVehicleInterface ) + { + m_pVehicleInterface->NPC_SetDriver( NULL ); + } + + BaseClass::UpdateOnRemove(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_VehicleDriver::PrescheduleThink( void ) +{ + if ( !m_hVehicleEntity ) + { + m_pVehicleInterface = NULL; + UTIL_Remove( this ); + return; + } + + // Keep up with my vehicle + SetAbsOrigin( m_hVehicleEntity->WorldSpaceCenter() ); + SetAbsAngles( m_hVehicleEntity->GetAbsAngles() ); + + BaseClass::PrescheduleThink(); + + if ( m_NPCState == NPC_STATE_IDLE ) + { + m_pVehicleInterface->NPC_Brake(); + return; + } + + // If we've been picked up by something (dropship probably), abort. + if ( m_hVehicleEntity->GetParent() ) + { + SetState( NPC_STATE_IDLE ); + ClearWaypoints(); + SetGoalEnt( NULL ); + return; + } + + DriveVehicle(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CNPC_VehicleDriver::SelectSchedule( void ) +{ + // Vehicle driver hangs in the air inside the vehicle, so we never need to fall to ground + ClearCondition( COND_FLOATING_OFF_GROUND ); + + if ( HasSpawnFlags(SF_VEHICLEDRIVER_INACTIVE) ) + { + SetState( NPC_STATE_IDLE ); + return SCHED_VEHICLEDRIVER_INACTIVE; + } + + if ( GetGoalEnt() ) + return SCHED_VEHICLEDRIVER_DRIVE_PATH; + + switch ( m_NPCState ) + { + case NPC_STATE_IDLE: + break; + + case NPC_STATE_ALERT: + break; + + case NPC_STATE_COMBAT: + { + if ( HasCondition(COND_NEW_ENEMY) || HasCondition( COND_ENEMY_DEAD ) ) + return BaseClass::SelectSchedule(); + + if ( HasCondition(COND_SEE_ENEMY) ) + { + // we can see the enemy + if ( HasCondition(COND_CAN_RANGE_ATTACK2) ) + return SCHED_RANGE_ATTACK2; + if ( HasCondition(COND_CAN_RANGE_ATTACK1) ) + return SCHED_RANGE_ATTACK1; + + // What to do here? Not necessarily easy to face enemy. + //if ( HasCondition(COND_NOT_FACING_ATTACK) ) + //return SCHED_COMBAT_FACE; + } + + // We can see him, but can't shoot him. Just wait and hope he comes closer. + return SCHED_VEHICLEDRIVER_COMBAT_WAIT; + } + break; + } + + return BaseClass::SelectSchedule(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CNPC_VehicleDriver::RangeAttack1Conditions( float flDot, float flDist ) +{ + // Vehicle not ready to fire again yet? + if ( m_pVehicleInterface->Weapon_PrimaryCanFireAt() > gpGlobals->curtime ) + return 0; + + // Check weapon range + float flMinRange, flMaxRange; + m_pVehicleInterface->Weapon_PrimaryRanges( &flMinRange, &flMaxRange ); + if ( flDist < flMinRange ) + return COND_TOO_CLOSE_TO_ATTACK; + if ( flDist > flMaxRange ) + return COND_TOO_FAR_TO_ATTACK; + + // Don't shoot backwards + Vector vecForward; + Vector vecToTarget = (GetEnemy()->GetAbsOrigin() - GetAbsOrigin()); + VectorNormalize(vecToTarget); + m_hVehicleEntity->GetVectors( &vecForward, NULL, NULL ); + float flForwardDot = DotProduct( vecForward, vecToTarget ); + if ( flForwardDot < 0 && fabs(flDot) < 0.5 ) + return COND_NOT_FACING_ATTACK; + + return COND_CAN_RANGE_ATTACK1; +} + +//========================================================= +// RangeAttack2Conditions +//========================================================= +int CNPC_VehicleDriver::RangeAttack2Conditions( float flDot, float flDist ) +{ + // Vehicle not ready to fire again yet? + if ( m_pVehicleInterface->Weapon_SecondaryCanFireAt() > gpGlobals->curtime ) + return 0; + + // Check weapon range + float flMinRange, flMaxRange; + m_pVehicleInterface->Weapon_SecondaryRanges( &flMinRange, &flMaxRange ); + if ( flDist < flMinRange ) + return COND_TOO_CLOSE_TO_ATTACK; + if ( flDist > flMaxRange ) + return COND_TOO_FAR_TO_ATTACK; + + return COND_CAN_RANGE_ATTACK2; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CNPC_VehicleDriver::TranslateSchedule( int scheduleType ) +{ + switch ( scheduleType ) + { + case SCHED_COMBAT_FACE: + { + // Vehicles can't rotate, so don't try and face + return TranslateSchedule( SCHED_CHASE_ENEMY ); + } + break; + + case SCHED_ALERT_FACE: + { + // Vehicles can't rotate, so don't try and face + return SCHED_ALERT_STAND; + } + break; + + case SCHED_CHASE_ENEMY_FAILED: + case SCHED_FAIL: + { + return SCHED_FAIL; + } + break; + } + + return BaseClass::TranslateSchedule(scheduleType); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pTask - +//----------------------------------------------------------------------------- +void CNPC_VehicleDriver::StartTask( const Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_RUN_PATH: + case TASK_WALK_PATH: + TaskComplete(); + break; + + case TASK_FACE_IDEAL: + case TASK_FACE_ENEMY: + { + // Vehicle ignores face commands, since it can't rotate on the spot. + TaskComplete(); + } + break; + + case TASK_VEHICLEDRIVER_GET_PATH: + { + if ( !GetGoalEnt() ) + { + TaskFail( FAIL_NO_TARGET ); + return; + } + + CheckForTeleport(); + + if ( g_debug_vehicledriver.GetInt() & DRIVER_DEBUG_PATH ) + { + NDebugOverlay::Box( GetGoalEnt()->GetAbsOrigin(), -Vector(50,50,50), Vector(50,50,50), 255,255,255, true, 5); + } + + AI_NavGoal_t goal( GOALTYPE_PATHCORNER, GetGoalEnt()->GetLocalOrigin(), ACT_WALK, AIN_DEF_TOLERANCE, AIN_YAW_TO_DEST); + if ( !GetNavigator()->SetGoal( goal ) ) + { + TaskFail( FAIL_NO_ROUTE ); + return; + } + + TaskComplete(); + } + break; + + case TASK_WAIT_FOR_MOVEMENT: + { + if (GetNavigator()->GetGoalType() == GOALTYPE_NONE) + { + TaskComplete(); + GetNavigator()->StopMoving(); // Stop moving + } + else if (!GetNavigator()->IsGoalActive()) + { + SetIdealActivity( GetStoppedActivity() ); + } + else + { + // Check validity of goal type + ValidateNavGoal(); + } + } + break; + + default: + BaseClass::StartTask( pTask ); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_VehicleDriver::RunTask( const Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + { + // Vehicle driver has no animations, so fire a burst at the target + CBaseEntity *pEnemy = GetEnemy(); + if ( pEnemy ) + { + // TODO: Get a bodytarget from the firing point of the gun in the vehicle + Vector vecTarget = GetEnemy()->BodyTarget( GetAbsOrigin(), false ); + m_pVehicleInterface->NPC_AimPrimaryWeapon( vecTarget ); + m_pVehicleInterface->NPC_PrimaryFire(); + TaskComplete(); + } + else + { + TaskFail(FAIL_NO_ENEMY); + return; + } + } + break; + + case TASK_RANGE_ATTACK2: + { + // Vehicle driver has no animations, so fire a burst at the target + CBaseEntity *pEnemy = GetEnemy(); + if ( pEnemy ) + { + // TODO: Get a bodytarget from the firing point of the gun in the vehicle + Vector vecTarget = GetEnemy()->BodyTarget( GetAbsOrigin(), false ); + m_pVehicleInterface->NPC_AimSecondaryWeapon( vecTarget ); + m_pVehicleInterface->NPC_SecondaryFire(); + TaskComplete(); + } + else + { + TaskFail(FAIL_NO_ENEMY); + return; + } + } + break; + + case TASK_WAIT_FOR_MOVEMENT: + { + BaseClass::RunTask( pTask ); + + if ( HasCondition(COND_SEE_ENEMY) ) + { + // we can see the enemy + if ( HasCondition(COND_CAN_RANGE_ATTACK2) ) + { + ChainRunTask( TASK_RANGE_ATTACK2, pTask->flTaskData ); + } + if ( HasCondition(COND_CAN_RANGE_ATTACK1) ) + { + ChainRunTask( TASK_RANGE_ATTACK1, pTask->flTaskData ); + } + } + } + break; + + default: + BaseClass::RunTask( pTask ); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_VehicleDriver::GatherEnemyConditions( CBaseEntity *pEnemy ) +{ + BaseClass::GatherEnemyConditions(pEnemy); +} + +//----------------------------------------------------------------------------- +// Purpose: Overridden because if the player is a criminal, we hate them. +//----------------------------------------------------------------------------- +Disposition_t CNPC_VehicleDriver::IRelationType(CBaseEntity *pTarget) +{ + // If it's the player and they are a criminal, we hate them. + if ( pTarget && pTarget->Classify() == CLASS_PLAYER) + { + if (GlobalEntity_GetState("gordon_precriminal") == GLOBAL_ON) + { + return(D_NU); + } + } + + return(BaseClass::IRelationType(pTarget)); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CNPC_VehicleDriver::OverrideMove( float flInterval ) +{ + if ( !m_hVehicleEntity ) + return true; + + // If we don't have a maxspeed, we've been stopped, so abort early + // Or we've been picked up by something (dropship probably). + if ( !m_flMaxSpeed || m_hVehicleEntity->GetParent() ) + { + m_pVehicleInterface->NPC_Brake(); + return true; + } + + // ----------------------------------------------------------------- + // If I have a route, keep it updated and move toward target + // ------------------------------------------------------------------ + if (GetNavigator()->IsGoalActive()) + { + if ( OverridePathMove( flInterval ) ) + return true; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_VehicleDriver::CalculatePostPoints( void ) +{ + m_vecPostPoint = m_vecDesiredPosition; + m_vecPostPostPoint = m_vecDesiredPosition; + + // If we have a waypoint beyond our current, use it instead. + if ( !GetNavigator()->CurWaypointIsGoal() ) + { + AI_Waypoint_t *pCurWaypoint = GetNavigator()->GetPath()->GetCurWaypoint(); + m_vecPostPoint = pCurWaypoint->GetNext()->GetPos(); + if ( pCurWaypoint->GetNext()->GetNext() ) + { + m_vecPostPostPoint = pCurWaypoint->GetNext()->GetNext()->GetPos(); + } + else + { + m_vecPostPostPoint = m_vecPostPoint; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Destroy our current waypoints +//----------------------------------------------------------------------------- +void CNPC_VehicleDriver::ClearWaypoints( void ) +{ + m_vecDesiredPosition = vec3_origin; + if ( m_pCurrentWaypoint ) + { + delete m_pCurrentWaypoint; + m_pCurrentWaypoint = NULL; + } + if ( m_pNextWaypoint ) + { + delete m_pNextWaypoint; + m_pNextWaypoint = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: We've hit a waypoint. Handle it, and return true if this is the +// end of the path. +//----------------------------------------------------------------------------- +bool CNPC_VehicleDriver::WaypointReached( void ) +{ + // We reached our current waypoint. + m_vecPrevPrevPoint = m_vecPrevPoint; + m_vecPrevPoint = GetAbsOrigin(); + + // If we've got to our goal, we're done here. + if ( GetNavigator()->CurWaypointIsGoal() ) + { + // Necessary for InPass outputs to be fired, is a no-op otherwise + GetNavigator()->AdvancePath(); + + // Stop pathing + ClearWaypoints(); + TaskComplete(); + SetGoalEnt( NULL ); + return true; + } + + AI_Waypoint_t *pCurWaypoint = GetNavigator()->GetPath()->GetCurWaypoint(); + if ( !pCurWaypoint ) + return false; + + // Check to see if the waypoint wants us to change speed + if ( pCurWaypoint->Flags() & bits_WP_TO_PATHCORNER ) + { + CBaseEntity *pEntity = pCurWaypoint->hPathCorner; + if ( pEntity ) + { + if ( pEntity->m_flSpeed > 0 ) + { + if ( pEntity->m_flSpeed <= 1.0 ) + { + m_flDriversMaxSpeed = pEntity->m_flSpeed; + RecalculateSpeeds(); + } + else + { + Warning("path_track %s tried to tell the npc_vehicledriver to set speed to %.3f. npc_vehicledriver only accepts values between 0 and 1.\n", STRING(pEntity->GetEntityName()), pEntity->m_flSpeed ); + } + } + } + } + + // Get the waypoints for the next part of the path + GetNavigator()->AdvancePath(); + if ( !GetNavigator()->GetPath()->GetCurWaypoint() ) + { + ClearWaypoints(); + TaskComplete(); + SetGoalEnt( NULL ); + return true; + } + + m_vecDesiredPosition = GetNavigator()->GetCurWaypointPos(); + CalculatePostPoints(); + + // Move to the next waypoint + delete m_pCurrentWaypoint; + m_pCurrentWaypoint = m_pNextWaypoint; + m_Waypoints[1] = new CVehicleWaypoint( m_vecPrevPoint, m_vecDesiredPosition, m_vecPostPoint, m_vecPostPostPoint ); + m_pNextWaypoint = m_Waypoints[1]; + + // Drop the spline marker back + m_flDistanceAlongSpline = MAX( 0, m_flDistanceAlongSpline - 1.0 ); + + CheckForTeleport(); + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CNPC_VehicleDriver::OverridePathMove( float flInterval ) +{ + // Setup our initial path data if we've just started running a path + if ( !m_pCurrentWaypoint ) + { + m_vecPrevPoint = GetAbsOrigin(); + m_vecPrevPrevPoint = GetAbsOrigin(); + m_vecDesiredPosition = GetNavigator()->GetCurWaypointPos(); + CalculatePostPoints(); + + // Init our two waypoints + m_Waypoints[0] = new CVehicleWaypoint( m_vecPrevPrevPoint, m_vecPrevPoint, m_vecDesiredPosition, m_vecPostPoint ); + m_Waypoints[1] = new CVehicleWaypoint( m_vecPrevPoint, m_vecDesiredPosition, m_vecPostPoint, m_vecPostPostPoint ); + m_pCurrentWaypoint = m_Waypoints[0]; + m_pNextWaypoint = m_Waypoints[1]; + + m_flDistanceAlongSpline = 0.2; + } + + // Have we reached our target? See if we've passed the current waypoint's plane. + Vector vecAbsMins, vecAbsMaxs; + CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs ); + if ( BoxOnPlaneSide( vecAbsMins, vecAbsMaxs, &m_pCurrentWaypoint->planeWaypoint ) == 3 ) + { + if ( WaypointReached() ) + return true; + } + + // Did we bypass it and reach the next one already? + if ( m_pNextWaypoint && BoxOnPlaneSide( vecAbsMins, vecAbsMaxs, &m_pNextWaypoint->planeWaypoint ) == 3 ) + { + if ( WaypointReached() ) + return true; + } + + // We may have just teleported, so check to make sure we have a waypoint + if ( !m_pCurrentWaypoint || !m_pNextWaypoint ) + return false; + + // Figure out which spline we're trucking along + CVehicleWaypoint *pCurrentSplineBeingTraversed = m_pCurrentWaypoint; + if ( m_flDistanceAlongSpline > 1 ) + { + pCurrentSplineBeingTraversed = m_pNextWaypoint; + } + + // Get our current speed, and check it against the length of the spline to know how far to advance our marker + AngularImpulse angVel; + Vector vecVelocity; + IPhysicsObject *pVehiclePhysics = m_hVehicleEntity->VPhysicsGetObject(); + + if( !pVehiclePhysics ) + { + // I think my vehicle has been destroyed. + return false; + } + + pVehiclePhysics->GetVelocity( &vecVelocity, &angVel ); + float flSpeed = vecVelocity.Length(); + float flIncTime = gpGlobals->curtime - GetLastThink(); + float flIncrement = flIncTime * (flSpeed / pCurrentSplineBeingTraversed->GetLength()); + + // Now advance our point along the spline + m_flDistanceAlongSpline = clamp( m_flDistanceAlongSpline + flIncrement, 0.f, 2.f); + if ( m_flDistanceAlongSpline > 1 ) + { + // We crossed the spline boundary + pCurrentSplineBeingTraversed = m_pNextWaypoint; + } + + Vector vSplinePoint = pCurrentSplineBeingTraversed->GetPointAt( m_flDistanceAlongSpline > 1 ? m_flDistanceAlongSpline-1 : m_flDistanceAlongSpline ); + Vector vSplineTangent = pCurrentSplineBeingTraversed->GetTangentAt( m_flDistanceAlongSpline > 1 ? m_flDistanceAlongSpline-1 : m_flDistanceAlongSpline ); + + // Now that we've got the target spline point & tangent, use it to decide what our desired velocity is. + // If we're close to the tangent, just use the tangent. Otherwise, Lerp towards it. + Vector vecToDesired = (vSplinePoint - GetAbsOrigin()); + float flDistToDesired = VectorNormalize( vecToDesired ); + float flTangentLength = VectorNormalize( vSplineTangent ); + + if ( flDistToDesired > (flTangentLength * 0.75) ) + { + m_vecDesiredVelocity = vecToDesired * flTangentLength; + } + else + { + VectorLerp( vSplineTangent, vecToDesired * flTangentLength, (flDistToDesired / (flTangentLength * 0.5)), m_vecDesiredVelocity ); + } + + // Decrease speed according to the turn we're trying to make + Vector vecRight; + m_hVehicleEntity->GetVectors( NULL, &vecRight, NULL ); + Vector vecNormVel = m_vecDesiredVelocity; + VectorNormalize( vecNormVel ); + float flDotRight = DotProduct( vecRight, vecNormVel ); + flSpeed = (1.0 - fabs(flDotRight)); + // Don't go slower than we've been told to go + if ( flSpeed < m_flDriversMinSpeed ) + { + flSpeed = m_flDriversMinSpeed; + } + m_vecDesiredVelocity = vecNormVel * (flSpeed * m_flMaxSpeed); + + // Bunch o'debug + if ( g_debug_vehicledriver.GetInt() & DRIVER_DEBUG_PATH ) + { + NDebugOverlay::Box( m_vecPrevPrevPoint, -Vector(15,15,15), Vector(15,15,15), 192,0,0, true, 0.1); + NDebugOverlay::Box( m_vecPrevPoint, -Vector(20,20,20), Vector(20,20,20), 255,0,0, true, 0.1); + NDebugOverlay::Box( m_vecPostPoint, -Vector(20,20,20), Vector(20,20,20), 0,192,0, true, 0.1); + NDebugOverlay::Box( m_vecPostPostPoint, -Vector(20,20,20), Vector(20,20,20), 0,128,0, true, 0.1); + NDebugOverlay::Box( vSplinePoint, -Vector(10,10,10), Vector(10,10,10), 0,0,255, true, 0.1); + NDebugOverlay::Line( vSplinePoint, vSplinePoint + (vSplineTangent * 40), 0,0,255, true, 0.1); + + //NDebugOverlay::HorzArrow( pCurrentSplineBeingTraversed->splinePoints[0], pCurrentSplineBeingTraversed->splinePoints[1], 30, 255,255,255,0, false, 0.1f ); + //NDebugOverlay::HorzArrow( pCurrentSplineBeingTraversed->splinePoints[1], pCurrentSplineBeingTraversed->splinePoints[2], 20, 255,255,255,0, false, 0.1f ); + //NDebugOverlay::HorzArrow( pCurrentSplineBeingTraversed->splinePoints[2], pCurrentSplineBeingTraversed->splinePoints[3], 10, 255,255,255,0, false, 0.1f ); + + // Draw the plane we're checking against for waypoint passing + Vector vecPlaneRight; + CrossProduct( m_pCurrentWaypoint->planeWaypoint.normal, Vector(0,0,1), vecPlaneRight ); + Vector vecPlane = m_pCurrentWaypoint->splinePoints[2]; + NDebugOverlay::Line( vecPlane + (vecPlaneRight * -100), vecPlane + (vecPlaneRight * 100), 255,0,0, true, 0.1); + + // Draw the next plane too + CrossProduct( m_pNextWaypoint->planeWaypoint.normal, Vector(0,0,1), vecPlaneRight ); + vecPlane = m_pNextWaypoint->splinePoints[2]; + NDebugOverlay::Line( vecPlane + (vecPlaneRight * -100), vecPlane + (vecPlaneRight * 100), 192,0,0, true, 0.1); + } + + if ( g_debug_vehicledriver.GetInt() & DRIVER_DEBUG_PATH_SPLINE ) + { + for ( int i = 0; i < 10; i++ ) + { + Vector vecTarget = m_pCurrentWaypoint->GetPointAt( 0.1 * i ); + Vector vecTangent = m_pCurrentWaypoint->GetTangentAt( 0.1 * i ); + VectorNormalize(vecTangent); + NDebugOverlay::Box( vecTarget, -Vector(10,10,10), Vector(10,10,10), 255,0,0, true, 0.1 ); + NDebugOverlay::Line( vecTarget, vecTarget + (vecTangent * 10), 255,255,0, true, 0.1); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: This takes the current place the NPC's trying to get to, figures out +// what keys to press to get the vehicle to go there, and then sends +// them to the vehicle. +//----------------------------------------------------------------------------- +void CNPC_VehicleDriver::DriveVehicle( void ) +{ + AngularImpulse angVel; + Vector vecVelocity; + IPhysicsObject *pVehiclePhysics = m_hVehicleEntity->VPhysicsGetObject(); + if ( !pVehiclePhysics ) + return; + pVehiclePhysics->GetVelocity( &vecVelocity, &angVel ); + float flSpeed = VectorNormalize( vecVelocity ); + + // If we have no target position to drive to, brake to a halt + if ( !m_flMaxSpeed || m_vecDesiredPosition == vec3_origin ) + { + if ( flSpeed > 1 ) + { + m_pVehicleInterface->NPC_Brake(); + } + return; + } + + if ( g_debug_vehicledriver.GetInt() & DRIVER_DEBUG_PATH ) + { + NDebugOverlay::Box(m_vecDesiredPosition, -Vector(20,20,20), Vector(20,20,20), 0,255,0, true, 0.1); + NDebugOverlay::Line(GetAbsOrigin(), GetAbsOrigin() + m_vecDesiredVelocity, 0,255,0, true, 0.1); + } + + m_flGoalSpeed = VectorNormalize(m_vecDesiredVelocity); + + // Is our target in front or behind us? + Vector vecForward, vecRight; + m_hVehicleEntity->GetVectors( &vecForward, &vecRight, NULL ); + float flDot = DotProduct( vecForward, m_vecDesiredVelocity ); + bool bBehind = ( flDot < 0 ); + float flVelDot = DotProduct( vecVelocity, m_vecDesiredVelocity ); + bool bGoingWrongWay = ( flVelDot < 0 ); + + // Figure out whether we should accelerate / decelerate + if ( bGoingWrongWay || (flSpeed < m_flGoalSpeed) ) + { + // If it's behind us, go backwards not forwards + if ( bBehind ) + { + m_pVehicleInterface->NPC_ThrottleReverse(); + } + else + { + m_pVehicleInterface->NPC_ThrottleForward(); + } + } + else + { + // Brake if we're go significantly too fast + if ( (flSpeed - 200) > m_flGoalSpeed ) + { + m_pVehicleInterface->NPC_Brake(); + } + else + { + m_pVehicleInterface->NPC_ThrottleCenter(); + } + } + + // Do we need to turn? + float flDotRight = DotProduct( vecRight, m_vecDesiredVelocity ); + if ( bBehind ) + { + // If we're driving backwards, flip our turning + flDotRight *= -1; + } + // Map it to the vehicle's steering + flDotRight *= (m_flSteering / 90); + + if ( flDotRight < 0 ) + { + // Turn left + m_pVehicleInterface->NPC_TurnLeft( -flDotRight ); + } + else if ( flDotRight > 0 ) + { + // Turn right + m_pVehicleInterface->NPC_TurnRight( flDotRight ); + } + else + { + m_pVehicleInterface->NPC_TurnCenter(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Check to see if we should teleport to the current path corner +//----------------------------------------------------------------------------- +void CNPC_VehicleDriver::CheckForTeleport( void ) +{ + if ( !GetGoalEnt() ) + return; + + CPathTrack *pTrack = dynamic_cast<CPathTrack *>( GetGoalEnt() ); + if ( !pTrack ) + return; + + // Does it have the teleport flag set? + if ( pTrack->HasSpawnFlags( SF_PATH_TELEPORT ) ) + { + IncrementInterpolationFrame(); + + // Teleport the vehicle to the pathcorner + Vector vecMins, vecMaxs; + vecMins = m_hVehicleEntity->CollisionProp()->OBBMins(); + vecMaxs = m_hVehicleEntity->CollisionProp()->OBBMaxs(); + Vector vecTarget = pTrack->GetAbsOrigin() - (vecMins + vecMaxs) * 0.5; + vecTarget.z += ((vecMaxs.z - vecMins.z) * 0.5) + 8; // Safety buffer + + // Orient it to face the next point + QAngle vecAngles = pTrack->GetAbsAngles(); + Vector vecToTarget = vec3_origin; + if ( pTrack->GetNext() ) + { + vecToTarget = (pTrack->GetNext()->GetAbsOrigin() - pTrack->GetAbsOrigin()); + VectorNormalize( vecToTarget ); + + // Vehicles are rotated 90 degrees + VectorAngles( vecToTarget, vecAngles ); + vecAngles[YAW] -= 90; + } + m_hVehicleEntity->Teleport( &vecTarget, &vecAngles, &vec3_origin ); + + // Teleport the driver + SetAbsOrigin( m_hVehicleEntity->WorldSpaceCenter() ); + SetAbsAngles( m_hVehicleEntity->GetAbsAngles() ); + + m_vecPrevPoint = pTrack->GetAbsOrigin(); + + // Move to the next waypoint, we've reached this one + if ( GetNavigator()->GetPath() ) + { + WaypointReached(); + } + + // Clear our waypoints, because the next waypoint is certainly invalid now. + ClearWaypoints(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CNPC_VehicleDriver::GetDefaultNavGoalTolerance() +{ + return 48; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_VehicleDriver::RecalculateSpeeds( void ) +{ + // Get data from the vehicle + const vehicleparams_t *pParams = m_pVehicleInterface->GetVehicleParams(); + if ( pParams ) + { + m_flMaxSpeed = pParams->engine.maxSpeed * m_flDriversMaxSpeed; + m_flSteering = pParams->steering.degreesSlow; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_VehicleDriver::InputSetDriversMaxSpeed( inputdata_t &inputdata ) +{ + m_flDriversMaxSpeed = inputdata.value.Float(); + + RecalculateSpeeds(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_VehicleDriver::InputSetDriversMinSpeed( inputdata_t &inputdata ) +{ + m_flDriversMinSpeed = inputdata.value.Float(); + + RecalculateSpeeds(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_VehicleDriver::InputStartForward( inputdata_t &inputdata ) +{ + CLEARBITS( m_spawnflags, SF_VEHICLEDRIVER_INACTIVE ); + if ( m_NPCState == NPC_STATE_IDLE ) + { + SetState( NPC_STATE_ALERT ); + } + SetCondition( COND_PROVOKED ); + + RecalculateSpeeds(); +} + +//----------------------------------------------------------------------------- +// Purpose: Tell the driver to stop moving +//----------------------------------------------------------------------------- +void CNPC_VehicleDriver::InputStop( inputdata_t &inputdata ) +{ + m_flMaxSpeed = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Tell the driver to start firing at targets +//----------------------------------------------------------------------------- +void CNPC_VehicleDriver::InputStartFiring( inputdata_t &inputdata ) +{ + CLEARBITS( m_spawnflags, SF_VEHICLEDRIVER_INACTIVE ); + SetCondition( COND_PROVOKED ); + + float flMinRange, flMaxRange; + // If the vehicle has a weapon, set our capability + if ( m_pVehicleInterface->NPC_HasPrimaryWeapon() ) + { + CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 ); + m_pVehicleInterface->Weapon_PrimaryRanges( &flMinRange, &flMaxRange ); + + // Ensure the look distances is long enough + if ( m_flDistTooFar < flMaxRange || GetSenses()->GetDistLook() < flMaxRange ) + { + m_flDistTooFar = flMaxRange; + SetDistLook( flMaxRange ); + } + } + + if ( m_pVehicleInterface->NPC_HasSecondaryWeapon() ) + { + CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK2 ); + m_pVehicleInterface->Weapon_SecondaryRanges( &flMinRange, &flMaxRange ); + + // Ensure the look distances is long enough + if ( m_flDistTooFar < flMaxRange || GetSenses()->GetDistLook() < flMaxRange ) + { + m_flDistTooFar = flMaxRange; + SetDistLook( flMaxRange ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Tell the driver to stop firing at targets +//----------------------------------------------------------------------------- +void CNPC_VehicleDriver::InputStopFiring( inputdata_t &inputdata ) +{ + // If the vehicle has a weapon, set our capability + CapabilitiesRemove( bits_CAP_INNATE_RANGE_ATTACK1 ); + CapabilitiesRemove( bits_CAP_INNATE_RANGE_ATTACK2 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_VehicleDriver::InputGotoPathCorner( inputdata_t &inputdata ) +{ + string_t iszPathName = inputdata.value.StringID(); + if ( iszPathName != NULL_STRING ) + { + CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, iszPathName ); + if ( !pEntity ) + { + Warning("npc_vehicledriver %s couldn't find entity named %s\n", STRING(GetEntityName()), STRING(iszPathName) ); + return; + } + + ClearWaypoints(); + + // Drive to the point + SetGoalEnt( pEntity ); + if ( m_NPCState == NPC_STATE_IDLE ) + { + SetState( NPC_STATE_ALERT ); + } + SetCondition( COND_PROVOKED ); + + // Force him to start forward + InputStartForward( inputdata ); + } +} + +//----------------------------------------------------------------------------- +// +// Schedules +// +//----------------------------------------------------------------------------- + +AI_BEGIN_CUSTOM_NPC( npc_vehicledriver, CNPC_VehicleDriver ) + + //Tasks + DECLARE_TASK( TASK_VEHICLEDRIVER_GET_PATH ) + + // Schedules + DEFINE_SCHEDULE + ( + SCHED_VEHICLEDRIVER_INACTIVE, + + " Tasks" + " TASK_WAIT_INDEFINITE 0" + "" + " Interrupts" + " COND_PROVOKED" + ) + + DEFINE_SCHEDULE + ( + SCHED_VEHICLEDRIVER_COMBAT_WAIT, + + " Tasks" + " TASK_WAIT 5" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_LIGHT_DAMAGE" + " COND_HEAVY_DAMAGE" + " COND_PROVOKED" + " COND_CAN_RANGE_ATTACK1" + " COND_CAN_RANGE_ATTACK2" + ) + + DEFINE_SCHEDULE + ( + SCHED_VEHICLEDRIVER_DRIVE_PATH, + + " Tasks" + " TASK_VEHICLEDRIVER_GET_PATH 0" + " TASK_WALK_PATH 9999" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_WAIT_PVS 0" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_PROVOKED" + ) + +AI_END_CUSTOM_NPC() |