diff --git a/data/sql/updates/pending_db_world/rev_1643973891449948100.sql b/data/sql/updates/pending_db_world/rev_1643973891449948100.sql new file mode 100644 index 000000000..2af155c78 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1643973891449948100.sql @@ -0,0 +1,27 @@ +INSERT INTO `version_db_world` (`sql_rev`) VALUES ('1643973891449948100'); + +DELETE FROM `command` WHERE `name` IN ('reload quest_greeting', 'reload quest_greeting_locale'); +INSERT INTO `command` (`name`, `security`, `help`) VALUES +('reload quest_greeting', 3, 'Syntax: .reload quest_greeting\nReload quest_greeting table.'), +('reload quest_greeting_locale', 3, 'Syntax: .reload quest_greeting_locale\nReload quest_greeting_locale table.'); + +DROP TABLE IF EXISTS `quest_greeting`; +CREATE TABLE `quest_greeting` ( + `ID` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', + `type` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `GreetEmoteType` SMALLINT UNSIGNED NOT NULL DEFAULT '0', + `GreetEmoteDelay` INT UNSIGNED NOT NULL DEFAULT '0', + `Greeting` TEXT, + `VerifiedBuild` SMALLINT NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`type`) +) ENGINE=MYISAM DEFAULT CHARSET=utf8mb4; + +DROP TABLE IF EXISTS `quest_greeting_locale`; +CREATE TABLE `quest_greeting_locale` ( + `ID` MEDIUMINT UNSIGNED NOT NULL DEFAULT '0', + `type` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `locale` VARCHAR(4) NOT NULL, + `Greeting` TEXT, + `VerifiedBuild` SMALLINT NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`type`,`locale`) +) ENGINE=MYISAM DEFAULT CHARSET=utf8mb4; diff --git a/src/server/game/Entities/Creature/GossipDef.cpp b/src/server/game/Entities/Creature/GossipDef.cpp index 303d829d1..1f7e3cc42 100644 --- a/src/server/game/Entities/Creature/GossipDef.cpp +++ b/src/server/game/Entities/Creature/GossipDef.cpp @@ -18,6 +18,7 @@ #include "GossipDef.h" #include "Formulas.h" #include "ObjectMgr.h" +#include "Object.h" #include "Opcodes.h" #include "Player.h" #include "QuestDef.h" @@ -306,44 +307,63 @@ void QuestMenu::ClearMenu() _questMenuItems.clear(); } -void PlayerMenu::SendQuestGiverQuestList(QEmote const& eEmote, const std::string& Title, ObjectGuid npcGUID) +void PlayerMenu::SendQuestGiverQuestList(QEmote const& eEmote, std::string const& Title, ObjectGuid guid) { - WorldPacket data(SMSG_QUESTGIVER_QUEST_LIST, 100 + _questMenu.GetMenuItemCount() * 75); // guess size - data << npcGUID; - data << Title; - data << uint32(eEmote._Delay); // player emote - data << uint32(eEmote._Emote); // NPC emote + WorldPacket data(SMSG_QUESTGIVER_QUEST_LIST, 100); // guess size + data << guid; + + if (QuestGreeting const* questGreeting = sObjectMgr->GetQuestGreeting(guid.GetTypeId(), guid.GetEntry())) + { + std::string strGreeting = questGreeting->Text; + + LocaleConstant localeConstant = _session->GetSessionDbLocaleIndex(); + if (localeConstant != LOCALE_enUS) + if (QuestGreetingLocale const* questGreetingLocale = sObjectMgr->GetQuestGreetingLocale(guid.GetTypeId(), guid.GetEntry())) + ObjectMgr::GetLocaleString(questGreetingLocale->Greeting, localeConstant, strGreeting); + + data << strGreeting; + data << uint32(questGreeting->EmoteDelay); + data << uint32(questGreeting->EmoteType); + } + else + { + data << Title; + data << uint32(eEmote._Delay); // player emote + data << uint32(eEmote._Emote); // NPC emote + } size_t count_pos = data.wpos(); - data << uint8 (_questMenu.GetMenuItemCount()); + data << uint8(0); uint32 count = 0; - for (uint32 iI = 0; iI < _questMenu.GetMenuItemCount(); ++iI) - { - QuestMenuItem const& qmi = _questMenu.GetItem(iI); - uint32 questID = qmi.QuestId; + for (uint32 i = 0; i < _questMenu.GetMenuItemCount(); ++i) + { + QuestMenuItem const& questMenuItem = _questMenu.GetItem(i); + + uint32 questID = questMenuItem.QuestId; if (Quest const* quest = sObjectMgr->GetQuestTemplate(questID)) { ++count; std::string title = quest->GetTitle(); - int32 locale = _session->GetSessionDbLocaleIndex(); - if (QuestLocale const* localeData = sObjectMgr->GetQuestLocale(questID)) - ObjectMgr::GetLocaleString(localeData->Title, locale, title); + LocaleConstant localeConstant = _session->GetSessionDbLocaleIndex(); + if (localeConstant != LOCALE_enUS) + if (QuestLocale const* questTemplateLocale = sObjectMgr->GetQuestLocale(questID)) + ObjectMgr::GetLocaleString(questTemplateLocale->Title, localeConstant, title); data << uint32(questID); - data << uint32(qmi.QuestIcon); + data << uint32(questMenuItem.QuestIcon); data << int32(quest->GetQuestLevel()); - data << uint32(quest->GetFlags()); // 3.3.3 quest flags - data << uint8(quest->IsRepeatable() && !quest->IsDailyOrWeekly() && !quest->IsMonthly()); // 3.3.3 icon changes: blue question mark or yellow exclamation mark + data << uint32(quest->GetFlags()); // 3.3.3 quest flags + data << uint8(quest->IsRepeatable() && !quest->IsDailyOrWeekly() && !quest->IsMonthly()); // 3.3.3 changes icon: blue question or yellow exclamation data << title; } } data.put(count_pos, count); _session->SendPacket(&data); - LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTGIVER_QUEST_LIST NPC {}", npcGUID.ToString()); + LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTGIVER_QUEST_LIST (QuestGiver: {})", guid.ToString()); } void PlayerMenu::SendQuestGiverStatus(uint8 questStatus, ObjectGuid npcGUID) const diff --git a/src/server/game/Entities/Creature/GossipDef.h b/src/server/game/Entities/Creature/GossipDef.h index f41a9de58..9b64b4672 100644 --- a/src/server/game/Entities/Creature/GossipDef.h +++ b/src/server/game/Entities/Creature/GossipDef.h @@ -20,6 +20,7 @@ #include "Common.h" #include "NPCHandler.h" +#include "Object.h" #include "QuestDef.h" class WorldSession; @@ -274,7 +275,7 @@ public: /*********************************************************/ void SendQuestGiverStatus(uint8 questStatus, ObjectGuid npcGUID) const; - void SendQuestGiverQuestList(QEmote const& eEmote, const std::string& Title, ObjectGuid npcGUID); + void SendQuestGiverQuestList(QEmote const& eEmote, std::string const& Title, ObjectGuid guid); void SendQuestQueryResponse(Quest const* quest) const; void SendQuestGiverQuestDetails(Quest const* quest, ObjectGuid npcGUID, bool activateAccept) const; diff --git a/src/server/game/Entities/Player/PlayerQuest.cpp b/src/server/game/Entities/Player/PlayerQuest.cpp index 83380d507..88c640c82 100644 --- a/src/server/game/Entities/Player/PlayerQuest.cpp +++ b/src/server/game/Entities/Player/PlayerQuest.cpp @@ -149,7 +149,7 @@ void Player::SendPreparedQuest(ObjectGuid guid) } } } - // multiple entries + // multiple entries else { QEmote qe; @@ -179,8 +179,8 @@ void Player::SendPreparedQuest(ObjectGuid guid) int loc_idx = GetSession()->GetSessionDbLocaleIndex(); if (loc_idx >= 0) - if (NpcTextLocale const* nl = sObjectMgr->GetNpcTextLocale(textid)) - ObjectMgr::GetLocaleString(nl->Text_0[0], loc_idx, title); + if (NpcTextLocale const* npcTextLocale = sObjectMgr->GetNpcTextLocale(textid)) + ObjectMgr::GetLocaleString(npcTextLocale->Text_0[0], loc_idx, title); } else { @@ -188,11 +188,12 @@ void Player::SendPreparedQuest(ObjectGuid guid) int loc_idx = GetSession()->GetSessionDbLocaleIndex(); if (loc_idx >= 0) - if (NpcTextLocale const* nl = sObjectMgr->GetNpcTextLocale(textid)) - ObjectMgr::GetLocaleString(nl->Text_1[0], loc_idx, title); + if (NpcTextLocale const* npcTextLocale = sObjectMgr->GetNpcTextLocale(textid)) + ObjectMgr::GetLocaleString(npcTextLocale->Text_1[0], loc_idx, title); } } } + PlayerTalkClass->SendQuestGiverQuestList(qe, title, guid); } } diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 9410012c1..b96c3a073 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -23,6 +23,7 @@ #include "Chat.h" #include "Common.h" #include "Config.h" +#include "Containers.h" #include "DatabaseEnv.h" #include "DisableMgr.h" #include "GameEventMgr.h" @@ -6111,6 +6112,132 @@ void ObjectMgr::LoadQuestAreaTriggers() LOG_INFO("server.loading", " "); } +QuestGreeting const* ObjectMgr::GetQuestGreeting(TypeID type, uint32 id) const +{ + uint32 typeIndex; + if (type == TYPEID_UNIT) + typeIndex = 0; + else if (type == TYPEID_GAMEOBJECT) + typeIndex = 1; + else + return nullptr; + + return Acore::Containers::MapGetValuePtr(_questGreetingStore[typeIndex], id); +} + +void ObjectMgr::LoadQuestGreetings() +{ + uint32 oldMSTime = getMSTime(); + + for (std::size_t i = 0; i < _questGreetingStore.size(); ++i) + _questGreetingStore[i].clear(); + + // 0 1 2 3 4 + QueryResult result = WorldDatabase.Query("SELECT ID, Type, GreetEmoteType, GreetEmoteDelay, Greeting FROM quest_greeting"); + if (!result) + { + LOG_INFO("server.loading", ">> Loaded 0 quest greetings. DB table `quest_greeting` is empty."); + return; + } + + uint32 count = 0; + + do + { + Field* fields = result->Fetch(); + + uint32 id = fields[0].Get(); + uint8 type = fields[1].Get(); + switch (type) + { + case 0: // Creature + if (!sObjectMgr->GetCreatureTemplate(id)) + { + LOG_ERROR("sql.sql", "Table `quest_greeting`: creature template entry {} does not exist.", id); + continue; + } + break; + case 1: // GameObject + if (!sObjectMgr->GetGameObjectTemplate(id)) + { + LOG_ERROR("sql.sql", "Table `quest_greeting`: gameobject template entry {} does not exist.", id); + continue; + } + break; + default: + continue; + } + + uint16 greetEmoteType = fields[2].Get(); + uint32 greetEmoteDelay = fields[3].Get(); + std::string greeting = fields[4].Get(); + + _questGreetingStore[type].emplace(std::piecewise_construct, std::forward_as_tuple(id), std::forward_as_tuple(greetEmoteType, greetEmoteDelay, std::move(greeting))); + + ++count; + } + while (result->NextRow()); + + LOG_INFO("server.loading", ">> Loaded {} quest_greeting in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); +} + +void ObjectMgr::LoadQuestGreetingsLocales() +{ + uint32 oldMSTime = getMSTime(); + + _questGreetingLocaleStore.clear(); + + // 0 1 2 3 + QueryResult result = WorldDatabase.Query("SELECT ID, Type, Locale, Greeting FROM quest_greeting_locale"); + if (!result) + { + LOG_INFO("server.loading", ">> Loaded 0 quest_greeting locales. DB table `quest_greeting_locale` is empty."); + return; + } + + uint32 count = 0; + + do + { + Field* fields = result->Fetch(); + + uint32 id = fields[0].Get(); + uint8 type = fields[1].Get(); + switch (type) + { + case 0: // Creature + if (!sObjectMgr->GetCreatureTemplate(id)) + { + LOG_ERROR("sql.sql", "Table `quest_greeting_locale`: creature template entry {} does not exist.", id); + continue; + } + break; + case 1: // GameObject + if (!sObjectMgr->GetGameObjectTemplate(id)) + { + LOG_ERROR("sql.sql", "Table `quest_greeting_locale`: gameobject template entry {} does not exist.", id); + continue; + } + break; + default: + continue; + } + + std::string localeName = fields[2].Get(); + + LocaleConstant locale = GetLocaleByName(localeName); + if (locale == LOCALE_enUS) + continue; + + QuestGreetingLocale& data = _questGreetingLocaleStore[MAKE_PAIR32(type, id)]; + AddLocaleString(fields[3].Get(), locale, data.Greeting); + + ++count; + } while (result->NextRow()); + + LOG_INFO("server.loading", ">> Loaded {} quest greeting locale strings in {} ms", (uint32)_questGreetingLocaleStore.size(), GetMSTimeDiffToNow(oldMSTime)); +} + void ObjectMgr::LoadQuestOfferRewardLocale() { uint32 oldMSTime = getMSTime(); diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index f27547d68..1b5435827 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -36,6 +36,7 @@ #include "QuestDef.h" #include "TemporarySummon.h" #include "VehicleDefines.h" +#include "GossipDef.h" #include #include #include @@ -585,6 +586,24 @@ struct PointOfInterest std::string Name; }; +struct QuestGreeting +{ + uint16 EmoteType; + uint32 EmoteDelay; + std::string Text; + + QuestGreeting() : EmoteType(0), EmoteDelay(0) { } + QuestGreeting(uint16 emoteType, uint32 emoteDelay, std::string text) + : EmoteType(emoteType), EmoteDelay(emoteDelay), Text(std::move(text)) { } +}; + +struct QuestGreetingLocale +{ + std::vector Greeting; +}; + +typedef std::unordered_map QuestGreetingLocaleContainer; + struct GossipMenuItems { uint32 MenuID; @@ -644,6 +663,8 @@ struct QuestPOI typedef std::vector QuestPOIVector; typedef std::unordered_map QuestPOIContainer; +typedef std::array, 2> QuestGreetingContainer; + typedef std::unordered_map CacheVendorItemContainer; typedef std::unordered_map CacheTrainerSpellContainer; @@ -1015,6 +1036,7 @@ public: void LoadPageTextLocales(); void LoadGossipMenuItemsLocales(); void LoadPointOfInterestLocales(); + void LoadQuestGreetingsLocales(); void LoadInstanceTemplate(); void LoadInstanceEncounters(); void LoadMailLevelRewards(); @@ -1027,6 +1049,7 @@ public: void LoadAreaTriggerTeleports(); void LoadAccessRequirements(); void LoadQuestAreaTriggers(); + void LoadQuestGreetings(); void LoadAreaTriggerScripts(); void LoadTavernAreaTriggers(); void LoadGameObjectForQuests(); @@ -1225,6 +1248,12 @@ public: if (itr == _pointOfInterestLocaleStore.end()) return nullptr; return &itr->second; } + [[nodiscard]] QuestGreetingLocale const* GetQuestGreetingLocale(TypeID type, uint32 id) const + { + QuestGreetingLocaleContainer::const_iterator itr = _questGreetingLocaleStore.find(MAKE_PAIR32(type, id)); + if (itr == _questGreetingLocaleStore.end()) return nullptr; + return &itr->second; + } [[nodiscard]] QuestOfferRewardLocale const* GetQuestOfferRewardLocale(uint32 entry) const { auto itr = _questOfferRewardLocaleStore.find(entry); @@ -1243,6 +1272,8 @@ public: if (itr == _npcTextLocaleStore.end()) return nullptr; return &itr->second; } + QuestGreeting const* GetQuestGreeting(TypeID type, uint32 id) const; + GameObjectData& NewGOData(ObjectGuid::LowType guid) { return _gameObjectDataStore[guid]; } void DeleteGOData(ObjectGuid::LowType guid); @@ -1409,6 +1440,7 @@ private: QuestAreaTriggerContainer _questAreaTriggerStore; TavernAreaTriggerContainer _tavernAreaTriggerStore; GossipTextContainer _gossipTextStore; + QuestGreetingContainer _questGreetingStore; AreaTriggerContainer _areaTriggerStore; AreaTriggerTeleportContainer _areaTriggerTeleportStore; AreaTriggerScriptContainer _areaTriggerScriptStore; @@ -1521,6 +1553,7 @@ private: AcoreStringContainer _acoreStringStore; GossipMenuItemsLocaleContainer _gossipMenuItemsLocaleStore; PointOfInterestLocaleContainer _pointOfInterestLocaleStore; + QuestGreetingLocaleContainer _questGreetingLocaleStore; CacheVendorItemContainer _cacheVendorItemStore; CacheTrainerSpellContainer _cacheTrainerSpellStore; diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index bc0aefb5f..692fcad86 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1737,6 +1737,11 @@ void World::SetInitialWorldSettings() LOG_INFO("server.loading", "Loading Quests Starters and Enders..."); sObjectMgr->LoadQuestStartersAndEnders(); // must be after quest load + LOG_INFO("server.loading", "Loading Quest Greetings..."); + sObjectMgr->LoadQuestGreetings(); // must be loaded after creature_template, gameobject_template tables + LOG_INFO("server.loading", "Loading Quest Greeting Locales..."); + sObjectMgr->LoadQuestGreetingsLocales(); // must be loaded after creature_template, gameobject_template tables + LOG_INFO("server.loading", "Loading Quest Money Rewards..."); sObjectMgr->LoadQuestMoneyRewards(); diff --git a/src/server/scripts/Commands/cs_reload.cpp b/src/server/scripts/Commands/cs_reload.cpp index 7f5ab230c..8cb93cf28 100644 --- a/src/server/scripts/Commands/cs_reload.cpp +++ b/src/server/scripts/Commands/cs_reload.cpp @@ -133,6 +133,8 @@ public: { "pickpocketing_loot_template", HandleReloadLootTemplatesPickpocketingCommand, SEC_ADMINISTRATOR, Console::Yes }, { "points_of_interest", HandleReloadPointsOfInterestCommand, SEC_ADMINISTRATOR, Console::Yes }, { "prospecting_loot_template", HandleReloadLootTemplatesProspectingCommand, SEC_ADMINISTRATOR, Console::Yes }, + { "quest_greeting", HandleReloadQuestGreetingCommand, SEC_ADMINISTRATOR, Console::Yes }, + { "quest_greeting_locale", HandleReloadLocalesQuestGreetingCommand, SEC_ADMINISTRATOR, Console::Yes }, { "quest_poi", HandleReloadQuestPOICommand, SEC_ADMINISTRATOR, Console::Yes }, { "quest_template", HandleReloadQuestTemplateCommand, SEC_ADMINISTRATOR, Console::Yes }, { "reference_loot_template", HandleReloadLootTemplatesReferenceCommand, SEC_ADMINISTRATOR, Console::Yes }, @@ -254,9 +256,11 @@ public: static bool HandleReloadAllQuestCommand(ChatHandler* handler) { + HandleReloadQuestGreetingCommand(handler); HandleReloadQuestAreaTriggersCommand(handler); HandleReloadQuestPOICommand(handler); HandleReloadQuestTemplateCommand(handler); + HandleReloadLocalesQuestGreetingCommand(handler); LOG_INFO("server.loading", "Re-Loading Quests Relations..."); sObjectMgr->LoadQuestStartersAndEnders(); @@ -529,6 +533,22 @@ public: return true; } + static bool HandleReloadQuestGreetingCommand(ChatHandler* handler) + { + LOG_INFO("server.loading", "Re-Loading Quest Greeting ..."); + sObjectMgr->LoadQuestGreetings(); + handler->SendGlobalGMSysMessage("DB table `quest_greeting` reloaded."); + return true; + } + + static bool HandleReloadLocalesQuestGreetingCommand(ChatHandler* handler) + { + LOG_INFO("server.loading", "Re-Loading Quest Greeting locales..."); + sObjectMgr->LoadQuestGreetingsLocales(); + handler->SendGlobalGMSysMessage("DB table `quest_greeting_locale` reloaded."); + return true; + } + static bool HandleReloadQuestTemplateCommand(ChatHandler* handler) { LOG_INFO("server.loading", "Re-Loading Quest Templates...");