1
0
Fork 0

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.
This commit is contained in:
Tiger Wang 2020-12-18 20:48:32 +00:00
parent 491238f799
commit 47c0b48bfd
8 changed files with 92 additions and 63 deletions

View File

@ -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)
)
{

View File

@ -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();

View File

@ -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;

View File

@ -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<cPlayer *>(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<float>(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<cPlayer *>(&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<cPlayer *>(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();
}

View File

@ -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;

View File

@ -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<cPlayer *>(GetTarget())->CanMobsTarget())
{

View File

@ -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();

View File

@ -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);
}