A* Pathfinding and better monster AI

This commit is contained in:
wiseoldman95
2015-05-01 13:18:47 +03:00
parent 259132d17e
commit 1b0e21e0b2
13 changed files with 737 additions and 176 deletions
+143 -127
View File
@@ -13,7 +13,7 @@
#include "../Chunk.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_EMPersonality(AGGRESSIVE)
, m_Target(nullptr)
, m_Path(nullptr)
, m_PathStatus(ePathFinderStatus::PATH_NOT_FOUND)
, m_IsFollowingPath(false)
, m_GiveUpCounter(0)
, m_bMovingToDestination(false)
, m_LastGroundHeight(POSY_TOINT)
, m_IdleInterval(0)
@@ -94,8 +98,9 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A
, m_DropChanceLeggings(0.085f)
, m_DropChanceBoots(0.085f)
, m_CanPickUpLoot(true)
, m_TicksSinceLastDamaged(100)
, m_BurnsInDaylight(false)
, m_RelativeWalkSpeed(1.0)
, m_RelativeWalkSpeed(1)
{
if (!a_ConfigName.empty())
{
@@ -118,87 +123,52 @@ void cMonster::SpawnOn(cClientHandle & a_Client)
void cMonster::TickPathFinding()
{
const int PosX = POSX_TOINT;
const int PosY = POSY_TOINT;
const int PosZ = POSZ_TOINT;
std::vector<Vector3d> m_PotentialCoordinates;
m_TraversedCoordinates.push_back(Vector3i(PosX, PosY, PosZ));
if (m_Path == nullptr)
{
Vector3d position = GetPosition();
Vector3d Dest = m_FinalDestination;
static const struct // Define which directions to try to move to
{
int x, z;
} 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 */)
{
// Too low/high, can't really do anything
FinishPathFinding();
return;
// Can someone explain why are these two NOT THE SAME???
// m_Path = new cPath(GetWorld(), GetPosition(), m_FinalDestination, 30);
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);
m_IsFollowingPath = false;
}
for (size_t i = 0; i < ARRAYCOUNT(gCrossCoords); i++)
m_PathStatus = m_Path->Step();
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 (
(!cBlockInfo::IsSolid(BlockAtY)) &&
(!cBlockInfo::IsSolid(BlockAtYP)) &&
(!IsBlockLava(BlockAtLowestY)) &&
(BlockAtLowestY != E_BLOCK_CACTUS) &&
(PosY - LowestY < FALL_DAMAGE_HEIGHT)
)
case ePathFinderStatus::CALCULATING:
{
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())
{
Vector3f ShortestCoords = m_PotentialCoordinates.front();
for (std::vector<Vector3d>::const_iterator itr = m_PotentialCoordinates.begin(); itr != m_PotentialCoordinates.end(); ++itr)
case ePathFinderStatus::PATH_FOUND:
{
Vector3f Distance = m_FinalDestination - ShortestCoords;
Vector3f Distance2 = m_FinalDestination - *itr;
if (Distance.SqrLength() > Distance2.SqrLength())
if (ReachedDestination() || !m_IsFollowingPath)
{
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)
{
FinishPathFinding();
m_FinalDestination = a_Position;
m_bMovingToDestination = true;
TickPathFinding();
}
void cMonster::StopMovingToPosition()
{
m_bMovingToDestination = false;
FinishPathFinding();
}
bool cMonster::IsCoordinateInTraversedList(Vector3i a_Coords)
{
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()
{
if ((m_Destination - GetPosition()).Length() < 0.5f)
@@ -239,13 +236,14 @@ bool cMonster::ReachedDestination()
bool cMonster::ReachedFinalDestination()
{
if ((GetPosition() - m_FinalDestination).Length() <= m_AttackRange)
{
return true;
}
return false;
}
@@ -268,6 +266,10 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
return;
}
if (m_TicksSinceLastDamaged < 100)
{
++m_TicksSinceLastDamaged;
}
if ((m_Target != nullptr) && m_Target->IsDestroyed())
{
m_Target = nullptr;
@@ -276,6 +278,8 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
// Burning in daylight
HandleDaylightBurning(a_Chunk);
if (m_bMovingToDestination)
{
if (m_bOnGround)
@@ -289,53 +293,51 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
}
}
TickPathFinding();
Vector3d Distance = m_Destination - GetPosition();
if (!ReachedDestination() && !ReachedFinalDestination()) // If we haven't reached any sort of destination, move
{
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;
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
if (--m_GiveUpCounter == 0)
{
FinishPathFinding();
}
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();
@@ -345,13 +347,13 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
case IDLE:
{
// If enemy passive we ignore checks for player visibility
// If enemy passive we ignore checks for player visibility.
InStateIdle(a_Dt);
break;
}
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);
break;
}
@@ -360,12 +362,13 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
InStateEscaping(a_Dt);
break;
}
case ATTACKING: break;
} // switch (m_EMState)
BroadcastMovementUpdate();
}
}
@@ -409,6 +412,7 @@ void cMonster::SetPitchAndYawFromDestination()
void cMonster::HandleFalling()
{
if (m_bOnGround)
@@ -460,7 +464,6 @@ int cMonster::FindFirstNonAirBlockPosition(double a_PosX, double a_PosZ)
bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI)
{
if (!super::DoTakeDamage(a_TDI))
@@ -476,6 +479,7 @@ bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI)
if (a_TDI.Attacker != nullptr)
{
m_Target = a_TDI.Attacker;
m_TicksSinceLastDamaged = 0;
}
return true;
}
@@ -692,7 +696,7 @@ void cMonster::InStateChasing(std::chrono::milliseconds a_Dt)
void cMonster::InStateEscaping(std::chrono::milliseconds a_Dt)
{
UNUSED(a_Dt);
if (m_Target != nullptr)
{
Vector3d newloc = GetPosition();
@@ -771,7 +775,7 @@ AString cMonster::MobTypeToString(eMonsterType a_MobType)
return g_MobTypeNames[i].m_lcName;
}
}
// Not found:
return "";
}
@@ -866,7 +870,7 @@ cMonster::eFamily cMonster::FamilyFromType(eMonsterType a_Type)
case mtWolf: return mfHostile;
case mtZombie: return mfHostile;
case mtZombiePigman: return mfHostile;
case mtInvalidType: break;
}
ASSERT(!"Unhandled mob type");
@@ -1041,7 +1045,7 @@ void cMonster::AddRandomArmorDropItem(cItems & a_Drops, short a_LootingLevel)
a_Drops.push_back(GetEquippedHelmet());
}
}
if (r1.randInt() % 200 < ((m_DropChanceChestplate * 200) + (a_LootingLevel * 2)))
{
if (!GetEquippedChestplate().IsEmpty())
@@ -1049,7 +1053,7 @@ void cMonster::AddRandomArmorDropItem(cItems & a_Drops, short a_LootingLevel)
a_Drops.push_back(GetEquippedChestplate());
}
}
if (r1.randInt() % 200 < ((m_DropChanceLeggings * 200) + (a_LootingLevel * 2)))
{
if (!GetEquippedLeggings().IsEmpty())
@@ -1057,7 +1061,7 @@ void cMonster::AddRandomArmorDropItem(cItems & a_Drops, short a_LootingLevel)
a_Drops.push_back(GetEquippedLeggings());
}
}
if (r1.randInt() % 200 < ((m_DropChanceBoots * 200) + (a_LootingLevel * 2)))
{
if (!GetEquippedBoots().IsEmpty())
@@ -1093,23 +1097,34 @@ void cMonster::HandleDaylightBurning(cChunk & a_Chunk)
{
return;
}
int RelY = POSY_TOINT;
if ((RelY < 0) || (RelY >= cChunkDef::Height))
{
// Outside the world
return;
}
int RelX = POSX_TOINT - GetChunkX() * cChunkDef::Width;
int RelZ = POSZ_TOINT - GetChunkZ() * cChunkDef::Width;
if (!a_Chunk.IsLightValid())
{
m_World->QueueLightChunk(GetChunkX(), GetChunkZ());
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 (
(a_Chunk.GetSkyLight(RelX, RelY, RelZ) == 15) && // In the daylight
(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
)
{
// Burn for 100 ticks, then decide again
StartBurning(100);
return true;
}
return false;
}
cMonster::eFamily cMonster::GetMobFamily(void) const
{
return FamilyFromType(m_MobType);