aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/server/database/Database/Implementation/CharacterDatabase.cpp4
-rw-r--r--src/server/database/Database/Implementation/CharacterDatabase.h4
-rw-r--r--src/server/game/Entities/Creature/Creature.cpp19
-rw-r--r--src/server/game/Entities/Creature/Creature.h2
-rw-r--r--src/server/game/Entities/GameObject/GameObject.cpp21
-rw-r--r--src/server/game/Entities/GameObject/GameObject.h2
-rw-r--r--src/server/game/Entities/Object/Object.cpp6
-rw-r--r--src/server/game/Entities/Object/Object.h3
-rw-r--r--src/server/game/Entities/Object/Updates/UpdateFields.cpp52
-rw-r--r--src/server/game/Entities/Object/Updates/UpdateFields.h8
-rw-r--r--src/server/game/Entities/Object/Updates/ViewerDependentValues.h146
-rw-r--r--src/server/game/Entities/Player/Player.cpp212
-rw-r--r--src/server/game/Entities/Player/Player.h7
-rw-r--r--src/server/game/Entities/Transport/Transport.cpp1
-rw-r--r--src/server/game/Globals/ObjectMgr.cpp366
-rw-r--r--src/server/game/Globals/ObjectMgr.h16
-rw-r--r--src/server/game/Handlers/CharacterHandler.cpp4
-rw-r--r--src/server/game/Handlers/QuestHandler.cpp63
-rw-r--r--src/server/game/Maps/SpawnData.h30
-rw-r--r--src/server/game/Quests/QuestDef.h2
-rw-r--r--src/server/game/Server/Packets/QuestPackets.cpp46
-rw-r--r--src/server/game/Server/Packets/QuestPackets.h47
-rw-r--r--src/server/game/Server/Protocol/Opcodes.cpp6
-rw-r--r--src/server/game/Server/WorldSession.h2
-rw-r--r--src/server/game/World/World.cpp12
25 files changed, 1045 insertions, 36 deletions
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp
index 97636d5d85d..89ba61db73e 100644
--- a/src/server/database/Database/Implementation/CharacterDatabase.cpp
+++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp
@@ -112,6 +112,7 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_SEL_CHARACTER_QUESTSTATUS_OBJECTIVES, "SELECT quest, objective, data FROM character_queststatus_objectives WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_QUESTSTATUS_OBJECTIVES_CRITERIA, "SELECT questObjectiveId FROM character_queststatus_objectives_criteria WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_QUESTSTATUS_OBJECTIVES_CRITERIA_PROGRESS, "SELECT criteriaId, counter, date FROM character_queststatus_objectives_criteria_progress WHERE guid = ?", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_SEL_CHARACTER_QUESTSTATUS_OBJECTIVES_SPAWN_TRACKING, "SELECT quest, objective, spawnTrackingId FROM character_queststatus_objectives_spawn_tracking WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_QUESTSTATUS_DAILY, "SELECT quest, time FROM character_queststatus_daily WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_QUESTSTATUS_WEEKLY, "SELECT quest FROM character_queststatus_weekly WHERE guid = ?", CONNECTION_ASYNC);
@@ -594,6 +595,7 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES_CRITERIA, "DELETE FROM character_queststatus_objectives_criteria WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES_CRITERIA_PROGRESS, "DELETE FROM character_queststatus_objectives_criteria_progress WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES_CRITERIA_PROGRESS_BY_CRITERIA, "DELETE FROM character_queststatus_objectives_criteria_progress WHERE guid = ? AND criteriaId = ?", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES_SPAWN_TRACKING, "DELETE FROM character_queststatus_objectives_spawn_tracking WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_SOCIAL_BY_GUID, "DELETE FROM character_social WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_SOCIAL_BY_FRIEND, "DELETE FROM character_social WHERE friend = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_ACHIEVEMENT_BY_ACHIEVEMENT, "DELETE FROM character_achievement WHERE achievement = ? AND guid = ?", CONNECTION_ASYNC);
@@ -644,6 +646,8 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES_BY_QUEST, "DELETE FROM character_queststatus_objectives WHERE guid = ? AND quest = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_QUESTSTATUS_OBJECTIVES_CRITERIA, "INSERT INTO character_queststatus_objectives_criteria (guid, questObjectiveId) VALUES (?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_QUESTSTATUS_OBJECTIVES_CRITERIA_PROGRESS, "INSERT INTO character_queststatus_objectives_criteria_progress (guid, criteriaId, counter, date) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_REP_CHAR_QUESTSTATUS_OBJECTIVES_SPAWN_TRACKING, "REPLACE INTO character_queststatus_objectives_spawn_tracking (guid, quest, objective, spawnTrackingId) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES_SPAWN_TRACKING_BY_QUEST, "DELETE FROM character_queststatus_objectives_spawn_tracking WHERE guid = ? AND quest = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_QUESTSTATUS_REWARDED, "INSERT IGNORE INTO character_queststatus_rewarded (guid, quest, active) VALUES (?, ?, 1)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_QUESTSTATUS_REWARDED_BY_QUEST, "DELETE FROM character_queststatus_rewarded WHERE guid = ? AND quest = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_QUESTSTATUS_REWARDED_FACTION_CHANGE, "UPDATE character_queststatus_rewarded SET quest = ? WHERE quest = ? AND guid = ?", CONNECTION_ASYNC);
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h
index fd781a223da..7a3e6b24467 100644
--- a/src/server/database/Database/Implementation/CharacterDatabase.h
+++ b/src/server/database/Database/Implementation/CharacterDatabase.h
@@ -73,6 +73,7 @@ enum CharacterDatabaseStatements : uint32
CHAR_SEL_CHARACTER_QUESTSTATUS_OBJECTIVES,
CHAR_SEL_CHARACTER_QUESTSTATUS_OBJECTIVES_CRITERIA,
CHAR_SEL_CHARACTER_QUESTSTATUS_OBJECTIVES_CRITERIA_PROGRESS,
+ CHAR_SEL_CHARACTER_QUESTSTATUS_OBJECTIVES_SPAWN_TRACKING,
CHAR_SEL_CHARACTER_QUESTSTATUS_DAILY,
CHAR_SEL_CHARACTER_QUESTSTATUS_WEEKLY,
CHAR_SEL_CHARACTER_QUESTSTATUS_MONTHLY,
@@ -464,6 +465,7 @@ enum CharacterDatabaseStatements : uint32
CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES_CRITERIA,
CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES_CRITERIA_PROGRESS,
CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES_CRITERIA_PROGRESS_BY_CRITERIA,
+ CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES_SPAWN_TRACKING,
CHAR_DEL_CHAR_SOCIAL_BY_GUID,
CHAR_DEL_CHAR_SOCIAL_BY_FRIEND,
CHAR_DEL_CHAR_ACHIEVEMENT_BY_ACHIEVEMENT,
@@ -514,6 +516,8 @@ enum CharacterDatabaseStatements : uint32
CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES_BY_QUEST,
CHAR_INS_CHAR_QUESTSTATUS_OBJECTIVES_CRITERIA,
CHAR_INS_CHAR_QUESTSTATUS_OBJECTIVES_CRITERIA_PROGRESS,
+ CHAR_REP_CHAR_QUESTSTATUS_OBJECTIVES_SPAWN_TRACKING,
+ CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES_SPAWN_TRACKING_BY_QUEST,
CHAR_INS_CHAR_QUESTSTATUS_REWARDED,
CHAR_DEL_CHAR_QUESTSTATUS_REWARDED_BY_QUEST,
CHAR_UPD_CHAR_QUESTSTATUS_REWARDED_FACTION_CHANGE,
diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp
index 22e60cf7490..815996ffec7 100644
--- a/src/server/game/Entities/Creature/Creature.cpp
+++ b/src/server/game/Entities/Creature/Creature.cpp
@@ -629,7 +629,7 @@ bool Creature::UpdateEntry(uint32 entry, CreatureData const* data /*= nullptr*/,
ReplaceAllDynamicFlags(UNIT_DYNFLAG_NONE);
- SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::StateAnimID), sDB2Manager.GetEmptyAnimStateID());
+ SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::StateWorldEffectsQuestObjectiveID), data ? data->spawnTrackingQuestObjectiveId : 0);
SetCanDualWield(cInfo->flags_extra & CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK);
@@ -3200,6 +3200,23 @@ void Creature::SetScriptStringId(std::string id)
}
}
+SpawnTrackingStateData const* Creature::GetSpawnTrackingStateDataForPlayer(Player const* player) const
+{
+ if (!player)
+ return nullptr;
+
+ if (SpawnMetadata const* data = sObjectMgr->GetSpawnMetadata(SPAWN_TYPE_CREATURE, GetSpawnId()))
+ {
+ if (data->spawnTrackingQuestObjectiveId && data->spawnTrackingData)
+ {
+ SpawnTrackingState state = player->GetSpawnTrackingStateByObjective(data->spawnTrackingData->SpawnTrackingId, data->spawnTrackingQuestObjectiveId);
+ return &data->spawnTrackingStates[AsUnderlyingType(state)];
+ }
+ }
+
+ return nullptr;
+}
+
VendorItemData const* Creature::GetVendorItems() const
{
return sObjectMgr->GetNpcVendorItemList(GetEntry());
diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h
index 7bf4560e4e4..7765c8732b7 100644
--- a/src/server/game/Entities/Creature/Creature.h
+++ b/src/server/game/Entities/Creature/Creature.h
@@ -276,6 +276,8 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
void SetScriptStringId(std::string id);
std::string_view GetStringId(StringIdType type) const { return m_stringIds[size_t(type)] ? std::string_view(*m_stringIds[size_t(type)]) : std::string_view(); }
+ SpawnTrackingStateData const* GetSpawnTrackingStateDataForPlayer(Player const* player) const override;
+
// override WorldObject function for proper name localization
std::string GetNameForLocaleIdx(LocaleConstant locale) const override;
diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp
index 8967fab4de8..b6e3ffd4809 100644
--- a/src/server/game/Entities/GameObject/GameObject.cpp
+++ b/src/server/game/Entities/GameObject/GameObject.cpp
@@ -1083,8 +1083,6 @@ bool GameObject::Create(uint32 entry, Map* map, Position const& pos, QuaternionD
SetGoState(goState);
SetGoArtKit(artKit);
- SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::SpawnTrackingStateAnimID), sDB2Manager.GetEmptyAnimStateID());
-
switch (goInfo->type)
{
case GAMEOBJECT_TYPE_FISHINGHOLE:
@@ -1969,6 +1967,8 @@ 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);
+
if (data->spawntimesecs >= 0)
{
m_spawnedByDefault = true;
@@ -3583,6 +3583,23 @@ void GameObject::SetScriptStringId(std::string id)
}
}
+SpawnTrackingStateData const* GameObject::GetSpawnTrackingStateDataForPlayer(Player const* player) const
+{
+ if (!player)
+ return nullptr;
+
+ if (SpawnMetadata const* data = sObjectMgr->GetSpawnMetadata(SPAWN_TYPE_GAMEOBJECT, GetSpawnId()))
+ {
+ if (data->spawnTrackingQuestObjectiveId && data->spawnTrackingData)
+ {
+ SpawnTrackingState state = player->GetSpawnTrackingStateByObjective(data->spawnTrackingData->SpawnTrackingId, data->spawnTrackingQuestObjectiveId);
+ return &data->spawnTrackingStates[AsUnderlyingType(state)];
+ }
+ }
+
+ return nullptr;
+}
+
// overwrite WorldObject function for proper name localization
std::string GameObject::GetNameForLocaleIdx(LocaleConstant locale) const
{
diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h
index a92a5bacdd8..6c641aa1f1e 100644
--- a/src/server/game/Entities/GameObject/GameObject.h
+++ b/src/server/game/Entities/GameObject/GameObject.h
@@ -387,6 +387,8 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject>
void SetScriptStringId(std::string id);
std::string_view GetStringId(StringIdType type) const { return m_stringIds[size_t(type)] ? std::string_view(*m_stringIds[size_t(type)]) : std::string_view(); }
+ SpawnTrackingStateData const* GetSpawnTrackingStateDataForPlayer(Player const* player) const override;
+
void SetDisplayId(uint32 displayid);
uint32 GetDisplayId() const { return m_gameObjectData->DisplayID; }
uint8 GetNameSetId() const;
diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp
index 6780fb0a270..724e852e58c 100644
--- a/src/server/game/Entities/Object/Object.cpp
+++ b/src/server/game/Entities/Object/Object.cpp
@@ -1581,6 +1581,12 @@ bool WorldObject::CanSeeOrDetect(WorldObject const* obj, bool implicitDetect, bo
if (!obj->IsPrivateObject() && !sConditionMgr->IsObjectMeetingVisibilityByObjectIdConditions(obj->GetTypeId(), obj->GetEntry(), this))
return false;
+ // Spawn tracking
+ if (Player const* player = ToPlayer())
+ if (SpawnTrackingStateData const* spawnTrackingStateData = obj->GetSpawnTrackingStateDataForPlayer(player))
+ if (!spawnTrackingStateData->Visible)
+ return false;
+
bool corpseVisibility = false;
if (distanceCheck)
{
diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h
index 5297de5fef4..2e96f92c59d 100644
--- a/src/server/game/Entities/Object/Object.h
+++ b/src/server/game/Entities/Object/Object.h
@@ -67,6 +67,7 @@ class ZoneScript;
struct FactionTemplateEntry;
struct Loot;
struct QuaternionData;
+struct SpawnTrackingStateData;
struct SpellPowerCost;
namespace WorldPackets
@@ -314,6 +315,8 @@ class TC_GAME_API Object
virtual Loot* GetLootForPlayer([[maybe_unused]] Player const* player) const { return nullptr; }
+ virtual SpawnTrackingStateData const* GetSpawnTrackingStateDataForPlayer([[maybe_unused]] Player const* player) const { return nullptr; }
+
protected:
Object();
diff --git a/src/server/game/Entities/Object/Updates/UpdateFields.cpp b/src/server/game/Entities/Object/Updates/UpdateFields.cpp
index bb9f989156e..03352b2c6ac 100644
--- a/src/server/game/Entities/Object/Updates/UpdateFields.cpp
+++ b/src/server/game/Entities/Object/Updates/UpdateFields.cpp
@@ -932,15 +932,16 @@ void UnitData::WriteCreate(ByteBuffer& data, EnumFlag<UpdateFieldFlag> fieldVisi
data << int32(ViewerDependentValue<DisplayIDTag>::GetValue(this, owner, receiver));
data << uint32(ViewerDependentValue<NpcFlagsTag>::GetValue(this, owner, receiver));
data << uint32(ViewerDependentValue<NpcFlags2Tag>::GetValue(this, owner, receiver));
- data << uint32(StateSpellVisualID);
- data << uint32(StateAnimID);
- data << uint32(StateAnimKitID);
- data << uint32(StateWorldEffectIDs->size());
+ data << uint32(ViewerDependentValue<StateSpellVisualIDTag>::GetValue(this, owner, receiver));
+ data << uint32(ViewerDependentValue<StateAnimIDTag>::GetValue(this, owner, receiver));
+ data << uint32(ViewerDependentValue<StateAnimKitIDTag>::GetValue(this, owner, receiver));
+ std::vector stateWorldEffects = ViewerDependentValue<StateWorldEffectIDsTag>::GetValue(this, owner, receiver);
+ data << uint32(stateWorldEffects.size());
data << uint32(StateWorldEffectsQuestObjectiveID);
data << int32(SpellOverrideNameID);
- for (uint32 i = 0; i < StateWorldEffectIDs->size(); ++i)
+ for (uint32 i = 0; i < stateWorldEffects.size(); ++i)
{
- data << uint32((*StateWorldEffectIDs)[i]);
+ data << uint32((stateWorldEffects)[i]);
}
data << Charm;
data << Summon;
@@ -1176,10 +1177,11 @@ void UnitData::WriteUpdate(ByteBuffer& data, Mask const& changesMask, bool ignor
}
if (changesMask[2])
{
- data.WriteBits(StateWorldEffectIDs->size(), 32);
- for (uint32 i = 0; i < StateWorldEffectIDs->size(); ++i)
+ std::vector stateWorldEffects = ViewerDependentValue<StateWorldEffectIDsTag>::GetValue(this, owner, receiver);
+ data.WriteBits(stateWorldEffects.size(), 32);
+ for (uint32 i = 0; i < stateWorldEffects.size(); ++i)
{
- data << uint32((*StateWorldEffectIDs)[i]);
+ data << uint32((stateWorldEffects)[i]);
}
}
}
@@ -1255,15 +1257,15 @@ void UnitData::WriteUpdate(ByteBuffer& data, Mask const& changesMask, bool ignor
}
if (changesMask[9])
{
- data << uint32(StateSpellVisualID);
+ data << uint32(ViewerDependentValue<StateSpellVisualIDTag>::GetValue(this, owner, receiver));
}
if (changesMask[10])
{
- data << uint32(StateAnimID);
+ data << uint32(ViewerDependentValue<StateAnimIDTag>::GetValue(this, owner, receiver));
}
if (changesMask[11])
{
- data << uint32(StateAnimKitID);
+ data << uint32(ViewerDependentValue<StateAnimKitIDTag>::GetValue(this, owner, receiver));
}
if (changesMask[12])
{
@@ -6438,14 +6440,15 @@ void GameObjectData::WriteCreate(ByteBuffer& data, EnumFlag<UpdateFieldFlag> fie
{
data << int32(DisplayID);
data << uint32(SpellVisualID);
- data << uint32(StateSpellVisualID);
- data << uint32(SpawnTrackingStateAnimID);
- data << uint32(SpawnTrackingStateAnimKitID);
- data << uint32(StateWorldEffectIDs->size());
+ data << uint32(ViewerDependentValue<StateSpellVisualIDTag>::GetValue(this, owner, receiver));
+ data << uint32(ViewerDependentValue<StateAnimIDTag>::GetValue(this, owner, receiver));
+ data << uint32(ViewerDependentValue<StateAnimKitIDTag>::GetValue(this, owner, receiver));
+ std::vector stateWorldEffects = ViewerDependentValue<StateWorldEffectIDsTag>::GetValue(this, owner, receiver);
+ data << uint32(stateWorldEffects.size());
data << uint32(StateWorldEffectsQuestObjectiveID);
- for (uint32 i = 0; i < StateWorldEffectIDs->size(); ++i)
+ for (uint32 i = 0; i < stateWorldEffects.size(); ++i)
{
- data << uint32((*StateWorldEffectIDs)[i]);
+ data << uint32((stateWorldEffects)[i]);
}
data << CreatedBy;
data << GuildGUID;
@@ -6490,10 +6493,11 @@ void GameObjectData::WriteUpdate(ByteBuffer& data, Mask const& changesMask, bool
{
if (changesMask[1])
{
- data.WriteBits(StateWorldEffectIDs->size(), 32);
- for (uint32 i = 0; i < StateWorldEffectIDs->size(); ++i)
+ std::vector stateWorldEffects = ViewerDependentValue<StateWorldEffectIDsTag>::GetValue(this, owner, receiver);
+ data.WriteBits(stateWorldEffects.size(), 32);
+ for (uint32 i = 0; i < stateWorldEffects.size(); ++i)
{
- data << uint32((*StateWorldEffectIDs)[i]);
+ data << uint32((stateWorldEffects)[i]);
}
}
}
@@ -6548,15 +6552,15 @@ void GameObjectData::WriteUpdate(ByteBuffer& data, Mask const& changesMask, bool
}
if (changesMask[6])
{
- data << uint32(StateSpellVisualID);
+ data << uint32(ViewerDependentValue<StateSpellVisualIDTag>::GetValue(this, owner, receiver));
}
if (changesMask[7])
{
- data << uint32(SpawnTrackingStateAnimID);
+ data << uint32(ViewerDependentValue<StateAnimIDTag>::GetValue(this, owner, receiver));
}
if (changesMask[8])
{
- data << uint32(SpawnTrackingStateAnimKitID);
+ data << uint32(ViewerDependentValue<StateAnimKitIDTag>::GetValue(this, owner, receiver));
}
if (changesMask[9])
{
diff --git a/src/server/game/Entities/Object/Updates/UpdateFields.h b/src/server/game/Entities/Object/Updates/UpdateFields.h
index 26ac4b7ca55..335a55666bd 100644
--- a/src/server/game/Entities/Object/Updates/UpdateFields.h
+++ b/src/server/game/Entities/Object/Updates/UpdateFields.h
@@ -262,6 +262,7 @@ struct UnitData : public IsUpdateFieldStructureTag, public HasChangesMask<220>
{
UpdateField<bool, 0, 1> Field_314;
UpdateField<std::vector<uint32>, 0, 2> StateWorldEffectIDs;
+ struct StateWorldEffectIDsTag : ViewerDependentValueTag<std::vector<uint32>> {};
DynamicUpdateField<UF::PassiveSpellHistory, 0, 3> PassiveSpells;
DynamicUpdateField<int32, 0, 4> WorldEffects;
DynamicUpdateField<ObjectGuid, 0, 5> ChannelObjects;
@@ -272,8 +273,11 @@ struct UnitData : public IsUpdateFieldStructureTag, public HasChangesMask<220>
UpdateField<uint32, 0, 8> NpcFlags2;
struct NpcFlags2Tag : ViewerDependentValueTag<uint32> {};
UpdateField<uint32, 0, 9> StateSpellVisualID;
+ struct StateSpellVisualIDTag : ViewerDependentValueTag<uint32> {};
UpdateField<uint32, 0, 10> StateAnimID;
+ struct StateAnimIDTag : ViewerDependentValueTag<uint32> {};
UpdateField<uint32, 0, 11> StateAnimKitID;
+ struct StateAnimKitIDTag : ViewerDependentValueTag<uint32> {};
UpdateField<uint32, 0, 12> StateWorldEffectsQuestObjectiveID;
UpdateField<int32, 0, 13> SpellOverrideNameID;
UpdateField<ObjectGuid, 0, 14> Charm;
@@ -1192,13 +1196,17 @@ struct ActivePlayerData : public IsUpdateFieldStructureTag, public HasChangesMas
struct GameObjectData : public IsUpdateFieldStructureTag, public HasChangesMask<25>
{
UpdateField<std::vector<uint32>, 0, 1> StateWorldEffectIDs;
+ struct StateWorldEffectIDsTag : ViewerDependentValueTag<std::vector<uint32>> {};
DynamicUpdateField<int32, 0, 2> EnableDoodadSets;
DynamicUpdateField<int32, 0, 3> WorldEffects;
UpdateField<int32, 0, 4> DisplayID;
UpdateField<uint32, 0, 5> SpellVisualID;
UpdateField<uint32, 0, 6> StateSpellVisualID;
+ struct StateSpellVisualIDTag : ViewerDependentValueTag<uint32> {};
UpdateField<uint32, 0, 7> SpawnTrackingStateAnimID;
+ struct StateAnimIDTag : ViewerDependentValueTag<uint32> {};
UpdateField<uint32, 0, 8> SpawnTrackingStateAnimKitID;
+ struct StateAnimKitIDTag : ViewerDependentValueTag<uint32> {};
UpdateField<uint32, 0, 9> StateWorldEffectsQuestObjectiveID;
UpdateField<ObjectGuid, 0, 10> CreatedBy;
UpdateField<ObjectGuid, 0, 11> GuildGUID;
diff --git a/src/server/game/Entities/Object/Updates/ViewerDependentValues.h b/src/server/game/Entities/Object/Updates/ViewerDependentValues.h
index 7a7f7dcf231..646673552c9 100644
--- a/src/server/game/Entities/Object/Updates/ViewerDependentValues.h
+++ b/src/server/game/Entities/Object/Updates/ViewerDependentValues.h
@@ -20,6 +20,7 @@
#include "Conversation.h"
#include "Creature.h"
+#include "DB2Stores.h"
#include "GameObject.h"
#include "Map.h"
#include "ObjectMgr.h"
@@ -138,6 +139,11 @@ public:
if (!gameObject->MeetsInteractCondition(receiver))
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)
+ dynFlags &= ~GO_DYNFLAG_LO_ACTIVATE;
}
dynamicFlags = dynFlags;
@@ -198,6 +204,78 @@ public:
};
template<>
+class ViewerDependentValue<UF::UnitData::StateWorldEffectIDsTag>
+{
+public:
+ using value_type = UF::UnitData::StateWorldEffectIDsTag::value_type;
+
+ static value_type GetValue(UF::UnitData const* unitData, Unit const* unit, Player const* receiver)
+ {
+ value_type stateWorldEffects = unitData->StateWorldEffectIDs;
+
+ if (unit->IsCreature())
+ if (SpawnTrackingStateData const* spawnTrackingStateData = unit->GetSpawnTrackingStateDataForPlayer(receiver))
+ stateWorldEffects = spawnTrackingStateData->StateWorldEffects;
+
+ return stateWorldEffects;
+ }
+};
+
+template<>
+class ViewerDependentValue<UF::UnitData::StateSpellVisualIDTag>
+{
+public:
+ using value_type = UF::UnitData::StateSpellVisualIDTag::value_type;
+
+ static value_type GetValue(UF::UnitData const* unitData, Unit const* unit, Player const* receiver)
+ {
+ value_type stateSpellVisual = unitData->StateSpellVisualID;
+
+ if (unit->IsCreature())
+ if (SpawnTrackingStateData const* spawnTrackingStateData = unit->GetSpawnTrackingStateDataForPlayer(receiver))
+ stateSpellVisual = spawnTrackingStateData->StateSpellVisualId.value_or(0);
+
+ return stateSpellVisual;
+ }
+};
+
+template<>
+class ViewerDependentValue<UF::UnitData::StateAnimIDTag>
+{
+public:
+ using value_type = UF::UnitData::StateAnimIDTag::value_type;
+
+ static value_type GetValue(UF::UnitData const* /*unitData*/, Unit const* unit, Player const* receiver)
+ {
+ value_type stateAnimId = sDB2Manager.GetEmptyAnimStateID();
+
+ if (unit->IsCreature())
+ if (SpawnTrackingStateData const* spawnTrackingStateData = unit->GetSpawnTrackingStateDataForPlayer(receiver))
+ stateAnimId = spawnTrackingStateData->StateAnimId.value_or(stateAnimId);
+
+ return stateAnimId;
+ }
+};
+
+template<>
+class ViewerDependentValue<UF::UnitData::StateAnimKitIDTag>
+{
+public:
+ using value_type = UF::UnitData::StateAnimKitIDTag::value_type;
+
+ static value_type GetValue(UF::UnitData const* unitData, Unit const* unit, Player const* receiver)
+ {
+ value_type stateAnimKitId = unitData->StateAnimKitID;
+
+ if (unit->IsCreature())
+ if (SpawnTrackingStateData const* spawnTrackingStateData = unit->GetSpawnTrackingStateDataForPlayer(receiver))
+ stateAnimKitId = spawnTrackingStateData->StateAnimKitId.value_or(0);
+
+ return stateAnimKitId;
+ }
+};
+
+template<>
class ViewerDependentValue<UF::UnitData::FactionTemplateTag>
{
public:
@@ -381,6 +459,74 @@ public:
};
template<>
+class ViewerDependentValue<UF::GameObjectData::StateWorldEffectIDsTag>
+{
+public:
+ using value_type = UF::GameObjectData::StateWorldEffectIDsTag::value_type;
+
+ static value_type GetValue(UF::GameObjectData const* gameObjectData, GameObject const* gameObject, Player const* receiver)
+ {
+ value_type stateWorldEffects = gameObjectData->StateWorldEffectIDs;
+
+ if (SpawnTrackingStateData const* spawnTrackingStateData = gameObject->GetSpawnTrackingStateDataForPlayer(receiver))
+ stateWorldEffects = spawnTrackingStateData->StateWorldEffects;
+
+ return stateWorldEffects;
+ }
+};
+
+template<>
+class ViewerDependentValue<UF::GameObjectData::StateSpellVisualIDTag>
+{
+public:
+ using value_type = UF::GameObjectData::StateSpellVisualIDTag::value_type;
+
+ static value_type GetValue(UF::GameObjectData const* gameObjectData, GameObject const* gameObject, Player const* receiver)
+ {
+ value_type stateSpellVisual = gameObjectData->StateSpellVisualID;
+
+ if (SpawnTrackingStateData const* spawnTrackingStateData = gameObject->GetSpawnTrackingStateDataForPlayer(receiver))
+ stateSpellVisual = spawnTrackingStateData->StateSpellVisualId.value_or(0);
+
+ return stateSpellVisual;
+ }
+};
+
+template<>
+class ViewerDependentValue<UF::GameObjectData::StateAnimIDTag>
+{
+public:
+ using value_type = UF::GameObjectData::StateAnimIDTag::value_type;
+
+ static value_type GetValue(UF::GameObjectData const* /*gameObjectData*/, GameObject const* gameObject, Player const* receiver)
+ {
+ value_type stateAnimId = sDB2Manager.GetEmptyAnimStateID();
+
+ if (SpawnTrackingStateData const* spawnTrackingStateData = gameObject->GetSpawnTrackingStateDataForPlayer(receiver))
+ stateAnimId = spawnTrackingStateData->StateAnimId.value_or(stateAnimId);
+
+ return stateAnimId;
+ }
+};
+
+template<>
+class ViewerDependentValue<UF::GameObjectData::StateAnimKitIDTag>
+{
+public:
+ using value_type = UF::GameObjectData::StateAnimKitIDTag::value_type;
+
+ static value_type GetValue(UF::GameObjectData const* gameObjectData, GameObject const* gameObject, Player const* receiver)
+ {
+ value_type stateAnimKitId = gameObjectData->SpawnTrackingStateAnimKitID;
+
+ if (SpawnTrackingStateData const* spawnTrackingStateData = gameObject->GetSpawnTrackingStateDataForPlayer(receiver))
+ stateAnimKitId = spawnTrackingStateData->StateAnimKitId.value_or(0);
+
+ return stateAnimKitId;
+ }
+};
+
+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 99197864561..670970e2536 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -4098,6 +4098,10 @@ void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRe
stmt->setUInt64(0, guid);
trans->Append(stmt);
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES_SPAWN_TRACKING);
+ stmt->setUInt64(0, guid);
+ trans->Append(stmt);
+
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_REWARDED);
stmt->setUInt64(0, guid);
trans->Append(stmt);
@@ -14881,6 +14885,8 @@ void Player::AddQuest(Quest const* quest, Object* questGiver)
QuestStatusData& questStatusData = questStatusItr->second;
QuestStatus oldStatus = questStatusData.Status;
+ SendForceSpawnTrackingUpdate(quest_id);
+
// check for repeatable quests status reset
SetQuestSlot(log_slot, quest_id);
questStatusData.Slot = log_slot;
@@ -14975,6 +14981,8 @@ void Player::CompleteQuest(uint32 quest_id)
{
if (quest_id)
{
+ SendForceSpawnTrackingUpdate(quest_id);
+
SetQuestStatus(quest_id, QUEST_STATUS_COMPLETE);
if (QuestStatusData const* questStatus = Trinity::Containers::MapGetValuePtr(m_QuestStatus, quest_id))
@@ -16769,6 +16777,33 @@ void Player::UpdateQuestObjectiveProgress(QuestObjectiveType objectiveType, int3
bool updatePhaseShift = false;
bool updateZoneAuras = false;
+ bool anyObjectiveChangedSpawnTrackingState = false;
+ bool isObjectiveTypeForSpawnTracking = [&]() -> bool
+ {
+ if (objectiveType == QUEST_OBJECTIVE_MONSTER ||
+ objectiveType == QUEST_OBJECTIVE_GAMEOBJECT ||
+ objectiveType == QUEST_OBJECTIVE_TALKTO ||
+ objectiveType == QUEST_OBJECTIVE_WINPETBATTLEAGAINSTNPC)
+ return true;
+
+ return false;
+ }();
+
+ SpawnMetadata const* data = nullptr;
+ if (isObjectiveTypeForSpawnTracking)
+ {
+ if (victimGuid.IsGameObject())
+ {
+ if (GameObject* gameObject = ObjectAccessor::GetGameObject(*this, victimGuid))
+ data = sObjectMgr->GetSpawnMetadata(SPAWN_TYPE_GAMEOBJECT, gameObject->GetSpawnId());
+ }
+ else if (victimGuid.IsCreatureOrVehicle())
+ {
+ if (Creature* creature = ObjectAccessor::GetCreatureOrPetOrVehicle(*this, victimGuid))
+ data = sObjectMgr->GetSpawnMetadata(SPAWN_TYPE_CREATURE, creature->GetSpawnId());
+ }
+ }
+
for (QuestObjectiveStatusMap::value_type const& objectiveItr : Trinity::Containers::MapEqualRange(m_questObjectiveStatus, { objectiveType, objectId }))
{
uint32 questId = objectiveItr.second.QuestStatusItr->first;
@@ -16879,6 +16914,41 @@ void Player::UpdateQuestObjectiveProgress(QuestObjectiveType objectiveType, int3
}
}
+ if (data && data->spawnTrackingQuestObjectiveId && data->spawnTrackingData)
+ {
+ if (objective->ID == data->spawnTrackingQuestObjectiveId)
+ {
+ // 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))
+ {
+ SpawnData const* spawnData = data->ToSpawnData();
+ if (!spawnData)
+ continue;
+
+ WorldPackets::Quest::QuestPOIUpdateResponse response;
+
+ 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;
+
+ SpawnTrackingState state = GetSpawnTrackingStateByObjective(data->spawnTrackingData->SpawnTrackingId, objective->ID);
+ responseInfo.Visible = data->spawnTrackingStates[AsUnderlyingType(state)].Visible;
+
+ response.SpawnTrackingResponses.push_back(std::move(responseInfo));
+ SendDirectMessage(response.Write());
+ }
+
+ anyObjectiveChangedSpawnTrackingState = true;
+ }
+ }
+
if (objectiveWasComplete != objectiveIsNowComplete)
anyObjectiveChangedCompletionState = true;
@@ -16911,7 +16981,7 @@ void Player::UpdateQuestObjectiveProgress(QuestObjectiveType objectiveType, int3
break;
}
- if (anyObjectiveChangedCompletionState)
+ if (anyObjectiveChangedCompletionState || anyObjectiveChangedSpawnTrackingState)
UpdateVisibleObjectInteractions(true, false, false, true);
if (updatePhaseShift)
@@ -17270,6 +17340,8 @@ void Player::SendQuestReward(Quest const* quest, Creature const* questGiver, uin
moneyReward = uint32(GetQuestMoneyReward(quest) + int32(quest->GetRewMoneyMaxLevel() * sWorld->getRate(RATE_DROP_MONEY)));
}
+ SendForceSpawnTrackingUpdate(questId);
+
if (quest->HasFlag(QUEST_FLAGS_TRACKING_EVENT))
return;
@@ -17471,6 +17543,52 @@ bool Player::HasPvPForcingQuest() const
return false;
}
+void Player::SendForceSpawnTrackingUpdate(uint32 questId) const
+{
+ if (questId)
+ {
+ WorldPackets::Quest::ForceSpawnTrackingUpdate data;
+ data.QuestID = questId;
+ SendDirectMessage(data.Write());
+ }
+}
+
+QuestObjective const* Player::GetActiveQuestObjectiveForForSpawnTracking(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)
+ return questObjective;
+
+ return nullptr;
+}
+
+SpawnTrackingState Player::GetSpawnTrackingStateByObjective(uint32 spawnTrackingId, uint32 questObjectiveId) const
+{
+ if (spawnTrackingId && questObjectiveId && sObjectMgr->IsQuestObjectiveForSpawnTracking(spawnTrackingId, questObjectiveId))
+ {
+ if (QuestObjective const* questObjective = sObjectMgr->GetQuestObjective(questObjectiveId))
+ {
+ if (IsQuestRewarded(questObjective->QuestID) || IsQuestObjectiveComplete(questObjective->QuestID, questObjective->ID))
+ return SpawnTrackingState::Complete;
+ else if (GetQuestStatus(questObjective->QuestID) != QUEST_STATUS_NONE)
+ {
+ auto itr = m_QuestStatus.find(questObjective->QuestID);
+ if (itr != m_QuestStatus.end() && itr->second.Slot < MAX_QUEST_LOG_SIZE)
+ {
+ auto itr2 = itr->second.SpawnTrackingList.find(std::make_pair(questObjective->StorageIndex, spawnTrackingId));
+ if (itr2 != itr->second.SpawnTrackingList.end())
+ return SpawnTrackingState::Complete;
+ }
+
+ return SpawnTrackingState::Active;
+ }
+ }
+ }
+
+ return SpawnTrackingState::None;
+}
+
/*********************************************************/
/*** LOAD SYSTEM ***/
/*********************************************************/
@@ -18374,6 +18492,7 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol
// after spell load, learn rewarded spell if need also
_LoadQuestStatus(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS));
_LoadQuestStatusObjectives(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS_OBJECTIVES));
+ _LoadQuestStatusObjectiveSpawnTrackings(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS_OBJECTIVES_SPAWN_TRACKING));
_LoadQuestStatusRewarded(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS_REW));
_LoadDailyQuestStatus(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_DAILY_QUEST_STATUS));
_LoadWeeklyQuestStatus(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_WEEKLY_QUEST_STATUS));
@@ -19485,7 +19604,7 @@ void Player::_LoadQuestStatusObjectives(PreparedQueryResult result)
{
//// 0 1 2
- //QueryResult* result = CharacterDatabase.PQuery("SELECT quest, objective, data WHERE guid = '{}'", GetGUIDLow());
+ //QueryResult* result = CharacterDatabase.PQuery("SELECT quest, objective, data FROM character_queststatus_objectives WHERE guid = '{}'", GetGUIDLow());
if (result)
{
@@ -19495,8 +19614,11 @@ void Player::_LoadQuestStatusObjectives(PreparedQueryResult result)
uint32 questID = fields[0].GetUInt32();
Quest const* quest = sObjectMgr->GetQuestTemplate(questID);
+ if (!quest)
+ continue;
+
auto itr = m_QuestStatus.find(questID);
- if (itr != m_QuestStatus.end() && itr->second.Slot < MAX_QUEST_LOG_SIZE && quest)
+ if (itr != m_QuestStatus.end() && itr->second.Slot < MAX_QUEST_LOG_SIZE)
{
QuestStatusData& questStatusData = itr->second;
uint8 storageIndex = fields[1].GetUInt8();
@@ -19519,6 +19641,47 @@ void Player::_LoadQuestStatusObjectives(PreparedQueryResult result)
}
}
+void Player::_LoadQuestStatusObjectiveSpawnTrackings(PreparedQueryResult result)
+{
+
+ //// 0 1 2
+ //QueryResult* result = CharacterDatabase.PQuery("SELECT quest, objective, spawnTrackingId FROM character_queststatus_objectives_spawn_tracking WHERE guid = '{}'", GetGUIDLow());
+
+ if (result)
+ {
+ do
+ {
+ Field* fields = result->Fetch();
+
+ uint32 questID = fields[0].GetUInt32();
+ Quest const* quest = sObjectMgr->GetQuestTemplate(questID);
+ if (!quest)
+ continue;
+
+ auto itr = m_QuestStatus.find(questID);
+ if (itr != m_QuestStatus.end() && itr->second.Slot < MAX_QUEST_LOG_SIZE)
+ {
+ QuestStatusData& questStatusData = itr->second;
+ uint8 storageIndex = fields[1].GetUInt8();
+ auto objectiveItr = std::find_if(quest->Objectives.begin(), quest->Objectives.end(), [=](QuestObjective const& objective) { return uint8(objective.StorageIndex) == storageIndex; });
+ if (objectiveItr != quest->Objectives.end())
+ {
+ uint32 spawnTrackingId = fields[2].GetUInt32();
+
+ if (sObjectMgr->IsQuestObjectiveForSpawnTracking(spawnTrackingId, objectiveItr->ID))
+ questStatusData.SpawnTrackingList.insert(std::make_pair(storageIndex, spawnTrackingId));
+ else
+ TC_LOG_ERROR("entities.player", "Player::_LoadQuestStatusObjectiveSpawnTrackings: Player '{}' ({}) has objective {} (quest {}) with unrelated spawn tracking {}.", GetName(), GetGUID().ToString(), objectiveItr->ID, questID, spawnTrackingId);
+ }
+ else
+ TC_LOG_ERROR("entities.player", "Player::_LoadQuestStatusObjectiveSpawnTrackings: Player '{}' ({}) has quest {} out of range objective index {}.", GetName(), GetGUID().ToString(), questID, storageIndex);
+ }
+ else
+ TC_LOG_ERROR("entities.player", "Player::_LoadQuestStatusObjectiveSpawnTrackings: Player {} ({}) does not have quest {} but has objective spawn trackings for it.", GetName(), GetGUID().ToString(), questID);
+ } while (result->NextRow());
+ }
+}
+
void Player::_LoadQuestStatusRewarded(PreparedQueryResult result)
{
// SELECT quest FROM character_queststatus_rewarded WHERE guid = ?
@@ -21008,6 +21171,22 @@ void Player::_SaveQuestStatus(CharacterDatabaseTransaction trans)
stmt->setInt32(3, count);
trans->Append(stmt);
}
+
+ // Save spawn trackings
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES_SPAWN_TRACKING_BY_QUEST);
+ stmt->setUInt64(0, GetGUID().GetCounter());
+ stmt->setUInt32(1, saveItr->first);
+ trans->Append(stmt);
+
+ for (auto [questObjectiveStorageIndex, spawnTrackingId] : qData.SpawnTrackingList)
+ {
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHAR_QUESTSTATUS_OBJECTIVES_SPAWN_TRACKING);
+ stmt->setUInt64(0, GetGUID().GetCounter());
+ stmt->setUInt32(1, statusItr->first);
+ stmt->setUInt8(2, questObjectiveStorageIndex);
+ stmt->setUInt32(3, spawnTrackingId);
+ trans->Append(stmt);
+ }
}
}
else
@@ -21022,6 +21201,11 @@ void Player::_SaveQuestStatus(CharacterDatabaseTransaction trans)
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, saveItr->first);
trans->Append(stmt);
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_OBJECTIVES_SPAWN_TRACKING_BY_QUEST);
+ stmt->setUInt64(0, GetGUID().GetCounter());
+ stmt->setUInt32(1, saveItr->first);
+ trans->Append(stmt);
}
}
@@ -23066,7 +23250,6 @@ void Player::InitDisplayIds()
}
SetDisplayId(model->DisplayID, true);
- SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::StateAnimID), sDB2Manager.GetEmptyAnimStateID());
}
inline bool Player::_StoreOrEquipNewItem(uint32 vendorslot, uint32 item, uint8 count, uint8 bag, uint8 slot, int64 price, ItemTemplate const* pProto, Creature* pVendor, VendorItem const* crItem, bool bStore)
@@ -25479,7 +25662,16 @@ void Player::UpdateVisibleObjectInteractions(bool allUnits, bool onlySpellClicks
UF::ObjectData::Base objMask;
UF::GameObjectData::Base goMask;
- if (m_questObjectiveStatus.contains({ QUEST_OBJECTIVE_GAMEOBJECT, int32(gameObject->GetEntry()) }) || gameObject->GetGOInfo()->GetConditionID1())
+ SpawnMetadata const* data = sObjectMgr->GetSpawnMetadata(SPAWN_TYPE_GAMEOBJECT, gameObject->GetSpawnId());
+ if (data && data->spawnTrackingQuestObjectiveId && data->spawnTrackingData)
+ {
+ 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);
+ }
+ else if (m_questObjectiveStatus.contains({ QUEST_OBJECTIVE_GAMEOBJECT, int32(gameObject->GetEntry()) }) || gameObject->GetGOInfo()->GetConditionID1())
objMask.MarkChanged(&UF::ObjectData::DynamicFlags);
else
{
@@ -25517,6 +25709,16 @@ void Player::UpdateVisibleObjectInteractions(bool allUnits, bool onlySpellClicks
if (creature->m_unitData->NpcFlags2)
unitMask.MarkChanged(&UF::UnitData::NpcFlags2);
+ SpawnMetadata const* data = sObjectMgr->GetSpawnMetadata(SPAWN_TYPE_CREATURE, creature->GetSpawnId());
+ if (data && data->spawnTrackingQuestObjectiveId && data->spawnTrackingData)
+ {
+ 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);
+ }
+
if (objMask.GetChangesMask().IsAnySet() || unitMask.GetChangesMask().IsAnySet())
creature->BuildValuesUpdateForPlayerWithMask(&udata, objMask.GetChangesMask(), unitMask.GetChangesMask(), this);
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index df005320e34..302c80bf757 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -107,6 +107,7 @@ enum class MovementStopReason : uint8;
enum PlayerRestState : uint8;
enum class PlayerCreateMode : int8;
enum RestTypes : uint8;
+enum class SpawnTrackingState : uint8;
enum TransferAbortReason : uint32;
namespace BattlePets
@@ -887,6 +888,7 @@ enum PlayerLoginQueryIndex
PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS_OBJECTIVES,
PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS_OBJECTIVES_CRITERIA,
PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS_OBJECTIVES_CRITERIA_PROGRESS,
+ PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS_OBJECTIVES_SPAWN_TRACKING,
PLAYER_LOGIN_QUERY_LOAD_DAILY_QUEST_STATUS,
PLAYER_LOGIN_QUERY_LOAD_REPUTATION,
PLAYER_LOGIN_QUERY_LOAD_INVENTORY,
@@ -1780,6 +1782,10 @@ 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;
+ SpawnTrackingState GetSpawnTrackingStateByObjective(uint32 spawnTrackingId, uint32 questObjectiveId) const;
+
/*********************************************************/
/*** LOAD SYSTEM ***/
/*********************************************************/
@@ -3028,6 +3034,7 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
static Item* _LoadMailedItem(ObjectGuid const& playerGuid, Player* player, uint64 mailId, Mail* mail, Field* fields, ItemAdditionalLoadInfo* addionalData);
void _LoadQuestStatus(PreparedQueryResult result);
void _LoadQuestStatusObjectives(PreparedQueryResult result);
+ void _LoadQuestStatusObjectiveSpawnTrackings(PreparedQueryResult result);
void _LoadQuestStatusRewarded(PreparedQueryResult result);
void _LoadDailyQuestStatus(PreparedQueryResult result);
void _LoadWeeklyQuestStatus(PreparedQueryResult result);
diff --git a/src/server/game/Entities/Transport/Transport.cpp b/src/server/game/Entities/Transport/Transport.cpp
index dadbd4e960c..4b8be6d4b73 100644
--- a/src/server/game/Entities/Transport/Transport.cpp
+++ b/src/server/game/Entities/Transport/Transport.cpp
@@ -148,7 +148,6 @@ bool Transport::Create(ObjectGuid::LowType guidlow, uint32 entry, float x, float
SetGoState(!goinfo->moTransport.allowstopping ? GO_STATE_READY : GO_STATE_ACTIVE);
SetGoType(GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT);
SetGoAnimProgress(255);
- SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::SpawnTrackingStateAnimID), sDB2Manager.GetEmptyAnimStateID());
SetName(goinfo->name);
SetLocalRotation(0.0f, 0.0f, 0.0f, 1.0f);
SetParentRotation(QuaternionData());
diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp
index 9bf75f1ff18..852d0a03a9e 100644
--- a/src/server/game/Globals/ObjectMgr.cpp
+++ b/src/server/game/Globals/ObjectMgr.cpp
@@ -2940,6 +2940,25 @@ SpawnData const* ObjectMgr::GetSpawnData(SpawnObjectType type, ObjectGuid::LowTy
void ObjectMgr::OnDeleteSpawnData(SpawnData const* data)
{
+ if (data->spawnTrackingData)
+ {
+ SpawnTrackingTemplateData const* spawnTrackingData = GetSpawnTrackingData(data->spawnTrackingData->SpawnTrackingId);
+ ASSERT(spawnTrackingData, "Creature data for (%u," UI64FMTD ") is being deleted and has invalid spawn tracking id %u!", uint32(data->type), data->spawnId, data->spawnTrackingData->SpawnTrackingId);
+
+ auto pair = _spawnTrackingMapStore.equal_range(data->spawnTrackingData->SpawnTrackingId);
+ bool erased = false;
+ for (auto it = pair.first; it != pair.second; ++it)
+ {
+ if (it->second != data)
+ continue;
+ _spawnTrackingMapStore.erase(it);
+ erased = true;
+ }
+
+ if (!erased)
+ ABORT_MSG("Spawn data (%u," UI64FMTD ") being removed is member of spawn tracking %u, but not actually listed in the lookup table for that spawn tracking!", uint32(data->type), data->spawnId, data->spawnTrackingData->SpawnTrackingId);
+ }
+
auto templateIt = _spawnGroupDataStore.find(data->spawnGroupData->groupId);
ASSERT(templateIt != _spawnGroupDataStore.end(), "Creature data for (%u," UI64FMTD ") is being deleted and has invalid spawn group index %u!", uint32(data->type), data->spawnId, data->spawnGroupData->groupId);
if (templateIt->second.flags & SPAWNGROUP_FLAG_SYSTEM) // system groups don't store their members in the map
@@ -11645,6 +11664,353 @@ std::vector<uint32> const* ObjectMgr::GetUiMapQuestsList(uint32 uiMapId) const
return Trinity::Containers::MapGetValuePtr(_uiMapQuestsStore, uiMapId);
}
+void ObjectMgr::LoadSpawnTrackingTemplates()
+{
+ uint32 oldMSTime = getMSTime();
+
+ // need for reload case
+ _spawnTrackingDataStore.clear();
+
+ // 0 1 2 3 4
+ QueryResult result = WorldDatabase.Query("SELECT SpawnTrackingId, MapId, PhaseId, PhaseGroup, PhaseUseFlags FROM spawn_tracking_template");
+
+ if (!result)
+ {
+ TC_LOG_INFO("server.loading", ">> Loaded 0 spawn tracking templates. DB table `spawn_tracking_template` is empty!");
+ return;
+ }
+
+ do
+ {
+ Field* fields = result->Fetch();
+
+ uint32 spawnTrackingId = fields[0].GetUInt32();
+ uint32 mapId = fields[1].GetUInt32();
+
+ if (!sMapStore.HasRecord(mapId))
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking_template` references non-existing map {}, skipped", mapId);
+ continue;
+ }
+
+ SpawnTrackingTemplateData& data = _spawnTrackingDataStore[spawnTrackingId];
+ data.SpawnTrackingId = spawnTrackingId;
+ data.MapId = mapId;
+ data.PhaseId = fields[2].GetUInt32();
+ data.PhaseGroup = fields[3].GetUInt32();
+ data.PhaseUseFlags = fields[4].GetUInt8();
+
+ if (data.PhaseGroup && data.PhaseId)
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking_template` has spawn tracking (Id: {}) with `PhaseId` and `PhaseGroup` set, `PhaseGroup` set to 0", data.SpawnTrackingId);
+ data.PhaseGroup = 0;
+ }
+
+ if (data.PhaseId)
+ {
+ if (!sPhaseStore.HasRecord(data.PhaseId))
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking_template` has spawn tracking (Id: {}) referencing non-existing `PhaseId` {}, set to 0", data.SpawnTrackingId, data.PhaseId);
+ data.PhaseId = 0;
+ }
+ }
+
+ if (data.PhaseGroup)
+ {
+ if (!sDB2Manager.GetPhasesForGroup(data.PhaseGroup))
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking_template` has spawn tracking (Id: {}) referencing non-existing `PhaseGroup` {}, set to 0", data.SpawnTrackingId, data.PhaseGroup);
+ data.PhaseGroup = 0;
+ }
+ }
+
+ if (data.PhaseUseFlags & ~PHASE_USE_FLAGS_ALL)
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking_template` has spawn tracking (Id: {}) referencing unknown `PhaseUseFlags`, removed unknown value.", data.SpawnTrackingId);
+ data.PhaseUseFlags &= PHASE_USE_FLAGS_ALL;
+ }
+
+ if (data.PhaseUseFlags & PHASE_USE_FLAGS_ALWAYS_VISIBLE && data.PhaseUseFlags & PHASE_USE_FLAGS_INVERSE)
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking_template` has spawn tracking (Id: {}) with `PhaseUseFlags` PHASE_USE_FLAGS_ALWAYS_VISIBLE and PHASE_USE_FLAGS_INVERSE,"
+ " removing PHASE_USE_FLAGS_INVERSE.", data.SpawnTrackingId);
+ data.PhaseUseFlags &= ~PHASE_USE_FLAGS_INVERSE;
+ }
+
+ } while (result->NextRow());
+
+ TC_LOG_INFO("server.loading", ">> Loaded {} spawn tracking templates in {} ms", _spawnTrackingDataStore.size(), GetMSTimeDiffToNow(oldMSTime));
+}
+
+SpawnTrackingTemplateData const* ObjectMgr::GetSpawnTrackingData(uint32 spawnTrackingId) const
+{
+ return Trinity::Containers::MapGetValuePtr(_spawnTrackingDataStore, spawnTrackingId);
+}
+
+bool ObjectMgr::IsQuestObjectiveForSpawnTracking(uint32 spawnTrackingId, uint32 questObjectiveId) const
+{
+ auto itr = _spawnTrackingQuestObjectiveStore.find(spawnTrackingId);
+ if (itr != _spawnTrackingQuestObjectiveStore.end())
+ {
+ std::vector<QuestObjective const*> const* questObjectiveList = &itr->second;
+ if (std::ranges::find(*questObjectiveList, questObjectiveId, &QuestObjective::ID) != (*questObjectiveList).end())
+ return true;
+ }
+ return false;
+}
+
+void ObjectMgr::LoadSpawnTrackingQuestObjectives()
+{
+ uint32 oldMSTime = getMSTime();
+
+ // need for reload case
+ _spawnTrackingQuestObjectiveStore.clear();
+
+ // 0 1
+ QueryResult result = WorldDatabase.Query("SELECT SpawnTrackingId, QuestObjectiveId FROM spawn_tracking_quest_objective");
+
+ if (!result)
+ {
+ TC_LOG_INFO("server.loading", ">> Loaded 0 spawn tracking quest objectives. DB table `spawn_tracking_quest_objective` is empty!");
+ return;
+ }
+
+ uint32 count = 0;
+
+ do
+ {
+ Field* fields = result->Fetch();
+
+ uint32 spawnTrackingId = fields[0].GetUInt32();
+ uint32 objectiveId = fields[1].GetUInt32();
+
+ if (!GetSpawnTrackingData(spawnTrackingId))
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking_quest_objective` has quest objective {} assigned to spawn tracking {}, but spawn tracking does not exist!", objectiveId, spawnTrackingId);
+ continue;
+ }
+
+ QuestObjective const* questObjective = GetQuestObjective(objectiveId);
+ if (!questObjective)
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking_quest_objective` has quest objective {} assigned to spawn tracking {}, but quest objective does not exist!", objectiveId, spawnTrackingId);
+ continue;
+ }
+
+ _spawnTrackingQuestObjectiveStore[spawnTrackingId].push_back(questObjective);
+
+ ++count;
+ } while (result->NextRow());
+
+ TC_LOG_INFO("server.loading", ">> Loaded {} spawn tracking quest objectives in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
+}
+
+void ObjectMgr::LoadSpawnTrackings()
+{
+ uint32 oldMSTime = getMSTime();
+
+ // need for reload case
+ _spawnTrackingMapStore.clear();
+
+ // 0 1 2 3
+ QueryResult result = WorldDatabase.Query("SELECT SpawnTrackingId, SpawnType, SpawnId, QuestObjectiveId FROM spawn_tracking");
+
+ if (!result)
+ {
+ TC_LOG_INFO("server.loading", ">> Loaded 0 spawn tracking members. DB table `spawn_tracking` is empty!");
+ return;
+ }
+
+ uint32 count = 0;
+
+ do
+ {
+ Field* fields = result->Fetch();
+ 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))
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking` has spawn data with invalid type {} listed for spawn tracking {}. Skipped.", uint32(spawnType), spawnTrackingId);
+ continue;
+ }
+ else if (spawnType == SPAWN_TYPE_AREATRIGGER)
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking` has areatrigger spawn ({}) listed for spawn tracking {}. Skipped.", uint32(spawnType), spawnTrackingId);
+ continue;
+ }
+
+ SpawnMetadata const* data = GetSpawnMetadata(spawnType, spawnId);
+ if (!data)
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking` has spawn ({},{}) not found, but is listed as a member of spawn tracking {}!", uint32(spawnType), spawnId, spawnTrackingId);
+ continue;
+ }
+ else if (data->spawnTrackingData)
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking` has spawn ({},{}) is listed as a member of spawn tracking {}, but is already a member of spawn tracking {}. Skipped.",
+ uint32(spawnType), spawnId, spawnTrackingId, data->spawnTrackingData->SpawnTrackingId);
+ continue;
+ }
+
+ SpawnTrackingTemplateData const* spawnTrackingTemplateData = GetSpawnTrackingData(spawnTrackingId);
+ if (!spawnTrackingTemplateData)
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking` has spawn tracking {} assigned to spawn ({},{}), but spawn tracking does not exist!", spawnTrackingId, uint32(spawnType), spawnId);
+ 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!",
+ spawnTrackingId, spawnTrackingTemplateData->MapId, uint32(spawnType), spawnId, data->mapId);
+ continue;
+ }
+
+ SpawnData const* spawnData = data->ToSpawnData();
+ if (spawnTrackingTemplateData->PhaseId != spawnData->phaseId || spawnTrackingTemplateData->PhaseGroup != spawnData->phaseGroup || spawnTrackingTemplateData->PhaseUseFlags != spawnData->phaseUseFlags)
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking` has spawn tracking {} with phase info (PhaseId: {}, PhaseGroup: {}, PhaseUseFlags: {}), ",
+ "but spawn ({},{}) has different phase info (PhaseId: {}, PhaseGroup: {}, PhaseUseFlags: {}) - spawn NOT added to spawn tracking!",
+ spawnTrackingId, spawnTrackingTemplateData->PhaseId, spawnTrackingTemplateData->PhaseGroup, spawnTrackingTemplateData->PhaseUseFlags,
+ uint32(spawnType), spawnId, spawnData->phaseId, spawnData->phaseGroup, spawnData->phaseUseFlags);
+ continue;
+ }
+
+ const_cast<SpawnMetadata*>(data)->spawnTrackingData = spawnTrackingTemplateData;
+ const_cast<SpawnMetadata*>(data)->spawnTrackingQuestObjectiveId = objectiveId;
+ _spawnTrackingMapStore.emplace(spawnTrackingId, data);
+
+ ++count;
+ } while (result->NextRow());
+
+ TC_LOG_INFO("server.loading", ">> Loaded {} spawn tracking members in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
+}
+
+void ObjectMgr::LoadSpawnTrackingStates()
+{
+ uint32 oldMSTime = getMSTime();
+
+ // 0 1 2 3 4 5 6 7
+ QueryResult result = WorldDatabase.Query("SELECT SpawnType, SpawnId, State, Visible, StateSpellVisualId, StateAnimId, StateAnimKitId, StateWorldEffects FROM spawn_tracking_state");
+
+ if (!result)
+ {
+ TC_LOG_INFO("server.loading", ">> Loaded 0 spawn tracking states. DB table `spawn_tracking_state` is empty!");
+ return;
+ }
+
+ uint32 count = 0;
+
+ do
+ {
+ Field* fields = result->Fetch();
+ SpawnObjectType spawnType = SpawnObjectType(fields[0].GetUInt8());
+ ObjectGuid::LowType spawnId = fields[1].GetUInt64();
+ SpawnTrackingState state = SpawnTrackingState(fields[2].GetUInt8());
+
+ if (!SpawnData::TypeIsValid(spawnType))
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking_state` has spawn data with invalid type {}. Skipped.", uint32(spawnType));
+ continue;
+ }
+ else if (spawnType == SPAWN_TYPE_AREATRIGGER)
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking_state` has areatrigger spawn ({}). Skipped.", uint32(spawnType));
+ continue;
+ }
+
+ SpawnMetadata const* data = GetSpawnMetadata(spawnType, spawnId);
+ if (!data)
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking_state` has spawn ({},{}) not found!", uint32(spawnType), spawnId);
+ continue;
+ }
+ else if (!data->spawnTrackingData)
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking_state` has spawn ({},{}) with spawn tracking states, but is not part of a spawn tracking. Skipped.", uint32(spawnType), spawnId);
+ continue;
+ }
+
+ if (state >= SpawnTrackingState::Max)
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking_state` has spawn ({},{}) with invalid state type {}. Skipped.", uint32(spawnType), spawnId, uint8(state));
+ continue;
+ }
+
+ SpawnTrackingStateData& spawnTrackingStateData = const_cast<SpawnMetadata*>(data)->spawnTrackingStates[AsUnderlyingType(state)];
+ spawnTrackingStateData.Visible = fields[3].GetBool();
+
+ if (!fields[4].IsNull())
+ {
+ spawnTrackingStateData.StateSpellVisualId = fields[4].GetUInt32();
+ if (!sSpellVisualStore.HasRecord(*spawnTrackingStateData.StateSpellVisualId))
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking_state` references invalid StateSpellVisualId {} for spawn ({},{}), set to none.",
+ *spawnTrackingStateData.StateSpellVisualId, uint32(spawnType), spawnId);
+ spawnTrackingStateData.StateSpellVisualId.reset();
+ }
+ }
+
+ if (!fields[5].IsNull())
+ {
+ spawnTrackingStateData.StateAnimId = fields[5].GetUInt16();
+ if (*spawnTrackingStateData.StateAnimId != sDB2Manager.GetEmptyAnimStateID() && !sAnimationDataStore.HasRecord(*spawnTrackingStateData.StateAnimId))
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking_state` references invalid StateAnimId {} for spawn ({},{}), set to none.",
+ *spawnTrackingStateData.StateAnimId, uint32(spawnType), spawnId);
+ spawnTrackingStateData.StateAnimId.reset();
+ }
+ }
+
+ if (!fields[6].IsNull())
+ {
+ spawnTrackingStateData.StateAnimKitId = fields[6].GetUInt16();
+ if (!sAnimKitStore.HasRecord(*spawnTrackingStateData.StateAnimKitId))
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking_state` references invalid StateAnimKitId {} for spawn ({},{}), set to none.",
+ *spawnTrackingStateData.StateAnimKitId, uint32(spawnType), spawnId);
+ spawnTrackingStateData.StateAnimKitId.reset();
+ }
+ }
+
+ if (!fields[7].IsNull())
+ {
+ std::vector<uint32> worldEffectList;
+ for (std::string_view worldEffectsStr : Trinity::Tokenize(fields[7].GetStringView(), ',', false))
+ {
+ Optional<uint32> worldEffectId = Trinity::StringTo<uint32>(worldEffectsStr);
+ if (!worldEffectId)
+ continue;
+
+ if (!sWorldEffectStore.HasRecord(*worldEffectId))
+ {
+ TC_LOG_ERROR("sql.sql", "Table `spawn_tracking_state` references invalid StateAnimKitId {} for spawn ({},{}). Skipped.",
+ *worldEffectId, uint32(spawnType), spawnId);
+ continue;
+ }
+
+ worldEffectList.push_back(*worldEffectId);
+ }
+
+ if (!worldEffectList.empty())
+ spawnTrackingStateData.StateWorldEffects = std::move(worldEffectList);
+ }
+
+ ++count;
+ } while (result->NextRow());
+
+ TC_LOG_INFO("server.loading", ">> Loaded {} spawn tracking states in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
+}
+
void ObjectMgr::LoadJumpChargeParams()
{
uint32 oldMSTime = getMSTime();
diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h
index 006b25a3ffc..416133c8430 100644
--- a/src/server/game/Globals/ObjectMgr.h
+++ b/src/server/game/Globals/ObjectMgr.h
@@ -521,6 +521,9 @@ typedef std::unordered_map<uint32, std::vector<uint32>> GameObjectQuestItemMap;
typedef std::unordered_map<uint32, SpawnGroupTemplateData> SpawnGroupDataContainer;
typedef std::multimap<uint32, SpawnMetadata const*> SpawnGroupLinkContainer;
typedef std::unordered_map<uint16, std::vector<InstanceSpawnGroupInfo>> InstanceSpawnGroupContainer;
+typedef std::unordered_map<uint32, SpawnTrackingTemplateData> SpawnTrackingTemplateContainer;
+typedef std::multimap<uint32, SpawnMetadata const*> SpawnTrackingLinkContainer;
+typedef std::unordered_map<uint32 /*spawnTrackingId*/, std::vector<QuestObjective const*>> SpawnTrackingQuestObjectiveContainer;
typedef std::map<TempSummonGroupKey, std::vector<TempSummonData>> TempSummonDataContainer;
typedef std::unordered_map<uint32, CreatureLocale> CreatureLocaleContainer;
typedef std::unordered_map<uint32, GameObjectLocale> GameObjectLocaleContainer;
@@ -1428,6 +1431,11 @@ class TC_GAME_API ObjectMgr
void LoadUiMapQuestLines();
void LoadUiMapQuests();
+ void LoadSpawnTrackingTemplates();
+ void LoadSpawnTrackingQuestObjectives();
+ void LoadSpawnTrackings();
+ void LoadSpawnTrackingStates();
+
void LoadJumpChargeParams();
void LoadPhaseNames();
@@ -1471,6 +1479,11 @@ class TC_GAME_API ObjectMgr
std::vector<uint32> const* GetSpawnGroupsForMap(uint32 mapId) const { auto it = _spawnGroupsByMap.find(mapId); return it != _spawnGroupsByMap.end() ? &it->second : nullptr; }
std::vector<InstanceSpawnGroupInfo> const* GetInstanceSpawnGroupsForMap(uint32 mapId) const { auto it = _instanceSpawnGroupStore.find(mapId); return it != _instanceSpawnGroupStore.end() ? &it->second : nullptr; }
+ SpawnTrackingTemplateData const* GetSpawnTrackingData(uint32 spawnTrackingId) const;
+ Trinity::IteratorPair<SpawnTrackingLinkContainer::const_iterator> GetSpawnMetadataForSpawnTracking(uint32 spawnTrackingId) const { return Trinity::Containers::MapEqualRange(_spawnTrackingMapStore, spawnTrackingId); }
+ std::vector<QuestObjective const*> const* GetSpawnTrackingQuestObjectiveList(uint32 spawnTrackingId) const { auto it = _spawnTrackingQuestObjectiveStore.find(spawnTrackingId); return it != _spawnTrackingQuestObjectiveStore.end() ? &it->second : nullptr; }
+ bool IsQuestObjectiveForSpawnTracking(uint32 spawnTrackingId, uint32 questObjectiveId) const;
+
MailLevelReward const* GetMailLevelReward(uint8 level, uint8 race) const
{
MailLevelRewardContainer::const_iterator map_itr = _mailLevelRewardStore.find(level);
@@ -1938,6 +1951,9 @@ class TC_GAME_API ObjectMgr
std::unordered_map<uint32, std::vector<uint32>> _spawnGroupsByMap;
SpawnGroupLinkContainer _spawnGroupMapStore;
InstanceSpawnGroupContainer _instanceSpawnGroupStore;
+ SpawnTrackingTemplateContainer _spawnTrackingDataStore;
+ SpawnTrackingLinkContainer _spawnTrackingMapStore;
+ SpawnTrackingQuestObjectiveContainer _spawnTrackingQuestObjectiveStore;
/// Stores temp summon data grouped by summoner's entry, summoner's type and group id
TempSummonDataContainer _tempSummonDataStore;
std::unordered_map<int32 /*choiceId*/, PlayerChoice> _playerChoices;
diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp
index a7321265e4f..2dbfcb4ff78 100644
--- a/src/server/game/Handlers/CharacterHandler.cpp
+++ b/src/server/game/Handlers/CharacterHandler.cpp
@@ -133,6 +133,10 @@ bool LoginQueryHolder::Initialize()
stmt->setUInt64(0, lowGuid);
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS_OBJECTIVES_CRITERIA_PROGRESS, stmt);
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_QUESTSTATUS_OBJECTIVES_SPAWN_TRACKING);
+ stmt->setUInt64(0, lowGuid);
+ res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS_OBJECTIVES_SPAWN_TRACKING, stmt);
+
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_QUESTSTATUS_DAILY);
stmt->setUInt64(0, lowGuid);
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_DAILY_QUEST_STATUS, stmt);
diff --git a/src/server/game/Handlers/QuestHandler.cpp b/src/server/game/Handlers/QuestHandler.cpp
index cec2672e689..956b4135d3a 100644
--- a/src/server/game/Handlers/QuestHandler.cpp
+++ b/src/server/game/Handlers/QuestHandler.cpp
@@ -445,6 +445,7 @@ void WorldSession::HandleQuestLogRemoveQuest(WorldPackets::Quest::QuestLogRemove
}
}
+ _player->SendForceSpawnTrackingUpdate(questId);
_player->SetQuestSlot(packet.Entry, 0);
_player->TakeQuestSourceItem(questId, true); // remove quest src item from player
_player->AbandonQuest(questId); // remove all quest items player received before abandoning quest. Note, this does not remove normal drop items that happen to be quest requirements.
@@ -873,3 +874,65 @@ void WorldSession::HandleUiMapQuestLinesRequest(WorldPackets::Quest::UiMapQuestL
SendPacket(response.Write());
}
+
+void WorldSession::HandleSpawnTrackingUpdate(WorldPackets::Quest::SpawnTrackingUpdate& spawnTrackingUpdate)
+{
+ WorldPackets::Quest::QuestPOIUpdateResponse response;
+
+ auto hasObjectTypeRequested = [](TypeMask objectTypeMask, SpawnObjectType objectType) -> bool
+ {
+ if (objectTypeMask & TYPEMASK_UNIT)
+ return objectType == SPAWN_TYPE_CREATURE;
+ else if (objectTypeMask & TYPEMASK_GAMEOBJECT)
+ return objectType == SPAWN_TYPE_GAMEOBJECT;
+
+ return false;
+ };
+
+ for (WorldPackets::Quest::SpawnTrackingRequestInfo const& requestInfo : spawnTrackingUpdate.SpawnTrackingRequests)
+ {
+ WorldPackets::Quest::SpawnTrackingResponseInfo responseInfo;
+ responseInfo.SpawnTrackingID = requestInfo.SpawnTrackingID;
+ responseInfo.ObjectID = requestInfo.ObjectID;
+
+ SpawnTrackingTemplateData const* spawnTrackingTemplateData = sObjectMgr->GetSpawnTrackingData(requestInfo.SpawnTrackingID);
+ QuestObjective const* activeQuestObjective = _player->GetActiveQuestObjectiveForForSpawnTracking(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))
+ {
+ responseInfo.PhaseID = spawnTrackingTemplateData->PhaseId;
+ responseInfo.PhaseGroupID = spawnTrackingTemplateData->PhaseGroup;
+ responseInfo.PhaseUseFlags = spawnTrackingTemplateData->PhaseUseFlags;
+
+ // Send spawn visibility data if available
+ if (requestInfo.ObjectTypeMask && requestInfo.ObjectTypeMask & (TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT))
+ {
+ // There should only be one entity
+ for (auto const& [spawnTrackingId, data] : sObjectMgr->GetSpawnMetadataForSpawnTracking(requestInfo.SpawnTrackingID))
+ {
+ SpawnData const* spawnData = data->ToSpawnData();
+ if (!spawnData)
+ continue;
+
+ if (spawnData->id != (uint32)requestInfo.ObjectID)
+ continue;
+
+ if (!hasObjectTypeRequested(TypeMask(requestInfo.ObjectTypeMask), data->type))
+ continue;
+
+ if (activeQuestObjective)
+ {
+ SpawnTrackingState state = _player->GetSpawnTrackingStateByObjective(spawnTrackingId, activeQuestObjective->ID);
+ responseInfo.Visible = data->spawnTrackingStates[AsUnderlyingType(state)].Visible;
+ break;
+ }
+ }
+ }
+ }
+
+ response.SpawnTrackingResponses.push_back(std::move(responseInfo));
+ }
+
+ SendPacket(response.Write());
+}
diff --git a/src/server/game/Maps/SpawnData.h b/src/server/game/Maps/SpawnData.h
index 25e6864e633..cd8da3dde36 100644
--- a/src/server/game/Maps/SpawnData.h
+++ b/src/server/game/Maps/SpawnData.h
@@ -19,6 +19,7 @@
#define TRINITY_SPAWNDATA_H
#include "DBCEnums.h"
+#include "Optional.h"
#include "Position.h"
#include <vector>
@@ -70,6 +71,32 @@ struct SpawnGroupTemplateData
SpawnGroupFlags flags;
};
+struct SpawnTrackingTemplateData
+{
+ uint32 SpawnTrackingId;
+ uint32 MapId;
+ uint32 PhaseId;
+ uint32 PhaseGroup;
+ uint8 PhaseUseFlags;
+};
+
+struct SpawnTrackingStateData
+{
+ bool Visible = true;
+ Optional<uint32> StateSpellVisualId;
+ Optional<uint16> StateAnimId;
+ Optional<uint16> StateAnimKitId;
+ std::vector<uint32> StateWorldEffects;
+};
+
+enum class SpawnTrackingState : uint8
+{
+ None = 0,
+ Active = 1,
+ Complete = 2,
+ Max
+};
+
namespace Trinity { namespace Impl {
template <typename T>
struct SpawnObjectTypeForImpl { static_assert(!std::is_same<T,T>::value, "This type does not have an associated spawn type!"); };
@@ -94,6 +121,9 @@ struct SpawnMetadata
uint32 mapId = MAPID_INVALID;
bool dbData = true;
SpawnGroupTemplateData const* spawnGroupData = nullptr;
+ SpawnTrackingTemplateData const* spawnTrackingData = nullptr;
+ uint32 spawnTrackingQuestObjectiveId = 0;
+ std::array<SpawnTrackingStateData, size_t(SpawnTrackingState::Max)> spawnTrackingStates;
protected:
SpawnMetadata(SpawnObjectType t) : type(t) {}
diff --git a/src/server/game/Quests/QuestDef.h b/src/server/game/Quests/QuestDef.h
index 3d34b4d2565..47b8e2c5cd0 100644
--- a/src/server/game/Quests/QuestDef.h
+++ b/src/server/game/Quests/QuestDef.h
@@ -28,6 +28,7 @@
#include "UniqueTrackablePtr.h"
#include "WorldPacket.h"
#include <bitset>
+#include <unordered_set>
#include <vector>
class Player;
@@ -872,6 +873,7 @@ struct QuestStatusData
time_t AcceptTime = time_t(0);
uint32 Timer = 0;
bool Explored = false;
+ std::unordered_set<std::pair<int8, uint32>> SpawnTrackingList;
};
#endif
diff --git a/src/server/game/Server/Packets/QuestPackets.cpp b/src/server/game/Server/Packets/QuestPackets.cpp
index 496d7f917b2..be591c38109 100644
--- a/src/server/game/Server/Packets/QuestPackets.cpp
+++ b/src/server/game/Server/Packets/QuestPackets.cpp
@@ -883,4 +883,50 @@ void UiMapQuestLinesRequest::Read()
{
_worldPacket >> UiMapID;
}
+
+ByteBuffer& operator>>(ByteBuffer& data, WorldPackets::Quest::SpawnTrackingRequestInfo& spawnTrackingRequestInfo)
+{
+ data >> spawnTrackingRequestInfo.ObjectTypeMask;
+ data >> spawnTrackingRequestInfo.ObjectID;
+ data >> spawnTrackingRequestInfo.SpawnTrackingID;
+ return data;
+}
+
+void SpawnTrackingUpdate::Read()
+{
+ SpawnTrackingRequests.resize(_worldPacket.read<uint32>());
+ for (SpawnTrackingRequestInfo& spawnTrackingRequestInfo : SpawnTrackingRequests)
+ _worldPacket >> spawnTrackingRequestInfo;
+}
+
+ByteBuffer& operator<<(ByteBuffer& data, SpawnTrackingResponseInfo const& spawnTrackingResponseInfo)
+{
+ data << uint32(spawnTrackingResponseInfo.SpawnTrackingID);
+ data << int32(spawnTrackingResponseInfo.ObjectID);
+ data << int32(spawnTrackingResponseInfo.PhaseID);
+ data << int32(spawnTrackingResponseInfo.PhaseGroupID);
+ data << int32(spawnTrackingResponseInfo.PhaseUseFlags);
+
+ data << Bits<1>(spawnTrackingResponseInfo.Visible);
+ data.FlushBits();
+
+ return data;
+}
+
+WorldPacket const* QuestPOIUpdateResponse::Write()
+{
+ _worldPacket << uint32(SpawnTrackingResponses.size());
+
+ for (SpawnTrackingResponseInfo const& spawnTrackingResponseInfo : SpawnTrackingResponses)
+ _worldPacket << spawnTrackingResponseInfo;
+
+ return &_worldPacket;
+}
+
+WorldPacket const* ForceSpawnTrackingUpdate::Write()
+{
+ _worldPacket << int32(QuestID);
+
+ return &_worldPacket;
+}
}
diff --git a/src/server/game/Server/Packets/QuestPackets.h b/src/server/game/Server/Packets/QuestPackets.h
index 133afeff714..5a6e0e9be6f 100644
--- a/src/server/game/Server/Packets/QuestPackets.h
+++ b/src/server/game/Server/Packets/QuestPackets.h
@@ -812,6 +812,53 @@ namespace WorldPackets
int32 UiMapID = 0;
};
+
+ struct SpawnTrackingRequestInfo
+ {
+ int32 ObjectTypeMask = 0;
+ int32 ObjectID = 0;
+ uint32 SpawnTrackingID = 0;
+ };
+
+ class SpawnTrackingUpdate final : public ClientPacket
+ {
+ public:
+ SpawnTrackingUpdate(WorldPacket&& packet) : ClientPacket(CMSG_SPAWN_TRACKING_UPDATE, std::move(packet)) { }
+
+ void Read() override;
+
+ std::vector<SpawnTrackingRequestInfo> SpawnTrackingRequests;
+ };
+
+ struct SpawnTrackingResponseInfo
+ {
+ uint32 SpawnTrackingID = 0;
+ int32 ObjectID = 0;
+ int32 PhaseID = 0;
+ int32 PhaseGroupID = 0;
+ int32 PhaseUseFlags = 0;
+ bool Visible = true;
+ };
+
+ class QuestPOIUpdateResponse final : public ServerPacket
+ {
+ public:
+ QuestPOIUpdateResponse() : ServerPacket(SMSG_QUEST_POI_UPDATE_RESPONSE, 21) { }
+
+ WorldPacket const* Write() override;
+
+ std::vector<SpawnTrackingResponseInfo> SpawnTrackingResponses;
+ };
+
+ class ForceSpawnTrackingUpdate final : public ServerPacket
+ {
+ public:
+ ForceSpawnTrackingUpdate() : ServerPacket(SMSG_FORCE_SPAWN_TRACKING_UPDATE, 4) { }
+
+ WorldPacket const* Write() override;
+
+ int32 QuestID = 0;
+ };
}
}
diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp
index 920f4b79e5a..c215da2dff2 100644
--- a/src/server/game/Server/Protocol/Opcodes.cpp
+++ b/src/server/game/Server/Protocol/Opcodes.cpp
@@ -941,7 +941,7 @@ void OpcodeTable::InitializeClientOpcodes()
DEFINE_HANDLER(CMSG_SORT_BANK_BAGS, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleSortBankBags);
DEFINE_HANDLER(CMSG_SORT_REAGENT_BANK_BAGS, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleSortReagentBankBags);
DEFINE_HANDLER(CMSG_SPECTATE_CHANGE, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL);
- DEFINE_HANDLER(CMSG_SPAWN_TRACKING_UPDATE, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL);
+ DEFINE_HANDLER(CMSG_SPAWN_TRACKING_UPDATE, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleSpawnTrackingUpdate);
DEFINE_HANDLER(CMSG_SPELL_CLICK, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleSpellClick);
DEFINE_HANDLER(CMSG_SPELL_EMPOWER_RELEASE, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleSpellEmpowerRelease);
DEFINE_HANDLER(CMSG_SPELL_EMPOWER_RESTART, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleSpellEmpowerRestart);
@@ -1401,7 +1401,7 @@ void OpcodeTable::InitializeServerOpcodes()
DEFINE_SERVER_OPCODE_HANDLER(SMSG_FORCE_ANIMATIONS, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_FORCE_OBJECT_RELINK, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_FORCE_RANDOM_TRANSMOG_TOAST, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
- DEFINE_SERVER_OPCODE_HANDLER(SMSG_FORCE_SPAWN_TRACKING_UPDATE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
+ DEFINE_SERVER_OPCODE_HANDLER(SMSG_FORCE_SPAWN_TRACKING_UPDATE, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_FRIEND_STATUS, STATUS_NEVER, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_GAIN_MAW_POWER, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_GAME_OBJECT_ACTIVATE_ANIM_KIT, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
@@ -1965,7 +1965,7 @@ void OpcodeTable::InitializeServerOpcodes()
DEFINE_SERVER_OPCODE_HANDLER(SMSG_QUEST_LOG_FULL, STATUS_NEVER, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_QUEST_NON_LOG_UPDATE_COMPLETE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_QUEST_POI_QUERY_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM);
- DEFINE_SERVER_OPCODE_HANDLER(SMSG_QUEST_POI_UPDATE_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
+ DEFINE_SERVER_OPCODE_HANDLER(SMSG_QUEST_POI_UPDATE_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_QUEST_PUSH_RESULT, STATUS_NEVER, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_QUEST_SESSION_INFO_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_QUEST_SESSION_READY_CHECK, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h
index 94291a97ca4..d36eb76f9b6 100644
--- a/src/server/game/Server/WorldSession.h
+++ b/src/server/game/Server/WorldSession.h
@@ -676,6 +676,7 @@ namespace WorldPackets
class RequestWorldQuestUpdate;
class ChoiceResponse;
class UiMapQuestLinesRequest;
+ class SpawnTrackingUpdate;
}
namespace RaF
@@ -1572,6 +1573,7 @@ class TC_GAME_API WorldSession
void HandlePlayerChoiceResponse(WorldPackets::Quest::ChoiceResponse& choiceResponse);
void HandleUiMapQuestLinesRequest(WorldPackets::Quest::UiMapQuestLinesRequest& uiMapQuestLinesRequest);
void HandleQueryTreasurePicker(WorldPackets::Query::QueryTreasurePicker const& queryTreasurePicker);
+ void HandleSpawnTrackingUpdate(WorldPackets::Quest::SpawnTrackingUpdate& spawnTrackingUpdate);
void HandleChatMessageOpcode(WorldPackets::Chat::ChatMessage& chatMessage);
void HandleChatMessageWhisperOpcode(WorldPackets::Chat::ChatMessageWhisper& chatMessageWhisper);
diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp
index 00de03a22cb..dd88e890df5 100644
--- a/src/server/game/World/World.cpp
+++ b/src/server/game/World/World.cpp
@@ -2190,6 +2190,18 @@ bool World::SetInitialWorldSettings()
TC_LOG_INFO("server.loading", "Loading Player Choices...");
sObjectMgr->LoadPlayerChoices();
+ TC_LOG_INFO("server.loading", "Loading Spawn Tracking Templates...");
+ sObjectMgr->LoadSpawnTrackingTemplates();
+
+ TC_LOG_INFO("server.loading", "Loading Spawn Tracking Quest Objectives...");
+ sObjectMgr->LoadSpawnTrackingQuestObjectives();
+
+ TC_LOG_INFO("server.loading", "Loading Spawn Tracking Spawns...");
+ sObjectMgr->LoadSpawnTrackings();
+
+ TC_LOG_INFO("server.loading", "Loading Spawn Tracking Spawn States...");
+ sObjectMgr->LoadSpawnTrackingStates();
+
if (m_bool_configs[CONFIG_LOAD_LOCALES])
{
TC_LOG_INFO("server.loading", "Loading Player Choices Locales...");