From 1b0e21e0b270b3938b81df119d5a741a3e1e1257 Mon Sep 17 00:00:00 2001 From: wiseoldman95 Date: Wed, 29 Apr 2015 19:24:14 +0300 Subject: A* Pathfinding and better monster AI --- src/Mobs/Monster.cpp | 268 +++++++++++++++++++++++++++------------------------ 1 file changed, 142 insertions(+), 126 deletions(-) (limited to 'src/Mobs/Monster.cpp') diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index 55d83302a..e225ff9b1 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -13,7 +13,7 @@ #include "../Chunk.h" #include "../FastRandom.h" - +#include "Path.h" @@ -74,6 +74,10 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A , m_EMState(IDLE) , m_EMPersonality(AGGRESSIVE) , m_Target(nullptr) + , m_Path(nullptr) + , m_PathStatus(ePathFinderStatus::PATH_NOT_FOUND) + , m_IsFollowingPath(false) + , m_GiveUpCounter(0) , m_bMovingToDestination(false) , m_LastGroundHeight(POSY_TOINT) , m_IdleInterval(0) @@ -94,8 +98,9 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A , m_DropChanceLeggings(0.085f) , m_DropChanceBoots(0.085f) , m_CanPickUpLoot(true) + , m_TicksSinceLastDamaged(100) , m_BurnsInDaylight(false) - , m_RelativeWalkSpeed(1.0) + , m_RelativeWalkSpeed(1) { if (!a_ConfigName.empty()) { @@ -118,87 +123,52 @@ void cMonster::SpawnOn(cClientHandle & a_Client) void cMonster::TickPathFinding() { - const int PosX = POSX_TOINT; - const int PosY = POSY_TOINT; - const int PosZ = POSZ_TOINT; - - std::vector m_PotentialCoordinates; - m_TraversedCoordinates.push_back(Vector3i(PosX, PosY, PosZ)); - static const struct // Define which directions to try to move to - { - int x, z; - } gCrossCoords[] = - { - { 1, 0}, - {-1, 0}, - { 0, 1}, - { 0, -1}, - } ; - - if ((PosY - 1 < 0) || (PosY + 2 >= cChunkDef::Height) /* PosY + 1 will never be true if PosY + 2 is not */) + if (m_Path == nullptr) { - // Too low/high, can't really do anything - FinishPathFinding(); - return; - } + Vector3d position = GetPosition(); + Vector3d Dest = m_FinalDestination; + + // Can someone explain why are these two NOT THE SAME??? + // m_Path = new cPath(GetWorld(), GetPosition(), m_FinalDestination, 30); + m_Path = new cPath(GetWorld(), Vector3d(floor(position.x), floor(position.y), floor(position.z)), Vector3d(floor(Dest.x), floor(Dest.y), floor(Dest.z)), 20); - for (size_t i = 0; i < ARRAYCOUNT(gCrossCoords); i++) + + m_IsFollowingPath = false; + } + m_PathStatus = m_Path->Step(); + switch (m_PathStatus) { - if (IsCoordinateInTraversedList(Vector3i(gCrossCoords[i].x + PosX, PosY, gCrossCoords[i].z + PosZ))) - { - continue; - } - BLOCKTYPE BlockAtY = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY, gCrossCoords[i].z + PosZ); - BLOCKTYPE BlockAtYP = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY + 1, gCrossCoords[i].z + PosZ); - BLOCKTYPE BlockAtYPP = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY + 2, gCrossCoords[i].z + PosZ); - int LowestY = FindFirstNonAirBlockPosition(gCrossCoords[i].x + PosX, gCrossCoords[i].z + PosZ); - BLOCKTYPE BlockAtLowestY = (LowestY >= cChunkDef::Height) ? E_BLOCK_AIR : m_World->GetBlock(gCrossCoords[i].x + PosX, LowestY, gCrossCoords[i].z + PosZ); - - if ( - (!cBlockInfo::IsSolid(BlockAtY)) && - (!cBlockInfo::IsSolid(BlockAtYP)) && - (!IsBlockLava(BlockAtLowestY)) && - (BlockAtLowestY != E_BLOCK_CACTUS) && - (PosY - LowestY < FALL_DAMAGE_HEIGHT) - ) + case ePathFinderStatus::PATH_NOT_FOUND: { - m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY, gCrossCoords[i].z + PosZ)); + FinishPathFinding(); + break; } - else if ( - (cBlockInfo::IsSolid(BlockAtY)) && - (BlockAtY != E_BLOCK_CACTUS) && - (!cBlockInfo::IsSolid(BlockAtYP)) && - (!cBlockInfo::IsSolid(BlockAtYPP)) && - (BlockAtY != E_BLOCK_FENCE) && - (BlockAtY != E_BLOCK_FENCE_GATE) - ) + + + case ePathFinderStatus::CALCULATING: { - m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY + 1, gCrossCoords[i].z + PosZ)); + m_Destination = GetPosition(); + break; } - } - if (!m_PotentialCoordinates.empty()) - { - Vector3f ShortestCoords = m_PotentialCoordinates.front(); - for (std::vector::const_iterator itr = m_PotentialCoordinates.begin(); itr != m_PotentialCoordinates.end(); ++itr) + + case ePathFinderStatus::PATH_FOUND: { - Vector3f Distance = m_FinalDestination - ShortestCoords; - Vector3f Distance2 = m_FinalDestination - *itr; - if (Distance.SqrLength() > Distance2.SqrLength()) + if (ReachedDestination() || !m_IsFollowingPath) { - ShortestCoords = *itr; + m_Destination = m_Path->GetNextPoint(); + m_IsFollowingPath = true; + m_GiveUpCounter = 40; // Give up after 40 ticks (2 seconds) if failed to reach m_Dest. } - } + if (m_Path->IsLastPoint()) + { + FinishPathFinding(); + } + break; - m_Destination = ShortestCoords; - m_Destination.z += 0.5f; - m_Destination.x += 0.5f; - } - else - { - FinishPathFinding(); + } } } @@ -206,17 +176,28 @@ void cMonster::TickPathFinding() +/* Currently, the mob will only start moving to a new position after the position it is +currently going to is reached. */ void cMonster::MoveToPosition(const Vector3d & a_Position) { - FinishPathFinding(); - m_FinalDestination = a_Position; m_bMovingToDestination = true; - TickPathFinding(); } + + +void cMonster::StopMovingToPosition() +{ + m_bMovingToDestination = false; + FinishPathFinding(); +} + + + + + bool cMonster::IsCoordinateInTraversedList(Vector3i a_Coords) { return (std::find(m_TraversedCoordinates.begin(), m_TraversedCoordinates.end(), a_Coords) != m_TraversedCoordinates.end()); @@ -226,6 +207,22 @@ bool cMonster::IsCoordinateInTraversedList(Vector3i a_Coords) +/* No one should call this except the pathfinder orthe monster tick or StopMovingToPosition. +Resets the pathfinder, usually starting a brand new path, unless called from StopMovingToPosition. */ +void cMonster::FinishPathFinding(void) +{ + if (m_Path != nullptr) + { + delete m_Path; + m_Path = nullptr; + + } +} + + + + + bool cMonster::ReachedDestination() { if ((m_Destination - GetPosition()).Length() < 0.5f) @@ -239,13 +236,14 @@ bool cMonster::ReachedDestination() + bool cMonster::ReachedFinalDestination() { if ((GetPosition() - m_FinalDestination).Length() <= m_AttackRange) { return true; } - + return false; } @@ -268,6 +266,10 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) return; } + if (m_TicksSinceLastDamaged < 100) + { + ++m_TicksSinceLastDamaged; + } if ((m_Target != nullptr) && m_Target->IsDestroyed()) { m_Target = nullptr; @@ -276,6 +278,8 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) // Burning in daylight HandleDaylightBurning(a_Chunk); + + if (m_bMovingToDestination) { if (m_bOnGround) @@ -289,52 +293,50 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) } } + TickPathFinding(); + Vector3d Distance = m_Destination - GetPosition(); if (!ReachedDestination() && !ReachedFinalDestination()) // If we haven't reached any sort of destination, move { - Distance.y = 0; - Distance.Normalize(); - - if (m_bOnGround) + if (--m_GiveUpCounter == 0) { - Distance *= 2.5f; - } - else if (IsSwimming()) - { - Distance *= 1.3f; + FinishPathFinding(); } else { - // Don't let the mob move too much if he's falling. - Distance *= 0.25f; - } - - // Apply walk speed: - Distance *= m_RelativeWalkSpeed; - - AddSpeedX(Distance.x); - AddSpeedZ(Distance.z); + Distance.y = 0; + Distance.Normalize(); + + if (m_bOnGround) + { + Distance *= 2.5f; + } + else if (IsSwimming()) + { + Distance *= 1.3f; + } + else + { + // Don't let the mob move too much if he's falling. + Distance *= 0.25f; + } + + // Apply walk speed: + Distance *= m_RelativeWalkSpeed; + + /* Reduced default speed. + Close to Vanilla, easier for mobs to follow m_Destinations, hence + better pathfinding. */ + Distance *= 0.5; + + AddSpeedX(Distance.x); + AddSpeedZ(Distance.z); - // It's too buggy! - /* - if (m_EMState == ESCAPING) - { - // Runs Faster when escaping :D otherwise they just walk away - SetSpeedX (GetSpeedX() * 2.f); - SetSpeedZ (GetSpeedZ() * 2.f); } - */ } - else + else if (ReachedFinalDestination()) { - if (ReachedFinalDestination()) // If we have reached the ultimate, final destination, stop pathfinding and attack if appropriate - { - FinishPathFinding(); - } - else - { - TickPathFinding(); // We have reached the next point in our path, calculate another point - } + StopMovingToPosition(); } } @@ -345,13 +347,13 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { case IDLE: { - // If enemy passive we ignore checks for player visibility + // If enemy passive we ignore checks for player visibility. InStateIdle(a_Dt); break; } case CHASING: { - // If we do not see a player anymore skip chasing action + // If we do not see a player anymore skip chasing action. InStateChasing(a_Dt); break; } @@ -360,12 +362,13 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) InStateEscaping(a_Dt); break; } - + case ATTACKING: break; } // switch (m_EMState) BroadcastMovementUpdate(); -} + } + @@ -409,6 +412,7 @@ void cMonster::SetPitchAndYawFromDestination() + void cMonster::HandleFalling() { if (m_bOnGround) @@ -460,7 +464,6 @@ int cMonster::FindFirstNonAirBlockPosition(double a_PosX, double a_PosZ) - bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI) { if (!super::DoTakeDamage(a_TDI)) @@ -476,6 +479,7 @@ bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI) if (a_TDI.Attacker != nullptr) { m_Target = a_TDI.Attacker; + m_TicksSinceLastDamaged = 0; } return true; } @@ -692,7 +696,7 @@ void cMonster::InStateChasing(std::chrono::milliseconds a_Dt) void cMonster::InStateEscaping(std::chrono::milliseconds a_Dt) { UNUSED(a_Dt); - + if (m_Target != nullptr) { Vector3d newloc = GetPosition(); @@ -771,7 +775,7 @@ AString cMonster::MobTypeToString(eMonsterType a_MobType) return g_MobTypeNames[i].m_lcName; } } - + // Not found: return ""; } @@ -866,7 +870,7 @@ cMonster::eFamily cMonster::FamilyFromType(eMonsterType a_Type) case mtWolf: return mfHostile; case mtZombie: return mfHostile; case mtZombiePigman: return mfHostile; - + case mtInvalidType: break; } ASSERT(!"Unhandled mob type"); @@ -1041,7 +1045,7 @@ void cMonster::AddRandomArmorDropItem(cItems & a_Drops, short a_LootingLevel) a_Drops.push_back(GetEquippedHelmet()); } } - + if (r1.randInt() % 200 < ((m_DropChanceChestplate * 200) + (a_LootingLevel * 2))) { if (!GetEquippedChestplate().IsEmpty()) @@ -1049,7 +1053,7 @@ void cMonster::AddRandomArmorDropItem(cItems & a_Drops, short a_LootingLevel) a_Drops.push_back(GetEquippedChestplate()); } } - + if (r1.randInt() % 200 < ((m_DropChanceLeggings * 200) + (a_LootingLevel * 2))) { if (!GetEquippedLeggings().IsEmpty()) @@ -1057,7 +1061,7 @@ void cMonster::AddRandomArmorDropItem(cItems & a_Drops, short a_LootingLevel) a_Drops.push_back(GetEquippedLeggings()); } } - + if (r1.randInt() % 200 < ((m_DropChanceBoots * 200) + (a_LootingLevel * 2))) { if (!GetEquippedBoots().IsEmpty()) @@ -1093,23 +1097,34 @@ void cMonster::HandleDaylightBurning(cChunk & a_Chunk) { return; } - + int RelY = POSY_TOINT; if ((RelY < 0) || (RelY >= cChunkDef::Height)) { // Outside the world return; } - - int RelX = POSX_TOINT - GetChunkX() * cChunkDef::Width; - int RelZ = POSZ_TOINT - GetChunkZ() * cChunkDef::Width; - if (!a_Chunk.IsLightValid()) { m_World->QueueLightChunk(GetChunkX(), GetChunkZ()); return; } + if (WouldBurnAt(GetPosition(), a_Chunk)) + { + // Burn for 100 ticks, then decide again + StartBurning(100); + } +} + + + + +bool cMonster::WouldBurnAt(Vector3d a_Location, cChunk & a_Chunk) +{ + int RelX = FloorC(a_Location.x) - a_Chunk.GetPosX() * cChunkDef::Width; + int RelY = FloorC(a_Location.y); + int RelZ = FloorC(a_Location.z) - a_Chunk.GetPosZ() * cChunkDef::Width; if ( (a_Chunk.GetSkyLight(RelX, RelY, RelZ) == 15) && // In the daylight (a_Chunk.GetBlock(RelX, RelY, RelZ) != E_BLOCK_SOULSAND) && // Not on soulsand @@ -1118,14 +1133,15 @@ void cMonster::HandleDaylightBurning(cChunk & a_Chunk) GetWorld()->IsWeatherSunnyAt(POSX_TOINT, POSZ_TOINT) // Not raining ) { - // Burn for 100 ticks, then decide again - StartBurning(100); + return true; } + return false; } + cMonster::eFamily cMonster::GetMobFamily(void) const { return FamilyFromType(m_MobType); -- cgit v1.2.3