summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/Mobs/AggressiveMonster.cpp7
-rw-r--r--src/Mobs/CMakeLists.txt2
-rw-r--r--src/Mobs/Monster.cpp268
-rw-r--r--src/Mobs/Monster.h20
-rw-r--r--src/Mobs/Path.cpp379
-rw-r--r--src/Mobs/Path.h161
-rw-r--r--src/Mobs/Pig.cpp1
-rw-r--r--src/Mobs/Sheep.cpp2
-rw-r--r--src/Mobs/Skeleton.cpp15
-rw-r--r--src/Mobs/Wolf.cpp4
-rw-r--r--src/Mobs/Zombie.cpp13
-rw-r--r--src/Server.cpp38
-rw-r--r--src/Vector3.h1
13 files changed, 736 insertions, 175 deletions
diff --git a/src/Mobs/AggressiveMonster.cpp b/src/Mobs/AggressiveMonster.cpp
index 526b39e39..d0fb79f6d 100644
--- a/src/Mobs/AggressiveMonster.cpp
+++ b/src/Mobs/AggressiveMonster.cpp
@@ -37,10 +37,7 @@ void cAggressiveMonster::InStateChasing(std::chrono::milliseconds a_Dt)
}
}
- if (!IsMovingToTargetPosition())
- {
- MoveToPosition(m_Target->GetPosition());
- }
+ MoveToPosition(m_Target->GetPosition());
}
}
@@ -100,7 +97,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/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/Monster.cpp b/src/Mobs/Monster.cpp
index 55d83302a..e225ff9b1 100644
--- a/src/Mobs/Monster.cpp
+++ b/src/Mobs/Monster.cpp
@@ -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));
- 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 */)
+ if (m_Path == nullptr)
{
- // Too low/high, can't really do anything
- FinishPathFinding();
- return;
- }
+ Vector3d position = GetPosition();
+ Vector3d Dest = m_FinalDestination;
+
+ // 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);
- for (size_t i = 0; i < ARRAYCOUNT(gCrossCoords); i++)
+
+ m_IsFollowingPath = false;
+ }
+ m_PathStatus = m_Path->Step();
+ switch (m_PathStatus)
{
- if (IsCoordinateInTraversedList(Vector3i(gCrossCoords[i].x + PosX, PosY, gCrossCoords[i].z + PosZ)))
- {
- continue;
- }
- 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::PATH_NOT_FOUND:
{
- m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY, gCrossCoords[i].z + PosZ));
+ FinishPathFinding();
+ 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::CALCULATING:
{
- m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY + 1, gCrossCoords[i].z + PosZ));
+ m_Destination = GetPosition();
+ break;
}
- }
- 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,52 +293,50 @@ 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)
+ if (--m_GiveUpCounter == 0)
{
- Distance *= 2.5f;
- }
- else if (IsSwimming())
- {
- Distance *= 1.3f;
+ FinishPathFinding();
}
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);
+ 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);
- // 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
+ else if (ReachedFinalDestination())
{
- 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
- }
+ StopMovingToPosition();
}
}
@@ -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);
diff --git a/src/Mobs/Monster.h b/src/Mobs/Monster.h
index 21ed0c25a..43861e021 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;
@@ -61,6 +62,7 @@ public:
virtual void OnRightClicked(cPlayer & a_Player) override;
virtual void MoveToPosition(const Vector3d & a_Position); // tolua_export
+ virtual void StopMovingToPosition();
virtual bool ReachedDestination(void);
// tolua_begin
@@ -162,6 +164,11 @@ protected:
/** A pointer to the entity this mobile is aiming to reach */
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 */
Vector3d m_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 */
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;
- }
+ void FinishPathFinding(void);
/** Sets the body yaw and head yaw/pitch based on next/ultimate destinations */
void SetPitchAndYawFromDestination(void);
@@ -239,10 +242,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);
+ 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..0cb03c925
--- /dev/null
+++ b/src/Mobs/Path.cpp
@@ -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
diff --git a/src/Mobs/Path.h b/src/Mobs/Path.h
new file mode 100644
index 000000000..05fd59155
--- /dev/null
+++ b/src/Mobs/Path.h
@@ -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
+};
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..ef049f8d4 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);
@@ -50,17 +50,18 @@ 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
+ // 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 (
!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;
- return;
+ StopMovingToPosition();
}
-
- super::MoveToPosition(a_Position);
}
diff --git a/src/Mobs/Wolf.cpp b/src/Mobs/Wolf.cpp
index b3eefdf79..c66763f17 100644
--- a/src/Mobs/Wolf.cpp
+++ b/src/Mobs/Wolf.cpp
@@ -137,7 +137,7 @@ void cWolf::OnRightClicked(cPlayer & a_Player)
}
}
}
-
+
m_World->BroadcastEntityMetadata(*this);
}
@@ -203,7 +203,7 @@ void cWolf::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
}
else if (IsSitting())
{
- m_bMovingToDestination = false;
+ StopMovingToPosition();
}
}
diff --git a/src/Mobs/Zombie.cpp b/src/Mobs/Zombie.cpp
index 63042e252..b2738050e 100644
--- a/src/Mobs/Zombie.cpp
+++ b/src/Mobs/Zombie.cpp
@@ -44,17 +44,18 @@ void cZombie::GetDrops(cItems & a_Drops, cEntity * a_Killer)
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 (
!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;
- return;
+ StopMovingToPosition();
}
-
- super::MoveToPosition(a_Position);
}
diff --git a/src/Server.cpp b/src/Server.cpp
index 8b6a2e769..996de2695 100644
--- a/src/Server.cpp
+++ b/src/Server.cpp
@@ -113,7 +113,7 @@ void cServer::cTickThread::Execute(void)
auto msec = std::chrono::duration_cast<std::chrono::milliseconds>(NowTime - LastTime).count();
m_ShouldTerminate = !m_Server.Tick(static_cast<float>(msec));
auto TickTime = std::chrono::steady_clock::now() - NowTime;
-
+
if (TickTime < msPerTick)
{
// Stretch tick time until it's at least msPerTick
@@ -206,7 +206,7 @@ bool cServer::InitServer(cIniFile & a_SettingsIni, bool a_ShouldAuth)
LOGINFO("Compatible protocol versions %s", MCS_PROTOCOL_VERSIONS);
m_Ports = ReadUpgradeIniPorts(a_SettingsIni, "Server", "Ports", "Port", "PortsIPv6", "25565");
-
+
m_RCONServer.Initialize(a_SettingsIni);
m_bIsConnected = true;
@@ -231,10 +231,10 @@ bool cServer::InitServer(cIniFile & a_SettingsIni, bool a_ShouldAuth)
{
LOGWARNING("WARNING: BungeeCord is allowed and server set to online mode. This is unsafe and will not work properly. Disable either authentication or BungeeCord in settings.ini.");
}
-
+
m_ShouldLoadOfflinePlayerData = a_SettingsIni.GetValueSetB("PlayerData", "LoadOfflinePlayerData", false);
m_ShouldLoadNamedPlayerData = a_SettingsIni.GetValueSetB("PlayerData", "LoadNamedPlayerData", true);
-
+
m_ClientViewDistance = a_SettingsIni.GetValueSetI("Server", "DefaultViewDistance", cClientHandle::DEFAULT_VIEW_DISTANCE);
if (m_ClientViewDistance < cClientHandle::MIN_VIEW_DISTANCE)
{
@@ -246,9 +246,9 @@ bool cServer::InitServer(cIniFile & a_SettingsIni, bool a_ShouldAuth)
m_ClientViewDistance = cClientHandle::MAX_VIEW_DISTANCE;
LOGINFO("Setting default viewdistance to the maximum of %d", m_ClientViewDistance);
}
-
+
PrepareKeys();
-
+
return true;
}
@@ -320,13 +320,13 @@ bool cServer::Tick(float a_Dt)
cCSLock Lock(m_CSPlayerCount);
m_PlayerCount += PlayerCountDiff;
}
-
+
// Send the tick to the plugins, as well as let the plugin manager reload, if asked to (issue #102):
cPluginManager::Get()->Tick(a_Dt);
-
+
// Let the Root process all the queued commands:
cRoot::Get()->TickCommands();
-
+
// Tick all clients not yet assigned to a world:
TickClients(a_Dt);
@@ -351,7 +351,7 @@ void cServer::TickClients(float a_Dt)
cClientHandlePtrs RemoveClients;
{
cCSLock Lock(m_CSClients);
-
+
// Remove clients that have moved to a world (the world will be ticking them from now on)
for (auto itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr)
{
@@ -365,7 +365,7 @@ void cServer::TickClients(float a_Dt)
}
} // for itr - m_ClientsToRemove[]
m_ClientsToRemove.clear();
-
+
// Tick the remaining clients, take out those that have been destroyed into RemoveClients
for (auto itr = m_Clients.begin(); itr != m_Clients.end();)
{
@@ -380,7 +380,7 @@ void cServer::TickClients(float a_Dt)
++itr;
} // for itr - m_Clients[]
}
-
+
// Delete the clients that have been destroyed
RemoveClients.clear();
}
@@ -439,7 +439,7 @@ void cServer::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallbac
}
// "stop" and "restart" are handled in cRoot::ExecuteConsoleCommand, our caller, due to its access to controlling variables
-
+
// "help" and "reload" are to be handled by MCS, so that they work no matter what
if (split[0] == "help")
{
@@ -529,7 +529,7 @@ void cServer::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallbac
DumpUsedMemory(&Output);
return;
}
-
+
else if (split[0].compare("killmem") == 0)
{
for (;;)
@@ -544,7 +544,7 @@ void cServer::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallbac
a_Output.Finished();
return;
}
-
+
a_Output.Out("Unknown command, type 'help' for all commands.");
a_Output.Finished();
}
@@ -558,13 +558,13 @@ void cServer::PrintHelp(const AStringVector & a_Split, cCommandOutputCallback &
UNUSED(a_Split);
typedef std::pair<AString, AString> AStringPair;
typedef std::vector<AStringPair> AStringPairs;
-
+
class cCallback :
public cPluginManager::cCommandEnumCallback
{
public:
cCallback(void) : m_MaxLen(0) {}
-
+
virtual bool Command(const AString & a_Command, const cPlugin * a_Plugin, const AString & a_Permission, const AString & a_HelpString) override
{
UNUSED(a_Plugin);
@@ -579,7 +579,7 @@ void cServer::PrintHelp(const AStringVector & a_Split, cCommandOutputCallback &
}
return false;
}
-
+
AStringPairs m_Commands;
size_t m_MaxLen;
} Callback;
@@ -625,7 +625,7 @@ void cServer::Shutdown(void)
srv->Close();
}
m_ServerHandles.clear();
-
+
// Notify the tick thread and wait for it to terminate:
m_bRestarting = true;
m_RestartEvent.Wait();
diff --git a/src/Vector3.h b/src/Vector3.h
index 36f277ba4..c5431438e 100644
--- a/src/Vector3.h
+++ b/src/Vector3.h
@@ -354,6 +354,7 @@ protected:
+
template <> inline Vector3<int> Vector3<int>::Floor(void) const
{
return *this;