Merge pull request #2037 from SafwatHalaby/aprox
Pathfinder - approximated paths when original destination unreachable
This commit is contained in:
commit
b7b088494c
@ -36,7 +36,6 @@ void cAggressiveMonster::InStateChasing(std::chrono::milliseconds a_Dt)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
MoveToPosition(m_Target->GetPosition());
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A
|
||||
, m_SoundDeath(a_SoundDeath)
|
||||
, m_AttackRate(3)
|
||||
, m_AttackDamage(1)
|
||||
, m_AttackRange(2)
|
||||
, m_AttackRange(1)
|
||||
, m_AttackInterval(0)
|
||||
, m_SightDistance(25)
|
||||
, m_DropChanceWeapon(0.085f)
|
||||
@ -147,10 +147,15 @@ bool cMonster::TickPathFinding(cChunk & a_Chunk)
|
||||
(Recalculate lots when close, calculate rarely when far) */
|
||||
if (
|
||||
((GetPosition() - m_PathFinderDestination).Length() < 0.25) ||
|
||||
((m_TicksSinceLastPathReset > 10) && (m_TicksSinceLastPathReset > (0.15 * (m_FinalDestination - GetPosition()).SqrLength())))
|
||||
((m_TicksSinceLastPathReset > 10) && (m_TicksSinceLastPathReset > (0.4 * (m_FinalDestination - GetPosition()).SqrLength())))
|
||||
)
|
||||
{
|
||||
ResetPathFinding();
|
||||
/* Re-calculating is expensive when there's no path to target, and it results in mobs freezing very often as a result of always recalculating.
|
||||
This is a workaround till we get better path recalculation. */
|
||||
if (!m_NoPathToTarget)
|
||||
{
|
||||
ResetPathFinding();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,12 +166,21 @@ bool cMonster::TickPathFinding(cChunk & a_Chunk)
|
||||
StopMovingToPosition(); // Invalid chunks, probably world is loading or something, cancel movement.
|
||||
return false;
|
||||
}
|
||||
m_NoPathToTarget = false;
|
||||
m_NoMoreWayPoints = false;
|
||||
m_PathFinderDestination = m_FinalDestination;
|
||||
m_Path = new cPath(a_Chunk, GetPosition().Floor(), m_PathFinderDestination.Floor(), 20);
|
||||
}
|
||||
|
||||
switch (m_Path->Step(a_Chunk))
|
||||
{
|
||||
case ePathFinderStatus::NEARBY_FOUND:
|
||||
{
|
||||
m_NoPathToTarget = true;
|
||||
m_Path->AcceptNearbyPath();
|
||||
break;
|
||||
}
|
||||
|
||||
case ePathFinderStatus::PATH_NOT_FOUND:
|
||||
{
|
||||
StopMovingToPosition(); // Give up pathfinding to that destination.
|
||||
@ -179,15 +193,22 @@ bool cMonster::TickPathFinding(cChunk & a_Chunk)
|
||||
}
|
||||
case ePathFinderStatus::PATH_FOUND:
|
||||
{
|
||||
if (--m_GiveUpCounter == 0)
|
||||
if (m_NoMoreWayPoints || (--m_GiveUpCounter == 0))
|
||||
{
|
||||
ResetPathFinding(); // Try to calculate a path again.
|
||||
return false;
|
||||
}
|
||||
else if (!m_Path->IsLastPoint() && (m_Path->IsFirstPoint() || ReachedNextWaypoint())) // Have we arrived at the next cell, as denoted by m_NextWayPointPosition?
|
||||
else if (!m_Path->IsLastPoint()) // Have we arrived at the next cell, as denoted by m_NextWayPointPosition?
|
||||
{
|
||||
m_NextWayPointPosition = Vector3d(0.5, 0, 0.5) + m_Path->GetNextPoint();
|
||||
m_GiveUpCounter = 40; // Give up after 40 ticks (2 seconds) if failed to reach m_NextWayPointPosition.
|
||||
if ((m_Path->IsFirstPoint() || ReachedNextWaypoint()))
|
||||
{
|
||||
m_NextWayPointPosition = Vector3d(0.5, 0, 0.5) + m_Path->GetNextPoint();
|
||||
m_GiveUpCounter = 40; // Give up after 40 ticks (2 seconds) if failed to reach m_NextWayPointPosition.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_NoMoreWayPoints = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -269,21 +290,59 @@ bool cMonster::EnsureProperDestination(cChunk & a_Chunk)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
int RelX = FloorC(m_FinalDestination.x) - Chunk->GetPosX() * cChunkDef::Width;
|
||||
int RelZ = FloorC(m_FinalDestination.z) - Chunk->GetPosZ() * cChunkDef::Width;
|
||||
|
||||
// If destination in the air, go down to the lowest air block.
|
||||
while (m_FinalDestination.y > 0)
|
||||
// If destination in the air, first try to go 1 block north, or east, or west.
|
||||
// This fixes the player leaning issue.
|
||||
// If that failed, we instead go down to the lowest air block.
|
||||
Chunk->GetBlockTypeMeta(RelX, FloorC(m_FinalDestination.y) - 1, RelZ, BlockType, BlockMeta);
|
||||
if (!cBlockInfo::IsSolid(BlockType))
|
||||
{
|
||||
Chunk->GetBlockTypeMeta(RelX, FloorC(m_FinalDestination.y) - 1, RelZ, BlockType, BlockMeta);
|
||||
if (cBlockInfo::IsSolid(BlockType))
|
||||
bool InTheAir = true;
|
||||
int x, z;
|
||||
for (z = -1; z <= 1; ++z)
|
||||
{
|
||||
break;
|
||||
for (x = -1; x <= 1; ++x)
|
||||
{
|
||||
if ((x==0) && (z==0))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Chunk = a_Chunk.GetNeighborChunk(FloorC(m_FinalDestination.x+x), FloorC(m_FinalDestination.z+z));
|
||||
if ((Chunk == nullptr) || !Chunk->IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
RelX = FloorC(m_FinalDestination.x+x) - Chunk->GetPosX() * cChunkDef::Width;
|
||||
RelZ = FloorC(m_FinalDestination.z+z) - Chunk->GetPosZ() * cChunkDef::Width;
|
||||
Chunk->GetBlockTypeMeta(RelX, FloorC(m_FinalDestination.y) - 1, RelZ, BlockType, BlockMeta);
|
||||
if (cBlockInfo::IsSolid(BlockType))
|
||||
{
|
||||
m_FinalDestination.x += x;
|
||||
m_FinalDestination.z += z;
|
||||
InTheAir = false;
|
||||
goto breakBothLoops;
|
||||
}
|
||||
}
|
||||
}
|
||||
m_FinalDestination.y -= 1;
|
||||
}
|
||||
breakBothLoops:
|
||||
|
||||
// Go down to the lowest air block.
|
||||
if (InTheAir)
|
||||
{
|
||||
while (m_FinalDestination.y > 0)
|
||||
{
|
||||
Chunk->GetBlockTypeMeta(RelX, FloorC(m_FinalDestination.y) - 1, RelZ, BlockType, BlockMeta);
|
||||
if (cBlockInfo::IsSolid(BlockType))
|
||||
{
|
||||
break;
|
||||
}
|
||||
m_FinalDestination.y -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If destination in water, go up to the highest water block.
|
||||
// If destination in solid, go up to first air block.
|
||||
@ -447,21 +506,29 @@ void cMonster::SetPitchAndYawFromDestination()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Vector3d BodyDistance = m_NextWayPointPosition - GetPosition();
|
||||
double BodyRotation, BodyPitch;
|
||||
BodyDistance.Normalize();
|
||||
VectorToEuler(BodyDistance.x, BodyDistance.y, BodyDistance.z, BodyRotation, BodyPitch);
|
||||
SetYaw(BodyRotation);
|
||||
|
||||
Vector3d Distance = FinalDestination - GetPosition();
|
||||
{
|
||||
double Rotation, Pitch;
|
||||
double HeadRotation, HeadPitch;
|
||||
Distance.Normalize();
|
||||
VectorToEuler(Distance.x, Distance.y, Distance.z, Rotation, Pitch);
|
||||
SetHeadYaw(Rotation);
|
||||
SetPitch(-Pitch);
|
||||
}
|
||||
|
||||
{
|
||||
Vector3d BodyDistance = m_NextWayPointPosition - GetPosition();
|
||||
double Rotation, Pitch;
|
||||
BodyDistance.Normalize();
|
||||
VectorToEuler(BodyDistance.x, BodyDistance.y, BodyDistance.z, Rotation, Pitch);
|
||||
SetYaw(Rotation);
|
||||
VectorToEuler(Distance.x, Distance.y, Distance.z, HeadRotation, HeadPitch);
|
||||
if (abs(BodyRotation - HeadRotation) < 120)
|
||||
{
|
||||
SetHeadYaw(HeadRotation);
|
||||
SetPitch(-HeadPitch);
|
||||
}
|
||||
else // We're not an owl. If it's more than 120, don't look behind and instead look at where you're walking.
|
||||
{
|
||||
SetHeadYaw(BodyRotation);
|
||||
SetPitch(-BodyPitch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,6 +180,13 @@ protected:
|
||||
/** Coordinates for the ultimate, final destination last given to the pathfinder. */
|
||||
Vector3d m_PathFinderDestination;
|
||||
|
||||
/** True if there's no path to target and we're walking to an approximated location. */
|
||||
bool m_NoPathToTarget;
|
||||
|
||||
/** Whether The mob has finished their path, note that this does not imply reaching the destination,
|
||||
the destination may sometimes differ from the current path. */
|
||||
bool m_NoMoreWayPoints;
|
||||
|
||||
/** Finds the lowest non-air block position (not the highest, as cWorld::GetHeight does)
|
||||
If current Y is nonsolid, goes down to try to find a solid block, then returns that + 1
|
||||
If current Y is solid, goes up to find first nonsolid block, and returns that.
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#define DISTANCE_MANHATTAN 0 // 1: More speed, a bit less accuracy 0: Max accuracy, less speed.
|
||||
#define HEURISTICS_ONLY 0 // 1: Much more speed, much less accurate.
|
||||
#define CALCULATIONS_PER_STEP 5 // Higher means more CPU load but faster path calculations.
|
||||
#define CALCULATIONS_PER_STEP 10 // Higher means more CPU load but faster path calculations.
|
||||
// The only version which guarantees the shortest path is 0, 0.
|
||||
|
||||
enum class eCellStatus {OPENLIST, CLOSEDLIST, NOLIST};
|
||||
@ -44,7 +44,8 @@ cPath::cPath(
|
||||
m_Destination(a_EndingPoint.Floor()),
|
||||
m_Source(a_StartingPoint.Floor()),
|
||||
m_CurrentPoint(0), // GetNextPoint increments this to 1, but that's fine, since the first cell is always a_StartingPoint
|
||||
m_Chunk(&a_Chunk)
|
||||
m_Chunk(&a_Chunk),
|
||||
m_BadChunkFound(false)
|
||||
{
|
||||
// TODO: if src not walkable OR dest not walkable, then abort.
|
||||
// Borrow a new "isWalkable" from ProcessIfWalkable, make ProcessIfWalkable also call isWalkable
|
||||
@ -55,6 +56,7 @@ cPath::cPath(
|
||||
return;
|
||||
}
|
||||
|
||||
m_NearestPointToTarget = GetCell(m_Source);
|
||||
m_Status = ePathFinderStatus::CALCULATING;
|
||||
m_StepsLeft = a_MaxSteps;
|
||||
|
||||
@ -81,15 +83,20 @@ cPath::~cPath()
|
||||
ePathFinderStatus cPath::Step(cChunk & a_Chunk)
|
||||
{
|
||||
m_Chunk = &a_Chunk;
|
||||
|
||||
if (m_Status != ePathFinderStatus::CALCULATING)
|
||||
{
|
||||
return m_Status;
|
||||
}
|
||||
|
||||
if (m_StepsLeft == 0)
|
||||
if (m_BadChunkFound)
|
||||
{
|
||||
FinishCalculation(ePathFinderStatus::PATH_NOT_FOUND);
|
||||
return m_Status;
|
||||
}
|
||||
|
||||
if (m_StepsLeft == 0)
|
||||
{
|
||||
AttemptToFindAlternative();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -102,9 +109,9 @@ ePathFinderStatus cPath::Step(cChunk & a_Chunk)
|
||||
break; // if we're here, m_Status must have changed either to PATH_FOUND or PATH_NOT_FOUND.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_Chunk = nullptr;
|
||||
m_Chunk = nullptr;
|
||||
}
|
||||
return m_Status;
|
||||
}
|
||||
|
||||
@ -112,6 +119,17 @@ ePathFinderStatus cPath::Step(cChunk & a_Chunk)
|
||||
|
||||
|
||||
|
||||
Vector3i cPath::AcceptNearbyPath()
|
||||
{
|
||||
ASSERT(m_Status == ePathFinderStatus::NEARBY_FOUND);
|
||||
m_Status = ePathFinderStatus::PATH_FOUND;
|
||||
return m_Destination;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cPath::IsSolid(const Vector3i & a_Location)
|
||||
{
|
||||
ASSERT(m_Chunk != nullptr);
|
||||
@ -119,6 +137,7 @@ bool cPath::IsSolid(const Vector3i & a_Location)
|
||||
auto Chunk = m_Chunk->GetNeighborChunk(a_Location.x, a_Location.z);
|
||||
if ((Chunk == nullptr) || !Chunk->IsValid())
|
||||
{
|
||||
m_BadChunkFound = true;
|
||||
return true;
|
||||
}
|
||||
m_Chunk = Chunk;
|
||||
@ -149,34 +168,29 @@ bool cPath::Step_Internal()
|
||||
{
|
||||
cPathCell * CurrentCell = OpenListPop();
|
||||
|
||||
// Path not reachable, open list exauhsted.
|
||||
// Path not reachable.
|
||||
if (CurrentCell == nullptr)
|
||||
{
|
||||
FinishCalculation(ePathFinderStatus::PATH_NOT_FOUND);
|
||||
ASSERT(m_Status == ePathFinderStatus::PATH_NOT_FOUND);
|
||||
AttemptToFindAlternative();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Path found.
|
||||
if (
|
||||
(CurrentCell->m_Location == m_Destination + Vector3i(0, 0, 1)) ||
|
||||
(CurrentCell->m_Location == m_Destination + Vector3i(1, 0, 0)) ||
|
||||
(CurrentCell->m_Location == m_Destination + Vector3i(-1, 0, 0)) ||
|
||||
(CurrentCell->m_Location == m_Destination + Vector3i(0, 0, -1)) ||
|
||||
(CurrentCell->m_Location == m_Destination + Vector3i(0, -1, 0))
|
||||
)
|
||||
if (CurrentCell->m_Location == m_Destination)
|
||||
{
|
||||
do
|
||||
{
|
||||
m_PathPoints.push_back(CurrentCell->m_Location); // Populate the cPath with points.
|
||||
CurrentCell = CurrentCell->m_Parent;
|
||||
} while (CurrentCell != nullptr);
|
||||
|
||||
BuildPath();
|
||||
FinishCalculation(ePathFinderStatus::PATH_FOUND);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Calculation not finished yet, process a currentCell by inspecting all neighbors.
|
||||
// Calculation not finished yet.
|
||||
// Check if we have a new NearestPoint.
|
||||
if (CurrentCell->m_H < m_NearestPointToTarget->m_H)
|
||||
{
|
||||
m_NearestPointToTarget = CurrentCell;
|
||||
}
|
||||
|
||||
// process a currentCell by inspecting all neighbors.
|
||||
|
||||
// Check North, South, East, West on all 3 different heights.
|
||||
int i;
|
||||
@ -213,6 +227,38 @@ bool cPath::Step_Internal()
|
||||
|
||||
|
||||
|
||||
void cPath::AttemptToFindAlternative()
|
||||
{
|
||||
if (m_NearestPointToTarget == GetCell(m_Source))
|
||||
{
|
||||
FinishCalculation(ePathFinderStatus::PATH_NOT_FOUND);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Destination = m_NearestPointToTarget->m_Location;
|
||||
BuildPath();
|
||||
FinishCalculation(ePathFinderStatus::NEARBY_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cPath::BuildPath()
|
||||
{
|
||||
cPathCell * CurrentCell = GetCell(m_Destination);
|
||||
do
|
||||
{
|
||||
m_PathPoints.push_back(CurrentCell->m_Location); // Populate the cPath with points.
|
||||
CurrentCell = CurrentCell->m_Parent;
|
||||
} while (CurrentCell != nullptr);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cPath::FinishCalculation()
|
||||
{
|
||||
m_Map.clear();
|
||||
@ -225,6 +271,10 @@ void cPath::FinishCalculation()
|
||||
|
||||
void cPath::FinishCalculation(ePathFinderStatus a_NewStatus)
|
||||
{
|
||||
if (m_BadChunkFound)
|
||||
{
|
||||
a_NewStatus = ePathFinderStatus::PATH_NOT_FOUND;
|
||||
}
|
||||
m_Status = a_NewStatus;
|
||||
FinishCalculation();
|
||||
}
|
||||
@ -250,7 +300,7 @@ cPathCell * cPath::OpenListPop() // Popping from the open list also means addin
|
||||
{
|
||||
if (m_OpenList.size() == 0)
|
||||
{
|
||||
return nullptr; // We've exhausted the search space and nothing was found, this will trigger a PATH_NOT_FOUND status.
|
||||
return nullptr; // We've exhausted the search space and nothing was found, this will trigger a PATH_NOT_FOUND or NEARBY_FOUND status.
|
||||
}
|
||||
|
||||
cPathCell * Ret = m_OpenList.top();
|
||||
|
@ -23,7 +23,7 @@ Put this in your .cpp:
|
||||
class cChunk;
|
||||
|
||||
/* Various little structs and classes */
|
||||
enum class ePathFinderStatus {CALCULATING, PATH_FOUND, PATH_NOT_FOUND};
|
||||
enum class ePathFinderStatus {CALCULATING, PATH_FOUND, PATH_NOT_FOUND, NEARBY_FOUND};
|
||||
struct cPathCell; // Defined inside Path.cpp
|
||||
class compareHeuristics
|
||||
{
|
||||
@ -62,9 +62,17 @@ public:
|
||||
/** Destroys the path and frees its memory. */
|
||||
~cPath();
|
||||
|
||||
/** Performs part of the path calculation and returns true if the path computation has finished. */
|
||||
/** Performs part of the path calculation and returns the appropriate status.
|
||||
If NEARBY_FOUND is returned, it means that the destination is not reachable, but a nearby destination
|
||||
is reachable. If the user likes the alternative destination, they can call AcceptNearbyPath to treat the path as found,
|
||||
and to make consequent calls to step return PATH_FOUND*/
|
||||
ePathFinderStatus Step(cChunk & a_Chunk);
|
||||
|
||||
/** Called after the PathFinder's step returns NEARBY_FOUND.
|
||||
Changes the PathFinder status from NEARBY_FOUND to PATH_FOUND, returns the nearby destination that
|
||||
the PathFinder found a path to. */
|
||||
Vector3i AcceptNearbyPath();
|
||||
|
||||
/* Point retrieval functions, inlined for performance. */
|
||||
/** Returns the next point in the path. */
|
||||
inline Vector3i GetNextPoint()
|
||||
@ -93,7 +101,10 @@ public:
|
||||
/** Returns the total number of points this path has. */
|
||||
inline int GetPointCount()
|
||||
{
|
||||
ASSERT(m_Status == ePathFinderStatus::PATH_FOUND);
|
||||
if (m_Status != ePathFinderStatus::PATH_FOUND)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return m_PathPoints.size();
|
||||
}
|
||||
|
||||
@ -119,6 +130,8 @@ private:
|
||||
bool Step_Internal(); // The public version just calls this version * CALCULATIONS_PER_CALL times.
|
||||
void FinishCalculation(); // Clears the memory used for calculating the path.
|
||||
void FinishCalculation(ePathFinderStatus a_NewStatus); // Clears the memory used for calculating the path and changes the status.
|
||||
void AttemptToFindAlternative();
|
||||
void BuildPath();
|
||||
|
||||
/* Openlist and closedlist management */
|
||||
void OpenListAdd(cPathCell * a_Cell);
|
||||
@ -135,6 +148,7 @@ private:
|
||||
Vector3i m_Destination;
|
||||
Vector3i m_Source;
|
||||
int m_StepsLeft;
|
||||
cPathCell * m_NearestPointToTarget;
|
||||
|
||||
/* Control fields */
|
||||
ePathFinderStatus m_Status;
|
||||
@ -145,6 +159,7 @@ private:
|
||||
|
||||
/* Interfacing with the world */
|
||||
cChunk * m_Chunk; // Only valid inside Step()!
|
||||
bool m_BadChunkFound;
|
||||
#ifdef COMPILING_PATHFIND_DEBUGGER
|
||||
#include "../path_irrlicht.cpp"
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user