/* * This file is part of the AzerothCore 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 "BattlegroundMgr.h" #include "Chat.h" #include "GossipDef.h" #include "Language.h" #include "ObjectMgr.h" #include "OutdoorPvPMgr.h" #include "Pet.h" #include "Player.h" #include "WorldSession.h" /*********************************************************/ /*** GOSSIP SYSTEM ***/ /*********************************************************/ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool showQuests /*= false*/) { PlayerMenu* menu = PlayerTalkClass; menu->ClearMenus(); menu->GetGossipMenu().SetMenuId(menuId); GossipMenuItemsMapBounds menuItemBounds = sObjectMgr->GetGossipMenuItemsMapBounds(menuId); // if default menuId and no menu options exist for this, use options from default options if (menuItemBounds.first == menuItemBounds.second && menuId == GetDefaultGossipMenuForSource(source)) menuItemBounds = sObjectMgr->GetGossipMenuItemsMapBounds(0); uint32 npcflags = 0; if (source->IsCreature()) { npcflags = source->ToUnit()->GetNpcFlags(); if (showQuests && npcflags & UNIT_NPC_FLAG_QUESTGIVER) PrepareQuestMenu(source->GetGUID()); } else if (source->IsGameObject()) if (showQuests && source->ToGameObject()->GetGoType() == GAMEOBJECT_TYPE_QUESTGIVER) PrepareQuestMenu(source->GetGUID()); for (GossipMenuItemsContainer::const_iterator itr = menuItemBounds.first; itr != menuItemBounds.second; ++itr) { bool canTalk = true; if (!sConditionMgr->IsObjectMeetToConditions(this, source, itr->second.Conditions)) continue; if (Creature* creature = source->ToCreature()) { if (!(itr->second.OptionNpcFlag & npcflags)) continue; switch (itr->second.OptionType) { case GOSSIP_OPTION_ARMORER: canTalk = false; // added in special mode break; case GOSSIP_OPTION_SPIRITHEALER: if (!isDead()) canTalk = false; break; case GOSSIP_OPTION_VENDOR: { if (!creature->isVendorWithIconSpeak()) { VendorItemData const* vendorItems = itr->second.ActionMenuID ? sObjectMgr->GetNpcVendorItemList(itr->second.ActionMenuID) : creature->GetVendorItems(); if (!vendorItems || vendorItems->Empty()) { LOG_ERROR("sql.sql", "Creature {} have UNIT_NPC_FLAG_VENDOR but have empty trading item list.", creature->GetGUID().ToString()); canTalk = false; } break; } break; } case GOSSIP_OPTION_LEARNDUALSPEC: case GOSSIP_OPTION_DUALSPEC_INFO: if (!(GetSpecsCount() == 1 && creature->CanResetTalents(this) && !(GetLevel() < sWorld->getIntConfig(CONFIG_MIN_DUALSPEC_LEVEL)))) canTalk = false; break; case GOSSIP_OPTION_UNLEARNTALENTS: if (!creature->CanResetTalents(this)) canTalk = false; break; case GOSSIP_OPTION_UNLEARNPETTALENTS: if (!GetPet() || GetPet()->getPetType() != HUNTER_PET || GetPet()->m_spells.size() <= 1 || !creature->CanResetTalents(this)) canTalk = false; break; case GOSSIP_OPTION_TAXIVENDOR: if (GetSession()->SendLearnNewTaxiNode(creature)) return; break; case GOSSIP_OPTION_BATTLEFIELD: if (!creature->isCanInteractWithBattleMaster(this, false)) canTalk = false; break; case GOSSIP_OPTION_STABLEPET: if (!IsClass(CLASS_HUNTER, CLASS_CONTEXT_PET)) canTalk = false; break; case GOSSIP_OPTION_QUESTGIVER: canTalk = false; break; case GOSSIP_OPTION_TRAINER: { Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(creature->GetEntry()); if (!trainer || !trainer->IsTrainerValidForPlayer(this)) { LOG_ERROR("sql.sql", "GOSSIP_OPTION_TRAINER:: Player {} (GUID: {}) requested wrong gossip menu: {} at Creature: {} (Entry: {})", GetName(), GetGUID().GetCounter(), menu->GetGossipMenu().GetMenuId(), creature->GetName(), creature->GetEntry()); canTalk = false; } } [[fallthrough]]; case GOSSIP_OPTION_GOSSIP: if (creature->isVendorWithIconSpeak()) { VendorItemData const* vendorItems = creature->GetVendorItems(); if (!vendorItems || vendorItems->Empty()) { canTalk = false; } } break; case GOSSIP_OPTION_SPIRITGUIDE: case GOSSIP_OPTION_INNKEEPER: case GOSSIP_OPTION_BANKER: case GOSSIP_OPTION_PETITIONER: case GOSSIP_OPTION_TABARDDESIGNER: case GOSSIP_OPTION_AUCTIONEER: break; // no checks case GOSSIP_OPTION_OUTDOORPVP: if (!sOutdoorPvPMgr->CanTalkTo(this, creature, itr->second)) canTalk = false; break; default: LOG_ERROR("sql.sql", "Creature entry {} has unknown OptionType {} for menu {}", creature->GetEntry(), itr->second.OptionType, itr->second.MenuID); canTalk = false; break; } } else if (GameObject* go = source->ToGameObject()) { switch (itr->second.OptionType) { case GOSSIP_OPTION_GOSSIP: if (go->GetGoType() != GAMEOBJECT_TYPE_QUESTGIVER && go->GetGoType() != GAMEOBJECT_TYPE_GOOBER) canTalk = false; break; default: canTalk = false; break; } } if (canTalk) { // using gossip_menu_option texts by default std::string strOptionText = itr->second.OptionText; std::string strBoxText = itr->second.BoxText; // search in broadcast_text and broadcast_text_locale BroadcastText const* optionBroadcastText = sObjectMgr->GetBroadcastText(itr->second.OptionBroadcastTextID); BroadcastText const* boxBroadcastText = sObjectMgr->GetBroadcastText(itr->second.BoxBroadcastTextID); LocaleConstant locale = GetSession()->GetSessionDbLocaleIndex(); if (optionBroadcastText) ObjectMgr::GetLocaleString(getGender() == GENDER_MALE ? optionBroadcastText->MaleText : optionBroadcastText->FemaleText, locale, strOptionText); if (boxBroadcastText) ObjectMgr::GetLocaleString(getGender() == GENDER_MALE ? boxBroadcastText->MaleText : boxBroadcastText->FemaleText, locale, strBoxText); // if the language is not default and the texts weren't found, maybe they're in gossip_menu_option_locale table if (locale != DEFAULT_LOCALE) { if (!optionBroadcastText) { /// Find localizations from database. if (GossipMenuItemsLocale const* gossipMenuLocale = sObjectMgr->GetGossipMenuItemsLocale(MAKE_PAIR32(menuId, itr->second.OptionID))) ObjectMgr::GetLocaleString(gossipMenuLocale->OptionText, locale, strOptionText); } if (!boxBroadcastText) { /// Find localizations from database. if (GossipMenuItemsLocale const* gossipMenuLocale = sObjectMgr->GetGossipMenuItemsLocale(MAKE_PAIR32(menuId, itr->second.OptionID))) ObjectMgr::GetLocaleString(gossipMenuLocale->BoxText, locale, strBoxText); } } menu->GetGossipMenu().AddMenuItem(itr->second.OptionID, itr->second.OptionIcon, strOptionText, 0, itr->second.OptionType, strBoxText, itr->second.BoxMoney, itr->second.BoxCoded); menu->GetGossipMenu().AddGossipMenuItemData(itr->second.OptionID, itr->second.ActionMenuID, itr->second.ActionPoiID); } } if (sWorld->getIntConfig(CONFIG_INSTANT_TAXI) == 2 && npcflags & UNIT_NPC_FLAG_FLIGHTMASTER) menu->GetGossipMenu().AddMenuItem(-1, GOSSIP_ICON_INTERACT_1, GetSession()->GetAcoreString(LANG_TOGGLE_INSTANT_FLIGHT), 0, GOSSIP_ACTION_TOGGLE_INSTANT_FLIGHT, "", 0, false); // instant flight toggle option } void Player::SendPreparedGossip(WorldObject* source) { if (!source) return; if (source->IsCreature()) { // in case no gossip flag and quest menu not empty, open quest menu (client expect gossip menu with this flag) if (!source->ToCreature()->HasNpcFlag(UNIT_NPC_FLAG_GOSSIP) && !PlayerTalkClass->GetQuestMenu().Empty()) { SendPreparedQuest(source->GetGUID()); return; } } else if (source->IsGameObject()) { // probably need to find a better way here if (!PlayerTalkClass->GetGossipMenu().GetMenuId() && !PlayerTalkClass->GetQuestMenu().Empty()) { SendPreparedQuest(source->GetGUID()); return; } } // in case non empty gossip menu (that not included quests list size) show it // (quest entries from quest menu will be included in list) uint32 textId = GetGossipTextId(source); if (uint32 menuId = PlayerTalkClass->GetGossipMenu().GetMenuId()) textId = GetGossipTextId(menuId, source); PlayerTalkClass->SendGossipMenu(textId, source->GetGUID()); } void Player::OnGossipSelect(WorldObject* source, uint32 gossipListId, uint32 menuId) { GossipMenu& gossipMenu = PlayerTalkClass->GetGossipMenu(); // if not same, then something funky is going on if (menuId != gossipMenu.GetMenuId()) return; GossipMenuItem const* item = gossipMenu.GetItem(gossipListId); if (!item) return; uint32 gossipOptionId = item->OptionType; ObjectGuid guid = source->GetGUID(); if (sWorld->getIntConfig(CONFIG_INSTANT_TAXI) == 2 && source->IsCreature()) { if (gossipOptionId == GOSSIP_ACTION_TOGGLE_INSTANT_FLIGHT && source->ToUnit()->GetNpcFlags() & UNIT_NPC_FLAG_FLIGHTMASTER) { ToggleInstantFlight(); if (m_isInstantFlightOn) ChatHandler(GetSession()).SendNotification(LANG_INSTANT_FLIGHT_ON); else ChatHandler(GetSession()).SendNotification(LANG_INSTANT_FLIGHT_OFF); PlayerTalkClass->SendCloseGossip(); return; } } if (source->IsGameObject()) { if (gossipOptionId > GOSSIP_OPTION_QUESTGIVER) { LOG_ERROR("entities.player", "Player guid {} request invalid gossip option for GameObject entry {}", GetGUID().ToString(), source->GetEntry()); return; } } GossipMenuItemData const* menuItemData = gossipMenu.GetItemData(gossipListId); if (!menuItemData) return; int32 cost = int32(item->BoxMoney); if (!HasEnoughMoney(cost)) { SendBuyError(BUY_ERR_NOT_ENOUGHT_MONEY, 0, 0, 0); PlayerTalkClass->SendCloseGossip(); return; } switch (gossipOptionId) { case GOSSIP_OPTION_GOSSIP: case GOSSIP_OPTION_DUALSPEC_INFO: { if (menuItemData->GossipActionPoi) PlayerTalkClass->SendPointOfInterest(menuItemData->GossipActionPoi); if (menuItemData->GossipActionMenuId) { PrepareGossipMenu(source, menuItemData->GossipActionMenuId); SendPreparedGossip(source); } break; } case GOSSIP_OPTION_OUTDOORPVP: sOutdoorPvPMgr->HandleGossipOption(this, source->ToCreature(), gossipListId); break; case GOSSIP_OPTION_SPIRITHEALER: if (isDead()) source->ToCreature()->CastSpell(source->ToCreature(), 17251, true, nullptr, nullptr, GetGUID()); break; case GOSSIP_OPTION_QUESTGIVER: PrepareQuestMenu(guid); SendPreparedQuest(guid); break; case GOSSIP_OPTION_VENDOR: case GOSSIP_OPTION_ARMORER: GetSession()->SendListInventory(guid, menuItemData->GossipActionMenuId); break; case GOSSIP_OPTION_STABLEPET: GetSession()->SendStablePet(guid); break; case GOSSIP_OPTION_TRAINER: GetSession()->SendTrainerList(source->ToCreature()); break; case GOSSIP_OPTION_LEARNDUALSPEC: if (GetSpecsCount() == 1 && GetLevel() >= sWorld->getIntConfig(CONFIG_MIN_DUALSPEC_LEVEL)) { // Cast spells that teach dual spec // Both are also ImplicitTarget self and must be cast by player CastSpell(this, 63680, true, nullptr, nullptr, GetGUID()); CastSpell(this, 63624, true, nullptr, nullptr, GetGUID()); PrepareGossipMenu(source, menuItemData->GossipActionMenuId); SendPreparedGossip(source); } break; case GOSSIP_OPTION_UNLEARNTALENTS: PlayerTalkClass->SendCloseGossip(); SendTalentWipeConfirm(guid); break; case GOSSIP_OPTION_UNLEARNPETTALENTS: PlayerTalkClass->SendCloseGossip(); ResetPetTalents(); break; case GOSSIP_OPTION_TAXIVENDOR: GetSession()->SendTaxiMenu(source->ToCreature()); break; case GOSSIP_OPTION_INNKEEPER: PlayerTalkClass->SendCloseGossip(); SetBindPoint(guid); break; case GOSSIP_OPTION_BANKER: GetSession()->SendShowBank(guid); break; case GOSSIP_OPTION_PETITIONER: PlayerTalkClass->SendCloseGossip(); GetSession()->SendPetitionShowList(guid); break; case GOSSIP_OPTION_TABARDDESIGNER: PlayerTalkClass->SendCloseGossip(); GetSession()->SendTabardVendorActivate(guid); break; case GOSSIP_OPTION_AUCTIONEER: GetSession()->SendAuctionHello(guid, source->ToCreature()); break; case GOSSIP_OPTION_SPIRITGUIDE: PrepareGossipMenu(source); SendPreparedGossip(source); break; case GOSSIP_OPTION_BATTLEFIELD: { BattlegroundTypeId bgTypeId = sBattlegroundMgr->GetBattleMasterBG(source->GetEntry()); if (bgTypeId == BATTLEGROUND_TYPE_NONE) { LOG_ERROR("entities.player", "A user ({}) requested battlegroundlist from a npc who is no battlemaster", GetGUID().ToString()); return; } GetSession()->SendBattleGroundList(guid, bgTypeId); break; } } ModifyMoney(-cost); } uint32 Player::GetGossipTextId(WorldObject* source) { if (!source) return DEFAULT_GOSSIP_MESSAGE; return GetGossipTextId(GetDefaultGossipMenuForSource(source), source); } uint32 Player::GetGossipTextId(uint32 menuId, WorldObject* source) { uint32 textId = DEFAULT_GOSSIP_MESSAGE; if (!menuId) return textId; GossipMenusMapBounds menuBounds = sObjectMgr->GetGossipMenusMapBounds(menuId); for (GossipMenusContainer::const_iterator itr = menuBounds.first; itr != menuBounds.second; ++itr) { if (sConditionMgr->IsObjectMeetToConditions(this, source, itr->second.Conditions)) textId = itr->second.TextID; } return textId; } uint32 Player::GetDefaultGossipMenuForSource(WorldObject* source) { switch (source->GetTypeId()) { case TYPEID_UNIT: return source->ToCreature()->GetCreatureTemplate()->GossipMenuId; case TYPEID_GAMEOBJECT: return source->ToGameObject()->GetGOInfo()->GetGossipMenuId(); default: break; } return 0; } void Player::ToggleInstantFlight() { m_isInstantFlightOn = !m_isInstantFlightOn; }