From 47c0b48bfd5df90cf889574c5634542d2aaa8873 Mon Sep 17 00:00:00 2001 From: Tiger Wang Date: Fri, 18 Dec 2020 20:48:32 +0000 Subject: [PATCH] Monsters: improve targeting * Replace DoWithNearestPlayer with bounding box search (avoid iterating through all players in world). * Do line-of-sight checks from eye-to-eye. + Added LOS and LOS lost timer to target lost checks, in addition to distance. --- src/Mobs/AggressiveMonster.cpp | 25 +++---- src/Mobs/Enderman.cpp | 20 +----- src/Mobs/Enderman.h | 1 - src/Mobs/Monster.cpp | 97 +++++++++++++++++++++------ src/Mobs/Monster.h | 3 +- src/Mobs/PassiveAggressiveMonster.cpp | 2 +- src/Mobs/PassiveMonster.cpp | 2 +- src/Mobs/Spider.cpp | 5 +- 8 files changed, 92 insertions(+), 63 deletions(-) diff --git a/src/Mobs/AggressiveMonster.cpp b/src/Mobs/AggressiveMonster.cpp index a904ac2da..17912a090 100644 --- a/src/Mobs/AggressiveMonster.cpp +++ b/src/Mobs/AggressiveMonster.cpp @@ -38,11 +38,6 @@ void cAggressiveMonster::InStateChasing(std::chrono::milliseconds a_Dt, cChunk & void cAggressiveMonster::EventSeePlayer(cPlayer * a_Player, cChunk & a_Chunk) { - if (!a_Player->CanMobsTarget()) - { - return; - } - Super::EventSeePlayer(a_Player, a_Chunk); m_EMState = CHASING; } @@ -60,27 +55,25 @@ void cAggressiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) return; } + // Set or clear m_Target depending on rules for this Monster: if (m_EMState == CHASING) { - CheckEventLostPlayer(); + CheckEventLostPlayer(a_Dt); } else { CheckEventSeePlayer(a_Chunk); } - auto target = GetTarget(); - if (target == nullptr) - { - return; - } - - // TODO: Currently all mobs see through lava, but only Nether-native mobs should be able to. - Vector3d MyHeadPosition = GetPosition() + Vector3d(0, GetHeight(), 0); - Vector3d TargetPosition = target->GetPosition() + Vector3d(0, target->GetHeight(), 0); if ( + (GetTarget() != nullptr) && TargetIsInRange() && - cLineBlockTracer::LineOfSightTrace(*GetWorld(), MyHeadPosition, TargetPosition, cLineBlockTracer::losAirWaterLava) && + cLineBlockTracer::LineOfSightTrace( + *GetWorld(), + GetPosition().addedY(GetHeight()), + GetTarget()->GetPosition().addedY(GetTarget()->GetHeight()), + cLineBlockTracer::losAirWaterLava // TODO: Currently all mobs see through lava, but only Nether-native mobs should be able to. + ) && (GetHealth() > 0.0) ) { diff --git a/src/Mobs/Enderman.cpp b/src/Mobs/Enderman.cpp index 3b8cd77e5..302593183 100644 --- a/src/Mobs/Enderman.cpp +++ b/src/Mobs/Enderman.cpp @@ -113,14 +113,8 @@ void cEnderman::CheckEventSeePlayer(cChunk & a_Chunk) ASSERT(Callback.GetPlayer() != nullptr); - if (!Callback.GetPlayer()->CanMobsTarget()) - { - return; - } - - // Target the player - cMonster::EventSeePlayer(Callback.GetPlayer(), a_Chunk); - m_EMState = CHASING; + // Target the player: + cAggressiveMonster::EventSeePlayer(Callback.GetPlayer(), a_Chunk); m_bIsScreaming = true; GetWorld()->BroadcastEntityMetadata(*this); } @@ -129,16 +123,6 @@ void cEnderman::CheckEventSeePlayer(cChunk & a_Chunk) -void cEnderman::CheckEventLostPlayer(void) -{ - Super::CheckEventLostPlayer(); - EventLosePlayer(); -} - - - - - void cEnderman::EventLosePlayer() { Super::EventLosePlayer(); diff --git a/src/Mobs/Enderman.h b/src/Mobs/Enderman.h index fa3b0d06b..a4864a7f1 100644 --- a/src/Mobs/Enderman.h +++ b/src/Mobs/Enderman.h @@ -20,7 +20,6 @@ public: virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; virtual void CheckEventSeePlayer(cChunk & a_Chunk) override; - virtual void CheckEventLostPlayer(void) override; virtual void EventLosePlayer(void) override; virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index b1975368d..048393e67 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -2,6 +2,7 @@ #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "IncludeAllMonsters.h" +#include "LineBlockTracer.h" #include "../BlockInfo.h" #include "../Root.h" #include "../Server.h" @@ -306,19 +307,6 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { ++m_TicksSinceLastDamaged; } - if ((GetTarget() != nullptr)) - { - ASSERT(GetTarget()->IsTicking()); - - if (GetTarget()->IsPlayer()) - { - if (!static_cast(GetTarget())->CanMobsTarget()) - { - SetTarget(nullptr); - m_EMState = IDLE; - } - } - } // Process the undead burning in daylight. HandleDaylightBurning(*Chunk, WouldBurnAt(GetPosition(), *Chunk)); @@ -764,29 +752,94 @@ void cMonster::OnRightClicked(cPlayer & a_Player) // monster sez: Do I see the player void cMonster::CheckEventSeePlayer(cChunk & a_Chunk) { - m_World->DoWithNearestPlayer(GetPosition(), static_cast(m_SightDistance), [&](cPlayer & a_Player) -> bool + if (GetTarget() != nullptr) { - EventSeePlayer(&a_Player, a_Chunk); - return true; - }, true); + return; + } + + cPlayer * TargetPlayer = nullptr; + double ClosestDistance = m_SightDistance * m_SightDistance; + const auto MyHeadPosition = GetPosition().addedY(GetHeight()); + + // Enumerate all players within sight: + m_World->ForEachEntityInBox({ GetPosition(), m_SightDistance * 2.0 }, [this, &TargetPlayer, &ClosestDistance, MyHeadPosition](cEntity & a_Entity) + { + if (!a_Entity.IsPlayer()) + { + // Continue iteration: + return false; + } + + const auto Player = static_cast(&a_Entity); + + if (!Player->CanMobsTarget()) + { + return false; + } + + const auto TargetHeadPosition = a_Entity.GetPosition().addedY(a_Entity.GetHeight()); + const auto TargetDistance = (TargetHeadPosition - MyHeadPosition).SqrLength(); + + // TODO: Currently all mobs see through lava, but only Nether-native mobs should be able to. + if ( + (TargetDistance < ClosestDistance) && + cLineBlockTracer::LineOfSightTrace(*GetWorld(), MyHeadPosition, TargetHeadPosition, cLineBlockTracer::losAirWaterLava) + ) + { + TargetPlayer = Player; + ClosestDistance = TargetDistance; + } + + return false; + }); + + // Target him if suitable player found: + if (TargetPlayer != nullptr) + { + EventSeePlayer(TargetPlayer, a_Chunk); + } } -void cMonster::CheckEventLostPlayer(void) +void cMonster::CheckEventLostPlayer(const std::chrono::milliseconds a_Dt) { - if (GetTarget() != nullptr) + const auto Target = GetTarget(); + + if (Target == nullptr) { - if ((GetTarget()->GetPosition() - GetPosition()).Length() > m_SightDistance) + return; + } + + // Check if the player died, is in creative mode, etc: + if (Target->IsPlayer() && !static_cast(Target)->CanMobsTarget()) + { + EventLosePlayer(); + return; + } + + // Check if the target is too far away: + if (!Target->GetBoundingBox().DoesIntersect({ GetPosition(), m_SightDistance * 2.0 })) + { + EventLosePlayer(); + return; + } + + const auto MyHeadPosition = GetPosition().addedY(GetHeight()); + const auto TargetHeadPosition = Target->GetPosition().addedY(Target->GetHeight()); + if (!cLineBlockTracer::LineOfSightTrace(*GetWorld(), MyHeadPosition, TargetHeadPosition, cLineBlockTracer::losAirWaterLava)) + { + if ((m_LoseSightAbandonTargetTimer += a_Dt) > std::chrono::seconds(4)) { EventLosePlayer(); } } else { - EventLosePlayer(); + // Subtract the amount of time we "handled" instead of setting to zero, so we don't ignore a large a_Dt of say, 8s: + m_LoseSightAbandonTargetTimer -= std::min(std::chrono::milliseconds(4000), m_LoseSightAbandonTargetTimer); } } @@ -809,7 +862,9 @@ void cMonster::EventSeePlayer(cPlayer * a_SeenPlayer, cChunk & a_Chunk) void cMonster::EventLosePlayer(void) { SetTarget(nullptr); + m_EMState = IDLE; + m_LoseSightAbandonTargetTimer = std::chrono::seconds::zero(); } diff --git a/src/Mobs/Monster.h b/src/Mobs/Monster.h index edd0a96c3..aeb8a3bf9 100644 --- a/src/Mobs/Monster.h +++ b/src/Mobs/Monster.h @@ -117,7 +117,7 @@ public: virtual bool IsUndead(void); virtual void EventLosePlayer(void); - virtual void CheckEventLostPlayer(void); + virtual void CheckEventLostPlayer(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); @@ -313,6 +313,7 @@ protected: double m_AttackRange; int m_AttackCoolDownTicksLeft; int m_SightDistance; + std::chrono::milliseconds m_LoseSightAbandonTargetTimer; float m_DropChanceWeapon; float m_DropChanceHelmet; diff --git a/src/Mobs/PassiveAggressiveMonster.cpp b/src/Mobs/PassiveAggressiveMonster.cpp index bf4a3167c..d96b87566 100644 --- a/src/Mobs/PassiveAggressiveMonster.cpp +++ b/src/Mobs/PassiveAggressiveMonster.cpp @@ -26,7 +26,7 @@ bool cPassiveAggressiveMonster::DoTakeDamage(TakeDamageInfo & a_TDI) return false; } - if ((GetTarget() != nullptr) && (GetTarget()->IsPlayer())) + if ((GetTarget() != nullptr) && GetTarget()->IsPlayer()) { if (static_cast(GetTarget())->CanMobsTarget()) { diff --git a/src/Mobs/PassiveMonster.cpp b/src/Mobs/PassiveMonster.cpp index 1843ceb73..e9c4070db 100644 --- a/src/Mobs/PassiveMonster.cpp +++ b/src/Mobs/PassiveMonster.cpp @@ -57,7 +57,7 @@ void cPassiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) if (m_EMState == ESCAPING) { - CheckEventLostPlayer(); + CheckEventLostPlayer(a_Dt); } cMonster::LoveTick(); diff --git a/src/Mobs/Spider.cpp b/src/Mobs/Spider.cpp index be8196cea..3677411f6 100644 --- a/src/Mobs/Spider.cpp +++ b/src/Mobs/Spider.cpp @@ -48,10 +48,7 @@ void cSpider::EventSeePlayer(cPlayer * a_Player, cChunk & a_Chunk) return; } - if ( - a_Player->CanMobsTarget() && - !((Chunk->GetSkyLightAltered(Rel.x, Rel.y, Rel.z) > 11) || (Chunk->GetBlockLight(Rel.x, Rel.y, Rel.z) > 11)) - ) + if ((Chunk->GetSkyLightAltered(Rel.x, Rel.y, Rel.z) <= 11) && (Chunk->GetBlockLight(Rel.x, Rel.y, Rel.z) <= 11)) { Super::EventSeePlayer(a_Player, a_Chunk); }