diff options
Diffstat (limited to 'src/Mobs')
-rw-r--r-- | src/Mobs/AggressiveMonster.cpp | 1 | ||||
-rw-r--r-- | src/Mobs/Monster.cpp | 124 | ||||
-rw-r--r-- | src/Mobs/Monster.h | 7 | ||||
-rw-r--r-- | src/Mobs/Path.cpp | 131 | ||||
-rw-r--r-- | src/Mobs/Path.h | 52 |
5 files changed, 231 insertions, 84 deletions
diff --git a/src/Mobs/AggressiveMonster.cpp b/src/Mobs/AggressiveMonster.cpp index d0fb79f6d..055ff47d2 100644 --- a/src/Mobs/AggressiveMonster.cpp +++ b/src/Mobs/AggressiveMonster.cpp @@ -36,7 +36,6 @@ void cAggressiveMonster::InStateChasing(std::chrono::milliseconds a_Dt) return; } } - MoveToPosition(m_Target->GetPosition()); } } diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index f3f8c6b24..f5d961096 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -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,15 +166,25 @@ 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_PathFinderDestination = m_Path->AcceptNearbyPath(); + break; + } + case ePathFinderStatus::PATH_NOT_FOUND: { - StopMovingToPosition(); // Give up pathfinding to that destination. + ResetPathFinding(); // Try to calculate a path again. + // Note that the next time may succeed, e.g. if a player breaks a barrier. break; } case ePathFinderStatus::CALCULATING: @@ -179,15 +194,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? + { + 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_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. + m_NoMoreWayPoints = true; } return true; } @@ -269,21 +291,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 +507,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 (std::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); + } } } diff --git a/src/Mobs/Monster.h b/src/Mobs/Monster.h index 5d20ba810..c4043b0e5 100644 --- a/src/Mobs/Monster.h +++ b/src/Mobs/Monster.h @@ -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. diff --git a/src/Mobs/Path.cpp b/src/Mobs/Path.cpp index ba7d615ae..eba29be7e 100644 --- a/src/Mobs/Path.cpp +++ b/src/Mobs/Path.cpp @@ -6,20 +6,14 @@ #include "Path.h" #include "../Chunk.h" + #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}; -struct cPathCell -{ - Vector3i m_Location; // Location of the cell in the world. - int m_F, m_G, m_H; // F, G, H as defined in regular A*. - eCellStatus m_Status; // Which list is the cell in? Either non, open, or closed. - cPathCell * m_Parent; // Cell's parent, as defined in regular A*. - bool m_IsSolid; // Is the cell an air or a solid? Partial solids are currently considered solids. -}; + + @@ -44,7 +38,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 +50,7 @@ cPath::cPath( return; } + m_NearestPointToTarget = GetCell(m_Source); m_Status = ePathFinderStatus::CALCULATING; m_StepsLeft = a_MaxSteps; @@ -81,15 +77,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 +103,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 +113,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 +131,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 +162,36 @@ 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 ((m_Destination - CurrentCell->m_Location).Length() < 5) + { + if (m_Rand.NextInt(4) == 0) + { + m_NearestPointToTarget = CurrentCell; + } + } + else 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,13 +228,40 @@ bool cPath::Step_Internal() -void cPath::FinishCalculation() +void cPath::AttemptToFindAlternative() { - for (auto && pair : m_Map) + if (m_NearestPointToTarget == GetCell(m_Source)) { - delete pair.second; + 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(); m_OpenList = std::priority_queue<cPathCell *, std::vector<cPathCell *>, compareHeuristics>{}; } @@ -230,6 +272,10 @@ void cPath::FinishCalculation() void cPath::FinishCalculation(ePathFinderStatus a_NewStatus) { + if (m_BadChunkFound) + { + a_NewStatus = ePathFinderStatus::PATH_NOT_FOUND; + } m_Status = a_NewStatus; FinishCalculation(); } @@ -255,7 +301,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(); @@ -343,23 +389,20 @@ void cPath::ProcessCell(cPathCell * a_Cell, cPathCell * a_Caller, int a_GDelta) cPathCell * cPath::GetCell(const Vector3i & a_Location) { // Create the cell in the hash table if it's not already there. - cPathCell * Cell; if (m_Map.count(a_Location) == 0) // Case 1: Cell is not on any list. We've never checked this cell before. { - Cell = new cPathCell(); - Cell->m_Location = a_Location; - m_Map[a_Location] = Cell; - Cell->m_IsSolid = IsSolid(a_Location); - Cell->m_Status = eCellStatus::NOLIST; + m_Map[a_Location].m_Location = a_Location; + m_Map[a_Location].m_IsSolid = IsSolid(a_Location); + m_Map[a_Location].m_Status = eCellStatus::NOLIST; #ifdef COMPILING_PATHFIND_DEBUGGER #ifdef COMPILING_PATHFIND_DEBUGGER_MARK_UNCHECKED si::setBlock(a_Location.x, a_Location.y, a_Location.z, debug_unchecked, Cell->m_IsSolid ? NORMAL : MINI); #endif #endif - return Cell; + return &m_Map[a_Location]; } else { - return m_Map[a_Location]; + return &m_Map[a_Location]; } } diff --git a/src/Mobs/Path.h b/src/Mobs/Path.h index adae77984..b296bbdf5 100644 --- a/src/Mobs/Path.h +++ b/src/Mobs/Path.h @@ -1,16 +1,13 @@ #pragma once -/* Wanna use the pathfinder? Put this in your header file: - -// Fwd: cPath +/* +// Needed Fwds: cPath enum class ePathFinderStatus; class cPath; - -Put this in your .cpp: -#include "...Path.h" */ +#include "../FastRandom.h" #ifdef COMPILING_PATHFIND_DEBUGGER /* Note: the COMPILING_PATHFIND_DEBUGGER flag is used by Native / WiseOldMan95 to debug this class outside of MCServer. This preprocessor flag is never set when compiling MCServer. */ @@ -23,14 +20,31 @@ Put this in your .cpp: class cChunk; /* Various little structs and classes */ -enum class ePathFinderStatus {CALCULATING, PATH_FOUND, PATH_NOT_FOUND}; -struct cPathCell; // Defined inside Path.cpp +enum class ePathFinderStatus {CALCULATING, PATH_FOUND, PATH_NOT_FOUND, NEARBY_FOUND}; +enum class eCellStatus {OPENLIST, CLOSEDLIST, NOLIST}; +struct cPathCell +{ + Vector3i m_Location; // Location of the cell in the world. + int m_F, m_G, m_H; // F, G, H as defined in regular A*. + eCellStatus m_Status; // Which list is the cell in? Either non, open, or closed. + cPathCell * m_Parent; // Cell's parent, as defined in regular A*. + bool m_IsSolid; // Is the cell an air or a solid? Partial solids are currently considered solids. +}; + + + + + class compareHeuristics { public: bool operator()(cPathCell * & a_V1, cPathCell * & a_V2); }; + + + + class cPath { public: @@ -62,9 +76,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 +115,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 +144,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); @@ -131,10 +158,12 @@ private: /* Pathfinding fields */ std::priority_queue<cPathCell *, std::vector<cPathCell *>, compareHeuristics> m_OpenList; - std::unordered_map<Vector3i, cPathCell *, VectorHasher> m_Map; + std::unordered_map<Vector3i, cPathCell, VectorHasher> m_Map; Vector3i m_Destination; Vector3i m_Source; int m_StepsLeft; + cPathCell * m_NearestPointToTarget; + cFastRandom m_Rand; /* Control fields */ ePathFinderStatus m_Status; @@ -145,6 +174,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 |