diff options
Diffstat (limited to 'src/Mobs')
31 files changed, 468 insertions, 191 deletions
diff --git a/src/Mobs/AggressiveMonster.cpp b/src/Mobs/AggressiveMonster.cpp index d0fb79f6d..7fde1e56b 100644 --- a/src/Mobs/AggressiveMonster.cpp +++ b/src/Mobs/AggressiveMonster.cpp @@ -30,13 +30,12 @@ void cAggressiveMonster::InStateChasing(std::chrono::milliseconds a_Dt) { if (m_Target->IsPlayer()) { - if (((cPlayer *)m_Target)->IsGameModeCreative()) + if (static_cast<cPlayer *>(m_Target)->IsGameModeCreative()) { m_EMState = IDLE; return; } } - MoveToPosition(m_Target->GetPosition()); } } @@ -47,7 +46,7 @@ void cAggressiveMonster::InStateChasing(std::chrono::milliseconds a_Dt) void cAggressiveMonster::EventSeePlayer(cEntity * a_Entity) { - if (!((cPlayer *)a_Entity)->IsGameModeCreative()) + if (!static_cast<cPlayer *>(a_Entity)->IsGameModeCreative()) { super::EventSeePlayer(a_Entity); m_EMState = CHASING; @@ -77,11 +76,14 @@ 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 (TargetIsInRange() && !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) + StopMovingToPosition(); Attack(a_Dt); } } @@ -109,12 +111,12 @@ void cAggressiveMonster::Attack(std::chrono::milliseconds a_Dt) bool cAggressiveMonster::IsMovingToTargetPosition() { // Difference between destination x and target x is negligible (to 10^-12 precision) - if (fabsf((float)m_FinalDestination.x - (float)m_Target->GetPosX()) < std::numeric_limits<float>::epsilon()) + if (fabsf(static_cast<float>(m_FinalDestination.x) - static_cast<float>(m_Target->GetPosX())) < std::numeric_limits<float>::epsilon()) { return false; } // Difference between destination z and target z is negligible (to 10^-12 precision) - else if (fabsf((float)m_FinalDestination.z - (float)m_Target->GetPosZ()) > std::numeric_limits<float>::epsilon()) + else if (fabsf(static_cast<float>(m_FinalDestination.z) - static_cast<float>(m_Target->GetPosZ())) > std::numeric_limits<float>::epsilon()) { return false; } diff --git a/src/Mobs/Blaze.cpp b/src/Mobs/Blaze.cpp index d4ad24166..731da6b18 100644 --- a/src/Mobs/Blaze.cpp +++ b/src/Mobs/Blaze.cpp @@ -23,7 +23,7 @@ void cBlaze::GetDrops(cItems & a_Drops, cEntity * a_Killer) { if ((a_Killer != nullptr) && (a_Killer->IsPlayer() || a_Killer->IsA("cWolf"))) { - int LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); + unsigned int LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); AddRandomDropItem(a_Drops, 0, 1 + LootingLevel, E_ITEM_BLAZE_ROD); } } diff --git a/src/Mobs/CMakeLists.txt b/src/Mobs/CMakeLists.txt index ffbcdf3ea..e99494508 100644 --- a/src/Mobs/CMakeLists.txt +++ b/src/Mobs/CMakeLists.txt @@ -80,6 +80,12 @@ SET (HDRS Zombie.h ZombiePigman.h) +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set_source_files_properties(Monster.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=sign-conversion -Wno-error=conversion -Wno-error=switch -Wno-error=switch-enum -Wno-error=float-equal -Wno-error=old-style-cast") + set_source_files_properties(SnowGolem.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=old-style-cast") + set_source_files_properties(Villager.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=old-style-cast") +endif() + if(NOT MSVC) add_library(Mobs ${SRCS} ${HDRS}) endif() diff --git a/src/Mobs/CaveSpider.cpp b/src/Mobs/CaveSpider.cpp index fa530db82..a8b40f52e 100644 --- a/src/Mobs/CaveSpider.cpp +++ b/src/Mobs/CaveSpider.cpp @@ -34,7 +34,7 @@ void cCaveSpider::Attack(std::chrono::milliseconds a_Dt) if (m_Target->IsPawn()) { // TODO: Easy = no poison, Medium = 7 seconds, Hard = 15 seconds - ((cPawn *) m_Target)->AddEntityEffect(cEntityEffect::effPoison, 7 * 20, 0); + static_cast<cPawn *>(m_Target)->AddEntityEffect(cEntityEffect::effPoison, 7 * 20, 0); } } @@ -44,7 +44,7 @@ void cCaveSpider::Attack(std::chrono::milliseconds a_Dt) void cCaveSpider::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - int LootingLevel = 0; + unsigned int LootingLevel = 0; if (a_Killer != nullptr) { LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); diff --git a/src/Mobs/Chicken.cpp b/src/Mobs/Chicken.cpp index b2b21d4ae..a52d1a2da 100644 --- a/src/Mobs/Chicken.cpp +++ b/src/Mobs/Chicken.cpp @@ -48,7 +48,7 @@ void cChicken::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cChicken::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - int LootingLevel = 0; + unsigned int LootingLevel = 0; if (a_Killer != nullptr) { LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); diff --git a/src/Mobs/Cow.cpp b/src/Mobs/Cow.cpp index 7dc6f3f37..a45010201 100644 --- a/src/Mobs/Cow.cpp +++ b/src/Mobs/Cow.cpp @@ -21,7 +21,7 @@ cCow::cCow(void) : void cCow::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - int LootingLevel = 0; + unsigned int LootingLevel = 0; if (a_Killer != nullptr) { LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); diff --git a/src/Mobs/Creeper.cpp b/src/Mobs/Creeper.cpp index 41796402f..30bd41f13 100644 --- a/src/Mobs/Creeper.cpp +++ b/src/Mobs/Creeper.cpp @@ -27,7 +27,7 @@ void cCreeper::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { super::Tick(a_Dt, a_Chunk); - if (!ReachedFinalDestination() && !m_BurnedWithFlintAndSteel) + if (!TargetIsInRange() && !m_BurnedWithFlintAndSteel) { m_ExplodingTimer = 0; m_bIsBlowing = false; @@ -60,7 +60,7 @@ void cCreeper::GetDrops(cItems & a_Drops, cEntity * a_Killer) return; } - int LootingLevel = 0; + unsigned int LootingLevel = 0; if (a_Killer != nullptr) { LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); @@ -124,7 +124,7 @@ void cCreeper::Attack(std::chrono::milliseconds a_Dt) if (!m_bIsBlowing) { - m_World->BroadcastSoundEffect("game.tnt.primed", GetPosX(), GetPosY(), GetPosZ(), 1.f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); + m_World->BroadcastSoundEffect("game.tnt.primed", GetPosX(), GetPosY(), GetPosZ(), 1.f, (0.75f + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64)); m_bIsBlowing = true; m_World->BroadcastEntityMetadata(*this); } @@ -142,7 +142,7 @@ void cCreeper::OnRightClicked(cPlayer & a_Player) { a_Player.UseEquippedItem(); } - m_World->BroadcastSoundEffect("game.tnt.primed", GetPosX(), GetPosY(), GetPosZ(), 1.f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); + m_World->BroadcastSoundEffect("game.tnt.primed", GetPosX(), GetPosY(), GetPosZ(), 1.f, (0.75f + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64)); m_bIsBlowing = true; m_World->BroadcastEntityMetadata(*this); m_BurnedWithFlintAndSteel = true; diff --git a/src/Mobs/Enderman.cpp b/src/Mobs/Enderman.cpp index 42c33884a..30bf82067 100644 --- a/src/Mobs/Enderman.cpp +++ b/src/Mobs/Enderman.cpp @@ -55,7 +55,7 @@ public: } cTracer LineOfSight(a_Player->GetWorld()); - if (LineOfSight.Trace(m_EndermanPos, Direction, (int)Direction.Length())) + if (LineOfSight.Trace(m_EndermanPos, Direction, static_cast<int>(Direction.Length()))) { // No direct line of sight return false; @@ -91,7 +91,7 @@ cEnderman::cEnderman(void) : void cEnderman::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - int LootingLevel = 0; + unsigned int LootingLevel = 0; if (a_Killer != nullptr) { LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); diff --git a/src/Mobs/Ghast.cpp b/src/Mobs/Ghast.cpp index d17047ab7..15bbe484b 100644 --- a/src/Mobs/Ghast.cpp +++ b/src/Mobs/Ghast.cpp @@ -19,7 +19,7 @@ cGhast::cGhast(void) : void cGhast::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - int LootingLevel = 0; + unsigned int LootingLevel = 0; if (a_Killer != nullptr) { LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); diff --git a/src/Mobs/Guardian.cpp b/src/Mobs/Guardian.cpp index 5eb30785b..1429e2b13 100644 --- a/src/Mobs/Guardian.cpp +++ b/src/Mobs/Guardian.cpp @@ -21,7 +21,7 @@ cGuardian::cGuardian(void) : void cGuardian::GetDrops(cItems & a_Drops, cEntity * a_Killer) { // Drops 0-3 Ink Sacs - int LootingLevel = 0; + unsigned int LootingLevel = 0; if (a_Killer != nullptr) { LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); @@ -37,19 +37,20 @@ void cGuardian::GetDrops(cItems & a_Drops, cEntity * a_Killer) void cGuardian::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { + m_IsFollowingPath = false; // Disable Pathfinding until it's fixed. TODO + // We must first process current location, and only then tick, otherwise we risk processing a location in a chunk // that is not where the entity currently resides (FS #411) - Vector3d Pos = GetPosition(); // TODO: Not a real behavior, but cool :D - int RelY = (int)floor(Pos.y); + int RelY = FloorC(Pos.y); if ((RelY < 0) || (RelY >= cChunkDef::Height)) { return; } - int RelX = (int)floor(Pos.x) - a_Chunk.GetPosX() * cChunkDef::Width; - int RelZ = (int)floor(Pos.z) - a_Chunk.GetPosZ() * cChunkDef::Width; + int RelX = FloorC(Pos.x) - a_Chunk.GetPosX() * cChunkDef::Width; + int RelZ = FloorC(Pos.z) - a_Chunk.GetPosZ() * cChunkDef::Width; BLOCKTYPE BlockType; if (a_Chunk.UnboundedRelGetBlockType(RelX, RelY, RelZ, BlockType) && !IsBlockWater(BlockType) && !IsOnFire()) { diff --git a/src/Mobs/Horse.cpp b/src/Mobs/Horse.cpp index 5b4c78bfc..ce3bd65eb 100644 --- a/src/Mobs/Horse.cpp +++ b/src/Mobs/Horse.cpp @@ -55,10 +55,10 @@ void cHorse::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { if (m_World->GetTickRandomNumber(50) == 25) { - m_World->BroadcastSoundParticleEffect(2000, (int)GetPosX(), (int)GetPosY(), (int)GetPosZ(), 0); - m_World->BroadcastSoundParticleEffect(2000, (int)GetPosX(), (int)GetPosY(), (int)GetPosZ(), 2); - m_World->BroadcastSoundParticleEffect(2000, (int)GetPosX(), (int)GetPosY(), (int)GetPosZ(), 6); - m_World->BroadcastSoundParticleEffect(2000, (int)GetPosX(), (int)GetPosY(), (int)GetPosZ(), 8); + m_World->BroadcastSoundParticleEffect(2000, FloorC(GetPosX()), FloorC(GetPosY()), FloorC(GetPosZ()), 0); + m_World->BroadcastSoundParticleEffect(2000, FloorC(GetPosX()), FloorC(GetPosY()), FloorC(GetPosZ()), 2); + m_World->BroadcastSoundParticleEffect(2000, FloorC(GetPosX()), FloorC(GetPosY()), FloorC(GetPosZ()), 6); + m_World->BroadcastSoundParticleEffect(2000, FloorC(GetPosX()), FloorC(GetPosY()), FloorC(GetPosZ()), 8); m_Attachee->Detach(); m_bIsRearing = true; @@ -140,7 +140,7 @@ void cHorse::OnRightClicked(cPlayer & a_Player) void cHorse::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - int LootingLevel = 0; + unsigned int LootingLevel = 0; if (a_Killer != nullptr) { LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); 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/Monster.cpp b/src/Mobs/Monster.cpp index f3f8c6b24..4befe307d 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -89,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) @@ -101,6 +101,7 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A , m_TicksSinceLastDamaged(100) , m_BurnsInDaylight(false) , m_RelativeWalkSpeed(1) + , m_Age(1) { if (!a_ConfigName.empty()) { @@ -147,10 +148,15 @@ bool cMonster::TickPathFinding(cChunk & a_Chunk) (Recalculate lots when close, calculate rarely when far) */ if ( ((GetPosition() - m_PathFinderDestination).Length() < 0.25) || - ((m_TicksSinceLastPathReset > 10) && (m_TicksSinceLastPathReset > (0.15 * (m_FinalDestination - GetPosition()).SqrLength()))) + ((m_TicksSinceLastPathReset > 10) && (m_TicksSinceLastPathReset > (0.4 * (m_FinalDestination - GetPosition()).SqrLength()))) ) { - ResetPathFinding(); + /* 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(); + } } } @@ -161,15 +167,25 @@ bool cMonster::TickPathFinding(cChunk & 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().Floor(), m_PathFinderDestination.Floor(), 20); + m_Path = new cPath(a_Chunk, GetPosition(), m_PathFinderDestination, 20, GetWidth(), GetHeight()); } switch (m_Path->Step(a_Chunk)) { + case ePathFinderStatus::NEARBY_FOUND: + { + m_NoPathToTarget = true; + m_PathFinderDestination = m_Path->AcceptNearbyPath(); + break; + } + case ePathFinderStatus::PATH_NOT_FOUND: { - StopMovingToPosition(); // Give up pathfinding to that destination. + 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: @@ -179,15 +195,30 @@ bool cMonster::TickPathFinding(cChunk & a_Chunk) } case ePathFinderStatus::PATH_FOUND: { - if (--m_GiveUpCounter == 0) + if (m_NoMoreWayPoints || (--m_GiveUpCounter == 0)) { - ResetPathFinding(); // Try to calculate a path again. + if (m_EMState == ATTACKING) + { + ResetPathFinding(); // Try to calculate a path again. + // This results in mobs hanging around an unreachable target (player). + } + else + { + StopMovingToPosition(); // Find a different place to go to. + } return false; } - else if (!m_Path->IsLastPoint() && (m_Path->IsFirstPoint() || ReachedNextWaypoint())) // Have we arrived at the next cell, as denoted by m_NextWayPointPosition? + 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_NextWayPointPosition = Vector3d(0.5, 0, 0.5) + m_Path->GetNextPoint(); - m_GiveUpCounter = 40; // Give up after 40 ticks (2 seconds) if failed to reach m_NextWayPointPosition. + m_NoMoreWayPoints = true; } return true; } @@ -269,21 +300,59 @@ bool cMonster::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; - // If destination in the air, go down to the lowest air block. - while (m_FinalDestination.y > 0) + // 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(m_FinalDestination.y) - 1, RelZ, BlockType, BlockMeta); - if (cBlockInfo::IsSolid(BlockType)) + bool InTheAir = true; + int x, z; + for (z = -1; z <= 1; ++z) { - break; + 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; + } + } } - m_FinalDestination.y -= 1; - } + 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. @@ -331,6 +400,7 @@ void cMonster::MoveToPosition(const Vector3d & a_Position) void cMonster::StopMovingToPosition() { m_IsFollowingPath = false; + ResetPathFinding(); } @@ -447,21 +517,36 @@ void cMonster::SetPitchAndYawFromDestination() } } - Vector3d Distance = FinalDestination - GetPosition(); + + Vector3d BodyDistance; + if (!m_IsFollowingPath && (m_Target != nullptr)) { - double Rotation, Pitch; - Distance.Normalize(); - VectorToEuler(Distance.x, Distance.y, Distance.z, Rotation, Pitch); - SetHeadYaw(Rotation); - SetPitch(-Pitch); + BodyDistance = m_Target->GetPosition() - GetPosition(); } + else + { + BodyDistance = m_NextWayPointPosition - GetPosition(); + } + double BodyRotation, BodyPitch; + BodyDistance.Normalize(); + VectorToEuler(BodyDistance.x, BodyDistance.y, BodyDistance.z, BodyRotation, BodyPitch); + SetYaw(BodyRotation); + Vector3d Distance = FinalDestination - GetPosition(); { - Vector3d BodyDistance = m_NextWayPointPosition - GetPosition(); - double Rotation, Pitch; - BodyDistance.Normalize(); - VectorToEuler(BodyDistance.x, BodyDistance.y, BodyDistance.z, Rotation, Pitch); - SetYaw(Rotation); + double HeadRotation, HeadPitch; + Distance.Normalize(); + VectorToEuler(Distance.x, Distance.y, Distance.z, HeadRotation, HeadPitch); + if (std::abs(BodyRotation - HeadRotation) < 90) + { + 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. + { + SetHeadYaw(BodyRotation); + SetPitch(-BodyPitch); + } } } @@ -989,9 +1074,9 @@ cMonster * cMonster::NewMonsterFromType(eMonsterType a_MobType) case mtHorse: { // Horses take a type (species), a colour, and a style (dots, stripes, etc.) - int HorseType = Random.NextInt(7); - int HorseColor = Random.NextInt(6); - int HorseStyle = Random.NextInt(6); + int HorseType = Random.NextInt(8); + int HorseColor = Random.NextInt(7); + int HorseStyle = Random.NextInt(5); int HorseTameTimes = Random.NextInt(6) + 1; if ((HorseType == 5) || (HorseType == 6) || (HorseType == 7)) @@ -1070,10 +1155,10 @@ void cMonster::AddRandomUncommonDropItem(cItems & a_Drops, float a_Chance, short -void cMonster::AddRandomRareDropItem(cItems & a_Drops, cItems & a_Items, short a_LootingLevel) +void cMonster::AddRandomRareDropItem(cItems & a_Drops, cItems & a_Items, unsigned int a_LootingLevel) { MTRand r1; - int Count = r1.randInt() % 200; + unsigned int Count = r1.randInt() % 200; if (Count < (5 + a_LootingLevel)) { int Rare = r1.randInt() % a_Items.Size(); @@ -1085,7 +1170,7 @@ void cMonster::AddRandomRareDropItem(cItems & a_Drops, cItems & a_Items, short a -void cMonster::AddRandomArmorDropItem(cItems & a_Drops, short a_LootingLevel) +void cMonster::AddRandomArmorDropItem(cItems & a_Drops, unsigned int a_LootingLevel) { MTRand r1; if (r1.randInt() % 200 < ((m_DropChanceHelmet * 200) + (a_LootingLevel * 2))) @@ -1125,7 +1210,7 @@ void cMonster::AddRandomArmorDropItem(cItems & a_Drops, short a_LootingLevel) -void cMonster::AddRandomWeaponDropItem(cItems & a_Drops, short a_LootingLevel) +void cMonster::AddRandomWeaponDropItem(cItems & a_Drops, unsigned int a_LootingLevel) { MTRand r1; if (r1.randInt() % 200 < ((m_DropChanceWeapon * 200) + (a_LootingLevel * 2))) diff --git a/src/Mobs/Monster.h b/src/Mobs/Monster.h index 5d20ba810..2832a1570 100644 --- a/src/Mobs/Monster.h +++ b/src/Mobs/Monster.h @@ -112,10 +112,14 @@ public: void SetRelativeWalkSpeed(double a_WalkSpeed) { m_RelativeWalkSpeed = a_WalkSpeed; } // tolua_export // Overridables to handle ageable mobs - virtual bool IsBaby (void) const { return false; } virtual bool IsTame (void) const { return false; } virtual bool IsSitting (void) const { return false; } + bool IsBaby (void) const { return m_Age < 0; } + char GetAge (void) const { return m_Age; } + void SetAge(char a_Age) { m_Age = a_Age; } + + // tolua_begin /** Returns true if the monster has a custom name. */ @@ -180,19 +184,29 @@ protected: /** 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 If current Y is solid, goes up to find first nonsolid block, and returns that. If no suitable position is found, returns cChunkDef::Height. */ int FindFirstNonAirBlockPosition(double a_PosX, double a_PosZ); - /** Returns if the ultimate, final destination has been reached */ - bool ReachedFinalDestination(void) { return ((m_FinalDestination - GetPosition()).SqrLength() < (m_AttackRange * m_AttackRange)); } + /** Returns if the ultimate, final destination has been reached. */ + bool ReachedFinalDestination(void) { return ((m_FinalDestination - GetPosition()).Length() < GetWidth()/2); } + + /** Returns whether or not the target is close enough for attack. */ + bool TargetIsInRange(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 */ + /** 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)); @@ -260,6 +274,8 @@ protected: bool m_BurnsInDaylight; double m_RelativeWalkSpeed; + char m_Age; + /** Adds a random number of a_Item between a_Min and a_Max to itemdrops a_Drops*/ void AddRandomDropItem(cItems & a_Drops, unsigned int a_Min, unsigned int a_Max, short a_Item, short a_ItemHealth = 0); @@ -267,13 +283,13 @@ protected: void AddRandomUncommonDropItem(cItems & a_Drops, float a_Chance, short a_Item, short a_ItemHealth = 0); /** Adds one rare item out of the list of rare items a_Items modified by the looting level a_LootingLevel(I-III or custom) to the itemdrop a_Drops*/ - void AddRandomRareDropItem(cItems & a_Drops, cItems & a_Items, short a_LootingLevel); + void AddRandomRareDropItem(cItems & a_Drops, cItems & a_Items, unsigned int a_LootingLevel); /** Adds armor that is equipped with the chance saved in m_DropChance[...] (this will be greter than 1 if piccked up or 0.085 + (0.01 per LootingLevel) if born with) to the drop*/ - void AddRandomArmorDropItem(cItems & a_Drops, short a_LootingLevel); + void AddRandomArmorDropItem(cItems & a_Drops, unsigned int a_LootingLevel); /** Adds weapon that is equipped with the chance saved in m_DropChance[...] (this will be greter than 1 if piccked up or 0.085 + (0.01 per LootingLevel) if born with) to the drop*/ - void AddRandomWeaponDropItem(cItems & a_Drops, short a_LootingLevel); + void AddRandomWeaponDropItem(cItems & a_Drops, unsigned int a_LootingLevel); } ; // tolua_export diff --git a/src/Mobs/Mooshroom.cpp b/src/Mobs/Mooshroom.cpp index ec533cfca..3b2fbad57 100644 --- a/src/Mobs/Mooshroom.cpp +++ b/src/Mobs/Mooshroom.cpp @@ -24,7 +24,7 @@ cMooshroom::cMooshroom(void) : void cMooshroom::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - int LootingLevel = 0; + unsigned int LootingLevel = 0; if (a_Killer != nullptr) { LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); diff --git a/src/Mobs/PassiveAggressiveMonster.cpp b/src/Mobs/PassiveAggressiveMonster.cpp index cb8650cb9..f5577f71f 100644 --- a/src/Mobs/PassiveAggressiveMonster.cpp +++ b/src/Mobs/PassiveAggressiveMonster.cpp @@ -28,7 +28,7 @@ bool cPassiveAggressiveMonster::DoTakeDamage(TakeDamageInfo & a_TDI) if ((m_Target != nullptr) && (m_Target->IsPlayer())) { - if (!((cPlayer *)m_Target)->IsGameModeCreative()) + if (!static_cast<cPlayer *>(m_Target)->IsGameModeCreative()) { m_EMState = CHASING; } diff --git a/src/Mobs/PassiveMonster.cpp b/src/Mobs/PassiveMonster.cpp index 012ca9949..c220a7128 100644 --- a/src/Mobs/PassiveMonster.cpp +++ b/src/Mobs/PassiveMonster.cpp @@ -48,7 +48,7 @@ void cPassiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { return; } - cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), (float)m_SightDistance); + cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), static_cast<float>(m_SightDistance)); if (a_Closest_Player != nullptr) { if (a_Closest_Player->GetEquippedItem().IsEqual(FollowedItem)) diff --git a/src/Mobs/Path.cpp b/src/Mobs/Path.cpp index dd306af13..e987381f7 100644 --- a/src/Mobs/Path.cpp +++ b/src/Mobs/Path.cpp @@ -6,20 +6,15 @@ #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 5 // Higher means more CPU load but faster path calculations. +#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. -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. -}; + + @@ -37,24 +32,40 @@ bool compareHeuristics::operator()(cPathCell * & a_Cell1, cPathCell * & a_Cell2) /* cPath implementation */ cPath::cPath( cChunk & a_Chunk, - const Vector3i & a_StartingPoint, const Vector3i & a_EndingPoint, int a_MaxSteps, + const Vector3d & a_StartingPoint, const Vector3d & a_EndingPoint, int a_MaxSteps, double a_BoundingBoxWidth, double a_BoundingBoxHeight, int a_MaxUp, int a_MaxDown ) : - m_Destination(a_EndingPoint.Floor()), - m_Source(a_StartingPoint.Floor()), + 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_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 = CeilC(a_BoundingBoxWidth); + m_BoundingBoxHeight = CeilC(a_BoundingBoxHeight); + m_HalfWidth = a_BoundingBoxWidth / 2; + + int HalfWidthInt = FloorC(a_BoundingBoxWidth / 2); + m_Source.x = FloorC(a_StartingPoint.x - HalfWidthInt); + m_Source.y = FloorC(a_StartingPoint.y); + m_Source.z = FloorC(a_StartingPoint.z - HalfWidthInt); + + m_Destination.x = FloorC(a_EndingPoint.x - HalfWidthInt); + m_Destination.y = FloorC(a_EndingPoint.y); + m_Destination.z = FloorC(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; @@ -81,15 +92,20 @@ cPath::~cPath() ePathFinderStatus cPath::Step(cChunk & a_Chunk) { m_Chunk = &a_Chunk; - if (m_Status != ePathFinderStatus::CALCULATING) { return m_Status; } - if (m_StepsLeft == 0) + if (m_BadChunkFound) { FinishCalculation(ePathFinderStatus::PATH_NOT_FOUND); + return m_Status; + } + + if (m_StepsLeft == 0) + { + AttemptToFindAlternative(); } else { @@ -102,9 +118,9 @@ ePathFinderStatus cPath::Step(cChunk & a_Chunk) break; // if we're here, m_Status must have changed either to PATH_FOUND or PATH_NOT_FOUND. } } - } - m_Chunk = nullptr; + m_Chunk = nullptr; + } return m_Status; } @@ -112,6 +128,17 @@ ePathFinderStatus cPath::Step(cChunk & a_Chunk) +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); @@ -119,6 +146,7 @@ bool cPath::IsSolid(const Vector3i & a_Location) auto Chunk = m_Chunk->GetNeighborChunk(a_Location.x, a_Location.z); if ((Chunk == nullptr) || !Chunk->IsValid()) { + m_BadChunkFound = true; return true; } m_Chunk = Chunk; @@ -129,13 +157,19 @@ bool cPath::IsSolid(const Vector3i & a_Location) 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)) + if ( + (BlockType == E_BLOCK_FENCE) || + (BlockType == E_BLOCK_FENCE_GATE) || + (BlockType == E_BLOCK_NETHER_BRICK_FENCE) || + ((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; // Mobs will always think that the fence is 2 blocks high and therefore won't jump over. + GetCell(a_Location + Vector3i(0, -1, 0))->m_IsSolid = true; } return cBlockInfo::IsSolid(BlockType); @@ -149,50 +183,81 @@ bool cPath::Step_Internal() { cPathCell * CurrentCell = OpenListPop(); - // Path not reachable, open list exauhsted. + // Path not reachable. if (CurrentCell == nullptr) { - FinishCalculation(ePathFinderStatus::PATH_NOT_FOUND); - ASSERT(m_Status == ePathFinderStatus::PATH_NOT_FOUND); + AttemptToFindAlternative(); return true; } // Path found. - if ( - (CurrentCell->m_Location == m_Destination + Vector3i(0, 0, 1)) || - (CurrentCell->m_Location == m_Destination + Vector3i(1, 0, 0)) || - (CurrentCell->m_Location == m_Destination + Vector3i(-1, 0, 0)) || - (CurrentCell->m_Location == m_Destination + Vector3i(0, 0, -1)) || - (CurrentCell->m_Location == m_Destination + Vector3i(0, -1, 0)) - ) + if (CurrentCell->m_Location == m_Destination) { - do - { - m_PathPoints.push_back(CurrentCell->m_Location); // Populate the cPath with points. - CurrentCell = CurrentCell->m_Parent; - } while (CurrentCell != nullptr); - + BuildPath(); FinishCalculation(ePathFinderStatus::PATH_FOUND); return true; } - // Calculation not finished yet, process a currentCell by inspecting all neighbors. + // 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 all 3 different heights. - int i; - for (i = -1; i <= 1; ++i) + // 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. + // x = -1: west, x = 1: east. + for (int x = -1; x <= 1; x += 2) { - ProcessIfWalkable(CurrentCell->m_Location + Vector3i(1, i, 0), CurrentCell, 10); - ProcessIfWalkable(CurrentCell->m_Location + Vector3i(-1, i, 0), CurrentCell, 10); - ProcessIfWalkable(CurrentCell->m_Location + Vector3i(0, i, 1), CurrentCell, 10); - ProcessIfWalkable(CurrentCell->m_Location + Vector3i(0, i, -1), CurrentCell, 10); + if (GetCell(CurrentCell->m_Location + Vector3i(x, 0, 0))->m_IsSolid) // If there's a solid our east / west. + { + if (!GetCell(CurrentCell->m_Location + Vector3i(0, 1, 0))->m_IsSolid) // If there isn't a solid above. + { + ProcessIfWalkable(CurrentCell->m_Location + Vector3i(x, 1, 0), CurrentCell, JUMP_G_COST); // Check east-up / west-up. + } + } + else + { + ProcessIfWalkable(CurrentCell->m_Location + Vector3i(x, -1, 0), CurrentCell, 14); // Else check east-down / 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 north / south. + { + if (!GetCell(CurrentCell->m_Location + Vector3i(0, 1, 0))->m_IsSolid) // If there isn't a solid above. + { + ProcessIfWalkable(CurrentCell->m_Location + Vector3i(0, 1, z), CurrentCell, JUMP_G_COST); // Check north-up / south-up. + } + } + else + { + ProcessIfWalkable(CurrentCell->m_Location + Vector3i(0, -1, z), CurrentCell, 14); // Else check north-down / south-down. + } } - // Check diagonals on mob's height only. - int x, z; - for (x = -1; x <= 1; x += 2) + // 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 (z = -1; z <= 1; z += 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) @@ -213,6 +278,38 @@ bool cPath::Step_Internal() +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(); @@ -225,6 +322,10 @@ void cPath::FinishCalculation() void cPath::FinishCalculation(ePathFinderStatus a_NewStatus) { + if (m_BadChunkFound) + { + a_NewStatus = ePathFinderStatus::PATH_NOT_FOUND; + } m_Status = a_NewStatus; FinishCalculation(); } @@ -250,7 +351,7 @@ cPathCell * cPath::OpenListPop() // Popping from the open list also means addin { 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. + 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(); @@ -269,7 +370,53 @@ si::setBlock((Ret)->m_Location.x, (Ret)->m_Location.y, (Ret)->m_Location.z, debu void cPath::ProcessIfWalkable(const Vector3i & a_Location, cPathCell * a_Parent, int a_Cost) { cPathCell * cell = GetCell(a_Location); - if (!cell->m_IsSolid && GetCell(a_Location + Vector3i(0, -1, 0))->m_IsSolid && !GetCell(a_Location + Vector3i(0, 1, 0))->m_IsSolid) + 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); } @@ -338,23 +485,20 @@ void cPath::ProcessCell(cPathCell * a_Cell, cPathCell * a_Caller, int a_GDelta) cPathCell * cPath::GetCell(const Vector3i & 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] = UniquePtr<cPathCell>(Cell); - Cell->m_IsSolid = IsSolid(a_Location); - Cell->m_Status = eCellStatus::NOLIST; + 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 Cell; + return &m_Map[a_Location]; } else { - return m_Map[a_Location].get(); + return &m_Map[a_Location]; } } diff --git a/src/Mobs/Path.h b/src/Mobs/Path.h index 008722d29..d4ad066e3 100644 --- a/src/Mobs/Path.h +++ b/src/Mobs/Path.h @@ -1,16 +1,13 @@ #pragma once -/* Wanna use the pathfinder? Put this in your header file: - -// Fwd: cPath +/* +// Needed Fwds: cPath enum class ePathFinderStatus; class cPath; - -Put this in your .cpp: -#include "...Path.h" */ +#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. */ @@ -23,14 +20,31 @@ Put this in your .cpp: class cChunk; /* Various little structs and classes */ -enum class ePathFinderStatus {CALCULATING, PATH_FOUND, PATH_NOT_FOUND}; -struct cPathCell; // Defined inside Path.cpp +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: @@ -54,23 +68,32 @@ public: @param a_MaxSteps The maximum steps before giving up. */ cPath( cChunk & a_Chunk, - const Vector3i & a_StartingPoint, const Vector3i & a_EndingPoint, int a_MaxSteps, - double a_BoundingBoxWidth = 1, double a_BoundingBoxHeight = 2, + 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 true if the path computation has finished. */ + /** 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 Vector3i GetNextPoint() + inline Vector3d GetNextPoint() { ASSERT(m_Status == ePathFinderStatus::PATH_FOUND); - return m_PathPoints[m_PathPoints.size() - 1 - (++m_CurrentPoint)]; + 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() @@ -84,34 +107,23 @@ public: return (m_CurrentPoint == 0); } /** Get the point at a_index. Remark: Internally, the indexes are reversed. */ - inline Vector3i GetPoint(size_t a_index) + inline Vector3d GetPoint(size_t a_index) { ASSERT(m_Status == ePathFinderStatus::PATH_FOUND); ASSERT(a_index < m_PathPoints.size()); - return m_PathPoints[m_PathPoints.size() - 1 - a_index]; + 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() + inline size_t GetPointCount() { - ASSERT(m_Status == ePathFinderStatus::PATH_FOUND); + 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 */ @@ -119,6 +131,8 @@ private: 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); @@ -131,10 +145,15 @@ private: /* Pathfinding fields */ std::priority_queue<cPathCell *, std::vector<cPathCell *>, compareHeuristics> m_OpenList; - std::unordered_map<Vector3i, UniquePtr<cPathCell>, VectorHasher> m_Map; + std::unordered_map<Vector3i, cPathCell, VectorHasher<int>> 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; @@ -145,6 +164,7 @@ private: /* 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 56d6abfd5..efa779ac2 100644 --- a/src/Mobs/Pig.cpp +++ b/src/Mobs/Pig.cpp @@ -21,7 +21,7 @@ cPig::cPig(void) : void cPig::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - int LootingLevel = 0; + unsigned int LootingLevel = 0; if (a_Killer != nullptr) { LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); diff --git a/src/Mobs/Rabbit.cpp b/src/Mobs/Rabbit.cpp index cf49d2744..c7f3d58f0 100644 --- a/src/Mobs/Rabbit.cpp +++ b/src/Mobs/Rabbit.cpp @@ -20,7 +20,7 @@ cRabbit::cRabbit(void) : void cRabbit::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - int LootingLevel = 0; + unsigned int LootingLevel = 0; if (a_Killer != nullptr) { LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); diff --git a/src/Mobs/Sheep.cpp b/src/Mobs/Sheep.cpp index ec24f167e..dcfef8135 100644 --- a/src/Mobs/Sheep.cpp +++ b/src/Mobs/Sheep.cpp @@ -37,10 +37,10 @@ void cSheep::GetDrops(cItems & a_Drops, cEntity * a_Killer) { if (!m_IsSheared) { - a_Drops.push_back(cItem(E_BLOCK_WOOL, 1, m_WoolColor)); + a_Drops.push_back(cItem(E_BLOCK_WOOL, 1, static_cast<short>(m_WoolColor))); } - int LootingLevel = 0; + unsigned int LootingLevel = 0; if (a_Killer != nullptr) { LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); @@ -65,7 +65,7 @@ void cSheep::OnRightClicked(cPlayer & a_Player) cItems Drops; int NumDrops = m_World->GetTickRandomNumber(2) + 1; - Drops.push_back(cItem(E_BLOCK_WOOL, NumDrops, m_WoolColor)); + Drops.push_back(cItem(E_BLOCK_WOOL, static_cast<char>(NumDrops), static_cast<short>(m_WoolColor))); m_World->SpawnItemPickups(Drops, GetPosX(), GetPosY(), GetPosZ(), 10); m_World->BroadcastSoundEffect("mob.sheep.shear", GetPosX(), GetPosY(), GetPosZ(), 1.0f, 1.0f); } diff --git a/src/Mobs/Skeleton.cpp b/src/Mobs/Skeleton.cpp index f99404669..767e5b95c 100644 --- a/src/Mobs/Skeleton.cpp +++ b/src/Mobs/Skeleton.cpp @@ -22,7 +22,7 @@ cSkeleton::cSkeleton(bool IsWither) : void cSkeleton::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - int LootingLevel = 0; + unsigned int LootingLevel = 0; if (a_Killer != nullptr) { LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); @@ -50,12 +50,13 @@ void cSkeleton::GetDrops(cItems & a_Drops, cEntity * a_Killer) void cSkeleton::Attack(std::chrono::milliseconds a_Dt) { + cFastRandom Random; m_AttackInterval += (static_cast<float>(a_Dt.count()) / 1000) * m_AttackRate; if ((m_Target != nullptr) && (m_AttackInterval > 3.0)) { - // Setting this higher gives us more wiggle room for attackrate - Vector3d Speed = GetLookVector() * 20; - Speed.y = Speed.y + 1; + Vector3d Inaccuracy = Vector3d(Random.NextFloat(0.5) - 0.25, Random.NextFloat(0.5) - 0.25, Random.NextFloat(0.5) - 0.25); + Vector3d Speed = (m_Target->GetPosition() + Inaccuracy - GetPosition()) * 5; + Speed.y = Speed.y - 1 + Random.NextInt(3); cArrowEntity * Arrow = new cArrowEntity(this, GetPosX(), GetPosY() + 1, GetPosZ(), Speed); if (Arrow == nullptr) { diff --git a/src/Mobs/Slime.cpp b/src/Mobs/Slime.cpp index 7fc4821d8..4988d1082 100644 --- a/src/Mobs/Slime.cpp +++ b/src/Mobs/Slime.cpp @@ -29,7 +29,7 @@ cSlime::cSlime(int a_Size) : void cSlime::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - int LootingLevel = 0; + unsigned int LootingLevel = 0; if (a_Killer != nullptr) { LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); diff --git a/src/Mobs/Spider.cpp b/src/Mobs/Spider.cpp index deb263c41..184a1d912 100644 --- a/src/Mobs/Spider.cpp +++ b/src/Mobs/Spider.cpp @@ -18,7 +18,7 @@ cSpider::cSpider(void) : void cSpider::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - int LootingLevel = 0; + unsigned int LootingLevel = 0; if (a_Killer != nullptr) { LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); diff --git a/src/Mobs/Squid.cpp b/src/Mobs/Squid.cpp index 3c508b65f..30fbfa1ff 100644 --- a/src/Mobs/Squid.cpp +++ b/src/Mobs/Squid.cpp @@ -21,7 +21,7 @@ cSquid::cSquid(void) : void cSquid::GetDrops(cItems & a_Drops, cEntity * a_Killer) { // Drops 0-3 Ink Sacs - int LootingLevel = 0; + unsigned int LootingLevel = 0; if (a_Killer != nullptr) { LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); @@ -35,19 +35,20 @@ void cSquid::GetDrops(cItems & a_Drops, cEntity * a_Killer) void cSquid::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { + m_IsFollowingPath = false; // Disable Pathfinding until it's fixed. TODO + // We must first process current location, and only then tick, otherwise we risk processing a location in a chunk // that is not where the entity currently resides (FS #411) - Vector3d Pos = GetPosition(); // TODO: Not a real behavior, but cool :D - int RelY = (int)floor(Pos.y); + int RelY = FloorC(Pos.y); if ((RelY < 0) || (RelY >= cChunkDef::Height)) { return; } - int RelX = (int)floor(Pos.x) - a_Chunk.GetPosX() * cChunkDef::Width; - int RelZ = (int)floor(Pos.z) - a_Chunk.GetPosZ() * cChunkDef::Width; + int RelX = FloorC(Pos.x) - a_Chunk.GetPosX() * cChunkDef::Width; + int RelZ = FloorC(Pos.z) - a_Chunk.GetPosZ() * cChunkDef::Width; BLOCKTYPE BlockType; if (a_Chunk.UnboundedRelGetBlockType(RelX, RelY, RelZ, BlockType) && !IsBlockWater(BlockType) && !IsOnFire()) { diff --git a/src/Mobs/Witch.cpp b/src/Mobs/Witch.cpp index a3cadbaa0..1f672b4f7 100644 --- a/src/Mobs/Witch.cpp +++ b/src/Mobs/Witch.cpp @@ -19,7 +19,7 @@ cWitch::cWitch(void) : void cWitch::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - int LootingLevel = 0; + unsigned int LootingLevel = 0; if (a_Killer != nullptr) { LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); diff --git a/src/Mobs/Wolf.cpp b/src/Mobs/Wolf.cpp index 3c2ec1520..7b5953523 100644 --- a/src/Mobs/Wolf.cpp +++ b/src/Mobs/Wolf.cpp @@ -20,6 +20,7 @@ cWolf::cWolf(void) : m_OwnerName(""), m_CollarColor(14) { + m_RelativeWalkSpeed = 2; } @@ -50,7 +51,7 @@ void cWolf::Attack(std::chrono::milliseconds a_Dt) if ((m_Target != nullptr) && (m_Target->IsPlayer())) { - if (((cPlayer *)m_Target)->GetName() != m_OwnerName) + if (static_cast<cPlayer *>(m_Target)->GetName() != m_OwnerName) { super::Attack(a_Dt); } @@ -157,7 +158,7 @@ void cWolf::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) super::Tick(a_Dt, a_Chunk); } - cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), (float)m_SightDistance); + cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), static_cast<float>(m_SightDistance)); if (a_Closest_Player != nullptr) { switch (a_Closest_Player->GetEquippedItem().m_ItemType) @@ -230,7 +231,7 @@ void cWolf::TickFollowPlayer() { // The player is present in the world, follow him: double Distance = (Callback.OwnerPos - GetPosition()).Length(); - if (Distance > 30) + if (Distance > 20) { Callback.OwnerPos.y = FindFirstNonAirBlockPosition(Callback.OwnerPos.x, Callback.OwnerPos.z); TeleportToCoords(Callback.OwnerPos.x, Callback.OwnerPos.y, Callback.OwnerPos.z); 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 fa4ac855d..a5b44e6c0 100644 --- a/src/Mobs/Zombie.cpp +++ b/src/Mobs/Zombie.cpp @@ -23,7 +23,7 @@ cZombie::cZombie(bool a_IsVillagerZombie) : void cZombie::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - int LootingLevel = 0; + unsigned int LootingLevel = 0; if (a_Killer != nullptr) { LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); diff --git a/src/Mobs/ZombiePigman.cpp b/src/Mobs/ZombiePigman.cpp index 8b415b0af..96d587287 100644 --- a/src/Mobs/ZombiePigman.cpp +++ b/src/Mobs/ZombiePigman.cpp @@ -18,7 +18,7 @@ cZombiePigman::cZombiePigman(void) : void cZombiePigman::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - int LootingLevel = 0; + unsigned int LootingLevel = 0; if (a_Killer != nullptr) { LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); |