/* * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "ConversationDataStore.h" #include "DB2Stores.h" #include "DatabaseEnv.h" #include "Log.h" #include "MapUtils.h" #include "ObjectMgr.h" #include "Timer.h" namespace { std::unordered_map _conversationTemplateStore; std::unordered_map _conversationLineTemplateStore; } void ConversationDataStore::LoadConversationTemplates() { _conversationLineTemplateStore.clear(); _conversationTemplateStore.clear(); std::unordered_map> actorsByConversation; if (QueryResult lineTemplates = WorldDatabase.Query("SELECT Id, UiCameraID, ActorIdx, Flags, ChatType FROM conversation_line_template")) { uint32 oldMSTime = getMSTime(); do { Field* fields = lineTemplates->Fetch(); uint32 id = fields[0].GetUInt32(); if (!sConversationLineStore.LookupEntry(id)) { TC_LOG_ERROR("sql.sql", "Table `conversation_line_template` has template for non existing ConversationLine (ID: {}), skipped", id); continue; } ConversationLineTemplate& conversationLine = _conversationLineTemplateStore[id]; conversationLine.Id = id; conversationLine.UiCameraID = fields[1].GetUInt32(); conversationLine.ActorIdx = fields[2].GetUInt8(); conversationLine.Flags = fields[3].GetUInt8(); conversationLine.ChatType = fields[4].GetUInt8(); } while (lineTemplates->NextRow()); TC_LOG_INFO("server.loading", ">> Loaded {} Conversation line templates in {} ms", _conversationLineTemplateStore.size(), GetMSTimeDiffToNow(oldMSTime)); } else { TC_LOG_INFO("server.loading", ">> Loaded 0 Conversation line templates. DB table `conversation_line_template` is empty."); } if (QueryResult actors = WorldDatabase.Query("SELECT ConversationId, ConversationActorId, ConversationActorGuid, Idx, CreatureId, CreatureDisplayInfoId, NoActorObject, ActivePlayerObject FROM conversation_actors")) { uint32 oldMSTime = getMSTime(); uint32 count = 0; struct ConversationActorDbRow { uint32 ConversationId = 0; uint32 ActorIndex = 0; ObjectGuid::LowType SpawnId = 0; uint32 CreatureId = 0; uint32 CreatureDisplayInfoId = 0; bool operator()(ConversationActorWorldObjectTemplate& worldObject) const { if (!sObjectMgr->GetCreatureData(SpawnId)) { TC_LOG_ERROR("sql.sql", "Table `conversation_actors` references an invalid creature guid (GUID: {}) for Conversation {} and Idx {}, skipped.", SpawnId, ConversationId, ActorIndex); return false; } if (CreatureId) TC_LOG_ERROR("sql.sql", "Table `conversation_actors` with ConversationActorGuid cannot have CreatureId ({}). Conversation {} and Idx {}.", CreatureId, ConversationId, ActorIndex); if (CreatureDisplayInfoId) TC_LOG_ERROR("sql.sql", "Table `conversation_actors` with ConversationActorGuid cannot have CreatureDisplayInfoId ({}). Conversation {} and Idx {}.", CreatureDisplayInfoId, ConversationId, ActorIndex); worldObject.SpawnId = SpawnId; return true; } bool operator()(ConversationActorNoObjectTemplate& noObject) const { if (!sObjectMgr->GetCreatureTemplate(CreatureId)) { TC_LOG_ERROR("sql.sql", "Table `conversation_actors` references an invalid creature id ({}) for Conversation {} and Idx {}, skipped.", CreatureId, ConversationId, ActorIndex); return false; } if (CreatureDisplayInfoId && !sCreatureDisplayInfoStore.LookupEntry(CreatureDisplayInfoId)) { TC_LOG_ERROR("sql.sql", "Table `conversation_actors` references an invalid creature display id ({}) for Conversation {} and Idx {}, skipped.", CreatureDisplayInfoId, ConversationId, ActorIndex); return false; } if (SpawnId) TC_LOG_ERROR("sql.sql", "Table `conversation_actors` with NoActorObject cannot have ConversationActorGuid ({}). Conversation {} and Idx {}.", SpawnId, ConversationId, ActorIndex); noObject.CreatureId = CreatureId; noObject.CreatureDisplayInfoId = CreatureDisplayInfoId; return true; } bool operator()([[maybe_unused]] ConversationActorActivePlayerTemplate& activePlayer) const { if (SpawnId) TC_LOG_ERROR("sql.sql", "Table `conversation_actors` with ActivePlayerObject cannot have ConversationActorGuid ({}). Conversation {} and Idx {}.", SpawnId, ConversationId, ActorIndex); if (CreatureId) TC_LOG_ERROR("sql.sql", "Table `conversation_actors` with ActivePlayerObject cannot have CreatureId ({}). Conversation {} and Idx {}.", CreatureId, ConversationId, ActorIndex); if (CreatureDisplayInfoId) TC_LOG_ERROR("sql.sql", "Table `conversation_actors` with ActivePlayerObject cannot have CreatureDisplayInfoId ({}). Conversation {} and Idx {}.", CreatureDisplayInfoId, ConversationId, ActorIndex); return true; } bool operator()(ConversationActorTalkingHeadTemplate& talkingHead) const { if (!sObjectMgr->GetCreatureTemplate(CreatureId)) { TC_LOG_ERROR("sql.sql", "Table `conversation_actors` references an invalid creature id ({}) for Conversation {} and Idx {}, skipped.", CreatureId, ConversationId, ActorIndex); return false; } if (CreatureDisplayInfoId && !sCreatureDisplayInfoStore.LookupEntry(CreatureDisplayInfoId)) { TC_LOG_ERROR("sql.sql", "Table `conversation_actors` references an invalid creature display id ({}) for Conversation {} and Idx {}, skipped.", CreatureDisplayInfoId, ConversationId, ActorIndex); return false; } if (SpawnId) TC_LOG_ERROR("sql.sql", "Table `conversation_actors` with TalkingHead cannot have ConversationActorGuid ({}). Conversation {} and Idx {}.", SpawnId, ConversationId, ActorIndex); talkingHead.CreatureId = CreatureId; talkingHead.CreatureDisplayInfoId = CreatureDisplayInfoId; return true; } }; do { Field* fields = actors->Fetch(); ConversationActorDbRow data; ConversationActorTemplate actor; data.ConversationId = fields[0].GetUInt32(); actor.Id = fields[1].GetUInt32(); data.SpawnId = fields[2].GetUInt64(); data.ActorIndex = actor.Index = fields[3].GetUInt16(); data.CreatureId = fields[4].GetUInt32(); data.CreatureDisplayInfoId = fields[5].GetUInt32(); bool noActorObject = fields[6].GetUInt8() == 1; bool activePlayerObject = fields[7].GetUInt8() == 1; if (activePlayerObject) actor.Data.emplace(); else if (noActorObject) actor.Data.emplace(); else if (data.SpawnId || !data.CreatureId) // @TODO: remove CreatureId check when actor flags are implemented actor.Data.emplace(); else actor.Data.emplace(); bool valid = std::visit(data, actor.Data); if (!valid) continue; actorsByConversation[data.ConversationId].push_back(actor); ++count; } while (actors->NextRow()); TC_LOG_INFO("server.loading", ">> Loaded {} Conversation actors in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); } else { TC_LOG_INFO("server.loading", ">> Loaded 0 Conversation actors. DB table `conversation_actors` is empty."); } // TODO: Remove this hack when NextConversationLineID is changed to uint32 auto getNextConversationLineId = [&](ConversationLineEntry const* conversationLine) { if (conversationLine && conversationLine->NextConversationLineID) { static constexpr uint32 FirstLineId = 60000; // Arbitrary id to cover the affected rows if (conversationLine->ID > FirstLineId && conversationLine->NextConversationLineID < (sConversationLineStore.GetNumRows() - USHRT_MAX - 1)) return (uint32)(USHRT_MAX + conversationLine->NextConversationLineID + 1); return (uint32)conversationLine->NextConversationLineID; } return 0u; }; // Validate FirstLineId std::unordered_map prevConversationLineIds; for (ConversationLineEntry const* conversationLine : sConversationLineStore) if (uint32 nextConversationLineId = getNextConversationLineId(conversationLine)) prevConversationLineIds[nextConversationLineId] = conversationLine->ID; auto getFirstLineIdFromAnyLineId = [&](uint32 lineId) { while (uint32 const* prevLineId = Trinity::Containers::MapGetValuePtr(prevConversationLineIds, lineId)) lineId = *prevLineId; return lineId; }; if (QueryResult templates = WorldDatabase.Query("SELECT Id, FirstLineId, TextureKitId, Flags, ScriptName FROM conversation_template")) { uint32 oldMSTime = getMSTime(); do { Field* fields = templates->Fetch(); ConversationTemplate conversationTemplate; conversationTemplate.Id = fields[0].GetUInt32(); conversationTemplate.FirstLineId = fields[1].GetUInt32(); conversationTemplate.TextureKitId = fields[2].GetUInt32(); conversationTemplate.Flags = (ConversationFlags)fields[3].GetUInt8(); conversationTemplate.ScriptId = sObjectMgr->GetScriptId(fields[4].GetStringView()); conversationTemplate.Actors = std::move(actorsByConversation[conversationTemplate.Id]); uint32 correctedFirstLineId = getFirstLineIdFromAnyLineId(conversationTemplate.FirstLineId); if (conversationTemplate.FirstLineId != correctedFirstLineId) { TC_LOG_ERROR("sql.sql", "Table `conversation_template` has incorrect FirstLineId {}, it should be {} for Conversation {}, corrected", conversationTemplate.FirstLineId, correctedFirstLineId, conversationTemplate.Id); conversationTemplate.FirstLineId = correctedFirstLineId; } ConversationLineEntry const* currentConversationLine = sConversationLineStore.LookupEntry(conversationTemplate.FirstLineId); if (!currentConversationLine) TC_LOG_ERROR("sql.sql", "Table `conversation_template` references an invalid line (ID: {}) for Conversation {}, skipped", conversationTemplate.FirstLineId, conversationTemplate.Id); while (currentConversationLine != nullptr) { if (ConversationLineTemplate const* conversationLineTemplate = Trinity::Containers::MapGetValuePtr(_conversationLineTemplateStore, currentConversationLine->ID)) conversationTemplate.Lines.push_back(conversationLineTemplate); else TC_LOG_ERROR("sql.sql", "Table `conversation_line_template` has missing template for line (ID: {}) in Conversation {}, skipped", currentConversationLine->ID, conversationTemplate.Id); uint32 nextConversationLineId = getNextConversationLineId(currentConversationLine); if (!nextConversationLineId) break; currentConversationLine = sConversationLineStore.AssertEntry(nextConversationLineId); } _conversationTemplateStore[conversationTemplate.Id] = std::move(conversationTemplate); } while (templates->NextRow()); TC_LOG_INFO("server.loading", ">> Loaded {} Conversation templates in {} ms", _conversationTemplateStore.size(), GetMSTimeDiffToNow(oldMSTime)); } else { TC_LOG_INFO("server.loading", ">> Loaded 0 Conversation templates. DB table `conversation_template` is empty."); } } ConversationTemplate const* ConversationDataStore::GetConversationTemplate(uint32 conversationId) const { return Trinity::Containers::MapGetValuePtr(_conversationTemplateStore, conversationId); } ConversationLineTemplate const* ConversationDataStore::GetConversationLineTemplate(uint32 conversationLineId) const { return Trinity::Containers::MapGetValuePtr(_conversationLineTemplateStore, conversationLineId); } ConversationDataStore* ConversationDataStore::Instance() { static ConversationDataStore instance; return &instance; }