/* * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * In addition, as a special exception, the author gives permission to * link the code of this program with the Half-Life Game Engine ("HL * Engine") and Modified Game Libraries ("MODs") developed by Valve, * L.L.C ("Valve"). You must obey the GNU General Public License in all * respects for all of the code used other than the HL Engine and MODs * from Valve. If you modify this file, you may extend this exception * to your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from your * version. * */ #pragma once // STL uses exceptions, but we are not compiling with them - ignore warning #pragma warning(disable : 4530) class CNavPath { public: CNavPath() { m_segmentCount = 0; } struct PathSegment { CNavArea *area; // the area along the path NavTraverseType how; // how to enter this area from the previous one Vector pos; // our movement goal position at this point in the path const CNavLadder *ladder; // if "how" refers to a ladder, this is it }; const PathSegment *operator[](int i) { return (i >= 0 && i < m_segmentCount) ? &m_path[i] : nullptr; } int GetSegmentCount() const { return m_segmentCount; } const Vector &GetEndpoint() const { return m_path[m_segmentCount - 1].pos; } bool IsAtEnd(const Vector &pos) const; // return true if position is at the end of the path float GetLength() const; // return length of path from start to finish bool GetPointAlongPath(float distAlong, Vector *pointOnPath) const; // return point a given distance along the path - if distance is out of path bounds, point is clamped to start/end // return the node index closest to the given distance along the path without going over - returns (-1) if error int GetSegmentIndexAlongPath(float distAlong) const; bool IsValid() const { return (m_segmentCount > 0); } void Invalidate() { m_segmentCount = 0; } // draw the path for debugging void Draw(); // compute closest point on path to given point bool FindClosestPointOnPath(const Vector *worldPos, int startIndex, int endIndex, Vector *close) const; void Optimize(); // Compute shortest path from 'start' to 'goal' via A* algorithm template bool Compute(const Vector *start, const Vector *goal, CostFunctor &costFunc) { Invalidate(); if (!start || !goal) return false; CNavArea *startArea = TheNavAreaGrid.GetNearestNavArea(start); if (!startArea) return false; CNavArea *goalArea = TheNavAreaGrid.GetNavArea(goal); // if we are already in the goal area, build trivial path if (startArea == goalArea) { BuildTrivialPath(start, goal); return true; } // make sure path end position is on the ground Vector pathEndPosition = *goal; if (goalArea) pathEndPosition.z = goalArea->GetZ(&pathEndPosition); else GetGroundHeight(&pathEndPosition, &pathEndPosition.z); // Compute shortest path to goal CNavArea *closestArea; bool pathToGoalExists = NavAreaBuildPath(startArea, goalArea, goal, costFunc, &closestArea); CNavArea *effectiveGoalArea = (pathToGoalExists) ? goalArea : closestArea; // Build path by following parent links int count = 0; CNavArea *area; for (area = effectiveGoalArea; area; area = area->GetParent()) count++; // save room for endpoint if (count > MAX_PATH_SEGMENTS - 1) count = MAX_PATH_SEGMENTS - 1; if (count == 0) return false; if (count == 1) { BuildTrivialPath(start, goal); return true; } m_segmentCount = count; for (area = effectiveGoalArea; count && area; area = area->GetParent()) { count--; m_path[count].area = area; m_path[count].how = area->GetParentHow(); } // compute path positions if (ComputePathPositions() == false) { Invalidate(); return false; } // append path end position m_path[m_segmentCount].area = effectiveGoalArea; m_path[m_segmentCount].pos = pathEndPosition; m_path[m_segmentCount].ladder = nullptr; m_path[m_segmentCount].how = NUM_TRAVERSE_TYPES; m_segmentCount++; return true; } private: enum { MAX_PATH_SEGMENTS = 256 }; PathSegment m_path[MAX_PATH_SEGMENTS]; int m_segmentCount; bool ComputePathPositions(); // determine actual path positions bool BuildTrivialPath(const Vector *start, const Vector *goal); // utility function for when start and goal are in the same area int FindNextOccludedNode(int anchor); // used by Optimize() }; // Monitor improv movement and determine if it becomes stuck class CStuckMonitor { public: CStuckMonitor(); void Reset(); void Update(CImprov *improv); bool IsStuck() const { return m_isStuck; } float GetDuration() const { return m_isStuck ? m_stuckTimer.GetElapsedTime() : 0.0f; } private: bool m_isStuck; // if true, we are stuck Vector m_stuckSpot; // the location where we became stuck IntervalTimer m_stuckTimer; // how long we have been stuck enum { MAX_VEL_SAMPLES = 5 }; float m_avgVel[MAX_VEL_SAMPLES]; int m_avgVelIndex; int m_avgVelCount; Vector m_lastCentroid; float m_lastTime; }; // The CNavPathFollower class implements path following behavior class CNavPathFollower { public: CNavPathFollower(); void SetImprov(CImprov *improv) { m_improv = improv; } void SetPath(CNavPath *path) { m_path = path; } void Reset(); void Update(float deltaT, bool avoidObstacles = true); // move improv along path void Debug(bool status) { m_isDebug = status; } // turn debugging on/off bool IsStuck() const { return m_stuckMonitor.IsStuck(); } // return true if improv is stuck void ResetStuck() { m_stuckMonitor.Reset(); } float GetStuckDuration() const { return m_stuckMonitor.GetDuration(); } // return how long we've been stuck void FeelerReflexAdjustment(Vector *goalPosition, float height = -1.0f); // adjust goal position if "feelers" are touched private: int FindOurPositionOnPath(Vector *close, bool local) const; // return the closest point to our current position on current path int FindPathPoint(float aheadRange, Vector *point, int *prevIndex); // compute a point a fixed distance ahead along our path. CImprov *m_improv; // who is doing the path following CNavPath *m_path; // the path being followed int m_segmentIndex; // the point on the path the improv is moving towards int m_behindIndex; // index of the node on the path just behind us Vector m_goal; // last computed follow goal bool m_isLadderStarted; bool m_isDebug; CStuckMonitor m_stuckMonitor; };