diff options
Diffstat (limited to 'src/Mobs')
-rw-r--r-- | src/Mobs/AggressiveMonster.cpp | 4 | ||||
-rw-r--r-- | src/Mobs/AggressiveMonster.h | 8 | ||||
-rw-r--r-- | src/Mobs/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/Mobs/Horse.cpp | 6 | ||||
-rw-r--r-- | src/Mobs/Horse.h | 6 | ||||
-rw-r--r-- | src/Mobs/Monster.cpp | 60 | ||||
-rw-r--r-- | src/Mobs/Monster.h | 8 | ||||
-rw-r--r-- | src/Mobs/Path.cpp | 331 | ||||
-rw-r--r-- | src/Mobs/Path.h | 63 | ||||
-rw-r--r-- | src/Mobs/PathFinder.cpp | 98 | ||||
-rw-r--r-- | src/Mobs/PathFinder.h | 24 |
11 files changed, 391 insertions, 219 deletions
diff --git a/src/Mobs/AggressiveMonster.cpp b/src/Mobs/AggressiveMonster.cpp index 309f6d985..56500309d 100644 --- a/src/Mobs/AggressiveMonster.cpp +++ b/src/Mobs/AggressiveMonster.cpp @@ -22,9 +22,9 @@ cAggressiveMonster::cAggressiveMonster(const AString & a_ConfigName, eMonsterTyp // What to do if in Chasing State -void cAggressiveMonster::InStateChasing(std::chrono::milliseconds a_Dt) +void cAggressiveMonster::InStateChasing(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { - super::InStateChasing(a_Dt); + super::InStateChasing(a_Dt, a_Chunk); if (m_Target != nullptr) { diff --git a/src/Mobs/AggressiveMonster.h b/src/Mobs/AggressiveMonster.h index 17539d39e..13f59842f 100644 --- a/src/Mobs/AggressiveMonster.h +++ b/src/Mobs/AggressiveMonster.h @@ -11,16 +11,16 @@ class cAggressiveMonster : public cMonster { typedef cMonster super; - + public: cAggressiveMonster(const AString & a_ConfigName, eMonsterType a_MobType, const AString & a_SoundHurt, const AString & a_SoundDeath, double a_Width, double a_Height); virtual void Tick (std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; - virtual void InStateChasing(std::chrono::milliseconds a_Dt) override; - + virtual void InStateChasing(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + virtual void EventSeePlayer(cEntity *) override; - + /** Try to perform attack returns true if attack was deemed successful (hit player, fired projectile, creeper exploded, etc.) even if it didn't actually do damage return false if e.g. the mob is still in cooldown from a previous attack */ diff --git a/src/Mobs/CMakeLists.txt b/src/Mobs/CMakeLists.txt index 14c7a8ca3..4336af7c3 100644 --- a/src/Mobs/CMakeLists.txt +++ b/src/Mobs/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required (VERSION 2.6) -project (MCServer) +project (Cuberite) include_directories ("${PROJECT_SOURCE_DIR}/../") diff --git a/src/Mobs/Horse.cpp b/src/Mobs/Horse.cpp index f133f9912..01906b7b4 100644 --- a/src/Mobs/Horse.cpp +++ b/src/Mobs/Horse.cpp @@ -72,7 +72,7 @@ void cHorse::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) m_bIsTame = true; } } - + if (m_bIsRearing) { if (m_RearTickCount == 20) @@ -161,12 +161,12 @@ void cHorse::GetDrops(cItems & a_Drops, cEntity * a_Killer) -void cHorse::InStateIdle(std::chrono::milliseconds a_Dt) +void cHorse::InStateIdle(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { // If horse is tame and someone is sitting on it, don't walk around if ((!m_bIsTame) || (m_Attachee == nullptr)) { - super::InStateIdle(a_Dt); + super::InStateIdle(a_Dt, a_Chunk); } } diff --git a/src/Mobs/Horse.h b/src/Mobs/Horse.h index c87d437c9..7afa5bf29 100644 --- a/src/Mobs/Horse.h +++ b/src/Mobs/Horse.h @@ -11,14 +11,14 @@ class cHorse : public cPassiveMonster { typedef cPassiveMonster super; - + public: cHorse(int Type, int Color, int Style, int TameTimes); CLASS_PROTODEF(cHorse) - + virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; - virtual void InStateIdle(std::chrono::milliseconds a_Dt) override; + virtual void InStateIdle(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; virtual void HandleSpeedFromAttachee(float a_Forward, float a_Sideways) override; virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; virtual void OnRightClicked(cPlayer & a_Player) override; diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index fa36285ba..7d8c3de02 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -133,9 +133,8 @@ void cMonster::MoveToWayPoint(cChunk & a_Chunk) { if (DoesPosYRequireJump(FloorC(m_NextWayPointPosition.y))) { - if ( - (IsOnGround() && (GetSpeedX() == 0.0f) && (GetSpeedY() == 0.0f)) // TODO water handling? - ) + if (((IsOnGround()) && (GetSpeed().SqrLength() == 0.0f)) || + (IsSwimming())) { m_bOnGround = false; m_JumpCoolDown = 20; @@ -303,18 +302,18 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) case IDLE: { // If enemy passive we ignore checks for player visibility. - InStateIdle(a_Dt); + InStateIdle(a_Dt, a_Chunk); break; } case CHASING: { // If we do not see a player anymore skip chasing action. - InStateChasing(a_Dt); + InStateChasing(a_Dt, a_Chunk); break; } case ESCAPING: { - InStateEscaping(a_Dt); + InStateEscaping(a_Dt, a_Chunk); break; } case ATTACKING: break; @@ -377,7 +376,7 @@ void cMonster::SetPitchAndYawFromDestination(bool a_IsFollowingPath) double HeadRotation, HeadPitch; HeadDistance.Normalize(); VectorToEuler(HeadDistance.x, HeadDistance.y, HeadDistance.z, HeadRotation, HeadPitch); - if (std::abs(BodyRotation - HeadRotation) < 90) + if ((std::abs(BodyRotation - HeadRotation) < 70) && (std::abs(HeadPitch) < 60)) { SetHeadYaw(HeadRotation); SetPitch(-HeadPitch); @@ -385,7 +384,7 @@ void cMonster::SetPitchAndYawFromDestination(bool a_IsFollowingPath) else { SetHeadYaw(BodyRotation); - SetPitch(-BodyPitch); + SetPitch(0); } } @@ -611,7 +610,7 @@ void cMonster::EventLosePlayer(void) -void cMonster::InStateIdle(std::chrono::milliseconds a_Dt) +void cMonster::InStateIdle(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { if (m_PathfinderActivated) { @@ -632,9 +631,24 @@ void cMonster::InStateIdle(std::chrono::milliseconds a_Dt) if ((Dist.SqrLength() > 2) && (rem >= 3)) { - Vector3d Destination(GetPosX() + Dist.x, 0, GetPosZ() + Dist.z); - Destination.y = FindFirstNonAirBlockPosition(Destination.x, Destination.z); - MoveToPosition(Destination); + + Vector3d Destination(GetPosX() + Dist.x, GetPosition().y, GetPosZ() + Dist.z); + + cChunk * Chunk = a_Chunk.GetNeighborChunk(static_cast<int>(Destination.x), static_cast<int>(Destination.z)); + if ((Chunk == nullptr) || !Chunk->IsValid()) + { + return; + } + + BLOCKTYPE BlockType; + NIBBLETYPE BlockMeta; + int RelX = static_cast<int>(Destination.x) - Chunk->GetPosX() * cChunkDef::Width; + int RelZ = static_cast<int>(Destination.z) - Chunk->GetPosZ() * cChunkDef::Width; + Chunk->GetBlockTypeMeta(RelX, static_cast<int>(Destination.y) - 1, RelZ, BlockType, BlockMeta); + if (BlockType != E_BLOCK_STATIONARY_WATER) // Idle mobs shouldn't enter water on purpose + { + MoveToPosition(Destination); + } } } } @@ -645,7 +659,7 @@ void cMonster::InStateIdle(std::chrono::milliseconds a_Dt) // What to do if in Chasing State // This state should always be defined in each child class -void cMonster::InStateChasing(std::chrono::milliseconds a_Dt) +void cMonster::InStateChasing(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { UNUSED(a_Dt); } @@ -655,7 +669,7 @@ void cMonster::InStateChasing(std::chrono::milliseconds a_Dt) // What to do if in Escaping State -void cMonster::InStateEscaping(std::chrono::milliseconds a_Dt) +void cMonster::InStateEscaping(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { UNUSED(a_Dt); @@ -1084,6 +1098,19 @@ void cMonster::HandleDaylightBurning(cChunk & a_Chunk, bool WouldBurn) bool cMonster::WouldBurnAt(Vector3d a_Location, cChunk & a_Chunk) { + // If the Y coord is out of range, return the most logical result without considering anything else: + int RelY = FloorC(a_Location.y); + if (RelY < 0) + { + // Never burn under the world + return false; + } + if (RelY >= cChunkDef::Height) + { + // Always burn above the world + return true; + } + cChunk * Chunk = a_Chunk.GetNeighborChunk(FloorC(a_Location.x), FloorC(a_Location.z)); if ((Chunk == nullptr) || (!Chunk->IsValid())) { @@ -1091,14 +1118,13 @@ bool cMonster::WouldBurnAt(Vector3d a_Location, cChunk & a_Chunk) } 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 + (GetWorld()->GetTimeOfDay() < 12000 + 1000) && // Daytime + GetWorld()->IsWeatherSunnyAt(POSX_TOINT, POSZ_TOINT) // Not raining ) { return true; diff --git a/src/Mobs/Monster.h b/src/Mobs/Monster.h index 98b67f343..cc830a058 100644 --- a/src/Mobs/Monster.h +++ b/src/Mobs/Monster.h @@ -79,9 +79,9 @@ public: virtual void EventLosePlayer(void); virtual void CheckEventLostPlayer(void); - virtual void InStateIdle (std::chrono::milliseconds a_Dt); - virtual void InStateChasing (std::chrono::milliseconds a_Dt); - virtual void InStateEscaping(std::chrono::milliseconds a_Dt); + virtual void InStateIdle (std::chrono::milliseconds a_Dt, cChunk & a_Chunk); + virtual void InStateChasing (std::chrono::milliseconds a_Dt, cChunk & a_Chunk); + virtual void InStateEscaping(std::chrono::milliseconds a_Dt, cChunk & a_Chunk); int GetAttackRate() { return static_cast<int>(m_AttackRate); } void SetAttackRate(float a_AttackRate) { m_AttackRate = a_AttackRate; } @@ -193,7 +193,7 @@ protected: /** 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)); + return ((a_PosY > POSY_TOINT)); } /** Move in a straight line to the next waypoint in the path, will jump if needed. */ diff --git a/src/Mobs/Path.cpp b/src/Mobs/Path.cpp index 1c8a3657f..03f0ad31a 100644 --- a/src/Mobs/Path.cpp +++ b/src/Mobs/Path.cpp @@ -7,6 +7,8 @@ #include "../Chunk.h" #define JUMP_G_COST 20 +#define NORMAL_G_COST 10 +#define DIAGONAL_G_COST 14 #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. @@ -30,8 +32,7 @@ bool compareHeuristics::operator()(cPathCell * & a_Cell1, cPathCell * & a_Cell2) 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 + double a_BoundingBoxWidth, double a_BoundingBoxHeight ) : m_StepsLeft(a_MaxSteps), m_IsValid(true), @@ -39,10 +40,8 @@ cPath::cPath( 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; // Treat all mobs width as 1 until physics is improved. This would also require changes to stepOnce to work. + a_BoundingBoxWidth = 1; // Treat all mobs width as 1 until physics is improved. m_BoundingBoxWidth = CeilC(a_BoundingBoxWidth); m_BoundingBoxHeight = CeilC(a_BoundingBoxHeight); @@ -57,7 +56,7 @@ cPath::cPath( m_Destination.y = FloorC(a_EndingPoint.y); m_Destination.z = FloorC(a_EndingPoint.z - HalfWidthInt); - if (!IsWalkable(m_Source)) + if (!IsWalkable(m_Source, m_Source)) { m_Status = ePathFinderStatus::PATH_NOT_FOUND; return; @@ -126,51 +125,6 @@ Vector3i cPath::AcceptNearbyPath() -bool cPath::IsSolid(const Vector3i & a_Location) -{ - ASSERT(m_Chunk != nullptr); - - if (!cChunkDef::IsValidHeight(a_Location.y)) - { - return false; - } - 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_OAK_FENCE_GATE) || - (BlockType == E_BLOCK_NETHER_BRICK_FENCE) || - (BlockType == E_BLOCK_COBBLESTONE_WALL) || - ((BlockType >= E_BLOCK_SPRUCE_FENCE_GATE) && (BlockType <= E_BLOCK_ACACIA_FENCE)) - ) - { - // TODO move this out of IsSolid to a proper place. - 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::StepOnce() { cPathCell * CurrentCell = OpenListPop(); @@ -209,40 +163,47 @@ bool cPath::StepOnce() // Now we start checking adjacent cells. - bool done_east = false, - done_west = false, - done_north = false, - done_south = false; // If true, no need to do more checks in that direction + // If true, no need to do more checks in that direction + bool DoneEast = false, + DoneWest = false, + DoneNorth = false, + DoneSouth = false; + + // If true, we can walk in that direction without changing height + // This is used for deciding if to calculate diagonals + bool WalkableEast = false, + WalkableWest = false, + WalkableNorth = false, + WalkableSouth = false; // If we can jump without hitting the ceiling - if (BodyFitsIn(CurrentCell->m_Location + Vector3i(0, 1, 0))) + if (BodyFitsIn(CurrentCell->m_Location + Vector3i(0, 1, 0), CurrentCell->m_Location)) { + // For ladder climbing + ProcessIfWalkable(CurrentCell->m_Location + Vector3i(0, 1, 0), CurrentCell, JUMP_G_COST); + // Check east-up - if (GetCell(CurrentCell->m_Location + Vector3i(1, 0, 0))->m_IsSolid) + if (ProcessIfWalkable(CurrentCell->m_Location + Vector3i(1, 1, 0), CurrentCell, JUMP_G_COST)) { - ProcessIfWalkable(CurrentCell->m_Location + Vector3i(1, 1, 0), CurrentCell, JUMP_G_COST); - done_east = true; + DoneEast = true; } // Check west-up - if (GetCell(CurrentCell->m_Location + Vector3i(-1, 0, 0))->m_IsSolid) + if (ProcessIfWalkable(CurrentCell->m_Location + Vector3i(-1, 1, 0), CurrentCell, JUMP_G_COST)) { - ProcessIfWalkable(CurrentCell->m_Location + Vector3i(-1, 1, 0), CurrentCell, JUMP_G_COST); - done_west = true; + DoneWest = true; } // Check north-up - if (GetCell(CurrentCell->m_Location + Vector3i(0, 0, -1))->m_IsSolid) + if (ProcessIfWalkable(CurrentCell->m_Location + Vector3i(0, 1, -1), CurrentCell, JUMP_G_COST)) { - ProcessIfWalkable(CurrentCell->m_Location + Vector3i(0, 1, -1), CurrentCell, JUMP_G_COST); - done_north = true; + DoneNorth = true; } // Check south-up - if (GetCell(CurrentCell->m_Location + Vector3i(0, 0, 1))->m_IsSolid) + if (ProcessIfWalkable(CurrentCell->m_Location + Vector3i(0, 1, 1), CurrentCell, JUMP_G_COST)) { - ProcessIfWalkable(CurrentCell->m_Location + Vector3i(0, 1, 1), CurrentCell, JUMP_G_COST); - done_south = true; + DoneSouth = true; } } @@ -251,72 +212,87 @@ bool cPath::StepOnce() // Check North, South, East, West at our own height or below. We are willing to jump up to 3 blocks down. - if (!done_east) + if (!DoneEast) { - for (int i = 0; i >= -3; --i) + for (int y = 0; y >= -3; --y) { - if (ProcessIfWalkable(CurrentCell->m_Location + Vector3i(1, i, 0), CurrentCell, 10)) + if (ProcessIfWalkable(CurrentCell->m_Location + Vector3i(1, y, 0), CurrentCell, NORMAL_G_COST)) { - done_east = true; + DoneEast = true; + if (y == 0) + { + WalkableEast = true; + } break; } } } - if (!done_west) + if (!DoneWest) { - for (int i = 0; i >= -3; --i) + for (int y = 0; y >= -3; --y) { - if (ProcessIfWalkable(CurrentCell->m_Location + Vector3i(-1, i, 0), CurrentCell, 10)) + if (ProcessIfWalkable(CurrentCell->m_Location + Vector3i(-1, y, 0), CurrentCell, NORMAL_G_COST)) { - done_west = true; + DoneWest = true; + if (y == 0) + { + WalkableWest = true; + } break; } } } - if (!done_south) + if (!DoneSouth) { - for (int i = 0; i >= -3; --i) + for (int y = 0; y >= -3; --y) { - if (ProcessIfWalkable(CurrentCell->m_Location + Vector3i(0, i, 1), CurrentCell, 10)) + if (ProcessIfWalkable(CurrentCell->m_Location + Vector3i(0, y, 1), CurrentCell, NORMAL_G_COST)) { - done_west = true; + DoneWest = true; + if (y == 0) + { + WalkableSouth = true; + } break; } } } - if (!done_north) + if (!DoneNorth) { - for (int i = 0; i >= -3; --i) + for (int y = 0; y >= -3; --y) { - if (ProcessIfWalkable(CurrentCell->m_Location + Vector3i(0, i, -1), CurrentCell, 10)) + if (ProcessIfWalkable(CurrentCell->m_Location + Vector3i(0, y, -1), CurrentCell, NORMAL_G_COST)) { - done_north = true; + DoneNorth = true; + if (y == 0) + { + WalkableNorth = true; + } break; } } } - // Check diagonals - - for (int x = -1; x <= 1; x += 2) + if (WalkableNorth && WalkableEast) { - 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). - } - } - } + ProcessIfWalkable(CurrentCell->m_Location + Vector3i(1, 0, -1), CurrentCell, DIAGONAL_G_COST); + } + if (WalkableNorth && WalkableWest) + { + ProcessIfWalkable(CurrentCell->m_Location + Vector3i(-1, 0, -1), CurrentCell, DIAGONAL_G_COST); + } + if (WalkableSouth && WalkableEast) + { + ProcessIfWalkable(CurrentCell->m_Location + Vector3i(1, 0, 1), CurrentCell, DIAGONAL_G_COST); + } + if (WalkableSouth && WalkableWest) + { + ProcessIfWalkable(CurrentCell->m_Location + Vector3i(-1, 0, 1), CurrentCell, DIAGONAL_G_COST); } return false; @@ -349,6 +325,12 @@ void cPath::BuildPath() cPathCell * CurrentCell = GetCell(m_Destination); while (CurrentCell->m_Parent != nullptr) { + // Waypoints are cylinders that start at some particular x, y, z and have infinite height. + // Submerging water waypoints allows swimming mobs to be able to touch them. + if (GetCell(CurrentCell->m_Location + Vector3i(0, -1, 0))->m_BlockType == E_BLOCK_STATIONARY_WATER) + { + CurrentCell->m_Location.y -= 30; + } m_PathPoints.push_back(CurrentCell->m_Location); // Populate the cPath with points. All midpoints are added. Destination is added. Source is excluded. CurrentCell = CurrentCell->m_Parent; } @@ -418,7 +400,7 @@ cPathCell * cPath::OpenListPop() // Popping from the open list also means addin bool cPath::ProcessIfWalkable(const Vector3i & a_Location, cPathCell * a_Parent, int a_Cost) { - if (IsWalkable(a_Location)) + if (IsWalkable(a_Location, a_Parent->m_Location)) { ProcessCell(GetCell(a_Location), a_Parent, a_Cost); return true; @@ -486,13 +468,72 @@ void cPath::ProcessCell(cPathCell * a_Cell, cPathCell * a_Caller, int a_GDelta) +void cPath::FillCellAttributes(cPathCell & a_Cell) +{ + const Vector3i & Location = a_Cell.m_Location; + + ASSERT(m_Chunk != nullptr); + + if (!cChunkDef::IsValidHeight(Location.y)) + { + // Players can't build outside the game height, so it must be air + a_Cell.m_IsSolid = false; + a_Cell.m_IsSpecial = false; + a_Cell.m_BlockType = E_BLOCK_AIR; + return; + } + auto Chunk = m_Chunk->GetNeighborChunk(Location.x, Location.z); + if ((Chunk == nullptr) || !Chunk->IsValid()) + { + m_BadChunkFound = true; + a_Cell.m_IsSolid = true; + a_Cell.m_IsSpecial = false; + a_Cell.m_BlockType = E_BLOCK_AIR; // m_BlockType is never used when m_IsSpecial is false, but it may be used if we implement dijkstra + return; + } + m_Chunk = Chunk; + + BLOCKTYPE BlockType; + NIBBLETYPE BlockMeta; + int RelX = Location.x - m_Chunk->GetPosX() * cChunkDef::Width; + int RelZ = Location.z - m_Chunk->GetPosZ() * cChunkDef::Width; + + m_Chunk->GetBlockTypeMeta(RelX, Location.y, RelZ, BlockType, BlockMeta); + a_Cell.m_BlockType = BlockType; + a_Cell.m_BlockMeta = BlockMeta; + + + if (BlockTypeIsSpecial(BlockType)) + { + a_Cell.m_IsSpecial = true; + a_Cell.m_IsSolid = true; // Specials are solids only from a certain direction. But their m_IsSolid is always true + } + else if ((a_Cell.m_BlockType == E_BLOCK_AIR) && IsBlockFence(GetCell(Location + Vector3i(0, -1, 0))->m_BlockType)) + { + // Air blocks with fences below them are consider Special Solids. That is, they sometimes behave as solids. + a_Cell.m_IsSpecial = true; + a_Cell.m_IsSolid = true; + } + else + { + + a_Cell.m_IsSpecial = false; + a_Cell.m_IsSolid = cBlockInfo::IsSolid(BlockType); + } + +} + + + + + 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); + FillCellAttributes(m_Map[a_Location]); m_Map[a_Location].m_Status = eCellStatus::NOLIST; #ifdef COMPILING_PATHFIND_DEBUGGER #ifdef COMPILING_PATHFIND_DEBUGGER_MARK_UNCHECKED @@ -511,16 +552,16 @@ cPathCell * cPath::GetCell(const Vector3i & a_Location) -bool cPath::IsWalkable(const Vector3i & a_Location) +bool cPath::IsWalkable(const Vector3i & a_Location, const Vector3i & a_Source) { - return (HasSolidBelow(a_Location) && BodyFitsIn(a_Location)); + return (HasSolidBelow(a_Location) && BodyFitsIn(a_Location, a_Source)); } - -bool cPath::BodyFitsIn(const Vector3i & a_Location) +// We need the source because some special blocks are solid only from a certain direction e.g. doors +bool cPath::BodyFitsIn(const Vector3i & a_Location, const Vector3i & a_Source) { int x, y, z; for (y = 0; y < m_BoundingBoxHeight; ++y) @@ -529,9 +570,20 @@ bool cPath::BodyFitsIn(const Vector3i & a_Location) { for (z = 0; z < m_BoundingBoxWidth; ++z) { - if (GetCell(a_Location + Vector3i(x, y, z))->m_IsSolid) + cPathCell * CurrentCell = GetCell(a_Location + Vector3i(x, y, z)); + if (CurrentCell->m_IsSolid) { - return false; + if (CurrentCell->m_IsSpecial) + { + if (SpecialIsSolidFromThisDirection(CurrentCell->m_BlockType, CurrentCell->m_BlockMeta, a_Location - a_Source)) + { + return false; + } + } + else + { + return false; + } } } } @@ -543,6 +595,71 @@ bool cPath::BodyFitsIn(const Vector3i & a_Location) +bool cPath::BlockTypeIsSpecial(BLOCKTYPE a_Type) +{ + if (IsBlockFence(a_Type)) + { + return true; + } + + switch (a_Type) + { + case E_BLOCK_OAK_DOOR: + case E_BLOCK_DARK_OAK_DOOR: + case E_BLOCK_TRAPDOOR: + case E_BLOCK_WATER: + case E_BLOCK_STATIONARY_WATER: + { + return true; + } + default: + { + return false; + } + } +} + + + + + +bool cPath::SpecialIsSolidFromThisDirection(BLOCKTYPE a_Type, NIBBLETYPE a_Meta, const Vector3i & a_Direction) +{ + if (a_Direction == Vector3i(0, 0, 0)) + { + return false; + } + + + + switch (a_Type) + { + // Air is special only when above a fence + case E_BLOCK_AIR: + { + // Treat the air block as solid if the mob is going upward and trying to climb a fence + if (a_Direction.y > 0) + { + return true; + } + else + { + return false; + } + } + + // TODO Fill this with the other specials after physics is fixed + } + + + + return true; +} + + + + + bool cPath::HasSolidBelow(const Vector3i & a_Location) { int x, z; diff --git a/src/Mobs/Path.h b/src/Mobs/Path.h index ac71968bd..f51b7da77 100644 --- a/src/Mobs/Path.h +++ b/src/Mobs/Path.h @@ -7,6 +7,7 @@ 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 @@ -19,22 +20,40 @@ class cPath; //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}; +/** The pathfinder has 3 types of cells (cPathCell). +1 - empty. m_IsSolid is false, m_IsSpecial is false. Air cells are always traversable by A*. +2 - occupied / solid. m_IsSolid is true, m_IsSpecial is false. Air cells are never traversable by A*. +3 - Special. m_IsSolid is true, m_IsSpecial is true. These cells are special: They may either behave as empty +or as occupied / solid, depending on the mob's direction of movement. For instance, an airblock above a fence is a special cell, +because when mobs attempt to travel to it by jumping, it acts as a solid. But when mobs fall and land on top of a fence, +it acts as air. Special cells include: Doors, ladders, trapdoors, water, gates. + +The main function which handles special blocks is SpecialIsSolidFromThisDirection. +This function receives a BlockType, a meta, and a direction of travel, +then it uses those 3 parameters to decide whether the special block should behave as a solid or as air in this +particular direction of travel. + +Currently, only fences and water are handled properly. The function always returns "true" (meaning: treat as occuiped/solid) for +the rest of the blocks. This will be fixed once the physics engine issues are fixed. */ 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. + bool m_IsSolid; // Is the cell an air or a solid? Partial solids are considered solids. If m_IsSpecial is true, this is always true. + bool m_IsSpecial; // The cell is special - it acts as "solid" or "air" depending on direction, e.g. door or top of fence. + BLOCKTYPE m_BlockType; + NIBBLETYPE m_BlockMeta; }; - class compareHeuristics { public: @@ -48,32 +67,22 @@ public: 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. + /** Creates a pathfinder instance. + After calling this, you are expected to call CalculationStep() once per tick or once per several ticks + until it returns something other than CALCULATING. - 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. */ + @param a_MaxSteps The maximum steps before giving up. + @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. */ 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 + double a_BoundingBoxWidth, double a_BoundingBoxHeight ); - /** Creates a dummy path which does nothing except returning false when isValid is called. */ + /** Creates an invalid path which is not usable. You shouldn't call any method other than isValid on such a path. */ cPath(); /** delete default constructors */ @@ -84,9 +93,11 @@ public: cPath & operator=(cPath && a_other) = delete; /** Performs part of the path calculation and returns the appropriate status. + If PATH_FOUND is returned, the path was found, and you can call query the instance for waypoints via GetNextWayPoint, etc. 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 */ + and to make consequent calls to step return PATH_FOUND + If PATH_NOT_FOUND is returned, then no path was found. */ ePathFinderStatus CalculationStep(cChunk & a_Chunk); /** Called after the PathFinder's step returns NEARBY_FOUND. @@ -142,7 +153,6 @@ public: private: /* General */ - bool IsSolid(const Vector3i & a_Location); // Query our hosting world and ask it if there's a solid at a_location. bool StepOnce(); // 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. @@ -152,7 +162,7 @@ private: /* Openlist and closedlist management */ void OpenListAdd(cPathCell * a_Cell); cPathCell * OpenListPop(); - bool ProcessIfWalkable(const Vector3i &a_Location, cPathCell * a_Parent, int a_Cost); + bool ProcessIfWalkable(const Vector3i & a_Location, cPathCell * a_Source, int a_Cost); /* Map management */ void ProcessCell(cPathCell * a_Cell, cPathCell * a_Caller, int a_GDelta); @@ -179,12 +189,15 @@ private: std::vector<Vector3i> m_PathPoints; /* Interfacing with the world */ + void FillCellAttributes(cPathCell & a_Cell); // Query our hosting world and fill the cell with info cChunk * m_Chunk; // Only valid inside Step()! bool m_BadChunkFound; /* High level world queries */ - bool IsWalkable(const Vector3i & a_Location); - bool BodyFitsIn(const Vector3i & a_Location); + bool IsWalkable(const Vector3i & a_Location, const Vector3i & a_Source); + bool BodyFitsIn(const Vector3i & a_Location, const Vector3i & a_Source); + bool BlockTypeIsSpecial(BLOCKTYPE a_Type); + bool SpecialIsSolidFromThisDirection(BLOCKTYPE a_Type, NIBBLETYPE a_Meta, const Vector3i & a_Direction); bool HasSolidBelow(const Vector3i & a_Location); #ifdef COMPILING_PATHFIND_DEBUGGER #include "../path_irrlicht.cpp" diff --git a/src/Mobs/PathFinder.cpp b/src/Mobs/PathFinder.cpp index bc79e2440..4b6e70bbd 100644 --- a/src/Mobs/PathFinder.cpp +++ b/src/Mobs/PathFinder.cpp @@ -32,12 +32,20 @@ ePathFinderStatus cPathFinder::GetNextWayPoint(cChunk & a_Chunk, const Vector3d } // Tweak the destination. If something is wrong with the destination or the chunk, rest for a while. - if (!EnsureProperDestination(a_Chunk)) + if (!(EnsureProperPoint(m_FinalDestination, a_Chunk) && EnsureProperPoint(m_Source, a_Chunk))) { m_NotFoundCooldown = 20; return ePathFinderStatus::PATH_NOT_FOUND; } + /* printf("%d %d %d -> %d %d %d\n", + static_cast<int>(m_Source.x), + static_cast<int>(m_Source.y), + static_cast<int>(m_Source.z), + static_cast<int>(m_FinalDestination.x), + static_cast<int>(m_FinalDestination.y), + static_cast<int>(m_FinalDestination.z)); */ + // Rest is over. Prepare m_Path by calling ResetPathFinding. if (m_NotFoundCooldown == 0) { @@ -83,7 +91,25 @@ ePathFinderStatus cPathFinder::GetNextWayPoint(cChunk & a_Chunk, const Vector3d { m_GiveUpCounter -= 1; - if ((m_GiveUpCounter == 0) || PathIsTooOld()) + if (m_GiveUpCounter == 0) + { + if (a_DontCare) + { + // We're having trouble reaching the next waypoint but the mob + // Doesn't care where to go, just tell him we got there ;) + m_FinalDestination = m_Source; + *a_Destination = m_FinalDestination; + ResetPathFinding(a_Chunk); + return ePathFinderStatus::CALCULATING; + } + else + { + ResetPathFinding(a_Chunk); + return ePathFinderStatus::CALCULATING; + } + } + + if (PathIsTooOld()) { ResetPathFinding(a_Chunk); return ePathFinderStatus::CALCULATING; @@ -153,9 +179,9 @@ void cPathFinder::ResetPathFinding(cChunk &a_Chunk) -bool cPathFinder::EnsureProperDestination(cChunk & a_Chunk) +bool cPathFinder::EnsureProperPoint(Vector3d & a_Vector, cChunk & a_Chunk) { - cChunk * Chunk = a_Chunk.GetNeighborChunk(FloorC(m_FinalDestination.x), FloorC(m_FinalDestination.z)); + cChunk * Chunk = a_Chunk.GetNeighborChunk(FloorC(a_Vector.x), FloorC(a_Vector.z)); BLOCKTYPE BlockType; NIBBLETYPE BlockMeta; @@ -164,14 +190,14 @@ bool cPathFinder::EnsureProperDestination(cChunk & a_Chunk) return false; } - int RelX = FloorC(m_FinalDestination.x) - Chunk->GetPosX() * cChunkDef::Width; - int RelZ = FloorC(m_FinalDestination.z) - Chunk->GetPosZ() * cChunkDef::Width; + int RelX = FloorC(a_Vector.x) - Chunk->GetPosX() * cChunkDef::Width; + int RelZ = FloorC(a_Vector.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)) + Chunk->GetBlockTypeMeta(RelX, FloorC(a_Vector.y) - 1, RelZ, BlockType, BlockMeta); + if (!(IsWaterOrSolid(BlockType))) { bool InTheAir = true; int x, z; @@ -183,18 +209,18 @@ bool cPathFinder::EnsureProperDestination(cChunk & a_Chunk) { continue; } - Chunk = a_Chunk.GetNeighborChunk(FloorC(m_FinalDestination.x+x), FloorC(m_FinalDestination.z+z)); + Chunk = a_Chunk.GetNeighborChunk(FloorC(a_Vector.x+x), FloorC(a_Vector.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)) + RelX = FloorC(a_Vector.x+x) - Chunk->GetPosX() * cChunkDef::Width; + RelZ = FloorC(a_Vector.z+z) - Chunk->GetPosZ() * cChunkDef::Width; + Chunk->GetBlockTypeMeta(RelX, FloorC(a_Vector.y) - 1, RelZ, BlockType, BlockMeta); + if (IsWaterOrSolid((BlockType))) { - m_FinalDestination.x += x; - m_FinalDestination.z += z; + a_Vector.x += x; + a_Vector.z += z; InTheAir = false; goto breakBothLoops; } @@ -205,43 +231,28 @@ bool cPathFinder::EnsureProperDestination(cChunk & a_Chunk) // Go down to the lowest air block. if (InTheAir) { - while (m_FinalDestination.y > 0) + while (a_Vector.y > 0) { - Chunk->GetBlockTypeMeta(RelX, FloorC(m_FinalDestination.y) - 1, RelZ, BlockType, BlockMeta); - if (cBlockInfo::IsSolid(BlockType)) + Chunk->GetBlockTypeMeta(RelX, FloorC(a_Vector.y) - 1, RelZ, BlockType, BlockMeta); + if (IsWaterOrSolid(BlockType)) { break; } - m_FinalDestination.y -= 1; + a_Vector.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) + // If destination in water or solid, go up to the first air block. + while (a_Vector.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 + Chunk->GetBlockTypeMeta(RelX, FloorC(a_Vector.y), RelZ, BlockType, BlockMeta); + if (!IsWaterOrSolid(BlockType)) { break; } - m_FinalDestination.y += 1; + a_Vector.y += 1; } - if (InWater) - { - m_FinalDestination.y -= 1; - } - return true; } @@ -250,6 +261,15 @@ bool cPathFinder::EnsureProperDestination(cChunk & a_Chunk) +bool cPathFinder::IsWaterOrSolid(BLOCKTYPE a_BlockType) +{ + return ((a_BlockType == E_BLOCK_STATIONARY_WATER) || cBlockInfo::IsSolid(a_BlockType)); +} + + + + + bool cPathFinder::PathIsTooOld() const { size_t acceptableDeviation = m_Path->WayPointsLeft() / 2; diff --git a/src/Mobs/PathFinder.h b/src/Mobs/PathFinder.h index 312bb950c..1bdc13a32 100644 --- a/src/Mobs/PathFinder.h +++ b/src/Mobs/PathFinder.h @@ -3,16 +3,11 @@ #include "Path.h" #define WAYPOINT_RADIUS 0.5 -/* -TODO DOXY style -This class wraps cPath. +/** This class wraps cPath. cPath is a "dumb device" - You give it point A and point B, and it returns a full path path. cPathFinder - You give it a constant stream of point A (where you are) and point B (where you want to go), -and it tells you where to go next. It manages path recalculation internally, and is much more efficient that calling cPath every step. - -*/ - +and it tells you where to go next. It manages path recalculation internally, and is much more efficient that calling cPath every step. */ class cPathFinder { @@ -80,17 +75,18 @@ private: /** When a path is not found, this cooldown prevents any recalculations for several ticks. */ int m_NotFoundCooldown; - /** 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 by this call. - 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); + /** Ensures the location is not in the air or under water. + May change the Y coordinate of the given vector. + 1. If a_Vector is the position of water, a_Vector's Y will be modified to point to the first air block above it. + 2. If a_Vector is the position of air, a_Vector's Y will be modified to point to the first airblock below it which has solid or water beneath. */ + bool EnsureProperPoint(Vector3d & a_Vector, cChunk & a_Chunk); /** Resets a pathfinding task, typically because m_FinalDestination has deviated too much from m_DeviationOrigin. */ void ResetPathFinding(cChunk &a_Chunk); + /** Return true the the blocktype is either water or solid */ + bool IsWaterOrSolid(BLOCKTYPE a_BlockType); + /** Is the path too old and should be recalculated? When this is true ResetPathFinding() is called. */ bool PathIsTooOld() const; }; |