aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMeji <alvaro.megias@outlook.com>2025-06-28 01:38:43 +0200
committerGitHub <noreply@github.com>2025-06-28 01:38:43 +0200
commit138b1b1a21ff871b54f1f2bd815de30a93b803eb (patch)
tree2d1f454eb9173417e622d8fcfea2901f41246a67
parent60400d25f5fff2dabd4aa74bbdbc0d2370360a35 (diff)
Core/SpawnTracking: Allow linking a spawn to more than one quest objective (#30995)
-rw-r--r--sql/updates/world/master/2025_06_28_00_world.sql1
-rw-r--r--src/server/game/Entities/Creature/Creature.cpp8
-rw-r--r--src/server/game/Entities/GameObject/GameObject.cpp8
-rw-r--r--src/server/game/Entities/Object/Updates/UpdateFields.cpp8
-rw-r--r--src/server/game/Entities/Object/Updates/UpdateFields.h2
-rw-r--r--src/server/game/Entities/Object/Updates/ViewerDependentValues.h76
-rw-r--r--src/server/game/Entities/Player/Player.cpp85
-rw-r--r--src/server/game/Entities/Player/Player.h3
-rw-r--r--src/server/game/Globals/ObjectMgr.cpp38
-rw-r--r--src/server/game/Handlers/QuestHandler.cpp4
-rw-r--r--src/server/game/Maps/SpawnData.h2
11 files changed, 180 insertions, 55 deletions
diff --git a/sql/updates/world/master/2025_06_28_00_world.sql b/sql/updates/world/master/2025_06_28_00_world.sql
new file mode 100644
index 00000000000..3267e8a0979
--- /dev/null
+++ b/sql/updates/world/master/2025_06_28_00_world.sql
@@ -0,0 +1 @@
+ALTER TABLE `spawn_tracking` CHANGE COLUMN `QuestObjectiveId` `QuestObjectiveIds` MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp
index 8e982a78800..964c6862faf 100644
--- a/src/server/game/Entities/Creature/Creature.cpp
+++ b/src/server/game/Entities/Creature/Creature.cpp
@@ -629,7 +629,9 @@ bool Creature::UpdateEntry(uint32 entry, CreatureData const* data /*= nullptr*/,
ReplaceAllDynamicFlags(UNIT_DYNFLAG_NONE);
- SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::StateWorldEffectsQuestObjectiveID), data ? data->spawnTrackingQuestObjectiveId : 0);
+ // Set StateWorldEffectsQuestObjectiveID if there is only one linked objective for this creature
+ if (data && data->spawnTrackingQuestObjectives.size() == 1)
+ SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::StateWorldEffectsQuestObjectiveID), data->spawnTrackingQuestObjectives.front());
SetCanDualWield(cInfo->flags_extra & CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK);
@@ -3207,9 +3209,9 @@ SpawnTrackingStateData const* Creature::GetSpawnTrackingStateDataForPlayer(Playe
if (CreatureData const* data = GetCreatureData())
{
- if (data->spawnTrackingQuestObjectiveId && data->spawnTrackingData)
+ if (data->spawnTrackingData && !data->spawnTrackingQuestObjectives.empty())
{
- SpawnTrackingState state = player->GetSpawnTrackingStateByObjective(data->spawnTrackingData->SpawnTrackingId, data->spawnTrackingQuestObjectiveId);
+ SpawnTrackingState state = player->GetSpawnTrackingStateByObjectives(data->spawnTrackingData->SpawnTrackingId, data->spawnTrackingQuestObjectives);
return &data->spawnTrackingStates[AsUnderlyingType(state)];
}
}
diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp
index 731bc5c7991..7e15231016a 100644
--- a/src/server/game/Entities/GameObject/GameObject.cpp
+++ b/src/server/game/Entities/GameObject/GameObject.cpp
@@ -1967,7 +1967,9 @@ bool GameObject::LoadFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap
PhasingHandler::InitDbPhaseShift(GetPhaseShift(), data->phaseUseFlags, data->phaseId, data->phaseGroup);
PhasingHandler::InitDbVisibleMapId(GetPhaseShift(), data->terrainSwapMap);
- SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::StateWorldEffectsQuestObjectiveID), data ? data->spawnTrackingQuestObjectiveId : 0);
+ // Set StateWorldEffectsQuestObjectiveID if there is only one linked objective for this gameobject
+ if (data && data->spawnTrackingQuestObjectives.size() == 1)
+ SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::StateWorldEffectsQuestObjectiveID), data->spawnTrackingQuestObjectives.front());
if (data->spawntimesecs >= 0)
{
@@ -3592,9 +3594,9 @@ SpawnTrackingStateData const* GameObject::GetSpawnTrackingStateDataForPlayer(Pla
if (GameObjectData const* data = GetGameObjectData())
{
- if (data->spawnTrackingQuestObjectiveId && data->spawnTrackingData)
+ if (data->spawnTrackingData && !data->spawnTrackingQuestObjectives.empty())
{
- SpawnTrackingState state = player->GetSpawnTrackingStateByObjective(data->spawnTrackingData->SpawnTrackingId, data->spawnTrackingQuestObjectiveId);
+ SpawnTrackingState state = player->GetSpawnTrackingStateByObjectives(data->spawnTrackingData->SpawnTrackingId, data->spawnTrackingQuestObjectives);
return &data->spawnTrackingStates[AsUnderlyingType(state)];
}
}
diff --git a/src/server/game/Entities/Object/Updates/UpdateFields.cpp b/src/server/game/Entities/Object/Updates/UpdateFields.cpp
index 7986d550120..419e2eca445 100644
--- a/src/server/game/Entities/Object/Updates/UpdateFields.cpp
+++ b/src/server/game/Entities/Object/Updates/UpdateFields.cpp
@@ -939,7 +939,7 @@ void UnitData::WriteCreate(ByteBuffer& data, EnumFlag<UpdateFieldFlag> fieldVisi
data << uint32(ViewerDependentValue<StateAnimKitIDTag>::GetValue(this, owner, receiver));
stateWorldEffectIDs = ViewerDependentValue<StateWorldEffectIDsTag>::GetValue(this, owner, receiver);
data << uint32(stateWorldEffectIDs->size());
- data << uint32(StateWorldEffectsQuestObjectiveID);
+ data << uint32(ViewerDependentValue<StateWorldEffectsQuestObjectiveIDTag>::GetValue(this, owner, receiver));
data << int32(SpellOverrideNameID);
for (uint32 i = 0; i < stateWorldEffectIDs->size(); ++i)
{
@@ -1275,7 +1275,7 @@ void UnitData::WriteUpdate(ByteBuffer& data, Mask const& changesMask, bool ignor
}
if (changesMask[12])
{
- data << uint32(StateWorldEffectsQuestObjectiveID);
+ data << uint32(ViewerDependentValue<StateWorldEffectsQuestObjectiveIDTag>::GetValue(this, owner, receiver));
}
if (changesMask[13])
{
@@ -6496,7 +6496,7 @@ void GameObjectData::WriteCreate(ByteBuffer& data, EnumFlag<UpdateFieldFlag> fie
data << uint32(ViewerDependentValue<SpawnTrackingStateAnimKitIDTag>::GetValue(this, owner, receiver));
stateWorldEffectIDs = ViewerDependentValue<StateWorldEffectIDsTag>::GetValue(this, owner, receiver);
data << uint32(stateWorldEffectIDs->size());
- data << uint32(StateWorldEffectsQuestObjectiveID);
+ data << uint32(ViewerDependentValue<StateWorldEffectsQuestObjectiveIDTag>::GetValue(this, owner, receiver));
for (uint32 i = 0; i < stateWorldEffectIDs->size(); ++i)
{
data << uint32((*stateWorldEffectIDs)[i]);
@@ -6617,7 +6617,7 @@ void GameObjectData::WriteUpdate(ByteBuffer& data, Mask const& changesMask, bool
}
if (changesMask[9])
{
- data << uint32(StateWorldEffectsQuestObjectiveID);
+ data << uint32(ViewerDependentValue<StateWorldEffectsQuestObjectiveIDTag>::GetValue(this, owner, receiver));
}
if (changesMask[10])
{
diff --git a/src/server/game/Entities/Object/Updates/UpdateFields.h b/src/server/game/Entities/Object/Updates/UpdateFields.h
index 853fc374f76..f14700d4940 100644
--- a/src/server/game/Entities/Object/Updates/UpdateFields.h
+++ b/src/server/game/Entities/Object/Updates/UpdateFields.h
@@ -279,6 +279,7 @@ struct UnitData : public IsUpdateFieldStructureTag, public HasChangesMask<222>
UpdateField<uint32, 0, 11> StateAnimKitID;
struct StateAnimKitIDTag : ViewerDependentValueTag<uint32> {};
UpdateField<uint32, 0, 12> StateWorldEffectsQuestObjectiveID;
+ struct StateWorldEffectsQuestObjectiveIDTag : ViewerDependentValueTag<uint32> {};
UpdateField<int32, 0, 13> SpellOverrideNameID;
UpdateField<ObjectGuid, 0, 14> Charm;
UpdateField<ObjectGuid, 0, 15> Summon;
@@ -1213,6 +1214,7 @@ struct GameObjectData : public IsUpdateFieldStructureTag, public HasChangesMask<
UpdateField<uint32, 0, 8> SpawnTrackingStateAnimKitID;
struct SpawnTrackingStateAnimKitIDTag : ViewerDependentValueTag<uint32> {};
UpdateField<uint32, 0, 9> StateWorldEffectsQuestObjectiveID;
+ struct StateWorldEffectsQuestObjectiveIDTag : ViewerDependentValueTag<uint32> {};
UpdateField<ObjectGuid, 0, 10> CreatedBy;
UpdateField<ObjectGuid, 0, 11> GuildGUID;
UpdateField<uint32, 0, 12> Flags;
diff --git a/src/server/game/Entities/Object/Updates/ViewerDependentValues.h b/src/server/game/Entities/Object/Updates/ViewerDependentValues.h
index 73400ed23d1..dacf78b0b3f 100644
--- a/src/server/game/Entities/Object/Updates/ViewerDependentValues.h
+++ b/src/server/game/Entities/Object/Updates/ViewerDependentValues.h
@@ -145,8 +145,8 @@ public:
dynFlags |= GO_DYNFLAG_LO_NO_INTERACT;
if (SpawnMetadata const* data = sObjectMgr->GetSpawnMetadata(SPAWN_TYPE_GAMEOBJECT, gameObject->GetSpawnId()))
- if (data->spawnTrackingQuestObjectiveId && data->spawnTrackingData)
- if (receiver->GetSpawnTrackingStateByObjective(data->spawnTrackingData->SpawnTrackingId, data->spawnTrackingQuestObjectiveId) != SpawnTrackingState::Active)
+ if (data->spawnTrackingData && !data->spawnTrackingQuestObjectives.empty())
+ if (receiver->GetSpawnTrackingStateByObjectives(data->spawnTrackingData->SpawnTrackingId, data->spawnTrackingQuestObjectives) != SpawnTrackingState::Active)
dynFlags &= ~GO_DYNFLAG_LO_ACTIVATE;
}
@@ -278,6 +278,42 @@ public:
};
template<>
+class ViewerDependentValue<UF::UnitData::StateWorldEffectsQuestObjectiveIDTag>
+{
+public:
+ using value_type = UF::UnitData::StateWorldEffectsQuestObjectiveIDTag::value_type;
+
+ static value_type GetValue(UF::UnitData const* unitData, Unit const* unit, Player const* receiver)
+ {
+ value_type stateWorldEffectsQuestObjectiveId = unitData->StateWorldEffectsQuestObjectiveID;
+
+ if (!stateWorldEffectsQuestObjectiveId && unit->IsCreature())
+ {
+ if (CreatureData const* data = unit->ToCreature()->GetCreatureData())
+ {
+ auto itr = data->spawnTrackingQuestObjectives.begin();
+ auto end = data->spawnTrackingQuestObjectives.end();
+ if (itr != end)
+ {
+ // If there is no valid objective for player, fill UF with first objective (if any)
+ stateWorldEffectsQuestObjectiveId = *itr;
+ while (++itr != end)
+ {
+ if (receiver->GetSpawnTrackingStateByObjective(data->spawnTrackingData->SpawnTrackingId, *itr) != SpawnTrackingState::Active)
+ continue;
+
+ stateWorldEffectsQuestObjectiveId = *itr;
+ break;
+ }
+ }
+ }
+ }
+
+ return stateWorldEffectsQuestObjectiveId;
+ }
+};
+
+template<>
class ViewerDependentValue<UF::UnitData::FactionTemplateTag>
{
public:
@@ -539,6 +575,42 @@ public:
};
template<>
+class ViewerDependentValue<UF::GameObjectData::StateWorldEffectsQuestObjectiveIDTag>
+{
+public:
+ using value_type = UF::GameObjectData::StateWorldEffectsQuestObjectiveIDTag::value_type;
+
+ static value_type GetValue(UF::GameObjectData const* gameObjectData, GameObject const* gameObject, Player const* receiver)
+ {
+ value_type stateWorldEffectsQuestObjectiveId = gameObjectData->StateWorldEffectsQuestObjectiveID;
+
+ if (!stateWorldEffectsQuestObjectiveId)
+ {
+ if (::GameObjectData const* data = gameObject->GetGameObjectData())
+ {
+ auto itr = data->spawnTrackingQuestObjectives.begin();
+ auto end = data->spawnTrackingQuestObjectives.end();
+ if (itr != end)
+ {
+ // If there is no valid objective for player, fill UF with first objective (if any)
+ stateWorldEffectsQuestObjectiveId = *itr;
+ while (++itr != end)
+ {
+ if (receiver->GetSpawnTrackingStateByObjective(data->spawnTrackingData->SpawnTrackingId, *itr) != SpawnTrackingState::Active)
+ continue;
+
+ stateWorldEffectsQuestObjectiveId = *itr;
+ break;
+ }
+ }
+ }
+ }
+
+ return stateWorldEffectsQuestObjectiveId;
+ }
+};
+
+template<>
class ViewerDependentValue<UF::GameObjectData::FlagsTag>
{
public:
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index f05a69123f1..351a4758222 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -16852,38 +16852,40 @@ void Player::UpdateQuestObjectiveProgress(QuestObjectiveType objectiveType, int3
}
}
- if (data && data->spawnTrackingQuestObjectiveId && data->spawnTrackingData)
+ if (data && data->spawnTrackingData && !data->spawnTrackingQuestObjectives.empty())
{
- if (objective->ID == data->spawnTrackingQuestObjectiveId)
+ for (uint32 spawnTrackingQuestObjectiveId : data->spawnTrackingQuestObjectives)
{
- // Store spawn tracking to return correct state in Player::GetSpawnTrackingStateByObjective
- QuestStatusData& questStatus = objectiveItr.second.QuestStatusItr->second;
- questStatus.SpawnTrackingList.insert(std::make_pair(objective->StorageIndex, data->spawnTrackingData->SpawnTrackingId));
-
- // Send QuestPOIUpdateResponse for every spawn linked to same SpawnTrackingId
- for (auto const& [spawnTrackingId, data] : sObjectMgr->GetSpawnMetadataForSpawnTracking(data->spawnTrackingData->SpawnTrackingId))
+ if (objective->ID == spawnTrackingQuestObjectiveId)
{
- SpawnData const* spawnData = data->ToSpawnData();
- if (!spawnData)
- continue;
+ // Store spawn tracking to return correct state in Player::GetSpawnTrackingStateByObjective
+ QuestStatusData& questStatus = objectiveItr.second.QuestStatusItr->second;
+ questStatus.SpawnTrackingList.insert(std::make_pair(objective->StorageIndex, data->spawnTrackingData->SpawnTrackingId));
- WorldPackets::Quest::QuestPOIUpdateResponse response;
+ // Send QuestPOIUpdateResponse for every spawn linked to same SpawnTrackingId
+ for (auto const& [spawnTrackingId, data] : sObjectMgr->GetSpawnMetadataForSpawnTracking(data->spawnTrackingData->SpawnTrackingId))
+ {
+ SpawnData const* spawnData = data->ToSpawnData();
+ if (!spawnData)
+ continue;
- WorldPackets::Quest::SpawnTrackingResponseInfo responseInfo;
- responseInfo.SpawnTrackingID = data->spawnTrackingData->SpawnTrackingId;
- responseInfo.ObjectID = spawnData->id;
- responseInfo.PhaseID = spawnData->phaseId;
- responseInfo.PhaseGroupID = spawnData->phaseGroup;
- responseInfo.PhaseUseFlags = spawnData->phaseUseFlags;
+ WorldPackets::Quest::QuestPOIUpdateResponse response;
- SpawnTrackingState state = GetSpawnTrackingStateByObjective(data->spawnTrackingData->SpawnTrackingId, objective->ID);
- responseInfo.Visible = data->spawnTrackingStates[AsUnderlyingType(state)].Visible;
+ WorldPackets::Quest::SpawnTrackingResponseInfo& responseInfo = response.SpawnTrackingResponses.emplace_back();
+ responseInfo.SpawnTrackingID = data->spawnTrackingData->SpawnTrackingId;
+ responseInfo.ObjectID = spawnData->id;
+ responseInfo.PhaseID = spawnData->phaseId;
+ responseInfo.PhaseGroupID = spawnData->phaseGroup;
+ responseInfo.PhaseUseFlags = spawnData->phaseUseFlags;
- response.SpawnTrackingResponses.push_back(std::move(responseInfo));
- SendDirectMessage(response.Write());
- }
+ SpawnTrackingState state = GetSpawnTrackingStateByObjectives(data->spawnTrackingData->SpawnTrackingId, data->spawnTrackingQuestObjectives);
+ responseInfo.Visible = data->spawnTrackingStates[AsUnderlyingType(state)].Visible;
- anyObjectiveChangedSpawnTrackingState = true;
+ SendDirectMessage(response.Write());
+ }
+
+ anyObjectiveChangedSpawnTrackingState = true;
+ }
}
}
@@ -17508,16 +17510,38 @@ void Player::SendForceSpawnTrackingUpdate(uint32 questId) const
}
}
-QuestObjective const* Player::GetActiveQuestObjectiveForForSpawnTracking(uint32 spawnTrackingId) const
+QuestObjective const* Player::GetActiveQuestObjectiveForSpawnTracking(uint32 spawnTrackingId) const
{
if (std::vector<QuestObjective const*> const* questObjectiveList = sObjectMgr->GetSpawnTrackingQuestObjectiveList(spawnTrackingId))
for (QuestObjective const* questObjective : *questObjectiveList)
- if (FindQuestSlot(questObjective->QuestID) < MAX_QUEST_LOG_SIZE)
+ if (IsQuestObjectiveCompletable(questObjective->QuestID, questObjective->ID))
return questObjective;
return nullptr;
}
+SpawnTrackingState Player::GetSpawnTrackingStateByObjectives(uint32 spawnTrackingId, std::vector<uint32> const& questObjectives) const
+{
+ if (spawnTrackingId && !questObjectives.empty())
+ {
+ bool hasAnyQuestObjectiveCompletable = false;
+ for (uint32 questObjectiveId : questObjectives)
+ {
+ SpawnTrackingState state = GetSpawnTrackingStateByObjective(spawnTrackingId, questObjectiveId);
+ if (state == SpawnTrackingState::Complete)
+ return SpawnTrackingState::Complete;
+
+ if (state == SpawnTrackingState::Active)
+ hasAnyQuestObjectiveCompletable = true;
+ }
+
+ if (hasAnyQuestObjectiveCompletable)
+ return SpawnTrackingState::Active;
+ }
+
+ return SpawnTrackingState::None;
+}
+
SpawnTrackingState Player::GetSpawnTrackingStateByObjective(uint32 spawnTrackingId, uint32 questObjectiveId) const
{
if (spawnTrackingId && questObjectiveId && sObjectMgr->IsQuestObjectiveForSpawnTracking(spawnTrackingId, questObjectiveId))
@@ -17526,7 +17550,8 @@ SpawnTrackingState Player::GetSpawnTrackingStateByObjective(uint32 spawnTracking
{
if (IsQuestRewarded(questObjective->QuestID) || IsQuestObjectiveComplete(questObjective->QuestID, questObjective->ID))
return SpawnTrackingState::Complete;
- else if (GetQuestStatus(questObjective->QuestID) != QUEST_STATUS_NONE && IsQuestObjectiveCompletable(questObjective->QuestID, questObjective->ID))
+
+ if (GetQuestStatus(questObjective->QuestID) != QUEST_STATUS_NONE && IsQuestObjectiveCompletable(questObjective->QuestID, questObjective->ID))
{
auto itr = m_QuestStatus.find(questObjective->QuestID);
if (itr != m_QuestStatus.end() && itr->second.Slot < MAX_QUEST_LOG_SIZE)
@@ -25742,13 +25767,14 @@ void Player::UpdateVisibleObjectInteractions(bool allUnits, bool onlySpellClicks
UF::GameObjectData::Base goMask;
SpawnMetadata const* data = sObjectMgr->GetSpawnMetadata(SPAWN_TYPE_GAMEOBJECT, gameObject->GetSpawnId());
- if (data && data->spawnTrackingQuestObjectiveId && data->spawnTrackingData)
+ if (data && data->spawnTrackingData && !data->spawnTrackingQuestObjectives.empty())
{
objMask.MarkChanged(&UF::ObjectData::DynamicFlags);
goMask.MarkChanged(&UF::GameObjectData::StateWorldEffectIDs);
goMask.MarkChanged(&UF::GameObjectData::StateSpellVisualID);
goMask.MarkChanged(&UF::GameObjectData::SpawnTrackingStateAnimID);
goMask.MarkChanged(&UF::GameObjectData::SpawnTrackingStateAnimKitID);
+ goMask.MarkChanged(&UF::GameObjectData::StateWorldEffectsQuestObjectiveID);
}
else if (m_questObjectiveStatus.contains({ QUEST_OBJECTIVE_GAMEOBJECT, int32(gameObject->GetEntry()) }) || gameObject->GetGOInfo()->GetConditionID1())
objMask.MarkChanged(&UF::ObjectData::DynamicFlags);
@@ -25789,13 +25815,14 @@ void Player::UpdateVisibleObjectInteractions(bool allUnits, bool onlySpellClicks
unitMask.MarkChanged(&UF::UnitData::NpcFlags2);
SpawnMetadata const* data = sObjectMgr->GetSpawnMetadata(SPAWN_TYPE_CREATURE, creature->GetSpawnId());
- if (data && data->spawnTrackingQuestObjectiveId && data->spawnTrackingData)
+ if (data && data->spawnTrackingData && !data->spawnTrackingQuestObjectives.empty())
{
objMask.MarkChanged(&UF::ObjectData::DynamicFlags);
unitMask.MarkChanged(&UF::UnitData::StateWorldEffectIDs);
unitMask.MarkChanged(&UF::UnitData::StateSpellVisualID);
unitMask.MarkChanged(&UF::UnitData::StateAnimID);
unitMask.MarkChanged(&UF::UnitData::StateAnimKitID);
+ unitMask.MarkChanged(&UF::UnitData::StateWorldEffectsQuestObjectiveID);
}
if (objMask.GetChangesMask().IsAnySet() || unitMask.GetChangesMask().IsAnySet())
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index 2d21399464d..cd457d24ca0 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -1797,7 +1797,8 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
bool HasPvPForcingQuest() const;
void SendForceSpawnTrackingUpdate(uint32 questId) const;
- QuestObjective const* GetActiveQuestObjectiveForForSpawnTracking(uint32 spawnTrackingId) const;
+ QuestObjective const* GetActiveQuestObjectiveForSpawnTracking(uint32 spawnTrackingId) const;
+ SpawnTrackingState GetSpawnTrackingStateByObjectives(uint32 spawnTrackingId, std::vector<uint32> const& questObjectives) const;
SpawnTrackingState GetSpawnTrackingStateByObjective(uint32 spawnTrackingId, uint32 questObjectiveId) const;
/*********************************************************/
diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp
index d47d494a313..ba7f7d79edc 100644
--- a/src/server/game/Globals/ObjectMgr.cpp
+++ b/src/server/game/Globals/ObjectMgr.cpp
@@ -11811,7 +11811,7 @@ void ObjectMgr::LoadSpawnTrackings()
_spawnTrackingMapStore.clear();
// 0 1 2 3
- QueryResult result = WorldDatabase.Query("SELECT SpawnTrackingId, SpawnType, SpawnId, QuestObjectiveId FROM spawn_tracking");
+ QueryResult result = WorldDatabase.Query("SELECT SpawnTrackingId, SpawnType, SpawnId, QuestObjectiveIds FROM spawn_tracking");
if (!result)
{
@@ -11827,7 +11827,6 @@ void ObjectMgr::LoadSpawnTrackings()
uint32 spawnTrackingId = fields[0].GetUInt32();
SpawnObjectType spawnType = SpawnObjectType(fields[1].GetUInt8());
ObjectGuid::LowType spawnId = fields[2].GetUInt64();
- uint32 objectiveId = fields[3].GetUInt32();
if (!SpawnData::TypeIsValid(spawnType))
{
@@ -11860,12 +11859,6 @@ void ObjectMgr::LoadSpawnTrackings()
continue;
}
- if (!IsQuestObjectiveForSpawnTracking(spawnTrackingId, objectiveId))
- {
- TC_LOG_ERROR("sql.sql", "Table `spawn_tracking` has spawn tracking {} assigned to spawn ({},{}), but spawn tracking is not linked to quest objective {}. Skipped.", spawnTrackingId, uint32(spawnType), spawnId, objectiveId);
- continue;
- }
-
if (spawnTrackingTemplateData->MapId != data->mapId)
{
TC_LOG_ERROR("sql.sql", "Table `spawn_tracking` has spawn tracking {} (map {}) assigned to spawn ({},{}), but spawn has map {} - spawn NOT added to spawn tracking!",
@@ -11883,8 +11876,33 @@ void ObjectMgr::LoadSpawnTrackings()
continue;
}
+ std::vector<uint32> objectiveList;
+ if (Optional<std::string_view> objectivesStr = fields[3].GetStringViewOrNull())
+ {
+ for (std::string_view objectiveStr : Trinity::Tokenize(*objectivesStr, ',', false))
+ {
+ Optional<uint32> objectiveId = Trinity::StringTo<uint32>(objectiveStr);
+ if (!objectiveId)
+ continue;
+
+ if (!IsQuestObjectiveForSpawnTracking(spawnTrackingId, *objectiveId))
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking` has spawn tracking {} assigned to spawn ({},{}), but spawn tracking is not linked to quest objective {}. Skipped.", spawnTrackingId, uint32(spawnType), spawnId, objectiveId);
+ continue;
+ }
+
+ objectiveList.push_back(*objectiveId);
+ }
+
+ if (objectiveList.empty())
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking` has spawn tracking {} assigned to spawn ({},{}), but spawn tracking is not linked to any quest objective - spawn NOT added to spawn tracking!", spawnTrackingId, uint32(spawnType), spawnId);
+ continue;
+ }
+ }
+
const_cast<SpawnMetadata*>(data)->spawnTrackingData = spawnTrackingTemplateData;
- const_cast<SpawnMetadata*>(data)->spawnTrackingQuestObjectiveId = objectiveId;
+ const_cast<SpawnMetadata*>(data)->spawnTrackingQuestObjectives = std::move(objectiveList);
_spawnTrackingMapStore.emplace(spawnTrackingId, data);
++count;
@@ -11981,7 +11999,7 @@ void ObjectMgr::LoadSpawnTrackingStates()
if (!sWorldEffectStore.HasRecord(*worldEffectId))
{
- TC_LOG_ERROR("sql.sql", "Table `spawn_tracking_state` references invalid StateAnimKitId {} for spawn ({},{}). Skipped.",
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking_state` references invalid WorldEffectId {} for spawn ({},{}). Skipped.",
*worldEffectId, uint32(spawnType), spawnId);
continue;
}
diff --git a/src/server/game/Handlers/QuestHandler.cpp b/src/server/game/Handlers/QuestHandler.cpp
index d69cd4829a9..c8d41b9dc78 100644
--- a/src/server/game/Handlers/QuestHandler.cpp
+++ b/src/server/game/Handlers/QuestHandler.cpp
@@ -892,7 +892,7 @@ void WorldSession::HandleSpawnTrackingUpdate(WorldPackets::Quest::SpawnTrackingU
responseInfo.ObjectID = requestInfo.ObjectID;
SpawnTrackingTemplateData const* spawnTrackingTemplateData = sObjectMgr->GetSpawnTrackingData(requestInfo.SpawnTrackingID);
- QuestObjective const* activeQuestObjective = _player->GetActiveQuestObjectiveForForSpawnTracking(requestInfo.SpawnTrackingID);
+ QuestObjective const* activeQuestObjective = _player->GetActiveQuestObjectiveForSpawnTracking(requestInfo.SpawnTrackingID);
// Send phase info if map is the same or spawn tracking related quests are taken or completed
if (spawnTrackingTemplateData && (_player->GetMapId() == spawnTrackingTemplateData->MapId || activeQuestObjective))
@@ -920,7 +920,7 @@ void WorldSession::HandleSpawnTrackingUpdate(WorldPackets::Quest::SpawnTrackingU
if (activeQuestObjective)
{
- SpawnTrackingState state = _player->GetSpawnTrackingStateByObjective(spawnTrackingId, activeQuestObjective->ID);
+ SpawnTrackingState state = _player->GetSpawnTrackingStateByObjectives(spawnTrackingId, data->spawnTrackingQuestObjectives);
responseInfo.Visible = data->spawnTrackingStates[AsUnderlyingType(state)].Visible;
break;
}
diff --git a/src/server/game/Maps/SpawnData.h b/src/server/game/Maps/SpawnData.h
index d9bae3bddfe..5964bc6fdd5 100644
--- a/src/server/game/Maps/SpawnData.h
+++ b/src/server/game/Maps/SpawnData.h
@@ -123,7 +123,7 @@ struct SpawnMetadata
bool dbData = true;
SpawnGroupTemplateData const* spawnGroupData = nullptr;
SpawnTrackingTemplateData const* spawnTrackingData = nullptr;
- uint32 spawnTrackingQuestObjectiveId = 0;
+ std::vector<uint32> spawnTrackingQuestObjectives;
std::array<SpawnTrackingStateData, size_t(SpawnTrackingState::Max)> spawnTrackingStates;
protected: