#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 #include #include 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 objects; Trinity::AllWorldObjectsInRange check(player, radius); Trinity::WorldObjectListSearcher 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); } }