summaryrefslogtreecommitdiffstats
path: root/src/Mobs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Mobs')
-rw-r--r--src/Mobs/AggressiveMonster.cpp14
-rw-r--r--src/Mobs/Bat.cpp2
-rw-r--r--src/Mobs/Blaze.cpp2
-rw-r--r--src/Mobs/CMakeLists.txt2
-rw-r--r--src/Mobs/Horse.h2
-rw-r--r--src/Mobs/IronGolem.cpp2
-rw-r--r--src/Mobs/MagmaCube.cpp14
-rw-r--r--src/Mobs/MagmaCube.h6
-rw-r--r--src/Mobs/Monster.cpp513
-rw-r--r--src/Mobs/Monster.h100
-rw-r--r--src/Mobs/Path.cpp491
-rw-r--r--src/Mobs/Path.h186
-rw-r--r--src/Mobs/Pig.cpp1
-rw-r--r--src/Mobs/Sheep.cpp2
-rw-r--r--src/Mobs/Skeleton.cpp21
-rw-r--r--src/Mobs/Skeleton.h5
-rw-r--r--src/Mobs/Slime.cpp2
-rw-r--r--src/Mobs/Slime.h2
-rw-r--r--src/Mobs/Villager.cpp2
-rw-r--r--src/Mobs/Wolf.cpp9
-rw-r--r--src/Mobs/Wolf.h4
-rw-r--r--src/Mobs/Zombie.cpp23
-rw-r--r--src/Mobs/Zombie.h8
23 files changed, 1111 insertions, 302 deletions
diff --git a/src/Mobs/AggressiveMonster.cpp b/src/Mobs/AggressiveMonster.cpp
index 526b39e39..648599999 100644
--- a/src/Mobs/AggressiveMonster.cpp
+++ b/src/Mobs/AggressiveMonster.cpp
@@ -36,11 +36,7 @@ void cAggressiveMonster::InStateChasing(std::chrono::milliseconds a_Dt)
return;
}
}
-
- if (!IsMovingToTargetPosition())
- {
- MoveToPosition(m_Target->GetPosition());
- }
+ MoveToPosition(m_Target->GetPosition());
}
}
@@ -80,9 +76,11 @@ void cAggressiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
}
cTracer LineOfSight(GetWorld());
- Vector3d AttackDirection(m_Target->GetPosition() - GetPosition());
+ Vector3d MyHeadPosition = GetPosition() + Vector3d(0, GetHeight(), 0);
+ Vector3d AttackDirection(m_Target->GetPosition() + Vector3d(0, m_Target->GetHeight(), 0) - MyHeadPosition);
- if (ReachedFinalDestination() && !LineOfSight.Trace(GetPosition(), AttackDirection, (int)AttackDirection.Length()))
+
+ if (ReachedFinalDestination() && !LineOfSight.Trace(MyHeadPosition, AttackDirection, static_cast<int>(AttackDirection.Length())))
{
// Attack if reached destination, target isn't null, and have a clear line of sight to target (so won't attack through walls)
Attack(a_Dt);
@@ -100,7 +98,7 @@ void cAggressiveMonster::Attack(std::chrono::milliseconds a_Dt)
{
return;
}
-
+
// Setting this higher gives us more wiggle room for attackrate
m_AttackInterval = 0.0;
m_Target->TakeDamage(dtMobAttack, this, m_AttackDamage, 0);
diff --git a/src/Mobs/Bat.cpp b/src/Mobs/Bat.cpp
index c072d4f48..e187e928a 100644
--- a/src/Mobs/Bat.cpp
+++ b/src/Mobs/Bat.cpp
@@ -9,6 +9,8 @@
cBat::cBat(void) :
super("Bat", mtBat, "mob.bat.hurt", "mob.bat.death", 0.5, 0.9)
{
+ SetGravity(-2.0f);
+ SetAirDrag(0.05f);
}
diff --git a/src/Mobs/Blaze.cpp b/src/Mobs/Blaze.cpp
index 89eeb3709..d4ad24166 100644
--- a/src/Mobs/Blaze.cpp
+++ b/src/Mobs/Blaze.cpp
@@ -11,6 +11,8 @@
cBlaze::cBlaze(void) :
super("Blaze", mtBlaze, "mob.blaze.hit", "mob.blaze.death", 0.6, 1.8)
{
+ SetGravity(-8.0f);
+ SetAirDrag(0.05f);
}
diff --git a/src/Mobs/CMakeLists.txt b/src/Mobs/CMakeLists.txt
index 7a291dcf2..ffbcdf3ea 100644
--- a/src/Mobs/CMakeLists.txt
+++ b/src/Mobs/CMakeLists.txt
@@ -24,6 +24,7 @@ SET (SRCS
Mooshroom.cpp
PassiveAggressiveMonster.cpp
PassiveMonster.cpp
+ Path.cpp
Pig.cpp
Rabbit.cpp
Sheep.cpp
@@ -62,6 +63,7 @@ SET (HDRS
Ocelot.h
PassiveAggressiveMonster.h
PassiveMonster.h
+ Path.h
Pig.h
Rabbit.h
Sheep.h
diff --git a/src/Mobs/Horse.h b/src/Mobs/Horse.h
index be283705e..27168ebae 100644
--- a/src/Mobs/Horse.h
+++ b/src/Mobs/Horse.h
@@ -26,7 +26,7 @@ public:
bool IsEating (void) const {return m_bIsEating; }
bool IsRearing (void) const {return m_bIsRearing; }
bool IsMthOpen (void) const {return m_bIsMouthOpen; }
- bool IsTame (void) const {return m_bIsTame; }
+ bool IsTame (void) const override {return m_bIsTame; }
int GetHorseType (void) const {return m_Type; }
int GetHorseColor (void) const {return m_Color; }
int GetHorseStyle (void) const {return m_Style; }
diff --git a/src/Mobs/IronGolem.cpp b/src/Mobs/IronGolem.cpp
index dae4615e4..b0e76daca 100644
--- a/src/Mobs/IronGolem.cpp
+++ b/src/Mobs/IronGolem.cpp
@@ -8,7 +8,7 @@
cIronGolem::cIronGolem(void) :
- super("IronGolem", mtIronGolem, "mob.IronGolem.hit", "mob.IronGolem.death", 1.4, 2.9)
+ super("IronGolem", mtIronGolem, "mob.irongolem.hit", "mob.irongolem.death", 1.4, 2.9)
{
}
diff --git a/src/Mobs/MagmaCube.cpp b/src/Mobs/MagmaCube.cpp
index 3e9abc108..c5dd0def0 100644
--- a/src/Mobs/MagmaCube.cpp
+++ b/src/Mobs/MagmaCube.cpp
@@ -7,7 +7,7 @@
cMagmaCube::cMagmaCube(int a_Size) :
- super("MagmaCube", mtMagmaCube, "mob.MagmaCube.big", "mob.MagmaCube.big", 0.6 * a_Size, 0.6 * a_Size),
+ super("MagmaCube", mtMagmaCube, Printf("mob.magmacube.%s", GetSizeName(a_Size).c_str()), Printf("mob.magmacube.%s", GetSizeName(a_Size).c_str()), 0.6 * a_Size, 0.6 * a_Size),
m_Size(a_Size)
{
}
@@ -27,4 +27,14 @@ void cMagmaCube::GetDrops(cItems & a_Drops, cEntity * a_Killer)
-
+AString cMagmaCube::GetSizeName(int a_Size)
+{
+ if (a_Size > 1)
+ {
+ return "big";
+ }
+ else
+ {
+ return "small";
+ }
+}
diff --git a/src/Mobs/MagmaCube.h b/src/Mobs/MagmaCube.h
index d66ea423a..b914dc867 100644
--- a/src/Mobs/MagmaCube.h
+++ b/src/Mobs/MagmaCube.h
@@ -19,10 +19,14 @@ public:
virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override;
int GetSize(void) const { return m_Size; }
+
+ /** Returns the text describing the slime's size, as used by the client's resource subsystem for sounds.
+ Returns either "big" or "small". */
+ static AString GetSizeName(int a_Size);
protected:
- /// Size of the MagmaCube, 1 .. 3, with 1 being the smallest
+ /// Size of the MagmaCube, 1, 2 and 4, with 1 being the smallest
int m_Size;
} ;
diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp
index a86497753..1da4124ed 100644
--- a/src/Mobs/Monster.cpp
+++ b/src/Mobs/Monster.cpp
@@ -13,7 +13,7 @@
#include "../Chunk.h"
#include "../FastRandom.h"
-
+#include "Path.h"
@@ -38,6 +38,7 @@ static const struct
{mtEnderman, "enderman", "Enderman"},
{mtEnderDragon, "enderdragon", "EnderDragon"},
{mtGhast, "ghast", "Ghast"},
+ {mtGiant, "giant", "Giant"},
{mtGuardian, "guardian", "Guardian"},
{mtHorse, "horse", "EntityHorse"},
{mtIronGolem, "irongolem", "VillagerGolem"},
@@ -73,8 +74,12 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A
, m_EMState(IDLE)
, m_EMPersonality(AGGRESSIVE)
, m_Target(nullptr)
- , m_bMovingToDestination(false)
+ , m_Path(nullptr)
+ , m_IsFollowingPath(false)
+ , m_GiveUpCounter(0)
+ , m_TicksSinceLastPathReset(1000)
, m_LastGroundHeight(POSY_TOINT)
+ , m_JumpCoolDown(0)
, m_IdleInterval(0)
, m_DestroyTimer(0)
, m_MobType(a_MobType)
@@ -84,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)
@@ -93,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())
{
@@ -115,89 +121,159 @@ void cMonster::SpawnOn(cClientHandle & a_Client)
-void cMonster::TickPathFinding()
+bool cMonster::TickPathFinding(cChunk & a_Chunk)
{
- 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));
-
- static const struct // Define which directions to try to move to
+ if (!m_IsFollowingPath)
{
- int x, z;
- } gCrossCoords[] =
+ return false;
+ }
+ if (m_TicksSinceLastPathReset < 1000)
{
- { 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 */)
+ // No need to count beyond 1000. 1000 is arbitary here.
+ ++m_TicksSinceLastPathReset;
+ }
+
+ if (ReachedFinalDestination())
{
- // Too low/high, can't really do anything
- FinishPathFinding();
- return;
+ StopMovingToPosition();
+ return false;
}
- for (size_t i = 0; i < ARRAYCOUNT(gCrossCoords); i++)
+ if ((m_FinalDestination - m_PathFinderDestination).Length() > 0.25) // if the distance between where we're going and where we should go is too big.
{
- if (IsCoordinateInTraversedList(Vector3i(gCrossCoords[i].x + PosX, PosY, gCrossCoords[i].z + PosZ)))
+ /* If we reached the last path waypoint,
+ Or if we haven't re-calculated for too long.
+ Interval is proportional to distance squared, and its minimum is 10.
+ (Recalculate lots when close, calculate rarely when far) */
+ if (
+ ((GetPosition() - m_PathFinderDestination).Length() < 0.25) ||
+ ((m_TicksSinceLastPathReset > 10) && (m_TicksSinceLastPathReset > (0.4 * (m_FinalDestination - GetPosition()).SqrLength())))
+ )
{
- continue;
+ /* 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();
+ }
}
+ }
- 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 (m_Path == nullptr)
+ {
+ if (!EnsureProperDestination(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(), m_PathFinderDestination, 20, GetWidth(), GetHeight());
+ }
- if (
- (!cBlockInfo::IsSolid(BlockAtY)) &&
- (!cBlockInfo::IsSolid(BlockAtYP)) &&
- (!IsBlockLava(BlockAtLowestY)) &&
- (BlockAtLowestY != E_BLOCK_CACTUS) &&
- (PosY - LowestY < FALL_DAMAGE_HEIGHT)
- )
+ switch (m_Path->Step(a_Chunk))
+ {
+ case ePathFinderStatus::NEARBY_FOUND:
{
- m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY, gCrossCoords[i].z + PosZ));
+ m_NoPathToTarget = true;
+ m_PathFinderDestination = m_Path->AcceptNearbyPath();
+ 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)
- )
+
+ case ePathFinderStatus::PATH_NOT_FOUND:
+ {
+ StopMovingToPosition(); // 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:
{
- m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY + 1, gCrossCoords[i].z + PosZ));
+ // Pathfinder needs more time
+ break;
+ }
+ case ePathFinderStatus::PATH_FOUND:
+ {
+ if (m_NoMoreWayPoints || (--m_GiveUpCounter == 0))
+ {
+ ResetPathFinding(); // Try to calculate a path again.
+ return false;
+ }
+ else if (!m_Path->IsLastPoint()) // Have we arrived at the next cell, as denoted by m_NextWayPointPosition?
+ {
+ if ((m_Path->IsFirstPoint() || ReachedNextWaypoint()))
+ {
+ m_NextWayPointPosition = 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;
}
}
- if (!m_PotentialCoordinates.empty())
+ return false;
+}
+
+
+
+
+
+void cMonster::MoveToWayPoint(cChunk & a_Chunk)
+{
+ if (m_JumpCoolDown == 0)
{
- Vector3f ShortestCoords = m_PotentialCoordinates.front();
- for (std::vector<Vector3d>::const_iterator itr = m_PotentialCoordinates.begin(); itr != m_PotentialCoordinates.end(); ++itr)
+ if (DoesPosYRequireJump(FloorC(m_NextWayPointPosition.y)))
{
- Vector3f Distance = m_FinalDestination - ShortestCoords;
- Vector3f Distance2 = m_FinalDestination - *itr;
- if (Distance.SqrLength() > Distance2.SqrLength())
+ if (
+ (IsOnGround() && (GetSpeedX() == 0) && (GetSpeedY() == 0)) ||
+ (IsSwimming() && (m_GiveUpCounter < 15))
+ )
{
- ShortestCoords = *itr;
+ m_bOnGround = false;
+ m_JumpCoolDown = 20;
+ // TODO: Change to AddSpeedY once collision detection is fixed - currently, mobs will go into blocks attempting to jump without a teleport
+ AddPosY(1.6); // Jump!!
+ SetSpeedX(3.2 * (m_NextWayPointPosition.x - GetPosition().x)); // Move forward in a preset speed.
+ SetSpeedZ(3.2 * (m_NextWayPointPosition.z - GetPosition().z)); // The numbers were picked based on trial and error and 1.6 and 3.2 are perfect.
}
}
-
- m_Destination = ShortestCoords;
- m_Destination.z += 0.5f;
- m_Destination.x += 0.5f;
}
else
{
- FinishPathFinding();
+ --m_JumpCoolDown;
+ }
+
+ Vector3d Distance = m_NextWayPointPosition - GetPosition();
+ if ((Distance.x != 0) || (Distance.z != 0))
+ {
+ 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_NextWayPointPositions, hence
+ better pathfinding. */
+ Distance *= 0.5;
+ AddSpeedX(Distance.x);
+ AddSpeedZ(Distance.z);
}
}
@@ -205,47 +281,130 @@ void cMonster::TickPathFinding()
-void cMonster::MoveToPosition(const Vector3d & a_Position)
+bool cMonster::EnsureProperDestination(cChunk & a_Chunk)
{
- FinishPathFinding();
+ cChunk * Chunk = a_Chunk.GetNeighborChunk(FloorC(m_FinalDestination.x), FloorC(m_FinalDestination.z));
+ BLOCKTYPE BlockType;
+ NIBBLETYPE BlockMeta;
+
+ if ((Chunk == nullptr) || !Chunk->IsValid())
+ {
+ 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, 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))
+ {
+ bool InTheAir = true;
+ int x, z;
+ for (z = -1; z <= 1; ++z)
+ {
+ 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;
+ }
+ }
+ }
+ 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.
+ bool InWater = false;
+ while (m_FinalDestination.y < cChunkDef::Height)
+ {
+ Chunk->GetBlockTypeMeta(RelX, FloorC(m_FinalDestination.y), RelZ, BlockType, BlockMeta);
+ if (BlockType == E_BLOCK_STATIONARY_WATER)
+ {
+ InWater = true;
+ }
+ else if (cBlockInfo::IsSolid(BlockType))
+ {
+ InWater = false;
+ }
+ else
+ {
+ break;
+ }
+ m_FinalDestination.y += 1;
+ }
+ if (InWater)
+ {
+ m_FinalDestination.y -= 1;
+ }
- m_FinalDestination = a_Position;
- m_bMovingToDestination = true;
- TickPathFinding();
+
+ return true;
}
-bool cMonster::IsCoordinateInTraversedList(Vector3i a_Coords)
+
+
+void cMonster::MoveToPosition(const Vector3d & a_Position)
{
- return (std::find(m_TraversedCoordinates.begin(), m_TraversedCoordinates.end(), a_Coords) != m_TraversedCoordinates.end());
+ m_FinalDestination = a_Position;
+ m_IsFollowingPath = true;
}
-bool cMonster::ReachedDestination()
+void cMonster::StopMovingToPosition()
{
- if ((m_Destination - GetPosition()).Length() < 0.5f)
- {
- return true;
- }
-
- return false;
+ m_IsFollowingPath = false;
}
-bool cMonster::ReachedFinalDestination()
+
+void cMonster::ResetPathFinding(void)
{
- if ((GetPosition() - m_FinalDestination).Length() <= m_AttackRange)
+ m_TicksSinceLastPathReset = 0;
+ if (m_Path != nullptr)
{
- return true;
+ delete m_Path;
+ m_Path = nullptr;
}
-
- return false;
}
@@ -255,10 +414,11 @@ bool cMonster::ReachedFinalDestination()
void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
super::Tick(a_Dt, a_Chunk);
+ GET_AND_VERIFY_CURRENT_CHUNK(Chunk, POSX_TOINT, POSZ_TOINT);
if (m_Health <= 0)
{
- // The mob is dead, but we're still animating the "puff" they leave when they die
+ // The mob is dead, but we're still animating the "puff" they leave when they die.
m_DestroyTimer += a_Dt;
if (m_DestroyTimer > std::chrono::seconds(1))
{
@@ -267,73 +427,36 @@ 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;
}
- // Burning in daylight
- HandleDaylightBurning(a_Chunk);
-
- if (m_bMovingToDestination)
+ // Process the undead burning in daylight.
+ HandleDaylightBurning(*Chunk, WouldBurnAt(GetPosition(), *Chunk));
+ if (TickPathFinding(*Chunk))
{
- if (m_bOnGround)
- {
- if (DoesPosYRequireJump((int)floor(m_Destination.y)))
- {
- m_bOnGround = false;
-
- // TODO: Change to AddSpeedY once collision detection is fixed - currently, mobs will go into blocks attempting to jump without a teleport
- AddPosY(1.2); // Jump!!
- }
- }
-
- Vector3d Distance = m_Destination - GetPosition();
- if (!ReachedDestination() && !ReachedFinalDestination()) // If we haven't reached any sort of destination, move
+ /* If I burn in daylight, and I won't burn where I'm standing, and I'll burn in my next position, and at least one of those is true:
+ 1. I am idle
+ 2. I was not hurt by a player recently.
+ Then STOP. */
+ if (
+ m_BurnsInDaylight && ((m_TicksSinceLastDamaged >= 100) || (m_EMState == IDLE)) &&
+ WouldBurnAt(m_NextWayPointPosition, *Chunk) &&
+ !WouldBurnAt(GetPosition(), *Chunk)
+ )
{
- 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);
- }
- */
+ // If we burn in daylight, and we would burn at the next step, and we won't burn where we are right now, and we weren't provoked recently:
+ StopMovingToPosition();
+ m_GiveUpCounter = 40; // This doesn't count as giving up, keep the giveup timer as is.
}
else
{
- if (ReachedFinalDestination()) // If we have reached the ultimate, final destination, stop pathfinding and attack if appropriate
- {
- FinishPathFinding();
- }
- else
- {
- TickPathFinding(); // We have reached the next point in our path, calculate another point
- }
+ MoveToWayPoint(*Chunk);
}
}
@@ -344,13 +467,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;
}
@@ -359,7 +482,6 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
InStateEscaping(a_Dt);
break;
}
-
case ATTACKING: break;
} // switch (m_EMState)
@@ -369,6 +491,7 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
+
void cMonster::SetPitchAndYawFromDestination()
{
Vector3d FinalDestination = m_FinalDestination;
@@ -376,31 +499,36 @@ void cMonster::SetPitchAndYawFromDestination()
{
if (m_Target->IsPlayer())
{
- FinalDestination.y = ((cPlayer *)m_Target)->GetStance();
+ FinalDestination.y = static_cast<cPlayer *>(m_Target)->GetStance() - 1;
}
else
{
- FinalDestination.y = GetHeight();
+ FinalDestination.y = m_Target->GetPosY() + GetHeight();
}
}
+
+
+ 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();
- if (Distance.SqrLength() > 0.1f)
{
+ double HeadRotation, HeadPitch;
+ Distance.Normalize();
+ VectorToEuler(Distance.x, Distance.y, Distance.z, HeadRotation, HeadPitch);
+ if (std::abs(BodyRotation - HeadRotation) < 120)
{
- double Rotation, Pitch;
- Distance.Normalize();
- VectorToEuler(Distance.x, Distance.y, Distance.z, Rotation, Pitch);
- SetHeadYaw(Rotation);
- SetPitch(-Pitch);
+ 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.
{
- Vector3d BodyDistance = m_Destination - GetPosition();
- double Rotation, Pitch;
- Distance.Normalize();
- VectorToEuler(BodyDistance.x, BodyDistance.y, BodyDistance.z, Rotation, Pitch);
- SetYaw(Rotation);
+ SetHeadYaw(BodyRotation);
+ SetPitch(-BodyPitch);
}
}
}
@@ -408,6 +536,7 @@ void cMonster::SetPitchAndYawFromDestination()
+
void cMonster::HandleFalling()
{
if (m_bOnGround)
@@ -459,7 +588,6 @@ int cMonster::FindFirstNonAirBlockPosition(double a_PosX, double a_PosZ)
-
bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI)
{
if (!super::DoTakeDamage(a_TDI))
@@ -475,6 +603,7 @@ bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI)
if (a_TDI.Attacker != nullptr)
{
m_Target = a_TDI.Attacker;
+ m_TicksSinceLastDamaged = 0;
}
return true;
}
@@ -640,7 +769,7 @@ void cMonster::EventLosePlayer(void)
void cMonster::InStateIdle(std::chrono::milliseconds a_Dt)
{
- if (m_bMovingToDestination)
+ if (m_IsFollowingPath)
{
return; // Still getting there
}
@@ -660,14 +789,8 @@ void cMonster::InStateIdle(std::chrono::milliseconds a_Dt)
if ((Dist.SqrLength() > 2) && (rem >= 3))
{
Vector3d Destination(GetPosX() + Dist.x, 0, GetPosZ() + Dist.z);
-
- int NextHeight = FindFirstNonAirBlockPosition(Destination.x, Destination.z);
-
- if (IsNextYPosReachable(NextHeight))
- {
- Destination.y = NextHeight;
- MoveToPosition(Destination);
- }
+ Destination.y = FindFirstNonAirBlockPosition(Destination.x, Destination.z);
+ MoveToPosition(Destination);
}
}
}
@@ -691,7 +814,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();
@@ -770,7 +893,7 @@ AString cMonster::MobTypeToString(eMonsterType a_MobType)
return g_MobTypeNames[i].m_lcName;
}
}
-
+
// Not found:
return "";
}
@@ -865,7 +988,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");
@@ -905,7 +1028,7 @@ cMonster * cMonster::NewMonsterFromType(eMonsterType a_MobType)
{
case mtMagmaCube:
{
- toReturn = new cMagmaCube(Random.NextInt(2) + 1);
+ toReturn = new cMagmaCube(1 << Random.NextInt(3)); // Size 1, 2 or 4
break;
}
case mtSlime:
@@ -1040,7 +1163,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())
@@ -1048,7 +1171,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())
@@ -1056,7 +1179,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())
@@ -1086,36 +1209,26 @@ void cMonster::AddRandomWeaponDropItem(cItems & a_Drops, short a_LootingLevel)
-void cMonster::HandleDaylightBurning(cChunk & a_Chunk)
+void cMonster::HandleDaylightBurning(cChunk & a_Chunk, bool WouldBurn)
{
if (!m_BurnsInDaylight)
{
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 (
- (a_Chunk.GetSkyLight(RelX, RelY, RelZ) == 15) && // In the daylight
- (a_Chunk.GetBlock(RelX, RelY, RelZ) != E_BLOCK_SOULSAND) && // Not on soulsand
- (GetWorld()->GetTimeOfDay() < (12000 + 1000)) && // It is nighttime
- !IsOnFire() && // Not already burning
- GetWorld()->IsWeatherSunnyAt(POSX_TOINT, POSZ_TOINT) // Not raining
- )
+ if (!IsOnFire() && WouldBurn)
{
// Burn for 100 ticks, then decide again
StartBurning(100);
@@ -1125,11 +1238,35 @@ void cMonster::HandleDaylightBurning(cChunk & a_Chunk)
-cMonster::eFamily cMonster::GetMobFamily(void) const
+bool cMonster::WouldBurnAt(Vector3d a_Location, cChunk & a_Chunk)
{
- return FamilyFromType(m_MobType);
+ cChunk * Chunk = a_Chunk.GetNeighborChunk(FloorC(a_Location.x), FloorC(a_Location.z));
+ if ((Chunk == nullptr) || (!Chunk->IsValid()))
+ {
+ return false;
+ }
+
+ int RelX = FloorC(a_Location.x) - Chunk->GetPosX() * cChunkDef::Width;
+ int RelY = FloorC(a_Location.y);
+ int RelZ = FloorC(a_Location.z) - Chunk->GetPosZ() * cChunkDef::Width;
+
+ if (
+ (Chunk->GetSkyLight(RelX, RelY, RelZ) == 15) && // In the daylight
+ (Chunk->GetBlock(RelX, RelY, RelZ) != E_BLOCK_SOULSAND) && // Not on soulsand
+ (GetWorld()->GetTimeOfDay() < (12000 + 1000)) && // It is nighttime
+ GetWorld()->IsWeatherSunnyAt(POSX_TOINT, POSZ_TOINT) // Not raining
+ )
+ {
+ return true;
+ }
+ return false;
}
+
+cMonster::eFamily cMonster::GetMobFamily(void) const
+{
+ return FamilyFromType(m_MobType);
+}
diff --git a/src/Mobs/Monster.h b/src/Mobs/Monster.h
index 21ed0c25a..c4043b0e5 100644
--- a/src/Mobs/Monster.h
+++ b/src/Mobs/Monster.h
@@ -10,11 +10,12 @@
-
-
class cClientHandle;
class cWorld;
+// Fwd: cPath
+enum class ePathFinderStatus;
+class cPath;
@@ -60,8 +61,9 @@ public:
virtual void OnRightClicked(cPlayer & a_Player) override;
+ /** Engage pathfinder and tell it to calculate a path to a given position, and move the mobile accordingly
+ Currently, the mob will only start moving to a new position after the position it is currently going to is reached. */
virtual void MoveToPosition(const Vector3d & a_Position); // tolua_export
- virtual bool ReachedDestination(void);
// tolua_begin
eMonsterType GetMobType(void) const { return m_MobType; }
@@ -158,19 +160,32 @@ public:
protected:
- /* ======= PATHFINDING ======= */
-
/** A pointer to the entity this mobile is aiming to reach */
cEntity * m_Target;
+ cPath * m_Path; // TODO unique ptr
+
+ /** Stores if mobile is currently moving towards the ultimate, final destination */
+ bool m_IsFollowingPath;
+
+ /* If 0, will give up reaching the next m_NextWayPointPosition and will re-compute path. */
+ int m_GiveUpCounter;
+ int m_TicksSinceLastPathReset;
+
/** Coordinates of the next position that should be reached */
- Vector3d m_Destination;
+ Vector3d m_NextWayPointPosition;
+
/** Coordinates for the ultimate, final destination. */
Vector3d m_FinalDestination;
- /** Returns if the ultimate, final destination has been reached */
- bool ReachedFinalDestination(void);
- /** Stores if mobile is currently moving towards the ultimate, final destination */
- bool m_bMovingToDestination;
+ /** 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
@@ -178,44 +193,50 @@ protected:
If no suitable position is found, returns cChunkDef::Height. */
int FindFirstNonAirBlockPosition(double a_PosX, double a_PosZ);
- /** Returns if a monster can actually reach a given height by jumping or walking */
- inline bool IsNextYPosReachable(int a_PosY)
- {
- return (
- (a_PosY <= POSY_TOINT) ||
- DoesPosYRequireJump(a_PosY)
- );
- }
+ /** Returns if the ultimate, final destination has been reached */
+ bool ReachedFinalDestination(void) { return ((m_FinalDestination - GetPosition()).SqrLength() < (m_AttackRange * m_AttackRange)); }
+
+ /** Returns if the intermediate waypoint of m_NextWayPointPosition has been reached */
+ bool ReachedNextWaypoint(void) { return ((m_NextWayPointPosition - GetPosition()).SqrLength() < 0.25); }
+
/** Returns if a monster can reach a given height by jumping */
inline bool DoesPosYRequireJump(int a_PosY)
{
return ((a_PosY > POSY_TOINT) && (a_PosY == POSY_TOINT + 1));
}
- /** A semi-temporary list to store the traversed coordinates during active pathfinding so we don't visit them again */
- std::vector<Vector3i> m_TraversedCoordinates;
- /** Returns if coordinate is in the traversed list */
- bool IsCoordinateInTraversedList(Vector3i a_Coords);
+ /** Finds the next place to go by calculating a path and setting the m_NextWayPointPosition variable for the next block to head to
+ This is based on the ultimate, final destination and the current position, as well as the A* algorithm, and any environmental hazards
+ Returns if a path is ready, and therefore if the mob should move to m_NextWayPointPosition
+ */
+ bool TickPathFinding(cChunk & a_Chunk);
- /** Finds the next place to go
- 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);
- /** Finishes a pathfinding task, be it due to failure or something else */
- inline void FinishPathFinding(void)
- {
- m_TraversedCoordinates.clear();
- m_bMovingToDestination = false;
- }
- /** Sets the body yaw and head yaw/pitch based on next/ultimate destinations */
- void SetPitchAndYawFromDestination(void);
+ /** Move in a straight line to the next waypoint in the path, will jump if needed. */
+ void MoveToWayPoint(cChunk & a_Chunk);
- /* =========================== */
- /* ========= FALLING ========= */
+ /** Ensures the destination is not buried underground or under water. Also ensures the destination is not in the air.
+ Only the Y coordinate of m_FinalDestination might be changed.
+ 1. If m_FinalDestination is the position of a water block, m_FinalDestination's Y will be modified to point to the heighest water block in the pool in the current column.
+ 2. If m_FinalDestination is the position of a solid, m_FinalDestination's Y will be modified to point to the first airblock above the solid in the current column.
+ 3. If m_FinalDestination is the position of an air block, Y will keep decreasing until hitting either a solid or water.
+ Now either 1 or 2 is performed. */
+ bool EnsureProperDestination(cChunk & a_Chunk);
+
+ /** Resets a pathfinding task, be it due to failure or something else
+ Resets the pathfinder. If m_IsFollowingPath is true, TickPathFinding starts a brand new path.
+ Should only be called by the pathfinder, cMonster::Tick or StopMovingToPosition. */
+ void ResetPathFinding(void);
+
+ /** Stops pathfinding
+ Calls ResetPathFinding and sets m_IsFollowingPath to false */
+ void StopMovingToPosition();
+
+ /** Sets the body yaw and head yaw / pitch based on next / ultimate destinations */
+ void SetPitchAndYawFromDestination(void);
virtual void HandleFalling(void);
int m_LastGroundHeight;
-
- /* =========================== */
+ int m_JumpCoolDown;
std::chrono::milliseconds m_IdleInterval;
std::chrono::milliseconds m_DestroyTimer;
@@ -239,10 +260,11 @@ protected:
float m_DropChanceLeggings;
float m_DropChanceBoots;
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 WouldBurn);
+ bool WouldBurnAt(Vector3d a_Location, cChunk & a_Chunk);
bool m_BurnsInDaylight;
-
double m_RelativeWalkSpeed;
/** Adds a random number of a_Item between a_Min and a_Max to itemdrops a_Drops*/
diff --git a/src/Mobs/Path.cpp b/src/Mobs/Path.cpp
new file mode 100644
index 000000000..6f3d43305
--- /dev/null
+++ b/src/Mobs/Path.cpp
@@ -0,0 +1,491 @@
+
+#include "Globals.h"
+
+#include <cmath>
+
+#include "Path.h"
+#include "../Chunk.h"
+
+#define JUMP_G_COST 20
+
+#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 10 // Higher means more CPU load but faster path calculations.
+// The only version which guarantees the shortest path is 0, 0.
+
+
+
+
+
+
+
+
+bool compareHeuristics::operator()(cPathCell * & a_Cell1, cPathCell * & a_Cell2)
+{
+ return a_Cell1->m_F > a_Cell2->m_F;
+}
+
+
+
+
+
+/* cPath implementation */
+cPath::cPath(
+ cChunk & a_Chunk,
+ const Vector3d & a_StartingPoint, const Vector3d & a_EndingPoint, int a_MaxSteps,
+ double a_BoundingBoxWidth, double a_BoundingBoxHeight,
+ int a_MaxUp, int a_MaxDown
+) :
+
+ 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_BadChunkFound(false)
+{
+ // TODO: if src not walkable OR dest not walkable, then abort.
+ // Borrow a new "isWalkable" from ProcessIfWalkable, make ProcessIfWalkable also call isWalkable
+
+ a_BoundingBoxWidth = 1; // Until we improve physics, if ever.
+
+ m_BoundingBoxWidth = ceil(a_BoundingBoxWidth);
+ m_BoundingBoxHeight = ceil(a_BoundingBoxHeight);
+ m_HalfWidth = a_BoundingBoxWidth / 2;
+
+ int HalfWidthInt = a_BoundingBoxWidth / 2;
+ m_Source.x = floor(a_StartingPoint.x - HalfWidthInt);
+ m_Source.y = floor(a_StartingPoint.y);
+ m_Source.z = floor(a_StartingPoint.z - HalfWidthInt);
+
+ m_Destination.x = floor(a_EndingPoint.x - HalfWidthInt);
+ m_Destination.y = floor(a_EndingPoint.y);
+ m_Destination.z = floor(a_EndingPoint.z - HalfWidthInt);
+
+ if (GetCell(m_Source)->m_IsSolid || GetCell(m_Destination)->m_IsSolid)
+ {
+ m_Status = ePathFinderStatus::PATH_NOT_FOUND;
+ return;
+ }
+
+ m_NearestPointToTarget = GetCell(m_Source);
+ m_Status = ePathFinderStatus::CALCULATING;
+ m_StepsLeft = a_MaxSteps;
+
+ ProcessCell(GetCell(a_StartingPoint), nullptr, 0);
+ m_Chunk = nullptr;
+}
+
+
+
+
+
+cPath::~cPath()
+{
+ if (m_Status == ePathFinderStatus::CALCULATING)
+ {
+ FinishCalculation();
+ }
+}
+
+
+
+
+
+ePathFinderStatus cPath::Step(cChunk & a_Chunk)
+{
+ m_Chunk = &a_Chunk;
+ if (m_Status != ePathFinderStatus::CALCULATING)
+ {
+ return m_Status;
+ }
+
+ if (m_BadChunkFound)
+ {
+ FinishCalculation(ePathFinderStatus::PATH_NOT_FOUND);
+ return m_Status;
+ }
+
+ if (m_StepsLeft == 0)
+ {
+ AttemptToFindAlternative();
+ }
+ 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.
+ }
+ }
+
+ m_Chunk = nullptr;
+ }
+ return m_Status;
+}
+
+
+
+
+
+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);
+
+ auto Chunk = m_Chunk->GetNeighborChunk(a_Location.x, a_Location.z);
+ if ((Chunk == nullptr) || !Chunk->IsValid())
+ {
+ m_BadChunkFound = true;
+ return true;
+ }
+ m_Chunk = Chunk;
+
+ BLOCKTYPE BlockType;
+ NIBBLETYPE BlockMeta;
+ int RelX = a_Location.x - m_Chunk->GetPosX() * cChunkDef::Width;
+ int RelZ = a_Location.z - m_Chunk->GetPosZ() * cChunkDef::Width;
+
+ m_Chunk->GetBlockTypeMeta(RelX, a_Location.y, RelZ, BlockType, BlockMeta);
+ if ((BlockType == E_BLOCK_FENCE) || (BlockType == E_BLOCK_FENCE_GATE))
+ {
+ GetCell(a_Location + Vector3i(0, 1, 0))->m_IsSolid = true; // Mobs will always think that the fence is 2 blocks high and therefore won't jump over.
+ }
+ if (BlockType == E_BLOCK_STATIONARY_WATER)
+ {
+ GetCell(a_Location + Vector3i(0, -1, 0))->m_IsSolid = true;
+ }
+
+ return cBlockInfo::IsSolid(BlockType);
+}
+
+
+
+
+
+bool cPath::Step_Internal()
+{
+ cPathCell * CurrentCell = OpenListPop();
+
+ // Path not reachable.
+ if (CurrentCell == nullptr)
+ {
+ AttemptToFindAlternative();
+ return true;
+ }
+
+ // Path found.
+ if (CurrentCell->m_Location == m_Destination)
+ {
+ BuildPath();
+ FinishCalculation(ePathFinderStatus::PATH_FOUND);
+ return true;
+ }
+
+ // Calculation not finished yet.
+ // Check if we have a new NearestPoint.
+ // TODO I don't like this that much, there should be a smarter way.
+ 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 our height.
+ ProcessIfWalkable(CurrentCell->m_Location + Vector3i(1, 0, 0), CurrentCell, 10);
+ ProcessIfWalkable(CurrentCell->m_Location + Vector3i(-1, 0, 0), CurrentCell, 10);
+ ProcessIfWalkable(CurrentCell->m_Location + Vector3i(0, 0, 1), CurrentCell, 10);
+ ProcessIfWalkable(CurrentCell->m_Location + Vector3i(0, 0, -1), CurrentCell, 10);
+
+ // Check diagonals on XY plane.
+ for (int x = -1; x <= 1; x += 2)
+ {
+ if (GetCell(CurrentCell->m_Location + Vector3i(x, 0, 0))->m_IsSolid) // If there's a solid our east / west.
+ {
+ ProcessIfWalkable(CurrentCell->m_Location + Vector3i(x, 1, 0), CurrentCell, JUMP_G_COST); // Check east / west-up.
+ }
+ else
+ {
+ ProcessIfWalkable(CurrentCell->m_Location + Vector3i(x, -1, 0), CurrentCell, 14); // Else check east / west-down.
+ }
+ }
+
+ // Check diagonals on the YZ plane.
+ for (int z = -1; z <= 1; z += 2)
+ {
+ if (GetCell(CurrentCell->m_Location + Vector3i(0, 0, z))->m_IsSolid) // If there's a solid our east / west.
+ {
+ ProcessIfWalkable(CurrentCell->m_Location + Vector3i(0, 1, z), CurrentCell, JUMP_G_COST); // Check east / west-up.
+ }
+ else
+ {
+ ProcessIfWalkable(CurrentCell->m_Location + Vector3i(0, -1, z), CurrentCell, 14); // Else check east / west-down.
+ }
+ }
+
+ // Check diagonals on the XZ plane. (Normal diagonals, this plane is special because of gravity, etc)
+ for (int x = -1; x <= 1; x += 2)
+ {
+ for (int z = -1; z <= 1; z += 2)
+ {
+ // This condition prevents diagonal corner cutting.
+ if (!GetCell(CurrentCell->m_Location + Vector3i(x, 0, 0))->m_IsSolid && !GetCell(CurrentCell->m_Location + Vector3i(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 + Vector3i(x, -1, 0))->m_IsSolid && GetCell(CurrentCell->m_Location + Vector3i(0, -1, z))->m_IsSolid)
+ {
+ ProcessIfWalkable(CurrentCell->m_Location + Vector3i(x, 0, z), CurrentCell, 14); // 14 is a good enough approximation of sqrt(10 + 10).
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+
+
+
+
+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();
+ m_OpenList = std::priority_queue<cPathCell *, std::vector<cPathCell *>, compareHeuristics>{};
+}
+
+
+
+
+
+void cPath::FinishCalculation(ePathFinderStatus a_NewStatus)
+{
+ if (m_BadChunkFound)
+ {
+ a_NewStatus = ePathFinderStatus::PATH_NOT_FOUND;
+ }
+ 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 or NEARBY_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 Vector3i & a_Location, cPathCell * a_Parent, int a_Cost)
+{
+ cPathCell * cell = GetCell(a_Location);
+ int x, y, z;
+
+ // Make sure we fit in the position.
+ for (y = 0; y < m_BoundingBoxHeight; ++y)
+ {
+ for (x = 0; x < m_BoundingBoxWidth; ++x)
+ {
+ for (z = 0; z < m_BoundingBoxWidth; ++z)
+ {
+ if (GetCell(a_Location + Vector3i(x, y, z))->m_IsSolid)
+ {
+ return;
+ }
+ }
+ }
+ }
+
+ /*y =-1;
+ for (x = 0; x < m_BoundingBoxWidth; ++x)
+ {
+ for (z = 0; z < m_BoundingBoxWidth; ++z)
+ {
+ if (!GetCell(a_Location + Vector3i(x, y, z))->m_IsSolid)
+ {
+ return;
+ }
+ }
+ }
+ ProcessCell(cell, a_Parent, a_Cost);*/
+
+ // Make sure there's at least 1 piece of solid below us.
+
+ bool GroundFlag = false;
+ y =-1;
+ for (x = 0; x < m_BoundingBoxWidth; ++x)
+ {
+ for (z = 0; z < m_BoundingBoxWidth; ++z)
+ {
+ if (GetCell(a_Location + Vector3i(x, y, z))->m_IsSolid)
+ {
+ GroundFlag = true;
+ break;
+ }
+ }
+ }
+
+ if (GroundFlag)
+ {
+ 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 = static_cast<decltype(a_Cell->m_H)>((a_Cell->m_Location - m_Destination).Length() * 10);
+ #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 Vector3i & a_Location)
+{
+ // Create the cell in the hash table if it's not already there.
+ if (m_Map.count(a_Location) == 0) // Case 1: Cell is not on any list. We've never checked this cell before.
+ {
+ 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 &m_Map[a_Location];
+ }
+ else
+ {
+ return &m_Map[a_Location];
+ }
+}
diff --git a/src/Mobs/Path.h b/src/Mobs/Path.h
new file mode 100644
index 000000000..3b9c0400e
--- /dev/null
+++ b/src/Mobs/Path.h
@@ -0,0 +1,186 @@
+
+#pragma once
+
+/*
+// Needed Fwds: cPath
+enum class ePathFinderStatus;
+class cPath;
+*/
+
+#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. */
+ #include "PathFinderIrrlicht_Head.h"
+#endif
+
+#include <unordered_map>
+
+//fwd: ../Chunk.h
+class cChunk;
+
+/* Various little structs and classes */
+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:
+ /** 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(
+ cChunk & a_Chunk,
+ const Vector3d & a_StartingPoint, const Vector3d & a_EndingPoint, int a_MaxSteps,
+ double a_BoundingBoxWidth, double a_BoundingBoxHeight,
+ int a_MaxUp = 1, int a_MaxDown = 1
+ );
+
+ /** Destroys the path and frees its memory. */
+ ~cPath();
+
+ /** 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 Vector3d GetNextPoint()
+ {
+ ASSERT(m_Status == ePathFinderStatus::PATH_FOUND);
+ Vector3i Point = m_PathPoints[m_PathPoints.size() - 1 - (++m_CurrentPoint)];
+ return Vector3d(Point.x + m_HalfWidth, Point.y, Point.z + m_HalfWidth);
+ }
+ /** 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);
+ return (m_CurrentPoint == m_PathPoints.size() - 1);
+ }
+ inline bool IsFirstPoint()
+ {
+ ASSERT(m_Status == ePathFinderStatus::PATH_FOUND);
+ return (m_CurrentPoint == 0);
+ }
+ /** Get the point at a_index. Remark: Internally, the indexes are reversed. */
+ inline Vector3d GetPoint(size_t a_index)
+ {
+ ASSERT(m_Status == ePathFinderStatus::PATH_FOUND);
+ ASSERT(a_index < m_PathPoints.size());
+ Vector3i Point = m_PathPoints[m_PathPoints.size() - 1 - a_index];
+ return Vector3d(Point.x + m_HalfWidth, Point.y, Point.z + m_HalfWidth);
+ }
+ /** Returns the total number of points this path has. */
+ inline int GetPointCount()
+ {
+ if (m_Status != ePathFinderStatus::PATH_FOUND)
+ {
+ return 0;
+ }
+ return m_PathPoints.size();
+ }
+
+ struct VectorHasher
+ {
+ std::size_t operator()(const Vector3i & 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 Vector3i & 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.
+ void AttemptToFindAlternative();
+ void BuildPath();
+
+ /* Openlist and closedlist management */
+ void OpenListAdd(cPathCell * a_Cell);
+ cPathCell * OpenListPop();
+ void ProcessIfWalkable(const Vector3i &a_Location, cPathCell * a_Parent, int a_Cost);
+
+ /* Map management */
+ void ProcessCell(cPathCell * a_Cell, cPathCell * a_Caller, int a_GDelta);
+ cPathCell * GetCell(const Vector3i & a_location);
+
+ /* Pathfinding fields */
+ std::priority_queue<cPathCell *, std::vector<cPathCell *>, compareHeuristics> m_OpenList;
+ std::unordered_map<Vector3i, cPathCell, VectorHasher> m_Map;
+ Vector3i m_Destination;
+ Vector3i m_Source;
+ int m_BoundingBoxWidth;
+ int m_BoundingBoxHeight;
+ double m_HalfWidth;
+ int m_StepsLeft;
+ cPathCell * m_NearestPointToTarget;
+ cFastRandom m_Rand;
+
+ /* Control fields */
+ ePathFinderStatus m_Status;
+
+ /* Final path fields */
+ size_t m_CurrentPoint;
+ std::vector<Vector3i> m_PathPoints;
+
+ /* Interfacing with the world */
+ cChunk * m_Chunk; // Only valid inside Step()!
+ bool m_BadChunkFound;
+ #ifdef COMPILING_PATHFIND_DEBUGGER
+ #include "../path_irrlicht.cpp"
+ #endif
+};
diff --git a/src/Mobs/Pig.cpp b/src/Mobs/Pig.cpp
index edd4d9de4..56d6abfd5 100644
--- a/src/Mobs/Pig.cpp
+++ b/src/Mobs/Pig.cpp
@@ -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))
{
MoveToPosition((m_Attachee->GetPosition()) + (m_Attachee->GetLookVector()*10));
- m_bMovingToDestination = true;
}
}
}
diff --git a/src/Mobs/Sheep.cpp b/src/Mobs/Sheep.cpp
index c0cdec035..ec24f167e 100644
--- a/src/Mobs/Sheep.cpp
+++ b/src/Mobs/Sheep.cpp
@@ -98,7 +98,7 @@ void cSheep::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
if (m_TimeToStopEating > 0)
{
- m_bMovingToDestination = false; // The sheep should not move when he's eating
+ StopMovingToPosition();
m_TimeToStopEating--;
if (m_TimeToStopEating == 0)
diff --git a/src/Mobs/Skeleton.cpp b/src/Mobs/Skeleton.cpp
index 331c8e8ad..f99404669 100644
--- a/src/Mobs/Skeleton.cpp
+++ b/src/Mobs/Skeleton.cpp
@@ -37,7 +37,7 @@ void cSkeleton::GetDrops(cItems & a_Drops, cEntity * a_Killer)
else
{
AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_ARROW);
-
+
}
AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_BONE);
AddRandomArmorDropItem(a_Drops, LootingLevel);
@@ -48,25 +48,6 @@ void cSkeleton::GetDrops(cItems & a_Drops, cEntity * a_Killer)
-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
- if (
- !IsOnFire() &&
- (m_World->GetBlockSkyLight((int)floor(a_Position.x), (int)floor(a_Position.y), (int)floor(a_Position.z)) - m_World->GetSkyDarkness() > 8)
- )
- {
- m_bMovingToDestination = false;
- return;
- }
-
- super::MoveToPosition(a_Position);
-}
-
-
-
-
-
void cSkeleton::Attack(std::chrono::milliseconds a_Dt)
{
m_AttackInterval += (static_cast<float>(a_Dt.count()) / 1000) * m_AttackRate;
diff --git a/src/Mobs/Skeleton.h b/src/Mobs/Skeleton.h
index 9c49c52fb..1b6ce4bf2 100644
--- a/src/Mobs/Skeleton.h
+++ b/src/Mobs/Skeleton.h
@@ -11,19 +11,18 @@ class cSkeleton :
public cAggressiveMonster
{
typedef cAggressiveMonster super;
-
+
public:
cSkeleton(bool IsWither);
CLASS_PROTODEF(cSkeleton)
virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override;
- virtual void MoveToPosition(const Vector3d & a_Position) override;
virtual void Attack(std::chrono::milliseconds a_Dt) override;
virtual void SpawnOn(cClientHandle & a_ClientHandle) override;
virtual bool IsUndead(void) override { return true; }
-
+
bool IsWither(void) const { return m_bIsWither; }
private:
diff --git a/src/Mobs/Slime.cpp b/src/Mobs/Slime.cpp
index e42501e47..7fc4821d8 100644
--- a/src/Mobs/Slime.cpp
+++ b/src/Mobs/Slime.cpp
@@ -89,7 +89,7 @@ void cSlime::KilledBy(TakeDamageInfo & a_TDI)
-const AString cSlime::GetSizeName(int a_Size) const
+AString cSlime::GetSizeName(int a_Size)
{
if (a_Size > 1)
{
diff --git a/src/Mobs/Slime.h b/src/Mobs/Slime.h
index 29605992d..40131b101 100644
--- a/src/Mobs/Slime.h
+++ b/src/Mobs/Slime.h
@@ -27,7 +27,7 @@ public:
/** Returns the text describing the slime's size, as used by the client's resource subsystem for sounds.
Returns either "big" or "small". */
- const AString GetSizeName(int a_Size) const;
+ static AString GetSizeName(int a_Size);
protected:
diff --git a/src/Mobs/Villager.cpp b/src/Mobs/Villager.cpp
index 6f647ac18..e4953d546 100644
--- a/src/Mobs/Villager.cpp
+++ b/src/Mobs/Villager.cpp
@@ -156,7 +156,7 @@ void cVillager::HandleFarmerPrepareFarmCrops()
void cVillager::HandleFarmerTryHarvestCrops()
{
// Harvest the crops if the villager isn't moving and if the crops are closer then 2 blocks.
- if (!m_bMovingToDestination && (GetPosition() - m_CropsPos).Length() < 2)
+ if (!m_IsFollowingPath && (GetPosition() - m_CropsPos).Length() < 2)
{
// Check if the blocks didn't change while the villager was walking to the coordinates.
BLOCKTYPE CropBlock = m_World->GetBlock(m_CropsPos.x, m_CropsPos.y, m_CropsPos.z);
diff --git a/src/Mobs/Wolf.cpp b/src/Mobs/Wolf.cpp
index b3eefdf79..3c2ec1520 100644
--- a/src/Mobs/Wolf.cpp
+++ b/src/Mobs/Wolf.cpp
@@ -5,6 +5,7 @@
#include "../World.h"
#include "../Entities/Player.h"
#include "../Items/ItemHandler.h"
+#include "Broadcaster.h"
@@ -83,13 +84,13 @@ void cWolf::OnRightClicked(cPlayer & a_Player)
SetIsTame(true);
SetOwner(a_Player.GetName(), a_Player.GetUUID());
m_World->BroadcastEntityStatus(*this, esWolfTamed);
- m_World->BroadcastParticleEffect("heart", (float) GetPosX(), (float) GetPosY(), (float) GetPosZ(), 0, 0, 0, 0, 5);
+ m_World->GetBroadcaster().BroadcastParticleEffect("heart", static_cast<Vector3f>(GetPosition()), Vector3f{}, 0, 5);
}
else
{
// Taming failed
m_World->BroadcastEntityStatus(*this, esWolfTaming);
- m_World->BroadcastParticleEffect("smoke", (float) GetPosX(), (float) GetPosY(), (float) GetPosZ(), 0, 0, 0, 0, 5);
+ m_World->GetBroadcaster().BroadcastParticleEffect("smoke", static_cast<Vector3f>(GetPosition()), Vector3f{}, 0, 5);
}
}
}
@@ -137,7 +138,7 @@ void cWolf::OnRightClicked(cPlayer & a_Player)
}
}
}
-
+
m_World->BroadcastEntityMetadata(*this);
}
@@ -203,7 +204,7 @@ void cWolf::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
}
else if (IsSitting())
{
- m_bMovingToDestination = false;
+ StopMovingToPosition();
}
}
diff --git a/src/Mobs/Wolf.h b/src/Mobs/Wolf.h
index 73ffb55c2..5de83acd8 100644
--- a/src/Mobs/Wolf.h
+++ b/src/Mobs/Wolf.h
@@ -25,8 +25,8 @@ public:
virtual void Attack(std::chrono::milliseconds a_Dt) override;
// Get functions
- bool IsSitting (void) const { return m_IsSitting; }
- bool IsTame (void) const { return m_IsTame; }
+ bool IsSitting (void) const override { return m_IsSitting; }
+ bool IsTame (void) const override { return m_IsTame; }
bool IsBegging (void) const { return m_IsBegging; }
bool IsAngry (void) const { return m_IsAngry; }
AString GetOwnerName (void) const { return m_OwnerName; }
diff --git a/src/Mobs/Zombie.cpp b/src/Mobs/Zombie.cpp
index 63042e252..fa4ac855d 100644
--- a/src/Mobs/Zombie.cpp
+++ b/src/Mobs/Zombie.cpp
@@ -37,26 +37,3 @@ void cZombie::GetDrops(cItems & a_Drops, cEntity * a_Killer)
AddRandomArmorDropItem(a_Drops, LootingLevel);
AddRandomWeaponDropItem(a_Drops, LootingLevel);
}
-
-
-
-
-
-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
- if (
- !IsOnFire() &&
- (m_World->GetBlockSkyLight((int)floor(a_Position.x), (int)floor(a_Position.y), (int)floor(a_Position.z)) - m_World->GetSkyDarkness() > 8)
- )
- {
- m_bMovingToDestination = false;
- return;
- }
-
- super::MoveToPosition(a_Position);
-}
-
-
-
-
diff --git a/src/Mobs/Zombie.h b/src/Mobs/Zombie.h
index 809c2a6fe..47a9f1904 100644
--- a/src/Mobs/Zombie.h
+++ b/src/Mobs/Zombie.h
@@ -10,17 +10,15 @@ class cZombie :
public cAggressiveMonster
{
typedef cAggressiveMonster super;
-
+
public:
cZombie(bool a_IsVillagerZombie);
CLASS_PROTODEF(cZombie)
-
- virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override;
- virtual void MoveToPosition(const Vector3d & a_Position) override;
+ virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override;
virtual bool IsUndead(void) override { return true; }
-
+
bool IsVillagerZombie(void) const { return m_IsVillagerZombie; }
bool IsConverting (void) const { return m_IsConverting; }