From 47c0b48bfd5df90cf889574c5634542d2aaa8873 Mon Sep 17 00:00:00 2001 From: Tiger Wang Date: Fri, 18 Dec 2020 20:48:32 +0000 Subject: 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/Monster.cpp | 97 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 76 insertions(+), 21 deletions(-) (limited to 'src/Mobs/Monster.cpp') 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) + { + return; + } + + // Check if the player died, is in creative mode, etc: + if (Target->IsPlayer() && !static_cast(Target)->CanMobsTarget()) { - if ((GetTarget()->GetPosition() - GetPosition()).Length() > m_SightDistance) + 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(); } -- cgit v1.2.3