diff options
| author | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
|---|---|---|
| committer | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
| commit | 39ed87570bdb2f86969d4be821c94b722dc71179 (patch) | |
| tree | abc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/ai_basenpc_movement.cpp | |
| download | source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip | |
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/ai_basenpc_movement.cpp')
| -rw-r--r-- | mp/src/game/server/ai_basenpc_movement.cpp | 499 |
1 files changed, 499 insertions, 0 deletions
diff --git a/mp/src/game/server/ai_basenpc_movement.cpp b/mp/src/game/server/ai_basenpc_movement.cpp new file mode 100644 index 00000000..2697f0af --- /dev/null +++ b/mp/src/game/server/ai_basenpc_movement.cpp @@ -0,0 +1,499 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+
+#include "cbase.h"
+
+#include "game.h"
+#include "ndebugoverlay.h"
+
+#include "ai_basenpc.h"
+#include "ai_hull.h"
+#include "ai_node.h"
+#include "ai_motor.h"
+#include "ai_navigator.h"
+#include "ai_hint.h"
+#include "scripted.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//=============================================================================
+// PATHING & HIGHER LEVEL MOVEMENT
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// Purpose: Static debug function to force all selected npcs to go to the
+// given node
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CAI_BaseNPC::ForceSelectedGo(CBaseEntity *pPlayer, const Vector &targetPos, const Vector &traceDir, bool bRun)
+{
+ CAI_BaseNPC *npc = gEntList.NextEntByClass( (CAI_BaseNPC *)NULL );
+
+ while (npc)
+ {
+ if (npc->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT)
+ {
+ Vector chasePosition = targetPos;
+ npc->TranslateNavGoal( pPlayer, chasePosition );
+ // It it legal to drop me here
+ Vector vUpBit = chasePosition;
+ vUpBit.z += 1;
+
+ trace_t tr;
+ AI_TraceHull( chasePosition, vUpBit, npc->GetHullMins(),
+ npc->GetHullMaxs(), MASK_NPCSOLID, npc, COLLISION_GROUP_NONE, &tr );
+ if (tr.startsolid || tr.fraction != 1.0 )
+ {
+ NDebugOverlay::BoxAngles(chasePosition, npc->GetHullMins(),
+ npc->GetHullMaxs(), npc->GetAbsAngles(), 255,0,0,20,0.5);
+ }
+
+ npc->m_vecLastPosition = chasePosition;
+
+ if (npc->m_hCine != NULL)
+ {
+ npc->ExitScriptedSequence();
+ }
+
+ if ( bRun )
+ npc->SetSchedule( SCHED_FORCED_GO_RUN );
+ else
+ npc->SetSchedule( SCHED_FORCED_GO );
+ npc->m_flMoveWaitFinished = gpGlobals->curtime;
+ }
+ npc = gEntList.NextEntByClass(npc);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Static debug function to make all selected npcs run around
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CAI_BaseNPC::ForceSelectedGoRandom(void)
+{
+ CAI_BaseNPC *npc = gEntList.NextEntByClass( (CAI_BaseNPC *)NULL );
+
+ while (npc)
+ {
+ if (npc->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT)
+ {
+ npc->SetSchedule( SCHED_RUN_RANDOM );
+ npc->GetNavigator()->SetMovementActivity(ACT_RUN);
+ }
+ npc = gEntList.NextEntByClass(npc);
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CAI_BaseNPC::ScheduledMoveToGoalEntity( int scheduleType, CBaseEntity *pGoalEntity, Activity movementActivity )
+{
+ if ( m_NPCState == NPC_STATE_NONE )
+ {
+ // More than likely being grabbed before first think. Set ideal state to prevent schedule stomp
+ m_NPCState = m_IdealNPCState;
+ }
+
+ SetSchedule( scheduleType );
+
+ SetGoalEnt( pGoalEntity );
+
+ // HACKHACK: Call through TranslateNavGoal to fixup this goal position
+ // UNDONE: Remove this and have NPCs that need this functionality fix up paths in the
+ // movement system instead of when they are specified.
+ AI_NavGoal_t goal(pGoalEntity->GetAbsOrigin(), movementActivity, AIN_DEF_TOLERANCE, AIN_YAW_TO_DEST);
+
+ TranslateNavGoal( pGoalEntity, goal.dest );
+
+ return GetNavigator()->SetGoal( goal );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CAI_BaseNPC::ScheduledFollowPath( int scheduleType, CBaseEntity *pPathStart, Activity movementActivity )
+{
+ if ( m_NPCState == NPC_STATE_NONE )
+ {
+ // More than likely being grabbed before first think. Set ideal state to prevent schedule stomp
+ m_NPCState = m_IdealNPCState;
+ }
+
+ SetSchedule( scheduleType );
+
+ SetGoalEnt( pPathStart );
+
+ // HACKHACK: Call through TranslateNavGoal to fixup this goal position
+ AI_NavGoal_t goal(GOALTYPE_PATHCORNER, pPathStart->GetLocalOrigin(), movementActivity, AIN_DEF_TOLERANCE, AIN_YAW_TO_DEST);
+
+ TranslateNavGoal( pPathStart, goal.dest );
+
+ return GetNavigator()->SetGoal( goal );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+bool CAI_BaseNPC::IsMoving( void )
+{
+ return GetNavigator()->IsGoalSet();
+}
+
+
+//-----------------------------------------------------------------------------
+
+bool CAI_BaseNPC::IsCurTaskContinuousMove()
+{
+ const Task_t* pTask = GetTask();
+
+ // This bit of logic strikes me funny, but the case does exist. (sjb)
+ if( !pTask )
+ return true;
+
+ switch( pTask->iTask )
+ {
+ case TASK_WAIT_FOR_MOVEMENT:
+ case TASK_MOVE_TO_TARGET_RANGE:
+ case TASK_MOVE_TO_GOAL_RANGE:
+ case TASK_WEAPON_RUN_PATH:
+ case TASK_PLAY_SCENE:
+ case TASK_RUN_PATH_TIMED:
+ case TASK_WALK_PATH_TIMED:
+ case TASK_RUN_PATH_FOR_UNITS:
+ case TASK_WALK_PATH_FOR_UNITS:
+ case TASK_RUN_PATH_FLEE:
+ case TASK_WALK_PATH_WITHIN_DIST:
+ case TASK_RUN_PATH_WITHIN_DIST:
+ return true;
+ break;
+
+ default:
+ return false;
+ break;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Used to specify that the NPC has a reason not to use the a navigation node
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+bool CAI_BaseNPC::IsUnusableNode(int iNodeID, CAI_Hint *pHint)
+{
+ if ( m_bHintGroupNavLimiting && m_strHintGroup != NULL_STRING && STRING(m_strHintGroup)[0] != 0 )
+ {
+ if (!pHint || pHint->GetGroup() != GetHintGroup())
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Checks the validity of the given route's goaltype
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+bool CAI_BaseNPC::ValidateNavGoal()
+{
+ if (GetNavigator()->GetGoalType() == GOALTYPE_COVER)
+ {
+ // Check if this location will block my enemy's line of sight to me
+ if (GetEnemy())
+ {
+ Activity nCoverActivity = GetCoverActivity( GetHintNode() );
+ Vector vCoverLocation = GetNavigator()->GetGoalPos();
+
+
+ // For now we have to drop the node to the floor so we can
+ // get an accurate postion of the NPC. Should change once Ken checks in
+ float floorZ = GetFloorZ(vCoverLocation);
+ vCoverLocation.z = floorZ;
+
+
+ Vector vEyePos = vCoverLocation + EyeOffset(nCoverActivity);
+
+ if (!IsCoverPosition( GetEnemy()->EyePosition(), vEyePos ) )
+ {
+ TaskFail(FAIL_BAD_PATH_GOAL);
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+float CAI_BaseNPC::OpenDoorAndWait( CBaseEntity *pDoor )
+{
+ float flTravelTime = 0;
+
+ //DevMsg( 2, "A door. ");
+ if (pDoor && !pDoor->IsLockedByMaster())
+ {
+ pDoor->Use(this, this, USE_ON, 0.0);
+ flTravelTime = pDoor->GetMoveDoneTime();
+ if ( pDoor->GetEntityName() != NULL_STRING )
+ {
+ CBaseEntity *pTarget = NULL;
+ for (;;)
+ {
+ pTarget = gEntList.FindEntityByName( pTarget, pDoor->GetEntityName() );
+
+ if ( pTarget != pDoor )
+ {
+ if ( !pTarget )
+ break;
+
+ if ( FClassnameIs( pTarget, pDoor->GetClassname() ) )
+ {
+ pTarget->Use(this, this, USE_ON, 0.0);
+ }
+ }
+ }
+ }
+ }
+
+ return gpGlobals->curtime + flTravelTime;
+}
+
+//-----------------------------------------------------------------------------
+
+bool CAI_BaseNPC::CanStandOn( CBaseEntity *pSurface ) const
+{
+ if ( !pSurface->IsAIWalkable() )
+ {
+ return false;
+ }
+
+ CAI_Navigator *pNavigator = const_cast<CAI_Navigator *>(GetNavigator());
+
+ if ( pNavigator->IsGoalActive() &&
+ pSurface == pNavigator->GetGoalTarget() )
+ return false;
+
+ return BaseClass::CanStandOn( pSurface );
+}
+
+//-----------------------------------------------------------------------------
+
+bool CAI_BaseNPC::IsJumpLegal( const Vector &startPos, const Vector &apex, const Vector &endPos,
+ float maxUp, float maxDown, float maxDist ) const
+{
+ if ((endPos.z - startPos.z) > maxUp + 0.1)
+ return false;
+ if ((startPos.z - endPos.z) > maxDown + 0.1)
+ return false;
+
+ if ((apex.z - startPos.z) > maxUp * 1.25 )
+ return false;
+
+ float dist = (startPos - endPos).Length();
+ if ( dist > maxDist + 0.1)
+ return false;
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if a reasonable jumping distance
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+bool CAI_BaseNPC::IsJumpLegal( const Vector &startPos, const Vector &apex, const Vector &endPos ) const
+{
+ const float MAX_JUMP_RISE = 80.0f;
+ const float MAX_JUMP_DISTANCE = 250.0f;
+ const float MAX_JUMP_DROP = 192.0f;
+
+ return IsJumpLegal( startPos, apex, endPos, MAX_JUMP_RISE, MAX_JUMP_DROP, MAX_JUMP_DISTANCE );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns a throw velocity from start to end position
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+Vector CAI_BaseNPC::CalcThrowVelocity(const Vector &startPos, const Vector &endPos, float fGravity, float fArcSize)
+{
+ // Get the height I have to throw to get to the target
+ float stepHeight = endPos.z - startPos.z;
+ float throwHeight = 0;
+
+ // -----------------------------------------------------------------
+ // Now calcluate the distance to a point halfway between our current
+ // and target position. (the apex of our throwing arc)
+ // -----------------------------------------------------------------
+ Vector targetDir2D = endPos - startPos;
+ targetDir2D.z = 0;
+
+ float distance = VectorNormalize(targetDir2D);
+
+
+ // If jumping up we want to throw a bit higher than the height diff
+ if (stepHeight > 0)
+ {
+ throwHeight = stepHeight + fArcSize;
+ }
+ else
+ {
+ throwHeight = fArcSize;
+ }
+ // Make sure that I at least catch some air
+ if (throwHeight < fArcSize)
+ {
+ throwHeight = fArcSize;
+ }
+
+
+ // -------------------------------------------------------------
+ // calculate the vertical and horizontal launch velocities
+ // -------------------------------------------------------------
+ float velVert = (float)sqrt(2.0f*fGravity*throwHeight);
+
+ float divisor = velVert;
+ divisor += (float)sqrt((2.0f*(-fGravity)*(stepHeight-throwHeight)));
+
+ float velHorz = (distance * fGravity)/divisor;
+
+ // -----------------------------------------------------------
+ // Make the horizontal throw vector and add vertical component
+ // -----------------------------------------------------------
+ Vector throwVel = targetDir2D * velHorz;
+ throwVel.z = velVert;
+
+ return throwVel;
+}
+
+bool CAI_BaseNPC::ShouldMoveWait()
+{
+ return (m_flMoveWaitFinished > gpGlobals->curtime);
+}
+
+float CAI_BaseNPC::GetStepDownMultiplier() const
+{
+ return m_pNavigator->GetStepDownMultiplier();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: execute any movement this sequence may have
+// Output :
+//-----------------------------------------------------------------------------
+bool CAI_BaseNPC::AutoMovement( CBaseEntity *pTarget, AIMoveTrace_t *pTraceResult )
+{
+ return AutoMovement( GetAnimTimeInterval(), pTarget, pTraceResult );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flInterval -
+// -
+// *pTraceResult -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAI_BaseNPC::AutoMovement( float flInterval, CBaseEntity *pTarget, AIMoveTrace_t *pTraceResult )
+{
+ bool ignored;
+ Vector newPos;
+ QAngle newAngles;
+
+ if (flInterval <= 0.0)
+ return true;
+
+ m_ScheduleState.bTaskRanAutomovement = true;
+
+ if (GetIntervalMovement( flInterval, ignored, newPos, newAngles ))
+ {
+ // DevMsg( "%.2f : (%.1f) %.1f %.1f %.1f\n", gpGlobals->curtime, (newPos - GetLocalOrigin()).Length(), newPos.x, newPos.y, newAngles.y );
+
+ if ( m_hCine )
+ {
+ m_hCine->ModifyScriptedAutoMovement( &newPos );
+ }
+
+ if (GetMoveType() == MOVETYPE_STEP)
+ {
+ if (!(GetFlags() & FL_FLY))
+ {
+ if ( !pTarget )
+ {
+ pTarget = GetNavTargetEntity();
+ }
+
+ return ( GetMotor()->MoveGroundStep( newPos, pTarget, newAngles.y, false, true, pTraceResult ) == AIM_SUCCESS );
+ }
+ else
+ {
+ // FIXME: here's no direct interface to a fly motor, plus this needs to support a state where going through the world is okay.
+ // FIXME: add callbacks into the script system for validation
+ // FIXME: add function on scripts to force only legal movements
+ // FIXME: GetIntervalMovement deals in Local space, nor global. Currently now way to communicate that through these interfaces.
+ SetLocalOrigin( newPos );
+ SetLocalAngles( newAngles );
+ return true;
+ }
+ }
+ else if (GetMoveType() == MOVETYPE_FLY)
+ {
+ Vector dist = newPos - GetLocalOrigin();
+
+ VectorScale( dist, 1.0 / flInterval, dist );
+
+ SetLocalVelocity( dist );
+ return true;
+ }
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: return max 1/10 second rate of turning
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+
+float CAI_BaseNPC::MaxYawSpeed( void )
+{
+ return 45;
+}
+
+//-----------------------------------------------------------------------------
+// Returns the estimate in seconds before we reach our nav goal.
+// -1 means we don't know / haven't calculated it yet.
+//-----------------------------------------------------------------------------
+float CAI_BaseNPC::GetTimeToNavGoal()
+{
+ float flDist = GetNavigator()->BuildAndGetPathDistToGoal();
+ if ( flDist < 0 )
+ {
+ return -1.0f;
+ }
+
+ float flSpeed = GetIdealSpeed();
+
+ // FIXME: needs to consider stopping time!
+ if (flSpeed > 0 && flDist > 0)
+ {
+ return flDist / flSpeed;
+ }
+ return 0.0;
+}
+
+//=============================================================================
|