Merge pull request #1920 from wiseoldman95/SquashedPathFinder
Implemented A* Pathfinding and updated monster.cpp to use it.
This commit is contained in:
commit
aa142757db
@ -37,10 +37,7 @@ void cAggressiveMonster::InStateChasing(std::chrono::milliseconds a_Dt)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsMovingToTargetPosition())
|
MoveToPosition(m_Target->GetPosition());
|
||||||
{
|
|
||||||
MoveToPosition(m_Target->GetPosition());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ SET (SRCS
|
|||||||
Mooshroom.cpp
|
Mooshroom.cpp
|
||||||
PassiveAggressiveMonster.cpp
|
PassiveAggressiveMonster.cpp
|
||||||
PassiveMonster.cpp
|
PassiveMonster.cpp
|
||||||
|
Path.cpp
|
||||||
Pig.cpp
|
Pig.cpp
|
||||||
Rabbit.cpp
|
Rabbit.cpp
|
||||||
Sheep.cpp
|
Sheep.cpp
|
||||||
@ -62,6 +63,7 @@ SET (HDRS
|
|||||||
Ocelot.h
|
Ocelot.h
|
||||||
PassiveAggressiveMonster.h
|
PassiveAggressiveMonster.h
|
||||||
PassiveMonster.h
|
PassiveMonster.h
|
||||||
|
Path.h
|
||||||
Pig.h
|
Pig.h
|
||||||
Rabbit.h
|
Rabbit.h
|
||||||
Sheep.h
|
Sheep.h
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
#include "../Chunk.h"
|
#include "../Chunk.h"
|
||||||
#include "../FastRandom.h"
|
#include "../FastRandom.h"
|
||||||
|
|
||||||
|
#include "Path.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -74,6 +74,10 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A
|
|||||||
, m_EMState(IDLE)
|
, m_EMState(IDLE)
|
||||||
, m_EMPersonality(AGGRESSIVE)
|
, m_EMPersonality(AGGRESSIVE)
|
||||||
, m_Target(nullptr)
|
, m_Target(nullptr)
|
||||||
|
, m_Path(nullptr)
|
||||||
|
, m_PathStatus(ePathFinderStatus::PATH_NOT_FOUND)
|
||||||
|
, m_IsFollowingPath(false)
|
||||||
|
, m_GiveUpCounter(0)
|
||||||
, m_bMovingToDestination(false)
|
, m_bMovingToDestination(false)
|
||||||
, m_LastGroundHeight(POSY_TOINT)
|
, m_LastGroundHeight(POSY_TOINT)
|
||||||
, m_IdleInterval(0)
|
, m_IdleInterval(0)
|
||||||
@ -94,8 +98,9 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A
|
|||||||
, m_DropChanceLeggings(0.085f)
|
, m_DropChanceLeggings(0.085f)
|
||||||
, m_DropChanceBoots(0.085f)
|
, m_DropChanceBoots(0.085f)
|
||||||
, m_CanPickUpLoot(true)
|
, m_CanPickUpLoot(true)
|
||||||
|
, m_TicksSinceLastDamaged(100)
|
||||||
, m_BurnsInDaylight(false)
|
, m_BurnsInDaylight(false)
|
||||||
, m_RelativeWalkSpeed(1.0)
|
, m_RelativeWalkSpeed(1)
|
||||||
{
|
{
|
||||||
if (!a_ConfigName.empty())
|
if (!a_ConfigName.empty())
|
||||||
{
|
{
|
||||||
@ -118,87 +123,52 @@ void cMonster::SpawnOn(cClientHandle & a_Client)
|
|||||||
|
|
||||||
void cMonster::TickPathFinding()
|
void cMonster::TickPathFinding()
|
||||||
{
|
{
|
||||||
const int PosX = POSX_TOINT;
|
|
||||||
const int PosY = POSY_TOINT;
|
|
||||||
const int PosZ = POSZ_TOINT;
|
|
||||||
|
|
||||||
std::vector<Vector3d> m_PotentialCoordinates;
|
if (m_Path == nullptr)
|
||||||
m_TraversedCoordinates.push_back(Vector3i(PosX, PosY, PosZ));
|
{
|
||||||
|
Vector3d position = GetPosition();
|
||||||
|
Vector3d Dest = m_FinalDestination;
|
||||||
|
|
||||||
static const struct // Define which directions to try to move to
|
// Can someone explain why are these two NOT THE SAME???
|
||||||
{
|
// m_Path = new cPath(GetWorld(), GetPosition(), m_FinalDestination, 30);
|
||||||
int x, z;
|
m_Path = new cPath(GetWorld(), Vector3d(floor(position.x), floor(position.y), floor(position.z)), Vector3d(floor(Dest.x), floor(Dest.y), floor(Dest.z)), 20);
|
||||||
} gCrossCoords[] =
|
|
||||||
{
|
|
||||||
{ 1, 0},
|
|
||||||
{-1, 0},
|
|
||||||
{ 0, 1},
|
|
||||||
{ 0, -1},
|
|
||||||
} ;
|
|
||||||
|
|
||||||
if ((PosY - 1 < 0) || (PosY + 2 >= cChunkDef::Height) /* PosY + 1 will never be true if PosY + 2 is not */)
|
|
||||||
{
|
m_IsFollowingPath = false;
|
||||||
// Too low/high, can't really do anything
|
|
||||||
FinishPathFinding();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
m_PathStatus = m_Path->Step();
|
||||||
for (size_t i = 0; i < ARRAYCOUNT(gCrossCoords); i++)
|
switch (m_PathStatus)
|
||||||
{
|
{
|
||||||
if (IsCoordinateInTraversedList(Vector3i(gCrossCoords[i].x + PosX, PosY, gCrossCoords[i].z + PosZ)))
|
|
||||||
|
case ePathFinderStatus::PATH_NOT_FOUND:
|
||||||
{
|
{
|
||||||
continue;
|
FinishPathFinding();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
BLOCKTYPE BlockAtY = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY, gCrossCoords[i].z + PosZ);
|
|
||||||
BLOCKTYPE BlockAtYP = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY + 1, gCrossCoords[i].z + PosZ);
|
|
||||||
BLOCKTYPE BlockAtYPP = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY + 2, gCrossCoords[i].z + PosZ);
|
|
||||||
int LowestY = FindFirstNonAirBlockPosition(gCrossCoords[i].x + PosX, gCrossCoords[i].z + PosZ);
|
|
||||||
BLOCKTYPE BlockAtLowestY = (LowestY >= cChunkDef::Height) ? E_BLOCK_AIR : m_World->GetBlock(gCrossCoords[i].x + PosX, LowestY, gCrossCoords[i].z + PosZ);
|
|
||||||
|
|
||||||
if (
|
case ePathFinderStatus::CALCULATING:
|
||||||
(!cBlockInfo::IsSolid(BlockAtY)) &&
|
|
||||||
(!cBlockInfo::IsSolid(BlockAtYP)) &&
|
|
||||||
(!IsBlockLava(BlockAtLowestY)) &&
|
|
||||||
(BlockAtLowestY != E_BLOCK_CACTUS) &&
|
|
||||||
(PosY - LowestY < FALL_DAMAGE_HEIGHT)
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY, gCrossCoords[i].z + PosZ));
|
m_Destination = GetPosition();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
else if (
|
|
||||||
(cBlockInfo::IsSolid(BlockAtY)) &&
|
|
||||||
(BlockAtY != E_BLOCK_CACTUS) &&
|
|
||||||
(!cBlockInfo::IsSolid(BlockAtYP)) &&
|
|
||||||
(!cBlockInfo::IsSolid(BlockAtYPP)) &&
|
|
||||||
(BlockAtY != E_BLOCK_FENCE) &&
|
|
||||||
(BlockAtY != E_BLOCK_FENCE_GATE)
|
|
||||||
)
|
|
||||||
{
|
|
||||||
m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY + 1, gCrossCoords[i].z + PosZ));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_PotentialCoordinates.empty())
|
|
||||||
{
|
case ePathFinderStatus::PATH_FOUND:
|
||||||
Vector3f ShortestCoords = m_PotentialCoordinates.front();
|
|
||||||
for (std::vector<Vector3d>::const_iterator itr = m_PotentialCoordinates.begin(); itr != m_PotentialCoordinates.end(); ++itr)
|
|
||||||
{
|
{
|
||||||
Vector3f Distance = m_FinalDestination - ShortestCoords;
|
if (ReachedDestination() || !m_IsFollowingPath)
|
||||||
Vector3f Distance2 = m_FinalDestination - *itr;
|
|
||||||
if (Distance.SqrLength() > Distance2.SqrLength())
|
|
||||||
{
|
{
|
||||||
ShortestCoords = *itr;
|
m_Destination = m_Path->GetNextPoint();
|
||||||
|
m_IsFollowingPath = true;
|
||||||
|
m_GiveUpCounter = 40; // Give up after 40 ticks (2 seconds) if failed to reach m_Dest.
|
||||||
}
|
}
|
||||||
}
|
if (m_Path->IsLastPoint())
|
||||||
|
{
|
||||||
|
FinishPathFinding();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
m_Destination = ShortestCoords;
|
}
|
||||||
m_Destination.z += 0.5f;
|
|
||||||
m_Destination.x += 0.5f;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
FinishPathFinding();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,17 +176,28 @@ void cMonster::TickPathFinding()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Currently, the mob will only start moving to a new position after the position it is
|
||||||
|
currently going to is reached. */
|
||||||
void cMonster::MoveToPosition(const Vector3d & a_Position)
|
void cMonster::MoveToPosition(const Vector3d & a_Position)
|
||||||
{
|
{
|
||||||
FinishPathFinding();
|
|
||||||
|
|
||||||
m_FinalDestination = a_Position;
|
m_FinalDestination = a_Position;
|
||||||
m_bMovingToDestination = true;
|
m_bMovingToDestination = true;
|
||||||
TickPathFinding();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cMonster::StopMovingToPosition()
|
||||||
|
{
|
||||||
|
m_bMovingToDestination = false;
|
||||||
|
FinishPathFinding();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool cMonster::IsCoordinateInTraversedList(Vector3i a_Coords)
|
bool cMonster::IsCoordinateInTraversedList(Vector3i a_Coords)
|
||||||
{
|
{
|
||||||
return (std::find(m_TraversedCoordinates.begin(), m_TraversedCoordinates.end(), a_Coords) != m_TraversedCoordinates.end());
|
return (std::find(m_TraversedCoordinates.begin(), m_TraversedCoordinates.end(), a_Coords) != m_TraversedCoordinates.end());
|
||||||
@ -226,6 +207,22 @@ bool cMonster::IsCoordinateInTraversedList(Vector3i a_Coords)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* No one should call this except the pathfinder orthe monster tick or StopMovingToPosition.
|
||||||
|
Resets the pathfinder, usually starting a brand new path, unless called from StopMovingToPosition. */
|
||||||
|
void cMonster::FinishPathFinding(void)
|
||||||
|
{
|
||||||
|
if (m_Path != nullptr)
|
||||||
|
{
|
||||||
|
delete m_Path;
|
||||||
|
m_Path = nullptr;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool cMonster::ReachedDestination()
|
bool cMonster::ReachedDestination()
|
||||||
{
|
{
|
||||||
if ((m_Destination - GetPosition()).Length() < 0.5f)
|
if ((m_Destination - GetPosition()).Length() < 0.5f)
|
||||||
@ -239,6 +236,7 @@ bool cMonster::ReachedDestination()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool cMonster::ReachedFinalDestination()
|
bool cMonster::ReachedFinalDestination()
|
||||||
{
|
{
|
||||||
if ((GetPosition() - m_FinalDestination).Length() <= m_AttackRange)
|
if ((GetPosition() - m_FinalDestination).Length() <= m_AttackRange)
|
||||||
@ -268,6 +266,10 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_TicksSinceLastDamaged < 100)
|
||||||
|
{
|
||||||
|
++m_TicksSinceLastDamaged;
|
||||||
|
}
|
||||||
if ((m_Target != nullptr) && m_Target->IsDestroyed())
|
if ((m_Target != nullptr) && m_Target->IsDestroyed())
|
||||||
{
|
{
|
||||||
m_Target = nullptr;
|
m_Target = nullptr;
|
||||||
@ -276,6 +278,8 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
|||||||
// Burning in daylight
|
// Burning in daylight
|
||||||
HandleDaylightBurning(a_Chunk);
|
HandleDaylightBurning(a_Chunk);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (m_bMovingToDestination)
|
if (m_bMovingToDestination)
|
||||||
{
|
{
|
||||||
if (m_bOnGround)
|
if (m_bOnGround)
|
||||||
@ -289,53 +293,51 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TickPathFinding();
|
||||||
|
|
||||||
Vector3d Distance = m_Destination - GetPosition();
|
Vector3d Distance = m_Destination - GetPosition();
|
||||||
if (!ReachedDestination() && !ReachedFinalDestination()) // If we haven't reached any sort of destination, move
|
if (!ReachedDestination() && !ReachedFinalDestination()) // If we haven't reached any sort of destination, move
|
||||||
{
|
{
|
||||||
Distance.y = 0;
|
if (--m_GiveUpCounter == 0)
|
||||||
Distance.Normalize();
|
|
||||||
|
|
||||||
if (m_bOnGround)
|
|
||||||
{
|
|
||||||
Distance *= 2.5f;
|
|
||||||
}
|
|
||||||
else if (IsSwimming())
|
|
||||||
{
|
|
||||||
Distance *= 1.3f;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Don't let the mob move too much if he's falling.
|
|
||||||
Distance *= 0.25f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply walk speed:
|
|
||||||
Distance *= m_RelativeWalkSpeed;
|
|
||||||
|
|
||||||
AddSpeedX(Distance.x);
|
|
||||||
AddSpeedZ(Distance.z);
|
|
||||||
|
|
||||||
// It's too buggy!
|
|
||||||
/*
|
|
||||||
if (m_EMState == ESCAPING)
|
|
||||||
{
|
|
||||||
// Runs Faster when escaping :D otherwise they just walk away
|
|
||||||
SetSpeedX (GetSpeedX() * 2.f);
|
|
||||||
SetSpeedZ (GetSpeedZ() * 2.f);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (ReachedFinalDestination()) // If we have reached the ultimate, final destination, stop pathfinding and attack if appropriate
|
|
||||||
{
|
{
|
||||||
FinishPathFinding();
|
FinishPathFinding();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TickPathFinding(); // We have reached the next point in our path, calculate another point
|
Distance.y = 0;
|
||||||
|
Distance.Normalize();
|
||||||
|
|
||||||
|
if (m_bOnGround)
|
||||||
|
{
|
||||||
|
Distance *= 2.5f;
|
||||||
|
}
|
||||||
|
else if (IsSwimming())
|
||||||
|
{
|
||||||
|
Distance *= 1.3f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Don't let the mob move too much if he's falling.
|
||||||
|
Distance *= 0.25f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply walk speed:
|
||||||
|
Distance *= m_RelativeWalkSpeed;
|
||||||
|
|
||||||
|
/* Reduced default speed.
|
||||||
|
Close to Vanilla, easier for mobs to follow m_Destinations, hence
|
||||||
|
better pathfinding. */
|
||||||
|
Distance *= 0.5;
|
||||||
|
|
||||||
|
AddSpeedX(Distance.x);
|
||||||
|
AddSpeedZ(Distance.z);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (ReachedFinalDestination())
|
||||||
|
{
|
||||||
|
StopMovingToPosition();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SetPitchAndYawFromDestination();
|
SetPitchAndYawFromDestination();
|
||||||
@ -345,13 +347,13 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
|||||||
{
|
{
|
||||||
case IDLE:
|
case IDLE:
|
||||||
{
|
{
|
||||||
// If enemy passive we ignore checks for player visibility
|
// If enemy passive we ignore checks for player visibility.
|
||||||
InStateIdle(a_Dt);
|
InStateIdle(a_Dt);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CHASING:
|
case CHASING:
|
||||||
{
|
{
|
||||||
// If we do not see a player anymore skip chasing action
|
// If we do not see a player anymore skip chasing action.
|
||||||
InStateChasing(a_Dt);
|
InStateChasing(a_Dt);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -365,7 +367,8 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
|||||||
} // switch (m_EMState)
|
} // switch (m_EMState)
|
||||||
|
|
||||||
BroadcastMovementUpdate();
|
BroadcastMovementUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -409,6 +412,7 @@ void cMonster::SetPitchAndYawFromDestination()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void cMonster::HandleFalling()
|
void cMonster::HandleFalling()
|
||||||
{
|
{
|
||||||
if (m_bOnGround)
|
if (m_bOnGround)
|
||||||
@ -460,7 +464,6 @@ int cMonster::FindFirstNonAirBlockPosition(double a_PosX, double a_PosZ)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI)
|
bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI)
|
||||||
{
|
{
|
||||||
if (!super::DoTakeDamage(a_TDI))
|
if (!super::DoTakeDamage(a_TDI))
|
||||||
@ -476,6 +479,7 @@ bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI)
|
|||||||
if (a_TDI.Attacker != nullptr)
|
if (a_TDI.Attacker != nullptr)
|
||||||
{
|
{
|
||||||
m_Target = a_TDI.Attacker;
|
m_Target = a_TDI.Attacker;
|
||||||
|
m_TicksSinceLastDamaged = 0;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1100,16 +1104,27 @@ void cMonster::HandleDaylightBurning(cChunk & a_Chunk)
|
|||||||
// Outside the world
|
// Outside the world
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int RelX = POSX_TOINT - GetChunkX() * cChunkDef::Width;
|
|
||||||
int RelZ = POSZ_TOINT - GetChunkZ() * cChunkDef::Width;
|
|
||||||
|
|
||||||
if (!a_Chunk.IsLightValid())
|
if (!a_Chunk.IsLightValid())
|
||||||
{
|
{
|
||||||
m_World->QueueLightChunk(GetChunkX(), GetChunkZ());
|
m_World->QueueLightChunk(GetChunkX(), GetChunkZ());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (WouldBurnAt(GetPosition(), a_Chunk))
|
||||||
|
{
|
||||||
|
// Burn for 100 ticks, then decide again
|
||||||
|
StartBurning(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool cMonster::WouldBurnAt(Vector3d a_Location, cChunk & a_Chunk)
|
||||||
|
{
|
||||||
|
int RelX = FloorC(a_Location.x) - a_Chunk.GetPosX() * cChunkDef::Width;
|
||||||
|
int RelY = FloorC(a_Location.y);
|
||||||
|
int RelZ = FloorC(a_Location.z) - a_Chunk.GetPosZ() * cChunkDef::Width;
|
||||||
if (
|
if (
|
||||||
(a_Chunk.GetSkyLight(RelX, RelY, RelZ) == 15) && // In the daylight
|
(a_Chunk.GetSkyLight(RelX, RelY, RelZ) == 15) && // In the daylight
|
||||||
(a_Chunk.GetBlock(RelX, RelY, RelZ) != E_BLOCK_SOULSAND) && // Not on soulsand
|
(a_Chunk.GetBlock(RelX, RelY, RelZ) != E_BLOCK_SOULSAND) && // Not on soulsand
|
||||||
@ -1118,14 +1133,15 @@ void cMonster::HandleDaylightBurning(cChunk & a_Chunk)
|
|||||||
GetWorld()->IsWeatherSunnyAt(POSX_TOINT, POSZ_TOINT) // Not raining
|
GetWorld()->IsWeatherSunnyAt(POSX_TOINT, POSZ_TOINT) // Not raining
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// Burn for 100 ticks, then decide again
|
return true;
|
||||||
StartBurning(100);
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
cMonster::eFamily cMonster::GetMobFamily(void) const
|
cMonster::eFamily cMonster::GetMobFamily(void) const
|
||||||
{
|
{
|
||||||
return FamilyFromType(m_MobType);
|
return FamilyFromType(m_MobType);
|
||||||
|
@ -10,11 +10,12 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class cClientHandle;
|
class cClientHandle;
|
||||||
class cWorld;
|
class cWorld;
|
||||||
|
|
||||||
|
// Fwd: cPath
|
||||||
|
enum class ePathFinderStatus;
|
||||||
|
class cPath;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -61,6 +62,7 @@ public:
|
|||||||
virtual void OnRightClicked(cPlayer & a_Player) override;
|
virtual void OnRightClicked(cPlayer & a_Player) override;
|
||||||
|
|
||||||
virtual void MoveToPosition(const Vector3d & a_Position); // tolua_export
|
virtual void MoveToPosition(const Vector3d & a_Position); // tolua_export
|
||||||
|
virtual void StopMovingToPosition();
|
||||||
virtual bool ReachedDestination(void);
|
virtual bool ReachedDestination(void);
|
||||||
|
|
||||||
// tolua_begin
|
// tolua_begin
|
||||||
@ -162,6 +164,11 @@ protected:
|
|||||||
|
|
||||||
/** A pointer to the entity this mobile is aiming to reach */
|
/** A pointer to the entity this mobile is aiming to reach */
|
||||||
cEntity * m_Target;
|
cEntity * m_Target;
|
||||||
|
cPath * m_Path; // TODO unique ptr
|
||||||
|
ePathFinderStatus m_PathStatus;
|
||||||
|
bool m_IsFollowingPath;
|
||||||
|
/* If 0, will give up reaching the next m_Dest and will re-compute path. */
|
||||||
|
int m_GiveUpCounter;
|
||||||
/** Coordinates of the next position that should be reached */
|
/** Coordinates of the next position that should be reached */
|
||||||
Vector3d m_Destination;
|
Vector3d m_Destination;
|
||||||
/** Coordinates for the ultimate, final destination. */
|
/** Coordinates for the ultimate, final destination. */
|
||||||
@ -201,11 +208,7 @@ protected:
|
|||||||
This is based on the ultimate, final destination and the current position, as well as the traversed coordinates, and any environmental hazards */
|
This is based on the ultimate, final destination and the current position, as well as the traversed coordinates, and any environmental hazards */
|
||||||
void TickPathFinding(void);
|
void TickPathFinding(void);
|
||||||
/** Finishes a pathfinding task, be it due to failure or something else */
|
/** Finishes a pathfinding task, be it due to failure or something else */
|
||||||
inline void FinishPathFinding(void)
|
void FinishPathFinding(void);
|
||||||
{
|
|
||||||
m_TraversedCoordinates.clear();
|
|
||||||
m_bMovingToDestination = false;
|
|
||||||
}
|
|
||||||
/** Sets the body yaw and head yaw/pitch based on next/ultimate destinations */
|
/** Sets the body yaw and head yaw/pitch based on next/ultimate destinations */
|
||||||
void SetPitchAndYawFromDestination(void);
|
void SetPitchAndYawFromDestination(void);
|
||||||
|
|
||||||
@ -239,10 +242,11 @@ protected:
|
|||||||
float m_DropChanceLeggings;
|
float m_DropChanceLeggings;
|
||||||
float m_DropChanceBoots;
|
float m_DropChanceBoots;
|
||||||
bool m_CanPickUpLoot;
|
bool m_CanPickUpLoot;
|
||||||
|
int m_TicksSinceLastDamaged; // How many ticks ago we were last damaged by a player?
|
||||||
|
|
||||||
void HandleDaylightBurning(cChunk & a_Chunk);
|
void HandleDaylightBurning(cChunk & a_Chunk);
|
||||||
|
bool WouldBurnAt(Vector3d a_Location, cChunk & a_Chunk);
|
||||||
bool m_BurnsInDaylight;
|
bool m_BurnsInDaylight;
|
||||||
|
|
||||||
double m_RelativeWalkSpeed;
|
double m_RelativeWalkSpeed;
|
||||||
|
|
||||||
/** Adds a random number of a_Item between a_Min and a_Max to itemdrops a_Drops*/
|
/** Adds a random number of a_Item between a_Min and a_Max to itemdrops a_Drops*/
|
||||||
|
379
src/Mobs/Path.cpp
Normal file
379
src/Mobs/Path.cpp
Normal file
@ -0,0 +1,379 @@
|
|||||||
|
#include "Globals.h"
|
||||||
|
#ifndef COMPILING_PATHFIND_DEBUGGER
|
||||||
|
/* MCServer headers */
|
||||||
|
#include "../World.h"
|
||||||
|
#include "../Chunk.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include "Path.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.
|
||||||
|
// The only version which guarantees the shortest path is 0, 0.
|
||||||
|
|
||||||
|
enum class eCellStatus {OPENLIST, CLOSEDLIST, NOLIST};
|
||||||
|
struct cPathCell
|
||||||
|
{
|
||||||
|
Vector3d 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.
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool compareHeuristics::operator()(cPathCell * & a_Cell1, cPathCell * & a_Cell2)
|
||||||
|
{
|
||||||
|
return a_Cell1->m_F > a_Cell2->m_F;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* cPath implementation */
|
||||||
|
cPath::cPath(
|
||||||
|
cWorld * a_World,
|
||||||
|
const Vector3d & a_StartingPoint, const Vector3d & a_EndingPoint, int a_MaxSteps,
|
||||||
|
double a_BoundingBoxWidth, double a_BoundingBoxHeight,
|
||||||
|
int a_MaxUp, int a_MaxDown
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// TODO: if src not walkable OR dest not walkable, then abort.
|
||||||
|
// Borrow a new "isWalkable" from ProcessIfWalkable, make ProcessIfWalkable also call isWalkable
|
||||||
|
|
||||||
|
m_World = a_World;
|
||||||
|
// m_World = cRoot::Get()->GetDefaultWorld();
|
||||||
|
|
||||||
|
m_Source = a_StartingPoint.Floor();
|
||||||
|
m_Destination = a_EndingPoint.Floor();
|
||||||
|
|
||||||
|
if (GetCell(m_Source)->m_IsSolid || GetCell(m_Destination)->m_IsSolid)
|
||||||
|
{
|
||||||
|
m_Status = ePathFinderStatus::PATH_NOT_FOUND;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Status = ePathFinderStatus::CALCULATING;
|
||||||
|
|
||||||
|
m_StepsLeft = a_MaxSteps;
|
||||||
|
m_PointCount = 0;
|
||||||
|
|
||||||
|
ProcessCell(GetCell(a_StartingPoint), nullptr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cPath::~cPath()
|
||||||
|
{
|
||||||
|
if (m_Status == ePathFinderStatus::CALCULATING)
|
||||||
|
{
|
||||||
|
FinishCalculation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ePathFinderStatus cPath::Step()
|
||||||
|
{
|
||||||
|
if (m_Status != ePathFinderStatus::CALCULATING)
|
||||||
|
{
|
||||||
|
return m_Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_StepsLeft == 0)
|
||||||
|
{
|
||||||
|
FinishCalculation(ePathFinderStatus::PATH_NOT_FOUND);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
--m_StepsLeft;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < CALCULATIONS_PER_STEP; ++i)
|
||||||
|
{
|
||||||
|
if (Step_Internal()) // Step_Internal returns true when no more calculation is needed.
|
||||||
|
{
|
||||||
|
break; // if we're here, m_Status must have changed either to PATH_FOUND or PATH_NOT_FOUND.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m_Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef COMPILING_PATHFIND_DEBUGGER
|
||||||
|
bool cPath::IsSolid(const Vector3d & a_Location)
|
||||||
|
{
|
||||||
|
int ChunkX, ChunkZ;
|
||||||
|
m_Item_CurrentBlock = a_Location;
|
||||||
|
cChunkDef::BlockToChunk(a_Location.x, a_Location.z, ChunkX, ChunkZ);
|
||||||
|
return !m_World->DoWithChunk(ChunkX, ChunkZ, * this);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool cPath::Step_Internal()
|
||||||
|
{
|
||||||
|
cPathCell * CurrentCell = OpenListPop();
|
||||||
|
|
||||||
|
// Path not reachable, open list exauhsted.
|
||||||
|
if (CurrentCell == nullptr)
|
||||||
|
{
|
||||||
|
FinishCalculation(ePathFinderStatus::PATH_NOT_FOUND);
|
||||||
|
ASSERT(m_Status == ePathFinderStatus::PATH_NOT_FOUND);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path found.
|
||||||
|
if (CurrentCell->m_Location == m_Destination)
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
AddPoint(CurrentCell->m_Location + Vector3d(0.5, 0, 0.5)); // Populate the cPath with points.
|
||||||
|
CurrentCell = CurrentCell->m_Parent;
|
||||||
|
} while (CurrentCell != nullptr);
|
||||||
|
|
||||||
|
m_CurrentPoint = -1;
|
||||||
|
FinishCalculation(ePathFinderStatus::PATH_FOUND);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculation not finished yet, process a currentCell by inspecting all neighbors.
|
||||||
|
|
||||||
|
// Check North, South, East, West on all 3 different heights.
|
||||||
|
int i;
|
||||||
|
for (i = -1; i <= 1; ++i)
|
||||||
|
{
|
||||||
|
ProcessIfWalkable(CurrentCell->m_Location + Vector3d(1, i, 0), CurrentCell, 10);
|
||||||
|
ProcessIfWalkable(CurrentCell->m_Location + Vector3d(-1, i, 0), CurrentCell, 10);
|
||||||
|
ProcessIfWalkable(CurrentCell->m_Location + Vector3d(0, i, 1), CurrentCell, 10);
|
||||||
|
ProcessIfWalkable(CurrentCell->m_Location + Vector3d(0, i, -1), CurrentCell, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check diagonals on mob's height only.
|
||||||
|
int x, z;
|
||||||
|
for (x = -1; x <= 1; x += 2)
|
||||||
|
{
|
||||||
|
for (z = -1; z <= 1; z += 2)
|
||||||
|
{
|
||||||
|
// This condition prevents diagonal corner cutting.
|
||||||
|
if (!GetCell(CurrentCell->m_Location + Vector3d(x, 0, 0))->m_IsSolid && !GetCell(CurrentCell->m_Location + Vector3d(0, 0, z))->m_IsSolid)
|
||||||
|
{
|
||||||
|
// This prevents falling of "sharp turns" e.g. a 1x1x20 rectangle in the air which breaks in a right angle suddenly.
|
||||||
|
if (GetCell(CurrentCell->m_Location + Vector3d(x, -1, 0))->m_IsSolid && GetCell(CurrentCell->m_Location + Vector3d(0, -1, z))->m_IsSolid)
|
||||||
|
{
|
||||||
|
ProcessIfWalkable(CurrentCell->m_Location + Vector3d(x, 0, z), CurrentCell, 14); // 14 is a good enough approximation of sqrt(10 + 10).
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cPath::FinishCalculation()
|
||||||
|
{
|
||||||
|
for (auto && pair : m_Map)
|
||||||
|
{
|
||||||
|
delete pair.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Map.clear();
|
||||||
|
m_OpenList.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cPath::FinishCalculation(ePathFinderStatus a_NewStatus)
|
||||||
|
{
|
||||||
|
m_Status = a_NewStatus;
|
||||||
|
FinishCalculation();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cPath::OpenListAdd(cPathCell * a_Cell)
|
||||||
|
{
|
||||||
|
a_Cell->m_Status = eCellStatus::OPENLIST;
|
||||||
|
m_OpenList.push(a_Cell);
|
||||||
|
#ifdef COMPILING_PATHFIND_DEBUGGER
|
||||||
|
si::setBlock(a_Cell->m_Location.x, a_Cell->m_Location.y, a_Cell->m_Location.z, debug_open, SetMini(a_Cell));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cPathCell * cPath::OpenListPop() // Popping from the open list also means adding to the closed list.
|
||||||
|
{
|
||||||
|
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.
|
||||||
|
}
|
||||||
|
|
||||||
|
cPathCell * Ret = m_OpenList.top();
|
||||||
|
m_OpenList.pop();
|
||||||
|
Ret->m_Status = eCellStatus::CLOSEDLIST;
|
||||||
|
#ifdef COMPILING_PATHFIND_DEBUGGER
|
||||||
|
si::setBlock((Ret)->m_Location.x, (Ret)->m_Location.y, (Ret)->m_Location.z, debug_closed, SetMini(Ret));
|
||||||
|
#endif
|
||||||
|
return Ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cPath::ProcessIfWalkable(const Vector3d & a_Location, cPathCell * a_Parent, int a_Cost)
|
||||||
|
{
|
||||||
|
cPathCell * cell = GetCell(a_Location);
|
||||||
|
if (!cell->m_IsSolid && GetCell(a_Location + Vector3d(0, -1, 0))->m_IsSolid && !GetCell(a_Location + Vector3d(0, 1, 0))->m_IsSolid)
|
||||||
|
{
|
||||||
|
ProcessCell(cell, a_Parent, a_Cost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cPath::ProcessCell(cPathCell * a_Cell, cPathCell * a_Caller, int a_GDelta)
|
||||||
|
{
|
||||||
|
// Case 1: Cell is in the closed list, ignore it.
|
||||||
|
if (a_Cell->m_Status == eCellStatus::CLOSEDLIST)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (a_Cell->m_Status == eCellStatus::NOLIST) // Case 2: The cell is not in any list.
|
||||||
|
{
|
||||||
|
// Cell is walkable, add it to the open list.
|
||||||
|
// Note that non-walkable cells are filtered out in Step_internal();
|
||||||
|
// Special case: Start cell goes here, gDelta is 0, caller is NULL.
|
||||||
|
a_Cell->m_Parent = a_Caller;
|
||||||
|
if (a_Caller != nullptr)
|
||||||
|
{
|
||||||
|
a_Cell->m_G = a_Caller->m_G + a_GDelta;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
a_Cell->m_G = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate H. This is A*'s Heuristics value.
|
||||||
|
#if DISTANCE_MANHATTAN == 1
|
||||||
|
// Manhattan distance. DeltaX + DeltaY + DeltaZ.
|
||||||
|
a_Cell->m_H = 10 * (abs(a_Cell->m_Location.x-m_Destination.x) + abs(a_Cell->m_Location.y-m_Destination.y) + abs(a_Cell->m_Location.z-m_Destination.z));
|
||||||
|
#else
|
||||||
|
// Euclidian distance. sqrt(DeltaX^2 + DeltaY^2 + DeltaZ^2), more precise.
|
||||||
|
a_Cell->m_H = std::sqrt((a_Cell->m_Location.x - m_Destination.x) * (a_Cell->m_Location.x - m_Destination.x) * 100 + (a_Cell->m_Location.y - m_Destination.y) * (a_Cell->m_Location.y - m_Destination.y) * 100 + (a_Cell->m_Location.z - m_Destination.z) * (a_Cell->m_Location.z - m_Destination.z) * 100);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if HEURISTICS_ONLY == 1
|
||||||
|
a_Cell->m_F = a_Cell->m_H; // Greedy search. https://en.wikipedia.org/wiki/Greedy_search
|
||||||
|
#else
|
||||||
|
a_Cell->m_F = a_Cell->m_H + a_Cell->m_G; // Regular A*.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
OpenListAdd(a_Cell);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 3: Cell is in the open list, check if G and H need an update.
|
||||||
|
int NewG = a_Caller->m_G + a_GDelta;
|
||||||
|
if (NewG < a_Cell->m_G)
|
||||||
|
{
|
||||||
|
a_Cell->m_G = NewG;
|
||||||
|
a_Cell->m_H = a_Cell->m_F + a_Cell->m_G;
|
||||||
|
a_Cell->m_Parent = a_Caller;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cPathCell * cPath::GetCell(const Vector3d & 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;
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return m_Map[a_Location];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Add the next point in the final path.
|
||||||
|
void cPath::AddPoint(Vector3d a_Vector)
|
||||||
|
{
|
||||||
|
m_PathPoints.push_back(a_Vector);
|
||||||
|
++m_PointCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef COMPILING_PATHFIND_DEBUGGER
|
||||||
|
bool cPath::Item(cChunk * a_Chunk) // returns FALSE if there's a solid or if we failed.
|
||||||
|
{
|
||||||
|
int RelX = m_Item_CurrentBlock.x - a_Chunk->GetPosX() * cChunkDef::Width;
|
||||||
|
int RelZ = m_Item_CurrentBlock.z - a_Chunk->GetPosZ() * cChunkDef::Width;
|
||||||
|
|
||||||
|
if (!a_Chunk->IsValid())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
BLOCKTYPE BlockType;
|
||||||
|
NIBBLETYPE BlockMeta;
|
||||||
|
a_Chunk->GetBlockTypeMeta(RelX, m_Item_CurrentBlock.y, RelZ, BlockType, BlockMeta);
|
||||||
|
return (!cBlockInfo::IsSolid(BlockType));
|
||||||
|
|
||||||
|
// TODO Maybe I should queue several blocks and call item() at once for all of them for better performance?
|
||||||
|
// I think Worktycho said each item() call needs 2 locks.
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
161
src/Mobs/Path.h
Normal file
161
src/Mobs/Path.h
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/* Wanna use the pathfinder? Put this in your header file:
|
||||||
|
|
||||||
|
// Fwd: cPath
|
||||||
|
enum class ePathFinderStatus;
|
||||||
|
class cPath;
|
||||||
|
|
||||||
|
Put this in your .cpp:
|
||||||
|
#include "...Path.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. */
|
||||||
|
#include "PathFinderIrrlicht_Head.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
/* MCServer forward declarations */
|
||||||
|
#ifndef COMPILING_PATHFIND_DEBUGGER
|
||||||
|
|
||||||
|
// fwd: cChunkMap.h
|
||||||
|
typedef cItemCallback<cChunk> cChunkCallback;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Various little structs and classes */
|
||||||
|
enum class ePathFinderStatus {CALCULATING, PATH_FOUND, PATH_NOT_FOUND};
|
||||||
|
struct cPathCell; // Defined inside Path.cpp
|
||||||
|
class compareHeuristics
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool operator()(cPathCell * & a_V1, cPathCell * & a_V2);
|
||||||
|
};
|
||||||
|
|
||||||
|
class cPath
|
||||||
|
#ifndef COMPILING_PATHFIND_DEBUGGER
|
||||||
|
: public cChunkCallback
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Creates a pathfinder instance. A Mob will probably need a single pathfinder instance for its entire life.
|
||||||
|
|
||||||
|
Note that if you have a man-sized mob (1x1x2, zombies, etc), you are advised to call this function without parameters
|
||||||
|
because the declaration might change in later version of the pathFinder, and a parameter-less call always assumes a man-sized mob.
|
||||||
|
|
||||||
|
If your mob is not man-sized, you are advised to use cPath(width, height), this would be compatible with future versions,
|
||||||
|
but please be aware that as of now those parameters will be ignored and your mob will be assumed to be man sized.
|
||||||
|
|
||||||
|
@param a_BoundingBoxWidth the character's boundingbox width in blocks. Currently the parameter is ignored and 1 is assumed.
|
||||||
|
@param a_BoundingBoxHeight the character's boundingbox width in blocks. Currently the parameter is ignored and 2 is assumed.
|
||||||
|
@param a_MaxUp the character's max jump height in blocks. Currently the parameter is ignored and 1 is assumed.
|
||||||
|
@param a_MaxDown How far is the character willing to fall? Currently the parameter is ignored and 1 is assumed. */
|
||||||
|
/** Attempts to find a path starting from source to destination.
|
||||||
|
After calling this, you are expected to call Step() once per tick or once per several ticks until it returns true. You should then call getPath() to obtain the path.
|
||||||
|
Calling this before a path is found resets the current path and starts another search.
|
||||||
|
@param a_StartingPoint The function expects this position to be the lowest block the mob is in, a rule of thumb: "The block where the Zombie's knees are at".
|
||||||
|
@param a_EndingPoint "The block where the Zombie's knees want to be".
|
||||||
|
@param a_MaxSteps The maximum steps before giving up. */
|
||||||
|
cPath(
|
||||||
|
cWorld * a_World,
|
||||||
|
const Vector3d & a_StartingPoint, const Vector3d & a_EndingPoint, int a_MaxSteps,
|
||||||
|
double a_BoundingBoxWidth = 1, double a_BoundingBoxHeight = 2,
|
||||||
|
int a_MaxUp = 1, int a_MaxDown = 1
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Destroys the path and frees its memory. */
|
||||||
|
~cPath();
|
||||||
|
|
||||||
|
/** Performs part of the path calculation and returns true if the path computation has finished. */
|
||||||
|
ePathFinderStatus Step();
|
||||||
|
|
||||||
|
/* Point retrieval functions, inlined for performance. */
|
||||||
|
/** Returns the next point in the path. */
|
||||||
|
inline Vector3d GetNextPoint()
|
||||||
|
{
|
||||||
|
ASSERT(m_Status == ePathFinderStatus::PATH_FOUND);
|
||||||
|
return m_PathPoints[m_PointCount - 1 - (++m_CurrentPoint)];
|
||||||
|
}
|
||||||
|
/** Checks whether this is the last point or not. Never call getnextPoint when this is true. */
|
||||||
|
inline bool IsLastPoint()
|
||||||
|
{
|
||||||
|
ASSERT(m_Status == ePathFinderStatus::PATH_FOUND);
|
||||||
|
ASSERT(m_CurrentPoint != -1); // You must call getFirstPoint at least once before calling this.
|
||||||
|
return (m_CurrentPoint == m_PointCount - 1);
|
||||||
|
}
|
||||||
|
/** Get the point at a_index. Remark: Internally, the indexes are reversed. */
|
||||||
|
inline Vector3d GetPoint(int a_index)
|
||||||
|
{
|
||||||
|
ASSERT(m_Status == ePathFinderStatus::PATH_FOUND);
|
||||||
|
ASSERT(a_index < m_PointCount);
|
||||||
|
return m_PathPoints[m_PointCount - 1 - a_index];
|
||||||
|
}
|
||||||
|
/** Returns the total number of points this path has. */
|
||||||
|
inline int GetPointCount()
|
||||||
|
{
|
||||||
|
ASSERT(m_Status == ePathFinderStatus::PATH_FOUND);
|
||||||
|
return m_PointCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VectorHasher
|
||||||
|
{
|
||||||
|
std::size_t operator()(const Vector3d & a_Vector) const
|
||||||
|
{
|
||||||
|
// Guaranteed to have no hash collisions for any 128x128x128 area. Suitable for pathfinding.
|
||||||
|
int32_t t = 0;
|
||||||
|
t += (int8_t)a_Vector.x;
|
||||||
|
t = t << 8;
|
||||||
|
t += (int8_t)a_Vector.y;
|
||||||
|
t = t << 8;
|
||||||
|
t += (int8_t)a_Vector.z;
|
||||||
|
t = t << 8;
|
||||||
|
return (size_t)t;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
|
||||||
|
/* General */
|
||||||
|
bool IsSolid(const Vector3d & a_Location); // Query our hosting world and ask it if there's a solid at a_location.
|
||||||
|
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.
|
||||||
|
|
||||||
|
/* Openlist and closedlist management */
|
||||||
|
void OpenListAdd(cPathCell * a_Cell);
|
||||||
|
cPathCell * OpenListPop();
|
||||||
|
void ProcessIfWalkable(const Vector3d &a_Location, cPathCell * a_Parent, int a_Cost);
|
||||||
|
|
||||||
|
/* Map management */
|
||||||
|
void ProcessCell(cPathCell * a_Cell, cPathCell * a_Caller, int a_GDelta);
|
||||||
|
cPathCell * GetCell(const Vector3d & a_location);
|
||||||
|
|
||||||
|
/* Pathfinding fields */
|
||||||
|
std::priority_queue<cPathCell *, std::vector<cPathCell *>, compareHeuristics> m_OpenList;
|
||||||
|
std::unordered_map<Vector3d, cPathCell *, VectorHasher> m_Map;
|
||||||
|
Vector3d m_Destination;
|
||||||
|
Vector3d m_Source;
|
||||||
|
int m_StepsLeft;
|
||||||
|
|
||||||
|
/* Control fields */
|
||||||
|
ePathFinderStatus m_Status;
|
||||||
|
|
||||||
|
/* Final path fields */
|
||||||
|
int m_PointCount;
|
||||||
|
int m_CurrentPoint;
|
||||||
|
std::vector<Vector3d> m_PathPoints;
|
||||||
|
void AddPoint(Vector3d a_Vector);
|
||||||
|
|
||||||
|
/* Interfacing with MCServer's world */
|
||||||
|
cWorld * m_World;
|
||||||
|
#ifndef COMPILING_PATHFIND_DEBUGGER
|
||||||
|
Vector3d m_Item_CurrentBlock; // Read by Item();, it's the only way to "pass it" parameters
|
||||||
|
protected:
|
||||||
|
virtual bool Item(cChunk * a_Chunk) override;
|
||||||
|
|
||||||
|
/* Interfacing with Irrlicht, has nothing to do with MCServer*/
|
||||||
|
#else
|
||||||
|
#include "../path_irrlicht.cpp"
|
||||||
|
#endif
|
||||||
|
};
|
@ -90,7 +90,6 @@ void cPig::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
|||||||
if (m_Attachee->IsPlayer() && (m_Attachee->GetEquippedWeapon().m_ItemType == E_ITEM_CARROT_ON_STICK))
|
if (m_Attachee->IsPlayer() && (m_Attachee->GetEquippedWeapon().m_ItemType == E_ITEM_CARROT_ON_STICK))
|
||||||
{
|
{
|
||||||
MoveToPosition((m_Attachee->GetPosition()) + (m_Attachee->GetLookVector()*10));
|
MoveToPosition((m_Attachee->GetPosition()) + (m_Attachee->GetLookVector()*10));
|
||||||
m_bMovingToDestination = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ void cSheep::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
|||||||
|
|
||||||
if (m_TimeToStopEating > 0)
|
if (m_TimeToStopEating > 0)
|
||||||
{
|
{
|
||||||
m_bMovingToDestination = false; // The sheep should not move when he's eating
|
StopMovingToPosition();
|
||||||
m_TimeToStopEating--;
|
m_TimeToStopEating--;
|
||||||
|
|
||||||
if (m_TimeToStopEating == 0)
|
if (m_TimeToStopEating == 0)
|
||||||
|
@ -50,17 +50,18 @@ void cSkeleton::GetDrops(cItems & a_Drops, cEntity * a_Killer)
|
|||||||
|
|
||||||
void cSkeleton::MoveToPosition(const Vector3d & a_Position)
|
void cSkeleton::MoveToPosition(const Vector3d & a_Position)
|
||||||
{
|
{
|
||||||
// If the destination is sufficiently skylight challenged AND the skeleton isn't on fire then block the movement
|
// Todo use WouldBurnAt(), not sure how to obtain a chunk though...
|
||||||
|
super::MoveToPosition(a_Position); // Look at the player and update m_Destination to hit them if they're close
|
||||||
|
|
||||||
|
// If the destination is sufficiently skylight challenged AND the skeleton isn't on fire AND we weren't attacked recently then block the movement
|
||||||
if (
|
if (
|
||||||
!IsOnFire() &&
|
!IsOnFire() &&
|
||||||
(m_World->GetBlockSkyLight((int)floor(a_Position.x), (int)floor(a_Position.y), (int)floor(a_Position.z)) - m_World->GetSkyDarkness() > 8)
|
(m_World->GetBlockSkyLight((int)floor(a_Position.x), (int)floor(a_Position.y), (int)floor(a_Position.z)) - m_World->GetSkyDarkness() > 8) &&
|
||||||
|
m_TicksSinceLastDamaged == 100
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
m_bMovingToDestination = false;
|
StopMovingToPosition();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
super::MoveToPosition(a_Position);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -203,7 +203,7 @@ void cWolf::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
|||||||
}
|
}
|
||||||
else if (IsSitting())
|
else if (IsSitting())
|
||||||
{
|
{
|
||||||
m_bMovingToDestination = false;
|
StopMovingToPosition();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,17 +44,18 @@ void cZombie::GetDrops(cItems & a_Drops, cEntity * a_Killer)
|
|||||||
|
|
||||||
void cZombie::MoveToPosition(const Vector3d & a_Position)
|
void cZombie::MoveToPosition(const Vector3d & a_Position)
|
||||||
{
|
{
|
||||||
// If the destination is sufficiently skylight challenged AND the skeleton isn't on fire then block the movement
|
// Todo use WouldBurnAt(), not sure how to obtain a chunk though...
|
||||||
|
super::MoveToPosition(a_Position); // Look at the player and update m_Destination to hit them if they're close
|
||||||
|
|
||||||
|
// If the destination is sufficiently skylight challenged AND the skeleton isn't on fire AND we weren't attacked recently then block the movement
|
||||||
if (
|
if (
|
||||||
!IsOnFire() &&
|
!IsOnFire() &&
|
||||||
(m_World->GetBlockSkyLight((int)floor(a_Position.x), (int)floor(a_Position.y), (int)floor(a_Position.z)) - m_World->GetSkyDarkness() > 8)
|
(m_World->GetBlockSkyLight((int)floor(a_Position.x), (int)floor(a_Position.y), (int)floor(a_Position.z)) - m_World->GetSkyDarkness() > 8) &&
|
||||||
|
m_TicksSinceLastDamaged == 100
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
m_bMovingToDestination = false;
|
StopMovingToPosition();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
super::MoveToPosition(a_Position);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -354,6 +354,7 @@ protected:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
template <> inline Vector3<int> Vector3<int>::Floor(void) const
|
template <> inline Vector3<int> Vector3<int>::Floor(void) const
|
||||||
{
|
{
|
||||||
return *this;
|
return *this;
|
||||||
|
Loading…
Reference in New Issue
Block a user