302 lines
10 KiB
C++
302 lines
10 KiB
C++
#include "ClientlessPerceptionMgr.h"
|
|
|
|
#include "CellImpl.h"
|
|
#include "ClientlessPlayerMgr.h"
|
|
#include "Creature.h"
|
|
#include "GridNotifiersImpl.h"
|
|
#include "Player.h"
|
|
#include "SharedDefines.h"
|
|
#include "World.h"
|
|
#include "WorldSession.h"
|
|
|
|
#include <algorithm>
|
|
#include <list>
|
|
#include <sstream>
|
|
|
|
namespace
|
|
{
|
|
constexpr float DEFAULT_SCAN_RADIUS = 40.0f;
|
|
constexpr uint32 SCAN_INTERVAL_MS = 1000;
|
|
|
|
ClientlessEntityRelation RelationFromReaction(ReputationRank reaction)
|
|
{
|
|
if (reaction <= REP_HOSTILE)
|
|
return ClientlessEntityRelation::Hostile;
|
|
|
|
if (reaction >= REP_FRIENDLY)
|
|
return ClientlessEntityRelation::Friendly;
|
|
|
|
return ClientlessEntityRelation::Neutral;
|
|
}
|
|
}
|
|
|
|
ClientlessPerceptionMgr* ClientlessPerceptionMgr::instance()
|
|
{
|
|
static ClientlessPerceptionMgr instance;
|
|
return &instance;
|
|
}
|
|
|
|
char const* ClientlessPerceptionMgr::TypeName(ClientlessEntityType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case ClientlessEntityType::Creature: return "Creature";
|
|
case ClientlessEntityType::Player: return "Player";
|
|
}
|
|
|
|
return "Unknown";
|
|
}
|
|
|
|
char const* ClientlessPerceptionMgr::RelationName(ClientlessEntityRelation relation)
|
|
{
|
|
switch (relation)
|
|
{
|
|
case ClientlessEntityRelation::Unknown: return "unknown";
|
|
case ClientlessEntityRelation::Friendly: return "friendly";
|
|
case ClientlessEntityRelation::Neutral: return "neutral";
|
|
case ClientlessEntityRelation::Hostile: return "hostile";
|
|
}
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
std::string ClientlessPerceptionMgr::CapabilitiesToString(uint64 capabilities)
|
|
{
|
|
std::ostringstream out;
|
|
bool first = true;
|
|
|
|
auto add = [&](uint64 flag, char const* name)
|
|
{
|
|
if ((capabilities & flag) == 0)
|
|
return;
|
|
|
|
if (!first)
|
|
out << ',';
|
|
|
|
out << name;
|
|
first = false;
|
|
};
|
|
|
|
add(CLIENTLESS_CAP_VENDOR, "vendor");
|
|
add(CLIENTLESS_CAP_TRAINER, "trainer");
|
|
add(CLIENTLESS_CAP_FLIGHTMASTER, "flightmaster");
|
|
add(CLIENTLESS_CAP_BANKER, "banker");
|
|
add(CLIENTLESS_CAP_INNKEEPER, "innkeeper");
|
|
add(CLIENTLESS_CAP_AUCTIONEER, "auctioneer");
|
|
add(CLIENTLESS_CAP_QUESTGIVER, "questgiver");
|
|
add(CLIENTLESS_CAP_GOSSIP, "gossip");
|
|
add(CLIENTLESS_CAP_REPAIR, "repair");
|
|
add(CLIENTLESS_CAP_BATTLEMASTER, "battlemaster");
|
|
add(CLIENTLESS_CAP_PETITIONER, "petitioner");
|
|
add(CLIENTLESS_CAP_SPIRITHEALER, "spirithealer");
|
|
add(CLIENTLESS_CAP_SPIRITGUIDE, "spiritguide");
|
|
add(CLIENTLESS_CAP_CRITTER, "critter");
|
|
add(CLIENTLESS_CAP_CLIENTLESS, "clientless");
|
|
|
|
return first ? "-" : out.str();
|
|
}
|
|
|
|
ClientlessEntityRelation ClientlessPerceptionMgr::DetermineRelation(Player* observer, Player* target)
|
|
{
|
|
if (!observer || !target)
|
|
return ClientlessEntityRelation::Unknown;
|
|
|
|
return RelationFromReaction(observer->GetReactionTo(target));
|
|
}
|
|
|
|
ClientlessEntityRelation ClientlessPerceptionMgr::DetermineRelation(Player* observer, Creature* target)
|
|
{
|
|
if (!observer || !target)
|
|
return ClientlessEntityRelation::Unknown;
|
|
|
|
return RelationFromReaction(observer->GetReactionTo(target));
|
|
}
|
|
|
|
bool ClientlessPerceptionMgr::ScanNow(Player* player, float radius)
|
|
{
|
|
if (!player || !player->IsInWorld())
|
|
return false;
|
|
|
|
radius = std::max(1.0f, std::min(radius, 100.0f));
|
|
uint32 const characterGuid = player->GetGUID().GetCounter();
|
|
|
|
std::list<WorldObject*> objects;
|
|
Trinity::AllWorldObjectsInRange check(player, radius);
|
|
Trinity::WorldObjectListSearcher<Trinity::AllWorldObjectsInRange> searcher(player, objects, check);
|
|
Cell::VisitAllObjects(player, searcher, radius);
|
|
|
|
ClientlessPerceptionSnapshot snapshot;
|
|
snapshot.Radius = radius;
|
|
snapshot.ScanAgeMs = 0;
|
|
|
|
for (WorldObject* object : objects)
|
|
{
|
|
if (!object)
|
|
continue;
|
|
|
|
if (Creature* creature = object->ToCreature())
|
|
{
|
|
ClientlessPerceivedEntity entity;
|
|
entity.Type = ClientlessEntityType::Creature;
|
|
entity.Guid = creature->GetGUID().GetCounter();
|
|
entity.Entry = creature->GetEntry();
|
|
entity.Name = creature->GetName();
|
|
entity.Distance = player->GetDistance(creature);
|
|
entity.MapId = creature->GetMapId();
|
|
entity.PositionX = creature->GetPositionX();
|
|
entity.PositionY = creature->GetPositionY();
|
|
entity.PositionZ = creature->GetPositionZ();
|
|
entity.Orientation = creature->GetOrientation();
|
|
entity.Level = creature->getLevel();
|
|
entity.Health = creature->GetHealth();
|
|
entity.MaxHealth = creature->GetMaxHealth();
|
|
entity.Alive = creature->IsAlive();
|
|
entity.InLineOfSight = player->IsWithinLOSInMap(creature);
|
|
entity.Attackable = player->IsValidAttackTarget(creature);
|
|
entity.Relation = DetermineRelation(player, creature);
|
|
|
|
uint64 capabilities = CLIENTLESS_CAP_NONE;
|
|
if (creature->IsVendor()) capabilities |= CLIENTLESS_CAP_VENDOR;
|
|
if (creature->IsTrainer()) capabilities |= CLIENTLESS_CAP_TRAINER;
|
|
if (creature->IsTaxi()) capabilities |= CLIENTLESS_CAP_FLIGHTMASTER;
|
|
if (creature->IsBanker()) capabilities |= CLIENTLESS_CAP_BANKER;
|
|
if (creature->IsInnkeeper()) capabilities |= CLIENTLESS_CAP_INNKEEPER;
|
|
if (creature->IsAuctioner()) capabilities |= CLIENTLESS_CAP_AUCTIONEER;
|
|
if (creature->IsQuestGiver()) capabilities |= CLIENTLESS_CAP_QUESTGIVER;
|
|
if (creature->IsGossip()) capabilities |= CLIENTLESS_CAP_GOSSIP;
|
|
if (creature->IsArmorer()) capabilities |= CLIENTLESS_CAP_REPAIR;
|
|
if (creature->IsBattleMaster()) capabilities |= CLIENTLESS_CAP_BATTLEMASTER;
|
|
if (creature->IsGuildMaster()) capabilities |= CLIENTLESS_CAP_PETITIONER;
|
|
if (creature->IsSpiritHealer()) capabilities |= CLIENTLESS_CAP_SPIRITHEALER;
|
|
if (creature->IsSpiritGuide()) capabilities |= CLIENTLESS_CAP_SPIRITGUIDE;
|
|
if (creature->IsCritter()) capabilities |= CLIENTLESS_CAP_CRITTER;
|
|
entity.Capabilities = capabilities;
|
|
|
|
snapshot.Entities.push_back(std::move(entity));
|
|
continue;
|
|
}
|
|
|
|
if (Player* otherPlayer = object->ToPlayer())
|
|
{
|
|
if (otherPlayer == player)
|
|
continue;
|
|
|
|
ClientlessPerceivedEntity entity;
|
|
entity.Type = ClientlessEntityType::Player;
|
|
entity.Guid = otherPlayer->GetGUID().GetCounter();
|
|
entity.Name = otherPlayer->GetName();
|
|
entity.Distance = player->GetDistance(otherPlayer);
|
|
entity.MapId = otherPlayer->GetMapId();
|
|
entity.PositionX = otherPlayer->GetPositionX();
|
|
entity.PositionY = otherPlayer->GetPositionY();
|
|
entity.PositionZ = otherPlayer->GetPositionZ();
|
|
entity.Orientation = otherPlayer->GetOrientation();
|
|
entity.Level = otherPlayer->getLevel();
|
|
entity.Health = otherPlayer->GetHealth();
|
|
entity.MaxHealth = otherPlayer->GetMaxHealth();
|
|
entity.Alive = otherPlayer->IsAlive();
|
|
entity.InLineOfSight = player->IsWithinLOSInMap(otherPlayer);
|
|
entity.Attackable = player->IsValidAttackTarget(otherPlayer);
|
|
entity.Relation = DetermineRelation(player, otherPlayer);
|
|
|
|
if (sClientlessPlayerMgr->IsManaged(entity.Guid))
|
|
entity.Capabilities |= CLIENTLESS_CAP_CLIENTLESS;
|
|
|
|
snapshot.Entities.push_back(std::move(entity));
|
|
}
|
|
}
|
|
|
|
std::sort(snapshot.Entities.begin(), snapshot.Entities.end(),
|
|
[](ClientlessPerceivedEntity const& a, ClientlessPerceivedEntity const& b)
|
|
{
|
|
return a.Distance < b.Distance;
|
|
});
|
|
|
|
Record& record = _records[characterGuid];
|
|
record.Snapshot = std::move(snapshot);
|
|
record.ScanTimerMs = SCAN_INTERVAL_MS;
|
|
return true;
|
|
}
|
|
|
|
ClientlessPerceptionSnapshot const* ClientlessPerceptionMgr::GetSnapshot(uint32 characterGuid) const
|
|
{
|
|
auto itr = _records.find(characterGuid);
|
|
return itr != _records.end() ? &itr->second.Snapshot : nullptr;
|
|
}
|
|
|
|
bool ClientlessPerceptionMgr::Matches(
|
|
ClientlessPerceivedEntity const& entity,
|
|
ClientlessPerceptionQuery const& query)
|
|
{
|
|
if (query.FilterType && entity.Type != query.Type)
|
|
return false;
|
|
|
|
if (query.FilterRelation && entity.Relation != query.Relation)
|
|
return false;
|
|
|
|
if (query.RequiredCapabilities &&
|
|
(entity.Capabilities & query.RequiredCapabilities) != query.RequiredCapabilities)
|
|
return false;
|
|
|
|
if (query.ForbiddenCapabilities &&
|
|
(entity.Capabilities & query.ForbiddenCapabilities) != 0)
|
|
return false;
|
|
|
|
if (query.RequireAlive && !entity.Alive)
|
|
return false;
|
|
|
|
if (query.RequireAttackable && !entity.Attackable)
|
|
return false;
|
|
|
|
if (query.RequireLineOfSight && !entity.InLineOfSight)
|
|
return false;
|
|
|
|
if (query.MaxDistance > 0.0f && entity.Distance > query.MaxDistance)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
ClientlessPerceivedEntity const* ClientlessPerceptionMgr::FindNearest(
|
|
uint32 characterGuid,
|
|
ClientlessPerceptionQuery const& query) const
|
|
{
|
|
ClientlessPerceptionSnapshot const* snapshot = GetSnapshot(characterGuid);
|
|
if (!snapshot)
|
|
return nullptr;
|
|
|
|
for (ClientlessPerceivedEntity const& entity : snapshot->Entities)
|
|
if (Matches(entity, query))
|
|
return &entity;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void ClientlessPerceptionMgr::Update(uint32 diff)
|
|
{
|
|
for (auto const& [characterGuid, session] : sWorld->GetAllClientlessSessions())
|
|
{
|
|
if (!session || !sClientlessPlayerMgr->IsManaged(characterGuid))
|
|
continue;
|
|
|
|
Player* player = session->GetPlayer();
|
|
if (!player || !player->IsInWorld())
|
|
continue;
|
|
|
|
Record& record = _records[characterGuid];
|
|
record.Snapshot.ScanAgeMs += diff;
|
|
|
|
if (record.ScanTimerMs > diff)
|
|
{
|
|
record.ScanTimerMs -= diff;
|
|
continue;
|
|
}
|
|
|
|
ScanNow(
|
|
player,
|
|
record.Snapshot.Radius > 0.0f
|
|
? record.Snapshot.Radius
|
|
: DEFAULT_SCAN_RADIUS);
|
|
}
|
|
}
|