From 70ea8ab00b82bdd8696ac7a1ee9264f680d337a5 Mon Sep 17 00:00:00 2001 From: sogladev Date: Sun, 12 Apr 2026 22:56:33 +0200 Subject: [PATCH] feat(Core/Conditions): Add more support for object visibility (#25415) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/server/game/Conditions/ConditionMgr.cpp | 130 +++++++++++++++++++- src/server/game/Conditions/ConditionMgr.h | 19 +-- src/server/game/Entities/Object/Object.cpp | 15 ++- src/server/game/Entities/Player/Player.cpp | 7 ++ src/server/game/Entities/Player/Player.h | 1 + 5 files changed, 152 insertions(+), 20 deletions(-) diff --git a/src/server/game/Conditions/ConditionMgr.cpp b/src/server/game/Conditions/ConditionMgr.cpp index 20012de6a..a577bf9a6 100644 --- a/src/server/game/Conditions/ConditionMgr.cpp +++ b/src/server/game/Conditions/ConditionMgr.cpp @@ -836,7 +836,7 @@ uint32 Condition::GetMaxAvailableConditionTargets() case CONDITION_SOURCE_TYPE_GOSSIP_MENU_OPTION: case CONDITION_SOURCE_TYPE_NPC_VENDOR: case CONDITION_SOURCE_TYPE_SPELL_PROC: - case CONDITION_SOURCE_TYPE_CREATURE_VISIBILITY: + case CONDITION_SOURCE_TYPE_OBJECT_VISIBILITY: return 2; default: break; @@ -975,12 +975,12 @@ bool ConditionMgr::CanHaveSourceGroupSet(ConditionSourceType sourceType) const { return (sourceType == CONDITION_SOURCE_TYPE_CREATURE_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_DISENCHANT_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_FISHING_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_GAMEOBJECT_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_ITEM_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_MAIL_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_MILLING_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_PICKPOCKETING_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_PROSPECTING_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_REFERENCE_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_SKINNING_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_SPELL_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_GOSSIP_MENU || sourceType == CONDITION_SOURCE_TYPE_GOSSIP_MENU_OPTION || sourceType == CONDITION_SOURCE_TYPE_VEHICLE_SPELL || sourceType == CONDITION_SOURCE_TYPE_GOSSIP_HELLO || - sourceType == CONDITION_SOURCE_TYPE_SPELL_IMPLICIT_TARGET || sourceType == CONDITION_SOURCE_TYPE_SPELL_CLICK_EVENT || sourceType == CONDITION_SOURCE_TYPE_SMART_EVENT || sourceType == CONDITION_SOURCE_TYPE_NPC_VENDOR || sourceType == CONDITION_SOURCE_TYPE_PLAYER_LOOT_TEMPLATE); + sourceType == CONDITION_SOURCE_TYPE_SPELL_IMPLICIT_TARGET || sourceType == CONDITION_SOURCE_TYPE_SPELL_CLICK_EVENT || sourceType == CONDITION_SOURCE_TYPE_SMART_EVENT || sourceType == CONDITION_SOURCE_TYPE_NPC_VENDOR || sourceType == CONDITION_SOURCE_TYPE_PLAYER_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_OBJECT_VISIBILITY); } bool ConditionMgr::CanHaveSourceIdSet(ConditionSourceType sourceType) const { - return (sourceType == CONDITION_SOURCE_TYPE_SMART_EVENT); + return (sourceType == CONDITION_SOURCE_TYPE_SMART_EVENT || sourceType == CONDITION_SOURCE_TYPE_OBJECT_VISIBILITY); } ConditionList ConditionMgr::GetConditionsForNotGroupedEntry(ConditionSourceType sourceType, uint32 entry) @@ -1073,6 +1073,43 @@ ConditionList ConditionMgr::GetConditionsForNpcVendorEvent(uint32 creatureId, ui return cond; } +ConditionList ConditionMgr::GetConditionsForObjectVisibility(const WorldObject* object) const +{ + ConditionList cond; + + if (!object->IsCreature() && !object->IsGameObject()) + return cond; + + uint32 entry = object->GetEntry(); + uint32 sourceGroup = object->IsGameObject() ? 1 : 0; + + auto itrBucket = ObjectVisibilityConditionStore.find(std::make_pair(entry, sourceGroup)); + if (itrBucket == ObjectVisibilityConditionStore.end()) + return cond; + + uint32 guid = object->IsGameObject() ? object->ToGameObject()->GetSpawnId() : object->ToCreature()->GetSpawnId(); + + auto const& sourceIdConditions = itrBucket->second; + + auto itrGuid = sourceIdConditions.find(guid); + if (itrGuid != sourceIdConditions.end()) + { + cond.insert(cond.end(), itrGuid->second.begin(), itrGuid->second.end()); + LOG_DEBUG("condition", "GetConditionsForObjectVisibility: found guid-level conditions for sourceGroup {} entry {} guid {}", sourceGroup, entry, guid); + } + else + { + auto itrEntry = sourceIdConditions.find(0); + if (itrEntry != sourceIdConditions.end()) + { + cond.insert(cond.end(), itrEntry->second.begin(), itrEntry->second.end()); + LOG_DEBUG("condition", "GetConditionsForObjectVisibility: found entry-level conditions for sourceGroup {} entry {}", sourceGroup, entry); + } + } + + return cond; +} + void ConditionMgr::LoadConditions(bool isReload) { uint32 oldMSTime = getMSTime(); @@ -1223,7 +1260,8 @@ void ConditionMgr::LoadConditions(bool isReload) cond->ErrorTextId = 0; } - if (cond->SourceGroup || cond->SourceType == CONDITION_SOURCE_TYPE_PLAYER_LOOT_TEMPLATE) + if (cond->SourceGroup || cond->SourceType == CONDITION_SOURCE_TYPE_PLAYER_LOOT_TEMPLATE + || cond->SourceType == CONDITION_SOURCE_TYPE_OBJECT_VISIBILITY) { bool valid = false; // handle grouped conditions @@ -1304,6 +1342,13 @@ void ConditionMgr::LoadConditions(bool isReload) ++count; continue; } + case CONDITION_SOURCE_TYPE_OBJECT_VISIBILITY: + { + ObjectVisibilityConditionStore[std::make_pair(uint32(cond->SourceEntry), cond->SourceGroup)][cond->SourceId].push_back(cond); + valid = true; + ++count; + continue; // do not add to AllocatedMemoryStore to avoid double-deleting + } case CONDITION_SOURCE_TYPE_PLAYER_LOOT_TEMPLATE: { valid = addToLootTemplate(cond, LootTemplates_Player.GetLootForConditionFill(cond->SourceGroup)); @@ -1866,6 +1911,68 @@ bool ConditionMgr::isSourceTypeValid(Condition* cond) } break; } + case CONDITION_SOURCE_TYPE_OBJECT_VISIBILITY: + { + if (cond->SourceGroup > 1) + { + LOG_ERROR("sql.sql", "CONDITION_SOURCE_TYPE_OBJECT_VISIBILITY has invalid SourceGroup {} for SourceEntry {}, expected 0 (creature) or 1 (gameobject)", cond->SourceGroup, cond->SourceEntry); + return false; + } + + if (cond->SourceEntry <= 0) + { + LOG_ERROR("sql.sql", "CONDITION_SOURCE_TYPE_OBJECT_VISIBILITY has invalid SourceEntry {}, expected a positive entry id.", cond->SourceEntry); + return false; + } + + if (cond->SourceGroup == 0 && !sObjectMgr->GetCreatureTemplate(uint32(cond->SourceEntry))) + { + LOG_ERROR("sql.sql", "CONDITION_SOURCE_TYPE_OBJECT_VISIBILITY points to non-existing creature entry {}, skipped.", cond->SourceEntry); + return false; + } + + if (cond->SourceGroup == 1 && !sObjectMgr->GetGameObjectTemplate(uint32(cond->SourceEntry))) + { + LOG_ERROR("sql.sql", "CONDITION_SOURCE_TYPE_OBJECT_VISIBILITY points to non-existing gameobject entry {}, skipped.", cond->SourceEntry); + return false; + } + + if (cond->SourceId) + { + if (cond->SourceGroup == 0) + { + CreatureData const* data = sObjectMgr->GetCreatureData(cond->SourceId); + if (!data) + { + LOG_ERROR("sql.sql", "CONDITION_SOURCE_TYPE_OBJECT_VISIBILITY points to non-existing creature guid {}, skipped.", cond->SourceId); + return false; + } + + if (data->id1 != uint32(cond->SourceEntry)) + { + LOG_ERROR("sql.sql", "CONDITION_SOURCE_TYPE_OBJECT_VISIBILITY has creature guid {} that does not match SourceEntry {}, skipped.", cond->SourceId, cond->SourceEntry); + return false; + } + } + else + { + GameObjectData const* data = sObjectMgr->GetGameObjectData(cond->SourceId); + if (!data) + { + LOG_ERROR("sql.sql", "CONDITION_SOURCE_TYPE_OBJECT_VISIBILITY points to non-existing gameobject guid {}, skipped.", cond->SourceId); + return false; + } + + if (data->id != uint32(cond->SourceEntry)) + { + LOG_ERROR("sql.sql", "CONDITION_SOURCE_TYPE_OBJECT_VISIBILITY has gameobject guid {} that does not match SourceEntry {}, skipped.", cond->SourceId, cond->SourceEntry); + return false; + } + } + } + + break; + } case CONDITION_SOURCE_TYPE_GOSSIP_MENU: case CONDITION_SOURCE_TYPE_GOSSIP_MENU_OPTION: case CONDITION_SOURCE_TYPE_SMART_EVENT: @@ -2593,6 +2700,21 @@ void ConditionMgr::Clean() NpcVendorConditionContainerStore.clear(); + for (auto& itr : ObjectVisibilityConditionStore) + { + for (auto& sourceIdConds : itr.second) + { + for (Condition* cond : sourceIdConds.second) + delete cond; + + sourceIdConds.second.clear(); + } + + itr.second.clear(); + } + + ObjectVisibilityConditionStore.clear(); + // this is a BIG hack, feel free to fix it if you can figure out the ConditionMgr ;) for (std::list::const_iterator itr = AllocatedMemoryStore.begin(); itr != AllocatedMemoryStore.end(); ++itr) delete *itr; diff --git a/src/server/game/Conditions/ConditionMgr.h b/src/server/game/Conditions/ConditionMgr.h index 5ff412854..585048a83 100644 --- a/src/server/game/Conditions/ConditionMgr.h +++ b/src/server/game/Conditions/ConditionMgr.h @@ -152,7 +152,7 @@ enum ConditionSourceType CONDITION_SOURCE_TYPE_GRAVEYARD = 27, // don't use on 3.3.5a CONDITION_SOURCE_TYPE_PLAYER_LOOT_TEMPLATE = 28, CONDITION_SOURCE_TYPE_CREATURE_RESPAWN = 29, - CONDITION_SOURCE_TYPE_CREATURE_VISIBILITY = 30, + CONDITION_SOURCE_TYPE_OBJECT_VISIBILITY = 30, CONDITION_SOURCE_TYPE_MAX = 31 // placeholder }; @@ -198,7 +198,7 @@ struct Condition ConditionSourceType SourceType; //SourceTypeOrReferenceId uint32 SourceGroup; int32 SourceEntry; - uint32 SourceId; // So far, only used in CONDITION_SOURCE_TYPE_SMART_EVENT + uint32 SourceId; // Used in CONDITION_SOURCE_TYPE_SMART_EVENT and CONDITION_SOURCE_TYPE_OBJECT_VISIBILITY uint32 ElseGroup; ConditionTypes ConditionType; //ConditionTypeOrReference uint32 ConditionValue1; @@ -242,6 +242,7 @@ typedef std::map ConditionContainer typedef std::map CreatureSpellConditionContainer; typedef std::map NpcVendorConditionContainer; typedef std::map, ConditionTypeContainer> SmartEventConditionContainer; +typedef std::map, std::map> ObjectVisibilityConditionContainer; typedef std::map ConditionReferenceContainer;//only used for references @@ -269,6 +270,7 @@ public: ConditionList GetConditionsForSmartEvent(int32 entryOrGuid, uint32 eventId, uint32 sourceType); ConditionList GetConditionsForVehicleSpell(uint32 creatureId, uint32 spellId); ConditionList GetConditionsForNpcVendorEvent(uint32 creatureId, uint32 itemId); + ConditionList GetConditionsForObjectVisibility(const WorldObject* object) const; private: bool isSourceTypeValid(Condition* cond); @@ -281,12 +283,13 @@ private: void Clean(); // free up resources std::list AllocatedMemoryStore; // some garbage collection :) - ConditionContainer ConditionStore; - ConditionReferenceContainer ConditionReferenceStore; - CreatureSpellConditionContainer VehicleSpellConditionStore; - CreatureSpellConditionContainer SpellClickEventConditionStore; - NpcVendorConditionContainer NpcVendorConditionContainerStore; - SmartEventConditionContainer SmartEventConditionStore; + ConditionContainer ConditionStore; + ConditionReferenceContainer ConditionReferenceStore; + CreatureSpellConditionContainer VehicleSpellConditionStore; + CreatureSpellConditionContainer SpellClickEventConditionStore; + NpcVendorConditionContainer NpcVendorConditionContainerStore; + SmartEventConditionContainer SmartEventConditionStore; + ObjectVisibilityConditionContainer ObjectVisibilityConditionStore; }; #define sConditionMgr ConditionMgr::instance() diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 9cd58f197..793cb8ed2 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -1770,24 +1770,23 @@ bool WorldObject::CanSeeOrDetect(WorldObject const* obj, bool ignoreStealth, boo if (Player const* player = ToPlayer()) { if (cObj->IsAIEnabled && !cObj->AI()->CanBeSeen(player)) - { return false; - } - ConditionList conditions = sConditionMgr->GetConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_CREATURE_VISIBILITY, cObj->GetEntry()); - if (!sConditionMgr->IsObjectMeetToConditions((WorldObject*)this, (WorldObject*)obj, conditions)) - { + if (!player->CanSeeObjectByVisibilityConditions(obj)) return false; - } } } // Gameobject scripts if (GameObject const* goObj = obj->ToGameObject()) { - if (ToPlayer() && !goObj->AI()->CanBeSeen(ToPlayer())) + if (Player const* player = ToPlayer()) { - return false; + if (!goObj->AI()->CanBeSeen(player)) + return false; + + if (!player->CanSeeObjectByVisibilityConditions(obj)) + return false; } } diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 674918aef..b7dcf8724 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -14338,6 +14338,13 @@ bool Player::CanSeeSpellClickOn(Creature const* c) const return false; } +bool Player::CanSeeObjectByVisibilityConditions(WorldObject const* object) const +{ + ConditionList conds = sConditionMgr->GetConditionsForObjectVisibility(object); + ConditionSourceInfo info = ConditionSourceInfo(const_cast(this), const_cast(object)); + return sConditionMgr->IsObjectMeetToConditions(info, conds); +} + /** * @brief Checks if any vendor option is available in the gossip menu tree for a given creature. * diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 45c5dc553..2eb10a110 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -2581,6 +2581,7 @@ public: //bool isActiveObject() const { return true; } bool CanSeeSpellClickOn(Creature const* creature) const; + [[nodiscard]] bool CanSeeObjectByVisibilityConditions(WorldObject const* object) const; [[nodiscard]] bool CanSeeVendor(Creature const* creature) const; [[nodiscard]] bool CanSeeTrainer(Creature const* creature) const;