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) void cAggressiveMonster::EventSeePlayer(cPlayer * a_Player, cChunk & a_Chunk)
{ {
if (!a_Player->CanMobsTarget())
{
return;
}
Super::EventSeePlayer(a_Player, a_Chunk); Super::EventSeePlayer(a_Player, a_Chunk);
m_EMState = CHASING; m_EMState = CHASING;
} }
@ -60,27 +55,25 @@ void cAggressiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
return; return;
} }
// Set or clear m_Target depending on rules for this Monster:
if (m_EMState == CHASING) if (m_EMState == CHASING)
{ {
CheckEventLostPlayer(); CheckEventLostPlayer(a_Dt);
} }
else else
{ {
CheckEventSeePlayer(a_Chunk); 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 ( if (
(GetTarget() != nullptr) &&
TargetIsInRange() && 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) (GetHealth() > 0.0)
) )
{ {

View File

@ -113,14 +113,8 @@ void cEnderman::CheckEventSeePlayer(cChunk & a_Chunk)
ASSERT(Callback.GetPlayer() != nullptr); ASSERT(Callback.GetPlayer() != nullptr);
if (!Callback.GetPlayer()->CanMobsTarget()) // Target the player:
{ cAggressiveMonster::EventSeePlayer(Callback.GetPlayer(), a_Chunk);
return;
}
// Target the player
cMonster::EventSeePlayer(Callback.GetPlayer(), a_Chunk);
m_EMState = CHASING;
m_bIsScreaming = true; m_bIsScreaming = true;
GetWorld()->BroadcastEntityMetadata(*this); GetWorld()->BroadcastEntityMetadata(*this);
} }
@ -129,16 +123,6 @@ void cEnderman::CheckEventSeePlayer(cChunk & a_Chunk)
void cEnderman::CheckEventLostPlayer(void)
{
Super::CheckEventLostPlayer();
EventLosePlayer();
}
void cEnderman::EventLosePlayer() void cEnderman::EventLosePlayer()
{ {
Super::EventLosePlayer(); Super::EventLosePlayer();

View File

@ -20,7 +20,6 @@ public:
virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override;
virtual void CheckEventSeePlayer(cChunk & a_Chunk) override; virtual void CheckEventSeePlayer(cChunk & a_Chunk) override;
virtual void CheckEventLostPlayer(void) override;
virtual void EventLosePlayer(void) override; virtual void EventLosePlayer(void) override;
virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) 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 "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "IncludeAllMonsters.h" #include "IncludeAllMonsters.h"
#include "LineBlockTracer.h"
#include "../BlockInfo.h" #include "../BlockInfo.h"
#include "../Root.h" #include "../Root.h"
#include "../Server.h" #include "../Server.h"
@ -306,19 +307,6 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
++m_TicksSinceLastDamaged; ++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. // Process the undead burning in daylight.
HandleDaylightBurning(*Chunk, WouldBurnAt(GetPosition(), *Chunk)); HandleDaylightBurning(*Chunk, WouldBurnAt(GetPosition(), *Chunk));
@ -764,29 +752,94 @@ void cMonster::OnRightClicked(cPlayer & a_Player)
// monster sez: Do I see the player // monster sez: Do I see the player
void cMonster::CheckEventSeePlayer(cChunk & a_Chunk) 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;
return true; }
}, true);
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(); EventLosePlayer();
} }
} }
else 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) void cMonster::EventLosePlayer(void)
{ {
SetTarget(nullptr); SetTarget(nullptr);
m_EMState = IDLE; m_EMState = IDLE;
m_LoseSightAbandonTargetTimer = std::chrono::seconds::zero();
} }

View File

@ -117,7 +117,7 @@ public:
virtual bool IsUndead(void); virtual bool IsUndead(void);
virtual void EventLosePlayer(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 InStateIdle (std::chrono::milliseconds a_Dt, cChunk & a_Chunk);
virtual void InStateChasing (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; double m_AttackRange;
int m_AttackCoolDownTicksLeft; int m_AttackCoolDownTicksLeft;
int m_SightDistance; int m_SightDistance;
std::chrono::milliseconds m_LoseSightAbandonTargetTimer;
float m_DropChanceWeapon; float m_DropChanceWeapon;
float m_DropChanceHelmet; float m_DropChanceHelmet;

View File

@ -26,7 +26,7 @@ bool cPassiveAggressiveMonster::DoTakeDamage(TakeDamageInfo & a_TDI)
return false; return false;
} }
if ((GetTarget() != nullptr) && (GetTarget()->IsPlayer())) if ((GetTarget() != nullptr) && GetTarget()->IsPlayer())
{ {
if (static_cast<cPlayer *>(GetTarget())->CanMobsTarget()) 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) if (m_EMState == ESCAPING)
{ {
CheckEventLostPlayer(); CheckEventLostPlayer(a_Dt);
} }
cMonster::LoveTick(); cMonster::LoveTick();

View File

@ -48,10 +48,7 @@ void cSpider::EventSeePlayer(cPlayer * a_Player, cChunk & a_Chunk)
return; return;
} }
if ( if ((Chunk->GetSkyLightAltered(Rel.x, Rel.y, Rel.z) <= 11) && (Chunk->GetBlockLight(Rel.x, Rel.y, Rel.z) <= 11))
a_Player->CanMobsTarget() &&
!((Chunk->GetSkyLightAltered(Rel.x, Rel.y, Rel.z) > 11) || (Chunk->GetBlockLight(Rel.x, Rel.y, Rel.z) > 11))
)
{ {
Super::EventSeePlayer(a_Player, a_Chunk); Super::EventSeePlayer(a_Player, a_Chunk);
} }