diff options
Diffstat (limited to 'src')
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..."); |